Merge pull request #10537 from osmandapp/master

update test branch
This commit is contained in:
Hardy 2021-01-06 21:06:52 +01:00 committed by GitHub
commit 1bf49a380d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
87 changed files with 3125 additions and 445 deletions

View file

@ -2544,7 +2544,7 @@ public class GPXUtilities {
if (maxlat == null) {
maxlat = parser.getAttributeValue("", "maxLat");
}
if (maxlat == null) {
if (maxlon == null) {
maxlon = parser.getAttributeValue("", "maxLon");
}

View file

@ -1,11 +1,5 @@
package net.osmand.binary;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import net.osmand.PlatformUtil;
import net.osmand.binary.BinaryMapAddressReaderAdapter.AddressRegion;
import net.osmand.binary.BinaryMapAddressReaderAdapter.CitiesBlock;
@ -29,6 +23,12 @@ import net.osmand.binary.OsmandIndex.TransportPart;
import org.apache.commons.logging.Log;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
public class CachedOsmandIndexes {
private OsmAndStoredIndex storedIndex;
@ -46,10 +46,12 @@ public class CachedOsmandIndexes {
storedIndexBuilder.setDateCreated(System.currentTimeMillis());
if (storedIndex != null) {
for (FileIndex ex : storedIndex.getFileIndexList()) {
if (!ex.getFileName().equals(f.getName())) {
storedIndexBuilder.addFileIndex(ex);
}
}
}
}
FileIndex.Builder fileIndex = OsmandIndex.FileIndex.newBuilder();
long d = reader.getDateCreated();
@ -172,10 +174,10 @@ public class CachedOsmandIndexes {
routing.addSubregions(rpart);
}
public BinaryMapIndexReader getReader(File f) throws IOException {
public BinaryMapIndexReader getReader(File f, boolean useStoredIndex) throws IOException {
RandomAccessFile mf = new RandomAccessFile(f.getPath(), "r");
FileIndex found = null;
if (storedIndex != null) {
if (storedIndex != null && useStoredIndex) {
for (int i = 0; i < storedIndex.getFileIndexCount(); i++) {
FileIndex fi = storedIndex.getFileIndex(i);
if (f.length() == fi.getSize() && f.getName().equals(fi.getFileName())) {

View file

@ -34,6 +34,7 @@ import org.json.JSONObject;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
@ -41,6 +42,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@ -68,6 +70,9 @@ public class SearchUICore {
private static boolean debugMode = false;
private static final Set<String> FILTER_DUPLICATE_POI_SUBTYPE = new TreeSet<String>(
Arrays.asList("building", "internet_access_yes"));
public SearchUICore(MapPoiTypes poiTypes, String locale, boolean transliterate) {
this.poiTypes = poiTypes;
taskQueue = new LinkedBlockingQueue<Runnable>();
@ -244,12 +249,17 @@ public class SearchUICore {
String type2 = a2.getType().getKeyName();
String subType1 = a1.getSubType();
String subType2 = a2.getSubType();
if(a1.getId().longValue() == a2.getId().longValue() && (subType1.equals("building") || subType2.equals("building"))) {
boolean isEqualId = a1.getId().longValue() == a2.getId().longValue();
if (isEqualId && (FILTER_DUPLICATE_POI_SUBTYPE.contains(subType1)
|| FILTER_DUPLICATE_POI_SUBTYPE.contains(subType2))) {
return true;
}
if (!type1.equals(type2)) {
} else if (!type1.equals(type2)) {
return false;
}
if (type1.equals("natural")) {
similarityRadius = 50000;
} else if (subType1.equals(subType2)) {
@ -987,15 +997,29 @@ public class SearchUICore {
// here 2 points are amenity
Amenity a1 = (Amenity) o1.object;
Amenity a2 = (Amenity) o2.object;
String type1 = a1.getType().getKeyName();
String type2 = a2.getType().getKeyName();
int cmp = c.collator.compare(type1, type2);
String subType1 = a1.getSubType() == null ? "" : a1.getSubType();
String subType2 = a2.getSubType() == null ? "" : a2.getSubType();
int cmp = 0;
if (FILTER_DUPLICATE_POI_SUBTYPE.contains(subType1)) {
cmp = 1;
} else if (FILTER_DUPLICATE_POI_SUBTYPE.contains(subType2)) {
cmp = -1;
}
if (cmp != 0) {
return cmp;
}
cmp = c.collator.compare(type1, type2);
if (cmp != 0) {
return cmp;
}
String subType1 = a1.getSubType() == null ? "" : a1.getSubType();
String subType2 = a2.getSubType() == null ? "" : a2.getSubType();
cmp = c.collator.compare(subType1, subType2);
if (cmp != 0) {
return cmp;

View file

@ -650,7 +650,7 @@ public class SearchCoreFactory {
if (p.hasObjectType(ObjectType.POI_TYPE)) {
return -1;
}
if (p.getUnknownWordToSearch().length() >= FIRST_WORD_MIN_LENGTH || p.getRadiusLevel() > 1) {
if (p.getUnknownWordToSearch().length() >= FIRST_WORD_MIN_LENGTH || p.isFirstUnknownSearchWordComplete()) {
return SEARCH_AMENITY_BY_NAME_API_PRIORITY_IF_3_CHAR;
}
return -1;

View file

@ -63,6 +63,7 @@
<meta-data android:name="com.sec.android.multiwindow.MINIMUM_SIZE_H" android:resource="@dimen/app_minimumsize_h" android:value="" />
<meta-data android:name="com.sec.minimode.icon.portrait.normal" android:resource="@mipmap/icon" android:value="" />
<meta-data android:name="com.sec.minimode.icon.landscape.normal" android:resource="@mipmap/icon" android:value="" />
<meta-data android:name="android.webkit.WebView.MetricsOptOut" android:value="true" />
<activity android:name="net.osmand.plus.activities.HelpActivity" />
<activity android:name="net.osmand.plus.activities.ExitActivity" />

View file

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:minHeight="@dimen/setting_list_item_group_height"
android:paddingLeft="@dimen/content_padding"
android:paddingRight="@dimen/content_padding"
android:paddingEnd="@dimen/content_padding"
android:paddingStart="@dimen/content_padding">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/icon"
android:layout_width="@dimen/standard_icon_size"
android:layout_height="@dimen/standard_icon_size"
android:layout_gravity="center_vertical"
android:layout_marginEnd="@dimen/content_padding"
android:layout_marginRight="@dimen/content_padding"
tools:src="@drawable/list_destination"/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:layout_marginLeft="@dimen/content_padding"
android:layout_marginStart="@dimen/content_padding"
android:orientation="vertical">
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:textColorPrimary"
android:textSize="@dimen/default_list_text_size"
app:typeface="@string/font_roboto_regular"
tools:text="Some title" />
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="2"
android:textColor="?android:textColorSecondary"
android:textSize="@dimen/default_desc_text_size"
app:typeface="@string/font_roboto_regular"
tools:text="Some description" />
</LinearLayout>
</LinearLayout>

View file

@ -9,7 +9,7 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="63dp"
android:layout_height="@dimen/bottom_sheet_large_list_item_height"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="@dimen/list_content_padding"
@ -71,8 +71,8 @@
android:id="@+id/divider_bottom"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="64dp"
android:layout_marginLeft="64dp"
android:layout_marginStart="@dimen/bottom_sheet_divider_margin_start"
android:layout_marginLeft="@dimen/bottom_sheet_divider_margin_start"
android:background="?attr/divider_color" />
</LinearLayout>

View file

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="@dimen/bottom_sheet_large_list_item_height"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="?attr/selectableItemBackground"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="@dimen/bottom_sheet_large_list_item_height"
android:gravity="center_vertical"
android:orientation="horizontal">
<LinearLayout
android:id="@+id/main_item_part"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:paddingLeft="@dimen/content_padding"
android:paddingStart="@dimen/content_padding"
android:paddingRight="0dp"
android:paddingEnd="0dp"
android:layout_weight="1"
android:gravity="center_vertical">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/bottom_sheet_icon_margin"
android:layout_marginRight="@dimen/bottom_sheet_icon_margin"
tools:src="@drawable/ic_action_coordinates_latitude"
tools:tint="?attr/default_icon_color" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/content_padding"
android:layout_marginRight="@dimen/content_padding"
android:layout_weight="1"
android:gravity="center_vertical"
android:orientation="vertical">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="2"
android:textAppearance="@style/TextAppearance.ListItemTitle"
android:textColor="?android:textColorPrimary"
tools:text="Item Title" />
<TextView
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:textColorSecondary"
tools:text="Item additional desription" />
</LinearLayout>
<androidx.appcompat.widget.AppCompatRadioButton
android:id="@+id/compound_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@null"
android:clickable="false"
android:focusable="false"
android:saveEnabled="false"
android:layout_marginRight="@dimen/content_padding"
android:layout_marginEnd="@dimen/content_padding" />
</LinearLayout>
<View
android:layout_width="1dp"
android:layout_height="36dp"
android:background="?attr/divider_color_basic"/>
<LinearLayout
android:id="@+id/eng_button"
android:paddingLeft="@dimen/content_padding"
android:paddingRight="@dimen/content_padding"
android:layout_width="wrap_content"
android:layout_height="match_parent">
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="@dimen/standard_icon_size"
android:layout_height="@dimen/standard_icon_size"
android:layout_gravity="center_vertical"
app:srcCompat="@drawable/ic_action_settings"
android:tint="?attr/default_icon_color" />
</LinearLayout>
</LinearLayout>
<View
android:id="@+id/divider_bottom"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="@dimen/bottom_sheet_divider_margin_start"
android:layout_marginLeft="@dimen/bottom_sheet_divider_margin_start"
android:background="?attr/divider_color" />
</LinearLayout>

View file

@ -14,7 +14,7 @@
app:theme="?attr/toolbar_theme"
android:background="?attr/pstsTabBackground"/>
<WebView
<net.osmand.plus.widgets.WebViewEx
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

View file

@ -63,7 +63,8 @@
android:layout_width="match_parent"
android:layout_weight="1"
android:layout_height="0dp">
<WebView
<net.osmand.plus.widgets.WebViewEx
android:id="@+id/content_web_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

View file

@ -4,7 +4,8 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<WebView android:id="@+id/webView"
<net.osmand.plus.widgets.WebViewEx
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>

View file

@ -4,12 +4,12 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<WebView
<net.osmand.plus.widgets.WebViewEx
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent">
</WebView>
</net.osmand.plus.widgets.WebViewEx>
<include layout="@layout/mapillary_no_internet"
android:id="@+id/mapillaryNoInternetLayout"

View file

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/list_background_color"
android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include layout="@layout/preference_toolbar_with_action_button" />
</com.google.android.material.appbar.AppBarLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<LinearLayout
android:id="@+id/segments_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
</ScrollView>
<include
layout="@layout/bottom_buttons"
android:layout_width="match_parent"
android:layout_height="@dimen/dialog_button_ex_height" />
</LinearLayout>

View file

@ -0,0 +1,186 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:osmand="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<FrameLayout
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/bottom_sheet_list_item_height"
android:paddingLeft="@dimen/content_padding"
android:paddingTop="@dimen/content_padding_half"
android:paddingRight="@dimen/content_padding"
android:paddingBottom="@dimen/content_padding_half"
tools:visibility="visible"
android:visibility="gone">
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:letterSpacing="@dimen/description_letter_spacing"
android:singleLine="true"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/default_list_text_size"
osmand:typeface="@string/font_roboto_medium"
tools:visibility="visible"
android:visibility="gone"
tools:text="Title" />
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:letterSpacing="@dimen/description_letter_spacing"
android:singleLine="true"
android:textColor="?android:attr/textColorSecondary"
android:textSize="@dimen/default_list_text_size"
osmand:typeface="@string/font_roboto_regular"
tools:visibility="visible"
android:visibility="gone"
tools:text="Subtitle" />
</FrameLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/selection_menu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:orientation="horizontal"
android:paddingStart="@dimen/content_padding"
android:paddingLeft="@dimen/content_padding"
android:paddingEnd="@dimen/content_padding"
android:paddingRight="@dimen/content_padding"
android:layout_marginBottom="@dimen/content_padding"
tools:itemCount="3"
tools:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/point_editor_icon_category_item"
tools:orientation="horizontal"
tools:visibility="visible"
android:visibility="gone" />
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:letterSpacing="@dimen/description_letter_spacing"
android:paddingLeft="@dimen/content_padding"
android:paddingTop="@dimen/content_padding_half"
android:paddingRight="@dimen/content_padding"
android:paddingBottom="@dimen/content_padding"
android:textColor="?android:attr/textColorSecondary"
android:textSize="@dimen/default_desc_text_size"
osmand:typeface="@string/font_roboto_regular"
tools:text="Description"
tools:visibility="visible"
android:visibility="gone" />
<LinearLayout
android:id="@+id/field_box_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/content_padding"
android:paddingRight="@dimen/content_padding"
android:orientation="vertical"
tools:visibility="visible"
android:visibility="gone">
<net.osmand.plus.widgets.OsmandTextFieldBoxes
android:id="@+id/field_box"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="true"
android:focusableInTouchMode="true"
osmand:errorColor="@color/color_invalid"
osmand:helperTextColor="?android:textColorSecondary"
tools:labelText="Hint"
osmand:primaryColor="@color/active_color_primary_dark"
osmand:secondaryColor="?android:textColorSecondary">
<studio.carbonylgroup.textfieldboxes.ExtendedEditText
android:id="@+id/edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textMultiLine|textNoSuggestions"
android:maxLines="4"
android:saveEnabled="false"
android:scrollHorizontally="false"
tools:text="Text" />
</net.osmand.plus.widgets.OsmandTextFieldBoxes>
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/helper_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:letterSpacing="@dimen/description_letter_spacing"
android:paddingTop="@dimen/content_padding_half"
android:textColor="?android:attr/textColorSecondary"
android:textSize="@dimen/default_desc_text_size"
osmand:typeface="@string/font_roboto_regular"
tools:text="Helper text"
tools:visibility="visible"
android:visibility="gone" />
</LinearLayout>
<View
android:id="@+id/bottom_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/divider_color_basic"
android:layout_marginTop="@dimen/content_padding"
tools:visibility="visible"
android:visibility="gone" />
<include
android:id="@+id/button"
layout="@layout/bottom_sheet_dialog_button"
android:layout_width="match_parent"
android:layout_height="@dimen/dialog_button_height"
android:layout_marginTop="@dimen/content_padding_half"
android:layout_marginLeft="@dimen/content_padding"
android:layout_marginRight="@dimen/content_padding"
tools:visibility="visible"
android:visibility="gone" />
<LinearLayout
android:id="@+id/result_container"
android:layout_width="match_parent"
android:layout_height="@dimen/setting_list_item_group_height"
android:orientation="horizontal"
tools:visibility="visible"
android:visibility="gone">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/result_icon"
android:layout_width="@dimen/standard_icon_size"
android:layout_height="@dimen/standard_icon_size"
android:layout_gravity="center_vertical"
android:layout_marginRight="@dimen/content_padding"
android:layout_marginLeft="@dimen/content_padding"
android:tint="?attr/default_icon_color"
tools:src="@drawable/ic_action_gdirections_dark" />
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/result_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:letterSpacing="@dimen/description_letter_spacing"
android:singleLine="true"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/default_list_text_size"
osmand:typeface="@string/font_roboto_regular"
tools:text="OK" />
</LinearLayout>
</LinearLayout>

View file

@ -0,0 +1,95 @@
<androidx.appcompat.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/toolbar_height"
app:contentInsetLeft="0dp"
app:contentInsetStart="0dp"
app:contentInsetRight="0dp"
app:contentInsetEnd="0dp"
app:theme="@style/ThemeOverlay.AppCompat.ActionBar">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:minHeight="@dimen/toolbar_height"
android:background="?attr/card_and_list_background_basic"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageButton
android:id="@+id/close_button"
style="@style/Widget.AppCompat.Toolbar.Button.Navigation"
android:layout_width="@dimen/toolbar_height"
android:layout_height="@dimen/toolbar_height"
android:contentDescription="@string/access_shared_string_navigate_up"
app:srcCompat="@drawable/ic_arrow_back"
android:tint="?attr/default_icon_color" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:layout_marginLeft="@dimen/content_padding"
android:layout_marginRight="@dimen/content_padding"
android:layout_marginStart="@dimen/content_padding"
android:layout_marginEnd="@dimen/content_padding"
android:paddingTop="@dimen/content_padding_half"
android:paddingBottom="@dimen/content_padding_half"
android:background="?attr/card_and_list_background_basic"
android:orientation="vertical">
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/toolbar_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:letterSpacing="@dimen/text_button_letter_spacing"
android:maxLines="2"
android:textColor="?android:textColorPrimary"
android:textSize="@dimen/dialog_header_text_size"
app:typeface="@string/font_roboto_medium"
tools:text="@string/routing_settings_2" />
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/toolbar_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="2"
android:textAppearance="@style/TextAppearance.ContextMenuSubtitle"
android:textColor="@null"
tools:text="Some description" />
</LinearLayout>
<FrameLayout
android:id="@+id/action_button"
android:layout_gravity="center"
android:layout_width="@dimen/acceptable_touch_radius"
android:layout_height="@dimen/acceptable_touch_radius">
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/action_button_icon"
style="@style/Widget.AppCompat.Toolbar.Button.Navigation"
android:layout_width="@dimen/standard_icon_size"
android:layout_height="@dimen/standard_icon_size"
android:layout_marginStart="@dimen/content_padding"
android:layout_marginLeft="@dimen/content_padding"
android:layout_marginEnd="@dimen/content_padding"
android:layout_marginRight="@dimen/content_padding"
android:contentDescription="@string/access_shared_string_navigate_up"
android:duplicateParentState="true"
android:clickable="false"
android:focusable="false"
android:layout_gravity="center"
android:scaleType="fitCenter"
tools:src="@drawable/ic_action_info_dark" />
</FrameLayout>
</LinearLayout>
</androidx.appcompat.widget.Toolbar>

View file

@ -3,7 +3,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<WebView
<net.osmand.plus.widgets.WebViewEx
android:id="@+id/printDialogWebview"
android:layout_width="match_parent"
android:layout_height="match_parent" />

View file

@ -74,7 +74,7 @@
android:layout_height="0dp"
android:layout_weight="1">
<WebView
<net.osmand.plus.widgets.WebViewEx
android:id="@+id/content_web_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />

View file

@ -4014,4 +4014,5 @@
<string name="routing_attr_allow_streams_name">السماح بالتيارات والمصارف</string>
<string name="routing_attr_allow_intermittent_description">السماح بالممرات المائية المتقطعة</string>
<string name="routing_attr_allow_intermittent_name">السماح بالممرات المائية المتقطعة</string>
<string name="voice_prompts_timetable">مطالبة صوتية</string>
</resources>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="switch_to_raster_map_to_see">Off-line vektorová mapa pro toto místo není dostupná. Stáhněte jí v \'Nastavení\' (\'Stáhnout mapy\'), nebo se přepněte na modul \'Online mapy\'.</string>
<string name="switch_to_raster_map_to_see">Stáhněte si off-line vektorovou mapu pro tuto oblast v menu \'Nastavení\' (\'Stáhnout mapy\'), nebo se přepněte na modul \'Online mapy\'.</string>
<string name="send_files_to_osm">Nahrát GPX soubory do OSM?</string>
<string name="gpx_visibility_txt">Viditelnost</string>
<string name="gpx_tags_txt">Tagy</string>
@ -24,17 +24,17 @@
<string name="north">sever</string>
<string name="north_north_east">severoseverovýchod</string>
<string name="north_east">severovýchod</string>
<string name="east_north_east">východovýchodosever</string>
<string name="east_north_east">východoseverovýchod</string>
<string name="east">východ</string>
<string name="east_south_east">východovýchodojih</string>
<string name="east_south_east">východojihovýchod</string>
<string name="south_east">jihovýchod</string>
<string name="south_south_east">jihojihovýchod</string>
<string name="south">jih</string>
<string name="south_south_west">jihojihozápad</string>
<string name="south_west">jihozápad</string>
<string name="west_south_west">západozápadojih</string>
<string name="west_south_west">západojihozápad</string>
<string name="west">západ</string>
<string name="west_north_west">západozápadosever</string>
<string name="west_north_west">západoseverozápad</string>
<string name="north_west">severozápad</string>
<string name="north_north_west">severoseverozápad</string>
<string name="front">vpřed</string>
@ -53,7 +53,7 @@
<string name="direction_style_sidewise">Podle stran (8 sektorů)</string>
<string name="direction_style_clockwise">Podle hodin (12 sektorů)</string>
<string name="settings_direction_style">Styl interpretace směrů</string>
<string name="settings_direction_style_descr">Vyberte způsob vyjádření relativního směru pohybu.</string>
<string name="settings_direction_style_descr">Vyberte způsob vyjádření relativního směru pohybu</string>
<string name="auto_announce_on">Spustit automatické ohlašování</string>
<string name="auto_announce_off">Zastavit automatické ohlašování</string>
<string name="i_am_here">Jsem zde</string>
@ -64,16 +64,16 @@
<string name="use_fluorescent_overlays">Fluorescentní barvy</string>
<string name="use_fluorescent_overlays_descr">Použít fluorescentní barvy pro zobrazení cest a tras.</string>
<string name="offline_edition">Off-line editace</string>
<string name="offline_edition_descr">Vždy používat off-line editaci.</string>
<string name="offline_edition_descr">Pokud je povolena off-line editace, změny budou nejprve uloženy lokálně a odeslány až na žádost, jinak budou změny odeslány ihned.</string>
<string name="update_poi_does_not_change_indexes">Změny POI bodů v aplikaci nemají vliv na zobrazení stažených map, změny se ukládají do souboru ve vašem zařízení.</string>
<string name="local_openstreetmap_uploading">Nahrávání…</string>
<string name="local_openstreetmap_were_uploaded">{0} POI/Poznámek bylo odesláno</string>
<string name="local_openstreetmap_were_uploaded">{0} POI/poznámek bylo odesláno</string>
<string name="local_openstreetmap_uploadall">Odeslat všechny</string>
<string name="local_openstreetmap_upload">Odeslat změny do OSM</string>
<string name="local_openstreetmap_upload">Odeslat změnu do OSM</string>
<string name="local_openstreetmap_delete">Smazat změnu</string>
<string name="local_openstreetmap_descr_title">Off-line editace OSM:</string>
<string name="local_openstreetmap_settings">OSM POI body/poznámky uložené na zařízení</string>
<string name="local_openstreetmap_settings_descr">Zobrazit a spravovat OSM POI body/poznámky uložené v databázi na zařízení.</string>
<string name="local_openstreetmap_settings">OSM POI/poznámky uložené v zařízení</string>
<string name="local_openstreetmap_settings_descr">Zobrazit a spravovat OSM POI/poznámky uložené v databázi v zařízení.</string>
<string name="live_monitoring_interval_descr">Zadejte interval nahrávání pozice na server.</string>
<string name="live_monitoring_interval">Interval přímého přenosu</string>
<string name="live_monitoring_url_descr">Zadejte webovou adresu serveru pro přímý přenos pozice. Parametry: lat={0}, lon={1}, timestamp={2}, hdop={3}, altitude={4}, speed={5}, bearing={6}.</string>
@ -97,7 +97,7 @@
<string name="index_name_other">Mapy celého světa a tematické mapy</string>
<string name="index_name_wiki">Celý svět - Wikipedie</string>
<string name="index_name_voice">Hlasové pokyny (nahrávky, omezené funkce)</string>
<string name="index_name_tts_voice">Hlasové pokyny (TTS-generované)</string>
<string name="index_name_tts_voice">Hlasové pokyny (TTS, doporučeno)</string>
<string name="amenity_type_osmwiki">Wikipedie (off-line)</string>
<string name="amenity_type_user_defined">Uživatelsky definované</string>
<string name="fav_export_confirmation">Soubor exportu obsahující Oblíbené již existuje. Nahradit ho\?</string>
@ -110,9 +110,9 @@
<string name="index_settings">Spravovat mapové soubory</string>
<string name="index_settings_descr">Stáhnout a spravovat off-line mapy uložené ve vašem zařízení.</string>
<string name="general_settings">Obecné</string>
<string name="general_settings_descr">Nastavení displeje, jazyka, zvuku a dalších parametrů.</string>
<string name="general_settings_descr">Nastavení zobrazení a dalších společných parametrů aplikace.</string>
<string name="user_name">Vaše OSM uživatelské jméno</string>
<string name="open_street_map_login_descr">Vaše uživatelské jméno na openstreetmap.org.</string>
<string name="open_street_map_login_descr">Potřebné pro přispívání do openstreetmap.org.</string>
<string name="user_password">Heslo</string>
<string name="osmand_service">Služba na pozadí</string>
<string name="osmand_service_descr">OsmAnd běží na pozadí i při vypnuté obrazovce.</string>
@ -139,10 +139,10 @@
<string name="city_type_city">Velké město</string>
<string name="animate_route_off">Zastavit simulaci</string>
<string name="animate_route">Zapnout animaci</string>
<string name="file_can_not_be_renamed">Nelze přejmenovat soubor.</string>
<string name="file_with_name_already_exists">Soubor tohoto jména již existuje.</string>
<string name="poi_query_by_name_matches_categories">Vašemu dotazu odpovídá několik kategorií POI:</string>
<string name="data_to_search_poi_not_available">Lokální data pro vyhledávání POI není dostupný.</string>
<string name="file_can_not_be_renamed">Nepodařilo se přejmenovat soubor.</string>
<string name="file_with_name_already_exists">Soubor s tímto názvem již existuje.</string>
<string name="poi_query_by_name_matches_categories">Bylo nalezeno několik souvisejících kategorií POI.</string>
<string name="data_to_search_poi_not_available">Stáhněte si offline data pro vyhledávání POI.</string>
<string name="poi_filter_by_name">Hledat podle jména</string>
<string name="old_poi_file_should_be_deleted">Soubor s POI daty \'%1$s\' již není potřeba a může být smazán.</string>
<string name="update_poi_file_not_found">Lokální soubor pro úpravu POI bodů nebyl nalezen a ani nemohl být vytvořen.</string>
@ -168,7 +168,7 @@
<string name="show_more_map_detail">Více mapových detailů</string>
<string name="show_more_map_detail_descr">Zobrazit některé detaily vektorové mapy (silnice, atd.) již při menším zvětšení.</string>
<string name="favourites_delete_multiple_succesful">Oblíbené body smazány.</string>
<string name="favorite_delete_multiple">Chystáte se smazat %1$d Oblíbených a %2$d skupin Oblíbených. Opravdu smazat\?</string>
<string name="favorite_delete_multiple">Opravdu chcete smazat %1$d Oblíbených a %2$d skupin Oblíbených\?</string>
<string name="favorite_home_category">Doma</string>
<string name="favorite_friends_category">Přátelé</string>
<string name="favorite_places_category">Místa</string>
@ -194,7 +194,7 @@
<string name="local_index_map_data">Mapová data</string>
<string name="local_indexes_cat_backup">Záloha</string>
<string name="local_indexes_cat_tts">Hlasové pokyny (TTS)</string>
<string name="local_indexes_cat_voice">Hlasové pokyny (media)</string>
<string name="local_indexes_cat_voice">Hlasové pokyny (nahrané)</string>
<string name="local_indexes_cat_tile">On-line mapy a dlaždice v mezipaměti</string>
<string name="local_indexes_cat_map">Standardní mapy (vektorové)</string>
<string name="local_indexes_cat_poi">Data POI</string>
@ -248,25 +248,25 @@
<string name="select_build_to_install">Vyberte jeden z balíčků OsmAnd k nainstalování</string>
<string name="contribution_activity">Speciální aktivita pro vývojovou verzi</string>
<string name="search_offline_clear_search">Nové hledání</string>
<string name="map_text_size_descr">Zvolte velikost písma ve jménech na mapě.</string>
<string name="map_text_size_descr">Velikost písma pro názvy na mapě:</string>
<string name="map_text_size">Velikost písma</string>
<string name="installing_new_resources">Rozbalování nových dat…</string>
<string name="internet_connection_required_for_online_route">On-line navigaci nelze použít, protože připojení k Internetu není k dispozici.</string>
<string name="internet_connection_required_for_online_route">On-line navigaci nelze použít bez připojení k internetu.</string>
<string name="tts_language_not_supported_title">Nepodporovaný jazyk</string>
<string name="tts_language_not_supported">Vybraný jazyk není podporován nainstalovaným TTS enginem. Najít jiný TTS v obchodě\? Jinak bude použit výchozí TTS jazyk.</string>
<string name="tts_language_not_supported">Vybraný jazyk není podporován nainstalovaným TTS (text-to-speech) modulem Androidu. Bude použit výchozí TTS jazyk. Najít jiný TTS modul v obchodě\?</string>
<string name="tts_missing_language_data_title">Chybějící data</string>
<string name="tts_missing_language_data">Přejít do obchodu pro stažení vybraného jazyka\?</string>
<string name="gpx_option_reverse_route">Obrátit GPX trasu</string>
<string name="gpx_option_reverse_route">Obrátit směr stopy</string>
<string name="gpx_option_destination_point">Použít současný cíl trasy</string>
<string name="gpx_option_from_start_point">Projet celou trasu</string>
<string name="switch_to_vector_map_to_see">Pro tuto oblast je k dispozici offline vektorová mapa.
\t
\t Pro zobrazení zvolte Menu → Nastavení mapy → Zdroj map… → Vektorové off-line mapy.</string>
\n\t
\n\tPro zobrazení zvolte Menu → Nastavení mapy → Zdroj map… → Vektorové off-line mapy.</string>
<string name="choose_audio_stream">Kanál pro navádění</string>
<string name="choose_audio_stream_descr">Zvolte kanál poskytující hlasové navádění.</string>
<string name="choose_audio_stream_descr">Zvolte reproduktor pro hlasové navádění.</string>
<string name="voice_stream_voice_call">Zvuk telefonního hovoru (přeruší Bluetooth autorádio)</string>
<string name="voice_stream_notification">Upozornění</string>
<string name="voice_stream_music">Média/Zvuk navigace</string>
<string name="voice_stream_music">Média/zvuky navigace</string>
<string name="warning_tile_layer_not_downloadable">Data pro mapovou vrstvu %1$s nelze stáhnout, reinstalace může pomoci.</string>
<string name="overlay_transparency_descr">Upravit průhlednost překryvné mapy.</string>
<string name="overlay_transparency">Průhlednost překryvu</string>
@ -279,9 +279,9 @@
<string name="layer_overlay">Překryvná mapa…</string>
<string name="shared_string_none">Žádná</string>
<string name="map_overlay">Překryvná mapa</string>
<string name="map_overlay_descr">Vyberte překryvnou mapu.</string>
<string name="map_overlay_descr">Vyberte překryvnou mapu</string>
<string name="tile_source_already_installed">Mapa již nainstalována, \'Nastavení\' budou aktualizována.</string>
<string name="select_tile_source_to_install">Vyberte mapy k instalaci nebo aktualizaci.</string>
<string name="select_tile_source_to_install">Vyberte (dlaždicové) mapy k instalaci nebo aktualizaci.</string>
<string name="internet_not_available">Nelze provést tuto akci bez připojení k Internetu.</string>
<string name="install_more">Instalovat další…</string>
<string name="level_to_switch_vector_raster_descr">Použít rastrové mapy pro cokoli nad tuto úroveň.</string>
@ -289,7 +289,7 @@
<string name="error_doing_search">Nelze provést offline hledání.</string>
<string name="search_osm_offline">Hledat pomocí polohy</string>
<string name="system_locale">Podle systému</string>
<string name="preferred_locale_descr">Volba jazyka (projeví se jakmile bude OsmAnd restartován).</string>
<string name="preferred_locale_descr">Jazyk zobrazení aplikace (projeví se po restartu OsmAnd).</string>
<string name="preferred_locale">Jazyk</string>
<string name="shared_string_next">Další</string>
<string name="shared_string_previous">Předchozí</string>
@ -310,18 +310,18 @@
<string name="add_waypoint_dialog_added">GPX bod na trase \'\'{0}\'\' byl přidán</string>
<string name="add_waypoint_dialog_title">Přidat bod na zaznamenávanou GPX trasu</string>
<string name="osmand_routing_experimental">Off-line navigace je experimentální a funguje jen pro větší vzdálenosti než 20 km. Navigace je dočasně přepnuta na on-line CloudMade.</string>
<string name="specified_dir_doesnt_exist">Nemohu najít zadaný adresář.</string>
<string name="specified_dir_doesnt_exist">Nepodařilo se najít zadaný adresář.</string>
<string name="application_dir">Adresář pro data</string>
<string name="gps_status_app_not_found">Aplikace pro zobrazení stavu GPS není nainstalovaná. Hledat v obchodě?</string>
<string name="voice_is_not_available_msg">Hlasové navádění je nedostupné. Přejděte do „Nastavení“ → „Nastavení navigace“ → „Hlasová data“ a vyberte nebo stáhněte balíček s hlasovými pokyny.</string>
<string name="voice_is_not_available_title">Žádná hlasová data nejsou zvolena</string>
<string name="voice_is_not_available_title">Zvolte balíček hlasových údajů</string>
<string name="trace_rendering_descr">Zaškrtněte pro zobrazení statistik o vykreslování mapy.</string>
<string name="trace_rendering">Sledovat vykreslování</string>
<string name="daynight_mode_day">Den</string>
<string name="daynight_mode_night">Noc</string>
<string name="daynight_mode_auto">Východ/západ slunce</string>
<string name="daynight_mode_sensor">Senzor osvětlení</string>
<string name="daynight_descr">Vyberte logiku pro přepínání mezi denním a nočním režimem.</string>
<string name="daynight_descr">Upravte přepínání mezi denním a nočním režimem.</string>
<string name="daynight">Denní/noční režim</string>
<string name="download_files_question">Stáhnout {0} souborů ({1} MB)?</string>
<string name="items_were_selected">vybráno {0} položek</string>
@ -332,24 +332,24 @@
<string name="fast_route_mode_descr">Zapněte pro výpočet nejrychlejší trasy nebo vypněte pro ekonomickou trasu.</string>
<string name="tiles_to_download_estimated_size">Pro zvětšení {0} je třeba stáhnout {1} mapových dlaždic, celkem {2} MB</string>
<string name="shared_string_download_map">Stáhnout mapu</string>
<string name="select_max_zoom_preload_area">Vyberte maximální zvětšení stahovaných map</string>
<string name="select_max_zoom_preload_area">Maximální přiblížení pro přednačítání</string>
<string name="maps_could_not_be_downloaded">Tuto mapu nelze stáhnout</string>
<string name="continuous_rendering">Průběžné vykreslování</string>
<string name="continuous_rendering_descr">Při zaškrtnuté volbě se mapa bude vykreslovat postupně.</string>
<string name="rendering_exception">Nelze vykreslit vybranou oblast</string>
<string name="rendering_exception">Nepodařilo se vykreslit vybranou oblast.</string>
<string name="rendering_out_of_memory">Nedostatek paměti pro zobrazení vybrané oblasti</string>
<string name="show_point_options">Možnosti bodu…</string>
<string name="renderer_load_sucess">Vykreslovač načten</string>
<string name="renderer_load_exception">Nepodařilo se načíst vykreslovač</string>
<string name="renderer_load_exception">Nepodařilo se načíst vykreslovací modul.</string>
<string name="renderers">Vektorový vykreslovač</string>
<string name="renderers_descr">Vyberte styl vektorového vykreslování.</string>
<string name="renderers_descr">Vyberte styl vykreslování</string>
<string name="poi_context_menu_website">Zobrazit webovou stránku bodu</string>
<string name="poi_context_menu_call">Zobrazit telefonní číslo bodu</string>
<string name="website">Webová stránka</string>
<string name="phone">telefon</string>
<string name="download_type_to_filter">vyhledat</string>
<string name="use_high_res_maps">Vysoké rozlišení</string>
<string name="use_high_res_maps_descr">Použít mapu s vysokým rozlišením pro jemné displeje.</string>
<string name="use_high_res_maps_descr">Neroztahovat (a nerozmazávat) mapové dlaždice na displejích s vysokou hustotou bodů.</string>
<string name="unknown_location">Pozice ještě není známa.</string>
<string name="context_menu_item_search_transport">Hledat veřejnou dopravu</string>
<string name="transport_searching_transport">Hledání dopravy (žádný cíl):</string>
@ -358,15 +358,15 @@
<string name="voice">Nahrávka hlasu</string>
<string name="no_vector_map_loaded">Vektorové mapy nebyly načteny</string>
<string name="gpx_files_not_found">Žádné GPX soubory nebyly nalezeny v adresáři tracks</string>
<string name="error_reading_gpx">Nelze načíst GPX data</string>
<string name="error_reading_gpx">Nepodařilo se načíst GPX data.</string>
<string name="vector_data">Vektorové off-line mapy</string>
<string name="transport_context_menu">Hledat dopravu ze zastávky</string>
<string name="transport_context_menu">Hledat dopravu na zastávce</string>
<string name="poi_context_menu_modify">Upravit POI</string>
<string name="poi_context_menu_delete">Smazat POI</string>
<string name="rotate_map_compass_opt">Směr kompasu</string>
<string name="rotate_map_bearing_opt">Směr pohybu</string>
<string name="rotate_map_none_opt">Neotáčet (sever vždy nahoru)</string>
<string name="rotate_map_to_bearing_descr">Zvolte směr natočení mapy.</string>
<string name="rotate_map_to_bearing_descr">Směr natočení mapy:</string>
<string name="rotate_map_to_bearing">Natočení mapy</string>
<string name="show_route">Ukázat cestu</string>
<string name="fav_imported_sucessfully">Oblíbená místa importována</string>
@ -374,7 +374,7 @@
<string name="fav_saved_sucessfully">Oblíbená místa uložena do {0}</string>
<string name="no_fav_to_save">Žádná Oblíbená místa k uložení</string>
<string name="shared_string_import">Importovat</string>
<string name="error_occurred_loading_gpx">Nelze načíst GPX</string>
<string name="error_occurred_loading_gpx">Nepodařilo se načíst GPX.</string>
<string name="send_report">Odeslat hlášení</string>
<string name="none_region_found">Na paměťové kartě nelze najít žádné stáhnuté mapy.</string>
<string name="poi_namefinder_query_empty">Psaním hledejte POI</string>
@ -383,7 +383,7 @@
<string name="layer_yandex_traffic">Yandex doprava</string>
<string name="layer_route">Cesta</string>
<string name="shared_string_favorites">Oblíbené</string>
<string name="layer_osm_bugs">OSM Poznámky (on-line)</string>
<string name="layer_osm_bugs">OSM poznámky (online)</string>
<string name="layer_poi">Vrstva POI…</string>
<string name="layer_map">Zdroj map…</string>
<string name="menu_layers">Mapová data</string>
@ -398,9 +398,9 @@
<string name="gps_provider">GPS</string>
<string name="int_seconds">sekund</string>
<string name="int_min">min.</string>
<string name="background_service_int_descr">Vyberte interval pro zaměřování polohy pro službu na pozadí.</string>
<string name="background_service_int_descr">Interval probuzení pro službu na pozadí:</string>
<string name="background_service_int">Interval zaměřování GPS</string>
<string name="background_service_provider_descr">Vyberte způsob získání polohy pro službu na pozadí.</string>
<string name="background_service_provider_descr">Způsob získání polohy službou na pozadí:</string>
<string name="background_service_provider">Poskytovatel polohy</string>
<string name="background_router_service_descr">Sleduje vaši pozici i když je obrazovka vypnutá.</string>
<string name="background_router_service">Sledování polohy na pozadí</string>
@ -415,7 +415,7 @@
<string name="voice_data_initializing">Inicializace hlasových dat…</string>
<string name="voice_data_not_supported">Nepodporovaná verze hlasových dat</string>
<string name="voice_data_corrupted">Zvolené hlasové údaje jsou poškozené</string>
<string name="voice_data_unavailable">Vybraná hlasová data jsou nedostupná</string>
<string name="voice_data_unavailable">Vybraný balíček hlasových dat není dostupný</string>
<string name="sd_unmounted">Paměťová karta není dostupná.
\nNeuvidíte mapu a nebudete moci ani nic najít.</string>
<string name="sd_mounted_ro">Paměťová karta má zakázaný zápis.

View file

@ -222,7 +222,7 @@
<string name="poi_craft_gardener">Gärtner</string>
<string name="poi_gas">Flüssiggasspeicher</string>
<string name="poi_gate">Tor</string>
<string name="poi_shop_yes">Gemischtwarenhandlung</string>
<string name="poi_shop_yes">Allgemeines Geschäft</string>
<string name="poi_geyser">Geysir</string>
<string name="poi_gift">Geschenkeladen</string>
<string name="poi_glacier">Gletscher</string>

View file

@ -4031,4 +4031,5 @@
<string name="routing_attr_allow_streams_name">Bäche und Entwässerungsgräben erlauben</string>
<string name="routing_attr_allow_intermittent_description">Gewässer erlauben, die nicht ständig Wasser führen</string>
<string name="routing_attr_allow_intermittent_name">Gewässer erlauben, die nicht ständig Wasser führen</string>
<string name="voice_prompts_timetable">Zeiten der Sprachansagen</string>
</resources>

View file

@ -4027,4 +4027,5 @@
<string name="routing_attr_allow_streams_name">Permesi riveretojn kaj fosaĵojn</string>
<string name="routing_attr_allow_intermittent_description">Permesi navigi per periode sekiĝantaj akvovojoj</string>
<string name="routing_attr_allow_intermittent_name">Permesi sezonajn akvovojojn</string>
<string name="voice_prompts_timetable">Tempoj de voĉaj anoncoj</string>
</resources>

View file

@ -4032,4 +4032,5 @@
<string name="routing_attr_allow_streams_name">Permitir arroyos y desagües</string>
<string name="routing_attr_allow_intermittent_description">Permite cursos de agua intermitentes</string>
<string name="routing_attr_allow_intermittent_name">Permitir cursos de agua intermitentes</string>
<string name="voice_prompts_timetable">Tiempo de los avisos por voz</string>
</resources>

View file

@ -4029,4 +4029,5 @@
<string name="profile_type_user_string">Perfil de usuario</string>
<string name="profile_type_osmand_string">Perfil de OsmAnd</string>
<string name="profile_by_default_description">Elige el perfil que será usado al iniciar la aplicación.</string>
<string name="voice_prompts_timetable">Tiempo de indicaciones por voz</string>
</resources>

View file

@ -4019,4 +4019,5 @@
<string name="routing_attr_allow_streams_description">Permitir arroyos y desagües</string>
<string name="routing_attr_allow_streams_name">Permitir arroyos y desagües</string>
<string name="routing_attr_allow_intermittent_description">Permitir vías de agua intermitentes</string>
<string name="voice_prompts_timetable">Tiempo de indicaciones por voz</string>
</resources>

View file

@ -3895,4 +3895,9 @@
<string name="poi_water_source_tap">Robinet</string>
<string name="poi_vaccination_covid19">Vaccination : covid19</string>
<string name="poi_health_specialty_vaccination_yes">Vaccination</string>
<string name="poi_wildlife_crossing_bat_tunnel">Tunnel à chauve-souris</string>
<string name="poi_wildlife_crossing_bat_bridge">Pont à chauve-</string>
<string name="poi_wildlife_crossing">Passage à faune</string>
<string name="poi_swimming_area">Zone de</string>
<string name="poi_lavoir">Lavoir</string>
</resources>

View file

@ -4006,4 +4006,17 @@
<string name="routing_attr_allow_intermittent_name">Autoriser les voies navigables intermittentes</string>
<string name="routing_attr_allow_streams_description">Autoriser les cours deau et les drains</string>
<string name="routing_attr_allow_streams_name">Autoriser les cours deau et les drains</string>
<string name="voice_prompts_timetable">Nombre d\'annonces vocales</string>
<string name="keep_it_empty_if_not">Si non, laissez vide</string>
<string name="shared_string_subtype">Sous-type</string>
<string name="shared_string_vehicle">Véhicule</string>
<string name="shared_string_api_key">Clé d\'API</string>
<string name="shared_string_server_url">URL du serveur</string>
<string name="shared_string_enter_param">Saisissez le paramètre</string>
<string name="test_route_calculation">Calculer un itinéraire dessai</string>
<string name="routing_engine_vehicle_type_driving">En voiture</string>
<string name="routing_engine_vehicle_type_foot">A pieds</string>
<string name="routing_engine_vehicle_type_bike">Vélo</string>
<string name="routing_engine_vehicle_type_car">Automobile</string>
<string name="message_error_recheck_parameters">Erreur, vérifiez les paramètres</string>
</resources>

View file

@ -585,7 +585,7 @@
<string name="choose_intersected_street">Keresztező utca kijelölése</string>
<string name="Closest_Amenities">Legközelebbi hasznos létesítmények</string>
<string name="app_mode_default">Térképböngészés</string>
<string name="app_mode_car">Vezetés</string>
<string name="app_mode_car">Autóvezetés</string>
<string name="app_mode_bicycle">Kerékpározás</string>
<string name="app_mode_pedestrian">Gyaloglás</string>
<string name="position_on_map_center">Középen</string>
@ -1292,7 +1292,7 @@
<string name="speak_poi">Közeli érdekes helyek (POI)</string>
<string name="download_additional_maps">Letöltöd a hiányzó térképeket %1$s (%2$d MB)?</string>
<string name="rendering_value_browse_map_name">Térképböngészés</string>
<string name="rendering_value_car_name">Autó</string>
<string name="rendering_value_car_name">Személyautó</string>
<string name="rendering_value_bicycle_name">Kerékpár</string>
<string name="rendering_value_pedestrian_name">Gyalogos</string>
<string name="record_plugin_description">Ez a bővítmény aktiválja a nyomvonalak rögzítésének és mentésének lehetőségét, ha megnyomja a GPX naplózó gombot a térképképernyőn, valamint képes minden navigációs útvonalat automatikusan egy GPX-fájlba naplózni.
@ -4019,4 +4019,20 @@
<string name="routing_attr_allow_streams_name">Patakok és vízelvezető árkok engedélyezése</string>
<string name="routing_attr_allow_intermittent_description">Időszakos vízfolyások engedélyezése</string>
<string name="routing_attr_allow_intermittent_name">Időszakos vízfolyások engedélyezése</string>
<string name="routing_engine_vehicle_type_driving">Autóvezetés</string>
<string name="routing_engine_vehicle_type_foot">Gyaloglás</string>
<string name="routing_engine_vehicle_type_bike">Kerékpár</string>
<string name="routing_engine_vehicle_type_car">Személyautó</string>
<string name="voice_prompts_timetable">Hangutasítások ideje</string>
<string name="add_online_routing_engine">Online útvonaltervező hozzáadása</string>
<string name="edit_online_routing_engine">Online útvonaltervező szerkesztése</string>
<string name="shared_string_subtype">Altípus</string>
<string name="shared_string_vehicle">Jármű</string>
<string name="shared_string_api_key">API-kulcs</string>
<string name="shared_string_server_url">Kiszolgáló URL-je</string>
<string name="shared_string_enter_param">Paraméter megadása</string>
<string name="keep_it_empty_if_not">Hagyja üresen, ha nem</string>
<string name="online_routing_example_hint">Az összes paraméterrel rendelkező URL így néz ki:</string>
<string name="test_route_calculation">Útvonaltervezés kipróbálása</string>
<string name="message_error_recheck_parameters">Hiba, ellenőrizze újra a paramétereket</string>
</resources>

View file

@ -2992,4 +2992,24 @@
<string name="poi_traffic_signals_arrow">Freccia</string>
<string name="poi_traffic_signals_vibration">Vibrazione</string>
<string name="poi_fire_hydrant_pressure_filter">Pressione</string>
<string name="poi_vending_elongated_coin">Moneta souvenir</string>
<string name="poi_drive_through_no">Drive-through: no</string>
<string name="poi_piste_grooming_mogul">Gobba</string>
<string name="poi_piste_grooming_backcountry">Fuoripista</string>
<string name="poi_piste_difficulty_freeride">Fuoripista</string>
<string name="poi_board_type_notice">Bacheca</string>
<string name="poi_information_trail_blaze">Segnaletica sentiero</string>
<string name="poi_wiki_lang_gu">Wiki gujarati</string>
<string name="poi_wiki_lang_yo">Wiki yoruba</string>
<string name="poi_wiki_lang_cv">Wiki ciuvascio</string>
<string name="poi_wiki_lang_ba">Wiki baschiro</string>
<string name="poi_wiki_lang_tg">wiki tagico</string>
<string name="poi_wiki_lang_ast">Wiki asturiano-leonese</string>
<string name="poi_wiki_lang_ky">Wiki chirghisa</string>
<string name="poi_wiki_lang_zhminnan">Wiki min meridionale</string>
<string name="poi_wiki_lang_min">Wiki minangkabau</string>
<string name="poi_wiki_lang_war">Wiki waray</string>
<string name="poi_wiki_lang_mr">Wiki maratino</string>
<string name="poi_wiki_lang_ml">Wiki malayalam</string>
<string name="poi_wiki_lang_fy">Wiki frisone occidentale</string>
</resources>

View file

@ -4031,4 +4031,20 @@
<string name="routing_attr_allow_intermittent_name">לאפשר מקטעים עם דרכי מים עונתיים</string>
<string name="gpx_upload_public_visibility_descr">„ציבורי” משמעו שהעקבות מופיעים באופן ציבורי בעקבות ה־GPS שלך וברישומי עקבות GPS ציבוריים וברישומי מעקב ציבוריים עם חותמות זמן בתצורה גולמית. הנתונים שמוגשים דרך ה־API אינם מפנים אל עמוד העקבות שלך. חותמות הזמן של נקודות המעקב אינן זמינות דרך ה־API של ה־GPS ונקודות המעקב אינן מסודרות בהתאם לזמן שתועדו.</string>
<string name="gpx_upload_private_visibility_descr">„פרטי” משמעות שהעקבות לא תופענה ברישומים ציבוריים אך נקודות מעקב ממתוכן תהיינה זמינות בסדר אקראי דרך ה־API הציבורי של ה־GPS ללא חותמות זמן.</string>
<string name="voice_prompts_timetable">זמני הכרזות</string>
<string name="add_online_routing_engine">הוספת מנוע ניווט מקוון</string>
<string name="edit_online_routing_engine">עריכת מנוע הניווט המקוון</string>
<string name="shared_string_subtype">תת־סוג</string>
<string name="shared_string_vehicle">כלי רכב</string>
<string name="shared_string_api_key">מפתח API</string>
<string name="shared_string_server_url">כתובת השרת</string>
<string name="shared_string_enter_param">נא למלא משתנים</string>
<string name="keep_it_empty_if_not">להשאיר ריק אם לא</string>
<string name="online_routing_example_hint">כתובת עם כל המשתנים נראית כך:</string>
<string name="test_route_calculation">בדיקת חישוב מסלול</string>
<string name="routing_engine_vehicle_type_driving">נהיגה</string>
<string name="routing_engine_vehicle_type_foot">ברגל</string>
<string name="routing_engine_vehicle_type_bike">אופנוע</string>
<string name="routing_engine_vehicle_type_car">מכונית</string>
<string name="message_error_recheck_parameters">שגיאה, נא לבדוק את המשתנים מחדש</string>
</resources>

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>

File diff suppressed because it is too large Load diff

View file

@ -3745,8 +3745,8 @@
<string name="route_between_points_add_track_desc">Selecteer een track waaraan je een nieuw segment wil toevoegen.</string>
<string name="plan_route_select_track_file_for_open">Selecteer een trackbestand om te openen.</string>
<string name="plan_route_exit_dialog_descr">Weet u zeker dat u alle wijzigingen in de geplande route wilt annuleren door deze te sluiten\?</string>
<string name="plan_route_trim_before">Opsmukken voor</string>
<string name="plan_route_trim_after">Opsmukken na</string>
<string name="plan_route_trim_before">"Alles wegknippen voor dit punt"</string>
<string name="plan_route_trim_after">Alles wegknippen na dit punt</string>
<string name="plan_route_change_route_type_before">Wijzig het routetype voor</string>
<string name="plan_route_change_route_type_after">Wijzig het routetype na</string>
<string name="release_3_8">• Bijgewerkt Plan een route functie: maakt het mogelijk om verschillende navigatietypes per segment te gebruiken en tracks op te nemen
@ -3925,4 +3925,19 @@
<string name="register_on_openplacereviews_desc">Foto\'s zijn afkomstig van het open data-project OpenPlaceReviews.org. Om uw foto\'s te uploaden, moet u zich aanmelden op deze website.</string>
<string name="register_opr_create_new_account">Maak een nieuw account aan</string>
<string name="register_opr_have_account">Ik heb al een account</string>
<string name="ltr_or_rtl_combine_via_star">%1$s * %2$s</string>
<string name="app_mode_light_aircraft">Licht vliegtuig</string>
<string name="plan_route_join_segments">Segmenten samenvoegen</string>
<string name="plan_route_split_before">Hiervoor splitsen</string>
<string name="plan_route_split_after">Hierna splitsen</string>
<string name="plan_route_add_new_segment">Nieuw segment toevoegen</string>
<string name="profile_type_osmand_string">OsmAnd-profiel</string>
<string name="profile_type_user_string">Gebruikersprofiel</string>
<string name="reverse_all_points">Volgorde van alle punten omdraaien</string>
<string name="shared_string_last_used">Laatst gebruikt</string>
<string name="routing_attr_prefer_hiking_routes_description">Voorkeur voor ongebaande wandelwegen</string>
<string name="routing_attr_prefer_hiking_routes_name">Voorkeur voor Hike-routes</string>
<string name="routing_attr_allow_streams_description">Beken en sloten gebruiken</string>
<string name="routing_attr_allow_streams_name">Beken en sloten gebruiken</string>
<string name="routing_attr_allow_intermittent_description">Waterwegen die periodiek water voeren gebruiken</string>
</resources>

View file

@ -4022,4 +4022,20 @@
<string name="routing_attr_allow_streams_name">Permitir riachos e drenos</string>
<string name="routing_attr_allow_intermittent_description">Permitir vias de água intermitentes</string>
<string name="routing_attr_allow_intermittent_name">Permitir vias de água intermitentes</string>
<string name="voice_prompts_timetable">Horários de avisos de voz</string>
<string name="add_online_routing_engine">Adicionar mecanismo de roteamento online</string>
<string name="edit_online_routing_engine">Editar mecanismo de roteamento online</string>
<string name="shared_string_subtype">Subtipo</string>
<string name="shared_string_vehicle">Veículo</string>
<string name="shared_string_api_key">Chave de API</string>
<string name="shared_string_server_url">URL do servidor</string>
<string name="shared_string_enter_param">Digite o parâmetro</string>
<string name="keep_it_empty_if_not">Mantenha-o vazio se não</string>
<string name="online_routing_example_hint">O URL com todos os parâmetros terá a seguinte aparência:</string>
<string name="test_route_calculation">Cálculo da rota de teste</string>
<string name="routing_engine_vehicle_type_driving">Dirigindo</string>
<string name="routing_engine_vehicle_type_foot"></string>
<string name="routing_engine_vehicle_type_bike">Bicicleta</string>
<string name="routing_engine_vehicle_type_car">Carro</string>
<string name="message_error_recheck_parameters">Erro, verifique novamente os parâmetros</string>
</resources>

View file

@ -4024,4 +4024,5 @@
<string name="routing_attr_allow_streams_name">Permite flussos e iscàrrigos</string>
<string name="routing_attr_allow_intermittent_description">Permite caminos de abba intermitentes</string>
<string name="routing_attr_allow_intermittent_name">Permite caminos de abba intermitentes</string>
<string name="voice_prompts_timetable">Nùmeru de annùntzios vocales</string>
</resources>

View file

@ -4027,4 +4027,5 @@
<string name="routing_attr_allow_streams_name">Povoliť potoky a odtokové kanály</string>
<string name="routing_attr_allow_intermittent_description">Povoliť dočasné vodné toky</string>
<string name="routing_attr_allow_intermittent_name">Povoliť dočasné vodné toky</string>
<string name="voice_prompts_timetable">Časy hlasových pokynov</string>
</resources>

View file

@ -3098,4 +3098,7 @@ Vänligen tillhandahåll fullständig kod</string>
<string name="osm_live_payment_desc_hw">Betalningen för prenumerationen är i enlighet med vad som valts. Du kan avbryta den i AppGallery när som hellst.</string>
<string name="routing_attr_avoid_footways_description">Undvik resor till fots</string>
<string name="routing_attr_avoid_footways_name">Undvik resor till</string>
<string name="search_download_wikipedia_maps">Ladda ner Wikipedia kartor</string>
<string name="plugin_wikipedia_description">Få information om sevärdheter från Wikipedia, en inbunden samling av offline artiklar om ställen och destinationer.</string>
<string name="track_show_start_finish_icons"></string>
</resources>

View file

@ -944,7 +944,7 @@
<string name="poi_route_hiking_lwn_poi">Yerel yürüyüş güzergahı</string>
<string name="poi_route_hiking_ref_poi">Yürüyüş yolu başvurusu</string>
<string name="poi_opening_hours">Çalışma saatleri</string>
<string name="poi_collection_times">Koleksiyon kez</string>
<string name="poi_collection_times">Toplama zamanları</string>
<string name="poi_description">ıklama</string>
<string name="poi_phone">Telefon</string>
<string name="poi_website">İnternet sitesi</string>
@ -3137,4 +3137,12 @@
<string name="poi_diplomatic_services_non_immigrant_visas_filter">Göçmen olmayan vizeleri</string>
<string name="poi_military_checkpoint">Askeri denetim noktası</string>
<string name="poi_checkpoint_hiking">Yürüyüş denetim noktası</string>
<string name="poi_street_cabinet_street_lighting">Dolap türü: sokak aydınlatması</string>
<string name="poi_street_cabinet_water_management">Dolap türü: su yönetimi</string>
<string name="poi_street_cabinet_waste">Dolap türü: atık</string>
<string name="poi_street_cabinet_postal_service">Dolap türü: posta hizmeti</string>
<string name="poi_street_cabinet_gas">Dolap türü: gaz</string>
<string name="poi_street_cabinet_cable_tv">Dolap türü: kablo tv</string>
<string name="poi_street_cabinet_telecom">Dolap türü: telefon</string>
<string name="poi_street_cabinet_power">Dolap türü: elektrik</string>
</resources>

View file

@ -1701,7 +1701,7 @@
<string name="remove_the_tag">Etiketi Kaldır</string>
<string name="version_settings_descr">Gecelik derlemeleri indir.</string>
<string name="version_settings">kurar</string>
<string name="rendering_attr_streetLighting_name">Sokak Aydınlatma</string>
<string name="rendering_attr_streetLighting_name">Sokak aydınlatma</string>
<string name="proxy_pref_title">Vekil sunucu</string>
<string name="proxy_pref_descr">Bir proxy sunucusu belirtin.</string>
<string name="settings_privacy">Gizlilik</string>
@ -3982,4 +3982,20 @@
<string name="routing_attr_allow_streams_name">Dere ve kanalizasyonlara izin ver</string>
<string name="routing_attr_allow_intermittent_description">Aralıklı su yollarına izin verin</string>
<string name="routing_attr_allow_intermittent_name">Aralıklı su yollarına izin ver</string>
<string name="voice_prompts_timetable">Sesli uyarı zamanları</string>
<string name="add_online_routing_engine">Çevrim içi yönlendirme motoru ekle</string>
<string name="edit_online_routing_engine">Çevrim içi yönlendirme motorunu düzenle</string>
<string name="shared_string_subtype">Alt tür</string>
<string name="shared_string_vehicle">Araç</string>
<string name="shared_string_api_key">API anahtarı</string>
<string name="shared_string_server_url">Sunucu URL\'si</string>
<string name="shared_string_enter_param">Parametre gir</string>
<string name="keep_it_empty_if_not">Değilse boş tut</string>
<string name="online_routing_example_hint">Tüm parametreleri ile URL şu şekilde görünecektir:</string>
<string name="test_route_calculation">Güzergah hesaplamayı test et</string>
<string name="routing_engine_vehicle_type_driving">Araba sürme</string>
<string name="routing_engine_vehicle_type_foot">Yürüme</string>
<string name="routing_engine_vehicle_type_bike">Bisiklet</string>
<string name="routing_engine_vehicle_type_car">Araba</string>
<string name="message_error_recheck_parameters">Hata, parametreleri tekrar gözden geçirin</string>
</resources>

View file

@ -4023,4 +4023,20 @@
<string name="routing_attr_prefer_hiking_routes_name">Надавати перевагу пішохідним маршрутам</string>
<string name="routing_attr_allow_streams_description">Дозволити потоки та стічні канали</string>
<string name="routing_attr_allow_streams_name">Дозволити потоки та стічні канали</string>
<string name="voice_prompts_timetable">Голосові підказки</string>
<string name="add_online_routing_engine">Додати мережний рушій маршрутизації</string>
<string name="edit_online_routing_engine">Змінити мережний рушій маршрутизації</string>
<string name="shared_string_subtype">Підтип</string>
<string name="shared_string_vehicle">Транспортний засіб</string>
<string name="shared_string_api_key">Ключ API</string>
<string name="shared_string_server_url">URL-адреса сервера</string>
<string name="shared_string_enter_param">Введіть параметр</string>
<string name="keep_it_empty_if_not">Залиште порожнім, якщо ні</string>
<string name="online_routing_example_hint">URL-адреса з усіма параметрами виглядатиме так:</string>
<string name="test_route_calculation">Тестове обчислення маршруту</string>
<string name="routing_engine_vehicle_type_driving">Водіння</string>
<string name="routing_engine_vehicle_type_foot">Пішки</string>
<string name="routing_engine_vehicle_type_bike">Велосипед</string>
<string name="routing_engine_vehicle_type_car">Автомобіль</string>
<string name="message_error_recheck_parameters">Помилка, повторно перевірте параметри</string>
</resources>

View file

@ -3898,4 +3898,7 @@
<string name="poi_lavoir">公共洗衣區</string>
<string name="poi_waste_transfer_station">垃圾站</string>
<string name="poi_swimming_area">游泳區</string>
<string name="poi_wildlife_crossing_bat_tunnel">蝙蝠隧道</string>
<string name="poi_wildlife_crossing_bat_bridge">蝙蝠橋</string>
<string name="poi_wildlife_crossing">野生動物穿越</string>
</resources>

View file

@ -4022,4 +4022,5 @@
<string name="routing_attr_allow_streams_name">允許溪流與水溝</string>
<string name="routing_attr_allow_intermittent_description">允許間歇水路</string>
<string name="routing_attr_allow_intermittent_name">允許間歇水路</string>
<string name="voice_prompts_timetable">語音提示時間</string>
</resources>

View file

@ -12,6 +12,22 @@
-->
<string name="copy_address">Copy address</string>
<string name="message_error_recheck_parameters">Error, recheck parameters</string>
<string name="routing_engine_vehicle_type_car">Car</string>
<string name="routing_engine_vehicle_type_bike">Bike</string>
<string name="routing_engine_vehicle_type_foot">Foot</string>
<string name="routing_engine_vehicle_type_driving">Driving</string>
<string name="test_route_calculation">Test route calculation</string>
<string name="online_routing_example_hint">URL with all parameters will look like this:</string>
<string name="keep_it_empty_if_not">Keep it empty if not</string>
<string name="shared_string_enter_param">Enter param</string>
<string name="shared_string_server_url">Server URL</string>
<string name="shared_string_api_key">API key</string>
<string name="shared_string_vehicle">Vehicle</string>
<string name="shared_string_subtype">Subtype</string>
<string name="edit_online_routing_engine">Edit online routing engine</string>
<string name="add_online_routing_engine">Add online routing engine</string>
<string name="routing_attr_allow_intermittent_name">Allow intermittent water ways</string>
<string name="routing_attr_allow_intermittent_description">Allow intermittent water ways</string>
<string name="routing_attr_allow_streams_name">Allow streams and drains</string>
@ -4012,5 +4028,4 @@
<string name="routing_attr_freeride_policy_description">\'Freeride\' and \'Off-piste\' are unofficial routes and passages. Typically ungroomed, unmaintained and not checked in the evening. Enter at your own risk.</string>
<string name="voice_prompts_timetable">Voice prompts times</string>
</resources>

View file

@ -23,12 +23,13 @@ import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;
import net.osmand.plus.widgets.WebViewEx;
/**
* WebView that its scroll position can be observed.
*/
public class ObservableWebView extends WebView implements Scrollable {
public class ObservableWebView extends WebViewEx implements Scrollable {
// Fields that should be saved onSaveInstanceState
private int mPrevScrollY;

View file

@ -44,6 +44,7 @@ import net.osmand.plus.mapmarkers.MapMarkersDbHelper;
import net.osmand.plus.mapmarkers.MapMarkersHelper;
import net.osmand.plus.monitoring.LiveMonitoringHelper;
import net.osmand.plus.monitoring.OsmandMonitoringPlugin;
import net.osmand.plus.onlinerouting.OnlineRoutingHelper;
import net.osmand.plus.osmedit.oauth.OsmOAuthHelper;
import net.osmand.plus.poi.PoiFiltersHelper;
import net.osmand.plus.quickaction.QuickActionRegistry;
@ -467,6 +468,7 @@ public class AppInitializer implements IProgress {
app.settingsHelper = startupInit(new SettingsHelper(app), SettingsHelper.class);
app.quickActionRegistry = startupInit(new QuickActionRegistry(app.getSettings()), QuickActionRegistry.class);
app.osmOAuthHelper = startupInit(new OsmOAuthHelper(app), OsmOAuthHelper.class);
app.onlineRoutingHelper = startupInit(new OnlineRoutingHelper(app), OnlineRoutingHelper.class);
initOpeningHoursParser();
}

View file

@ -66,6 +66,7 @@ import net.osmand.plus.mapmarkers.MapMarkersDbHelper;
import net.osmand.plus.mapmarkers.MapMarkersHelper;
import net.osmand.plus.measurementtool.MeasurementEditingContext;
import net.osmand.plus.monitoring.LiveMonitoringHelper;
import net.osmand.plus.onlinerouting.OnlineRoutingHelper;
import net.osmand.plus.osmedit.oauth.OsmOAuthHelper;
import net.osmand.plus.poi.PoiFiltersHelper;
import net.osmand.plus.quickaction.QuickActionRegistry;
@ -158,6 +159,7 @@ public class OsmandApplication extends MultiDexApplication {
QuickActionRegistry quickActionRegistry;
OsmOAuthHelper osmOAuthHelper;
MeasurementEditingContext measurementEditingContext;
OnlineRoutingHelper onlineRoutingHelper;
private Resources localizedResources;
private Map<String, Builder> customRoutingConfigs = new ConcurrentHashMap<>();
@ -475,6 +477,10 @@ public class OsmandApplication extends MultiDexApplication {
this.measurementEditingContext = context;
}
public OnlineRoutingHelper getOnlineRoutingHelper() {
return onlineRoutingHelper;
}
public TransportRoutingHelper getTransportRoutingHelper() {
return transportRoutingHelper;
}

View file

@ -3,7 +3,6 @@
*/
package net.osmand.plus.activities;
import android.view.Window;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import android.annotation.SuppressLint;

View file

@ -576,7 +576,7 @@ public class DownloadIndexesThread {
manager.indexVoiceFiles(this);
manager.indexFontFiles(this);
if (vectorMapsToReindex) {
warnings = manager.indexingMaps(this);
warnings = manager.indexingMaps(this, filesToReindex);
}
List<String> wns = manager.indexAdditionalMaps(this);
if (wns != null) {

View file

@ -103,6 +103,14 @@ public class AndroidUiHelper {
}
}
public static void setVisibility(int visibility, View ... views) {
for (View view : views) {
if (view != null && view.getVisibility() != visibility) {
view.setVisibility(visibility);
}
}
}
public static boolean isXLargeDevice(@NonNull Activity ctx) {
int lt = (ctx.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK);
return lt == Configuration.SCREENLAYOUT_SIZE_XLARGE;

View file

@ -90,7 +90,7 @@ public class IntentHelper {
String zoom = data.getQueryParameter("z");
int z = settings.getLastKnownMapZoom();
if (zoom != null) {
z = Integer.parseInt(zoom);
z = (int) Double.parseDouble(zoom);
}
settings.setMapLocationToShow(lt, ln, z, new PointDescription(lt, ln));
} catch (NumberFormatException e) {

View file

@ -29,20 +29,29 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.view.ContextThemeWrapper;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;
import net.osmand.AndroidUtils;
import net.osmand.PlatformUtil;
import net.osmand.data.Amenity;
import net.osmand.data.LatLon;
import net.osmand.data.PointDescription;
import net.osmand.data.QuadRect;
import net.osmand.osm.PoiCategory;
import net.osmand.osm.PoiType;
import net.osmand.osm.io.NetworkUtils;
import net.osmand.plus.*;
import net.osmand.plus.OsmAndFormatter;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.OsmandPlugin;
import net.osmand.plus.R;
import net.osmand.plus.UiUtilities;
import net.osmand.plus.Version;
import net.osmand.plus.activities.ActivityResultListener;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.helpers.FontCache;
@ -51,6 +60,7 @@ import net.osmand.plus.mapcontextmenu.builders.cards.CardsRowBuilder;
import net.osmand.plus.mapcontextmenu.builders.cards.ImageCard;
import net.osmand.plus.mapcontextmenu.builders.cards.ImageCard.GetImageCardsTask;
import net.osmand.plus.mapcontextmenu.builders.cards.NoImagesCard;
import net.osmand.plus.mapcontextmenu.controllers.AmenityMenuController;
import net.osmand.plus.mapcontextmenu.controllers.TransportStopController;
import net.osmand.plus.openplacereviews.AddPhotosBottomSheetDialogFragment;
import net.osmand.plus.openplacereviews.OPRConstants;
@ -65,6 +75,7 @@ import net.osmand.plus.widgets.TextViewEx;
import net.osmand.plus.widgets.tools.ClickableSpanTouchListener;
import net.osmand.util.Algorithms;
import net.osmand.util.MapUtils;
import org.apache.commons.logging.Log;
import org.openplacereviews.opendb.util.exception.FailedVerificationException;
@ -72,7 +83,13 @@ import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static net.osmand.plus.mapcontextmenu.builders.cards.ImageCard.GetImageCardsTask.GetImageCardsListener;
@ -91,13 +108,15 @@ public class MenuBuilder {
private boolean firstRow;
protected boolean matchWidthDivider;
protected boolean light;
private long objectId;
private Amenity amenity;
private LatLon latLon;
private boolean hidden;
private boolean showTitleIfTruncated = true;
private boolean showNearestWiki = false;
private boolean showNearestPoi = false;
private boolean showOnlinePhotos = true;
protected List<Amenity> nearestWiki = new ArrayList<>();
protected List<Amenity> nearestPoi = new ArrayList<>();
private List<OsmandPlugin> menuPlugins = new ArrayList<>();
@Nullable
private CardsRowBuilder onlinePhotoCardsRow;
@ -208,10 +227,18 @@ public class MenuBuilder {
return showNearestWiki;
}
public boolean isShowNearestPoi() {
return showNearestPoi;
}
public void setShowNearestWiki(boolean showNearestWiki) {
this.showNearestWiki = showNearestWiki;
}
public void setShowNearestPoi(boolean showNearestPoi) {
this.showNearestPoi = showNearestPoi;
}
public void setShowTitleIfTruncated(boolean showTitleIfTruncated) {
this.showTitleIfTruncated = showTitleIfTruncated;
}
@ -224,9 +251,8 @@ public class MenuBuilder {
this.showOnlinePhotos = showOnlinePhotos;
}
public void setShowNearestWiki(boolean showNearestWiki, long objectId) {
this.objectId = objectId;
this.showNearestWiki = showNearestWiki;
public void setAmenity(Amenity amenity) {
this.amenity = amenity;
}
public void addMenuPlugin(OsmandPlugin plugin) {
@ -246,6 +272,7 @@ public class MenuBuilder {
buildTitleRow(view);
}
buildNearestWikiRow(view);
buildNearestPoiRow(view);
if (needBuildPlainMenuItems()) {
buildPlainMenuItems(view);
}
@ -325,10 +352,20 @@ public class MenuBuilder {
}
protected void buildNearestWikiRow(View view) {
if (processNearestWiki() && nearestWiki.size() > 0) {
buildRow(view, R.drawable.ic_action_wikipedia, null, app.getString(R.string.wiki_around) + " (" + nearestWiki.size() + ")", 0,
true, getCollapsableWikiView(view.getContext(), true),
false, 0, false, null, false);
buildNearestRow(view, nearestWiki, processNearestWiki(),
R.drawable.ic_action_wikipedia, app.getString(R.string.wiki_around));
}
protected void buildNearestPoiRow(View view) {
buildNearestRow(view, nearestPoi, processNearestPoi(),
nearestPoi.isEmpty() ? 0 : AmenityMenuController.getRightIconId(nearestPoi.get(0)),
app.getString(R.string.speak_poi));
}
protected void buildNearestRow(View view, List<Amenity> nearestAmenities, boolean process, int iconId, String text) {
if (process && nearestAmenities.size() > 0) {
buildRow(view, iconId, null, text + " (" + nearestAmenities.size() + ")", 0, true,
getCollapsableView(view.getContext(), true, nearestAmenities), false, 0, false, null, false);
}
}
@ -1118,20 +1155,23 @@ public class MenuBuilder {
return new CollapsableView(textView, this, collapsed);
}
protected CollapsableView getCollapsableWikiView(Context context, boolean collapsed) {
protected CollapsableView getCollapsableView(Context context, boolean collapsed, List<Amenity> nearestAmenities) {
LinearLayout view = (LinearLayout) buildCollapsableContentView(context, collapsed, true);
for (final Amenity wiki : nearestWiki) {
for (final Amenity poi : nearestAmenities) {
TextViewEx button = buildButtonInCollapsableView(context, false, false);
String name = wiki.getName(preferredMapAppLang, transliterateNames);
String name = poi.getName(preferredMapAppLang, transliterateNames);
if (Algorithms.isBlank(name)) {
name = AmenityMenuController.getTypeStr(poi);
}
button.setText(name);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LatLon latLon = new LatLon(wiki.getLocation().getLatitude(), wiki.getLocation().getLongitude());
PointDescription pointDescription = mapActivity.getMapLayers().getPoiMapLayer().getObjectName(wiki);
mapActivity.getContextMenu().show(latLon, pointDescription, wiki);
LatLon latLon = new LatLon(poi.getLocation().getLatitude(), poi.getLocation().getLongitude());
PointDescription pointDescription = mapActivity.getMapLayers().getPoiMapLayer().getObjectName(poi);
mapActivity.getContextMenu().show(latLon, pointDescription, poi);
}
});
view.addView(button);
@ -1191,14 +1231,36 @@ public class MenuBuilder {
}
protected boolean processNearestWiki() {
if (showNearestWiki && latLon != null) {
QuadRect rect = MapUtils.calculateLatLonBbox(
latLon.getLatitude(), latLon.getLongitude(), 250);
PoiUIFilter wikiPoiFilter = app.getPoiFilters().getTopWikiPoiFilter();
if (showNearestWiki && latLon != null && amenity != null) {
PoiUIFilter filter = app.getPoiFilters().getTopWikiPoiFilter();
if (filter != null) {
nearestWiki = getSortedAmenities(filter, latLon);
return true;
}
}
return false;
}
nearestWiki = getAmenities(rect, wikiPoiFilter);
protected boolean processNearestPoi() {
if (showNearestPoi && latLon != null && amenity != null) {
PoiCategory pc = amenity.getType();
PoiType pt = pc.getPoiTypeByKeyName(amenity.getSubType());
PoiUIFilter filter = app.getPoiFilters().getFilterById(PoiUIFilter.STD_PREFIX + pt.getKeyName());
if (filter != null) {
nearestPoi = getSortedAmenities(filter, latLon);
return true;
}
}
return false;
}
Collections.sort(nearestWiki, new Comparator<Amenity>() {
private List<Amenity> getSortedAmenities(PoiUIFilter filter, final LatLon latLon) {
QuadRect rect = MapUtils.calculateLatLonBbox(latLon.getLatitude(), latLon.getLongitude(), 250);
List<Amenity> nearestAmenities = getAmenities(rect, filter);
nearestAmenities.remove(amenity);
Collections.sort(nearestAmenities, new Comparator<Amenity>() {
@Override
public int compare(Amenity o1, Amenity o2) {
@ -1207,17 +1269,8 @@ public class MenuBuilder {
return Double.compare(d1, d2);
}
});
Long id = objectId;
List<Amenity> wikiList = new ArrayList<>();
for (Amenity wiki : nearestWiki) {
if (wiki.getId().equals(id)) {
wikiList.add(wiki);
}
}
nearestWiki.removeAll(wikiList);
return true;
}
return false;
return nearestAmenities;
}
private List<Amenity> getAmenities(QuadRect rect, PoiUIFilter wikiPoiFilter) {

View file

@ -35,6 +35,7 @@ import net.osmand.plus.helpers.FontCache;
import net.osmand.plus.helpers.enums.MetricsConstants;
import net.osmand.plus.mapcontextmenu.CollapsableView;
import net.osmand.plus.mapcontextmenu.MenuBuilder;
import net.osmand.plus.mapcontextmenu.controllers.AmenityMenuController;
import net.osmand.plus.osmedit.OsmEditingPlugin;
import net.osmand.plus.poi.PoiUIFilter;
import net.osmand.plus.views.layers.POIMapLayer;
@ -78,7 +79,9 @@ public class AmenityMenuBuilder extends MenuBuilder {
public AmenityMenuBuilder(@NonNull MapActivity mapActivity, final @NonNull Amenity amenity) {
super(mapActivity);
this.amenity = amenity;
setShowNearestWiki(true, amenity.getId());
setAmenity(amenity);
setShowNearestWiki(true);
setShowNearestPoi(!amenity.getType().isWiki());
metricSystem = mapActivity.getMyApplication().getSettings().METRIC_SYSTEM.get();
}
@ -86,6 +89,10 @@ public class AmenityMenuBuilder extends MenuBuilder {
protected void buildNearestWikiRow(View view) {
}
@Override
protected void buildNearestPoiRow(View view) {
}
private void buildRow(View view, int iconId, String text, String textPrefix,
boolean collapsable, final CollapsableView collapsableView,
int textColor, boolean isWiki, boolean isText, boolean needLinks,
@ -664,11 +671,19 @@ public class AmenityMenuBuilder extends MenuBuilder {
if (processNearestWiki() && nearestWiki.size() > 0) {
AmenityInfoRow wikiInfo = new AmenityInfoRow(
"nearest_wiki", R.drawable.ic_plugin_wikipedia, null, app.getString(R.string.wiki_around) + " (" + nearestWiki.size() + ")", true,
getCollapsableWikiView(view.getContext(), true),
getCollapsableView(view.getContext(), true, nearestWiki),
0, false, false, false, 1000, null, false, false, false, 0);
buildAmenityRow(view, wikiInfo);
}
if (processNearestPoi() && nearestPoi.size() > 0) {
AmenityInfoRow poiInfo = new AmenityInfoRow(
"nearest_poi", AmenityMenuController.getRightIconId(amenity), null, app.getString(R.string.speak_poi) + " (" + nearestPoi.size() + ")", true,
getCollapsableView(view.getContext(), true, nearestPoi),
0, false, false, false, 1000, null, false, false, false, 0);
buildAmenityRow(view, poiInfo);
}
if (osmEditingEnabled && amenity.getId() != null
&& amenity.getId() > 0 &&
(amenity.getId() % 2 == 0 || (amenity.getId() >> 1) < Integer.MAX_VALUE)) {

View file

@ -59,9 +59,9 @@ public class FavouritePointMenuBuilder extends MenuBuilder {
}
@Override
protected void buildNearestWikiRow(View view) {
protected void buildNearestRow(View view, List<Amenity> nearestAmenities, boolean process, int iconId, String text) {
if (originObject == null || !(originObject instanceof Amenity)) {
super.buildNearestWikiRow(view);
super.buildNearestRow(view, nearestAmenities, process, iconId, text);
}
}

View file

@ -1,6 +1,7 @@
package net.osmand.plus.mapcontextmenu.builders.cards;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.Intent;
@ -23,6 +24,7 @@ import androidx.core.content.ContextCompat;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.widgets.WebViewEx;
public abstract class AbstractCard {
@ -55,7 +57,7 @@ public abstract class AbstractCard {
@SuppressLint("SetJavaScriptEnabled")
@SuppressWarnings("deprecation")
protected static void openUrl(@NonNull Context ctx,
protected static void openUrl(@NonNull Activity ctx,
@NonNull OsmandApplication app,
@Nullable String title,
@NonNull String url,
@ -90,7 +92,7 @@ public abstract class AbstractCard {
}
});
final WebView wv = new WebView(ctx);
final WebView wv = new WebViewEx(ctx);
wv.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {

View file

@ -116,7 +116,7 @@ public class AmenityMenuController extends MenuController {
return getRightIconId(amenity);
}
private static int getRightIconId(Amenity amenity) {
public static int getRightIconId(Amenity amenity) {
String id = null;
PoiType st = amenity.getType().getPoiTypeByKeyName(amenity.getSubType());
if (st != null) {

View file

@ -67,8 +67,9 @@ public class HorizontalSelectionAdapter extends RecyclerView.Adapter<HorizontalS
textView.setPadding(innerPadding, 0, innerPadding, 0);
int activeColorResId = nightMode ? R.color.active_color_primary_dark : R.color.active_color_primary_light;
if (item.equals(selectedItem) && item.isEnabled()) {
AndroidUtils.setBackground(holder.button, app.getUIUtilities().getPaintedIcon(
R.drawable.bg_select_icon_group_button, ContextCompat.getColor(app, activeColorResId)));
AndroidUtils.setBackground(holder.button, UiUtilities.createTintedDrawable(app,
R.drawable.bg_select_icon_group_button,
ContextCompat.getColor(app, activeColorResId)));
textView.setTextColor(ContextCompat.getColor(app, R.color.color_white));
} else {
if (!item.isEnabled()) {

View file

@ -36,6 +36,7 @@ public class ShareMenu extends BaseMenuController {
public enum ShareItem {
MESSAGE(R.drawable.ic_action_message, R.string.shared_string_send),
CLIPBOARD(R.drawable.ic_action_copy, R.string.shared_string_copy),
ADDRESS(R.drawable.ic_action_copy, R.string.copy_address),
NAME(R.drawable.ic_action_copy, R.string.copy_location_name),
COORDINATES(R.drawable.ic_action_copy, R.string.copy_coordinates),
GEO(R.drawable.ic_world_globe_dark, R.string.share_geo),
@ -66,6 +67,7 @@ public class ShareMenu extends BaseMenuController {
List<ShareItem> list = new LinkedList<>();
list.add(ShareItem.MESSAGE);
list.add(ShareItem.CLIPBOARD);
list.add(ShareItem.ADDRESS);
list.add(ShareItem.NAME);
list.add(ShareItem.COORDINATES);
list.add(ShareItem.GEO);
@ -121,6 +123,15 @@ public class ShareMenu extends BaseMenuController {
case CLIPBOARD:
ShareDialog.sendToClipboard(mapActivity, sms);
break;
case ADDRESS:
if (!Algorithms.isEmpty(address)) {
ShareDialog.sendToClipboard(mapActivity, address);
} else {
Toast.makeText(mapActivity,
R.string.no_address_found,
Toast.LENGTH_LONG).show();
}
break;
case NAME:
if (!Algorithms.isEmpty(title)) {
ShareDialog.sendToClipboard(mapActivity, title);

View file

@ -19,6 +19,7 @@ import com.google.android.material.appbar.AppBarLayout;
import net.osmand.AndroidUtils;
import net.osmand.plus.R;
import net.osmand.plus.base.BaseOsmAndDialogFragment;
import net.osmand.plus.widgets.WebViewEx;
public class GpxDescriptionDialogFragment extends BaseOsmAndDialogFragment {
@ -48,7 +49,7 @@ public class GpxDescriptionDialogFragment extends BaseOsmAndDialogFragment {
AppBarLayout appBar = new AppBarLayout(ctx);
appBar.addView(topBar);
WebView webView = new WebView(ctx);
WebView webView = new WebViewEx(ctx);
webView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
webView.getSettings().setTextZoom((int) (getResources().getConfiguration().fontScale * 100f));
Bundle args = getArguments();

View file

@ -0,0 +1,196 @@
package net.osmand.plus.onlinerouting;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
import android.widget.EditText;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import net.osmand.AndroidUtils;
import net.osmand.CallbackWithObject;
import net.osmand.plus.R;
import net.osmand.plus.UiUtilities;
import net.osmand.plus.UiUtilities.DialogButtonType;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.helpers.AndroidUiHelper;
import net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter;
import net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter.HorizontalSelectionAdapterListener;
import net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter.HorizontalSelectionItem;
import net.osmand.plus.routepreparationmenu.cards.BaseCard;
import net.osmand.plus.widgets.OsmandTextFieldBoxes;
import java.util.List;
public class OnlineRoutingCard extends BaseCard {
private View headerContainer;
private TextView tvHeaderTitle;
private TextView tvHeaderSubtitle;
private RecyclerView rvSelectionMenu;
private HorizontalSelectionAdapter adapter;
private TextView tvDescription;
private View fieldBoxContainer;
private OsmandTextFieldBoxes textFieldBoxes;
private EditText editText;
private TextView tvHelperText;
private View bottomDivider;
private View button;
private OnTextChangedListener onTextChangedListener;
public OnlineRoutingCard(@NonNull MapActivity mapActivity, boolean nightMode) {
super(mapActivity);
this.nightMode = nightMode;
}
@Override
public int getCardLayoutId() {
return R.layout.online_routing_preference_segment;
}
@Override
protected void updateContent() {
headerContainer = view.findViewById(R.id.header);
tvHeaderTitle = view.findViewById(R.id.title);
tvHeaderSubtitle = view.findViewById(R.id.subtitle);
rvSelectionMenu = view.findViewById(R.id.selection_menu);
tvDescription = view.findViewById(R.id.description);
fieldBoxContainer = view.findViewById(R.id.field_box_container);
textFieldBoxes = view.findViewById(R.id.field_box);
editText = view.findViewById(R.id.edit_text);
tvHelperText = view.findViewById(R.id.helper_text);
bottomDivider = view.findViewById(R.id.bottom_divider);
button = view.findViewById(R.id.button);
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
if (onTextChangedListener != null) {
boolean editedByUser = editText.getTag() == null;
String text = editText.getText().toString();
onTextChangedListener.onTextChanged(editedByUser, text);
}
}
});
editText.setOnFocusChangeListener(new OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus) {
editText.setSelection(editText.getText().length());
AndroidUtils.showSoftKeyboard(getMapActivity(), editText);
}
}
});
}
public void setHeaderTitle(@NonNull String title) {
showElements(headerContainer, tvHeaderTitle);
tvHeaderTitle.setText(title);
}
public void setHeaderSubtitle(@NonNull String subtitle) {
showElements(headerContainer, tvHeaderSubtitle);
tvHeaderSubtitle.setText(subtitle);
}
public void setSelectionMenu(List<HorizontalSelectionItem> items,
String selectedItemTitle,
final CallbackWithObject<HorizontalSelectionItem> callback) {
showElements(rvSelectionMenu);
rvSelectionMenu.setLayoutManager(
new LinearLayoutManager(app, RecyclerView.HORIZONTAL, false));
adapter = new HorizontalSelectionAdapter(app, nightMode);
adapter.setItems(items);
adapter.setSelectedItemByTitle(selectedItemTitle);
adapter.setListener(new HorizontalSelectionAdapterListener() {
@Override
public void onItemSelected(HorizontalSelectionItem item) {
if (callback.processResult(item)) {
adapter.setSelectedItem(item);
}
}
});
rvSelectionMenu.setAdapter(adapter);
}
public void setDescription(@NonNull String description) {
showElements(tvDescription);
tvDescription.setText(description);
}
public void setFieldBoxLabelText(@NonNull String labelText) {
showElements(fieldBoxContainer);
textFieldBoxes.setLabelText(labelText);
}
public void setFieldBoxHelperText(@NonNull String helperText) {
showElements(fieldBoxContainer, tvHelperText);
tvHelperText.setText(helperText);
}
public void setEditedText(@NonNull String text) {
editText.setTag(""); // needed to indicate that the text was edited programmatically
editText.setText(text);
editText.setTag(null);
}
public String getEditedText() {
return editText.getText().toString();
}
public void showDivider() {
showElements(bottomDivider);
}
public void setButton(String title, OnClickListener listener) {
showElements(button);
button.setOnClickListener(listener);
UiUtilities.setupDialogButton(nightMode, button, DialogButtonType.PRIMARY, title);
}
public void show() {
showElements(view);
}
public void hide() {
hideElements(view);
}
public void showFieldBox() {
showElements(fieldBoxContainer);
}
public void hideFieldBox() {
hideElements(fieldBoxContainer);
}
private void showElements(View... views) {
AndroidUiHelper.setVisibility(View.VISIBLE, views);
}
private void hideElements(View... views) {
AndroidUiHelper.setVisibility(View.GONE, views);
}
public void setOnTextChangedListener(OnTextChangedListener onTextChangedListener) {
this.onTextChangedListener = onTextChangedListener;
}
public interface OnTextChangedListener {
void onTextChanged(boolean editedByUser, String text);
}
}

View file

@ -0,0 +1,106 @@
package net.osmand.plus.onlinerouting;
import android.content.Context;
import androidx.annotation.NonNull;
import net.osmand.plus.R;
import net.osmand.util.Algorithms;
import java.util.HashMap;
import java.util.Map;
public class OnlineRoutingEngine {
public final static String ONLINE_ROUTING_ENGINE_PREFIX = "online_routing_engine_";
public enum EngineParameterType {
CUSTOM_SERVER_URL,
CUSTOM_NAME,
API_KEY
}
private String stringKey;
private ServerType serverType;
private String vehicleKey;
private Map<String, String> params = new HashMap<>();
public OnlineRoutingEngine(@NonNull String stringKey,
@NonNull ServerType serverType,
@NonNull String vehicleKey,
Map<String, String> params) {
this(stringKey, serverType, vehicleKey);
this.params = params;
}
public OnlineRoutingEngine(@NonNull String stringKey,
@NonNull ServerType serverType,
@NonNull String vehicleKey) {
this.stringKey = stringKey;
this.serverType = serverType;
this.vehicleKey = vehicleKey;
}
public String getStringKey() {
return stringKey;
}
public ServerType getServerType() {
return serverType;
}
public String getVehicleKey() {
return vehicleKey;
}
public Map<String, String> getParams() {
return params;
}
public String getBaseUrl() {
String customServerUrl = getParameter(EngineParameterType.CUSTOM_SERVER_URL);
if (!Algorithms.isEmpty(customServerUrl)) {
return customServerUrl;
} else {
return serverType.getBaseUrl();
}
}
public String getParameter(EngineParameterType paramType) {
return params.get(paramType.name());
}
public void putParameter(EngineParameterType paramType, String paramValue) {
params.put(paramType.name(), paramValue);
}
public String getName(@NonNull Context ctx) {
String customName = getParameter(EngineParameterType.CUSTOM_NAME);
if (customName != null) {
return customName;
} else {
return getStandardName(ctx);
}
}
private String getStandardName(@NonNull Context ctx) {
return getStandardName(ctx, serverType, vehicleKey);
}
public static String getStandardName(@NonNull Context ctx,
@NonNull ServerType serverType,
@NonNull String vehicleKey) {
String vehicleTitle = VehicleType.toHumanString(ctx, vehicleKey);
String pattern = ctx.getString(R.string.ltr_or_rtl_combine_via_dash);
return String.format(pattern, serverType.getTitle(), vehicleTitle);
}
public static OnlineRoutingEngine createNewEngine(@NonNull ServerType serverType,
@NonNull String vehicleKey) {
return new OnlineRoutingEngine(generateKey(), serverType, vehicleKey);
}
private static String generateKey() {
return ONLINE_ROUTING_ENGINE_PREFIX + System.currentTimeMillis();
}
}

View file

@ -0,0 +1,610 @@
package net.osmand.plus.onlinerouting;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import net.osmand.AndroidNetworkUtils;
import net.osmand.AndroidNetworkUtils.OnRequestResultListener;
import net.osmand.AndroidUtils;
import net.osmand.CallbackWithObject;
import net.osmand.data.LatLon;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.UiUtilities;
import net.osmand.plus.UiUtilities.DialogButtonType;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.base.BaseOsmAndFragment;
import net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter.HorizontalSelectionItem;
import net.osmand.plus.onlinerouting.OnlineRoutingCard.OnTextChangedListener;
import net.osmand.plus.onlinerouting.OnlineRoutingEngine.EngineParameterType;
import net.osmand.plus.routepreparationmenu.cards.BaseCard;
import net.osmand.plus.settings.backend.ApplicationMode;
import net.osmand.util.Algorithms;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
public class OnlineRoutingEngineFragment extends BaseOsmAndFragment {
public static final String TAG = OnlineRoutingEngineFragment.class.getSimpleName();
private static final String ENGINE_NAME_KEY = "engine_name";
private static final String ENGINE_SERVER_KEY = "engine_server";
private static final String ENGINE_SERVER_URL_KEY = "engine_server_url";
private static final String ENGINE_VEHICLE_TYPE_KEY = "engine_vehicle_type";
private static final String ENGINE_CUSTOM_VEHICLE_KEY = "engine_custom_vehicle";
private static final String ENGINE_API_KEY_KEY = "engine_api_key";
private static final String EXAMPLE_LOCATION_KEY = "example_location";
private static final String APP_MODE_KEY = "app_mode";
private static final String EDITED_ENGINE_KEY = "edited_engine_key";
private OsmandApplication app;
private MapActivity mapActivity;
private OnlineRoutingHelper helper;
private View view;
private ViewGroup segmentsContainer;
private OnlineRoutingCard nameCard;
private OnlineRoutingCard serverCard;
private OnlineRoutingCard vehicleCard;
private OnlineRoutingCard apiKeyCard;
private OnlineRoutingCard exampleCard;
private View testResultsContainer;
private ApplicationMode appMode;
private OnlineRoutingEngineObject engine;
private ExampleLocation selectedLocation;
private String editedEngineKey;
private enum ExampleLocation {
AMSTERDAM("Amsterdam",
new LatLon(52.379189, 4.899431),
new LatLon(52.308056, 4.764167)),
BERLIN("Berlin",
new LatLon(52.520008, 13.404954),
new LatLon(52.3666652, 13.501997992)),
NEW_YORK("New York",
new LatLon(43.000000, -75.000000),
new LatLon(40.641766, -73.780968)),
PARIS("Paris",
new LatLon(48.864716, 2.349014),
new LatLon(48.948437, 2.434931));
ExampleLocation(String name, LatLon cityCenterLatLon, LatLon cityAirportLatLon) {
this.name = name;
this.cityCenterLatLon = cityCenterLatLon;
this.cityAirportLatLon = cityAirportLatLon;
}
private String name;
private LatLon cityCenterLatLon;
private LatLon cityAirportLatLon;
public String getName() {
return name;
}
public LatLon getCityCenterLatLon() {
return cityCenterLatLon;
}
public LatLon getCityAirportLatLon() {
return cityAirportLatLon;
}
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
app = requireMyApplication();
mapActivity = getMapActivity();
helper = app.getOnlineRoutingHelper();
engine = new OnlineRoutingEngineObject();
if (savedInstanceState != null) {
restoreState(savedInstanceState);
} else {
initState();
}
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
view = getInflater().inflate(
R.layout.online_routing_engine_fragment, container, false);
segmentsContainer = (ViewGroup) view.findViewById(R.id.segments_container);
if (Build.VERSION.SDK_INT >= 21) {
AndroidUtils.addStatusBarPadding21v(getContext(), view);
}
setupToolbar((Toolbar) view.findViewById(R.id.toolbar));
setupNameCard();
setupServerCard();
setupVehicleCard();
setupApiKeyCard();
setupExampleCard();
setupResultsContainer();
addSpaceSegment();
setupButtons();
updateCardViews(nameCard, serverCard, vehicleCard, exampleCard);
return view;
}
private void setupNameCard() {
nameCard = new OnlineRoutingCard(mapActivity, isNightMode());
nameCard.build(mapActivity);
nameCard.setDescription(getString(R.string.select_nav_profile_dialog_message));
nameCard.setEditedText(engine.getName(app));
nameCard.setFieldBoxLabelText(getString(R.string.shared_string_name));
nameCard.setOnTextChangedListener(new OnTextChangedListener() {
@Override
public void onTextChanged(boolean changedByUser, String text) {
if (changedByUser) {
engine.customName = text;
}
}
});
nameCard.showDivider();
segmentsContainer.addView(nameCard.getView());
}
private void setupServerCard() {
serverCard = new OnlineRoutingCard(mapActivity, isNightMode());
serverCard.build(mapActivity);
serverCard.setHeaderTitle(getString(R.string.shared_string_type));
List<HorizontalSelectionItem> serverItems = new ArrayList<>();
for (ServerType server : ServerType.values()) {
serverItems.add(new HorizontalSelectionItem(server.getTitle(), server));
}
serverCard.setSelectionMenu(serverItems, engine.serverType.getTitle(),
new CallbackWithObject<HorizontalSelectionItem>() {
@Override
public boolean processResult(HorizontalSelectionItem result) {
ServerType server = (ServerType) result.getObject();
if (engine.serverType != server) {
engine.serverType = server;
updateCardViews(nameCard, serverCard, exampleCard);
return true;
}
return false;
}
});
serverCard.setOnTextChangedListener(new OnTextChangedListener() {
@Override
public void onTextChanged(boolean editedByUser, String text) {
if (editedByUser) {
engine.customServerUrl = text;
}
}
});
serverCard.setFieldBoxLabelText(getString(R.string.shared_string_server_url));
serverCard.showDivider();
segmentsContainer.addView(serverCard.getView());
}
private void setupVehicleCard() {
vehicleCard = new OnlineRoutingCard(mapActivity, isNightMode());
vehicleCard.build(mapActivity);
vehicleCard.setHeaderTitle(getString(R.string.shared_string_vehicle));
List<HorizontalSelectionItem> vehicleItems = new ArrayList<>();
for (VehicleType vehicle : VehicleType.values()) {
vehicleItems.add(new HorizontalSelectionItem(vehicle.getTitle(app), vehicle));
}
vehicleCard.setSelectionMenu(vehicleItems, engine.vehicleType.getTitle(app),
new CallbackWithObject<HorizontalSelectionItem>() {
@Override
public boolean processResult(HorizontalSelectionItem result) {
VehicleType vehicle = (VehicleType) result.getObject();
if (engine.vehicleType != vehicle) {
engine.vehicleType = vehicle;
updateCardViews(nameCard, vehicleCard, exampleCard);
return true;
}
return false;
}
});
vehicleCard.setFieldBoxLabelText(getString(R.string.shared_string_custom));
vehicleCard.setOnTextChangedListener(new OnTextChangedListener() {
@Override
public void onTextChanged(boolean editedByUser, String text) {
if (editedByUser) {
engine.customVehicleKey = text;
updateCardViews(nameCard, exampleCard);
}
}
});
vehicleCard.setEditedText(engine.customVehicleKey);
vehicleCard.setFieldBoxHelperText(getString(R.string.shared_string_enter_param));
vehicleCard.showDivider();
segmentsContainer.addView(vehicleCard.getView());
}
private void setupApiKeyCard() {
apiKeyCard = new OnlineRoutingCard(mapActivity, isNightMode());
apiKeyCard.build(mapActivity);
apiKeyCard.setHeaderTitle(getString(R.string.shared_string_api_key));
apiKeyCard.setFieldBoxLabelText(getString(R.string.keep_it_empty_if_not));
apiKeyCard.setEditedText(engine.apiKey);
apiKeyCard.showDivider();
apiKeyCard.setOnTextChangedListener(new OnTextChangedListener() {
@Override
public void onTextChanged(boolean editedByUser, String text) {
engine.apiKey = text;
updateCardViews(exampleCard);
}
});
segmentsContainer.addView(apiKeyCard.getView());
}
private void setupExampleCard() {
exampleCard = new OnlineRoutingCard(mapActivity, isNightMode());
exampleCard.build(mapActivity);
exampleCard.setHeaderTitle(getString(R.string.shared_string_example));
List<HorizontalSelectionItem> locationItems = new ArrayList<>();
for (ExampleLocation location : ExampleLocation.values()) {
locationItems.add(new HorizontalSelectionItem(location.getName(), location));
}
exampleCard.setSelectionMenu(locationItems, selectedLocation.getName(),
new CallbackWithObject<HorizontalSelectionItem>() {
@Override
public boolean processResult(HorizontalSelectionItem result) {
ExampleLocation location = (ExampleLocation) result.getObject();
if (selectedLocation != location) {
selectedLocation = location;
updateCardViews(exampleCard);
return true;
}
return false;
}
});
exampleCard.setFieldBoxHelperText(getString(R.string.online_routing_example_hint));
exampleCard.setButton(getString(R.string.test_route_calculation), new View.OnClickListener() {
@Override
public void onClick(View v) {
testEngineWork();
}
});
segmentsContainer.addView(exampleCard.getView());
}
private void setupResultsContainer() {
testResultsContainer = getInflater().inflate(
R.layout.bottom_sheet_item_with_descr_64dp, segmentsContainer, false);
testResultsContainer.setVisibility(View.GONE);
segmentsContainer.addView(testResultsContainer);
}
private void addSpaceSegment() {
int space = (int) getResources().getDimension(R.dimen.empty_state_text_button_padding_top);
View bottomSpaceView = new View(app);
bottomSpaceView.setLayoutParams(
new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, space));
segmentsContainer.addView(bottomSpaceView);
}
private void setupToolbar(Toolbar toolbar) {
ImageView navigationIcon = toolbar.findViewById(R.id.close_button);
navigationIcon.setImageResource(R.drawable.ic_action_close);
navigationIcon.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
}
});
TextView title = toolbar.findViewById(R.id.toolbar_title);
toolbar.findViewById(R.id.toolbar_subtitle).setVisibility(View.GONE);
View actionBtn = toolbar.findViewById(R.id.action_button);
if (isEditingMode()) {
title.setText(getString(R.string.edit_online_routing_engine));
ImageView ivBtn = toolbar.findViewById(R.id.action_button_icon);
ivBtn.setImageDrawable(
getIcon(R.drawable.ic_action_delete_dark, R.color.color_osm_edit_delete));
actionBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
deleteEngine();
dismiss();
}
});
} else {
title.setText(getString(R.string.add_online_routing_engine));
actionBtn.setVisibility(View.GONE);
}
}
private void updateCardViews(BaseCard... cardsToUpdate) {
for (BaseCard card : cardsToUpdate) {
if (nameCard.equals(card)) {
if (Algorithms.isEmpty(engine.customName)) {
String name = OnlineRoutingEngine.getStandardName(app, engine.serverType, engine.getVehicleKey());
nameCard.setEditedText(name);
}
} else if (serverCard.equals(card)) {
serverCard.setHeaderSubtitle(engine.serverType.getTitle());
serverCard.setEditedText(engine.getBaseUrl());
if (engine.serverType == ServerType.GRAPHHOPER) {
apiKeyCard.show();
} else {
apiKeyCard.hide();
}
} else if (vehicleCard.equals(card)) {
vehicleCard.setHeaderSubtitle(engine.vehicleType.getTitle(app));
if (engine.vehicleType == VehicleType.CUSTOM) {
vehicleCard.showFieldBox();
vehicleCard.setEditedText(engine.getVehicleKey());
} else {
vehicleCard.hideFieldBox();
}
} else if (exampleCard.equals(card)) {
exampleCard.setEditedText(getTestUrl());
}
}
}
private void setupButtons() {
boolean nightMode = isNightMode();
View cancelButton = view.findViewById(R.id.dismiss_button);
UiUtilities.setupDialogButton(nightMode, cancelButton,
DialogButtonType.SECONDARY, R.string.shared_string_cancel);
cancelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
}
});
view.findViewById(R.id.buttons_divider).setVisibility(View.VISIBLE);
View saveButton = view.findViewById(R.id.right_bottom_button);
UiUtilities.setupDialogButton(nightMode, saveButton,
UiUtilities.DialogButtonType.PRIMARY, R.string.shared_string_save);
saveButton.setVisibility(View.VISIBLE);
saveButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
saveChanges();
dismiss();
}
});
}
private void saveChanges() {
OnlineRoutingEngine engineToSave;
if (isEditingMode()) {
engineToSave = new OnlineRoutingEngine(editedEngineKey, engine.serverType, engine.getVehicleKey());
} else {
engineToSave = OnlineRoutingEngine.createNewEngine(engine.serverType, engine.getVehicleKey());
}
engineToSave.putParameter(EngineParameterType.CUSTOM_SERVER_URL, engine.customServerUrl);
engineToSave.putParameter(EngineParameterType.CUSTOM_NAME, engine.customName);
if (engine.serverType == ServerType.GRAPHHOPER) {
engineToSave.putParameter(EngineParameterType.API_KEY, engine.apiKey);
}
helper.saveEngine(engineToSave);
}
private void deleteEngine() {
helper.deleteEngine(editedEngineKey);
}
private String getTestUrl() {
String baseUrl = engine.serverType.getBaseUrl();
String vehicle = engine.getVehicleKey();
LatLon startPoint = selectedLocation.getCityCenterLatLon();
LatLon endPoint = selectedLocation.getCityAirportLatLon();
if (engine.serverType == ServerType.GRAPHHOPER) {
return baseUrl + "?" + "point=" + startPoint.getLatitude()
+ "," + startPoint.getLongitude()
+ "&" + "point=" + endPoint.getLatitude()
+ "," + endPoint.getLongitude()
+ "&" + "vehicle=" + vehicle
+ (!Algorithms.isEmpty(engine.apiKey) ? ("&" + "key=" + engine.apiKey) : "");
} else {
return baseUrl + vehicle + "/" + startPoint.getLatitude()
+ "," + startPoint.getLongitude()
+ ";" + endPoint.getLatitude()
+ "," + endPoint.getLongitude()
+ "?" + "geometries=geojson";
}
}
private void testEngineWork() {
final ServerType server = engine.serverType;
final ExampleLocation location = selectedLocation;
AndroidNetworkUtils.sendRequestAsync(app, exampleCard.getEditedText(), null,
null, false, false, new OnRequestResultListener() {
@Override
public void onResult(String response) {
boolean resultOk = false;
if (response != null) {
try {
JSONObject obj = new JSONObject(response);
if (server == ServerType.GRAPHHOPER) {
resultOk = obj.has("paths");
} else if (server == ServerType.OSRM) {
resultOk = obj.has("routes");
}
} catch (JSONException e) {
}
}
showTestResults(resultOk, location);
}
});
}
private void showTestResults(boolean resultOk, ExampleLocation location) {
testResultsContainer.setVisibility(View.VISIBLE);
ImageView ivImage = testResultsContainer.findViewById(R.id.icon);
TextView tvTitle = testResultsContainer.findViewById(R.id.title);
TextView tvDescription = testResultsContainer.findViewById(R.id.description);
if (resultOk) {
ivImage.setImageDrawable(getContentIcon(R.drawable.ic_action_gdirections_dark));
tvTitle.setText(getString(R.string.shared_string_ok));
} else {
ivImage.setImageDrawable(getContentIcon(R.drawable.ic_action_alert));
tvTitle.setText(getString(R.string.message_error_recheck_parameters));
}
tvDescription.setText(location.getName());
}
private boolean isEditingMode() {
return editedEngineKey != null;
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
saveState(outState);
}
private void saveState(Bundle outState) {
outState.putString(ENGINE_NAME_KEY, engine.customName);
outState.putString(ENGINE_SERVER_KEY, engine.serverType.name());
outState.putString(ENGINE_SERVER_URL_KEY, engine.customServerUrl);
outState.putString(ENGINE_VEHICLE_TYPE_KEY, engine.vehicleType.name());
outState.putString(ENGINE_CUSTOM_VEHICLE_KEY, engine.customVehicleKey);
outState.putString(ENGINE_API_KEY_KEY, engine.apiKey);
outState.putString(EXAMPLE_LOCATION_KEY, selectedLocation.name());
if (appMode != null) {
outState.putString(APP_MODE_KEY, appMode.getStringKey());
}
outState.putString(EDITED_ENGINE_KEY, editedEngineKey);
}
private void restoreState(Bundle savedState) {
engine.customName = savedState.getString(ENGINE_NAME_KEY);
engine.serverType = ServerType.valueOf(savedState.getString(ENGINE_SERVER_KEY));
engine.customServerUrl = savedState.getString(ENGINE_SERVER_URL_KEY);
engine.vehicleType = VehicleType.valueOf(savedState.getString(ENGINE_VEHICLE_TYPE_KEY));
engine.customVehicleKey = savedState.getString(ENGINE_CUSTOM_VEHICLE_KEY);
engine.apiKey = savedState.getString(ENGINE_API_KEY_KEY);
selectedLocation = ExampleLocation.valueOf(savedState.getString(EXAMPLE_LOCATION_KEY));
appMode = ApplicationMode.valueOfStringKey(savedState.getString(APP_MODE_KEY), null);
editedEngineKey = savedState.getString(EDITED_ENGINE_KEY);
}
private void initState() {
engine.serverType = ServerType.values()[0];
engine.vehicleType = VehicleType.values()[0];
selectedLocation = ExampleLocation.values()[0];
if (isEditingMode()) {
OnlineRoutingEngine editedEngine = helper.getEngineByKey(editedEngineKey);
if (editedEngine != null) {
engine.customName = editedEngine.getParameter(EngineParameterType.CUSTOM_NAME);
engine.serverType = editedEngine.getServerType();
engine.customServerUrl = editedEngine.getParameter(EngineParameterType.CUSTOM_SERVER_URL);
String vehicleKey = editedEngine.getVehicleKey();
if (vehicleKey != null) {
VehicleType vehicleType = VehicleType.getVehicleByKey(vehicleKey);
if (vehicleType == VehicleType.CUSTOM) {
engine.customVehicleKey = vehicleKey;
}
engine.vehicleType = vehicleType;
}
engine.apiKey = editedEngine.getParameter(EngineParameterType.API_KEY);
}
}
}
private void dismiss() {
FragmentActivity activity = getActivity();
if (activity != null) {
activity.onBackPressed();
}
}
private boolean isNightMode() {
return !app.getSettings().isLightContentForMode(appMode);
}
@Nullable
private MapActivity getMapActivity() {
FragmentActivity activity = getActivity();
if (activity instanceof MapActivity) {
return (MapActivity) activity;
} else {
return null;
}
}
private LayoutInflater getInflater() {
return UiUtilities.getInflater(mapActivity, isNightMode());
}
public static void showInstance(@NonNull FragmentActivity activity,
@NonNull ApplicationMode appMode,
String editedEngineKey) {
FragmentManager fm = activity.getSupportFragmentManager();
if (!fm.isStateSaved() && fm.findFragmentByTag(OnlineRoutingEngineFragment.TAG) == null) {
OnlineRoutingEngineFragment fragment = new OnlineRoutingEngineFragment();
fragment.appMode = appMode;
fragment.editedEngineKey = editedEngineKey;
fm.beginTransaction()
.add(R.id.fragmentContainer, fragment, TAG)
.addToBackStack(TAG).commitAllowingStateLoss();
}
}
private static class OnlineRoutingEngineObject {
private String customName;
private ServerType serverType;
private String customServerUrl;
private VehicleType vehicleType;
private String customVehicleKey;
private String apiKey;
public String getVehicleKey() {
if (vehicleType == VehicleType.CUSTOM) {
return customVehicleKey;
}
return vehicleType.getKey();
}
public String getBaseUrl() {
return customServerUrl != null ? customServerUrl : serverType.getBaseUrl();
}
public String getName(Context ctx) {
if (customName != null) {
return customName;
}
return OnlineRoutingEngine.getStandardName(ctx, serverType, getVehicleKey());
}
}
}

View file

@ -0,0 +1,155 @@
package net.osmand.plus.onlinerouting;
import androidx.annotation.NonNull;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import net.osmand.PlatformUtil;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.util.Algorithms;
import org.apache.commons.logging.Log;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class OnlineRoutingHelper {
private static final Log LOG = PlatformUtil.getLog(OnlineRoutingHelper.class);
private OsmandApplication app;
private OsmandSettings settings;
private List<OnlineRoutingEngine> cachedEngines;
private Map<String, OnlineRoutingEngine> cachedEnginesMap;
public OnlineRoutingHelper(OsmandApplication app) {
this.app = app;
this.settings = app.getSettings();
loadFromSettings();
}
@NonNull
public List<OnlineRoutingEngine> getEngines() {
return cachedEngines;
}
public OnlineRoutingEngine getEngineByKey(String stringKey) {
return cachedEnginesMap.get(stringKey);
}
public void saveEngine(@NonNull OnlineRoutingEngine engine) {
String stringKey = engine.getStringKey();
OnlineRoutingEngine existedEngine = cachedEnginesMap.get(stringKey);
if (existedEngine != null) {
int index = cachedEngines.indexOf(existedEngine);
cachedEngines.set(index, engine);
} else {
cachedEngines.add(engine);
}
cachedEnginesMap.put(stringKey, engine);
saveToSettings();
}
public void deleteEngine(@NonNull String stringKey) {
OnlineRoutingEngine engine = getEngineByKey(stringKey);
if (engine != null) {
deleteEngine(engine);
}
}
public void deleteEngine(@NonNull OnlineRoutingEngine engine) {
String stringKey = engine.getStringKey();
if (cachedEnginesMap.containsKey(stringKey)) {
OnlineRoutingEngine existedEngine = cachedEnginesMap.remove(stringKey);
cachedEngines.remove(existedEngine);
saveToSettings();
}
}
private void loadFromSettings() {
cachedEngines = readFromSettings();
cachedEnginesMap = new HashMap<>();
for (OnlineRoutingEngine engine : cachedEngines) {
cachedEnginesMap.put(engine.getStringKey(), engine);
}
}
@NonNull
private List<OnlineRoutingEngine> readFromSettings() {
List<OnlineRoutingEngine> engines = new ArrayList<>();
String jsonString = settings.ONLINE_ROUTING_ENGINES.get();
if (!Algorithms.isEmpty(jsonString)) {
try {
JSONObject json = new JSONObject(jsonString);
readFromJson(json, engines);
} catch (JSONException e) {
LOG.debug("Error when create a new JSONObject: " + e.toString());
}
}
return engines;
}
private void saveToSettings() {
if (!Algorithms.isEmpty(cachedEngines)) {
JSONObject json = new JSONObject();
if (writeToJson(json, cachedEngines)) {
settings.ONLINE_ROUTING_ENGINES.set(json.toString());
}
} else {
settings.ONLINE_ROUTING_ENGINES.set(null);
}
}
private static void readFromJson(JSONObject json, List<OnlineRoutingEngine> engines) {
try {
if (!json.has("items")) {
return;
}
Gson gson = new Gson();
Type type = new TypeToken<HashMap<String, String>>() {
}.getType();
JSONArray itemsJson = json.getJSONArray("items");
for (int i = 0; i < itemsJson.length(); i++) {
JSONObject object = itemsJson.getJSONObject(i);
String key = object.getString("key");
String vehicleKey = object.getString("vehicle");
ServerType serverType = ServerType.valueOf(object.getString("serverType"));
String paramsString = object.getString("params");
HashMap<String, String> params = gson.fromJson(paramsString, type);
engines.add(new OnlineRoutingEngine(key, serverType, vehicleKey, params));
}
} catch (JSONException e) {
LOG.debug("Error when reading engines from JSON: " + e.toString());
}
}
private static boolean writeToJson(JSONObject json, List<OnlineRoutingEngine> engines) {
JSONArray jsonArray = new JSONArray();
Gson gson = new Gson();
Type type = new TypeToken<HashMap<String, String>>() {
}.getType();
try {
for (OnlineRoutingEngine engine : engines) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("key", engine.getStringKey());
jsonObject.put("serverType", engine.getServerType().name());
jsonObject.put("vehicle", engine.getVehicleKey());
jsonObject.put("params", gson.toJson(engine.getParams(), type));
jsonArray.put(jsonObject);
}
json.put("items", jsonArray);
return true;
} catch (JSONException e) {
LOG.debug("Error when writing engines to JSON: " + e.toString());
return false;
}
}
}

View file

@ -0,0 +1,22 @@
package net.osmand.plus.onlinerouting;
public enum ServerType {
GRAPHHOPER("Graphhoper", "https://graphhopper.com/api/1/route"),
OSRM("OSRM", "https://zlzk.biz/route/v1/");
ServerType(String title, String baseUrl) {
this.title = title;
this.baseUrl = baseUrl;
}
private String title;
private String baseUrl;
public String getTitle() {
return title;
}
public String getBaseUrl() {
return baseUrl;
}
}

View file

@ -0,0 +1,50 @@
package net.osmand.plus.onlinerouting;
import android.content.Context;
import androidx.annotation.NonNull;
import net.osmand.plus.R;
import net.osmand.util.Algorithms;
public enum VehicleType {
CAR("car", R.string.routing_engine_vehicle_type_car),
BIKE("bike", R.string.routing_engine_vehicle_type_bike),
FOOT("foot", R.string.routing_engine_vehicle_type_foot),
DRIVING("driving", R.string.routing_engine_vehicle_type_driving),
CUSTOM("", R.string.shared_string_custom);
VehicleType(String key, int titleId) {
this.key = key;
this.titleId = titleId;
}
private String key;
private int titleId;
public String getKey() {
return key;
}
public String getTitle(Context ctx) {
return ctx.getString(titleId);
}
public static String toHumanString(@NonNull Context ctx,
@NonNull String key) {
VehicleType vehicleType = getVehicleByKey(key);
if (vehicleType == CUSTOM) {
return Algorithms.capitalizeFirstLetter(key);
}
return vehicleType.getTitle(ctx);
}
public static VehicleType getVehicleByKey(String key) {
for (VehicleType v : values()) {
if (Algorithms.objectEquals(v.getKey(), key)) {
return v;
}
}
return CUSTOM;
}
}

View file

@ -138,7 +138,11 @@ public class OsmOAuthAuthorizationAdapter {
@Override
protected void onPostExecute(@NonNull OAuth1RequestToken requestToken) {
if (requestToken != null) {
loadWebView(rootLayout, nightMode, client.getService().getAuthorizationUrl(requestToken));
} else {
app.showShortToastMessage(app.getString(R.string.internet_not_available));
}
}
}

View file

@ -45,7 +45,11 @@ public class OsmOAuthHelper {
}
public void authorize(@NonNull String oauthVerifier) {
if (oauthVerifier != null) {
authorizationAdapter.authorize(oauthVerifier, this);
} else {
updateAdapter();
}
}
public void resetAuthorization() {

View file

@ -0,0 +1,12 @@
package net.osmand.plus.profiles;
import net.osmand.plus.R;
public class OnlineRoutingEngineDataObject extends ProfileDataObject {
public OnlineRoutingEngineDataObject(String name,
String description,
String stringKey) {
super(name, description, stringKey, R.drawable.ic_world_globe_dark, false, null);
}
}

View file

@ -5,6 +5,8 @@ import android.content.Context;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.OsmandPlugin;
import net.osmand.plus.R;
import net.osmand.plus.onlinerouting.OnlineRoutingEngine;
import net.osmand.plus.profiles.RoutingProfileDataObject.RoutingProfilesResources;
import net.osmand.plus.settings.backend.ApplicationMode;
import net.osmand.router.GeneralRouter;
import net.osmand.router.RoutingConfiguration;
@ -66,6 +68,15 @@ public class ProfileDataUtils {
return result;
}
public static List<OnlineRoutingEngineDataObject> getOnlineRoutingProfiles(OsmandApplication app) {
List<OnlineRoutingEngineDataObject> objects = new ArrayList<>();
for (OnlineRoutingEngine engine : app.getOnlineRoutingHelper().getEngines()) {
objects.add(new OnlineRoutingEngineDataObject(
engine.getName(app), engine.getBaseUrl(), engine.getStringKey()));
}
return objects;
}
public static Map<String, List<RoutingProfileDataObject>> getRoutingProfilesByFileNames(OsmandApplication app) {
Map<String, List<RoutingProfileDataObject>> result = new HashMap<>();
for (final RoutingProfileDataObject profile : getRoutingProfiles(app).values()) {
@ -83,24 +94,24 @@ public class ProfileDataUtils {
public static Map<String, RoutingProfileDataObject> getRoutingProfiles(OsmandApplication context) {
Map<String, RoutingProfileDataObject> profilesObjects = new HashMap<>();
profilesObjects.put(RoutingProfileDataObject.RoutingProfilesResources.STRAIGHT_LINE_MODE.name(), new RoutingProfileDataObject(
RoutingProfileDataObject.RoutingProfilesResources.STRAIGHT_LINE_MODE.name(),
context.getString(RoutingProfileDataObject.RoutingProfilesResources.STRAIGHT_LINE_MODE.getStringRes()),
profilesObjects.put(RoutingProfilesResources.STRAIGHT_LINE_MODE.name(), new RoutingProfileDataObject(
RoutingProfilesResources.STRAIGHT_LINE_MODE.name(),
context.getString(RoutingProfilesResources.STRAIGHT_LINE_MODE.getStringRes()),
context.getString(R.string.special_routing_type),
RoutingProfileDataObject.RoutingProfilesResources.STRAIGHT_LINE_MODE.getIconRes(),
RoutingProfilesResources.STRAIGHT_LINE_MODE.getIconRes(),
false, null));
profilesObjects.put(RoutingProfileDataObject.RoutingProfilesResources.DIRECT_TO_MODE.name(), new RoutingProfileDataObject(
RoutingProfileDataObject.RoutingProfilesResources.DIRECT_TO_MODE.name(),
context.getString(RoutingProfileDataObject.RoutingProfilesResources.DIRECT_TO_MODE.getStringRes()),
profilesObjects.put(RoutingProfilesResources.DIRECT_TO_MODE.name(), new RoutingProfileDataObject(
RoutingProfilesResources.DIRECT_TO_MODE.name(),
context.getString(RoutingProfilesResources.DIRECT_TO_MODE.getStringRes()),
context.getString(R.string.special_routing_type),
RoutingProfileDataObject.RoutingProfilesResources.DIRECT_TO_MODE.getIconRes(),
RoutingProfilesResources.DIRECT_TO_MODE.getIconRes(),
false, null));
if (context.getBRouterService() != null) {
profilesObjects.put(RoutingProfileDataObject.RoutingProfilesResources.BROUTER_MODE.name(), new RoutingProfileDataObject(
RoutingProfileDataObject.RoutingProfilesResources.BROUTER_MODE.name(),
context.getString(RoutingProfileDataObject.RoutingProfilesResources.BROUTER_MODE.getStringRes()),
profilesObjects.put(RoutingProfilesResources.BROUTER_MODE.name(), new RoutingProfileDataObject(
RoutingProfilesResources.BROUTER_MODE.name(),
context.getString(RoutingProfilesResources.BROUTER_MODE.getStringRes()),
context.getString(R.string.third_party_routing_type),
RoutingProfileDataObject.RoutingProfilesResources.BROUTER_MODE.getIconRes(),
RoutingProfilesResources.BROUTER_MODE.getIconRes(),
false, null));
}
@ -123,9 +134,9 @@ public class ProfileDataUtils {
String fileName = router.getFilename();
if (!Algorithms.isEmpty(fileName)) {
description = fileName;
} else if (RoutingProfileDataObject.RoutingProfilesResources.isRpValue(name.toUpperCase())) {
iconRes = RoutingProfileDataObject.RoutingProfilesResources.valueOf(name.toUpperCase()).getIconRes();
name = app.getString(RoutingProfileDataObject.RoutingProfilesResources.valueOf(name.toUpperCase()).getStringRes());
} else if (RoutingProfilesResources.isRpValue(name.toUpperCase())) {
iconRes = RoutingProfilesResources.valueOf(name.toUpperCase()).getIconRes();
name = app.getString(RoutingProfilesResources.valueOf(name.toUpperCase()).getStringRes());
}
profilesObjects.put(routerKey, new RoutingProfileDataObject(routerKey, name, description,
iconRes, false, fileName));

View file

@ -22,7 +22,6 @@ import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import net.osmand.AndroidUtils;
import net.osmand.CallbackWithObject;
@ -39,6 +38,7 @@ import net.osmand.plus.base.bottomsheetmenu.simpleitems.TitleItem;
import net.osmand.plus.helpers.FontCache;
import net.osmand.plus.settings.backend.ApplicationMode;
import net.osmand.plus.settings.bottomsheets.BasePreferenceBottomSheet;
import net.osmand.plus.onlinerouting.OnlineRoutingEngineFragment;
import net.osmand.router.RoutingConfiguration;
import org.apache.commons.logging.Log;
@ -149,17 +149,23 @@ public class SelectProfileBottomSheet extends BasePreferenceBottomSheet {
items.add(new TitleItem(getString(R.string.select_nav_profile_dialog_title)));
items.add(new LongDescriptionItem(getString(R.string.select_nav_profile_dialog_message)));
for (int i = 0; i < profiles.size(); i++) {
final RoutingProfileDataObject profile = (RoutingProfileDataObject) profiles.get(i);
boolean showBottomDivider = false;
if (profiles.get(i) instanceof RoutingProfileDataObject) {
final RoutingProfileDataObject profile = (RoutingProfileDataObject) profiles.get(i);
if (i < profiles.size() - 1) {
if (profiles.get(i + 1) instanceof RoutingProfileDataObject) {
RoutingProfileDataObject nextProfile = (RoutingProfileDataObject) profiles.get(i + 1);
if (profile.getFileName() == null) {
showBottomDivider = nextProfile.getFileName() != null;
} else {
showBottomDivider = !profile.getFileName().equals(nextProfile.getFileName());
}
} else {
showBottomDivider = true;
}
addProfileItem(profile, showBottomDivider);
}
}
addProfileItem(profiles.get(i), showBottomDivider);
}
items.add(new DividerItem(app));
items.add(new LongDescriptionItem(app.getString(R.string.osmand_routing_promo)));
@ -179,6 +185,15 @@ public class SelectProfileBottomSheet extends BasePreferenceBottomSheet {
});
}
});
addButtonItem(R.string.add_online_routing_engine, R.drawable.ic_world_globe_dark, new OnClickListener() {
@Override
public void onClick(View v) {
if (getActivity() != null) {
OnlineRoutingEngineFragment.showInstance(getActivity(), getAppMode(), null);
}
dismiss();
}
});
items.add(new BaseBottomSheetItem.Builder()
.setCustomView(bottomSpaceView)
.create());
@ -220,7 +235,10 @@ public class SelectProfileBottomSheet extends BasePreferenceBottomSheet {
int activeColorResId = nightMode ? R.color.active_color_primary_dark : R.color.active_color_primary_light;
int iconDefaultColorResId = nightMode ? R.color.icon_color_default_dark : R.color.icon_color_default_light;
View itemView = UiUtilities.getInflater(getContext(), nightMode).inflate(R.layout.bottom_sheet_item_with_descr_and_radio_btn, null);
View itemView = UiUtilities.getInflater(getContext(), nightMode).inflate(
profile instanceof OnlineRoutingEngineDataObject ?
R.layout.bottom_sheet_item_with_descr_radio_and_icon_btn :
R.layout.bottom_sheet_item_with_descr_and_radio_btn, null);
TextView tvTitle = itemView.findViewById(R.id.title);
TextView tvDescription = itemView.findViewById(R.id.description);
ImageView ivIcon = itemView.findViewById(R.id.icon);
@ -253,8 +271,15 @@ public class SelectProfileBottomSheet extends BasePreferenceBottomSheet {
args.putString(PROFILE_KEY_ARG, profile.getStringKey());
Fragment target = getTargetFragment();
if (target instanceof OnSelectProfileCallback) {
if (profile instanceof OnlineRoutingEngineDataObject) {
if (getActivity() != null) {
OnlineRoutingEngineFragment.showInstance(getActivity(), getAppMode(), profile.getStringKey());
}
dismiss();
} else {
((OnSelectProfileCallback) target).onProfileSelected(args);
}
}
dismiss();
}
})
@ -338,6 +363,7 @@ public class SelectProfileBottomSheet extends BasePreferenceBottomSheet {
case NAVIGATION_PROFILE:
profiles.addAll(ProfileDataUtils.getSortedRoutingProfiles(app));
profiles.addAll(ProfileDataUtils.getOnlineRoutingProfiles(app));
break;
case DEFAULT_PROFILE:
@ -358,6 +384,7 @@ public class SelectProfileBottomSheet extends BasePreferenceBottomSheet {
public static void showInstance(@NonNull FragmentActivity activity,
@NonNull DialogMode dialogMode,
@Nullable Fragment target,
ApplicationMode appMode,
String selectedItemKey,
boolean usedOnMap) {
SelectProfileBottomSheet fragment = new SelectProfileBottomSheet();
@ -366,6 +393,7 @@ public class SelectProfileBottomSheet extends BasePreferenceBottomSheet {
args.putString(SELECTED_KEY, selectedItemKey);
fragment.setArguments(args);
fragment.setUsedOnMap(usedOnMap);
fragment.setAppMode(appMode);
fragment.setTargetFragment(target, 0);
fragment.show(activity.getSupportFragmentManager(), TAG);
}

View file

@ -621,7 +621,11 @@ public class ResourceManager {
}
}
public List<String> indexingMaps(final IProgress progress) {
public List<String> indexingMaps(IProgress progress) {
return indexingMaps(progress, Collections.<File>emptyList());
}
public List<String> indexingMaps(final IProgress progress, List<File> filesToReindex) {
long val = System.currentTimeMillis();
ArrayList<File> files = new ArrayList<File>();
File appPath = context.getAppPath(null);
@ -688,7 +692,7 @@ public class ResourceManager {
try {
BinaryMapIndexReader mapReader = null;
try {
mapReader = cachedOsmandIndexes.getReader(f);
mapReader = cachedOsmandIndexes.getReader(f, !filesToReindex.contains(f));
if (mapReader.getVersion() != IndexConstants.BINARY_MAP_VERSION) {
mapReader = null;
}

View file

@ -48,6 +48,7 @@ import net.osmand.plus.helpers.enums.TracksSortByMode;
import net.osmand.plus.mapillary.MapillaryPlugin;
import net.osmand.plus.mapmarkers.CoordinateInputFormats.Format;
import net.osmand.plus.mapmarkers.MapMarkersMode;
import net.osmand.plus.onlinerouting.OnlineRoutingEngine;
import net.osmand.plus.profiles.LocationIcon;
import net.osmand.plus.profiles.NavigationIcon;
import net.osmand.plus.profiles.ProfileIconColors;
@ -1015,6 +1016,8 @@ public class OsmandSettings {
ROUTE_SERVICE.setModeDefaultValue(ApplicationMode.AIRCRAFT, RouteService.STRAIGHT);
}
public final CommonPreference<String> ONLINE_ROUTING_ENGINES = new StringPreference(this, "online_routing_engines", null);
public final CommonPreference<NavigationIcon> NAVIGATION_ICON = new EnumStringPreference<>(this, "navigation_icon", NavigationIcon.DEFAULT, NavigationIcon.values()).makeProfile().cache();
{

View file

@ -38,12 +38,12 @@ public abstract class BasePreferenceBottomSheet extends MenuBottomSheetDialogFra
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
appMode = ApplicationMode.valueOfStringKey(savedInstanceState.getString(APP_MODE_KEY), null);
applyQueryType = ApplyQueryType.valueOf(savedInstanceState.getString(APPLY_QUERY_TYPE));
profileDependent = savedInstanceState.getBoolean(PROFILE_DEPENDENT, false);
}
super.onCreate(savedInstanceState);
}
@Override

View file

@ -125,8 +125,9 @@ public class GlobalSettingsFragment extends BaseSettingsFragment
if (prefId.equals(settings.DEFAULT_APPLICATION_MODE.getId())) {
if (getActivity() != null) {
String defaultModeKey = settings.DEFAULT_APPLICATION_MODE.get().getStringKey();
SelectProfileBottomSheet.showInstance(getActivity(),
DialogMode.DEFAULT_PROFILE, this, defaultModeKey, false);
SelectProfileBottomSheet.showInstance(
getActivity(), DialogMode.DEFAULT_PROFILE, this,
getSelectedAppMode(), defaultModeKey, false);
}
} else if (settings.SPEED_CAMERAS_UNINSTALLED.getId().equals(prefId) && !settings.SPEED_CAMERAS_UNINSTALLED.get()) {
FragmentManager fm = getFragmentManager();

View file

@ -119,8 +119,9 @@ public class MainSettingsFragment extends BaseSettingsFragment implements OnSele
return true;
} else if (CREATE_PROFILE.equals(prefId)) {
if (getActivity() != null) {
SelectProfileBottomSheet.showInstance(getActivity(),
DialogMode.BASE_PROFILE, this, null, false);
SelectProfileBottomSheet.showInstance(
getActivity(), DialogMode.BASE_PROFILE, this,
getSelectedAppMode(), null, false);
}
} else if (IMPORT_PROFILE.equals(prefId)) {
final MapActivity mapActivity = getMapActivity();

View file

@ -126,7 +126,7 @@ public class NavigationFragment extends BaseSettingsFragment implements OnSelect
if (getActivity() != null) {
SelectProfileBottomSheet.showInstance(
getActivity(), SelectProfileBottomSheet.DialogMode.NAVIGATION_PROFILE,
this, routingProfileKey, false);
this, getSelectedAppMode(), routingProfileKey, false);
}
}
return false;

View file

@ -403,7 +403,7 @@ public class ProfileAppearanceFragment extends BaseSettingsFragment implements O
if (getActivity() != null) {
SelectProfileBottomSheet.showInstance(
getActivity(), DialogMode.BASE_PROFILE, ProfileAppearanceFragment.this,
selectedAppModeKey, false);
getSelectedAppMode(), selectedAppModeKey, false);
}
}
}

View file

@ -0,0 +1,49 @@
package net.osmand.plus.widgets;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Build;
import android.util.AttributeSet;
import android.webkit.WebView;
import net.osmand.plus.OsmandApplication;
public class WebViewEx extends WebView {
public WebViewEx(Context context) {
super(context);
fixWebViewResetsLocaleToUserDefault(context);
}
public WebViewEx(Context context, AttributeSet attrs) {
super(context, attrs);
fixWebViewResetsLocaleToUserDefault(context);
}
public WebViewEx(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
fixWebViewResetsLocaleToUserDefault(context);
}
public WebViewEx(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
fixWebViewResetsLocaleToUserDefault(context);
}
public WebViewEx(Context context, AttributeSet attrs, int defStyleAttr, boolean privateBrowsing) {
super(context, attrs, defStyleAttr, privateBrowsing);
fixWebViewResetsLocaleToUserDefault(context);
}
public void fixWebViewResetsLocaleToUserDefault(Context ctx) {
// issue details: https://issuetracker.google.com/issues/37113860
// also see: https://gist.github.com/amake/0ac7724681ac1c178c6f95a5b09f03ce
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
OsmandApplication app = (OsmandApplication) ctx.getApplicationContext();
app.checkPreferredLocale();
ctx.getResources().updateConfiguration(
new Configuration(app.getResources().getConfiguration()),
ctx.getResources().getDisplayMetrics());
}
}
}

View file

@ -289,18 +289,17 @@ public class WikiArticleHelper {
@Nullable
public static String getPartialContent(String source) {
if (source == null) {
if (Algorithms.isEmpty(source)) {
return null;
}
String content = source.replaceAll("\\n", "");
int firstParagraphStart = content.indexOf(P_OPENED);
int firstParagraphEnd = content.indexOf(P_CLOSED);
firstParagraphEnd = firstParagraphEnd < firstParagraphStart ? content.indexOf(P_CLOSED, firstParagraphStart) : firstParagraphEnd;
if (firstParagraphStart == -1 || firstParagraphEnd == -1
|| firstParagraphEnd < firstParagraphStart) {
return null;
}
String firstParagraphHtml = content.substring(firstParagraphStart, firstParagraphEnd + P_CLOSED.length());
String firstParagraphHtml = null;
if (firstParagraphStart != -1 && firstParagraphEnd != -1
&& firstParagraphEnd >= firstParagraphStart) {
firstParagraphHtml = content.substring(firstParagraphStart, firstParagraphEnd + P_CLOSED.length());
while (firstParagraphHtml.substring(P_OPENED.length(), firstParagraphHtml.length() - P_CLOSED.length()).trim().isEmpty()
&& (firstParagraphEnd + P_CLOSED.length()) < content.length()) {
firstParagraphStart = content.indexOf(P_OPENED, firstParagraphEnd);
@ -311,11 +310,18 @@ public class WikiArticleHelper {
break;
}
}
}
if (Algorithms.isEmpty(firstParagraphHtml)) {
firstParagraphHtml = source;
}
if (Algorithms.isEmpty(firstParagraphHtml)) {
return null;
}
String firstParagraphText = Html.fromHtml(firstParagraphHtml.replaceAll("(<(/)(a|img)>)|(<(a|img).+?>)|(<div.+?/div>)", ""))
.toString().trim();
String[] phrases = firstParagraphText.split("\\. ");
StringBuilder res = new StringBuilder();
int limit = Math.min(phrases.length, PARTIAL_CONTENT_PHRASES);
for (int i = 0; i < limit; i++) {
@ -324,7 +330,6 @@ public class WikiArticleHelper {
res.append(". ");
}
}
return res.toString();
}

View file

@ -17,7 +17,6 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.ImageView;
import android.widget.TextView;
@ -28,7 +27,6 @@ import androidx.appcompat.widget.PopupMenu;
import androidx.appcompat.widget.Toolbar;
import androidx.browser.customtabs.CustomTabsIntent;
import androidx.core.content.ContextCompat;
import androidx.core.view.MotionEventCompat;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;

View file

@ -373,7 +373,7 @@ public class WikivoyageArticleDialogFragment extends WikiArticleBaseDialogFragme
@NonNull String title,
@NonNull String lang) {
TravelArticleIdentifier articleId = app.getTravelHelper().getArticleId(title, lang);
return showInstance(app, fm, articleId, lang);
return articleId != null && showInstance(app, fm, articleId, lang);
}
public static boolean showInstance(@NonNull OsmandApplication app,

View file

@ -9,11 +9,9 @@ import androidx.annotation.Nullable;
import androidx.annotation.Size;
import net.osmand.GPXUtilities.GPXFile;
import net.osmand.Location;
import net.osmand.aidl.search.SearchResult;
import net.osmand.data.LatLon;
import net.osmand.IndexConstants;
import net.osmand.plus.OsmandApplication;
import net.osmand.util.Algorithms;
import net.osmand.util.MapUtils;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
@ -22,8 +20,6 @@ import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.Objects;
public class TravelArticle {
@ -40,22 +36,40 @@ public class TravelArticle {
String imageTitle;
GPXFile gpxFile;
String routeId;
String routeSource;
String routeSource = "";
long originalId;
String lang;
String contentsJson;
String aggregatedPartOf;
String fullContent;
long lastModified;
@NonNull
public TravelArticleIdentifier generateIdentifier() {
return new TravelArticleIdentifier(this);
}
@NonNull
public static String getTravelBook(@NonNull OsmandApplication app, @NonNull File file) {
return file.getPath().replace(app.getAppPath(IndexConstants.WIKIVOYAGE_INDEX_DIR).getPath() + "/", "");
}
@Nullable
public String getTravelBook(@NonNull OsmandApplication app) {
return file != null ? getTravelBook(app, file) : null;
}
public File getFile() {
return file;
}
public long getLastModified() {
if (lastModified > 0) {
return lastModified;
}
return file != null ? file.lastModified() : 0;
}
public String getTitle() {
return title;
}

View file

@ -563,6 +563,9 @@ public class TravelDbHelper implements TravelHelper {
cursor.close();
}
}
if (res == null) {
res = localDataHelper.getSavedArticle(articleId.file, articleId.routeId, lang);
}
return res;
}
@ -643,13 +646,19 @@ public class TravelDbHelper implements TravelHelper {
cursor.close();
}
}
if (res.isEmpty()) {
List<TravelArticle> articles = localDataHelper.getSavedArticles(articleId.file, articleId.routeId);
for (TravelArticle a : articles) {
res.add(a.getLang());
}
}
return res;
}
@NonNull
private TravelArticle readArticle(SQLiteCursor cursor) {
TravelArticle res = new TravelArticle();
res.file = selectedTravelBook;
res.title = cursor.getString(0);
try {
res.content = Algorithms.gzipToString(cursor.getBlob(1)).trim();
@ -671,7 +680,6 @@ public class TravelDbHelper implements TravelHelper {
} catch (IOException e) {
LOG.error(e.getMessage(), e);
}
return res;
}

View file

@ -56,8 +56,7 @@ public interface TravelHelper {
File createGpxFile(@NonNull final TravelArticle article);
// TODO: this method should be deleted once TravelDBHelper is deleted
// For TravelOBFHelper it could always return "" and should be no problem
// Bookmarks should be refactored properly to support multiple files
@Nullable
String getSelectedTravelBookName();
String getWikivoyageFileName();

View file

@ -4,11 +4,13 @@ package net.osmand.plus.wikivoyage.data;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import net.osmand.IndexConstants;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.api.SQLiteAPI.SQLiteConnection;
import net.osmand.plus.api.SQLiteAPI.SQLiteCursor;
import net.osmand.plus.wikipedia.WikiArticleHelper;
import net.osmand.util.Algorithms;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@ -24,12 +26,12 @@ public class TravelLocalDataHelper {
private static final int HISTORY_ITEMS_LIMIT = 300;
private WikivoyageLocalDataDbHelper dbHelper;
private final WikivoyageLocalDataDbHelper dbHelper;
private Map<String, WikivoyageSearchHistoryItem> historyMap = new HashMap<>();
private List<TravelArticle> savedArticles = new ArrayList<>();
private Set<Listener> listeners = new HashSet<>();
private final Set<Listener> listeners = new HashSet<>();
public void addListener(Listener listener) {
listeners.add(listener);
@ -70,21 +72,21 @@ public class TravelLocalDataHelper {
}
public void addToHistory(@NonNull TravelArticle article) {
addToHistory(article.getTitle(), article.getLang(), article.getIsPartOf());
}
File file = article.getFile();
String title = article.getTitle();
String lang = article.getLang();
String isPartOf = article.getIsPartOf();
public void addToHistory(String title, String lang, String isPartOf) {
String key = getHistoryKey(lang, title);
WikivoyageSearchHistoryItem item = historyMap.get(key);
boolean newItem = item == null;
if (newItem) {
item = new WikivoyageSearchHistoryItem();
}
WikivoyageSearchHistoryItem item = new WikivoyageSearchHistoryItem();
item.articleFile = file;
item.articleTitle = title;
item.lang = lang;
item.isPartOf = isPartOf;
item.lastAccessed = System.currentTimeMillis();
if (newItem) {
String key = item.getKey();
boolean exists = historyMap.containsKey(key);
if (!exists) {
dbHelper.addHistoryItem(item);
historyMap.put(key, item);
} else {
@ -98,10 +100,6 @@ public class TravelLocalDataHelper {
}
}
static String getHistoryKey(String lang, String title) {
return lang + ":"+title;
}
@NonNull
public List<TravelArticle> getSavedArticles() {
return new ArrayList<>(savedArticles);
@ -109,19 +107,8 @@ public class TravelLocalDataHelper {
public void addArticleToSaved(@NonNull TravelArticle article) {
if (!isArticleSaved(article)) {
TravelArticle saved = new TravelArticle();
saved.title = article.title;
saved.lang = article.lang;
saved.aggregatedPartOf = article.aggregatedPartOf;
saved.imageTitle = article.imageTitle;
saved.content = WikiArticleHelper.getPartialContent(article.getContent());
saved.lat = article.lat;
saved.lon = article.lon;
saved.routeId = article.routeId;
saved.fullContent = article.getContent();
saved.contentsJson = article.contentsJson;
savedArticles.add(saved);
dbHelper.addSavedArticle(saved);
savedArticles.add(article);
dbHelper.addSavedArticle(article);
notifySavedUpdated();
}
}
@ -164,17 +151,29 @@ public class TravelLocalDataHelper {
}
@Nullable
public TravelArticle getSavedArticle(String routeId, String lang) {
public TravelArticle getSavedArticle(File file, String routeId, String lang) {
for (TravelArticle article : savedArticles) {
if (article.routeId != null && article.routeId.equals(routeId)
&& article.lang != null && article.lang.equals(lang)) {
article.content = article.fullContent;
if (Algorithms.objectEquals(article.file, file)
&& Algorithms.stringsEqual(article.routeId, routeId)
&& Algorithms.stringsEqual(article.lang, lang)) {
return article;
}
}
return null;
}
@NonNull
public List<TravelArticle> getSavedArticles(File file, String routeId) {
List<TravelArticle> articles = new ArrayList<>();
for (TravelArticle article : savedArticles) {
if (Algorithms.objectEquals(article.file, file)
&& Algorithms.stringsEqual(article.routeId, routeId)) {
articles.add(article);
}
}
return articles;
}
public interface Listener {
void savedArticlesUpdated();
@ -182,7 +181,7 @@ public class TravelLocalDataHelper {
private static class WikivoyageLocalDataDbHelper {
private static final int DB_VERSION = 6;
private static final int DB_VERSION = 7;
private static final String DB_NAME = "wikivoyage_local_data";
private static final String HISTORY_TABLE_NAME = "wikivoyage_search_history";
@ -219,6 +218,7 @@ public class TravelLocalDataHelper {
private static final String BOOKMARKS_COL_ROUTE_ID = "route_id";
private static final String BOOKMARKS_COL_CONTENT_JSON = "content_json";
private static final String BOOKMARKS_COL_CONTENT = "content";
private static final String BOOKMARKS_COL_LAST_MODIFIED = "last_modified";
private static final String BOOKMARKS_TABLE_CREATE = "CREATE TABLE IF NOT EXISTS " +
BOOKMARKS_TABLE_NAME + " (" +
@ -226,25 +226,26 @@ public class TravelLocalDataHelper {
BOOKMARKS_COL_LANG + " TEXT, " +
BOOKMARKS_COL_IS_PART_OF + " TEXT, " +
BOOKMARKS_COL_IMAGE_TITLE + " TEXT, " +
BOOKMARKS_COL_PARTIAL_CONTENT + " TEXT, " +
BOOKMARKS_COL_TRAVEL_BOOK + " TEXT, " +
BOOKMARKS_COL_LAT + " double, " +
BOOKMARKS_COL_LON + " double, " +
BOOKMARKS_COL_ROUTE_ID + " TEXT, " +
BOOKMARKS_COL_CONTENT_JSON + " TEXT, " +
BOOKMARKS_COL_CONTENT + " TEXT" + ");";
BOOKMARKS_COL_CONTENT + " TEXT, " +
BOOKMARKS_COL_LAST_MODIFIED + " long" + ");";
private static final String BOOKMARKS_TABLE_SELECT = "SELECT " +
BOOKMARKS_COL_ARTICLE_TITLE + ", " +
BOOKMARKS_COL_LANG + ", " +
BOOKMARKS_COL_IS_PART_OF + ", " +
BOOKMARKS_COL_IMAGE_TITLE + ", " +
BOOKMARKS_COL_PARTIAL_CONTENT + ", " +
BOOKMARKS_COL_TRAVEL_BOOK + ", " +
BOOKMARKS_COL_LAT + ", " +
BOOKMARKS_COL_LON + ", " +
BOOKMARKS_COL_ROUTE_ID + ", " +
BOOKMARKS_COL_CONTENT_JSON + ", " +
BOOKMARKS_COL_CONTENT +
BOOKMARKS_COL_CONTENT + ", " +
BOOKMARKS_COL_LAST_MODIFIED +
" FROM " + BOOKMARKS_TABLE_NAME;
private final OsmandApplication context;
@ -253,8 +254,12 @@ public class TravelLocalDataHelper {
this.context = context;
}
@Nullable
private SQLiteConnection openConnection(boolean readonly) {
SQLiteConnection conn = context.getSQLiteAPI().getOrCreateDatabase(DB_NAME, readonly);
if (conn == null) {
return null;
}
if (conn.getVersion() < DB_VERSION) {
if (readonly) {
conn.close();
@ -283,7 +288,7 @@ public class TravelLocalDataHelper {
if (oldVersion < 3) {
conn.execSQL("ALTER TABLE " + HISTORY_TABLE_NAME + " ADD " + HISTORY_COL_TRAVEL_BOOK + " TEXT");
conn.execSQL("ALTER TABLE " + BOOKMARKS_TABLE_NAME + " ADD " + BOOKMARKS_COL_TRAVEL_BOOK + " TEXT");
String selectedTravelBookName = getSelectedTravelBookName();
String selectedTravelBookName = context.getTravelHelper().getSelectedTravelBookName();
if (selectedTravelBookName != null) {
Object[] args = new Object[]{selectedTravelBookName};
conn.execSQL("UPDATE " + HISTORY_TABLE_NAME + " SET " + HISTORY_COL_TRAVEL_BOOK + " = ?", args);
@ -301,20 +306,23 @@ public class TravelLocalDataHelper {
conn.execSQL("ALTER TABLE " + BOOKMARKS_TABLE_NAME + " ADD " + BOOKMARKS_COL_CONTENT_JSON + " TEXT");
conn.execSQL("ALTER TABLE " + BOOKMARKS_TABLE_NAME + " ADD " + BOOKMARKS_COL_CONTENT + " TEXT");
}
if (oldVersion < 7) {
conn.execSQL("ALTER TABLE " + BOOKMARKS_TABLE_NAME + " ADD " + BOOKMARKS_COL_LAST_MODIFIED + " long");
conn.execSQL("UPDATE " + BOOKMARKS_TABLE_NAME +
" SET " + BOOKMARKS_COL_CONTENT + " = " + BOOKMARKS_COL_PARTIAL_CONTENT +
" WHERE " + BOOKMARKS_COL_CONTENT + " is null");
conn.execSQL("UPDATE " + BOOKMARKS_TABLE_NAME +
" SET " + BOOKMARKS_COL_PARTIAL_CONTENT + " = null");
}
}
@NonNull
Map<String, WikivoyageSearchHistoryItem> getAllHistoryMap() {
Map<String, WikivoyageSearchHistoryItem> res = new LinkedHashMap<>();
String travelBook = getSelectedTravelBookName();
if (travelBook == null) {
return res;
}
SQLiteConnection conn = openConnection(true);
if (conn != null) {
try {
String query = HISTORY_TABLE_SELECT + " WHERE " + HISTORY_COL_TRAVEL_BOOK + " = ?";
SQLiteCursor cursor = conn.rawQuery(query, new String[]{travelBook});
SQLiteCursor cursor = conn.rawQuery(HISTORY_TABLE_SELECT, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
do {
@ -322,8 +330,8 @@ public class TravelLocalDataHelper {
res.put(item.getKey(), item);
} while (cursor.moveToNext());
}
}
cursor.close();
}
} finally {
conn.close();
}
@ -331,8 +339,8 @@ public class TravelLocalDataHelper {
return res;
}
void addHistoryItem(WikivoyageSearchHistoryItem item) {
String travelBook = getSelectedTravelBookName();
void addHistoryItem(@NonNull WikivoyageSearchHistoryItem item) {
String travelBook = item.getTravelBook(context);
if (travelBook == null) {
return;
}
@ -349,8 +357,8 @@ public class TravelLocalDataHelper {
}
}
void updateHistoryItem(WikivoyageSearchHistoryItem item) {
String travelBook = getSelectedTravelBookName();
void updateHistoryItem(@NonNull WikivoyageSearchHistoryItem item) {
String travelBook = item.getTravelBook(context);
if (travelBook == null) {
return;
}
@ -371,8 +379,8 @@ public class TravelLocalDataHelper {
}
}
void removeHistoryItem(WikivoyageSearchHistoryItem item) {
String travelBook = getSelectedTravelBookName();
void removeHistoryItem(@NonNull WikivoyageSearchHistoryItem item) {
String travelBook = item.getTravelBook(context);
if (travelBook == null) {
return;
}
@ -391,16 +399,10 @@ public class TravelLocalDataHelper {
}
void clearAllHistory() {
String travelBook = getSelectedTravelBookName();
if (travelBook == null) {
return;
}
SQLiteConnection conn = openConnection(false);
if (conn != null) {
try {
conn.execSQL("DELETE FROM " + HISTORY_TABLE_NAME +
" WHERE " + HISTORY_COL_TRAVEL_BOOK + " = ?",
new Object[]{travelBook});
conn.execSQL("DELETE FROM " + HISTORY_TABLE_NAME);
} finally {
conn.close();
}
@ -410,19 +412,21 @@ public class TravelLocalDataHelper {
@NonNull
List<TravelArticle> readSavedArticles() {
List<TravelArticle> res = new ArrayList<>();
String travelBook = getSelectedTravelBookName();
if (travelBook == null) {
return res;
}
SQLiteConnection conn = openConnection(true);
if (conn != null) {
try {
String query = BOOKMARKS_TABLE_SELECT + " WHERE " + BOOKMARKS_COL_TRAVEL_BOOK + " = ?";
SQLiteCursor cursor = conn.rawQuery(query, new String[]{travelBook});
SQLiteCursor cursor = conn.rawQuery(BOOKMARKS_TABLE_SELECT, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
do {
res.add(readSavedArticle(cursor));
TravelArticle dbArticle = readSavedArticle(cursor);
TravelArticle article = context.getTravelHelper().getArticleById(dbArticle.generateIdentifier(), dbArticle.lang);
if (article != null && article.getLastModified() > dbArticle.getLastModified()) {
updateSavedArticle(dbArticle, article);
res.add(article);
} else {
res.add(dbArticle);
}
} while (cursor.moveToNext());
}
cursor.close();
@ -434,8 +438,8 @@ public class TravelLocalDataHelper {
return res;
}
void addSavedArticle(TravelArticle article) {
String travelBook = getSelectedTravelBookName();
void addSavedArticle(@NonNull TravelArticle article) {
String travelBook = article.getTravelBook(context);
if (travelBook == null) {
return;
}
@ -447,26 +451,26 @@ public class TravelLocalDataHelper {
BOOKMARKS_COL_LANG + ", " +
BOOKMARKS_COL_IS_PART_OF + ", " +
BOOKMARKS_COL_IMAGE_TITLE + ", " +
BOOKMARKS_COL_PARTIAL_CONTENT + ", " +
BOOKMARKS_COL_TRAVEL_BOOK + ", " +
BOOKMARKS_COL_LAT + ", " +
BOOKMARKS_COL_LON + ", " +
BOOKMARKS_COL_ROUTE_ID + ", " +
BOOKMARKS_COL_CONTENT_JSON + ", " +
BOOKMARKS_COL_CONTENT +
BOOKMARKS_COL_CONTENT + ", " +
BOOKMARKS_COL_LAST_MODIFIED +
") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
conn.execSQL(query, new Object[]{article.title, article.lang,
article.aggregatedPartOf, article.imageTitle, article.content,
article.aggregatedPartOf, article.imageTitle,
travelBook, article.lat, article.lon, article.routeId, article.contentsJson,
article.fullContent});
article.content, article.getFile().lastModified()});
} finally {
conn.close();
}
}
}
void removeSavedArticle(TravelArticle article) {
String travelBook = getSelectedTravelBookName();
void removeSavedArticle(@NonNull TravelArticle article) {
String travelBook = article.getTravelBook(context);
if (travelBook == null) {
return;
}
@ -475,44 +479,78 @@ public class TravelLocalDataHelper {
try {
conn.execSQL("DELETE FROM " + BOOKMARKS_TABLE_NAME +
" WHERE " + BOOKMARKS_COL_ARTICLE_TITLE + " = ?" +
" AND " + BOOKMARKS_COL_ROUTE_ID + " = ?" +
" AND " + BOOKMARKS_COL_LANG + " = ?" +
" AND " + BOOKMARKS_COL_TRAVEL_BOOK + " = ?",
new Object[]{article.title, article.lang, travelBook});
new Object[]{article.title, article.routeId, article.lang, travelBook});
} finally {
conn.close();
}
}
}
@Nullable
private String getSelectedTravelBookName() {
return context.getTravelHelper().getSelectedTravelBookName();
void updateSavedArticle(@NonNull TravelArticle odlArticle, @NonNull TravelArticle newArticle) {
String travelBook = odlArticle.getTravelBook(context);
if (travelBook == null) {
return;
}
SQLiteConnection conn = openConnection(false);
if (conn != null) {
try {
conn.execSQL("UPDATE " + BOOKMARKS_TABLE_NAME + " SET " +
BOOKMARKS_COL_ARTICLE_TITLE + " = ?, " +
BOOKMARKS_COL_LANG + " = ?, " +
BOOKMARKS_COL_IS_PART_OF + " = ?, " +
BOOKMARKS_COL_IMAGE_TITLE + " = ?, " +
BOOKMARKS_COL_TRAVEL_BOOK + " = ?, " +
BOOKMARKS_COL_LAT + " = ?, " +
BOOKMARKS_COL_LON + " = ?, " +
BOOKMARKS_COL_ROUTE_ID + " = ?, " +
BOOKMARKS_COL_CONTENT_JSON + " = ?, " +
BOOKMARKS_COL_CONTENT + " = ?, " +
BOOKMARKS_COL_LAST_MODIFIED + " = ?, " +
"WHERE " + BOOKMARKS_COL_ARTICLE_TITLE + " = ? " +
" AND " + BOOKMARKS_COL_ROUTE_ID + " = ?" +
" AND " + BOOKMARKS_COL_LANG + " = ?" +
" AND " + BOOKMARKS_COL_TRAVEL_BOOK + " = ?",
new Object[]{newArticle.title, newArticle.lang, newArticle.aggregatedPartOf,
newArticle.imageTitle, travelBook, newArticle.lat, newArticle.lon,
newArticle.routeId, newArticle.content, newArticle.contentsJson,
odlArticle.title, odlArticle.routeId, odlArticle.lang, travelBook});
} finally {
conn.close();
}
}
}
@NonNull
private WikivoyageSearchHistoryItem readHistoryItem(SQLiteCursor cursor) {
WikivoyageSearchHistoryItem res = new WikivoyageSearchHistoryItem();
res.articleTitle = cursor.getString(cursor.getColumnIndex(HISTORY_COL_ARTICLE_TITLE));
res.lang = cursor.getString(cursor.getColumnIndex(HISTORY_COL_LANG));
res.isPartOf = cursor.getString(cursor.getColumnIndex(HISTORY_COL_IS_PART_OF));
res.lastAccessed = cursor.getLong(cursor.getColumnIndex(HISTORY_COL_LAST_ACCESSED));
return res;
}
@NonNull
private TravelArticle readSavedArticle(SQLiteCursor cursor) {
TravelArticle res = new TravelArticle();
res.title = cursor.getString(cursor.getColumnIndex(BOOKMARKS_COL_ARTICLE_TITLE));
res.lang = cursor.getString(cursor.getColumnIndex(BOOKMARKS_COL_LANG));
res.aggregatedPartOf = cursor.getString(cursor.getColumnIndex(BOOKMARKS_COL_IS_PART_OF));
res.imageTitle = cursor.getString(cursor.getColumnIndex(BOOKMARKS_COL_IMAGE_TITLE));
res.content = cursor.getString(cursor.getColumnIndex(BOOKMARKS_COL_PARTIAL_CONTENT));
res.content = cursor.getString(cursor.getColumnIndex(BOOKMARKS_COL_CONTENT));
res.lat = cursor.getDouble(cursor.getColumnIndex(BOOKMARKS_COL_LAT));
res.lon = cursor.getDouble(cursor.getColumnIndex(BOOKMARKS_COL_LON));
res.routeId = cursor.getString(cursor.getColumnIndex(BOOKMARKS_COL_ROUTE_ID));
res.contentsJson = cursor.getString(cursor.getColumnIndex(BOOKMARKS_COL_CONTENT_JSON));
res.fullContent = cursor.getString(cursor.getColumnIndex(BOOKMARKS_COL_CONTENT));
String travelBook = cursor.getString(cursor.getColumnIndex(BOOKMARKS_COL_TRAVEL_BOOK));
if (!Algorithms.isEmpty(travelBook)) {
res.file = context.getAppPath(IndexConstants.WIKIVOYAGE_INDEX_DIR + travelBook);
res.lastModified = cursor.getLong(cursor.getColumnIndex(BOOKMARKS_COL_LAST_MODIFIED));
}
return res;
}
}

View file

@ -278,7 +278,7 @@ public class TravelObfHelper implements TravelHelper {
parts = null;
}
Map<String, List<WikivoyageSearchResult>> navMap = new HashMap<>();
Set<String> headers = new LinkedHashSet<String>();
Set<String> headers = new LinkedHashSet<>();
Map<String, WikivoyageSearchResult> headerObjs = new HashMap<>();
Map<File, List<Amenity>> amenityMap = new HashMap<>();
for (BinaryMapIndexReader reader : getReaders()) {
@ -335,7 +335,7 @@ public class TravelObfHelper implements TravelHelper {
navMap.put(rs.isPartOf, l);
}
l.add(rs);
if (headers != null && headers.contains(a.getTitle())) {
if (headers.contains(a.getTitle())) {
headerObjs.put(a.getTitle(), rs);
}
}
@ -365,7 +365,7 @@ public class TravelObfHelper implements TravelHelper {
@Override
public TravelArticle getArticleById(@NonNull TravelArticleIdentifier articleId, @NonNull String lang) {
TravelArticle article = getCachedArticle(articleId, lang);
return article == null ? findArticleById(articleId, lang) : article;
return article == null ? localDataHelper.getSavedArticle(articleId.file, articleId.routeId, lang) : article;
}
@Nullable
@ -390,10 +390,11 @@ public class TravelObfHelper implements TravelHelper {
private TravelArticle findArticleById(@NonNull final TravelArticleIdentifier articleId, final String lang) {
TravelArticle article = null;
final boolean isDbArticle = articleId.file != null && articleId.file.getName().endsWith(IndexConstants.BINARY_WIKIVOYAGE_MAP_INDEX_EXT);
final List<Amenity> amenities = new ArrayList<>();
for (BinaryMapIndexReader reader : getReaders()) {
try {
if (articleId.file != null && !articleId.file.equals(reader.getFile())) {
if (articleId.file != null && !articleId.file.equals(reader.getFile()) && !isDbArticle) {
continue;
}
SearchRequest<Amenity> req = BinaryMapIndexReader.buildSearchPoiRequest(0, 0,
@ -404,7 +405,7 @@ public class TravelObfHelper implements TravelHelper {
@Override
public boolean publish(Amenity amenity) {
if (Algorithms.stringsEqual(articleId.routeId, Algorithms.emptyIfNull(amenity.getTagContent(Amenity.ROUTE_ID, null)))
&& Algorithms.stringsEqual(articleId.routeSource, Algorithms.emptyIfNull(amenity.getTagContent(Amenity.ROUTE_SOURCE, null)))) {
&& Algorithms.stringsEqual(articleId.routeSource, Algorithms.emptyIfNull(amenity.getTagContent(Amenity.ROUTE_SOURCE, null))) || isDbArticle) {
amenities.add(amenity);
done = true;
}
@ -519,10 +520,15 @@ public class TravelObfHelper implements TravelHelper {
ArrayList<String> res = new ArrayList<>();
TravelArticle article = getArticleById(articleId, "");
if (article != null) {
Map<String, TravelArticle> articles = cachedArticles.get(articleId);
Map<String, TravelArticle> articles = cachedArticles.get(article.generateIdentifier());
if (articles != null) {
res.addAll(articles.keySet());
}
} else {
List<TravelArticle> articles = localDataHelper.getSavedArticles(articleId.file, articleId.routeId);
for (TravelArticle a : articles) {
res.add(a.getLang());
}
}
return res;
}
@ -547,7 +553,7 @@ public class TravelObfHelper implements TravelHelper {
@Override
public String getSelectedTravelBookName() {
return "";
return null;
}
@Override

View file

@ -1,17 +1,36 @@
package net.osmand.plus.wikivoyage.data;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import net.osmand.plus.OsmandApplication;
import java.io.File;
public class WikivoyageSearchHistoryItem {
File articleFile;
String articleTitle;
String lang;
String isPartOf;
long lastAccessed;
public String getKey() {
return TravelLocalDataHelper.getHistoryKey(lang, articleTitle);
public static String getKey(String lang, String title, @Nullable File file) {
return lang + ":" + title + (file != null ? ":" + file.getName() : "");
}
public String getKey() {
return getKey(lang, articleTitle, articleFile);
}
public File getArticleFile() {
return articleFile;
}
@Nullable
public String getTravelBook(@NonNull OsmandApplication app) {
return articleFile != null ? TravelArticle.getTravelBook(app, articleFile) : null;
}
public String getArticleTitle() {
return articleTitle;

View file

@ -22,6 +22,7 @@ import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.R;
import net.osmand.plus.UiUtilities;
import net.osmand.plus.widgets.tools.CropCircleTransformation;
import net.osmand.plus.wikipedia.WikiArticleHelper;
import net.osmand.plus.wikivoyage.WikivoyageUtils;
import net.osmand.plus.wikivoyage.data.TravelArticle;
import net.osmand.plus.wikivoyage.data.TravelLocalDataHelper;
@ -102,7 +103,7 @@ public class SavedArticlesRvAdapter extends RecyclerView.Adapter<RecyclerView.Vi
holder.icon.setVisibility(loaded == null || loaded.booleanValue() ? View.VISIBLE : View.GONE);
holder.title.setText(article.getTitle());
holder.content.setText(article.getContent());
holder.content.setText(WikiArticleHelper.getPartialContent(article.getContent()));
holder.partOf.setText(article.getGeoDescription());
holder.leftButton.setText(app.getString(R.string.shared_string_read));
holder.leftButton.setCompoundDrawablesWithIntrinsicBounds(readIcon, null, null, null);
@ -178,7 +179,7 @@ public class SavedArticlesRvAdapter extends RecyclerView.Adapter<RecyclerView.Vi
@Override
public void onClick(View view) {
Object item = getItemByPosition();
if (item != null && item instanceof TravelArticle) {
if (item instanceof TravelArticle) {
if (listener != null) {
listener.openArticle((TravelArticle) item);
}
@ -193,7 +194,7 @@ public class SavedArticlesRvAdapter extends RecyclerView.Adapter<RecyclerView.Vi
@Override
public void onClick(View view) {
Object item = getItemByPosition();
if (item != null && item instanceof TravelArticle) {
if (item instanceof TravelArticle) {
final TravelArticle article = (TravelArticle) item;
final TravelLocalDataHelper ldh = app.getTravelHelper().getBookmarksHelper();
ldh.removeArticleFromSaved(article);