diff --git a/OsmAnd-java/src/main/java/net/osmand/GPXUtilities.java b/OsmAnd-java/src/main/java/net/osmand/GPXUtilities.java index b8835ff2c6..b2cef88e80 100644 --- a/OsmAnd-java/src/main/java/net/osmand/GPXUtilities.java +++ b/OsmAnd-java/src/main/java/net/osmand/GPXUtilities.java @@ -2544,7 +2544,7 @@ public class GPXUtilities { if (maxlat == null) { maxlat = parser.getAttributeValue("", "maxLat"); } - if (maxlat == null) { + if (maxlon == null) { maxlon = parser.getAttributeValue("", "maxLon"); } diff --git a/OsmAnd-java/src/main/java/net/osmand/binary/CachedOsmandIndexes.java b/OsmAnd-java/src/main/java/net/osmand/binary/CachedOsmandIndexes.java index 63c81d13fa..56b5731577 100644 --- a/OsmAnd-java/src/main/java/net/osmand/binary/CachedOsmandIndexes.java +++ b/OsmAnd-java/src/main/java/net/osmand/binary/CachedOsmandIndexes.java @@ -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,42 +23,50 @@ 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; private OsmAndStoredIndex.Builder storedIndexBuilder; private Log log = PlatformUtil.getLog(CachedOsmandIndexes.class); private boolean hasChanged = true; - + public static final int VERSION = 2; public void addToCache(BinaryMapIndexReader reader, File f) { hasChanged = true; - if(storedIndexBuilder == null) { + if (storedIndexBuilder == null) { storedIndexBuilder = OsmandIndex.OsmAndStoredIndex.newBuilder(); storedIndexBuilder.setVersion(VERSION); storedIndexBuilder.setDateCreated(System.currentTimeMillis()); - if(storedIndex != null) { - for(FileIndex ex : storedIndex.getFileIndexList()) { - storedIndexBuilder.addFileIndex(ex); + 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(); - fileIndex.setDateModified(d== 0?f.lastModified() : d); + fileIndex.setDateModified(d == 0 ? f.lastModified() : d); fileIndex.setSize(f.length()); fileIndex.setVersion(reader.getVersion()); fileIndex.setFileName(f.getName()); - for(MapIndex index : reader.getMapIndexes()) { + for (MapIndex index : reader.getMapIndexes()) { MapPart.Builder map = OsmandIndex.MapPart.newBuilder(); map.setSize(index.getLength()); map.setOffset(index.getFilePointer()); - if(index.getName() != null) { + if (index.getName() != null) { map.setName(index.getName()); } - for(MapRoot mr : index.getRoots() ) { + for (MapRoot mr : index.getRoots()) { MapLevel.Builder lev = OsmandIndex.MapLevel.newBuilder(); lev.setSize(mr.length); lev.setOffset(mr.filePointer); @@ -78,36 +80,36 @@ public class CachedOsmandIndexes { } fileIndex.addMapIndex(map); } - - for(AddressRegion index : reader.getAddressIndexes()) { + + for (AddressRegion index : reader.getAddressIndexes()) { AddressPart.Builder addr = OsmandIndex.AddressPart.newBuilder(); addr.setSize(index.getLength()); addr.setOffset(index.getFilePointer()); - if(index.getName() != null) { + if (index.getName() != null) { addr.setName(index.getName()); } - if(index.getEnName() != null) { + if (index.getEnName() != null) { addr.setNameEn(index.getEnName()); } addr.setIndexNameOffset(index.getIndexNameOffset()); - for(CitiesBlock mr : index.getCities() ) { + for (CitiesBlock mr : index.getCities()) { CityBlock.Builder cblock = OsmandIndex.CityBlock.newBuilder(); cblock.setSize(mr.length); cblock.setOffset(mr.filePointer); cblock.setType(mr.type); addr.addCities(cblock); } - for(String s : index.getAttributeTagsTable()) { + for (String s : index.getAttributeTagsTable()) { addr.addAdditionalTags(s); } fileIndex.addAddressIndex(addr); } - - for(PoiRegion index : reader.getPoiIndexes()) { + + for (PoiRegion index : reader.getPoiIndexes()) { PoiPart.Builder poi = OsmandIndex.PoiPart.newBuilder(); poi.setSize(index.getLength()); poi.setOffset(index.getFilePointer()); - if(index.getName() != null) { + if (index.getName() != null) { poi.setName(index.getName()); } poi.setLeft(index.left31); @@ -116,12 +118,12 @@ public class CachedOsmandIndexes { poi.setBottom(index.bottom31); fileIndex.addPoiIndex(poi.build()); } - - for(TransportIndex index : reader.getTransportIndexes()) { + + for (TransportIndex index : reader.getTransportIndexes()) { TransportPart.Builder transport = OsmandIndex.TransportPart.newBuilder(); transport.setSize(index.getLength()); transport.setOffset(index.getFilePointer()); - if(index.getName() != null) { + if (index.getName() != null) { transport.setName(index.getName()); } transport.setLeft(index.getLeft()); @@ -130,33 +132,33 @@ public class CachedOsmandIndexes { transport.setBottom(index.getBottom()); transport.setStopsTableLength(index.stopsFileLength); transport.setStopsTableOffset(index.stopsFileOffset); -// if(index.incompleteRoutesLength > 0) { + // if(index.incompleteRoutesLength > 0) { transport.setIncompleteRoutesLength(index.incompleteRoutesLength); transport.setIncompleteRoutesOffset(index.incompleteRoutesOffset); -// } + // } transport.setStringTableLength(index.stringTable.length); transport.setStringTableOffset(index.stringTable.fileOffset); fileIndex.addTransportIndex(transport); } - - for(RouteRegion index : reader.getRoutingIndexes()) { + + for (RouteRegion index : reader.getRoutingIndexes()) { RoutingPart.Builder routing = OsmandIndex.RoutingPart.newBuilder(); routing.setSize(index.getLength()); routing.setOffset(index.getFilePointer()); - if(index.getName() != null) { + if (index.getName() != null) { routing.setName(index.getName()); } - for(RouteSubregion sub : index.getSubregions()) { + for (RouteSubregion sub : index.getSubregions()) { addRouteSubregion(routing, sub, false); } - for(RouteSubregion sub : index.getBaseSubregions()) { + for (RouteSubregion sub : index.getBaseSubregions()) { addRouteSubregion(routing, sub, true); } fileIndex.addRoutingIndex(routing); } - + storedIndexBuilder.addFileIndex(fileIndex); - + } private void addRouteSubregion(RoutingPart.Builder routing, RouteSubregion sub, boolean base) { @@ -171,11 +173,11 @@ public class CachedOsmandIndexes { rpart.setShifToData(sub.shiftToData); 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())) { @@ -191,26 +193,26 @@ public class CachedOsmandIndexes { reader = new BinaryMapIndexReader(mf, f); addToCache(reader, f); if (log.isDebugEnabled()) { - log.debug("Initializing db " + f.getAbsolutePath() + " " + (System.currentTimeMillis() - val ) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + log.debug("Initializing db " + f.getAbsolutePath() + " " + (System.currentTimeMillis() - val) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } } else { reader = initFileIndex(found, mf, f); } return reader; } - + private BinaryMapIndexReader initFileIndex(FileIndex found, RandomAccessFile mf, File f) throws IOException { BinaryMapIndexReader reader = new BinaryMapIndexReader(mf, f, false); reader.version = found.getVersion(); reader.dateCreated = found.getDateModified(); - - for(MapPart index : found.getMapIndexList()) { + + for (MapPart index : found.getMapIndexList()) { MapIndex mi = new MapIndex(); mi.length = (int) index.getSize(); mi.filePointer = (int) index.getOffset(); mi.name = index.getName(); - - for(MapLevel mr : index.getLevelsList()) { + + for (MapLevel mr : index.getLevelsList()) { MapRoot root = new MapRoot(); root.length = (int) mr.getSize(); root.filePointer = (int) mr.getOffset(); @@ -226,15 +228,15 @@ public class CachedOsmandIndexes { reader.indexes.add(mi); reader.basemap = reader.basemap || mi.isBaseMap(); } - - for(AddressPart index : found.getAddressIndexList()) { + + for (AddressPart index : found.getAddressIndexList()) { AddressRegion mi = new AddressRegion(); mi.length = (int) index.getSize(); mi.filePointer = (int) index.getOffset(); mi.name = index.getName(); mi.enName = index.getNameEn(); mi.indexNameOffset = index.getIndexNameOffset(); - for(CityBlock mr : index.getCitiesList() ) { + for (CityBlock mr : index.getCitiesList()) { CitiesBlock cblock = new CitiesBlock(); cblock.length = (int) mr.getSize(); cblock.filePointer = (int) mr.getOffset(); @@ -245,8 +247,8 @@ public class CachedOsmandIndexes { reader.addressIndexes.add(mi); reader.indexes.add(mi); } - - for(PoiPart index : found.getPoiIndexList()) { + + for (PoiPart index : found.getPoiIndexList()) { PoiRegion mi = new PoiRegion(); mi.length = (int) index.getSize(); mi.filePointer = (int) index.getOffset(); @@ -258,14 +260,14 @@ public class CachedOsmandIndexes { reader.poiIndexes.add(mi); reader.indexes.add(mi); } - - for(TransportPart index : found.getTransportIndexList()) { + + for (TransportPart index : found.getTransportIndexList()) { TransportIndex mi = new TransportIndex(); mi.length = (int) index.getSize(); mi.filePointer = (int) index.getOffset(); mi.name = index.getName(); mi.left = index.getLeft(); - mi.right =index.getRight(); + mi.right = index.getRight(); mi.top = index.getTop(); mi.bottom = index.getBottom(); mi.stopsFileLength = index.getStopsTableLength(); @@ -278,14 +280,14 @@ public class CachedOsmandIndexes { reader.transportIndexes.add(mi); reader.indexes.add(mi); } - - for(RoutingPart index : found.getRoutingIndexList()) { + + for (RoutingPart index : found.getRoutingIndexList()) { RouteRegion mi = new RouteRegion(); mi.length = (int) index.getSize(); mi.filePointer = (int) index.getOffset(); mi.name = index.getName(); - - for(RoutingSubregion mr : index.getSubregionsList()) { + + for (RoutingSubregion mr : index.getSubregionsList()) { RouteSubregion sub = new RouteSubregion(mi); sub.length = (int) mr.getSize(); sub.filePointer = (int) mr.getOffset(); @@ -294,7 +296,7 @@ public class CachedOsmandIndexes { sub.top = mr.getTop(); sub.bottom = mr.getBottom(); sub.shiftToData = mr.getShifToData(); - if(mr.getBasemap()) { + if (mr.getBasemap()) { mi.basesubregions.add(sub); } else { mi.subregions.add(sub); @@ -303,7 +305,7 @@ public class CachedOsmandIndexes { reader.routingIndexes.add(mi); reader.indexes.add(mi); } - + return reader; } @@ -313,7 +315,7 @@ public class CachedOsmandIndexes { try { storedIndex = OsmandIndex.OsmAndStoredIndex.newBuilder().mergeFrom(is).build(); hasChanged = false; - if(storedIndex.getVersion() != version){ + if (storedIndex.getVersion() != version) { storedIndex = null; } } finally { @@ -321,7 +323,7 @@ public class CachedOsmandIndexes { } log.info("Initialize cache " + (System.currentTimeMillis() - time)); } - + public void writeToFile(File f) throws IOException { if (hasChanged) { FileOutputStream outputStream = new FileOutputStream(f); diff --git a/OsmAnd-java/src/main/java/net/osmand/search/SearchUICore.java b/OsmAnd-java/src/main/java/net/osmand/search/SearchUICore.java index e63870b1a7..df8c2bd5be 100644 --- a/OsmAnd-java/src/main/java/net/osmand/search/SearchUICore.java +++ b/OsmAnd-java/src/main/java/net/osmand/search/SearchUICore.java @@ -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; @@ -67,6 +69,9 @@ public class SearchUICore { private MapPoiTypes poiTypes; private static boolean debugMode = false; + + private static final Set FILTER_DUPLICATE_POI_SUBTYPE = new TreeSet( + Arrays.asList("building", "internet_access_yes")); public SearchUICore(MapPoiTypes poiTypes, String locale, boolean transliterate) { this.poiTypes = poiTypes; @@ -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; diff --git a/OsmAnd-java/src/main/java/net/osmand/search/core/SearchCoreFactory.java b/OsmAnd-java/src/main/java/net/osmand/search/core/SearchCoreFactory.java index c221d2ca77..5c2faf0b51 100644 --- a/OsmAnd-java/src/main/java/net/osmand/search/core/SearchCoreFactory.java +++ b/OsmAnd-java/src/main/java/net/osmand/search/core/SearchCoreFactory.java @@ -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; diff --git a/OsmAnd/AndroidManifest.xml b/OsmAnd/AndroidManifest.xml index 67709531f9..01f760954b 100644 --- a/OsmAnd/AndroidManifest.xml +++ b/OsmAnd/AndroidManifest.xml @@ -63,7 +63,8 @@ - + + + + + + + + + + + + + + + diff --git a/OsmAnd/res/layout/bottom_sheet_item_with_descr_and_radio_btn.xml b/OsmAnd/res/layout/bottom_sheet_item_with_descr_and_radio_btn.xml index 4bace93e9f..a346825302 100644 --- a/OsmAnd/res/layout/bottom_sheet_item_with_descr_and_radio_btn.xml +++ b/OsmAnd/res/layout/bottom_sheet_item_with_descr_and_radio_btn.xml @@ -9,7 +9,7 @@ diff --git a/OsmAnd/res/layout/bottom_sheet_item_with_descr_radio_and_icon_btn.xml b/OsmAnd/res/layout/bottom_sheet_item_with_descr_radio_and_icon_btn.xml new file mode 100644 index 0000000000..d81614fbb1 --- /dev/null +++ b/OsmAnd/res/layout/bottom_sheet_item_with_descr_radio_and_icon_btn.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OsmAnd/res/layout/fragment_help_article.xml b/OsmAnd/res/layout/fragment_help_article.xml index cd4456a080..bb6e44b2a2 100644 --- a/OsmAnd/res/layout/fragment_help_article.xml +++ b/OsmAnd/res/layout/fragment_help_article.xml @@ -14,7 +14,7 @@ app:theme="?attr/toolbar_theme" android:background="?attr/pstsTabBackground"/> - diff --git a/OsmAnd/res/layout/fragment_wikivoyage_article_dialog.xml b/OsmAnd/res/layout/fragment_wikivoyage_article_dialog.xml index 232e041b75..76fe893831 100644 --- a/OsmAnd/res/layout/fragment_wikivoyage_article_dialog.xml +++ b/OsmAnd/res/layout/fragment_wikivoyage_article_dialog.xml @@ -63,7 +63,8 @@ android:layout_width="match_parent" android:layout_weight="1" android:layout_height="0dp"> - diff --git a/OsmAnd/res/layout/help_activity.xml b/OsmAnd/res/layout/help_activity.xml index 8ae5922aa6..d7f2d97070 100644 --- a/OsmAnd/res/layout/help_activity.xml +++ b/OsmAnd/res/layout/help_activity.xml @@ -4,7 +4,8 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - diff --git a/OsmAnd/res/layout/mapillary_web_view.xml b/OsmAnd/res/layout/mapillary_web_view.xml index 3afc91e2af..3e2459b375 100644 --- a/OsmAnd/res/layout/mapillary_web_view.xml +++ b/OsmAnd/res/layout/mapillary_web_view.xml @@ -4,12 +4,12 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/layout/online_routing_preference_segment.xml b/OsmAnd/res/layout/online_routing_preference_segment.xml new file mode 100644 index 0000000000..5f095e126c --- /dev/null +++ b/OsmAnd/res/layout/online_routing_preference_segment.xml @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/layout/preference_toolbar_with_action_button.xml b/OsmAnd/res/layout/preference_toolbar_with_action_button.xml new file mode 100644 index 0000000000..7c072f7daf --- /dev/null +++ b/OsmAnd/res/layout/preference_toolbar_with_action_button.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/layout/print_dialog.xml b/OsmAnd/res/layout/print_dialog.xml index b76ddb1083..9ea458cc32 100644 --- a/OsmAnd/res/layout/print_dialog.xml +++ b/OsmAnd/res/layout/print_dialog.xml @@ -3,7 +3,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - diff --git a/OsmAnd/res/layout/wikipedia_dialog_fragment.xml b/OsmAnd/res/layout/wikipedia_dialog_fragment.xml index 91714016cd..ae21c22585 100644 --- a/OsmAnd/res/layout/wikipedia_dialog_fragment.xml +++ b/OsmAnd/res/layout/wikipedia_dialog_fragment.xml @@ -74,7 +74,7 @@ android:layout_height="0dp" android:layout_weight="1"> - diff --git a/OsmAnd/res/values-ar/strings.xml b/OsmAnd/res/values-ar/strings.xml index 80095419cd..7da14706cc 100644 --- a/OsmAnd/res/values-ar/strings.xml +++ b/OsmAnd/res/values-ar/strings.xml @@ -4014,4 +4014,5 @@ السماح بالتيارات والمصارف السماح بالممرات المائية المتقطعة السماح بالممرات المائية المتقطعة + مطالبة صوتية \ No newline at end of file diff --git a/OsmAnd/res/values-cs/strings.xml b/OsmAnd/res/values-cs/strings.xml index 6cbc3a9714..3159e5591a 100644 --- a/OsmAnd/res/values-cs/strings.xml +++ b/OsmAnd/res/values-cs/strings.xml @@ -1,6 +1,6 @@ - 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\'. + 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\'. Nahrát GPX soubory do OSM? Viditelnost Tagy @@ -24,17 +24,17 @@ sever severoseverovýchod severovýchod - východovýchodosever + východoseverovýchod východ - východovýchodojih + východojihovýchod jihovýchod jihojihovýchod jih jihojihozápad jihozápad - západozápadojih + západojihozápad západ - západozápadosever + západoseverozápad severozápad severoseverozápad vpřed @@ -53,7 +53,7 @@ Podle stran (8 sektorů) Podle hodin (12 sektorů) Styl interpretace směrů - Vyberte způsob vyjádření relativního směru pohybu. + Vyberte způsob vyjádření relativního směru pohybu Spustit automatické ohlašování Zastavit automatické ohlašování Jsem zde @@ -64,16 +64,16 @@ Fluorescentní barvy Použít fluorescentní barvy pro zobrazení cest a tras. Off-line editace - Vždy používat off-line editaci. + 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. 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í. Nahrávání… - {0} POI/Poznámek bylo odesláno + {0} POI/poznámek bylo odesláno Odeslat všechny - Odeslat změny do OSM + Odeslat změnu do OSM Smazat změnu Off-line editace OSM: - OSM POI body/poznámky uložené na zařízení - Zobrazit a spravovat OSM POI body/poznámky uložené v databázi na zařízení. + OSM POI/poznámky uložené v zařízení + Zobrazit a spravovat OSM POI/poznámky uložené v databázi v zařízení. Zadejte interval nahrávání pozice na server. Interval přímého přenosu 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}. @@ -97,7 +97,7 @@ Mapy celého světa a tematické mapy Celý svět - Wikipedie Hlasové pokyny (nahrávky, omezené funkce) - Hlasové pokyny (TTS-generované) + Hlasové pokyny (TTS, doporučeno) Wikipedie (off-line) Uživatelsky definované Soubor exportu obsahující Oblíbené již existuje. Nahradit ho\? @@ -110,9 +110,9 @@ Spravovat mapové soubory Stáhnout a spravovat off-line mapy uložené ve vašem zařízení. Obecné - Nastavení displeje, jazyka, zvuku a dalších parametrů. + Nastavení zobrazení a dalších společných parametrů aplikace. Vaše OSM uživatelské jméno - Vaše uživatelské jméno na openstreetmap.org. + Potřebné pro přispívání do openstreetmap.org. Heslo Služba na pozadí OsmAnd běží na pozadí i při vypnuté obrazovce. @@ -139,10 +139,10 @@ Velké město Zastavit simulaci Zapnout animaci - Nelze přejmenovat soubor. - Soubor tohoto jména již existuje. - Vašemu dotazu odpovídá několik kategorií POI: - Lokální data pro vyhledávání POI není dostupný. + Nepodařilo se přejmenovat soubor. + Soubor s tímto názvem již existuje. + Bylo nalezeno několik souvisejících kategorií POI. + Stáhněte si offline data pro vyhledávání POI. Hledat podle jména Soubor s POI daty \'%1$s\' již není potřeba a může být smazán. Lokální soubor pro úpravu POI bodů nebyl nalezen a ani nemohl být vytvořen. @@ -168,7 +168,7 @@ Více mapových detailů Zobrazit některé detaily vektorové mapy (silnice, atd.) již při menším zvětšení. Oblíbené body smazány. - Chystáte se smazat %1$d Oblíbených a %2$d skupin Oblíbených. Opravdu smazat\? + Opravdu chcete smazat %1$d Oblíbených a %2$d skupin Oblíbených\? Doma Přátelé Místa @@ -194,7 +194,7 @@ Mapová data Záloha Hlasové pokyny (TTS) - Hlasové pokyny (media) + Hlasové pokyny (nahrané) On-line mapy a dlaždice v mezipaměti Standardní mapy (vektorové) Data POI @@ -248,25 +248,25 @@ Vyberte jeden z balíčků OsmAnd k nainstalování Speciální aktivita pro vývojovou verzi Nové hledání - Zvolte velikost písma ve jménech na mapě. + Velikost písma pro názvy na mapě: Velikost písma Rozbalování nových dat… - On-line navigaci nelze použít, protože připojení k Internetu není k dispozici. + On-line navigaci nelze použít bez připojení k internetu. Nepodporovaný jazyk - Vybraný jazyk není podporován nainstalovaným TTS enginem. Najít jiný TTS v obchodě\? Jinak bude použit výchozí TTS jazyk. + 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ě\? Chybějící data Přejít do obchodu pro stažení vybraného jazyka\? - Obrátit GPX trasu + Obrátit směr stopy Použít současný cíl trasy Projet celou trasu Pro tuto oblast je k dispozici offline vektorová mapa. -\t -\t Pro zobrazení zvolte Menu → Nastavení mapy → Zdroj map… → Vektorové off-line mapy. +\n\t +\n\tPro zobrazení zvolte Menu → Nastavení mapy → Zdroj map… → Vektorové off-line mapy. Kanál pro navádění - Zvolte kanál poskytující hlasové navádění. + Zvolte reproduktor pro hlasové navádění. Zvuk telefonního hovoru (přeruší Bluetooth autorádio) Upozornění - Média/Zvuk navigace + Média/zvuky navigace Data pro mapovou vrstvu %1$s nelze stáhnout, reinstalace může pomoci. Upravit průhlednost překryvné mapy. Průhlednost překryvu @@ -279,9 +279,9 @@ Překryvná mapa… Žádná Překryvná mapa - Vyberte překryvnou mapu. + Vyberte překryvnou mapu Mapa již nainstalována, \'Nastavení\' budou aktualizována. - Vyberte mapy k instalaci nebo aktualizaci. + Vyberte (dlaždicové) mapy k instalaci nebo aktualizaci. Nelze provést tuto akci bez připojení k Internetu. Instalovat další… Použít rastrové mapy pro cokoli nad tuto úroveň. @@ -289,7 +289,7 @@ Nelze provést offline hledání. Hledat pomocí polohy Podle systému - Volba jazyka (projeví se jakmile bude OsmAnd restartován). + Jazyk zobrazení aplikace (projeví se po restartu OsmAnd). Jazyk Další Předchozí @@ -310,18 +310,18 @@ GPX bod na trase \'\'{0}\'\' byl přidán Přidat bod na zaznamenávanou GPX trasu 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. - Nemohu najít zadaný adresář. + Nepodařilo se najít zadaný adresář. Adresář pro data Aplikace pro zobrazení stavu GPS není nainstalovaná. Hledat v obchodě? 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. - Žádná hlasová data nejsou zvolena + Zvolte balíček hlasových údajů Zaškrtněte pro zobrazení statistik o vykreslování mapy. Sledovat vykreslování Den Noc Východ/západ slunce Senzor osvětlení - Vyberte logiku pro přepínání mezi denním a nočním režimem. + Upravte přepínání mezi denním a nočním režimem. Denní/noční režim Stáhnout {0} souborů ({1} MB)? vybráno {0} položek @@ -332,24 +332,24 @@ Zapněte pro výpočet nejrychlejší trasy nebo vypněte pro ekonomickou trasu. Pro zvětšení {0} je třeba stáhnout {1} mapových dlaždic, celkem {2} MB Stáhnout mapu - Vyberte maximální zvětšení stahovaných map + Maximální přiblížení pro přednačítání Tuto mapu nelze stáhnout Průběžné vykreslování Při zaškrtnuté volbě se mapa bude vykreslovat postupně. - Nelze vykreslit vybranou oblast + Nepodařilo se vykreslit vybranou oblast. Nedostatek paměti pro zobrazení vybrané oblasti Možnosti bodu… Vykreslovač načten - Nepodařilo se načíst vykreslovač + Nepodařilo se načíst vykreslovací modul. Vektorový vykreslovač - Vyberte styl vektorového vykreslování. + Vyberte styl vykreslování Zobrazit webovou stránku bodu Zobrazit telefonní číslo bodu Webová stránka telefon vyhledat Vysoké rozlišení - Použít mapu s vysokým rozlišením pro jemné displeje. + Neroztahovat (a nerozmazávat) mapové dlaždice na displejích s vysokou hustotou bodů. Pozice ještě není známa. Hledat veřejnou dopravu Hledání dopravy (žádný cíl): @@ -358,15 +358,15 @@ Nahrávka hlasu Vektorové mapy nebyly načteny Žádné GPX soubory nebyly nalezeny v adresáři tracks - Nelze načíst GPX data + Nepodařilo se načíst GPX data. Vektorové off-line mapy - Hledat dopravu ze zastávky + Hledat dopravu na zastávce Upravit POI Smazat POI Směr kompasu Směr pohybu Neotáčet (sever vždy nahoru) - Zvolte směr natočení mapy. + Směr natočení mapy: Natočení mapy Ukázat cestu Oblíbená místa importována @@ -374,7 +374,7 @@ Oblíbená místa uložena do {0} Žádná Oblíbená místa k uložení Importovat - Nelze načíst GPX + Nepodařilo se načíst GPX. Odeslat hlášení Na paměťové kartě nelze najít žádné stáhnuté mapy. Psaním hledejte POI @@ -383,7 +383,7 @@ Yandex doprava Cesta Oblíbené - OSM Poznámky (on-line) + OSM poznámky (online) Vrstva POI… Zdroj map… Mapová data @@ -398,9 +398,9 @@ GPS sekund min. - Vyberte interval pro zaměřování polohy pro službu na pozadí. + Interval probuzení pro službu na pozadí: Interval zaměřování GPS - Vyberte způsob získání polohy pro službu na pozadí. + Způsob získání polohy službou na pozadí: Poskytovatel polohy Sleduje vaši pozici i když je obrazovka vypnutá. Sledování polohy na pozadí @@ -415,7 +415,7 @@ Inicializace hlasových dat… Nepodporovaná verze hlasových dat Zvolené hlasové údaje jsou poškozené - Vybraná hlasová data jsou nedostupná + Vybraný balíček hlasových dat není dostupný Paměťová karta není dostupná. \nNeuvidíte mapu a nebudete moci ani nic najít. Paměťová karta má zakázaný zápis. diff --git a/OsmAnd/res/values-de/phrases.xml b/OsmAnd/res/values-de/phrases.xml index 028d5b5da0..bae219d165 100644 --- a/OsmAnd/res/values-de/phrases.xml +++ b/OsmAnd/res/values-de/phrases.xml @@ -222,7 +222,7 @@ Gärtner Flüssiggasspeicher Tor - Gemischtwarenhandlung + Allgemeines Geschäft Geysir Geschenkeladen Gletscher diff --git a/OsmAnd/res/values-de/strings.xml b/OsmAnd/res/values-de/strings.xml index 95344ac93f..870673e61e 100644 --- a/OsmAnd/res/values-de/strings.xml +++ b/OsmAnd/res/values-de/strings.xml @@ -4031,4 +4031,5 @@ Bäche und Entwässerungsgräben erlauben Gewässer erlauben, die nicht ständig Wasser führen Gewässer erlauben, die nicht ständig Wasser führen + Zeiten der Sprachansagen \ No newline at end of file diff --git a/OsmAnd/res/values-eo/strings.xml b/OsmAnd/res/values-eo/strings.xml index 32dfa4de74..bf083e4542 100644 --- a/OsmAnd/res/values-eo/strings.xml +++ b/OsmAnd/res/values-eo/strings.xml @@ -4027,4 +4027,5 @@ Permesi riveretojn kaj fosaĵojn Permesi navigi per periode sekiĝantaj akvovojoj Permesi sezonajn akvovojojn + Tempoj de voĉaj anoncoj \ No newline at end of file diff --git a/OsmAnd/res/values-es-rAR/strings.xml b/OsmAnd/res/values-es-rAR/strings.xml index 101029bd57..cb5bb0ca9b 100644 --- a/OsmAnd/res/values-es-rAR/strings.xml +++ b/OsmAnd/res/values-es-rAR/strings.xml @@ -4032,4 +4032,5 @@ Permitir arroyos y desagües Permite cursos de agua intermitentes Permitir cursos de agua intermitentes + Tiempo de los avisos por voz \ No newline at end of file diff --git a/OsmAnd/res/values-es-rUS/strings.xml b/OsmAnd/res/values-es-rUS/strings.xml index 311a2c875c..783a216b59 100644 --- a/OsmAnd/res/values-es-rUS/strings.xml +++ b/OsmAnd/res/values-es-rUS/strings.xml @@ -4029,4 +4029,5 @@ Perfil de usuario Perfil de OsmAnd Elige el perfil que será usado al iniciar la aplicación. + Tiempo de indicaciones por voz \ No newline at end of file diff --git a/OsmAnd/res/values-es/strings.xml b/OsmAnd/res/values-es/strings.xml index 484933e2aa..ae61a43fae 100644 --- a/OsmAnd/res/values-es/strings.xml +++ b/OsmAnd/res/values-es/strings.xml @@ -4019,4 +4019,5 @@ Permitir arroyos y desagües Permitir arroyos y desagües Permitir vías de agua intermitentes + Tiempo de indicaciones por voz \ No newline at end of file diff --git a/OsmAnd/res/values-fr/phrases.xml b/OsmAnd/res/values-fr/phrases.xml index 92e6a64d53..1d225381f7 100644 --- a/OsmAnd/res/values-fr/phrases.xml +++ b/OsmAnd/res/values-fr/phrases.xml @@ -3895,4 +3895,9 @@ Robinet Vaccination : covid19 Vaccination + Tunnel à chauve-souris + Pont à chauve- + Passage à faune + Zone de + Lavoir \ No newline at end of file diff --git a/OsmAnd/res/values-fr/strings.xml b/OsmAnd/res/values-fr/strings.xml index 137cfe1597..633470a815 100644 --- a/OsmAnd/res/values-fr/strings.xml +++ b/OsmAnd/res/values-fr/strings.xml @@ -4006,4 +4006,17 @@ Autoriser les voies navigables intermittentes Autoriser les cours d’eau et les drains Autoriser les cours d’eau et les drains + Nombre d\'annonces vocales + Si non, laissez vide + Sous-type + Véhicule + Clé d\'API + URL du serveur + Saisissez le paramètre + Calculer un itinéraire d’essai + En voiture + A pieds + Vélo + Automobile + Erreur, vérifiez les paramètres \ No newline at end of file diff --git a/OsmAnd/res/values-hu/strings.xml b/OsmAnd/res/values-hu/strings.xml index 2142695973..a9e91052bd 100644 --- a/OsmAnd/res/values-hu/strings.xml +++ b/OsmAnd/res/values-hu/strings.xml @@ -585,7 +585,7 @@ Keresztező utca kijelölése Legközelebbi hasznos létesítmények Térképböngészés - Vezetés + Autóvezetés Kerékpározás Gyaloglás Középen @@ -1292,7 +1292,7 @@ Közeli érdekes helyek (POI) Letöltöd a hiányzó térképeket %1$s (%2$d MB)? Térképböngészés - Autó + Személyautó Kerékpár Gyalogos 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 @@ Patakok és vízelvezető árkok engedélyezése Időszakos vízfolyások engedélyezése Időszakos vízfolyások engedélyezése + Autóvezetés + Gyaloglás + Kerékpár + Személyautó + Hangutasítások ideje + Online útvonaltervező hozzáadása + Online útvonaltervező szerkesztése + Altípus + Jármű + API-kulcs + Kiszolgáló URL-je + Paraméter megadása + Hagyja üresen, ha nem + Az összes paraméterrel rendelkező URL így néz ki: + Útvonaltervezés kipróbálása + Hiba, ellenőrizze újra a paramétereket \ No newline at end of file diff --git a/OsmAnd/res/values-it/phrases.xml b/OsmAnd/res/values-it/phrases.xml index cf0dcb6921..12ecad8876 100644 --- a/OsmAnd/res/values-it/phrases.xml +++ b/OsmAnd/res/values-it/phrases.xml @@ -2992,4 +2992,24 @@ Freccia Vibrazione Pressione + Moneta souvenir + Drive-through: no + Gobba + Fuoripista + Fuoripista + Bacheca + Segnaletica sentiero + Wiki gujarati + Wiki yoruba + Wiki ciuvascio + Wiki baschiro + wiki tagico + Wiki asturiano-leonese + Wiki chirghisa + Wiki min meridionale + Wiki minangkabau + Wiki waray + Wiki maratino + Wiki malayalam + Wiki frisone occidentale \ No newline at end of file diff --git a/OsmAnd/res/values-iw/strings.xml b/OsmAnd/res/values-iw/strings.xml index 90f56acd71..f2f971e562 100644 --- a/OsmAnd/res/values-iw/strings.xml +++ b/OsmAnd/res/values-iw/strings.xml @@ -4031,4 +4031,20 @@ לאפשר מקטעים עם דרכי מים עונתיים „ציבורי” משמעו שהעקבות מופיעים באופן ציבורי בעקבות ה־GPS שלך וברישומי עקבות GPS ציבוריים וברישומי מעקב ציבוריים עם חותמות זמן בתצורה גולמית. הנתונים שמוגשים דרך ה־API אינם מפנים אל עמוד העקבות שלך. חותמות הזמן של נקודות המעקב אינן זמינות דרך ה־API של ה־GPS ונקודות המעקב אינן מסודרות בהתאם לזמן שתועדו. „פרטי” משמעות שהעקבות לא תופענה ברישומים ציבוריים אך נקודות מעקב ממתוכן תהיינה זמינות בסדר אקראי דרך ה־API הציבורי של ה־GPS ללא חותמות זמן. + זמני הכרזות + הוספת מנוע ניווט מקוון + עריכת מנוע הניווט המקוון + תת־סוג + כלי רכב + מפתח API + כתובת השרת + נא למלא משתנים + להשאיר ריק אם לא + כתובת עם כל המשתנים נראית כך: + בדיקת חישוב מסלול + נהיגה + ברגל + אופנוע + מכונית + שגיאה, נא לבדוק את המשתנים מחדש \ No newline at end of file diff --git a/OsmAnd/res/values-ka/phrases.xml b/OsmAnd/res/values-ka/phrases.xml new file mode 100644 index 0000000000..a6b3daec93 --- /dev/null +++ b/OsmAnd/res/values-ka/phrases.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/OsmAnd/res/values-ka/strings.xml b/OsmAnd/res/values-ka/strings.xml index a3f8153e60..a39fb02b7b 100644 --- a/OsmAnd/res/values-ka/strings.xml +++ b/OsmAnd/res/values-ka/strings.xml @@ -5,13 +5,13 @@ მარცხენა მხარეს საჭის გამომყენებელი ქვეყნებისათვის. საწყისი პოზიცია ნაპოვნი ჯერ არაა. პოზიცია უცნობია. - გამჭირვალობის შეცვლა (0 - გამჭირვალე, 255 - გაუმჭირვალე) + გამჭირვალობა (0 - გამჭირვალე, 255 - გაუმჭირვალე) გნებავთ ჩამოტვირთვის შეწყვეტა? პროგრამის მთავარი თვისებების გამოსაყენებლად თქვენ გჭირდებათ მონაცემები რომლებიც გამოიყენება გათიშულ რეჟიმში, რომელიც შეგიძლიათ ჩამოტვირთოთ (პარამეტრები->მონაცემები გათიშვისას). ამის შემდეგ თქვენ შეგეძლებათ ძებნა როგორც მისამართის, ასევე საჯარო ტრანსპორტის მიხედვით. ბაზური რუკა საჭიროა პროგრამის ფუნქციონირებისათვის და არჩეულ იქნა გადმოსაწერად. არაფერი არ იქნა ნაპოვნი. თუ ვერ პოულობთ თქვენი რეგიონის რუკას, შეგიძლიათ შექმნათ თვითონ. (იხილეთ https://osmand.net). მიმდინარე რუკები (ნახაზები) - გათიშული რუკები (ვექტორული) + სტანდარტული რუკები (ვექტორული) გათიშული რუკების ჩამოტვირთვა, მართვა და დეტალები. "ჩართეთ მიმდინარე რუკების დამატება სხვადასხვა წყაროების სანახავად" მიმდინარე რუკები @@ -51,7 +51,7 @@ მენიუში დაბრუნება დაპატარავება გადიდება - გადიდების ზომა + გადიდების დონე ჩრდილოეთი ჩრდილო-ჩრდილო-აღმოსავლეთი ჩრდილო-აღმოსავლეთი @@ -68,9 +68,9 @@ დასავლეთი-ჩრდილო-დასავლეთი ჩრდილო-დასავლეთი ჩრდილოეთი-ჩრდილო-დასავლეთი - წინა + წინ წინა მარჯვენა - მარჯვენა + მარჯვნივ უკანა მარჯვენა უკან უკანა მარცხნივ @@ -97,14 +97,14 @@ ბილიკებისა და გზების მანათობელი ფერებით ჩვენება. POI-ს ჩასწორება გათიშულ რეჟიმში ყოველთვის გამოვიყენოთ POI-ს ჩასწორების გათიშული რეჟიმი. წინააღმდეგ შემთხვევაში ცვლილებები მყისიერად აიტვირთება. - აპლიკაციაში POI-ის ცვლილებები არ ეხება ჩამოტვირთულ რუკის ფაილებს. ცვლილებები ინახება ლოკალურ ფაილში. + აპლიკაციაში POI-ის ცვლილებები არ ეხება ჩამოტვირთულ რუკის ფაილებს. ცვლილებები ინახება თქვენს მოწყობილობაში. ატვირთვა… - {0} POI/Bugs ატვირთულია + {0} POI/notes ატვირთულია ყველას ატვირთვა ცვლილების OSM-ზე ატვირთვა ცვლილების წაშლა OSM POI-ის ასინქრონული ჩასწორება: - ადგილობრივად შენახული OSM POI-ები/Bugs + ადგილობრივად შენახული OSM POI-ები ადგილობრივ ბაზაში შენახული OSM POI-ების/Bugs ჩვენება და მართვა. მიუთითეთ მიმდინარე რეჟიმში დევნის ინტერვალი. ცოცხალი დევნის ინტერვალი @@ -122,10 +122,10 @@ ევროპა ევროპა - საფრანგეთი ევროპა - გერმანია - ევროპა/აზია - რუსეთი + რუსეთი აფრიკა აზია - ოკეანეთი + ავსტრალია და ოკეანეთი მსოფლიო რუკები მსოფლიო ვიკიპედია ჩაწერილი ხმოვანი პაკეტები @@ -173,32 +173,32 @@ ფაილისათვის სახელის გადარქმევა შეუძლებელია. ფაილი მოცემული სახელით უკვე არსებობს. ძებნის შედეგები შეიცავს რამოდენიმე POI კატეგორიას. - ადგილობრივი მონაცემები POI-ს ძებნისათვის არ არსებობს. + POI-ების მოსაძებნად გთხოვთ ჩამოწეროთ გათიშული რუკები. სახელით ძებნა POI-ის მონაცემთა ფაილის \'%1$s\', არსებობს ასლი და მისი წაშლა შესაძლებელია. ლოკალური ფაილი POI-ის ცვლილებების შესანახად ნაპოვნი არ იქნა და მისი შექმნა შეუძლებელია. განახლება OsmAnd+-მდე - სერვერი შეიცავს რუკის ფაილებს რომლებიც არაა შესაბამისი აპლიკაციის მიმდინარე ვერსიასთან. გთხოვთ განაახლოთ აპლიკაცია მათ ჩამოსაწერად და გამოსაყენებლად. + ახალი რუკების გამოსაყენებლად გთხოვთ განაახლოთ აპლიკაცია. სახელის გადარქმევა მიმდინარე POI-ს ძებნა სახელით მდებარეობის ძებნა… - მდებარეობა (ნაპოვნია) + ჩემი მდებარეობა (ნაპოვნია) მისამართი… რჩეულები… არააღწერილი - უკანასკნელად ნანახი რუკის ხედი - ძებნა ახლოს : + მიდინარე რუკის ცენტრი + საწყისი : ახლოს ძებნა გზა შეინახა წარმატებით. სახელი \'%1$s\'. ფაილის სახელი: ფაილი მოცემული სახელით უკვე არსებობს. შენახვა GPX ფაილების OSM-ში ატვირთვა. ისინი გამოიყენება რუკების გასაუმჯობესებლად. - %1$d %2$d -დან წარმატებით აიტვირთა. + ატვირთულია %1$d %2$d -დან. OSM-ში გაგზავნა მეტი დეტალები რუკის შესახებ ვექტორული რუკის დეტალების (გზების და ა.შ.) ჩვენება პატარა გადიდებისას. - რჩეული წერტილები წარმატებით წაიშალა. + რჩეული წერტილები წაშლილია. თქვენ აპირებთ წაშალოთ %1$d რჩეული და %2$d რჩეულთა ჯგუფი. დარწმუნებული ხართ? საწყისი მეგობრები @@ -206,13 +206,13 @@ სახელი კატეგორია არა, მადლობა - მსოფლიოს ბაზური რუკა, რომელიც შეიცავს მთელ მსოფლიოს პატარა გადიდებისას, არ არსებობს. გთხოვთ გადმოწეროთ World_basemap_x.obf. - დასტაში \"offline\" sdcard-ზე არ არსებობს მონაცემები. გთხოვთ გადმოწეროთ რუკებით გათიშულ რეჟიმში სარგებლობისათვის . + მსოფლიოს ბაზური რუკა, რომელიც შეიცავს მთელ მსოფლიოს პატარა გადიდებისას, არ არსებობს. ტ6გთხოვთ გადმოწეროთ World_basemap_x.obf. + გთხოვთ გადმოწეროთ რუკები გათიშულ რეჟიმში სარგებლობისათვის . "\n\nპარამეტრების შესაცვლელად დააჭირეთ და გეჭიროთ" - გამოცემა - %1$d %2$d -დან წარმატებით გაიარა დეაქტივაცია. - %1$d %2$d -დან წარმატებით წაიშალა. - %1$d %2$d -დან წარმატებით გააქტიურდა. + ლოკალური ვერსია + %1$d %2$d -დან დეაქტივირებულია. + %1$d %2$d -დან წაშლილია. + %1$d %2$d -დან გააქტიურებულია. %1$s არ არსებობს თქვენ აპირებთ %1$s %2$s . გავაგრძელოთ? გათიშული მონაცემების მმართველი. @@ -239,7 +239,7 @@ არჩეული ენა არაა მხარდაჭერილი ანდროიდის მიმდინარე ვერსიის TTS ძრავის მიერ. გნებავთ მოვძებნოთ სხვა ძრავი market-ში\? წინააღმდეგ შემთხვევაში გამოყენებულ იქნება მიმდინარე TTS ენა\? ნაკლული მონაცემები არჩეული ენის შესაბამისი მონაცემების პოვნა შეუძლებელია. გნებავთ მაღაზიაში მათი მოძებნა? - GPX-ის უკუღმა მიმართულება + უკუღმა მიმართულება მიმდინარე მიმართულების გამოყენება მიმდინარე ბილიკის დევნა ამ ლოკაციისათვის არსებობს გათიშული ვექტორული რუკა.\n\t\n\tაქტივაციისათვის \'მენიუ\' -> \'ხედის გამართვა\' -> \'რუკის წყარო…\' -> \'გათიშული ვექტორული რუკები\'. @@ -247,7 +247,7 @@ აირჩიეთ არხი ხმოვანი ბრძანებებისათვის. ხმოვანი დარეკვა შეტყობინების ხმა - მუსიკის ხმა + მუსიკის/ნავიგაციის ხმა შეუძლებელია %1$s -ის ჩამოტვირთვა, გთხოვთ თავიდან დააყენოთ აპლიკაცია. გამჭირვალობის შეცვლა. გამჭირვალობა @@ -275,7 +275,7 @@ შემდეგი წინა სიგრძისა და სიჩქარის ერთეულები. - გაზომვის ერთეული + სიგრძის ერთეულები მილი/ფუტი მილი/იარდი კილომეტრი/მეტრი @@ -288,7 +288,7 @@ მდებარეობის სანახავად მიჰყევით ბმულს %1$s ან ანდროიდის ბმულს %2$s მდებარეობის გაგზავნა მდებარეობის გაზიარება - გზის წერტილი \'\'{0}\'\' წარმატებით დაემატა + გზის წერტილი \'\'{0}\'\' დამატებულია ჩაწერილი GPX ბილიკისათვის სანავიგაციო წერტილის დამატება ადმინისტრაციული ბარიერი @@ -310,7 +310,7 @@ სპორტი ნოყიერი ტურიზმი - სატრანსპორტო + ტრანსპორტი მისამართების ინდექსირება… რუკების ინდექსირება… POI-ის ინდექსირება… @@ -319,7 +319,7 @@ კმ კმ/ს - ამოღებული რუკის ფორმატი \'\'{0}\'\'-ს მხარდაჭერა ამოღებულია + რუკის ფორმატის \'\'{0}\'\'-ს მხარდაჭერა ამოღებულია მანქანის შეკეთება უახლოესი POI ხელით შეყვანილი ფილტრი @@ -331,7 +331,7 @@ ინდექსი \'\'{0}\'\' არ ეტევა მეხსიერებაში ინდექსის ვერსიის \'\'{0}\'\' მხარდაჭერა არ არსებობს OsmAnd-ის გათიშული ნავიგაცია არის საცდელი თვისება რომებიც არ მუშაობს 20 კმ-ზე მეტი მანძილებისათვის.\n\nნავიგაციის სერვისი დროებით გადართულია მიმდინარე სერვისზე CloudMade. - შეუძლებელია მითითებული დასტის პოვნა. + მითითებული დასტის პოვნა შეუძლებელია. შესანახი დასტა დაყენებულია OsmAnd-ის წინა ვერსია. მთელი გათიშული მონაცემების მხარდაჭერილია და გამოყენებულ იქნება ახალ აპლიკაციაში. რჩეული წერტილების გამოსაყენებლად ისინი უნდა დააექსპორტოთ ძველიდან და შეიტანოთ ახალში. ვერსია {0}-ის ჩადგმა წარმატებულად დამთავრდა ({1}). @@ -343,7 +343,7 @@ უფასო ვერსიის შეწირულობის აქტივობა ვერ ვიპოვე GPS სტატუსის აპლიკაცია. გნებავთ მისი მოძებნა მაღაზიაში? ხმოვანი ბრძანებები მიუწვდომელია. გთხოვთ გადახვიდეთ პარამეტრებში, აირჩიოთ მხარდაჭერილი ხმოვანი პაკეტი და დააყენოთ ის. - ხმოვანი ბრძანებები არჩეული არაა + აირჩიეთ ხმოვანი დამხმარის პაკეტი დღე ღამე მზის ჩასვლა/ამოსვლა @@ -359,13 +359,13 @@ ჩართეთ უსწრაფესი გზის დასათვლელად ან გამორთეთ უმოკლესისათვის. გადიდებისას {0} გადმოსაწერია {1} ნაწილი ({2} MB) რუკის გადმოწერა - აირჩიეთ მაქსიმალური გადიდება ჩასატვირთად + მაქსიმალური გადიდება ჩასატვირთად რუკის გადმოწერა შეუძლებელია გაგრძელებადი რენდერი მიყოლებული რენდერის ჩვენება ცალცალკე სურათების ჩვენების მაგიერ. მონიშნული არეალის რენდერის დროს მოხდა შეცდომა. მდებარეობის პარამეტრები … - რენდერერი წარმატებით ჩაიტვირთა + რენდერერი ჩატვირთულია მოხდა შეცდომა: რენდერერი არ ჩაიტვირთა. ვექტორული მარენდერებელი რენდერის გარეგნობის არჩევა @@ -380,7 +380,7 @@ ტრანსპორტის ძებნა (არ მოიძებნა): ტრანსპორტის პასუხები ({0} -მდე): ტრანსპორტის თავიდან ძებნა - ხმა + ჩაწერილი ხმა ვექტორული რუკები ჩატვირთული არ იქნა GPX ფაილები /tracks დასტაში ნაპოვნი არ იქნა GPX მონაცემების კითხვის შეცდომა. @@ -390,7 +390,7 @@ POI-ს წაშლა კომპასისაკენ მოძრაობის მიმართულებით - არ შეაბრუნო + არ შეაბრუნო (ჩრდილოეთი ყოველთვის ზემოთკენაა) რუკის გასწორება: რუკის ორიენტაცია დეტალები გზის შესახებ @@ -409,7 +409,7 @@ გზა რჩეულები OSM-ის ხოჭოები - POI… + POI შრე… რუკის წყარო… ხედის აღწერა POI-ის ძებნა @@ -441,8 +441,10 @@ ხმოვანი მონაცემები მხარდაჭერილი არაა ხმოვანი მონაცემები დაზიანებულია არჩეული ხმოვანი მონაცემები მიუწვდომელია - SDcard მიუწვდომელია.\nთქვენ არ შეგეძლებათ რუკების დანახვა ან რაიმეს ძებნა. - SDcard იმყოფება მხოლოდ-კითხვის რეჟიმში.\nთქვენ შეგიძლიათ მხოლოდ უკვე ჩატვირთული რუკების გამოყენება. ინტერნეტიდან ჩამოტვირთვა შეუძლებელია. + მეხსერების ბარათი მიუწვდომელია. +\nთქვენ არ შეგეძლებათ რუკების დანახვა ან რაიმეს ძებნა. + მეხსიერების ბარათი იმყოფება მხოლოდ-კითხვის რეჟიმში. +\nთქვენ შეგიძლიათ მხოლოდ უკვე ჩატვირთული რუკების გამოყენება. ინტერნეტიდან ჩამოტვირთვა შეუძლებელია. ფაილის განშლა… შებრუნდით მარჯვნივ და წინ შებრუნდით მკვეთრად მარჯვნივ და წინ @@ -454,8 +456,8 @@ მიმართულება გვიან რაიონების გადმოწერა - სიგნალის ძებნა… - რუკაზე უკანასკნელი მდებარეობის მოძიება + სიგნალის ლოდინი… + მიმდინარე რუკის ცენტრის ახლოს ძებნა ახლო ძიება იგივე როგორც მოწყობილობაზე პორტრეტი @@ -480,11 +482,11 @@ რუკაზე საზოგადოებრივი ტრანსპორტის გაჩერებების ჩვენება. გაჩერებების ჩვენება OsmAnd - სანავიგაციო აპლიკაცია - POI მონაცემები წარმატებით განახლდა (ჩატვირთულ იქნა {0}) + POI მონაცემები განახლდა (ჩაიტვირთა {0}) შეცდომა ლოკალური POI სიის განახლებისას. შეცდომა მონაცემების სერვერიდან კითხვისას. გათიშული POI მონაცემები ამ არეალისათვის ხელმიუწვდომელია - პატარა ზომის გადიდებისათვის POI-ის განახლება არ შეიძლება + გაადიდეთ POI-ების ჩასასწორებლად POI-ის განახლება განვანახლო ლოკალური მონაცემები ინტერნეტიდან? ქალაქი: {0} @@ -500,7 +502,7 @@ მიმდინარეობს ძებნა… მისამართის ძებნა… მისამართის OSM Nominatim -ით ძებნა - სახლის ნომერი, ქუჩა, ქალაქი + ონლაინ ძებნა: სახლის ნომერი, ქუჩა, ქალაქი გათიშული ინტერნეტი მაქს. მიმდინარე გადიდება @@ -510,7 +512,7 @@ ნავიგაციის სერვისი შენახვის დასტა SDCard-ზე მიუწვდომელია! ჩამოვტვირთო {0} - {1} ? - გათიშული მონაცემები {0}-სათვის უკვე არსებობს ({1}). გნებავთ მისი განახლება ({2}) ? + გათიშული მონაცემები {0}-სათვის უკვე არსებობს ({1}). განვაახლო ({2}) \? მისამართი ჩამოტვირთვა დასრულებულია წვდომადი რაიონების სიის ჩამოტვირთვა… @@ -519,7 +521,7 @@ რჩეული წერტილები არ არსებობს შეცვლა ჩვენება მხოლოდ - მიყოლა + მიყოლის დაწყება აირჩიეთ გადაადგილების ტიპი (არჩევანი): გთხოვთ ჯერ აირჩიოთ მიმართულება მიმართულებები @@ -543,12 +545,12 @@ ახალი გზა დათვლილია. დაშორება მიხვედით. კოორდინატები არასწორია! - OsmAnd-ის რუკაზე დაბრუნება + რუკაზე დაბრუნება დახურვა მონაცემების კითხვა… ლოკალური მონაცემების კითხვა… ბოლოს გაშვებული OsmAnd მოკვდა. ჟურნალის ფაილია {0}. გთხოვთ მოგვწეროთ ჟურნალის ფაილი და როგორ მოხდა ეს. - GPX ძიების SD-ზე შენახვა… + GPX ფაილის შენახვა… დამთავრდა ინტერნეტის გამოყენება გზის დასათვლელად. მიმდინარე ნავიგაციის გამოყენება @@ -576,20 +578,20 @@ ძებნა მისამართით აირჩიეთ შენობა აირჩიეთ ქუჩა - აირჩიეთ ქალაქი + აირჩიეთ ქალაქი ან საფოსტო კოდი აირჩიეთ ქვეყანა ხედვის კუთხის ჩვენება რუკის 3D ხედვის ჩართვა. რუკის 3D ხედვა ბოლოს გამოყენებული POI-ის რუკაზე ჩვენება. - POI-ის ჩვენება + POI შრის ჩვენება აირჩიეთ მიმდინარე ან ქეშირებული რუკის ნაწილის წყარო. რუკის წყარო რუკის წყარო ინტერნეტის გამოყენება - მდებარეობის ჩვენება + თქვენი მდებარეობის ჩვენება GPS კოორდინატების რუკაზე ჩვენება - ინტერნეტის გამოყენება ნაკლული რუკის წარწერების ჩამოსატვირთად + ნაკლული რუკის წარწერების გადმოწერა ნავიგაციის აპლიკაცია გასვლა ძებნა @@ -599,23 +601,23 @@ ქალაქის ინკრემენტულად ძებნა. ქალაქის/საფოსტო ინდექსის საპოვნელად შეიყვანეთ პირველი 3 ან მეტი სიმბოლო ქუჩის ინკრემენტული ძებნა შენობის ინკრემენტული ძებნა - აირჩიეთ რაიონი სიიდან + ჯერ აირჩიეთ რაიონი აირჩიეთ მკვეთი ქუჩა უახლოესი დასახლებული პუნქტები რუკის დათვალიერება - მანქანა - ველოსიპედი - ფეხით მოსიარულე + მანქანით + ველოსიპედით + ფეხით ცენტრი ძირი შეიყვანეთ განედი და გრძედი არჩეულ ფორმატში (D - გრადუსი, M - წუთ, S - წამი) განედი გრძედი - DDD.DD + DDD.DDDDD DDD MM.MMM DDD MM SS.S რუკაზე ჩვენება - აირჩიეთ მისამართი + მისამართი რაიონი ქალაქი ქუჩა @@ -631,11 +633,11 @@ არა რჩეულის სახელი რჩეული - რჩეული წერტილი \'\'{0}\'\' წარმატებით დაემატა. + რჩეული წერტილი \'\'{0}\'\' დაემატა. რჩეულის ჩასწორება რჩეულის წაშლა გნებავთ რჩეული წერტილის \'%s\' წაშლა? - რჩეული წერტილი {0} წარმატებით წაიშალა. + რჩეული წერტილი {0} წაშლილია. შეტყობინება ავტორი კომენტარი დამატებულია @@ -646,12 +648,12 @@ წავშალო {0} (კომენტარი)? POI-ს წაშლა წაშლა - POI წარმატებით წაიშალა + წაშლილია ჩამატება შეცვლა - მოქმედება {0} წარმატებით დასრულდა. - გაუთვალისწინებელი შეცდომა {0}-ის შესრულების დროს. - {0}-ს შესრულების დროს მოხდა შეცდომა I/O. + მოქმედება {0} დასრულებულია. + შეცდომა {0}-ის შესრულების დროს. + {0}-ის I/O შეცდომა. უჯრედის მონაცემები ჩატვირთული არაა გახსნა კომენტარი @@ -662,8 +664,8 @@ ფილტრი ჩაწერა როგორც წავშალო მონიშნული ფილტრი? - წაიშალა ფილტრი %1$s - შეიქმნა ფილტრი %1$s + წაიშალა ფილტრი \'%1$s\' + შეიქმნა ფილტრი \'%1$s\' გზის მონაცემები ფორმატი POI - ინტერესის წერტილი @@ -672,7 +674,7 @@ ტრანსპორტის ძებნა რჩეულებში ძებნა ყველა მიწისზედა რუკის ობიექტის გამჭირვალედ ჩვენება. - პოლიგონების გარეშე + პოლიგონები რენდერის რეჟიმი რუკის გაუმჯობესება მომხმარებლის პროფილის მიხედვით აირჩიეთ მინიმალური გადიდების ზომა რუკის საჩვენებლად. შეიძლება საჭირო გახდეს SRTM ფაილი: @@ -689,7 +691,7 @@ საკუთარი მანქანის გაჩერებიდან გამოყვანა გაფრთხილება - შეტყობინება თქვენი მანქანის გამოყვენის შესახებ კალენდარში უკვე არსებობს. ის დარჩება ხელით წაშლამდე დარჩება კალენდარში. + შეტყობინება თქვენი მანქანის გამოყვანის შესახებ კალენდარში უკვე არსებობს. ის კალენდარში ხელით წაშლამდე დარჩება. პარკინგის დროის ლიმიტის დაყენება გნებავთ დაყენებული მანქანის მდებარეობის წაშლა? პარკინგის მაჩვენებლის წაშლა @@ -723,9 +725,9 @@ კომპასის გამოყენება გზის გადათვლა სასურველი წერტილის პოვნის შემთხვევაში წინა გზა არ დასრულებულა. გნებავთ მისი გაყოლა? (%1$s წმ) - სიჩქარის კამერების ჩვენება - სიჩქარის ლიმიტების ჩვენება - ფასიანი გზებისათვის გვერდის ავლა + სიჩქარის კამერები + სიჩქარის ლიმიტები + ფასიანი გზებისათვის თავის არიდება ქუჩის სახელი გამართვა სად ვარ @@ -741,11 +743,11 @@ შემდეგი შესახვევი (პატარა) მეორე შემდეგი შესახვევი პატარა რუკა - ეკრანის ჩაკეტვის ჩართვა/გამორთვა + ეკრანის ჩაკეტვა ეკრანი ჩაკეტილია დააყენეთ გაღვიძების ინტერვალი: - დააჭირეთ ბოქლომს ეკრანის გასახსნელად - ეკრანზე ჩაკეტვის მოხსნა + ეკრანის გასაღებად შეეხეთ ბოქლომს + ჩაკეტვის მოხსნა პროგრამის ფონურ რეჟიმში გაშვება შეჩერდით \nგაშვებულია ფონურ რეჟიმში @@ -753,8 +755,8 @@ ინფორმაცია დღეღამის შესახებ რენდერის ატრიბუტები ეკრანის გამართვა - ბილიკების ჩვენება - მოუკირწყლავი გზებისათვის თავის არიდება + ბილიკები + მოუკირწყლავი გზებისათვისთვის თავის არიდება ბორნისათვის თავის არიდება თავის არიდება… გაფრთხილებების ჩვენება… @@ -762,7 +764,7 @@ მანათობელი გზები სახაზავის ჩვენება ხედვის მიმართულება - გამჭირვალე ხედი + გამჭირვალე ღილაკები გაგრძელებადი ელ-ფოსტა მზის ამოსვლა: %1$s \nჩასვლა: %2$s @@ -788,19 +790,19 @@ გთხოვთ ჩართოთ GPS ჟურნალირების სერვისები გზა ვერ მოიძებნა - გზის სანიშნის მოცილება - გზის სანიშნი %1$s - გზის სანიშნი %1$s - გზის ბოლო ნიშნის დამატება - გზის პირველი წერტილის დამატება - გზის ბოლო წერტილის დამატება - გზის პირველი წერტილის დამატება + დანიშნულების წერტილის წაშლა + დანიშნულების წერტილი %1$s + შუალედური დანიშნულების წერტილი %1$s + დანიშნულების წერტილის ბოლო ნიშნის დამატება + დანიშნულების ადგილის პირველი წერტილის დამატება + დანიშნულების ადგილის ბოლო წერტილის დამატება + დანიშნულების ადგილის პირველი წერტილის დამატება დანიშნულების წერტილის შეცვლა თქვენ უკვე მიუთითეთ დანიშნულების წერტილი: გზის ნიშნულები - გზის ნიშნანი %1$s მეტისმეტად შორსაა უახლოესი გზიდან. + დანიშნულების წერტილი %1$s მეტისმეტად შორსაა უახლოესი გზიდან. თქვენ მიაღწიეთ თქვენს დანიშნულების წერტილს - გზის წერტილად დამატება + შუალედურ დანიშნულების წერტილად დამატება საბოლოო წერტილი მეტისმეტად სორსაა უახლოესი გზიდან. ჭდის დამატება ზუსტი რეჟიმი… @@ -815,7 +817,7 @@ ნავიგაციისას პიზიციის გზებისათვის მიბმა. გზისთვის მიბმა OsmAnd არის ნავიგაციის აპლიკაცია ღია წყაროთი გათიშული და ონლაინ რუკების მხარდაჭერით - გზის წერტილი + შუალედური დანიშნულების პუნქტი აუდიო/ვიდეო მონაცემები დარწმუნებული ბრძანდებით რომ გნებავთ ნავიგაციის შეწყვეტა? დარწმუნებული ბრძანდებით რომ გნებავთ გაასუფთაოთ თქვენი დანიშნულების წერტილი? @@ -1251,7 +1253,7 @@ აირჩიეთ საიმპორტო პუნქტები. Mapillary-ში დამატება OpenPlaceReviews-ში დამატება - + თვალთვალი გზის ჩაწერა ჯგუფის ჩასწორება @@ -1798,4 +1800,554 @@ პირადი შემობრუნება თეთრი + %1$d გადმოწერა + ბილიკების ჩვენება + ბილიკების დამალვა + ბილიკების ჩვენება/დამალვა + გამოსვლა + %1$s-ით + ნაჩვენები ბილიკები + მეტის ჩვენება + წინა გზა + სამსახურის დამატება + სახლის დამატება + შუალედური დანიშნულების წერტილები + ხმოვანი დახმარება + ნავიგაციის სიმულაცია + საზოგადოებრივი ტრანსპორტი + გზის დათვლა… + ფეხით + შუალედური წერტილი + შუალედურის დამატება + დანიშნულების პუნქტის დაყენება + ღამის რეჟიმი + დღის რეჟიმი + გავუშვა OsmAnd\? + OsmAnd + მოქმედების ჩასწორება + მოგზაურთა გიდები + მოგზაურთა გიდები + გზისთვის თვალყურის დევნება + გზისთვის თვალყურის დევნება + GPX ფაილი + შუალედური დრო + ყველას გადმოწერა + სურათების ჩვენება + აპლიკაციის რესტარტი + OsmAnd-ის ჯგუფი + პოპულარული ადგილები + ფასიანი დამატება + ფასიანი აპლიკაცია + ეს რეგიონი + ჩასწორების დაწყება + ფაილის გადმოწერა + განახლება ხელმისაწვდომია + გათიშული Wikipedia + ულიმიტო გადმოწერები + ერთხელ გადახდა + აირჩიეთ გეგმა + სურათების გადმოწერა + მოგზაურობის წიგნაკი + სურათების გადმოწერა + სურათების კეში + ტურისტული გიდები + სტატია წაშლილია + დამახსოვრებული სტატიები + ჯგუფი წაშლილია + ახლოდან დაწყება + ბოლოდან დაწყება + შეიყვანეთ განედი + შეიყვანეთ გრძედი + რუკა იმპორტირებულია + წინ გვირაბია + OSM სანიშნები + ყველა მონაცემი + თვითგამოცხადების პერიოდი + ჭკვიანი თვითგამოცხადება + აირჩიეთ პროფილი, რომელიც გამოყენებული იქნება აპლიკაციის სტარტისას. + საფეხმავლო ბილიკებისთვის პრიორიტეტის მიცემა + საფეხმავლო ბილიკებისთვის პრიორიტეტის მიცემა + არხების და ნაკადულების ჩვენება + არხების და რუების ჩვენება + საზღვაო მილი + POI-ის ატვირთვა + მოქმედების წაშლა + მოქმედების შეცვლა + მოქმედების შექმნა + მანამდე სანახავი + შემდეგ სანახავი + მარშრუტის წერტილები + საცობი იწოვება + არა-საავტომობილო გზები + გაყოფის ინტერვალი + ბორცვების შემცველი შრე + მარკერების ისტორია + GPX ფაილის OpenStreetMap-ში გაგზავნა + შეიყვანეთ მძიმით დაცილებული ჭდეები. + OSM სანიშნის დახურვა + სურათის ატვირთვა შეუძლებელია. გთხოვთ, სცადოთ მოგვიანებით + გთხოვთ მონიშნოთ ფაილში ჩასაწერი მონაცემები. + ადგილი საკმარისი არაა + ყველა წერტილის რევერსი + GPS ფაილი ცარიელია + უკანა გზა + მთლიანი გზა + შემდეგი ნაწილი + ნავიგაციის პროფილი + სასრიალო + უკრაინული + ფაილის ტიპის არჩევა + OSC ფაილი + უსახელო მდებარეობა + POI ჭდეები + სახელის გარეშე + იღება + იღება + შემოწირულობები სულ + ტიპით + თარიღით + ჩაინიშნეთ! + ჯგუფების იმპორტი + რჩეულების კატეგორია + შემდეგი ველი + სანიშნის სახელის გადარქმევა + გავლილად მონიშვნა + მთელს ეკრანზე + ფაილის იმპორტი + არასწორი მონაცემები + არასწორი ფორმატი + წინ და უკან + გზა დათვლილია + რუკის ჩვენება + მუქი ნარინჯისფერი + შენიშვნის ჩასწორება + კოორდინატების ფორმატი + კოორდინატების შეყვანა + გზის გეგმა + ჩემი მდებარეობა + პოზიციის გამოყენება + გავლილის დამალვა + გავლილის ჩვენება + დალაგება: + წელს + გააქტიურება + დალაგება + დაშორების მაჩვენებელი + წერტილის გადაადგილება + რჩეულის დამატება + ხაზის დამატება + ხაზის ჩასწორება + გზის წერტილი + GPX ფაილის სახელი: + მანძილის გაზომვა + ნავიგაციის დაწყება/დასრულება + ნავიგაციის პაუზა/გაგრძელება + გამჭირვალე ვარდისფერი + რადიუსის მზომავი + შეიყვანეთ მომხმარებელი + არასწორი მომხმარებლის სახელი + Mapillary-ის გახსნა + ონლაინ სურათები + %1$s-ში + საზღვაო რუკები + შეძენების აღდგენა + მარჯვენასაჭიანი მოძრაობა + პარკინგის პარამეტრები + არასწორი OLC +\n + რუკის წყაროები + შრის დამატება + რუკის შრეები + რუკის სტილი + POI-ის სია + რუკის გამართვა + %1$s-ის დამალვა + %1$s-ის ჩვენება + POI-ს ჩვენება/დამალვა + რჩეულების დამალვა + რჩეულების ჩვენება + რჩეულების ჩვენება/დამალვა + მოქმედების წაშლა + მოქმედების დამატება + რჩეულის დამატება + მოქმედების ჩასწორება + მოქმედების დამატება + ხმის გაჩუმება + გაჩუმების გამორთვა + ხმის ჩართვა/გამორთვა + POI-ს დამატება + ეკრანი %d + მოქმედება %d + სწრაფი მოქმედება + წყლის დამალვა + ჩინური (ტრადიციული) + ჩინური (გამარტივებული) + ბელარუსული (ლათინური) + სერბული (ლათინური) + POI-ს ატვირთვა + გზის ანგარიში + მეტის დამატება… + მონაცემები არაა + საშობაო POI + მუქი ყავისფერი + ღია ყავისფერი + სახელების ტრანსლიტერაცია + კატეგორიების ჩასწორება + არჩეული კატეგორიები + ახალი ფილტრი + ფილტრის წაშლა + ფილტრის შენახვა + ფილტრები + მიწისქვეშა ობიექტები + წაშლილია + წაშლილია + გზა დაბლოკილია + POI ხატულები + ყველაფრის გაუქმება + საწყისი წერტილი + განახლების ზომა + არჩეული არაა + მეხსიერების ზომა + კლიპის სიგრძე + განახლების დრო + ხელმისაწვდომი რუკები + განახლებები მიმდინარე რეჟიმში + ახლა განახლება + ცოცხალ რეჟიმში განახლება + კიბეების თავიდან აცილება + არა კიბეები + მენიუს გამოყენება + ხელსაწყოების პანელის გამოყენება + გზის ტიპი + GPX-ს სიგანე + GPX ფერი + შენიშვნა დახურულია + შენიშვნა შექმნილია + შენიშვნის დახურვა + შენიშვნის თავიდან გახსნა + კომენტარის დამატება + შენიშვნის შექმნა + OSM შენიშვნა + ალბანური (ტოსკა) + ქვედა გერმანული + მეტის წაკითხვა + შეთავაზებული ობიექტები + რუკის აღწერა + ტექნიკური სტატიები + რუკის თვალიერება + პირველი გაშვება + ახალი ვერსია + QR კოდი + რუკის ჩვენება + რუკა გადმოწერილია + მდებარეობის გაზიარება + %.1f მბ + კატეგორიის სახელი + მსოფლიო რუკები + რეგიონალური რუკები + სრული ვერსია + კატეგორია + ახლის დამატება + მოგზაურობის ჩაწერა + ინფორმაცია რჩეულის შესახებ + რჩეულის დამატება + ახლახანს მონახულებული ადგილები + სამუშაო დღეები + POI-ს ტიპი + საკონტაქტო ინფორმაცია + იკეტება + იღება + შენობის ნომერი + ხელსაწყოთა პანელის გამართვა + შეცდომა: {0} + კიდევ სცადეთ + ატვირთულია {0}/{1} + ცვლილების წაშლა + დეტალების ჩვენება + გასვლა + სიმაღლის ლიმიტი + სახლების ნომრები + რუკების საცავი + შიდა მეხსიერება + ხელით მითითებული + მრავალმომხმარებლიანი საცავი + გარე მეხსიერება + აღწერის ჩვენება. + არ გამოიყენო + ინფორმაცია A-GPS-ის შესახებ + რუკების გადმოწერა + მიმდინარე გზა + გნებავთ ისტორიის გასუფთავება\? + თქვენს მიერ ჩასწორებული + OSM-ის ჩასწორებები + ძირითადი პარამეტრები + ნავიგაციის პარამეტრები + მთლიანი სია + ჩემი ადგილები + ჩემი მდებარეობა + ყველას ჩვენება + მოქმედება {0} + გაუთვალისწინებელი შეცდომა + შენიშვნის მოხსნა + გზის წერტილები + გზის ნაწილები + სახელის გადარქმევა შეუძლებელია. + შენიშვნის გაზიარება + ონლაინ რუკა + A/V შენიშვნები + გაფრთხილება საცობის შესახებ + კალათა + ჩასადები + ნივთები + დღე გავიდა + მხოლოდ გზები + სასრიალო ქანობები + მოწყობილობის მეხსიერება + თავისუფალია %1$s + თხილამურების მოვლა + პარკინგის ადგილი + ხმოვანი მოთხოვნები + სტატუსის ველი + ჟურნალიზაციის ინტერვალი + მოგზაურობის ჩაწერა + მიწისქვეშა გზები + ადგილობრივი სახელები + გზის წერტილები: %1$s + ქვეგზები: %1$s + წყლის დონე + მისამართები + საწვავის დასაზოგი გზა + უჯრა + უგზო ადგილი + მონიშნული გზა + გზა + გზები + გზები + გაკეთება + შიგნით + სია + პარამეტრები + თავიდან + თავიდან ჩატვირთვა + Mapillary + დამატება + დაპაუზებული + ხილვადი + გაფრთხილებები + ღილაკები + მართვის პანელი + დამატებები + გზები + დამატებები + გადაცილებულია + + გადმოწერა + ქვევერსია + პროქსი + ადგილობრივი + შიგნით + იგივეს დატოვება + უკვე მაქვს ანგარიში + ძებნის ისტორია + უფლების მინიჭება + თავისუფალი ადგილი + რუკების ძებნა… + დავიწყოთ + მიწისზედა ობიექტები + კოორდინატების ძებნა + სორბული (ზედა) + ღიაა 24/7 + ღიაა + მეხსიერების ბარათი + კოორდინატების ფორმატი + ესპანური (ამერიკული) + მიმდინარე ბილიკი + უნგრული (ზრდილობიანი) + ტაქტილური მიმართულებები + მაგნიტური დამჭერი + დაყენებული არაა + ხეების სია + გაშლილი სია + ცარიელი სია + სიის ჩამოშლა + შეიყვანეთ აღწერა. + შეიყვანეთ კატეგორია + შეიყვანეთ სახელი + აირჩიეთ კატეგორია + ნაგულისხმევი ფერი + რუკის რეჟიმი + სრული ანგარიში + გადატანა ↓ + ნავიგაციის დასრულება + გზის თავიდან აცილება + ზიარი მეხსიერება + ზედა პანელი + გზის გადათვლა + გამოწერის პარამეტრები + თვიური გადასახადი + ხარდაჭერილი რეგიონი + საზოგადო სახელი + სხვა სანიშნები + ანონიმურად ატვირთვა + რუკის სანიშნი + რუკის სანიშნები + აქტიური სანიშნები + მიმდევრობის შებრუნება + პოლიგონების ჩვენება + პარკინგის პოვნა + ცვლილებების შენახვა + ელ-ფოსტა + მეტის წაკითხვა + OAuth-ით შესვლა + ლოკალური რუკები + OpenStreetMap-ზე შესვლა + გთხოვთ, შეხვიდეთ OpenStreetMap.org-ზე + გამოიყენეთ მომხმარებელ და პაროლი + გამოწერის მართვა + OpenPlaceReviews.org-ზე +\nრეგისტრაცია + რუკები, როლებიც შეიძლება დაგჭირდეთ + სტატია ნაპოვნი არაა + ვიკიპედიის მონაცემების გადმოწერა + ულიმიტო წვდომა + რუკების საათობრივი განახლება + რუკების თვიური განახლება + %1$s - ის ყიდვა + მხოლოდ Wifi-ზე + მხოლოდ Wifi-ზე + OSM-ის მომხმარებელი და პაროლი + ხაზად შენახვა + გზის წერტილი 1 + %2$d %1$d-დან + ბოლოს ჩასწორებული + აირჩიეთ სიგანე + მიმართულების ისრები + მოტოციკლი Enduro + მოტორიანი სკუტერი + Mapillary-ის ჩვენება/დამალვა + Mapillary-ის დამალვა + Mapillary-ის ჩვენება + წავშალოთ ყველა\? + ეკრანის კონტროლი + ნავიგაციის ინსტრუქციები + ჩართვის ღილაკი + SQLiteDB ფაილი + ვადა + საცავის ფორმატი + პროფილის დამატება + პარკინგის ადგილები + სწრაფი მოქმედება + რადიუსის მზომი + მანძილის გაზომვა + რუკის სანიშნები + OsmAnd-ის შესყიდვები + ნავიგაციის პროფილები + დამატებითი რუკები + %1$s, %2$s + ყველა ენა + რენდერის სტილი + კუთხე: %s° + მინიმალური გადაადგილება + მინიმალური სიზუსტე + მინიმალური სიჩქარე + Web მისამართი + გზის გადათვლა + ჟურნალის სიზუსტე + პროფილის იმპორტი + %1$s: %2$s + %1$s-%2$s + არჩეული პროფილი + პროფილის გარეგნობა + პროფილის ჩასწორება + აირჩიეთ ფერი + მთავარი პროფილი + %s-ის გადმოწერა + სეგმენტების შეერთება + გამორთვა + დამატებული პროფილები + აირჩიეთ ხატულა + სამარშრუტო ავტობუსი + ნავიგაციის ტიპი + პროფილის სახელი + არა, მადლობა + ნანახი ეკრანები + გადმოწერილი რუკები + კორდინატების ღილაკი + GPS-ის ძებნა + ტრანსპორტის ტიპი + არ გადააადგილო + რუკების გადაადგილება + ჟურნალის გაგზავნა + ბორნების არიდება + ბორნების გარეშე + მიწისქვეშა გადასასვლელების გარეშე + მატარებლის არიდება + მატარებლის გარეშე + ავტობუსების გარეშე + ტრამვაის არიდება + ტრამვაის გარეშე + კუთის საზომი ერთეული + %s რეჟიმი + საწყისი წერტილი + პროფილის იმპორტი + პროფილის ექსპორტი + ბორცვების დამალვა/ჩვენება + ბორცვების დამალვა + ბორცვების ჩვენება + %1$s კბ + %1$s მბ + %1$s გბ + %1$s ტბ + სიგანის ლიმიტი + თხილამურის სიძნელე + თხილამურის ტიპი + ნაგულისხმევად + მანქანის პარამეტრები + OsmAnd-ის გამართვა + პროფილის გადართვა + პროფილის გამართვა + ნავიგაციის გამართვა + დაყენებული დამატებები + რუკის ხედი + რუკის გარეგნობა + შეტყობინება ჩართვისას + ცვლილების გაუქმება + პარამეტრის შეცვლა + UTM სტანდარტი + გამოწერის გაუქმება + მაშინ %1$s + 3 თვე + სალაშქრო მანქანა (RV) + ღია გზა + ზედაპირის სიმყარე + უმეტესად რბილი + უმეტესად მყარი + მყარი (მოუკირწყლავი) + მყარი (დაგებული) + მოყინული გზა + ზამთრის გზა + გზის ტიპი + გზის ტიპები + მეხუთე დონე + მეოთხე დონე + მესამე დონე + მეორე დონე + პირველი დონე + პირადი გადამყვანი + ახალი პროფილი + ნაგულისხმევი სიჩქარე + მაქს. სიჩქარე + მინ. სიჩქარე + შეგროვებული მონაცემები + სასრიალო ტურიზმი + აპლიკაციის პროფილები + BRouter (გათიშული) + სწორი ხაზი + პლანერი + პროფილის წაშლა + ცვლილებების შენახვა + სახელი უკვე არსებობს + ბაზური პროფილი + ტიპი: %s + რეჟიმი: %s \ No newline at end of file diff --git a/OsmAnd/res/values-nl/strings.xml b/OsmAnd/res/values-nl/strings.xml index 88fcee0486..b9061fecbf 100644 --- a/OsmAnd/res/values-nl/strings.xml +++ b/OsmAnd/res/values-nl/strings.xml @@ -3745,8 +3745,8 @@ Selecteer een track waaraan je een nieuw segment wil toevoegen. Selecteer een trackbestand om te openen. Weet u zeker dat u alle wijzigingen in de geplande route wilt annuleren door deze te sluiten\? - Opsmukken voor - Opsmukken na + "Alles wegknippen voor dit punt" + Alles wegknippen na dit punt Wijzig het routetype voor Wijzig het routetype na • Bijgewerkt Plan een route functie: maakt het mogelijk om verschillende navigatietypes per segment te gebruiken en tracks op te nemen @@ -3925,4 +3925,19 @@ Foto\'s zijn afkomstig van het open data-project OpenPlaceReviews.org. Om uw foto\'s te uploaden, moet u zich aanmelden op deze website. Maak een nieuw account aan Ik heb al een account + %1$s * %2$s + Licht vliegtuig + Segmenten samenvoegen + Hiervoor splitsen + Hierna splitsen + Nieuw segment toevoegen + OsmAnd-profiel + Gebruikersprofiel + Volgorde van alle punten omdraaien + Laatst gebruikt + Voorkeur voor ongebaande wandelwegen + Voorkeur voor Hike-routes + Beken en sloten gebruiken + Beken en sloten gebruiken + Waterwegen die periodiek water voeren gebruiken \ No newline at end of file diff --git a/OsmAnd/res/values-pt-rBR/strings.xml b/OsmAnd/res/values-pt-rBR/strings.xml index e39bfa1ccb..8516bae334 100644 --- a/OsmAnd/res/values-pt-rBR/strings.xml +++ b/OsmAnd/res/values-pt-rBR/strings.xml @@ -4022,4 +4022,20 @@ Permitir riachos e drenos Permitir vias de água intermitentes Permitir vias de água intermitentes + Horários de avisos de voz + Adicionar mecanismo de roteamento online + Editar mecanismo de roteamento online + Subtipo + Veículo + Chave de API + URL do servidor + Digite o parâmetro + Mantenha-o vazio se não + O URL com todos os parâmetros terá a seguinte aparência: + Cálculo da rota de teste + Dirigindo + + Bicicleta + Carro + Erro, verifique novamente os parâmetros \ No newline at end of file diff --git a/OsmAnd/res/values-sc/strings.xml b/OsmAnd/res/values-sc/strings.xml index 764c40e408..473e0bae95 100644 --- a/OsmAnd/res/values-sc/strings.xml +++ b/OsmAnd/res/values-sc/strings.xml @@ -4024,4 +4024,5 @@ Permite flussos e iscàrrigos Permite caminos de abba intermitentes Permite caminos de abba intermitentes + Nùmeru de annùntzios vocales \ No newline at end of file diff --git a/OsmAnd/res/values-sk/strings.xml b/OsmAnd/res/values-sk/strings.xml index ca4436bcc3..27d1976a65 100644 --- a/OsmAnd/res/values-sk/strings.xml +++ b/OsmAnd/res/values-sk/strings.xml @@ -4027,4 +4027,5 @@ Povoliť potoky a odtokové kanály Povoliť dočasné vodné toky Povoliť dočasné vodné toky + Časy hlasových pokynov \ No newline at end of file diff --git a/OsmAnd/res/values-sv/strings.xml b/OsmAnd/res/values-sv/strings.xml index 4a7805d1bc..692e68f62c 100644 --- a/OsmAnd/res/values-sv/strings.xml +++ b/OsmAnd/res/values-sv/strings.xml @@ -3098,4 +3098,7 @@ Vänligen tillhandahåll fullständig kod Betalningen för prenumerationen är i enlighet med vad som valts. Du kan avbryta den i AppGallery när som hellst. Undvik resor till fots Undvik resor till + Ladda ner Wikipedia kartor + Få information om sevärdheter från Wikipedia, en inbunden samling av offline artiklar om ställen och destinationer. + \ No newline at end of file diff --git a/OsmAnd/res/values-tr/phrases.xml b/OsmAnd/res/values-tr/phrases.xml index 6fef6292b4..cf6117a083 100644 --- a/OsmAnd/res/values-tr/phrases.xml +++ b/OsmAnd/res/values-tr/phrases.xml @@ -944,7 +944,7 @@ Yerel yürüyüş güzergahı Yürüyüş yolu başvurusu Çalışma saatleri - Koleksiyon kez + Toplama zamanları Açıklama Telefon İnternet sitesi @@ -3137,4 +3137,12 @@ Göçmen olmayan vizeleri Askeri denetim noktası Yürüyüş denetim noktası + Dolap türü: sokak aydınlatması + Dolap türü: su yönetimi + Dolap türü: atık + Dolap türü: posta hizmeti + Dolap türü: gaz + Dolap türü: kablo tv + Dolap türü: telefon + Dolap türü: elektrik \ No newline at end of file diff --git a/OsmAnd/res/values-tr/strings.xml b/OsmAnd/res/values-tr/strings.xml index 76669e85df..fccc6158e3 100644 --- a/OsmAnd/res/values-tr/strings.xml +++ b/OsmAnd/res/values-tr/strings.xml @@ -1701,7 +1701,7 @@ Etiketi Kaldır Gecelik derlemeleri indir. kurar - Sokak Aydınlatma + Sokak aydınlatma Vekil sunucu Bir proxy sunucusu belirtin. Gizlilik @@ -3982,4 +3982,20 @@ Dere ve kanalizasyonlara izin ver Aralıklı su yollarına izin verin Aralıklı su yollarına izin ver + Sesli uyarı zamanları + Çevrim içi yönlendirme motoru ekle + Çevrim içi yönlendirme motorunu düzenle + Alt tür + Araç + API anahtarı + Sunucu URL\'si + Parametre gir + Değilse boş tut + Tüm parametreleri ile URL şu şekilde görünecektir: + Güzergah hesaplamayı test et + Araba sürme + Yürüme + Bisiklet + Araba + Hata, parametreleri tekrar gözden geçirin \ No newline at end of file diff --git a/OsmAnd/res/values-uk/strings.xml b/OsmAnd/res/values-uk/strings.xml index 5fecd504a6..4e70eb1284 100644 --- a/OsmAnd/res/values-uk/strings.xml +++ b/OsmAnd/res/values-uk/strings.xml @@ -4023,4 +4023,20 @@ Надавати перевагу пішохідним маршрутам Дозволити потоки та стічні канали Дозволити потоки та стічні канали + Голосові підказки + Додати мережний рушій маршрутизації + Змінити мережний рушій маршрутизації + Підтип + Транспортний засіб + Ключ API + URL-адреса сервера + Введіть параметр + Залиште порожнім, якщо ні + URL-адреса з усіма параметрами виглядатиме так: + Тестове обчислення маршруту + Водіння + Пішки + Велосипед + Автомобіль + Помилка, повторно перевірте параметри \ No newline at end of file diff --git a/OsmAnd/res/values-zh-rTW/phrases.xml b/OsmAnd/res/values-zh-rTW/phrases.xml index 8778b0b0b5..d7224cb178 100644 --- a/OsmAnd/res/values-zh-rTW/phrases.xml +++ b/OsmAnd/res/values-zh-rTW/phrases.xml @@ -3898,4 +3898,7 @@ 公共洗衣區 垃圾站 游泳區 + 蝙蝠隧道 + 蝙蝠橋 + 野生動物穿越 \ No newline at end of file diff --git a/OsmAnd/res/values-zh-rTW/strings.xml b/OsmAnd/res/values-zh-rTW/strings.xml index 17c657df59..115f04c6d7 100644 --- a/OsmAnd/res/values-zh-rTW/strings.xml +++ b/OsmAnd/res/values-zh-rTW/strings.xml @@ -4022,4 +4022,5 @@ 允許溪流與水溝 允許間歇水路 允許間歇水路 + 語音提示時間 \ No newline at end of file diff --git a/OsmAnd/res/values/strings.xml b/OsmAnd/res/values/strings.xml index 71f91520b5..4991659469 100644 --- a/OsmAnd/res/values/strings.xml +++ b/OsmAnd/res/values/strings.xml @@ -12,6 +12,22 @@ --> + Copy address + Error, recheck parameters + Car + Bike + Foot + Driving + Test route calculation + URL with all parameters will look like this: + Keep it empty if not + Enter param + Server URL + API key + Vehicle + Subtype + Edit online routing engine + Add online routing engine Allow intermittent water ways Allow intermittent water ways Allow streams and drains @@ -4012,5 +4028,4 @@ \'Freeride\' and \'Off-piste\' are unofficial routes and passages. Typically ungroomed, unmaintained and not checked in the evening. Enter at your own risk. Voice prompts times - diff --git a/OsmAnd/src/com/github/ksoichiro/android/observablescrollview/ObservableWebView.java b/OsmAnd/src/com/github/ksoichiro/android/observablescrollview/ObservableWebView.java index 3e023f74a5..30ee12b52b 100644 --- a/OsmAnd/src/com/github/ksoichiro/android/observablescrollview/ObservableWebView.java +++ b/OsmAnd/src/com/github/ksoichiro/android/observablescrollview/ObservableWebView.java @@ -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; diff --git a/OsmAnd/src/net/osmand/plus/AppInitializer.java b/OsmAnd/src/net/osmand/plus/AppInitializer.java index eda57e3c28..424855f2a0 100644 --- a/OsmAnd/src/net/osmand/plus/AppInitializer.java +++ b/OsmAnd/src/net/osmand/plus/AppInitializer.java @@ -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(); } diff --git a/OsmAnd/src/net/osmand/plus/OsmandApplication.java b/OsmAnd/src/net/osmand/plus/OsmandApplication.java index db7c0d6433..8deb9c81a8 100644 --- a/OsmAnd/src/net/osmand/plus/OsmandApplication.java +++ b/OsmAnd/src/net/osmand/plus/OsmandApplication.java @@ -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 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; } diff --git a/OsmAnd/src/net/osmand/plus/activities/PrintDialogActivity.java b/OsmAnd/src/net/osmand/plus/activities/PrintDialogActivity.java index 6c7e4d6579..0963b4d782 100644 --- a/OsmAnd/src/net/osmand/plus/activities/PrintDialogActivity.java +++ b/OsmAnd/src/net/osmand/plus/activities/PrintDialogActivity.java @@ -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; diff --git a/OsmAnd/src/net/osmand/plus/download/DownloadIndexesThread.java b/OsmAnd/src/net/osmand/plus/download/DownloadIndexesThread.java index 3355d67a32..9d47f57762 100644 --- a/OsmAnd/src/net/osmand/plus/download/DownloadIndexesThread.java +++ b/OsmAnd/src/net/osmand/plus/download/DownloadIndexesThread.java @@ -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 wns = manager.indexAdditionalMaps(this); if (wns != null) { diff --git a/OsmAnd/src/net/osmand/plus/helpers/AndroidUiHelper.java b/OsmAnd/src/net/osmand/plus/helpers/AndroidUiHelper.java index 45f5fe3ff4..9a038b787f 100644 --- a/OsmAnd/src/net/osmand/plus/helpers/AndroidUiHelper.java +++ b/OsmAnd/src/net/osmand/plus/helpers/AndroidUiHelper.java @@ -102,6 +102,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); diff --git a/OsmAnd/src/net/osmand/plus/helpers/IntentHelper.java b/OsmAnd/src/net/osmand/plus/helpers/IntentHelper.java index b8e176529a..e4e6fac9dd 100644 --- a/OsmAnd/src/net/osmand/plus/helpers/IntentHelper.java +++ b/OsmAnd/src/net/osmand/plus/helpers/IntentHelper.java @@ -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) { diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java index 1d419a6b09..d11d27d34c 100644 --- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java +++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java @@ -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 nearestWiki = new ArrayList<>(); + protected List nearestPoi = new ArrayList<>(); private List 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 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 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,35 +1231,48 @@ public class MenuBuilder { } protected boolean processNearestWiki() { - if (showNearestWiki && latLon != null) { - QuadRect rect = MapUtils.calculateLatLonBbox( - latLon.getLatitude(), latLon.getLongitude(), 250); - PoiUIFilter wikiPoiFilter = app.getPoiFilters().getTopWikiPoiFilter(); - - nearestWiki = getAmenities(rect, wikiPoiFilter); - - Collections.sort(nearestWiki, new Comparator() { - - @Override - public int compare(Amenity o1, Amenity o2) { - double d1 = MapUtils.getDistance(latLon, o1.getLocation()); - double d2 = MapUtils.getDistance(latLon, o2.getLocation()); - return Double.compare(d1, d2); - } - }); - Long id = objectId; - List wikiList = new ArrayList<>(); - for (Amenity wiki : nearestWiki) { - if (wiki.getId().equals(id)) { - wikiList.add(wiki); - } + if (showNearestWiki && latLon != null && amenity != null) { + PoiUIFilter filter = app.getPoiFilters().getTopWikiPoiFilter(); + if (filter != null) { + nearestWiki = getSortedAmenities(filter, latLon); + return true; } - nearestWiki.removeAll(wikiList); - return true; } return false; } + 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; + } + + private List getSortedAmenities(PoiUIFilter filter, final LatLon latLon) { + QuadRect rect = MapUtils.calculateLatLonBbox(latLon.getLatitude(), latLon.getLongitude(), 250); + + List nearestAmenities = getAmenities(rect, filter); + nearestAmenities.remove(amenity); + + Collections.sort(nearestAmenities, new Comparator() { + + @Override + public int compare(Amenity o1, Amenity o2) { + double d1 = MapUtils.getDistance(latLon, o1.getLocation()); + double d2 = MapUtils.getDistance(latLon, o2.getLocation()); + return Double.compare(d1, d2); + } + }); + + return nearestAmenities; + } + private List getAmenities(QuadRect rect, PoiUIFilter wikiPoiFilter) { return wikiPoiFilter.searchAmenities(rect.top, rect.left, rect.bottom, rect.right, -1, null); diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/builders/AmenityMenuBuilder.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/builders/AmenityMenuBuilder.java index 12b1b0d497..c0db653e3e 100644 --- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/builders/AmenityMenuBuilder.java +++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/builders/AmenityMenuBuilder.java @@ -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)) { diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/builders/FavouritePointMenuBuilder.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/builders/FavouritePointMenuBuilder.java index 1e6df280a3..03110c8b32 100644 --- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/builders/FavouritePointMenuBuilder.java +++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/builders/FavouritePointMenuBuilder.java @@ -59,9 +59,9 @@ public class FavouritePointMenuBuilder extends MenuBuilder { } @Override - protected void buildNearestWikiRow(View view) { + protected void buildNearestRow(View view, List nearestAmenities, boolean process, int iconId, String text) { if (originObject == null || !(originObject instanceof Amenity)) { - super.buildNearestWikiRow(view); + super.buildNearestRow(view, nearestAmenities, process, iconId, text); } } diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/builders/cards/AbstractCard.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/builders/cards/AbstractCard.java index 82d935b091..73964b03d1 100644 --- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/builders/cards/AbstractCard.java +++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/builders/cards/AbstractCard.java @@ -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) { diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/controllers/AmenityMenuController.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/controllers/AmenityMenuController.java index 11564a1b73..e67b8dc11b 100644 --- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/controllers/AmenityMenuController.java +++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/controllers/AmenityMenuController.java @@ -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) { diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/other/HorizontalSelectionAdapter.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/other/HorizontalSelectionAdapter.java index 2299a62c7a..b26a377266 100644 --- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/other/HorizontalSelectionAdapter.java +++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/other/HorizontalSelectionAdapter.java @@ -67,8 +67,9 @@ public class HorizontalSelectionAdapter extends RecyclerView.Adapter 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); diff --git a/OsmAnd/src/net/osmand/plus/myplaces/GpxDescriptionDialogFragment.java b/OsmAnd/src/net/osmand/plus/myplaces/GpxDescriptionDialogFragment.java index 7cab05bc3a..aa17926449 100644 --- a/OsmAnd/src/net/osmand/plus/myplaces/GpxDescriptionDialogFragment.java +++ b/OsmAnd/src/net/osmand/plus/myplaces/GpxDescriptionDialogFragment.java @@ -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(); diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingCard.java b/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingCard.java new file mode 100644 index 0000000000..1b9e7f3b29 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingCard.java @@ -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 items, + String selectedItemTitle, + final CallbackWithObject 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); + } +} diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingEngine.java b/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingEngine.java new file mode 100644 index 0000000000..10ba169cb1 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingEngine.java @@ -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 params = new HashMap<>(); + + public OnlineRoutingEngine(@NonNull String stringKey, + @NonNull ServerType serverType, + @NonNull String vehicleKey, + Map 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 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(); + } +} diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingEngineFragment.java b/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingEngineFragment.java new file mode 100644 index 0000000000..18b24a8762 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingEngineFragment.java @@ -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 serverItems = new ArrayList<>(); + for (ServerType server : ServerType.values()) { + serverItems.add(new HorizontalSelectionItem(server.getTitle(), server)); + } + serverCard.setSelectionMenu(serverItems, engine.serverType.getTitle(), + new CallbackWithObject() { + @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 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() { + @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 locationItems = new ArrayList<>(); + for (ExampleLocation location : ExampleLocation.values()) { + locationItems.add(new HorizontalSelectionItem(location.getName(), location)); + } + exampleCard.setSelectionMenu(locationItems, selectedLocation.getName(), + new CallbackWithObject() { + @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()); + } + } +} diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingHelper.java b/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingHelper.java new file mode 100644 index 0000000000..8a9e42bd54 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingHelper.java @@ -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 cachedEngines; + private Map cachedEnginesMap; + + public OnlineRoutingHelper(OsmandApplication app) { + this.app = app; + this.settings = app.getSettings(); + loadFromSettings(); + } + + @NonNull + public List 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 readFromSettings() { + List 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 engines) { + try { + if (!json.has("items")) { + return; + } + Gson gson = new Gson(); + Type type = new TypeToken>() { + }.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 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 engines) { + JSONArray jsonArray = new JSONArray(); + Gson gson = new Gson(); + Type type = new TypeToken>() { + }.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; + } + } +} diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/ServerType.java b/OsmAnd/src/net/osmand/plus/onlinerouting/ServerType.java new file mode 100644 index 0000000000..7eb30798d5 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/ServerType.java @@ -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; + } +} diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/VehicleType.java b/OsmAnd/src/net/osmand/plus/onlinerouting/VehicleType.java new file mode 100644 index 0000000000..3e3fea6660 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/VehicleType.java @@ -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; + } +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/osmedit/oauth/OsmOAuthAuthorizationAdapter.java b/OsmAnd/src/net/osmand/plus/osmedit/oauth/OsmOAuthAuthorizationAdapter.java index d1c9f36e9a..abc5d1f407 100644 --- a/OsmAnd/src/net/osmand/plus/osmedit/oauth/OsmOAuthAuthorizationAdapter.java +++ b/OsmAnd/src/net/osmand/plus/osmedit/oauth/OsmOAuthAuthorizationAdapter.java @@ -138,7 +138,11 @@ public class OsmOAuthAuthorizationAdapter { @Override protected void onPostExecute(@NonNull OAuth1RequestToken requestToken) { - loadWebView(rootLayout, nightMode, client.getService().getAuthorizationUrl(requestToken)); + if (requestToken != null) { + loadWebView(rootLayout, nightMode, client.getService().getAuthorizationUrl(requestToken)); + } else { + app.showShortToastMessage(app.getString(R.string.internet_not_available)); + } } } diff --git a/OsmAnd/src/net/osmand/plus/osmedit/oauth/OsmOAuthHelper.java b/OsmAnd/src/net/osmand/plus/osmedit/oauth/OsmOAuthHelper.java index 8db1079b8b..0eed60b924 100644 --- a/OsmAnd/src/net/osmand/plus/osmedit/oauth/OsmOAuthHelper.java +++ b/OsmAnd/src/net/osmand/plus/osmedit/oauth/OsmOAuthHelper.java @@ -45,7 +45,11 @@ public class OsmOAuthHelper { } public void authorize(@NonNull String oauthVerifier) { - authorizationAdapter.authorize(oauthVerifier, this); + if (oauthVerifier != null) { + authorizationAdapter.authorize(oauthVerifier, this); + } else { + updateAdapter(); + } } public void resetAuthorization() { diff --git a/OsmAnd/src/net/osmand/plus/profiles/OnlineRoutingEngineDataObject.java b/OsmAnd/src/net/osmand/plus/profiles/OnlineRoutingEngineDataObject.java new file mode 100644 index 0000000000..46a35ea67e --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/profiles/OnlineRoutingEngineDataObject.java @@ -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); + } +} diff --git a/OsmAnd/src/net/osmand/plus/profiles/ProfileDataUtils.java b/OsmAnd/src/net/osmand/plus/profiles/ProfileDataUtils.java index 4cbc9b1940..f769f69bdb 100644 --- a/OsmAnd/src/net/osmand/plus/profiles/ProfileDataUtils.java +++ b/OsmAnd/src/net/osmand/plus/profiles/ProfileDataUtils.java @@ -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 getOnlineRoutingProfiles(OsmandApplication app) { + List 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> getRoutingProfilesByFileNames(OsmandApplication app) { Map> result = new HashMap<>(); for (final RoutingProfileDataObject profile : getRoutingProfiles(app).values()) { @@ -83,24 +94,24 @@ public class ProfileDataUtils { public static Map getRoutingProfiles(OsmandApplication context) { Map 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)); diff --git a/OsmAnd/src/net/osmand/plus/profiles/SelectProfileBottomSheet.java b/OsmAnd/src/net/osmand/plus/profiles/SelectProfileBottomSheet.java index eaf99b9503..df2bd8b879 100644 --- a/OsmAnd/src/net/osmand/plus/profiles/SelectProfileBottomSheet.java +++ b/OsmAnd/src/net/osmand/plus/profiles/SelectProfileBottomSheet.java @@ -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 (i < profiles.size() - 1) { - RoutingProfileDataObject nextProfile = (RoutingProfileDataObject) profiles.get(i + 1); - if (profile.getFileName() == null) { - showBottomDivider = nextProfile.getFileName() != null; - } else { - showBottomDivider = !profile.getFileName().equals(nextProfile.getFileName()); + 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,7 +271,14 @@ public class SelectProfileBottomSheet extends BasePreferenceBottomSheet { args.putString(PROFILE_KEY_ARG, profile.getStringKey()); Fragment target = getTargetFragment(); if (target instanceof OnSelectProfileCallback) { - ((OnSelectProfileCallback) target).onProfileSelected(args); + 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); } diff --git a/OsmAnd/src/net/osmand/plus/resources/ResourceManager.java b/OsmAnd/src/net/osmand/plus/resources/ResourceManager.java index ffd8bb4e83..db1c3c96d6 100644 --- a/OsmAnd/src/net/osmand/plus/resources/ResourceManager.java +++ b/OsmAnd/src/net/osmand/plus/resources/ResourceManager.java @@ -621,7 +621,11 @@ public class ResourceManager { } } - public List indexingMaps(final IProgress progress) { + public List indexingMaps(IProgress progress) { + return indexingMaps(progress, Collections.emptyList()); + } + + public List indexingMaps(final IProgress progress, List filesToReindex) { long val = System.currentTimeMillis(); ArrayList files = new ArrayList(); 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; } diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/OsmandSettings.java b/OsmAnd/src/net/osmand/plus/settings/backend/OsmandSettings.java index 44f948d651..efaaff576b 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/OsmandSettings.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/OsmandSettings.java @@ -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 ONLINE_ROUTING_ENGINES = new StringPreference(this, "online_routing_engines", null); + public final CommonPreference NAVIGATION_ICON = new EnumStringPreference<>(this, "navigation_icon", NavigationIcon.DEFAULT, NavigationIcon.values()).makeProfile().cache(); { diff --git a/OsmAnd/src/net/osmand/plus/settings/bottomsheets/BasePreferenceBottomSheet.java b/OsmAnd/src/net/osmand/plus/settings/bottomsheets/BasePreferenceBottomSheet.java index 6a71538ff1..67e2bfcfa0 100644 --- a/OsmAnd/src/net/osmand/plus/settings/bottomsheets/BasePreferenceBottomSheet.java +++ b/OsmAnd/src/net/osmand/plus/settings/bottomsheets/BasePreferenceBottomSheet.java @@ -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 diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/GlobalSettingsFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/GlobalSettingsFragment.java index e957eb7748..fa7383228c 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/GlobalSettingsFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/GlobalSettingsFragment.java @@ -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(); diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/MainSettingsFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/MainSettingsFragment.java index f7ae122851..fdd5508352 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/MainSettingsFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/MainSettingsFragment.java @@ -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(); diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/NavigationFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/NavigationFragment.java index 0720dc7714..de8481f222 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/NavigationFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/NavigationFragment.java @@ -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; diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/ProfileAppearanceFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/ProfileAppearanceFragment.java index 6913770773..7a42818745 100644 --- a/OsmAnd/src/net/osmand/plus/settings/fragments/ProfileAppearanceFragment.java +++ b/OsmAnd/src/net/osmand/plus/settings/fragments/ProfileAppearanceFragment.java @@ -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); } } } diff --git a/OsmAnd/src/net/osmand/plus/widgets/WebViewEx.java b/OsmAnd/src/net/osmand/plus/widgets/WebViewEx.java new file mode 100644 index 0000000000..c83e1110cb --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/widgets/WebViewEx.java @@ -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()); + } + } +} diff --git a/OsmAnd/src/net/osmand/plus/wikipedia/WikiArticleHelper.java b/OsmAnd/src/net/osmand/plus/wikipedia/WikiArticleHelper.java index a0ced8eb86..56137a780c 100644 --- a/OsmAnd/src/net/osmand/plus/wikipedia/WikiArticleHelper.java +++ b/OsmAnd/src/net/osmand/plus/wikipedia/WikiArticleHelper.java @@ -289,33 +289,39 @@ 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()); - 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); - firstParagraphEnd = firstParagraphStart == -1 ? -1 : content.indexOf(P_CLOSED, firstParagraphStart); - if (firstParagraphStart != -1 && firstParagraphEnd != -1) { - firstParagraphHtml = content.substring(firstParagraphStart, firstParagraphEnd + P_CLOSED.length()); - } else { - break; + 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); + firstParagraphEnd = firstParagraphStart == -1 ? -1 : content.indexOf(P_CLOSED, firstParagraphStart); + if (firstParagraphStart != -1 && firstParagraphEnd != -1) { + firstParagraphHtml = content.substring(firstParagraphStart, firstParagraphEnd + P_CLOSED.length()); + } else { + break; + } } } + if (Algorithms.isEmpty(firstParagraphHtml)) { + firstParagraphHtml = source; + } + if (Algorithms.isEmpty(firstParagraphHtml)) { + return null; + } + String firstParagraphText = Html.fromHtml(firstParagraphHtml.replaceAll("(<(/)(a|img)>)|(<(a|img).+?>)|()", "")) .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(); } diff --git a/OsmAnd/src/net/osmand/plus/wikipedia/WikipediaDialogFragment.java b/OsmAnd/src/net/osmand/plus/wikipedia/WikipediaDialogFragment.java index 72ea3560cb..17a8500361 100644 --- a/OsmAnd/src/net/osmand/plus/wikipedia/WikipediaDialogFragment.java +++ b/OsmAnd/src/net/osmand/plus/wikipedia/WikipediaDialogFragment.java @@ -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; diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/article/WikivoyageArticleDialogFragment.java b/OsmAnd/src/net/osmand/plus/wikivoyage/article/WikivoyageArticleDialogFragment.java index 9303fb7b84..2d450eaa6b 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/article/WikivoyageArticleDialogFragment.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/article/WikivoyageArticleDialogFragment.java @@ -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, diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelArticle.java b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelArticle.java index d4ee0027e7..9a5aca4fa1 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelArticle.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelArticle.java @@ -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; } diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelDbHelper.java b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelDbHelper.java index 53ac5866a9..38380015c3 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelDbHelper.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelDbHelper.java @@ -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 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; } diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelHelper.java b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelHelper.java index 4d512888db..87b5bc1e95 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelHelper.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelHelper.java @@ -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(); diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelLocalDataHelper.java b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelLocalDataHelper.java index 3cb7cf776e..288b71dd88 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelLocalDataHelper.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelLocalDataHelper.java @@ -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 historyMap = new HashMap<>(); private List savedArticles = new ArrayList<>(); - private Set listeners = new HashSet<>(); + private final Set 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 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 getSavedArticles(File file, String routeId) { + List 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 getAllHistoryMap() { Map 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(); } - 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 readSavedArticles() { List 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; } } diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelObfHelper.java b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelObfHelper.java index 5130b349f7..94a9d73033 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelObfHelper.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelObfHelper.java @@ -278,7 +278,7 @@ public class TravelObfHelper implements TravelHelper { parts = null; } Map> navMap = new HashMap<>(); - Set headers = new LinkedHashSet(); + Set headers = new LinkedHashSet<>(); Map headerObjs = new HashMap<>(); Map> 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 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 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 res = new ArrayList<>(); TravelArticle article = getArticleById(articleId, ""); if (article != null) { - Map articles = cachedArticles.get(articleId); + Map articles = cachedArticles.get(article.generateIdentifier()); if (articles != null) { res.addAll(articles.keySet()); } + } else { + List 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 diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/data/WikivoyageSearchHistoryItem.java b/OsmAnd/src/net/osmand/plus/wikivoyage/data/WikivoyageSearchHistoryItem.java index aee3b183b4..cc41d2582d 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/data/WikivoyageSearchHistoryItem.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/data/WikivoyageSearchHistoryItem.java @@ -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; diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/explore/SavedArticlesRvAdapter.java b/OsmAnd/src/net/osmand/plus/wikivoyage/explore/SavedArticlesRvAdapter.java index 77989b695b..8996e4d375 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/explore/SavedArticlesRvAdapter.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/explore/SavedArticlesRvAdapter.java @@ -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