Merge branch 'master' of ssh://github.com/osmandapp/Osmand into RoutePreparationMenu

This commit is contained in:
Chumva 2018-12-12 11:45:43 +02:00
commit c824c1ba79
46 changed files with 2944 additions and 1426 deletions

View file

@ -124,6 +124,9 @@ public class CollatorStringMatcher implements StringMatcher {
public static boolean cstartsWith(Collator collator, String searchInParam, String theStart,
boolean checkBeginning, boolean checkSpaces, boolean equals, boolean trim) {
String searchIn = searchInParam.toLowerCase(Locale.getDefault());
if (trim && searchIn.length() > 0) {
searchIn += " ";
}
int searchInLength = searchIn.length();
if (trim && searchInLength > 0 && theStart.length() > searchInLength) {
theStart = theStart.substring(0, searchInLength);

View file

@ -194,7 +194,6 @@ public class Way extends Entity {
}
if (nodeIds != null) {
nodeIds.reverse();
;
}
}
}

View file

@ -319,7 +319,7 @@ public class SearchUICore {
SearchAmenityTypesAPI searchAmenityTypesAPI = new SearchAmenityTypesAPI(poiTypes);
apis.add(searchAmenityTypesAPI);
apis.add(new SearchCoreFactory.SearchAmenityByTypeAPI(poiTypes, searchAmenityTypesAPI));
apis.add(new SearchCoreFactory.SearchAmenityByNameAPI(searchAmenityTypesAPI));
apis.add(new SearchCoreFactory.SearchAmenityByNameAPI());
SearchBuildingAndIntersectionsByStreetAPI streetsApi =
new SearchCoreFactory.SearchBuildingAndIntersectionsByStreetAPI();
apis.add(streetsApi);
@ -500,7 +500,7 @@ public class SearchUICore {
}
currentSearchResult = collection;
if (phrase.getSettings().isExportObjects()) {
rm.createTestJSON(collection);
//rm.createTestJSON(collection);
}
rm.searchFinished(phrase);
if (onResultsComplete != null) {
@ -841,7 +841,9 @@ public class SearchUICore {
@Override
public int compare(SearchResult o1, SearchResult o2) {
if (!ObjectType.isTopVisible(o1.objectType) && !ObjectType.isTopVisible(o2.objectType)) {
boolean topVisible1 = ObjectType.isTopVisible(o1.objectType);
boolean topVisible2 = ObjectType.isTopVisible(o2.objectType);
if ((!topVisible1 && !topVisible2) || (topVisible1 && topVisible2)) {
if (o1.isUnknownPhraseMatches() != o2.isUnknownPhraseMatches()) {
return o1.isUnknownPhraseMatches() ? -1 : 1;
} else if (o1.getFoundWordCount() != o2.getFoundWordCount()) {

View file

@ -316,6 +316,7 @@ public class SearchCoreFactory {
String word = phrase.getUnknownWordToSearch();
NameStringMatcher nm = phrase.getNameStringMatcher(word, phrase.isUnknownSearchWordComplete());
NameStringMatcher wordEqualsMatcher = phrase.getNameStringMatcher(word, true);
boolean firstUnknownWordMatches = word.equals(phrase.getUnknownSearchWord());
resArray.clear();
resArray = townCitiesQR.queryInBox(bbox, resArray);
int limit = 0;
@ -337,8 +338,8 @@ public class SearchCoreFactory {
if (phrase.isEmptyQueryAllowed() && phrase.isEmpty()) {
resultMatcher.publish(res);
} else if (nm.matches(res.localeName) || nm.matches(res.otherNames)) {
res.firstUnknownWordMatches = word.equals(phrase.getUnknownSearchWord());
res.unknownPhraseMatches = wordEqualsMatcher.matches(res.localeName) || wordEqualsMatcher.matches(res.otherNames);
res.firstUnknownWordMatches = firstUnknownWordMatches;
res.unknownPhraseMatches = wordEqualsMatcher.matches(res.localeName);
subSearchApiOrPublish(phrase, resultMatcher, res, cityApi);
}
if (limit++ > LIMIT * phrase.getRadiusLevel()) {
@ -465,6 +466,7 @@ public class SearchCoreFactory {
String wordToSearch = phrase.getUnknownWordToSearch();
NameStringMatcher wordEqualsMatcher = phrase.getNameStringMatcher(wordToSearch, true);
boolean firstUnknownWordMatches = wordToSearch.equals(phrase.getUnknownSearchWord());
while (offlineIterator.hasNext() && wordToSearch.length() > 0) {
BinaryMapIndexReader r = offlineIterator.next();
currentFile[0] = r;
@ -478,8 +480,8 @@ public class SearchCoreFactory {
}
r.searchAddressDataByName(req);
for (SearchResult res : immediateResults) {
res.firstUnknownWordMatches = wordToSearch.equals(phrase.getUnknownSearchWord());
res.unknownPhraseMatches = wordEqualsMatcher.matches(res.localeName) || wordEqualsMatcher.matches(res.otherNames);
res.firstUnknownWordMatches = firstUnknownWordMatches;
res.unknownPhraseMatches = wordEqualsMatcher.matches(res.localeName);
if (res.objectType == ObjectType.STREET) {
City ct = ((Street) res.object).getCity();
phrase.countUnknownWordsMatch(res,
@ -501,11 +503,9 @@ public class SearchCoreFactory {
private static final int BBOX_RADIUS = 500 * 1000;
private static final int BBOX_RADIUS_INSIDE = 10000 * 1000; // to support city search for basemap
private static final int FIRST_WORD_MIN_LENGTH = 3;
private SearchAmenityTypesAPI searchAmenityTypesAPI;
public SearchAmenityByNameAPI(SearchAmenityTypesAPI searchAmenityTypesAPI) {
public SearchAmenityByNameAPI() {
super(ObjectType.POI);
this.searchAmenityTypesAPI = searchAmenityTypesAPI;
}
@Override
@ -574,7 +574,7 @@ public class SearchCoreFactory {
}
sr.priority = SEARCH_AMENITY_BY_NAME_PRIORITY;
if (phraseMatcher != null) {
sr.unknownPhraseMatches = phraseMatcher.matches(sr.localeName) || phraseMatcher.matches(sr.otherNames);
sr.unknownPhraseMatches = phraseMatcher.matches(sr.localeName);
}
phrase.countUnknownWordsMatch(sr);
sr.objectType = ObjectType.POI;
@ -662,17 +662,19 @@ public class SearchCoreFactory {
categories = types.getCategories(false);
}
List<AbstractPoiType> results = new ArrayList<AbstractPoiType>();
NameStringMatcher nm =
new NameStringMatcher(phrase.getUnknownSearchPhrase(), StringMatcherMode.CHECK_ONLY_STARTS_WITH_TRIM);
Set<String> filters = new HashSet<>();
NameStringMatcher nm;
String unknownSearchPhrase = phrase.getUnknownSearchPhrase();
if (phrase.getUnknownSearchWord().length() < unknownSearchPhrase.length()) {
nm = new NameStringMatcher(unknownSearchPhrase, StringMatcherMode.CHECK_ONLY_STARTS_WITH_TRIM);
} else {
nm = new NameStringMatcher(unknownSearchPhrase, StringMatcherMode.CHECK_STARTS_FROM_SPACE);
}
for (AbstractPoiType pf : topVisibleFilters) {
if (!phrase.isUnknownSearchWordPresent()
|| nm.matches(pf.getTranslation())
|| nm.matches(pf.getEnTranslation())
|| nm.matches(pf.getSynonyms())) {
results.add(pf);
filters.add(pf.getTranslation());
}
}
if (phrase.isUnknownSearchWordPresent()) {
@ -682,7 +684,6 @@ public class SearchCoreFactory {
|| nm.matches(c.getEnTranslation())
|| nm.matches(c.getSynonyms()))) {
results.add(c);
filters.add(c.getTranslation());
}
}
Iterator<Entry<String, PoiType>> it = translatedNames.entrySet().iterator();
@ -690,7 +691,7 @@ public class SearchCoreFactory {
Entry<String, PoiType> e = it.next();
PoiType pt = e.getValue();
if (pt.getCategory() != types.getOtherMapCategory()) {
if (!results.contains(pt) && !filters.contains(pt.getTranslation())
if (!results.contains(pt)
&& (nm.matches(pt.getEnTranslation())
|| nm.matches(pt.getTranslation())
|| nm.matches(pt.getSynonyms()))) {
@ -714,6 +715,8 @@ public class SearchCoreFactory {
phrase.setUnknownSearchWordPoiTypes(new ArrayList<>(results));
if (resultMatcher != null) {
String word = phrase.getUnknownSearchWord();
NameStringMatcher startMatch = new NameStringMatcher(word, StringMatcherMode.CHECK_ONLY_STARTS_WITH);
for (AbstractPoiType pt : results) {
SearchResult res = new SearchResult(phrase);
res.localeName = pt.getTranslation();
@ -721,6 +724,7 @@ public class SearchCoreFactory {
res.priority = SEARCH_AMENITY_TYPE_PRIORITY;
res.priorityDistance = 0;
res.objectType = ObjectType.POI_TYPE;
res.firstUnknownWordMatches = startMatch.matches(res.localeName);
resultMatcher.publish(res);
}
for (int i = 0; i < customPoiFilters.size(); i++) {
@ -913,7 +917,7 @@ public class SearchCoreFactory {
res.priority = SEARCH_AMENITY_BY_TYPE_PRIORITY;
res.priorityDistance = 1;
if (phraseMatcher != null) {
boolean unknownPhraseMatches = phraseMatcher.matches(res.localeName) || phraseMatcher.matches(res.otherNames);
boolean unknownPhraseMatches = phraseMatcher.matches(res.localeName);
AbstractPoiType unknownSearchWordPoiType = phrase.getUnknownSearchWordPoiType();
if (unknownPhraseMatches && unknownSearchWordPoiType != null) {
unknownPhraseMatches = !phraseMatcher.matches(unknownSearchWordPoiType.getTranslation())
@ -1037,7 +1041,7 @@ public class SearchCoreFactory {
phrase.getNameStringMatcher().matches(res.localeName) ||
phrase.getNameStringMatcher().matches(res.otherNames);
if (phraseMatcher != null) {
res.unknownPhraseMatches = phraseMatcher.matches(res.localeName) || phraseMatcher.matches(res.otherNames);
res.unknownPhraseMatches = phraseMatcher.matches(res.localeName);
}
res.localeRelatedObjectName = c.getName(phrase.getSettings().getLang(), phrase.getSettings().isTransliterate());
res.object = object;

View file

@ -9,6 +9,11 @@ public class SearchExportSettings {
exportBuildings = true;
}
public SearchExportSettings(boolean exportEmptyCities, boolean exportBuildings) {
this.exportEmptyCities = exportEmptyCities;
this.exportBuildings = exportBuildings;
}
public boolean isExportEmptyCities() {
return exportEmptyCities;
}

View file

@ -24,6 +24,7 @@ public class SearchSettings {
private boolean emptyQueryAllowed;
private boolean sortByName;
private SearchExportSettings exportSettings;
//private SearchExportSettings exportSettings = new SearchExportSettings(false, false);
public SearchSettings(SearchSettings s) {
if(s != null) {

View file

@ -526,11 +526,20 @@ public class GeoPointParserUtil {
if (params.containsKey("z")) {
zmPart = params.get("z");
}
String[] vls = silentSplit(path, ",");
String[] vls = null;
if(path.contains("@")) {
path = path.substring(path.indexOf("@") + 1);
if(path.contains(",")) {
vls = silentSplit(path, ",");
}
}
if(vls == null) {
vls = silentSplit(path, ",");
}
if (vls.length >= 2) {
double lat = parseSilentDouble(vls[0]);
double lon = parseSilentDouble(vls[1]);
double lat = parseSilentDouble(vls[0], Double.NaN);
double lon = parseSilentDouble(vls[1], Double.NaN);
int zoom = GeoParsedPoint.NO_ZOOM;
if (vls.length >= 3 || zmPart.length() > 0) {
if (zmPart.length() == 0) {
@ -543,7 +552,9 @@ public class GeoPointParserUtil {
}
zoom = parseZoom(zmPart);
}
return new GeoParsedPoint(lat, lon, zoom);
if(!Double.isNaN(lat) && !Double.isNaN(lon)) {
return new GeoParsedPoint(lat, lon, zoom);
}
}
return new GeoParsedPoint(URLDecoder.decode(opath));
}
@ -566,13 +577,17 @@ public class GeoPointParserUtil {
}
private static double parseSilentDouble(String zoom) {
return parseSilentDouble(zoom, 0);
}
private static double parseSilentDouble(String zoom, double vl) {
try {
if (zoom != null) {
return Double.valueOf(zoom);
}
} catch (NumberFormatException e) {
}
return 0;
return vl;
}
private static int parseSilentInt(String zoom) {
@ -739,8 +754,9 @@ public class GeoPointParserUtil {
@Override
public String toString() {
return isGeoPoint() ? "GeoParsedPoint [lat=" + lat + ", lon=" + lon + ", zoom=" + zoom
+ ", label=" + label + "]" : "GeoParsedPoint [query=" + query;
return isGeoPoint() ?
String.format("GeoParsedPoint [lat=%.5f, lon=%.5f, zoom=%d, label=%s]", lat, lon, zoom, label) :
String.format("GeoParsedPoint [query=%s]",query);
}
}
}

View file

@ -19,6 +19,19 @@ public class GeoPointParserUtilTest {
GeoParsedPoint test = GeoPointParserUtil.parse("geo:0,0?q=86HJV99P%2B29");
Assert.assertEquals(test.getQuery(), "86HJV99P+29");
}
@Test
public void testGoogleMaps() {
// https://www.google.com/maps?daddr=Bahnhofplatz+3,+7000+Chur@46.853582,9.529903
GeoParsedPoint actual = GeoPointParserUtil.parse(
"https://www.google.com/maps?daddr=Bahnhofplatz+3,+7000+Chur");
assertGeoPoint(actual, new GeoParsedPoint("Bahnhofplatz 3, 7000 Chur"));
actual = GeoPointParserUtil.parse(
"https://www.google.com/maps?daddr=Bahnhofplatz+3,+7000+Chur@46.853582,9.529903");
System.out.println(actual);
assertGeoPoint(actual, new GeoParsedPoint(46.853582, 9.529903));
}
@Test
public void testGeoPoint() {

View file

@ -11,8 +11,7 @@
},
"phrase": "parking",
"results": [
"Park (Leisure)",
"Parking (Filter)",
"Parking (Personal transport)",
"Parking entrance (Personal transport)",
"Parking fee (Charging station / Transportation)",
"Parking fee: no (Charging station / Transportation)",
@ -21,8 +20,8 @@
"Parking tickets (Vending machine / Store)",
"Parking tickets (Vending machine / Store)",
"Parking time limit (Parking / Personal transport)",
"Parking",
"Parking",
"Bicycle parking (Bicycle transport)",
"Motorcycle parking (Personal transport)",
"Parking",
"Parking",
"Parking",

View file

@ -0,0 +1,662 @@
{
"settings": {
"lat": "51.04933",
"lon": "13.73815",
"radiusLevel": 1,
"totalLimit": -1,
"lang": "",
"transliterateIfMissing": false,
"emptyQueryAllowed": false,
"sortByName": false
},
"phrase": "Biergarten",
"results": [
"Biergarten (Food)",
"Biergarten Italienisches Dörfchen",
"Biergarten Narrenhäus'l",
"Biergarten Elbsegler",
"Biergarten Elbsegler",
"Biergarten",
"Biergarten",
"Biergarten",
"Biergarten",
"Biergarten"
],
"amenities": [
{
"lat": "51.02489",
"lon": "13.69860",
"id": 244538865,
"subType": "biergarten",
"type": "sustenance",
"additionalInfo": {
"surface_fine_gravel": "fine_gravel"
}
},
{
"lat": "51.09478",
"lon": "13.84153",
"id": 865332598,
"subType": "biergarten",
"type": "sustenance",
"additionalInfo": {
"operator": "Einkehr"
}
},
{
"lat": "51.01123",
"lon": "13.67918",
"id": 9712288200,
"subType": "biergarten",
"type": "sustenance",
"additionalInfo": {
"website": "http://www.zur-linde-freital.de/",
"operator": "Zur Linde",
"image": "http://commons.wikimedia.org/wiki/File:Hotel_und_Gasthaus_Zur_Linde_Freital-Birkigt.jpg"
}
},
{
"name": "Altes Wettbüro",
"lat": "51.06472",
"lon": "13.74467",
"id": 11451975032,
"subType": "biergarten",
"type": "sustenance",
"openingHours": "Tu-Sa 17:00-22:00",
"additionalInfo": {
"wheelchair_limited": "limited",
"outdoor_seating_yes": "yes",
"outdoor_seating_filter_yes": "yes",
"opening_hours": "Tu-Sa 17:00-22:00",
"website": "http://www.altes-wettbuero.de",
"phone": "+49 351 6588983",
"operator": "Falk Gruß"
}
},
{
"name": "Carolaschlösschen",
"lat": "51.03341",
"lon": "13.76392",
"id": 3704353318,
"subType": "biergarten",
"type": "sustenance",
"openingHours": "Mo-Su 11:00-24:00",
"additionalInfo": {
"wheelchair_limited": "limited",
"opening_hours": "Mo-Su 11:00-24:00",
"facebook": "https://www.facebook.com/carolaschloesschen/"
}
},
{
"lat": "51.01251",
"lon": "13.69407",
"id": 562115695,
"subType": "biergarten",
"type": "sustenance"
},
{
"name": "Torwirtschaft",
"lat": "51.04156",
"lon": "13.75139",
"id": 517007905,
"subType": "biergarten",
"type": "sustenance",
"additionalInfo": {
"wheelchair_yes": "yes",
"website": "http://www.torwirtschaft-dresden.de",
"facebook": "https://www.facebook.com/Torwirtschaft-der-Biergarten-f%C3%BCr-alle-Dynamofans-168677023169963/"
}
},
{
"name": "Biergarten Italienisches Dörfchen",
"lat": "51.05429",
"lon": "13.73759",
"id": 486232011,
"subType": "biergarten",
"type": "sustenance"
},
{
"lat": "51.04261",
"lon": "13.75229",
"id": 98518191,
"subType": "biergarten",
"type": "sustenance",
"additionalInfo": {
"wheelchair_yes": "yes"
}
},
{
"name": "Körnergarten",
"names": {
"prefix": "Biergarten"
},
"lat": "51.05316",
"lon": "13.81192",
"id": 3704353594,
"subType": "biergarten",
"type": "sustenance",
"openingHours": "Mo-Su 11:00-24:00",
"additionalInfo": {
"wheelchair_limited": "limited",
"opening_hours": "Mo-Su 11:00-24:00",
"website": "http://www.koernergarten.de/"
}
},
{
"lat": "51.06397",
"lon": "13.79748",
"id": 670742479,
"subType": "biergarten",
"type": "sustenance"
},
{
"name": "Weingut Seifert",
"lat": "51.11134",
"lon": "13.66701",
"id": 789740008,
"subType": "biergarten",
"type": "sustenance",
"additionalInfo": {
"note": "not really a 'biergarten' :)"
}
},
{
"lat": "50.98710",
"lon": "13.65086",
"id": 9037536542,
"subType": "biergarten",
"type": "sustenance",
"additionalInfo": {
"operator": "Zum Gründl"
}
},
{
"lat": "51.10538",
"lon": "13.62622",
"id": 9289210778,
"subType": "biergarten",
"type": "sustenance",
"additionalInfo": {
"operator": "Zum Bürgergarten"
}
},
{
"lat": "51.09459",
"lon": "13.84170",
"id": 5598697742,
"subType": "biergarten",
"type": "sustenance",
"additionalInfo": {
"operator": "Einkehr"
}
},
{
"name": "Trobischhof",
"lat": "51.08649",
"lon": "13.71338",
"id": 409847251,
"subType": "biergarten",
"type": "sustenance",
"additionalInfo": {
"wheelchair_limited": "limited"
}
},
{
"name": "Demnitz Elbegarten",
"lat": "51.05328",
"lon": "13.81175",
"id": 771102016,
"subType": "biergarten",
"type": "sustenance",
"additionalInfo": {
"wheelchair_no": "no",
"cuisine_german": "german",
"website": "http://www.elbegarten.de"
}
},
{
"name": "Augustus Garten am Narrenhäusl",
"lat": "51.05697",
"lon": "13.74166",
"id": 465538291,
"subType": "biergarten",
"type": "sustenance"
},
{
"lat": "51.01529",
"lon": "13.65107",
"id": 7482890514,
"subType": "biergarten",
"type": "sustenance",
"additionalInfo": {
"operator": "Burgwartschänke"
}
},
{
"name": "el Horst",
"lat": "51.04361",
"lon": "13.78979",
"id": 559979958,
"subType": "biergarten",
"type": "sustenance",
"openingHours": "Oct-Apr: Mo-Fr 17:00-18:00+, Oct-Apr: Sa,PH 11:30-18:00+, May-Sep: Mo-Fr 15:00-18:00+, May-Sep: Sa,PH 11:30-18:00+",
"additionalInfo": {
"opening_hours": "Oct-Apr: Mo-Fr 17:00-18:00+, Oct-Apr: Sa,PH 11:30-18:00+, May-Sep: Mo-Fr 15:00-18:00+, May-Sep: Sa,PH 11:30-18:00+"
}
},
{
"lat": "51.07376",
"lon": "13.70135",
"id": 553613482,
"subType": "biergarten",
"type": "sustenance",
"additionalInfo": {
"operator": "Wirtshaus Lindenschänke"
}
},
{
"name": "Wachstube",
"lat": "51.04254",
"lon": "13.75222",
"id": 46060263,
"subType": "biergarten",
"type": "sustenance",
"openingHours": "11:00+",
"additionalInfo": {
"wheelchair_no": "no",
"opening_hours": "11:00+",
"website": "https://www.torwirtschaft-dresden.de/wachstube/restaurant.php",
"phone": "+49 351 4466975",
"image": "https://commons.wikimedia.org/wiki/File:Torhaus_N_Grosser_Garten_Dresden-2.jpg",
"wheelchair_description:de": "Zugang ins Gebäude nur über Stufen",
"height": "5.7",
"facebook": "https://www.facebook.com/Wachstube-im-Gro%C3%9Fen-Garten-Dresden-144180698984577/",
"email": "info@wachstube-dresden.de"
}
},
{
"lat": "51.09050",
"lon": "13.70555",
"id": 6300849046,
"subType": "biergarten",
"type": "sustenance",
"additionalInfo": {
"operator": "Bäckerei Werner"
}
},
{
"name": "Biergarten Elbsegler",
"lat": "51.05731",
"lon": "13.73973",
"id": 465318113,
"subType": "biergarten",
"type": "sustenance",
"additionalInfo": {
"wheelchair_limited": "limited"
}
},
{
"name": "Straußenwirtschaft Weingut Pesterwitz",
"lat": "51.02883",
"lon": "13.64144",
"id": 478369183,
"subType": "biergarten",
"type": "sustenance",
"additionalInfo": {
"website": "https://www.gut-pesterwitz.de/"
}
},
{
"name": "Schillergarten",
"lat": "51.05228",
"lon": "13.80908",
"id": 493702544,
"subType": "biergarten",
"type": "sustenance",
"openingHours": "Mo-Su 11:00-01:00",
"additionalInfo": {
"wheelchair_yes": "yes",
"opening_hours": "Mo-Su 11:00-01:00"
}
},
{
"lat": "51.04111",
"lon": "13.80554",
"id": 5837244174,
"subType": "biergarten",
"type": "sustenance",
"additionalInfo": {
"operator": "Astloch"
}
},
{
"name": "Spitzwegerich",
"lat": "51.07767",
"lon": "13.70757",
"id": 412610355,
"subType": "biergarten",
"type": "sustenance",
"additionalInfo": {
"wheelchair_yes": "yes",
"toilets_wheelchair_no": "no"
}
},
{
"name": "Biergarten Goldener Anker",
"lat": "51.10399",
"lon": "13.62850",
"id": 143015201,
"subType": "biergarten",
"type": "sustenance",
"openingHours": "Tu-Su,PH 11:00-21:00",
"additionalInfo": {
"wheelchair_yes": "yes",
"opening_hours": "Tu-Su,PH 11:00-21:00"
}
},
{
"name": "Bottoms Up",
"lat": "51.06516",
"lon": "13.75615",
"id": 1394136626,
"subType": "biergarten",
"type": "sustenance"
},
{
"name": "Biergarten Narrenhäus'l",
"lat": "51.05666",
"lon": "13.74130",
"id": 466763007,
"subType": "biergarten",
"type": "sustenance"
},
{
"name": "Zum Schießhaus",
"lat": "51.05464",
"lon": "13.72727",
"id": 326416539,
"subType": "biergarten",
"type": "sustenance",
"openingHours": "Mo-Su,PH 11:00-23:00",
"additionalInfo": {
"wheelchair_yes": "yes",
"toilets_wheelchair_no": "no",
"opening_hours": "Mo-Su,PH 11:00-23:00"
}
},
{
"name": "Palais Bistro",
"lat": "51.05182",
"lon": "13.73682",
"id": 501015667,
"subType": "biergarten",
"type": "sustenance",
"openingHours": "Apr-Oct: Mo-Su 11:00-24:00, Nov-Mar: Mo-Th 12:00-14:30,17:30-24:00, Nov-Mar: Fr-Su 12:00-24:00",
"additionalInfo": {
"wheelchair_yes": "yes",
"opening_hours": "Apr-Oct: Mo-Su 11:00-24:00, Nov-Mar: Mo-Th 12:00-14:30,17:30-24:00, Nov-Mar: Fr-Su 12:00-24:00"
}
},
{
"name": "Brauhaus am Waldschlößchen",
"lat": "51.06778",
"lon": "13.77786",
"id": 594989978,
"subType": "biergarten",
"type": "sustenance",
"openingHours": "Mo-Su 11:00-01:00",
"additionalInfo": {
"wheelchair_limited": "limited",
"opening_hours": "Mo-Su 11:00-01:00"
}
},
{
"lat": "51.00222",
"lon": "13.68504",
"id": 2469999180,
"subType": "biergarten",
"type": "sustenance"
},
{
"name": "Biergarten Elbsegler",
"lat": "51.05738",
"lon": "13.73913",
"id": 466539133,
"subType": "biergarten",
"type": "sustenance"
},
{
"lat": "51.01010",
"lon": "13.66208",
"id": 7987508388,
"subType": "biergarten",
"type": "sustenance",
"additionalInfo": {
"wheelchair_yes": "yes",
"operator": "Zum goldenen Löwen"
}
},
{
"lat": "50.99476",
"lon": "13.72701",
"id": 6765029580,
"subType": "biergarten",
"type": "sustenance",
"additionalInfo": {
"operator": "Eutschützer Mühle"
}
},
{
"lat": "50.99295",
"lon": "13.64362",
"id": 6065174964,
"subType": "biergarten",
"type": "sustenance",
"additionalInfo": {
"operator": "Alte Schmiede"
}
},
{
"name": "Louisengarten",
"lat": "51.06681",
"lon": "13.75278",
"id": 68516655,
"subType": "biergarten",
"type": "sustenance",
"openingHours": "Apr-Sep Su-Th 16:00+; Apr-Sep Fr,Sa 15:00+ || off \"Bei unfreundlichem Wetter\"",
"additionalInfo": {
"wheelchair_no": "no",
"opening_hours": "Apr-Sep Su-Th 16:00+; Apr-Sep Fr,Sa 15:00+ || off \"Bei unfreundlichem Wetter\"",
"website": "http://www.biergarten-dresden.de",
"note": "Neustädter Winter Hüttn von Oktober-Dezember",
"alt_name": "Neustädter Winter Hüttn"
}
},
{
"lat": "51.00871",
"lon": "13.65959",
"id": 9237211698,
"subType": "biergarten",
"type": "sustenance",
"additionalInfo": {
"wheelchair_yes": "yes",
"operator": "Akropolis"
}
},
{
"lat": "51.03435",
"lon": "13.76042",
"id": 595971770,
"subType": "biergarten",
"type": "sustenance",
"openingHours": "Apr-Oct: Mo-Fr 11:00-19:00; Sa-Su 10:00-19:00; PH 10:00-19:00",
"additionalInfo": {
"wheelchair_yes": "yes",
"opening_hours": "Apr-Oct: Mo-Fr 11:00-19:00; Sa-Su 10:00-19:00; PH 10:00-19:00",
"website": "https://www.grosser-garten-dresden.de/de/gaesteservice/gastronomie-shop/",
"phone": "+49 152 37 00 67 53"
}
},
{
"name": "Landgut Hofewiese",
"lat": "51.10989",
"lon": "13.83207",
"id": 8455509378,
"subType": "biergarten",
"type": "sustenance",
"additionalInfo": {
"wheelchair_yes": "yes"
}
},
{
"name": "Café & Restaurant Saite",
"lat": "51.07636",
"lon": "13.74825",
"id": 577481323,
"subType": "biergarten",
"type": "sustenance",
"openingHours": "Mo-Fr 18:00-24:00, Su 10:00-15:00",
"additionalInfo": {
"wheelchair_limited": "limited",
"opening_hours": "Mo-Fr 18:00-24:00, Su 10:00-15:00"
}
},
{
"name": "Alt-Dresden",
"lat": "51.04977",
"lon": "13.69881",
"id": 970594717,
"subType": "biergarten",
"type": "sustenance",
"additionalInfo": {
"wheelchair_limited": "limited",
"phone": "0351 4135133"
}
},
{
"lat": "51.08677",
"lon": "13.70081",
"id": 413620001,
"subType": "biergarten",
"type": "sustenance"
},
{
"lat": "50.98618",
"lon": "13.63208",
"id": 10355747978,
"subType": "biergarten",
"type": "sustenance",
"additionalInfo": {
"operator": "Hirschbergschenke"
}
},
{
"name": "Fährgarten Johannstadt",
"lat": "51.06151",
"lon": "13.76679",
"id": 4415013507072,
"subType": "biergarten",
"type": "sustenance",
"additionalInfo": {
"wheelchair_yes": "yes",
"outdoor_seating_yes": "yes",
"outdoor_seating_filter_yes": "yes",
"website": "http://www.faehrgarten.de/",
"phone": "+49 351 4596262",
"brewery_additional": "Radeberger",
"email": "info@faehrgarten.de"
}
},
{
"lat": "51.02343",
"lon": "13.73347",
"id": 4563637454,
"subType": "biergarten",
"type": "sustenance"
},
{
"name": "Besenwirtschaft Steinrücken",
"lat": "51.11590",
"lon": "13.62457",
"id": 7474665554,
"subType": "biergarten",
"type": "sustenance",
"additionalInfo": {
"website": "http://www.besenwirtschaft-steinruecken.de/index.html"
}
},
{
"lat": "51.05181",
"lon": "13.81396",
"id": 9819148822,
"subType": "biergarten",
"type": "sustenance",
"additionalInfo": {
"operator": "WSV \"Am Blauen Wunder\""
}
},
{
"name": "Zacke",
"lat": "51.01300",
"lon": "13.63618",
"id": 7498805230,
"subType": "biergarten",
"type": "sustenance"
},
{
"name": "Klotzscher Sommerwirtschaft",
"lat": "51.11072",
"lon": "13.76879",
"id": 717794449,
"subType": "biergarten",
"type": "sustenance",
"openingHours": "Mo-Fr 17:00-23:00; Sa-Su 11:00-23:00; Nov-Mar off",
"additionalInfo": {
"opening_hours": "Mo-Fr 17:00-23:00; Sa-Su 11:00-23:00; Nov-Mar off",
"website": "http://klotzschersommerwirtschaft.de",
"phone": "+49 351 8804570",
"fax": "+49 351 8902050",
"email": "Kontakt@klotzscher-sommerwirtschaft.de",
"capacity": "20"
}
},
{
"name": "Brauhaus Watzke",
"lat": "51.07744",
"lon": "13.71540",
"id": 397976739,
"subType": "biergarten",
"type": "sustenance",
"additionalInfo": {
"wheelchair_limited": "limited",
"toilets_wheelchair_no": "no"
}
},
{
"name": "Trachauer Sommergarten",
"lat": "51.09166",
"lon": "13.70289",
"id": 1822089672,
"subType": "biergarten",
"type": "sustenance",
"openingHours": "Mo-Su 17:00-21:00",
"additionalInfo": {
"opening_hours": "Mo-Su 17:00-21:00",
"note": "Auch im Winter geöffnet"
}
},
{
"lat": "51.06520",
"lon": "13.82406",
"id": 11565282314,
"subType": "biergarten",
"type": "sustenance",
"openingHours": "seasonal",
"additionalInfo": {
"outdoor_seating_yes": "yes",
"outdoor_seating_filter_yes": "yes",
"opening_hours": "seasonal"
}
}
]
}

View file

@ -0,0 +1,175 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources><string name="last_update_from_telegram">Апошняе абнаўленне з Telegram</string>
<string name="enter_another_device_name">Абярыце імя, якое вы яшчэ не выкарыстоўвалі</string>
<string name="device_added_successfully">%1$s дададзена.</string>
<string name="shared_string_add">Дадаць</string>
<string name="error_adding_new_device">Не атрымалася дадаць новую прыладу</string>
<string name="enter_device_name_description">Максімальная даўжыня назвы новай прылады - 200 сімвалаў.</string>
<string name="device_name_is_too_long">Назва прылады занадта доўгая</string>
<string name="device_name_cannot_be_empty">Назва прылады не можа быць пустой</string>
<string name="device_name">Назва прылады</string>
<string name="shared_string_hide">Схаваць</string>
<string name="share_location_as_description_second_line">Вы можаце стварыць і праглядзець ідэнтыфікатар прылады ў кліенце Тэлеграма, выкарыстоўваючы %1$s бота. %2$s</string>
<string name="share_location_as_description">Калі вы хочаце падлучыць некалькі прылад да аднаго рахунка тэлеграм, то вам неабходна выкарыстаць розныя прылады, каб падзяліцца месцазнаходжаннем.</string>
<string name="last_updated_location">Апошняе абнаўленне месцазнаходжання:</string>
<string name="successfully_sent_and_updated">Паспяхова адпраўлена і абноўлена</string>
<string name="not_possible_to_send_to_telegram_chats">Немагчыма адправіць у размову Тэлеграм:</string>
<string name="waiting_for_response_from_telegram">Чаканне адказу ад Тэлеграм</string>
<string name="sending_location_messages">Адпраўленне месцазнаходжання</string>
<string name="initializing">Запуск</string>
<string name="searching_for_gps">Пазіцыянаванне…</string>
<string name="connecting_to_the_internet">Злучэнне з Інтэрнэтам</string>
<string name="background_work_description">Змена параметраў аптымізацыі батарэі для стабілізацыі абмену інфармацыяй аб месцазнаходжанні.</string>
<string name="background_work">Праца ў фонавым рэжыме</string>
<string name="battery_optimization_description">Выключыць аптымізацыю батарэі для OsmAnd Telegram, каб прадухіліць нечаканае выключэнне фонавага рэжыму.</string>
<string name="sharing_in_background">Абмен у фонавым рэжыме</string>
<string name="go_to_settings">Перайсці ў налады</string>
<string name="shared_string_later">Пазней</string>
<string name="not_sent_yet">Яшчэ не адпраўлена</string>
<string name="not_found_yet">Яшчэ не знойдзена</string>
<string name="re_send_location">Пераадправіць звесткі аб месцазнаходжанні</string>
<string name="last_available_location">Апошняе даступнае месца</string>
<string name="sharing_status">"Статус абмену "</string>
<string name="sharing_enabled">Абмен: Уключаны</string>
<string name="shared_string_status">Статус</string>
<string name="no_gps_connection">Злучэнне з GPS адсутнічае</string>
<string name="no_internet_connection">Злучэнне з Інтэрнэтам адсутнічае</string>
<string name="shared_string_disable">Выключыць</string>
<string name="shared_string_save">Захаваць</string>
<string name="add_device">Дадаць прыладу</string>
<string name="share_location_as">Падзяліцца месцазнаходжаннем як</string>
<string name="live_now_description">Кантакты і групы для абмену месцазнаходжаннем.</string>
<string name="logout_from_osmand_telegram_descr">"Вы не зможаце падзяліцца сваім месцазнаходжаннем і ўбачыць месцазнаходжанне іншых. Сапраўды выйсці з OsmAnd Telegram\? "</string>
<string name="logout_from_osmand_telegram">Выйсці з OsmAnd Telegram\?</string>
<string name="shared_string_name">Імя</string>
<string name="by_distance">Па адлегласці</string>
<string name="by_name">Па імёнах</string>
<string name="by_group">Па групе</string>
<string name="shared_string_sort">Упарадкаваць</string>
<string name="shared_string_sort_by">Упарадкаваць па</string>
<string name="choose_osmand_desc">Абярыце версію OsmAnd, у якой кантакты будуць адлюстроўвацца на мапе.</string>
<string name="choose_osmand">Абярыце версію OsmAnd для выкарыстання</string>
<string name="disable_all_sharing_desc">Выключыць абмен для ўсіх абраных размоў (%1$d).</string>
<string name="disable_all_sharing">Выключыць усе абмены</string>
<string name="turn_off_all">Выключыце ўсе</string>
<string name="shared_string_exit">Выйсці</string>
<string name="time_ago">таму</string>
<string name="last_response">Апошні адказ</string>
<string name="shared_string_group">Група</string>
<string name="logout_no_internet_msg">Падлучыцеся да Інтэрнэту, каб карэктна выйсці з Тэлеграм.</string>
<string name="shared_string_close">Закрыць</string>
<string name="disconnect_from_telegram_desc">Для таго, каб скасаваць абмен месцазнаходжаннем, адкрыйце Тэлеграм, перайдзіце ў Налады → Прыватнасць і бяспека → Сеансы і спыніце сеанс OsmAnd Telegram.</string>
<string name="disconnect_from_telegram">Як выключыць абмен месцазнаходжаннем у OsmAnd праз Тэлеграм</string>
<string name="logout_help_desc">Як выключыць абмен месцазнаходжаннем у OsmAnd праз Тэлеграм</string>
<string name="connected_account">Падлучаны рахунак</string>
<string name="shared_string_account">Рахунак</string>
<string name="in_time">у %1$s</string>
<string name="osmand_connect_desc">Абраць версію OsmAnd, якую OsmAnd Telegram будзе выкарыстоўваць для адлюстравання пазіцыі.</string>
<string name="osmand_connect">Злучэнне з OsmAnd</string>
<string name="location_history_desc">Схаваць кантакты, якія не перамяшчаліся пэўны час.</string>
<string name="location_history">Гісторыя месцазнаходжанняў</string>
<string name="stale_location_desc">Апошні раз кантакт рухаўся.</string>
<string name="stale_location">Не рухаецца</string>
<string name="send_my_location_desc">Вызначыць мінімальны інтэрвал для абмену інфармацыяй аб месцазнаходжанні.</string>
<string name="send_my_location">Адправіць маё месцазнаходжанне</string>
<string name="gps_and_location">Пазіцыя</string>
<string name="sharing_time">"Час абмену "</string>
<string name="expire_at">Сыходзіць</string>
<string name="stop_sharing_all">Абмен уключаны (выключыць)</string>
<string name="turn_off_location_sharing">Выключыць абмен</string>
<string name="open_osmand">Адкрыць OsmAnd</string>
<string name="shared_string_live">Дзейных</string>
<string name="shared_string_bot">Бот</string>
<string name="get_telegram_title">Рэгістрацыя ў Telegram</string>
<string name="get_telegram_account_first">Для абмену вам неабходны рахунак Тэлеграм.</string>
<string name="get_telegram_description_continue">Калі ласка, ўсталюйце Тэлеграм і наладзьце рахунак.</string>
<string name="get_telegram_after_creating_account">Пасля гэтага вы зможаце выкарыстоўваць дадатак.</string>
<string name="shared_string_all">Усе</string>
<string name="shared_string_off">Выкл</string>
<string name="already_registered_in_telegram">Вам неабходна мець рахунак Тэлеграм і нумар тэлефона</string>
<string name="do_not_have_telegram">У мяне няма рахунка Тэлеграм</string>
<string name="enter_phone_number">Увядзіце нумар тэлефона</string>
<string name="enter_authentication_code">Увядзіце код аўтарызацыі</string>
<string name="set_visible_time_for_all">Вызначце бачны час для ўсіх</string>
<string name="hours_and_minutes_format">%1$d г %2$d хв</string>
<string name="minutes_format">%1$d хв</string>
<string name="hours_format">%1$d г</string>
<string name="shared_string_install">Усталяваць</string>
<string name="shared_string_share">Падзяліцца</string>
<string name="shared_string_back">Назад</string>
<string name="visible_time_for_all">Бачны для ўсіх час</string>
<string name="set_time_description">Задайце час, што будуць бачыць абраныя вамі кантакты і групы ў рэжыме рэальнага часу.</string>
<string name="set_time">Задаць час</string>
<string name="location_sharing_description">Абярыце кантакты і групы, з якімі хочаце абменьвацца вашым месцазнаходжаннем.</string>
<string name="my_location_search_hint">Пошук: група альбо кантакт</string>
<string name="start_location_sharing">Падзяліцца месцазнаходжаннем</string>
<string name="show_on_map">Паказаць на мапе</string>
<string name="app_name">ОsmAnd Telegram</string>
<string name="phone_number_title">Нумар тэлефона</string>
<string name="phone_number_descr">Нумар тэлефона ў міжнародным фармаце</string>
<string name="shared_string_password">Пароль</string>
<string name="enter_code">Увядзіце код</string>
<string name="authentication_code">Код аўтэнтыфікацыі</string>
<string name="authentication_code_descr">Тэлеграм адправіў вам код для OsmAnd для ўваходу ў рахунак.</string>
<string name="enter_password">Увядзіце пароль</string>
<string name="password_descr">Пароль Тэлеграм</string>
<string name="shared_string_login">Увайсці</string>
<string name="shared_string_logout">Выйсці</string>
<string name="initialization">Запуск</string>
<string name="logging_out">Выхад</string>
<string name="closing">Закрыццё</string>
<string name="gps_network_not_enabled">Уключыць \"Месцазнаходжанне\"\?</string>
<string name="not_logged_in">Вы не ўвайшлі</string>
<string name="shared_string_continue">Працягнуць</string>
<string name="shared_string_cancel">Скасаваць</string>
<string name="shared_string_settings">Налады</string>
<string name="no_location_permission">Дадатак не мае дазволу на доступ да даных аб месцазнаходжанні.</string>
<string name="gps_not_available">Калі ласка, ўключыце \"Месцазнаходжанне\" ў сістэмных наладах</string>
<string name="location_service_no_gps_available">Абярыце аднаго пастаўшчыка месцазнаходжання, каб падзяліцца сваім месцазнаходжаннем.</string>
<string name="osmand_service">Фонавы рэжым</string>
<string name="osmand_service_descr">OsmAnd Telegram працуе ў фонавым рэжыме з выключаным экранам.</string>
<string name="shared_string_distance">Адлегласць</string>
<string name="share_location">Падзяліцца месцазнаходжаннем</string>
<string name="sharing_location">Абмен данымі аб месцазнаходжанні</string>
<string name="process_service">Сэрвіс OsmAnd Telegram</string>
<string name="osmand_logo">Лагатып OsmAnd</string>
<string name="install_osmand_dialog_message">Спачатку вам неабходна ўсталяваць бясплатную ці платную версію OsmAnd</string>
<string name="install_osmand">Усталяваць OsmAnd</string>
<string name="show_users_on_map">Паказаць карыстальнікаў на мапе</string>
<string name="active_chats">Актыўныя размовы</string>
<string name="shared_string_authorization">Аўтарызацыя</string>
<string name="shared_string_authorization_descr">Калі ласка, увядзіце ваш нумар тэлефона, звязаны з Тэлеграм, ў міжнародным фармаце</string>
<string name="shared_string_welcome">Вітаем</string>
<string name="yard">ярд</string>
<string name="foot">фут</string>
<string name="mile">міл</string>
<string name="km">км</string>
<string name="m">м</string>
<string name="nm">м.мілі</string>
<string name="min_mile">хв/м</string>
<string name="min_km">хв/км</string>
<string name="nm_h">м.міль/г</string>
<string name="m_s">м/с</string>
<string name="km_h">км/г</string>
<string name="mile_per_hour">м/г</string>
<string name="si_kmh">Кіламетраў за гадзіну</string>
<string name="si_mph">Міляў за гадзіну</string>
<string name="si_m_s">Метраў за секунду</string>
<string name="si_min_km">Хвілін на кіламетар</string>
<string name="si_min_m">Хвілін на мілю</string>
<string name="si_nm_h">Марскіх міль за гадзіну (вузлоў)</string>
<string name="si_mi_feet">Мілі/футы</string>
<string name="si_mi_yard">Мілі/ярды</string>
<string name="si_km_m">Кіламетры/метры</string>
<string name="si_nm">Марскія мілі</string>
<string name="si_mi_meters">Мілі/метры</string>
<string name="shared_string_hour_short">г</string>
<string name="shared_string_minute_short">хвіл</string>
<string name="shared_string_second_short">сек</string>
<string name="welcome_descr"><b>Абмен месцазнаходжаннем OsmAnd</b> Дае магчымасць дзяліцца сваім месцазнаходжаннем і бачыць месцазнаходжанне іншых у OsmAnd.<br/><br/> Дадатак выкарыстоўвае Telegram API, таму вам неабходны рахунак Тэлеграм.</string>
<string name="my_location">Маё месцазнаходжанне</string>
<string name="live_now">Зараз дзейнічае</string>
</resources>

View file

@ -19,4 +19,5 @@
<string name="re_send_location">بازفرستی موقعیت</string>
<string name="sharing_enabled">اشتراک‌گذاری: فعال</string>
<string name="shared_string_status">وضعیت</string>
<string name="shared_string_close">بستن</string>
</resources>

View file

@ -0,0 +1,29 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources><string name="last_update_from_telegram">עדכון אחרון מטלגרם</string>
<string name="enter_another_device_name">נא לבחור שם שלא השתמשת בו עדיין</string>
<string name="device_added_successfully">%1$s נוסף.</string>
<string name="shared_string_add">הוספה</string>
<string name="error_adding_new_device">לא ניתן להוסיף מכשיר חדש</string>
<string name="enter_device_name_description">נא לתת שם באורך של עד 200 תווים למכשיר החדש שלך.</string>
<string name="device_name_is_too_long">שם המכשיר ארוך מדי</string>
<string name="device_name_cannot_be_empty">שם המכשיר לא יכול להישאר ריק</string>
<string name="device_name">שם המכשיר</string>
<string name="shared_string_hide">הסתרה</string>
<string name="share_location_as_description_second_line">ניתן ליצור ולצפות במזהה ההתקן בלקוח הטלגרם הזה באמצעות רובוט ההתכתבות %1$s. %2$s</string>
<string name="share_location_as_description">אם ברצונך לחבר מגוון מכשירים לחשבון טלגרם אחד, עליך להשתמש במכשיר אחר כדי לשתף את המיקום שלך.</string>
<string name="last_updated_location">המיקום האחרון שעודכן:</string>
<string name="successfully_sent_and_updated">נשלח ועודכן בהצלחה</string>
<string name="not_possible_to_send_to_telegram_chats">אין אפשרות לשלוח להתכתבויות בטלגרם:</string>
<string name="send_location_as">שליחת מיקום בתור</string>
<string name="send_location_as_descr">נא לבחור כיצד הודעות עם המיקום שלך תיראנה.</string>
<string name="shared_string_map">מפה</string>
<string name="shared_string_text">טקסט</string>
<string name="map_and_text">מפה וטקסט</string>
<string name="waiting_for_response_from_telegram">בהמתנה לתגובה מטלגרם</string>
<string name="sending_location_messages">המיקום נשלח</string>
<string name="initializing">מופעל</string>
<string name="searching_for_gps">מתבצע איתור המיקום…</string>
<string name="connecting_to_the_internet">מתבצעת התחברות לאינטרנט</string>
<string name="background_work_description">ניתן לשנות הגדרות מיטוב סוללה כדי לייצב את שיתוף המיקום.</string>
<string name="background_work">עבודת רקע</string>
</resources>

View file

@ -174,4 +174,9 @@
<string name="last_update_from_telegram">Siste oppdatering fra Telegram</string>
<string name="send_location_as">Send plassering som</string>
<string name="send_location_as_descr">Velg hvordan meldinger med din plassering skal se ut.</string>
<string name="shared_string_map">Kart</string>
<string name="shared_string_text">Tekst</string>
<string name="map_and_text">Kart og tekst</string>
</resources>

View file

@ -159,4 +159,5 @@
<string name="my_location">Моя позиція</string>
<string name="send_my_location_desc">Встановіть мінімальний інтервал між надсиланням позиції.</string>
<string name="expire_at">Діє до</string>
<string name="last_update_from_telegram">Нещодавнє оновлення з Telegram</string>
</resources>

View file

@ -1,4 +1,9 @@
<resources>
<string name="send_location_as">Send location as</string>
<string name="send_location_as_descr">Choose how messages with your location will look like.</string>
<string name="shared_string_map">Map</string>
<string name="shared_string_text">Text</string>
<string name="map_and_text">Map and text</string>
<string name="last_update_from_telegram">Last update from Telegram</string>
<string name="enter_another_device_name">Pick a name you haven\'t already used</string>
<string name="device_added_successfully">%1$s added.</string>

View file

@ -7,7 +7,6 @@ import android.support.annotation.DrawableRes
import android.support.annotation.StringRes
import android.text.SpannableStringBuilder
import android.text.style.ForegroundColorSpan
import net.osmand.data.LatLon
import net.osmand.telegram.helpers.OsmandAidlHelper
import net.osmand.telegram.helpers.TelegramHelper
import net.osmand.telegram.utils.AndroidUtils
@ -43,9 +42,15 @@ private val LOC_HISTORY_VALUES_SEC = listOf(
24 * 60 * 60L
)
const val SHARE_TYPE_MAP = "Map"
const val SHARE_TYPE_TEXT = "Text"
const val SHARE_TYPE_MAP_AND_TEXT = "Map_and_text"
private val SHARE_TYPE_VALUES = listOf(SHARE_TYPE_MAP, SHARE_TYPE_TEXT, SHARE_TYPE_MAP_AND_TEXT)
private const val SEND_MY_LOC_DEFAULT_INDEX = 6
private const val STALE_LOC_DEFAULT_INDEX = 4
private const val LOC_HISTORY_DEFAULT_INDEX = 2
private const val STALE_LOC_DEFAULT_INDEX = 0
private const val LOC_HISTORY_DEFAULT_INDEX = 7
private const val SHARE_TYPE_DEFAULT_INDEX = 2
private const val SETTINGS_NAME = "osmand_telegram_settings"
@ -60,6 +65,7 @@ private const val SPEED_CONSTANTS_KEY = "speed_constants"
private const val SEND_MY_LOC_INTERVAL_KEY = "send_my_loc_interval"
private const val STALE_LOC_TIME_KEY = "stale_loc_time"
private const val LOC_HISTORY_TIME_KEY = "loc_history_time"
private const val SHARE_TYPE_KEY = "share_type"
private const val APP_TO_CONNECT_PACKAGE_KEY = "app_to_connect_package"
@ -94,13 +100,14 @@ class TelegramSettings(private val app: TelegramApplication) {
var sendMyLocInterval = SEND_MY_LOC_VALUES_SEC[SEND_MY_LOC_DEFAULT_INDEX]
var staleLocTime = STALE_LOC_VALUES_SEC[STALE_LOC_DEFAULT_INDEX]
var locHistoryTime = LOC_HISTORY_VALUES_SEC[LOC_HISTORY_DEFAULT_INDEX]
var shareTypeValue = SHARE_TYPE_VALUES[SHARE_TYPE_DEFAULT_INDEX]
var appToConnectPackage = ""
private set
var liveNowSortType = LiveNowSortType.SORT_BY_GROUP
val gpsAndLocPrefs = listOf(SendMyLocPref(), StaleLocPref(), LocHistoryPref())
val gpsAndLocPrefs = listOf(SendMyLocPref(), StaleLocPref(), LocHistoryPref(), ShareTypePref())
var batteryOptimisationAsked = false
@ -113,6 +120,8 @@ class TelegramSettings(private val app: TelegramApplication) {
fun isSharingLocationToChat(chatId: Long) = shareChatsInfo.containsKey(chatId)
fun isSharingLocationToUser(userId: Int) = shareChatsInfo.values.any { it.userId == userId }
fun hasAnyChatToShowOnMap() = !hiddenOnMapChats.containsAll(getLiveNowChats())
fun isShowingChatOnMap(chatId: Long) = !hiddenOnMapChats.contains(chatId)
@ -132,35 +141,33 @@ class TelegramSettings(private val app: TelegramApplication) {
addActiveTime: Long = ADDITIONAL_ACTIVE_TIME_VALUES_SEC[0]
) {
if (share) {
val lp: Long = when {
livePeriod < TelegramHelper.MIN_LOCATION_MESSAGE_LIVE_PERIOD_SEC -> TelegramHelper.MIN_LOCATION_MESSAGE_LIVE_PERIOD_SEC.toLong()
else -> livePeriod
}
var shareChatInfo = shareChatsInfo[chatId]
if (shareChatInfo == null) {
shareChatInfo = ShareChatInfo()
}
val currentTime = System.currentTimeMillis() / 1000
val user = app.telegramHelper.getCurrentUser()
if (user != null && currentSharingMode != user.id.toString() && shareChatInfo.start == -1L) {
shareChatInfo.shouldSendViaBotMessage = true
val chat = app.telegramHelper.getChat(chatId)
if (chat != null && (chat.type is TdApi.ChatTypePrivate || chat.type is TdApi.ChatTypeSecret)) {
shareChatInfo.userId = app.telegramHelper.getUserIdFromChatType(chat.type)
}
shareChatInfo.chatId = chatId
shareChatInfo.start = currentTime
if (shareChatInfo.livePeriod == -1L) {
shareChatInfo.livePeriod = lp
}
shareChatInfo.userSetLivePeriod = lp
shareChatInfo.userSetLivePeriodStart = currentTime
shareChatInfo.currentMessageLimit = currentTime + Math.min(lp, TelegramHelper.MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC.toLong())
shareChatInfo.additionalActiveTime = addActiveTime
updateChatShareInfo(shareChatInfo, livePeriod, addActiveTime)
shareChatsInfo[chatId] = shareChatInfo
} else {
shareChatsInfo.remove(chatId)
}
}
fun shareLocationToUser(
userId: Int,
livePeriod: Long = DEFAULT_VISIBLE_TIME_SECONDS,
addActiveTime: Long = ADDITIONAL_ACTIVE_TIME_VALUES_SEC[0]
) {
val shareChatInfo = ShareChatInfo()
shareChatInfo.userId = userId
updateChatShareInfo(shareChatInfo, livePeriod, addActiveTime)
app.telegramHelper.createPrivateChatWithUser(userId, shareChatInfo, shareChatsInfo)
}
fun updateShareDevices(list: List<DeviceBot>) {
shareDevices = list.toHashSet()
}
@ -195,9 +202,7 @@ class TelegramSettings(private val app: TelegramApplication) {
return false
}
fun getShareDeviceNameWithExternalId(externalId: String): String? {
return shareDevices.singleOrNull { it.externalId == externalId }?.deviceName
}
fun getCurrentSharingDevice() = shareDevices.singleOrNull { it.externalId == currentSharingMode }
fun getLastSuccessfulSendTime() = shareChatsInfo.values.maxBy { it.lastSuccessfulSendTimeMs }?.lastSuccessfulSendTimeMs ?: -1
@ -235,9 +240,14 @@ class TelegramSettings(private val app: TelegramApplication) {
fun updateShareInfo(message: TdApi.Message) {
val shareChatInfo = shareChatsInfo[message.chatId]
val content = message.content
if (shareChatInfo != null && content is TdApi.MessageLocation) {
shareChatInfo.currentMessageId = message.id
shareChatInfo.lastSuccessfulLocation = LatLon(content.location.latitude, content.location.longitude)
if (shareChatInfo != null) {
when (content) {
is TdApi.MessageLocation -> shareChatInfo.currentMapMessageId = message.id
is TdApi.MessageText -> {
shareChatInfo.currentTextMessageId = message.id
shareChatInfo.updateTextMessageId++
}
}
shareChatInfo.lastSuccessfulSendTimeMs = Math.max(message.editDate, message.date) * 1000L
}
}
@ -271,6 +281,31 @@ class TelegramSettings(private val app: TelegramApplication) {
}
}
private fun updateChatShareInfo(
shareChatInfo: ShareChatInfo,
livePeriod: Long = DEFAULT_VISIBLE_TIME_SECONDS,
addActiveTime: Long = ADDITIONAL_ACTIVE_TIME_VALUES_SEC[0]
) {
val lp: Long = when {
livePeriod < TelegramHelper.MIN_LOCATION_MESSAGE_LIVE_PERIOD_SEC -> TelegramHelper.MIN_LOCATION_MESSAGE_LIVE_PERIOD_SEC.toLong()
else -> livePeriod
}
val currentTime = System.currentTimeMillis() / 1000
val user = app.telegramHelper.getCurrentUser()
if (user != null && currentSharingMode != user.id.toString() && shareChatInfo.start == -1L) {
shareChatInfo.shouldSendViaBotMessage = true
}
shareChatInfo.start = currentTime
if (shareChatInfo.livePeriod == -1L) {
shareChatInfo.livePeriod = lp
}
shareChatInfo.userSetLivePeriod = lp
shareChatInfo.userSetLivePeriodStart = currentTime
shareChatInfo.currentMessageLimit = currentTime + Math.min(lp, TelegramHelper.MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC.toLong())
shareChatInfo.additionalActiveTime = addActiveTime
}
private fun getNewSharingStatusHistoryItem(): SharingStatus {
return SharingStatus().apply {
statusChangeTime = System.currentTimeMillis()
@ -357,9 +392,14 @@ class TelegramSettings(private val app: TelegramApplication) {
}
fun onDeleteLiveMessages(chatId: Long, messages: List<Long>) {
val currentMessageId = shareChatsInfo[chatId]?.currentMessageId
if (messages.contains(currentMessageId)) {
shareChatsInfo[chatId]?.currentMessageId = -1
val currentMapMessageId = shareChatsInfo[chatId]?.currentMapMessageId
if (messages.contains(currentMapMessageId)) {
shareChatsInfo[chatId]?.currentMapMessageId = -1
}
val currentTextMessageId = shareChatsInfo[chatId]?.currentTextMessageId
if (messages.contains(currentTextMessageId)) {
shareChatsInfo[chatId]?.currentTextMessageId = -1
shareChatsInfo[chatId]?.updateTextMessageId = 1
}
}
@ -383,6 +423,8 @@ class TelegramSettings(private val app: TelegramApplication) {
edit.putLong(STALE_LOC_TIME_KEY, staleLocTime)
edit.putLong(LOC_HISTORY_TIME_KEY, locHistoryTime)
edit.putString(SHARE_TYPE_KEY, shareTypeValue)
edit.putString(APP_TO_CONNECT_PACKAGE_KEY, appToConnectPackage)
edit.putString(LIVE_NOW_SORT_TYPE_KEY, liveNowSortType.name)
@ -433,6 +475,8 @@ class TelegramSettings(private val app: TelegramApplication) {
staleLocTime = prefs.getLong(STALE_LOC_TIME_KEY, staleLocDef)
val locHistoryDef = LOC_HISTORY_VALUES_SEC[LOC_HISTORY_DEFAULT_INDEX]
locHistoryTime = prefs.getLong(LOC_HISTORY_TIME_KEY, locHistoryDef)
val shareTypeDef = SHARE_TYPE_VALUES[SHARE_TYPE_DEFAULT_INDEX]
shareTypeValue = prefs.getString(SHARE_TYPE_KEY, shareTypeDef)
currentSharingMode = prefs.getString(SHARING_MODE_KEY, "")
@ -472,10 +516,13 @@ class TelegramSettings(private val app: TelegramApplication) {
shareChatsInfo.forEach { (chatId, chatInfo) ->
val obj = JSONObject()
obj.put(ShareChatInfo.CHAT_ID_KEY, chatId)
obj.put(ShareChatInfo.USER_ID_KEY, chatInfo.userId)
obj.put(ShareChatInfo.START_KEY, chatInfo.start)
obj.put(ShareChatInfo.LIVE_PERIOD_KEY, chatInfo.livePeriod)
obj.put(ShareChatInfo.LIMIT_KEY, chatInfo.currentMessageLimit)
obj.put(ShareChatInfo.CURRENT_MESSAGE_ID_KEY, chatInfo.currentMessageId)
obj.put(ShareChatInfo.UPDATE_TEXT_MESSAGE_ID_KEY, chatInfo.updateTextMessageId)
obj.put(ShareChatInfo.CURRENT_MAP_MESSAGE_ID_KEY, chatInfo.currentMapMessageId)
obj.put(ShareChatInfo.CURRENT_TEXT_MESSAGE_ID_KEY, chatInfo.currentTextMessageId)
obj.put(ShareChatInfo.USER_SET_LIVE_PERIOD_KEY, chatInfo.userSetLivePeriod)
obj.put(ShareChatInfo.USER_SET_LIVE_PERIOD_START_KEY, chatInfo.userSetLivePeriodStart)
obj.put(ShareChatInfo.LAST_SUCCESSFUL_SEND_TIME_KEY, chatInfo.lastSuccessfulSendTimeMs)
@ -493,10 +540,13 @@ class TelegramSettings(private val app: TelegramApplication) {
val obj = json.getJSONObject(i)
val shareInfo = ShareChatInfo().apply {
chatId = obj.optLong(ShareChatInfo.CHAT_ID_KEY)
userId = obj.optInt(ShareChatInfo.USER_ID_KEY)
start = obj.optLong(ShareChatInfo.START_KEY)
livePeriod = obj.optLong(ShareChatInfo.LIVE_PERIOD_KEY)
currentMessageLimit = obj.optLong(ShareChatInfo.LIMIT_KEY)
currentMessageId = obj.optLong(ShareChatInfo.CURRENT_MESSAGE_ID_KEY)
updateTextMessageId = obj.optInt(ShareChatInfo.UPDATE_TEXT_MESSAGE_ID_KEY)
currentMapMessageId = obj.optLong(ShareChatInfo.CURRENT_MAP_MESSAGE_ID_KEY)
currentTextMessageId = obj.optLong(ShareChatInfo.CURRENT_TEXT_MESSAGE_ID_KEY)
userSetLivePeriod = obj.optLong(ShareChatInfo.USER_SET_LIVE_PERIOD_KEY)
userSetLivePeriodStart = obj.optLong(ShareChatInfo.USER_SET_LIVE_PERIOD_START_KEY)
lastSuccessfulSendTimeMs = obj.optLong(ShareChatInfo.LAST_SUCCESSFUL_SEND_TIME_KEY)
@ -572,6 +622,41 @@ class TelegramSettings(private val app: TelegramApplication) {
}
}
inner class ShareTypePref : DurationPref(
R.drawable.ic_action_location_history,
R.string.send_location_as,
R.string.send_location_as_descr,
emptyList()
) {
override fun getCurrentValue(): String {
return getTextValue(shareTypeValue)
}
override fun setCurrentValue(index: Int) {
val newSharingType = SHARE_TYPE_VALUES[index]
if (shareTypeValue != newSharingType && app.telegramHelper.getCurrentUser()?.id.toString() != currentSharingMode) {
shareChatsInfo.forEach { (_, shareInfo) ->
shareInfo.shouldSendViaBotMessage = true
}
}
shareTypeValue = newSharingType
}
override fun getMenuItems(): List<String> {
return SHARE_TYPE_VALUES.map { getTextValue(it) }
}
private fun getTextValue(shareType: String): String {
return when (shareType) {
SHARE_TYPE_MAP -> app.getString(R.string.shared_string_map)
SHARE_TYPE_TEXT -> app.getString(R.string.shared_string_text)
SHARE_TYPE_MAP_AND_TEXT -> app.getString(R.string.map_and_text)
else -> ""
}
}
}
abstract inner class DurationPref(
@DrawableRes val iconId: Int,
@StringRes val titleId: Int,
@ -583,7 +668,7 @@ class TelegramSettings(private val app: TelegramApplication) {
abstract fun setCurrentValue(index: Int)
fun getMenuItems() = values.map { OsmandFormatter.getFormattedDuration(app, it) }
open fun getMenuItems() = values.map { OsmandFormatter.getFormattedDuration(app, it) }
}
enum class AppConnect(
@ -738,13 +823,15 @@ class TelegramSettings(private val app: TelegramApplication) {
class ShareChatInfo {
var chatId = -1L
var userId = -1
var start = -1L
var livePeriod = -1L
var updateTextMessageId = 1
var currentMessageLimit = -1L
var currentMessageId = -1L
var currentMapMessageId = -1L
var currentTextMessageId = -1L
var userSetLivePeriod = -1L
var userSetLivePeriodStart = -1L
var lastSuccessfulLocation: LatLon? = null
var lastSuccessfulSendTimeMs = -1L
var shouldDeletePreviousMessage = false
var shouldSendViaBotMessage = false
@ -767,10 +854,13 @@ class TelegramSettings(private val app: TelegramApplication) {
companion object {
internal const val CHAT_ID_KEY = "chatId"
internal const val USER_ID_KEY = "userId"
internal const val START_KEY = "start"
internal const val LIVE_PERIOD_KEY = "livePeriod"
internal const val LIMIT_KEY = "limit"
internal const val CURRENT_MESSAGE_ID_KEY = "currentMessageId"
internal const val UPDATE_TEXT_MESSAGE_ID_KEY = "updateTextMessageId"
internal const val CURRENT_MAP_MESSAGE_ID_KEY = "currentMapMessageId"
internal const val CURRENT_TEXT_MESSAGE_ID_KEY = "currentTextMessageId"
internal const val USER_SET_LIVE_PERIOD_KEY = "userSetLivePeriod"
internal const val USER_SET_LIVE_PERIOD_START_KEY = "userSetLivePeriodStart"
internal const val LAST_SUCCESSFUL_SEND_TIME_KEY = "lastSuccessfulSendTime"

View file

@ -2,8 +2,7 @@ package net.osmand.telegram.helpers
import net.osmand.Location
import net.osmand.PlatformUtil
import net.osmand.telegram.TelegramApplication
import net.osmand.telegram.TelegramSettings
import net.osmand.telegram.*
import net.osmand.telegram.notifications.TelegramNotification.NotificationType
import net.osmand.telegram.utils.AndroidNetworkUtils
import net.osmand.telegram.utils.BASE_URL
@ -57,9 +56,16 @@ class ShareLocationHelper(private val app: TelegramApplication) {
val sharingMode = app.settings.currentSharingMode
if (user != null && sharingMode == user.id.toString()) {
app.telegramHelper.sendLiveLocationMessage(chatsShareInfo, latitude, longitude)
when (app.settings.shareTypeValue) {
SHARE_TYPE_MAP -> app.telegramHelper.sendLiveLocationMessage(chatsShareInfo, latitude, longitude)
SHARE_TYPE_TEXT -> app.telegramHelper.sendLiveLocationText(chatsShareInfo, location)
SHARE_TYPE_MAP_AND_TEXT -> {
app.telegramHelper.sendLiveLocationMessage(chatsShareInfo, latitude, longitude)
app.telegramHelper.sendLiveLocationText(chatsShareInfo, location)
}
}
} else if (sharingMode.isNotEmpty()) {
val url = "$BASE_URL/device/$sharingMode/send?lat=$latitude&lon=$longitude"
val url = getDeviceSharingUrl(location,sharingMode)
AndroidNetworkUtils.sendRequestAsync(app, url, null, "Send Location", false, false,
object : AndroidNetworkUtils.OnRequestResultListener {
override fun onResult(result: String?) {
@ -150,6 +156,24 @@ class ShareLocationHelper(private val app: TelegramApplication) {
refreshNotification()
}
private fun getDeviceSharingUrl(loc: Location, sharingMode: String): String {
val url = "$BASE_URL/device/$sharingMode/send?lat=${loc.latitude}&lon=${loc.longitude}"
val builder = StringBuilder(url)
if (loc.hasBearing() && loc.bearing != 0.0f) {
builder.append("&azi=${loc.bearing}")
}
if (loc.hasSpeed() && loc.speed != 0.0f) {
builder.append("&spd=${loc.speed}")
}
if (loc.hasAltitude() && loc.altitude != 0.0) {
builder.append("&alt=${loc.altitude}")
}
if (loc.hasAccuracy() && loc.accuracy != 0.0f) {
builder.append("&hdop=${loc.accuracy}")
}
return builder.toString()
}
private fun updateShareInfoSuccessfulSendTime(result: String?, chatsShareInfo: Map<Long, TelegramSettings.ShareChatInfo>) {
if (result != null) {
try {
@ -167,11 +191,11 @@ class ShareLocationHelper(private val app: TelegramApplication) {
}
private fun checkAndSendViaBotMessages(chatsShareInfo: Map<Long, TelegramSettings.ShareChatInfo>, location: TdApi.Location, osmandBot: TdApi.User) {
val deviceName = app.settings.getShareDeviceNameWithExternalId(app.settings.currentSharingMode)
if (deviceName != null) {
val device = app.settings.getCurrentSharingDevice()
if (device != null) {
chatsShareInfo.forEach { (_, shareInfo) ->
if (shareInfo.shouldSendViaBotMessage) {
app.telegramHelper.sendViaBotLocationMessage(osmandBot.id, shareInfo, location, deviceName)
app.telegramHelper.sendViaBotLocationMessage(osmandBot.id, shareInfo, location, device,app.settings.shareTypeValue)
shareInfo.shouldSendViaBotMessage = false
}
}

View file

@ -9,6 +9,7 @@ import net.osmand.aidl.maplayer.point.AMapPoint
import net.osmand.telegram.R
import net.osmand.telegram.TelegramApplication
import net.osmand.telegram.helpers.TelegramHelper.MessageOsmAndBotLocation
import net.osmand.telegram.helpers.TelegramHelper.MessageUserTextLocation
import net.osmand.telegram.helpers.TelegramUiHelper.ListItem
import net.osmand.telegram.utils.AndroidUtils
import org.drinkless.td.libcore.telegram.TdApi
@ -79,7 +80,7 @@ class ShowLocationHelper(private val app: TelegramApplication) {
val content = message.content
val date = telegramHelper.getLastUpdatedTime(message)
val stale = System.currentTimeMillis() / 1000 - date > app.settings.staleLocTime
if (chatTitle != null && content is TdApi.MessageLocation) {
if (chatTitle != null && (content is TdApi.MessageLocation || (content is MessageUserTextLocation && content.isValid()))) {
var userName = ""
var photoPath: String? = null
val user = telegramHelper.getUser(message.senderUserId)
@ -102,12 +103,19 @@ class ShowLocationHelper(private val app: TelegramApplication) {
}
setupMapLayer()
val params = generatePointParams(photoPath, stale)
if (update) {
osmandAidlHelper.updateMapPoint(MAP_LAYER_ID, "${chatId}_${message.senderUserId}", userName, userName,
chatTitle, Color.WHITE, ALatLon(content.location.latitude, content.location.longitude), null, params)
} else {
osmandAidlHelper.addMapPoint(MAP_LAYER_ID, "${chatId}_${message.senderUserId}", userName, userName,
chatTitle, Color.WHITE, ALatLon(content.location.latitude, content.location.longitude), null, params)
val aLatLon = when (content) {
is TdApi.MessageLocation -> ALatLon(content.location.latitude, content.location.longitude)
is MessageUserTextLocation -> ALatLon(content.lat, content.lon)
else -> null
}
if (aLatLon != null) {
if (update) {
osmandAidlHelper.updateMapPoint(MAP_LAYER_ID, "${chatId}_${message.senderUserId}", userName, userName,
chatTitle, Color.WHITE, aLatLon, null, params)
} else {
osmandAidlHelper.addMapPoint(MAP_LAYER_ID, "${chatId}_${message.senderUserId}", userName, userName,
chatTitle, Color.WHITE, aLatLon, null, params)
}
}
} else if (chatTitle != null && content is MessageOsmAndBotLocation && content.isValid()) {
val name = content.name
@ -242,7 +250,7 @@ class ShowLocationHelper(private val app: TelegramApplication) {
private fun removeMapPoint(chatId: Long, message: TdApi.Message) {
val content = message.content
if (content is TdApi.MessageLocation) {
if (content is TdApi.MessageLocation || content is MessageUserTextLocation) {
osmandAidlHelper.removeMapPoint(MAP_LAYER_ID, "${chatId}_${message.senderUserId}")
} else if (content is MessageOsmAndBotLocation) {
osmandAidlHelper.removeMapPoint(MAP_LAYER_ID, "${chatId}_${content.name}")

View file

@ -1,11 +1,17 @@
package net.osmand.telegram.helpers
import android.text.TextUtils
import net.osmand.Location
import net.osmand.PlatformUtil
import net.osmand.telegram.SHARE_TYPE_MAP
import net.osmand.telegram.SHARE_TYPE_MAP_AND_TEXT
import net.osmand.telegram.SHARE_TYPE_TEXT
import net.osmand.telegram.TelegramSettings
import net.osmand.telegram.helpers.TelegramHelper.TelegramAuthenticationParameterType.*
import net.osmand.telegram.utils.BASE_SHARING_URL
import net.osmand.telegram.utils.GRAYSCALE_PHOTOS_DIR
import net.osmand.telegram.utils.GRAYSCALE_PHOTOS_EXT
import net.osmand.util.GeoPointParserUtil
import org.drinkless.td.libcore.telegram.Client
import org.drinkless.td.libcore.telegram.Client.ResultHandler
import org.drinkless.td.libcore.telegram.TdApi
@ -34,7 +40,13 @@ class TelegramHelper private constructor() {
private const val LOCATION_PREFIX = "Location: "
private const val LAST_LOCATION_PREFIX = "Last location: "
private const val UPDATED_PREFIX = "Updated: "
private const val USER_TEXT_LOCATION_TITLE = "\uD83D\uDDFA OsmAnd sharing:"
private const val ALTITUDE_PREFIX = "Altitude: "
private const val SPEED_PREFIX = "Speed: "
private const val HDOP_PREFIX = "Horizontal precision: "
private const val NOW = "now"
private const val FEW_SECONDS_AGO = "few seconds ago"
private const val SECONDS_AGO_SUFFIX = " seconds ago"
private const val MINUTES_AGO_SUFFIX = " minutes ago"
@ -68,6 +80,7 @@ class TelegramHelper private constructor() {
var lastTelegramUpdateTime: Int = 0
private val users = ConcurrentHashMap<Int, TdApi.User>()
private val contacts = ConcurrentHashMap<Int, TdApi.User>()
private val basicGroups = ConcurrentHashMap<Int, TdApi.BasicGroup>()
private val supergroups = ConcurrentHashMap<Int, TdApi.Supergroup>()
private val secretChats = ConcurrentHashMap<Int, TdApi.SecretChat>()
@ -141,6 +154,8 @@ class TelegramHelper private constructor() {
fun getChatListIds() = getChatList().map { it.chatId }
fun getContacts() = contacts
fun getChatIds() = chats.keys().toList()
fun getChat(id: Long) = chats[id]
@ -197,10 +212,10 @@ class TelegramHelper private constructor() {
fun getLastUpdatedTime(message: TdApi.Message): Int {
val content = message.content
return if (content is MessageOsmAndBotLocation) {
content.lastUpdated
} else {
Math.max(message.editDate, message.date)
return when (content) {
is MessageOsmAndBotLocation -> content.lastUpdated
is MessageUserTextLocation -> content.lastUpdated
else -> Math.max(message.editDate, message.date)
}
}
@ -237,6 +252,7 @@ class TelegramHelper private constructor() {
fun onTelegramChatsRead()
fun onTelegramChatsChanged()
fun onTelegramChatChanged(chat: TdApi.Chat)
fun onTelegramChatCreated(chat: TdApi.Chat)
fun onTelegramUserChanged(user: TdApi.User)
fun onTelegramError(code: Int, message: String)
}
@ -387,7 +403,12 @@ class TelegramHelper private constructor() {
fun hasGrayscaleUserPhoto(userId: Int): Boolean {
return File("$appDir/$GRAYSCALE_PHOTOS_DIR$userId$GRAYSCALE_PHOTOS_EXT").exists()
}
private fun isUserLocationMessage(message: TdApi.Message): Boolean {
val cont = message.content
return (cont is MessageUserTextLocation || cont is TdApi.MessageLocation)
}
private fun hasLocalUserPhoto(user: TdApi.User): Boolean {
val localPhoto = user.profilePhoto?.small?.local
return if (localPhoto != null) {
@ -485,9 +506,9 @@ class TelegramHelper private constructor() {
}
}
fun sendViaBotLocationMessage(userId: Int, shareInfo: TelegramSettings.ShareChatInfo, location: TdApi.Location, query: String) {
fun sendViaBotLocationMessage(userId: Int, shareInfo: TelegramSettings.ShareChatInfo, location: TdApi.Location, device: TelegramSettings.DeviceBot, shareType:String) {
log.debug("sendViaBotLocationMessage - ${shareInfo.chatId}")
client?.send(TdApi.GetInlineQueryResults(userId, shareInfo.chatId, location, query, "")) { obj ->
client?.send(TdApi.GetInlineQueryResults(userId, shareInfo.chatId, location, device.deviceName, "")) { obj ->
when (obj.constructor) {
TdApi.Error.CONSTRUCTOR -> {
val error = obj as TdApi.Error
@ -498,7 +519,7 @@ class TelegramHelper private constructor() {
}
}
TdApi.InlineQueryResults.CONSTRUCTOR -> {
sendViaBotMessageFromQueryResults(shareInfo, obj as TdApi.InlineQueryResults, query)
sendViaBotMessageFromQueryResults(shareInfo, obj as TdApi.InlineQueryResults, device.externalId, shareType)
}
}
}
@ -507,16 +528,26 @@ class TelegramHelper private constructor() {
private fun sendViaBotMessageFromQueryResults(
shareInfo: TelegramSettings.ShareChatInfo,
inlineQueryResults: TdApi.InlineQueryResults,
query: String
deviceId: String,
shareType: String
) {
val queryResults = inlineQueryResults.results.asList()
if (queryResults.isNotEmpty()) {
val resultArticle = queryResults.firstOrNull {
(it is TdApi.InlineQueryResultArticle && it.id.startsWith("t") && it.title == query)
val resultArticles = mutableListOf<TdApi.InlineQueryResultArticle>()
queryResults.forEach {
if (it is TdApi.InlineQueryResultArticle && it.id.substring(1) == deviceId) {
val textLocationArticle = it.id.startsWith("t")
val mapLocationArticle = it.id.startsWith("m")
if (shareType == SHARE_TYPE_MAP && mapLocationArticle
|| shareType == SHARE_TYPE_TEXT && textLocationArticle
|| shareType == SHARE_TYPE_MAP_AND_TEXT && (textLocationArticle || mapLocationArticle)) {
resultArticles.add(it)
}
}
}
if (resultArticle != null && resultArticle is TdApi.InlineQueryResultArticle) {
resultArticles.forEach {
client?.send(TdApi.SendInlineQueryResultMessage(shareInfo.chatId, 0, true,
true, inlineQueryResults.inlineQueryId, resultArticle.id)) { obj ->
true, inlineQueryResults.inlineQueryId, it.id)) { obj ->
handleLiveLocationMessageUpdate(obj, shareInfo)
}
}
@ -561,6 +592,73 @@ class TelegramHelper private constructor() {
}
}
private fun requestContacts(){
client?.send(TdApi.GetContacts()) { obj ->
when (obj.constructor) {
TdApi.Error.CONSTRUCTOR -> {
val error = obj as TdApi.Error
if (error.code != IGNORED_ERROR_CODE) {
listener?.onTelegramError(error.code, error.message)
}
}
TdApi.Users.CONSTRUCTOR -> {
val usersIds = obj as TdApi.Users
usersIds.userIds.forEach {
requestUser(it)
}
}
}
}
}
private fun requestUser(id: Int) {
client?.send(TdApi.GetUser(id)) { obj ->
when (obj.constructor) {
TdApi.Error.CONSTRUCTOR -> {
val error = obj as TdApi.Error
if (error.code != IGNORED_ERROR_CODE) {
listener?.onTelegramError(error.code, error.message)
}
}
TdApi.User.CONSTRUCTOR -> {
val user = obj as TdApi.User
contacts[user.id] = user
if (!hasLocalUserPhoto(user) && hasRemoteUserPhoto(user)) {
requestUserPhoto(user)
}
}
}
}
}
fun createPrivateChatWithUser(
userId: Int,
shareInfo: TelegramSettings.ShareChatInfo,
shareChatsInfo: ConcurrentHashMap<Long, TelegramSettings.ShareChatInfo>
) {
client?.send(TdApi.CreatePrivateChat(userId, false)) { obj ->
when (obj.constructor) {
TdApi.Error.CONSTRUCTOR -> {
val error = obj as TdApi.Error
needRefreshActiveLiveLocationMessages = true
if (error.code == MESSAGE_CANNOT_BE_EDITED_ERROR_CODE) {
shareInfo.shouldDeletePreviousMessage = true
} else if (error.code != IGNORED_ERROR_CODE) {
shareInfo.hasSharingError = true
outgoingMessagesListeners.forEach {
it.onSendLiveLocationError(error.code, error.message)
}
}
}
TdApi.Chat.CONSTRUCTOR -> {
shareInfo.chatId = (obj as TdApi.Chat).id
shareChatsInfo[shareInfo.chatId] = shareInfo
listener?.onTelegramChatCreated(obj)
}
}
}
}
fun loadMessage(chatId: Long, messageId: Long) {
requestMessage(chatId, messageId, this@TelegramHelper::addNewMessage)
}
@ -580,7 +678,11 @@ class TelegramHelper private constructor() {
val viaBot = isOsmAndBot(message.viaBotUserId)
val oldContent = message.content
if (oldContent is TdApi.MessageText) {
message.content = parseOsmAndBotLocation(oldContent.text.text)
if (oldContent.text.text.startsWith(DEVICE_PREFIX)) {
message.content = parseTextLocation(oldContent.text)
} else if (oldContent.text.text.startsWith(USER_TEXT_LOCATION_TITLE)) {
message.content = parseTextLocation(oldContent.text, false)
}
} else if (oldContent is TdApi.MessageLocation && (fromBot || viaBot)) {
message.content = parseOsmAndBotLocation(message)
}
@ -609,7 +711,7 @@ class TelegramHelper private constructor() {
}
}
}
} else if (sameSender) {
} else if (sameSender && isUserLocationMessage(message) && isUserLocationMessage(newMessage)) {
iterator.remove()
}
}
@ -638,9 +740,9 @@ class TelegramHelper private constructor() {
}
fun stopSendingLiveLocationToChat(shareInfo: TelegramSettings.ShareChatInfo) {
if (shareInfo.currentMessageId != -1L && shareInfo.chatId != -1L) {
if (shareInfo.currentMapMessageId != -1L && shareInfo.chatId != -1L) {
client?.send(
TdApi.EditMessageLiveLocation(shareInfo.chatId, shareInfo.currentMessageId, null, null)) { obj ->
TdApi.EditMessageLiveLocation(shareInfo.chatId, shareInfo.currentMapMessageId, null, null)) { obj ->
handleLiveLocationMessageUpdate(obj, shareInfo)
}
}
@ -683,20 +785,28 @@ class TelegramHelper private constructor() {
}
}
private fun recreateLiveLocationMessage(shareInfo: TelegramSettings.ShareChatInfo, content: TdApi.InputMessageLocation) {
if (shareInfo.currentMessageId != -1L && shareInfo.chatId != -1L) {
log.info("recreateLiveLocationMessage - $shareInfo.currentMessageId")
private fun recreateLiveLocationMessage(
shareInfo: TelegramSettings.ShareChatInfo,
content: TdApi.InputMessageContent
) {
if (shareInfo.chatId != -1L) {
val array = LongArray(1)
array[0] = shareInfo.currentMessageId
client?.send(TdApi.DeleteMessages(shareInfo.chatId, array, true)) { obj ->
when (obj.constructor) {
TdApi.Ok.CONSTRUCTOR -> sendNewLiveLocationMessage(shareInfo, content)
TdApi.Error.CONSTRUCTOR -> {
val error = obj as TdApi.Error
if (error.code != IGNORED_ERROR_CODE) {
needRefreshActiveLiveLocationMessages = true
outgoingMessagesListeners.forEach {
it.onSendLiveLocationError(error.code, error.message)
if (content is TdApi.InputMessageLocation) {
array[0] = shareInfo.currentMapMessageId
} else if (content is TdApi.InputMessageLocation) {
array[0] = shareInfo.currentTextMessageId
}
if (array[0] != 0L) {
client?.send(TdApi.DeleteMessages(shareInfo.chatId, array, true)) { obj ->
when (obj.constructor) {
TdApi.Ok.CONSTRUCTOR -> sendNewLiveLocationMessage(shareInfo, content)
TdApi.Error.CONSTRUCTOR -> {
val error = obj as TdApi.Error
if (error.code != IGNORED_ERROR_CODE) {
needRefreshActiveLiveLocationMessages = true
outgoingMessagesListeners.forEach {
it.onSendLiveLocationError(error.code, error.message)
}
}
}
}
@ -706,7 +816,7 @@ class TelegramHelper private constructor() {
needRefreshActiveLiveLocationMessages = true
}
private fun sendNewLiveLocationMessage(shareInfo: TelegramSettings.ShareChatInfo, content: TdApi.InputMessageLocation) {
private fun sendNewLiveLocationMessage(shareInfo: TelegramSettings.ShareChatInfo, content: TdApi.InputMessageContent) {
needRefreshActiveLiveLocationMessages = true
log.info("sendNewLiveLocationMessage")
client?.send(
@ -728,12 +838,12 @@ class TelegramHelper private constructor() {
shareInfo.livePeriod.toInt()
}
val content = TdApi.InputMessageLocation(location, livePeriod)
val msgId = shareInfo.currentMessageId
val msgId = shareInfo.currentMapMessageId
if (msgId != -1L) {
if (shareInfo.shouldDeletePreviousMessage) {
recreateLiveLocationMessage(shareInfo, content)
shareInfo.shouldDeletePreviousMessage = false
shareInfo.currentMessageId = -1
shareInfo.currentMapMessageId = -1
} else {
log.info("EditMessageLiveLocation - $msgId")
client?.send(
@ -780,6 +890,94 @@ class TelegramHelper private constructor() {
}
}
fun sendLiveLocationText(chatsShareInfo: Map<Long, TelegramSettings.ShareChatInfo>, location: Location) {
chatsShareInfo.forEach { (chatId, shareInfo) ->
if (shareInfo.getChatLiveMessageExpireTime() <= 0) {
return@forEach
}
val msgId = shareInfo.currentTextMessageId
if (msgId == -1L) {
shareInfo.updateTextMessageId = 1
}
val content = getTextMessageContent(shareInfo.updateTextMessageId, location)
if (msgId != -1L) {
if (shareInfo.shouldDeletePreviousMessage) {
recreateLiveLocationMessage(shareInfo, content)
shareInfo.shouldDeletePreviousMessage = false
shareInfo.currentTextMessageId = -1
shareInfo.updateTextMessageId = 1
} else {
client?.send(TdApi.EditMessageText(chatId, msgId, null, content)) { obj ->
handleLiveLocationMessageUpdate(obj, shareInfo)
}
}
} else {
client?.send(TdApi.SendMessage(chatId, 0, false, false, null, content)) { obj ->
handleLiveLocationMessageUpdate(obj, shareInfo)
}
}
}
}
private fun formatLocation(sig: Location): String {
return String.format(Locale.US, "%.5f, %.5f", sig.latitude, sig.longitude)
}
private fun formatTime(ti: Long, now: Boolean): String {
val dt = Date(ti)
val current = System.currentTimeMillis() / 1000
val tm = ti / 1000
return when {
current - tm < 10 -> if (now) NOW else FEW_SECONDS_AGO
current - tm < 50 -> (current - tm).toString() + SECONDS_AGO_SUFFIX
current - tm < 60 * 60 * 2 -> ((current - tm) / 60).toString() + MINUTES_AGO_SUFFIX
current - tm < 60 * 60 * 24 -> ((current - tm) / (60 * 60)).toString() + HOURS_AGO_SUFFIX
else -> UTC_DATE_FORMAT.format(dt) + " " + UTC_TIME_FORMAT.format(dt) + UTC_FORMAT_SUFFIX
}
}
private fun formatFullTime(ti: Long): String {
val dt = Date(ti)
return UTC_DATE_FORMAT.format(dt) + " " + UTC_TIME_FORMAT.format(dt) + " UTC"
}
private fun getTextMessageContent(updateId: Int, location: Location): TdApi.InputMessageText {
val entities = mutableListOf<TdApi.TextEntity>()
val builder = StringBuilder()
val locationMessage = formatLocation(location)
val locationTime = formatTime(location.time, true)
builder.append("$USER_TEXT_LOCATION_TITLE\n")
entities.add(TdApi.TextEntity(builder.lastIndex, LOCATION_PREFIX.length, TdApi.TextEntityTypeBold()))
builder.append(LOCATION_PREFIX)
entities.add(TdApi.TextEntity(builder.length, locationMessage.length,
TdApi.TextEntityTypeTextUrl("$BASE_SHARING_URL?lat=${location.latitude}&lon=${location.longitude}")))
builder.append("$locationMessage ($locationTime)\n")
if (location.hasAltitude() && location.altitude != 0.0) {
entities.add(TdApi.TextEntity(builder.lastIndex, ALTITUDE_PREFIX.length, TdApi.TextEntityTypeBold()))
builder.append(String.format(Locale.US, "$ALTITUDE_PREFIX%.1f m\n", location.altitude))
}
if (location.hasSpeed() && location.speed > 0) {
entities.add(TdApi.TextEntity(builder.lastIndex, SPEED_PREFIX.length, TdApi.TextEntityTypeBold()))
builder.append(String.format(Locale.US, "$SPEED_PREFIX%.1f m/s\n", location.speed))
}
if (location.hasAccuracy() && location.accuracy != 0.0f && location.speed == 0.0f) {
entities.add(TdApi.TextEntity(builder.lastIndex, HDOP_PREFIX.length, TdApi.TextEntityTypeBold()))
builder.append(String.format(Locale.US, "$HDOP_PREFIX%d m\n", location.accuracy.toInt()))
}
if (updateId == 0) {
builder.append(String.format("$UPDATED_PREFIX%s\n", formatFullTime(location.time)))
} else {
builder.append(String.format("$UPDATED_PREFIX%s (%d)\n", formatFullTime(location.time), updateId))
}
val textMessage = builder.toString().trim()
return TdApi.InputMessageText(TdApi.FormattedText(textMessage, entities.toTypedArray()), false, true)
}
/**
* @chatId Id of the chat
* @message Text of the message
@ -896,6 +1094,7 @@ class TelegramHelper private constructor() {
if (haveAuthorization) {
requestChats(true)
requestCurrentUser()
requestContacts()
}
}
val newAuthState = getTelegramAuthorizationState()
@ -915,9 +1114,10 @@ class TelegramHelper private constructor() {
return false
}
val content = content
val isUserTextLocation = (content is TdApi.MessageText) && content.text.text.startsWith(USER_TEXT_LOCATION_TITLE)
return when (content) {
is TdApi.MessageLocation -> true
is TdApi.MessageText -> (isOsmAndBot) && content.text.text.startsWith(DEVICE_PREFIX)
is TdApi.MessageText -> (isOsmAndBot) && content.text.text.startsWith(DEVICE_PREFIX) || (isUserTextLocation && senderUserId != currentUser?.id)
else -> false
}
}
@ -942,13 +1142,16 @@ class TelegramHelper private constructor() {
}
}
private fun parseOsmAndBotLocation(text: String): MessageOsmAndBotLocation {
val res = MessageOsmAndBotLocation()
private fun parseTextLocation(text: TdApi.FormattedText, botLocation: Boolean = true): MessageLocation {
val res = if (botLocation) MessageOsmAndBotLocation() else MessageUserTextLocation()
var locationNA = false
for (s in text.lines()) {
for (s in text.text.lines()) {
when {
s.startsWith(DEVICE_PREFIX) -> {
res.name = s.removePrefix(DEVICE_PREFIX)
if (res is MessageOsmAndBotLocation) {
res.name = s.removePrefix(DEVICE_PREFIX)
}
}
s.startsWith(LOCATION_PREFIX) || s.startsWith(LAST_LOCATION_PREFIX) -> {
var locStr: String
@ -965,7 +1168,15 @@ class TelegramHelper private constructor() {
parse = false
}
}
if (parse) {
val urlTextEntity = text.entities.firstOrNull { it.type is TdApi.TextEntityTypeTextUrl }
if (urlTextEntity != null && urlTextEntity.offset == text.text.indexOf(locStr)) {
val url = (urlTextEntity.type as TdApi.TextEntityTypeTextUrl).url
val point: GeoPointParserUtil.GeoParsedPoint? = GeoPointParserUtil.parse(url)
if (point != null) {
res.lat = point.latitude
res.lon = point.longitude
}
} else if (parse) {
try {
val (latS, lonS) = locStr.split(" ")
val updatedS = locStr.substring(locStr.indexOf("("), locStr.length)
@ -1029,10 +1240,8 @@ class TelegramHelper private constructor() {
return 0
}
class MessageOsmAndBotLocation : TdApi.MessageContent() {
abstract class MessageLocation : TdApi.MessageContent() {
var name: String = ""
internal set
var lat: Double = Double.NaN
internal set
var lon: Double = Double.NaN
@ -1042,7 +1251,21 @@ class TelegramHelper private constructor() {
override fun getConstructor() = -1
fun isValid() = name != "" && lat != Double.NaN && lon != Double.NaN
abstract fun isValid(): Boolean
}
class MessageOsmAndBotLocation : MessageLocation() {
var name: String = ""
internal set
override fun isValid() = name != "" && lat != Double.NaN && lon != Double.NaN
}
class MessageUserTextLocation : MessageLocation() {
override fun isValid() = lat != Double.NaN && lon != Double.NaN
}
class OrderedChat internal constructor(internal val order: Long, internal val chatId: Long, internal val isChannel: Boolean) : Comparable<OrderedChat> {
@ -1085,6 +1308,9 @@ class TelegramHelper private constructor() {
val updateUser = obj as TdApi.UpdateUser
val user = updateUser.user
users[updateUser.user.id] = user
if (user.outgoingLink is TdApi.LinkStateIsContact) {
contacts[user.id] = user
}
if (isOsmAndBot(user.id)) {
osmandBot = user
}
@ -1256,8 +1482,10 @@ class TelegramHelper private constructor() {
synchronized(message) {
lastTelegramUpdateTime = Math.max(message.date, message.editDate)
val newContent = updateMessageContent.newContent
val fromBot = isOsmAndBot(message.senderUserId)
val viaBot = isOsmAndBot(message.viaBotUserId)
message.content = if (newContent is TdApi.MessageText) {
parseOsmAndBotLocation(newContent.text.text)
parseTextLocation(newContent.text, (fromBot || viaBot))
} else if (newContent is TdApi.MessageLocation &&
(isOsmAndBot(message.senderUserId) || isOsmAndBot(message.viaBotUserId))) {
parseOsmAndBotLocationContent(message.content as MessageOsmAndBotLocation, newContent)
@ -1377,6 +1605,13 @@ class TelegramHelper private constructor() {
fullInfoUpdatesListeners.forEach { it.onSupergroupFullInfoUpdated(id, info) }
}
}
TdApi.UpdateMessageSendSucceeded.CONSTRUCTOR -> {
val udateMessageSendSucceeded = obj as TdApi.UpdateMessageSendSucceeded
val message = udateMessageSendSucceeded.message
outgoingMessagesListeners.forEach {
it.onUpdateMessages(listOf(message))
}
}
}
}
}

View file

@ -7,6 +7,7 @@ import net.osmand.data.LatLon
import net.osmand.telegram.R
import net.osmand.telegram.TelegramApplication
import net.osmand.telegram.helpers.TelegramHelper.MessageOsmAndBotLocation
import net.osmand.telegram.helpers.TelegramHelper.MessageUserTextLocation
import org.drinkless.td.libcore.telegram.TdApi
object TelegramUiHelper {
@ -67,6 +68,8 @@ object TelegramUiHelper {
val content = message.content
if (content is TdApi.MessageLocation) {
res.latLon = LatLon(content.location.latitude, content.location.longitude)
} else if (content is MessageUserTextLocation) {
res.latLon = LatLon(content.lat, content.lon)
}
}
if (user != null) {
@ -106,6 +109,7 @@ object TelegramUiHelper {
val content = message.content
return when (content) {
is MessageOsmAndBotLocation -> botMessageToLocationItem(chat, content)
is MessageUserTextLocation -> locationMessageToLocationItem(helper, chat, message)
is TdApi.MessageLocation -> locationMessageToLocationItem(helper, chat, message)
else -> null
}
@ -120,6 +124,7 @@ object TelegramUiHelper {
return when (content) {
is MessageOsmAndBotLocation -> botMessageToChatItem(helper, chat, content)
is TdApi.MessageLocation -> locationMessageToChatItem(helper, chat, message)
is MessageUserTextLocation -> locationMessageToChatItem(helper, chat, message)
else -> null
}
}
@ -148,12 +153,16 @@ object TelegramUiHelper {
message: TdApi.Message
): LocationItem? {
val user = helper.getUser(message.senderUserId) ?: return null
val content = message.content as TdApi.MessageLocation
val content = message.content
return LocationItem().apply {
chatId = chat.id
chatTitle = chat.title
name = TelegramUiHelper.getUserName(user)
latLon = LatLon(content.location.latitude, content.location.longitude)
latLon = when (content) {
is TdApi.MessageLocation -> LatLon(content.location.latitude, content.location.longitude)
is MessageUserTextLocation -> LatLon(content.lat, content.lon)
else -> null
}
photoPath = helper.getUserPhotoPath(user)
grayscalePhotoPath = helper.getUserGreyPhotoPath(user)
placeholderId = R.drawable.img_user_picture
@ -190,12 +199,16 @@ object TelegramUiHelper {
message: TdApi.Message
): ChatItem? {
val user = helper.getUser(message.senderUserId) ?: return null
val content = message.content as TdApi.MessageLocation
val content = message.content
return ChatItem().apply {
chatId = chat.id
chatTitle = chat.title
name = TelegramUiHelper.getUserName(user)
latLon = LatLon(content.location.latitude, content.location.longitude)
latLon = when (content) {
is TdApi.MessageLocation -> LatLon(content.location.latitude, content.location.longitude)
is MessageUserTextLocation -> LatLon(content.lat, content.lon)
else -> null
}
if (helper.isGroup(chat)) {
photoPath = helper.getUserPhotoPath(user)
groupPhotoPath = chat.photo?.small?.local?.path

View file

@ -171,6 +171,10 @@ class LiveNowTabFragment : Fragment(), TelegramListener, TelegramIncomingMessage
updateList()
}
override fun onTelegramChatCreated(chat: TdApi.Chat) {
updateList()
}
override fun onTelegramUserChanged(user: TdApi.User) {
updateList()
}

View file

@ -254,6 +254,12 @@ class MainActivity : AppCompatActivity(), TelegramListener, ActionButtonsListene
}
}
override fun onTelegramChatCreated(chat: TdApi.Chat) {
runOnUi {
listeners.forEach { it.get()?.onTelegramChatCreated(chat) }
}
}
override fun onTelegramUserChanged(user: TdApi.User) {
val photoPath = telegramHelper.getUserPhotoPath(user)
if (photoPath != null) {

View file

@ -28,6 +28,7 @@ import net.osmand.telegram.utils.OsmandFormatter
import org.drinkless.td.libcore.telegram.TdApi
private const val SELECTED_CHATS_KEY = "selected_chats"
private const val SELECTED_CHATS_USERS = "selected_users"
private const val SHARE_LOCATION_CHAT = 1
private const val DEFAULT_CHAT = 0
@ -71,6 +72,7 @@ class MyLocationTabFragment : Fragment(), TelegramListener {
private lateinit var appBarOutlineProvider: ViewOutlineProvider
private val selectedChats = HashSet<Long>()
private val selectedUsers = HashSet<Long>()
private var actionButtonsListener: ActionButtonsListener? = null
@ -96,10 +98,15 @@ class MyLocationTabFragment : Fragment(), TelegramListener {
sharingMode = settings.hasAnyChatToShareLocation()
savedInstanceState?.apply {
selectedChats.addAll(getLongArray(SELECTED_CHATS_KEY).toSet())
if (selectedChats.isNotEmpty()) {
actionButtonsListener?.switchButtonsVisibility(true)
val chatsArray = getLongArray(SELECTED_CHATS_KEY)
val usersArray = getLongArray(SELECTED_CHATS_KEY)
if (chatsArray != null) {
selectedChats.addAll(chatsArray.toSet())
}
if (usersArray != null) {
selectedUsers.addAll(usersArray.toSet())
}
actionButtonsListener?.switchButtonsVisibility((selectedUsers.isNotEmpty() || selectedChats.isNotEmpty()))
}
val mainView = inflater.inflate(R.layout.fragment_my_location_tab, container, false)
@ -191,7 +198,7 @@ class MyLocationTabFragment : Fragment(), TelegramListener {
mainView.findViewById<View>(R.id.stop_all_sharing_row).setOnClickListener {
fragmentManager?.also { fm ->
DisableSharingBottomSheet.showInstance(fm, this, adapter.chats.size)
DisableSharingBottomSheet.showInstance(fm, this, adapter.items.size)
}
}
@ -236,6 +243,7 @@ class MyLocationTabFragment : Fragment(), TelegramListener {
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putLongArray(SELECTED_CHATS_KEY, selectedChats.toLongArray())
outState.putLongArray(SELECTED_CHATS_USERS, selectedUsers.toLongArray())
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
@ -274,7 +282,7 @@ class MyLocationTabFragment : Fragment(), TelegramListener {
TelegramHelper.TelegramAuthorizationState.LOGGING_OUT,
TelegramHelper.TelegramAuthorizationState.CLOSED,
TelegramHelper.TelegramAuthorizationState.UNKNOWN -> {
adapter.chats = mutableListOf()
adapter.items = mutableListOf()
}
else -> Unit
}
@ -292,6 +300,11 @@ class MyLocationTabFragment : Fragment(), TelegramListener {
updateContent()
}
override fun onTelegramChatCreated(chat: TdApi.Chat) {
sharingMode = settings.hasAnyChatToShareLocation()
updateContent()
}
override fun onTelegramUserChanged(user: TdApi.User) {
if (user.id == telegramHelper.getCurrentUser()?.id) {
updateCurrentUserPhoto()
@ -303,9 +316,9 @@ class MyLocationTabFragment : Fragment(), TelegramListener {
}
fun onPrimaryBtnClick() {
if (selectedChats.isNotEmpty()) {
if (selectedChats.isNotEmpty() || selectedUsers.isNotEmpty()) {
val fm = fragmentManager ?: return
SetTimeDialogFragment.showInstance(fm, selectedChats, this)
SetTimeDialogFragment.showInstance(fm, selectedChats, selectedUsers, this)
}
}
@ -351,6 +364,7 @@ class MyLocationTabFragment : Fragment(), TelegramListener {
private fun clearSelection() {
selectedChats.clear()
selectedUsers.clear()
adapter.notifyDataSetChanged()
actionButtonsListener?.switchButtonsVisibility(false)
}
@ -453,8 +467,10 @@ class MyLocationTabFragment : Fragment(), TelegramListener {
}
private fun updateList() {
val items: MutableList<TdApi.Object> = mutableListOf()
val chats: MutableList<TdApi.Chat> = mutableListOf()
val currentUser = telegramHelper.getCurrentUser()
val contacts = telegramHelper.getContacts()
val chatList = if (sharingMode) {
settings.getShareLocationChats()
} else {
@ -473,27 +489,55 @@ class MyLocationTabFragment : Fragment(), TelegramListener {
chats.add(chat)
}
}
items.addAll(chats)
if (!sharingMode) {
for (user in contacts.values) {
val containsInChats = chats.any { telegramHelper.getUserIdFromChatType(it.type) == user.id }
if ((!sharingMode && settings.isSharingLocationToUser(user.id)) || user.id == currentUser?.id || containsInChats) {
continue
}
items.add(user)
}
}
if (sharingMode && settings.hasAnyChatToShareLocation()) {
adapter.chats = sortAdapterItems(chats)
adapter.items = sortAdapterItems(items)
} else {
adapter.chats = chats
adapter.items = items
}
}
private fun sortAdapterItems(list: MutableList<TdApi.Chat>): MutableList<TdApi.Chat> {
list.sortWith(Comparator<TdApi.Chat> { o1, o2 -> o1.title.compareTo(o2.title) })
private fun sortAdapterItems(list: MutableList<TdApi.Object>): MutableList<TdApi.Object> {
list.sortWith(Comparator<TdApi.Object> { o1, o2 ->
val title1 = when (o1) {
is TdApi.Chat -> o1.title
is TdApi.User -> TelegramUiHelper.getUserName(o1)
else -> ""
}
val title2 = when (o2) {
is TdApi.Chat -> o2.title
is TdApi.User -> TelegramUiHelper.getUserName(o2)
else -> ""
}
title1.compareTo(title2)
})
return list
}
inner class MyLocationListAdapter : RecyclerView.Adapter<MyLocationListAdapter.BaseViewHolder>() {
var chats = mutableListOf<TdApi.Chat>()
var items = mutableListOf<TdApi.Object>()
set(value) {
field = value
notifyDataSetChanged()
}
override fun getItemViewType(position: Int): Int {
return if (settings.isSharingLocationToChat(chats[position].id) && sharingMode) {
val item = items[position]
val id = when (item) {
is TdApi.Chat -> item.id
is TdApi.User -> item.id.toLong()
else -> -1
}
return if (settings.isSharingLocationToChat(id) && sharingMode) {
SHARE_LOCATION_CHAT
} else {
DEFAULT_CHAT
@ -518,14 +562,34 @@ class MyLocationTabFragment : Fragment(), TelegramListener {
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
val chat = chats[position]
val lastItem = position == itemCount - 1
val placeholderId = if (telegramHelper.isGroup(chat)) R.drawable.img_group_picture else R.drawable.img_user_picture
val live = settings.isSharingLocationToChat(chat.id)
val shareInfo = settings.getChatsShareInfo()[chat.id]
val item = items[position]
val isChat = item is TdApi.Chat
val itemId = if (isChat) {
(item as TdApi.Chat).id
} else {
(item as TdApi.User).id.toLong()
}
TelegramUiHelper.setupPhoto(app, holder.icon, chat.photo?.small?.local?.path, placeholderId, false)
holder.title?.text = chat.title
val lastItem = position == itemCount - 1
val placeholderId = if (isChat && telegramHelper.isGroup(item as TdApi.Chat)) R.drawable.img_group_picture else R.drawable.img_user_picture
val live = (isChat && settings.isSharingLocationToChat(itemId))
val shareInfo = if (isChat) settings.getChatsShareInfo()[itemId] else null
val photoPath = when (item) {
is TdApi.Chat -> item.photo?.small?.local?.path
is TdApi.User -> item.profilePhoto?.small?.local?.path
else -> null
}
TelegramUiHelper.setupPhoto(app, holder.icon, photoPath, placeholderId, false)
val title = when (item) {
is TdApi.Chat -> item.title
is TdApi.User -> TelegramUiHelper.getUserName(item)
else -> null
}
holder.title?.text = title
if (holder is ChatViewHolder) {
holder.description?.visibility = View.GONE
@ -535,21 +599,33 @@ class MyLocationTabFragment : Fragment(), TelegramListener {
holder.checkBox?.apply {
visibility = View.VISIBLE
setOnCheckedChangeListener(null)
isChecked = selectedChats.contains(chat.id)
isChecked = if (isChat) {
selectedChats.contains(itemId)
} else {
selectedUsers.contains(itemId)
}
setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
selectedChats.add(chat.id)
if (isChat) {
selectedChats.add(itemId)
} else {
selectedUsers.add(itemId)
}
} else {
selectedChats.remove(chat.id)
if (isChat) {
selectedChats.remove(itemId)
} else {
selectedUsers.remove(itemId)
}
}
actionButtonsListener?.switchButtonsVisibility(selectedChats.isNotEmpty())
actionButtonsListener?.switchButtonsVisibility(selectedChats.isNotEmpty() || selectedUsers.isNotEmpty())
}
}
}
holder.bottomShadow?.visibility = if (lastItem) View.VISIBLE else View.GONE
holder.itemView.setOnClickListener {
if (live) {
settings.shareLocationToChat(chat.id, false)
settings.shareLocationToChat(itemId, false)
shareLocationHelper.stopSharingLocation()
notifyItemChanged(position)
} else {
@ -563,11 +639,11 @@ class MyLocationTabFragment : Fragment(), TelegramListener {
isChecked = live
setOnCheckedChangeListener { _, isChecked ->
if (!isChecked) {
settings.shareLocationToChat(chat.id, false)
settings.shareLocationToChat(itemId, false)
if (shareInfo != null) {
telegramHelper.stopSendingLiveLocationToChat(shareInfo)
}
removeItem(chat)
removeItem(item)
}
}
}
@ -591,7 +667,11 @@ class MyLocationTabFragment : Fragment(), TelegramListener {
val expireTime = shareInfo?.getChatLiveMessageExpireTime() ?: 0
val newLivePeriod = expireTime + (shareInfo?.additionalActiveTime ?: ADDITIONAL_ACTIVE_TIME_VALUES_SEC[0])
val nextAdditionalActiveTime = shareInfo?.getNextAdditionalActiveTime() ?: ADDITIONAL_ACTIVE_TIME_VALUES_SEC[1]
settings.shareLocationToChat(chat.id, true, newLivePeriod, nextAdditionalActiveTime)
if (isChat) {
settings.shareLocationToChat(itemId, true, newLivePeriod, nextAdditionalActiveTime)
} else {
settings.shareLocationToUser(itemId.toInt(), newLivePeriod, nextAdditionalActiveTime)
}
notifyItemChanged(position)
}
}
@ -616,9 +696,9 @@ class MyLocationTabFragment : Fragment(), TelegramListener {
private fun getStopSharingVisibility(expiresIn: Long) = if (expiresIn > 0) View.VISIBLE else View.INVISIBLE
private fun removeItem(chat: TdApi.Chat) {
chats.remove(chat)
if (chats.isEmpty()) {
private fun removeItem(chat: TdApi.Object) {
items.remove(chat)
if (items.isEmpty()) {
sharingMode = false
updateContent()
shareLocationHelper.stopSharingLocation()
@ -627,7 +707,7 @@ class MyLocationTabFragment : Fragment(), TelegramListener {
}
}
override fun getItemCount() = chats.size
override fun getItemCount() = items.size
abstract inner class BaseViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val icon: ImageView? = view.findViewById(R.id.icon)

View file

@ -14,8 +14,8 @@ import android.widget.TextView
import net.osmand.Location
import net.osmand.data.LatLon
import net.osmand.telegram.R
import net.osmand.telegram.TelegramLocationProvider.TelegramLocationListener
import net.osmand.telegram.TelegramLocationProvider.TelegramCompassListener
import net.osmand.telegram.TelegramLocationProvider.TelegramLocationListener
import net.osmand.telegram.helpers.ShareLocationHelper
import net.osmand.telegram.helpers.TelegramUiHelper
import net.osmand.telegram.ui.SetTimeDialogFragment.SetTimeListAdapter.ChatViewHolder
@ -36,6 +36,7 @@ class SetTimeDialogFragment : BaseDialogFragment(), TelegramLocationListener, Te
private lateinit var timeForAllValue: TextView
private val chatLivePeriods = HashMap<Long, Long>()
private val userLivePeriods = HashMap<Long, Long>()
private var location: Location? = null
private var heading: Float? = null
@ -90,6 +91,9 @@ class SetTimeDialogFragment : BaseDialogFragment(), TelegramLocationListener, Te
chatLivePeriods.forEach { (chatId, livePeriod) ->
settings.shareLocationToChat(chatId, true, livePeriod)
}
userLivePeriods.forEach { (userId, livePeriod) ->
settings.shareLocationToUser(userId.toInt(), livePeriod)
}
app.shareLocationHelper.startSharingLocation()
targetFragment?.also {
it.onActivityResult(targetRequestCode, LOCATION_SHARED_REQUEST_CODE, null)
@ -121,7 +125,13 @@ class SetTimeDialogFragment : BaseDialogFragment(), TelegramLocationListener, Te
chats.add(id)
chats.add(livePeriod)
}
val users = mutableListOf<Long>()
for ((id, livePeriod) in userLivePeriods) {
users.add(id)
users.add(livePeriod)
}
outState.putLongArray(CHATS_KEY, chats.toLongArray())
outState.putLongArray(USERS_KEY, users.toLongArray())
}
override fun updateLocation(location: Location?) {
@ -167,17 +177,27 @@ class SetTimeDialogFragment : BaseDialogFragment(), TelegramLocationListener, Te
private fun readFromBundle(bundle: Bundle?) {
chatLivePeriods.clear()
userLivePeriods.clear()
bundle?.getLongArray(CHATS_KEY)?.also {
for (i in 0 until it.size step 2) {
val livePeriod = settings.getChatLivePeriod(it[i])
chatLivePeriods[it[i]] = livePeriod ?: it[i + 1]
}
}
bundle?.getLongArray(USERS_KEY)?.also {
for (j in 0 until it.size step 2) {
val livePeriod = settings.getChatLivePeriod(it[j])
userLivePeriods[it[j]] = livePeriod ?: it[j + 1]
}
}
}
private fun getTimeForAll(useDefValue: Boolean = false): Long {
val returnVal = if (useDefValue) DEFAULT_VISIBLE_TIME_SECONDS else NO_VALUE
val iterator = chatLivePeriods.values.iterator()
val allTime = mutableListOf<Long>()
allTime.addAll(chatLivePeriods.values)
allTime.addAll(userLivePeriods.values)
val iterator = allTime.iterator()
if (!iterator.hasNext()) {
return returnVal
}
@ -202,7 +222,7 @@ class SetTimeDialogFragment : BaseDialogFragment(), TelegramLocationListener, Te
}
}
private fun selectDuration(id: Long? = null) {
private fun selectDuration(id: Long? = null, isChat: Boolean = true) {
val timeForAll = getTimeForAll(true)
val defSeconds = if (id == null) timeForAll else chatLivePeriods[id] ?: timeForAll
val (defHours, defMinutes) = secondsToHoursAndMinutes(defSeconds)
@ -213,11 +233,18 @@ class SetTimeDialogFragment : BaseDialogFragment(), TelegramLocationListener, Te
TimeUnit.MINUTES.toSeconds(minutes.toLong())
if (seconds >= ShareLocationHelper.MIN_LOCATION_MESSAGE_LIVE_PERIOD_SEC) {
if (id != null) {
chatLivePeriods[id] = seconds
if (isChat) {
chatLivePeriods[id] = seconds
} else {
userLivePeriods[id] = seconds
}
} else {
chatLivePeriods.keys.forEach {
chatLivePeriods[it] = seconds
}
userLivePeriods.keys.forEach {
userLivePeriods[it] = seconds
}
}
updateTimeForAllRow()
adapter.notifyDataSetChanged()
@ -242,17 +269,19 @@ class SetTimeDialogFragment : BaseDialogFragment(), TelegramLocationListener, Te
}
private fun updateList() {
val chats: MutableList<TdApi.Chat> = mutableListOf()
val items: MutableList<TdApi.Object> = mutableListOf()
telegramHelper.getChatList().filter { chatLivePeriods.keys.contains(it.chatId) }
.forEach { orderedChat ->
telegramHelper.getChat(orderedChat.chatId)?.also { chats.add(it) }
telegramHelper.getChat(orderedChat.chatId)?.also { items.add(it) }
}
adapter.chats = chats
telegramHelper.getContacts().values.filter { userLivePeriods.keys.contains(it.id.toLong()) }
.forEach { user -> items.add(user) }
adapter.items = items
}
inner class SetTimeListAdapter : RecyclerView.Adapter<ChatViewHolder>() {
var chats: List<TdApi.Chat> = emptyList()
var items: List<TdApi.Object> = emptyList()
set(value) {
field = value
notifyDataSetChanged()
@ -265,18 +294,38 @@ class SetTimeDialogFragment : BaseDialogFragment(), TelegramLocationListener, Te
}
override fun onBindViewHolder(holder: ChatViewHolder, position: Int) {
val chat = chats[position]
val placeholderId = if (telegramHelper.isGroup(chat)) R.drawable.img_group_picture else R.drawable.img_user_picture
val item = items[position]
val isChat = item is TdApi.Chat
val itemId = if (isChat) {
(item as TdApi.Chat).id
} else {
(item as TdApi.User).id.toLong()
}
TelegramUiHelper.setupPhoto(app, holder.icon, chat.photo?.small?.local?.path, placeholderId, false)
holder.title?.text = chat.title
val placeholderId = if (isChat && telegramHelper.isGroup((item as TdApi.Chat))) R.drawable.img_group_picture else R.drawable.img_user_picture
if (telegramHelper.isGroup(chat)) {
val photoPath = when (item) {
is TdApi.Chat -> item.photo?.small?.local?.path
is TdApi.User -> item.profilePhoto?.small?.local?.path
else -> null
}
TelegramUiHelper.setupPhoto(app, holder.icon, photoPath, placeholderId, false)
val title = when (item) {
is TdApi.Chat -> item.title
is TdApi.User -> TelegramUiHelper.getUserName(item)
else -> null
}
holder.title?.text = title
if (isChat && telegramHelper.isGroup((item as TdApi.Chat))) {
holder.locationViewContainer?.visibility = View.GONE
holder.description?.visibility = View.VISIBLE
holder.description?.text = getString(R.string.shared_string_group)
} else {
val message = telegramHelper.getChatMessages(chat.id).firstOrNull()
val message = telegramHelper.getChatMessages(itemId).firstOrNull()
val content = message?.content
if (message != null && content is TdApi.MessageLocation && (location != null && content.location != null)) {
val lastUpdated = telegramHelper.getLastUpdatedTime(message)
@ -301,15 +350,19 @@ class SetTimeDialogFragment : BaseDialogFragment(), TelegramLocationListener, Te
holder.textInArea?.apply {
visibility = View.VISIBLE
chatLivePeriods[chat.id]?.also { text = formatLivePeriod(it) }
if (isChat) {
chatLivePeriods[itemId]?.also { text = formatLivePeriod(it) }
} else {
userLivePeriods[itemId]?.also { text = formatLivePeriod(it) }
}
}
holder.bottomShadow?.visibility = View.GONE
holder.itemView.setOnClickListener {
selectDuration(chat.id)
selectDuration(itemId, isChat)
}
}
override fun getItemCount() = chats.size
override fun getItemCount() = items.size
inner class ChatViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
val icon: ImageView? = view.findViewById(R.id.icon)
@ -329,18 +382,31 @@ class SetTimeDialogFragment : BaseDialogFragment(), TelegramLocationListener, Te
private const val TAG = "SetTimeDialogFragment"
private const val CHATS_KEY = "chats_key"
private const val USERS_KEY = "users_key"
private const val DEFAULT_VISIBLE_TIME_SECONDS = 60 * 60L // 1 hour
private const val NO_VALUE = -1L
fun showInstance(fm: FragmentManager, chatIds: Set<Long>, target: Fragment): Boolean {
fun showInstance(fm: FragmentManager, chatIds: Set<Long>, usersIds: Set<Long>, target: Fragment): Boolean {
return try {
val chats = mutableListOf<Long>()
for (id in chatIds) {
chats.add(id)
chats.add(DEFAULT_VISIBLE_TIME_SECONDS)
}
val users = mutableListOf<Long>()
for (id in usersIds) {
users.add(id)
users.add(DEFAULT_VISIBLE_TIME_SECONDS)
}
SetTimeDialogFragment().apply {
arguments = Bundle().apply { putLongArray(CHATS_KEY, chats.toLongArray()) }
arguments = Bundle().apply {
if (chats.isNotEmpty()) {
putLongArray(CHATS_KEY, chats.toLongArray())
}
if (users.isNotEmpty()) {
putLongArray(USERS_KEY, users.toLongArray())
}
}
setTargetFragment(target, LOCATION_SHARED_REQUEST_CODE)
show(fm, TAG)
}

View file

@ -228,7 +228,11 @@ class SettingsDialogFragment : BaseDialogFragment() {
isModal = true
anchorView = valueView
setContentWidth(AndroidUtils.getPopupMenuWidth(ctx, menuList))
height = AndroidUtils.getPopupMenuHeight(ctx)
height = if (menuList.size < 6) {
ListPopupWindow.WRAP_CONTENT
} else {
AndroidUtils.getPopupMenuHeight(ctx)
}
setDropDownGravity(Gravity.END or Gravity.TOP)
setAdapter(ArrayAdapter(ctx, R.layout.popup_list_text_item, menuList))
setOnItemClickListener { _, _, position, _ ->

View file

@ -10,6 +10,8 @@ import org.json.JSONObject
const val BASE_URL = "https://live.osmand.net"
const val BASE_SHARING_URL = "http://osmand.net/go"
object OsmandApiUtils {
private val log = PlatformUtil.getLog(OsmandApiUtils::class.java)

View file

@ -52,10 +52,10 @@ android {
minSdkVersion System.getenv("MIN_SDK_VERSION") ? System.getenv("MIN_SDK_VERSION").toInteger() :
(analytics ? 15 : 14)
targetSdkVersion 26
versionCode 320
versionCode 330
versionCode System.getenv("APK_NUMBER_VERSION") ? System.getenv("APK_NUMBER_VERSION").toInteger() : versionCode
multiDexEnabled true
versionName "3.2.0"
versionName "3.3.0"
versionName System.getenv("APK_VERSION")? System.getenv("APK_VERSION").toString(): versionName
versionName System.getenv("APK_VERSION_SUFFIX")? versionName + System.getenv("APK_VERSION_SUFFIX").toString(): versionName
}

View file

@ -63,7 +63,7 @@
<string name="download_srtm_maps">الخطوط المحيطية (الكنتورية)</string>
<string name="download_regular_maps">الخرائط العادية</string>
<string name="rendering_attr_noAdminboundaries_name">الحدود</string>
<string name="map_widget_max_speed">حدّ السرعة</string>
<string name="map_widget_max_speed">حد السرعة</string>
@ -91,7 +91,7 @@
<string name="poi_filter_food_shop">متاجر المواد الغذائية</string>
<string name="poi_filter_for_tourists">للسياح</string>
<string name="poi_filter_fuel">وقود</string>
<string name="show_warnings_title">إظهار تنبيهات…</string>
<string name="show_warnings_title">إظهار التنبيهات…</string>
<string name="use_compass_navigation">استخدم البوصلة</string>
<string name="avoid_motorway">تجنب الطرق السريعة</string>
@ -208,7 +208,7 @@
<string name="driving_region_europe_asia">أوروبا، آسيا، أمريكا اللاتينية، وغيرها</string>
<string name="driving_region_uk">المملكة المتحدة، الهند وما شابه ذلك</string>
<string name="speak_title">أعلن…</string>
<string name="speak_title">أعلان…</string>
<string name="osb_author_or_password_not_specified">يرجى تحديد مستخدم OSM وكلمة سره في الإعدادات</string>
<string name="clear_intermediate_points">محو الوجهات الوسيطة</string>
<string name="keep_intermediate_points">الحفاظ على الوجهات الوسيطة</string>
@ -326,17 +326,17 @@
<string name="speak_descr">ضبط إعدادات نطق أسماء الشوارع، وتحذيرات المرور (توقيفات الإجبارية، ومطبات السرعة)، و تحذيرات كاميرات السرعة، و السرعة القصوى.</string>
<string name="speak_street_names">نطق أسماء الشوارع ( نطق آلي TTS )</string>
<string name="speak_speed_limit">السرعة القصوى</string>
<string name="speak_street_names">أسماء الشوارع (TTS)</string>
<string name="speak_speed_limit">حد السرعة</string>
<string name="speak_cameras">كاميرات السرعة</string>
<string name="speak_traffic_warnings">تحذيرات حركة المرور</string>
<string name="route_descr_map_location">الخريطة: </string>
<string name="destination_point">الوجهة %1$s</string>
<string name="search_street_in_neighborhood_cities">البحث عن الشارع في المدن المجاورة</string>
<string name="intermediate_items_sort_by_distance">فرز باب لـ باب </string>
<string name="intermediate_items_sort_by_distance">فرز باب لـ باب</string>
<string name="wait_current_task_finished">الرجاء الانتظار حتى انتهاء المهمة الحالية</string>
<string name="available_downloads_left">توفر %1$d ملفات للتنزيل</string>
<string name="cancel_route">ألغ المسار</string>
<string name="cancel_route">صرف المسار</string>
<string name="monitoring_settings">تسجيل الرحلة</string>
<string name="monitoring_settings_descr">قم بضبط كيفية تسجيل الرحلات.</string>
@ -397,9 +397,9 @@
<string name="add_as_last_destination_point">أضف كآخر وجهة وسيطة</string>
<string name="add_as_first_destination_point">أضف كأول وجهة وسيطة</string>
<string name="intermediate_point_too_far">الوجهة %1$s بعيدة جدا من أقرب طريق.</string>
<string name="arrived_at_intermediate_point">إنك قد وصلت إلى وجهتك الوسيطة</string>
<string name="arrived_at_intermediate_point">لقد وصلت إلى وجهتك الوسيطة</string>
<string name="context_menu_item_intermediate_point">أضف كوجهة وسيطة</string>
<string name="map_widget_intermediate_distance">وجهة متوسطة</string>
<string name="map_widget_intermediate_distance">وجهة وسيطة</string>
<string name="ending_point_too_far">نقطة الوصول بعيدة جدا عن أقرب طريق.</string>
<string name="add_tag">أضف وسما</string>
<string name="show_warnings_descr">ضبط تحذيرات المرور ( حدود السرعة، والتوقف القسري، والمطبات الصناعية ) ، وتحذيرات كاميرا السرعة ، ومعلومات الممرات.</string>
@ -409,7 +409,7 @@
<string name="edit_tilesource_successfully">حُفِظ المصدر التّجانبي %1$s بنجاح</string>
<string name="use_compass_navigation_descr">استخدم البوصلة عندما لا تكون هناك أي وجهة محددة.</string>
<string name="auto_zoom_map_descr">ملائمة تقريب الخريطة وفقا لسرعتك (هذا بينما تتزامن الخريطة مع الموقع الحالي).</string>
<string name="auto_zoom_map">تقريب الخريطة تلقائيًا</string>
<string name="auto_zoom_map">تكبير الخريطة تلقائيًا</string>
<string name="snap_to_road_descr">ألصق الموقع إلى الشوارع أثناء الملاحة.</string>
<string name="snap_to_road">الالتصاق بالطريق</string>
<string name="osmand_play_title_30_chars">خرائط OsmAnd وملاحة</string>
@ -625,16 +625,16 @@
<string name="auto_announce_on">تفعيل الإخطار التلقائي</string>
<string name="audionotes_location_not_defined">لم يتم ربط الملاحظة بأي موقع. إضغط على \"استخدام المكان …\" لترفق ملاحظة بالمكان المحدد.</string>
<string name="animate_routing_route_not_calculated">الرجاء، احتسب المسار أولا</string>
<string name="animate_routing_route_not_calculated">الرجاء حساب الطريق أولا</string>
<string name="animate_routing_route">حاكي باستخدام المسار المحتسب </string>
<string name="animate_routing_gpx">حاكي باستخدام مسار GPX</string>
<string name="shared_string_remember_my_choice">تذكر اختياري</string>
<string name="native_app_allocated_memory_descr">إجمالي الذاكرة الأصلية المخصصة من قبل التطبيق %1$s ميغابايت (Dalvik %2$s ميغابايت، الأخرى %3$s ميغابايت). الذاكرة النسبية %4$s ميغابايت (حد الجهاز %5$s ميغابايت Dalvik %6$s ميغابايت).</string>
<string name="app_mode_hiking">مشي</string>
<string name="app_mode_hiking">التنزه</string>
<string name="app_mode_motorcycle">دراجة نارية</string>
<string name="app_mode_boat">قارب</string>
<string name="app_mode_aircraft">الطائرات</string>
<string name="app_mode_aircraft">طائرة</string>
<string name="app_modes_choose_descr">اختر أوضاع الاستخدام لتكون مرئية في التطبيق.</string>
<string name="app_modes_choose">ملامح التطبيق</string>
@ -976,7 +976,7 @@
<string name="hours_ago">ساعة</string>
<string name="minutes_ago">دقيقة</string>
<string name="seconds_ago">ثانية</string>
<string name="always_center_position_on_map">اظهر الموضع في المنتصف دائماً</string>
<string name="always_center_position_on_map">اظهر الموقع في الوسط دائما</string>
<string name="localization_pref_title">التوطين</string>
<string name="lang_hr">الكرواتية</string>
<string name="lang_pt_br">البرتغالية (البرازيل)</string>
@ -1005,11 +1005,11 @@
<string name="route_info">معلومات الطريق</string>
<string name="routing_attr_avoid_toll_name">تجنب الطرق ذات الرسوم</string>
<string name="routing_attr_avoid_toll_description">إجتناب الطرق التي يجب دفع رسوم للمرور فيها.</string>
<string name="routing_attr_avoid_unpaved_name">تجنب الطرق غير المعبّدة</string>
<string name="routing_attr_avoid_toll_description">تجنب الطرق ذات الرسوم</string>
<string name="routing_attr_avoid_unpaved_name">تجنب الطرق غير المعبدة</string>
<string name="routing_attr_avoid_unpaved_description">اجتناب الطرق الترابية والوعرة.</string>
<string name="routing_attr_avoid_ferries_name">تجنب العبّارات</string>
<string name="routing_attr_weight_name">حد الوزن المسموح</string>
<string name="routing_attr_weight_name">الوزن الأقصى</string>
<string name="android_19_location_disabled">منذ نسخة أندرويد كتكات 4.4، لا يمكنك تنزيل أو تحديث الخرائط في مكان التخزين السابق (%s). هل تريد تغيير المكان إلى مكان مسموح ونسخ كل الملفات إليه ؟
ملاحظة 1 : الملفات القديمة ستبقى كما هي دون لمس (ولكن يمكن حذفها يدويا).
@ -1142,7 +1142,7 @@
<string name="get_directions">الاتجاهات</string>
<string name="show_point_options">إستخدام الموقع …</string>
<string name="favorite">مفضلة</string>
<string name="speak_favorites">نقاط مفضلة مجاورة</string>
<string name="speak_favorites">النقاط المفضلة مجاورة</string>
<string name="save_as_favorites_points">إحفظ كمجموعة مفضلة</string>
<string name="add_favorite_dialog_top_text">أدخل اسم المفضلة</string>
<string name="add_favorite_dialog_favourite_added_template">"تم إضافة النقطة المفضلة \'\'{0}\'\' بنجاح."</string>
@ -1221,12 +1221,12 @@
<string name="rendering_attr_tramRoutes_name">خطوط الترام</string>
<string name="rendering_category_routes">طرق</string>
<string name="rendering_category_transport">المواصلات</string>
<string name="rendering_category_others">سمات أخرى للخريطة</string>
<string name="rendering_category_others">ميزات آخرى للخريطة</string>
<string name="map_widget_appearance_rem">العناصر المتبقية</string>
<string name="map_widget_top">شريط المعلومات</string>
<string name="map_widget_right">اللوحة اليمنى</string>
<string name="map_widget_left">اللوحة اليسرى</string>
<string name="search_radius_proximity">ضمن</string>
<string name="search_radius_proximity">في غضون</string>
<string name="anonymous_user">مستخدم مجهول</string>
<string name="logged_as">سجل الدخول بـ %1$s</string>
@ -1285,7 +1285,7 @@
<string name="rendering_attr_hideNonVehicleHighways_name">الطرق السريعة غير المخصصة للمركبات</string>
<string name="rendering_attr_hideText_name">النص</string>
<string name="rendering_attr_buildings15zoom_name">المباني في التقريب 15</string>
<string name="rendering_attr_moreDetailed_name">مزيد من التفاصيل</string>
<string name="rendering_attr_moreDetailed_name">التفاصيل اكثر</string>
<string name="rendering_attr_lessDetailed_name">تفاصيل أقل</string>
<string name="rendering_attr_showSurfaceGrade_name">إظهار جودة الطريق</string>
@ -1327,21 +1327,21 @@
<string name="rendering_attr_busRoutes_name">طرق الحافلات</string>
<string name="rendering_attr_subwayMode_name">سكك مترو الأنفاق</string>
<string name="rendering_attr_shareTaxiRoutes_name">شارك طرق التاكسي</string>
<string name="speed_limit_exceed_message">حدد حد السرعة المسموح به لتلقي تنبيه صوتي ما إذا تجاوزته.</string>
<string name="speed_limit_exceed_message">حدد مجال السرعة المسموح به لتتلقى تنبيه صوتي ما إذا تجاوزته.</string>
<string name="traffic_warning_border_control">مراقبة الحدود</string>
<string name="traffic_warning_payment">كشك الرسوم</string>
<string name="traffic_warning_calming">تخفيف الازدحام</string>
<string name="traffic_warning_speed_camera">كاميرا مراقبة السرعة</string>
<string name="traffic_warning">تنبيه مروري</string>
<string name="speak_poi">POI مجاورة</string>
<string name="traffic_warning_speed_camera">كاميرا السرعة</string>
<string name="traffic_warning">تحذير حركة المرور</string>
<string name="speak_poi">POI المجاورة</string>
<string name="way_alarms">تنبيهات مرورية</string>
<string name="save_track_to_gpx_globally_headline">تسجيل المسار حسب الطلب</string>
<string name="download_additional_maps">حمّل الخرائط المفقودة %1$s (%2$d MB)؟</string>
<string name="rendering_attr_hideAccess_name">قيود الدخول</string>
<string name="rendering_attr_showAccess_name">عرض قيود الدخول</string>
<string name="rendering_attr_showSurfaces_name">عرض سطح الطريق</string>
<string name="rendering_attr_showCycleRoutes_name">عرض الطرق الدائرية</string>
<string name="rendering_attr_showSurfaces_name">إظهار سطح الطريق</string>
<string name="rendering_attr_showCycleRoutes_name">عرض الطرق الخاصة بالدرجات</string>
<string name="delay_navigation_start">ابدأ التوجيه دروان بدوران تلقائيا</string>
@ -1410,14 +1410,14 @@
<string name="calculate_osmand_route_without_internet">احسب قسم طريق OsmAnd بدون إنترنت</string>
<string name="gpx_option_calculate_first_last_segment">احسب طريق OsmAnd لأول وآخر قسمين في الطريق</string>
<string name="use_displayed_track_for_navigation">هل ترغب باستخدام المسار المعروض للملاحة؟</string>
<string name="keep_and_add_destination_point">أضافة كنقطة وجهة لاحقة</string>
<string name="keep_and_add_destination_point">اضافة كنقطة وجهة لاحقة</string>
<string name="select_gpx">اختر GPX…</string>
<string name="route_descr_select_destination">حدد الوجهة</string>
<string name="routing_attr_prefer_motorway_name">تفضيل طرق الدراجات النارية</string>
<string name="routing_attr_prefer_motorway_description">تفضيل طرق الدراجات النارية.</string>
<string name="routing_attr_avoid_ferries_description">تجنب العبّارات المائية.</string>
<string name="routing_attr_prefer_motorway_name">تفضل الطرق السريعة</string>
<string name="routing_attr_prefer_motorway_description">تفضيل الطرق السريعة</string>
<string name="routing_attr_avoid_ferries_description">تجنب العبّارات المائية</string>
<string name="routing_attr_avoid_motorway_name">تجنب الطرق السريعة</string>
<string name="routing_attr_avoid_motorway_description">تجنب طرق الدراجات النارية.</string>
<string name="routing_attr_avoid_motorway_description">تجنب الطرق السريعة</string>
<string name="routing_attr_weight_description">حدد وزن المركبة الأقصى المسموح به على الطرقات.</string>
<string name="copying_osmand_files_descr">نسخ ملفات بيانات OsmAnd إلى المسار الجديد (%s)…</string>
<string name="calculate_osmand_route_gpx">احسب طريق OsmAnd بدون اتصال</string>
@ -1640,7 +1640,7 @@
<string name="lang_es_ar">الإسبانية (الأرجنتين)</string>
<string name="si_nm">ميل بحري</string>
<string name="dark_theme">داكن</string>
<string name="routing_attr_height_name">تحديد الإرتفاع</string>
<string name="routing_attr_height_name">الإرتفاع الاقصى</string>
<string name="default_speed_system_descr">تحديد وحدة السرعة.</string>
<string name="default_speed_system">وحدة السرعة</string>
<string name="plugin_settings">ملحقات</string>
@ -1819,7 +1819,7 @@
<string name="active_markers">العلامات المفعلة</string>
<string name="map_markers">علامات خرائطية</string>
<string name="routing_attr_avoid_borders_name">منع تخطي الحدود</string>
<string name="routing_attr_avoid_borders_name">تجنب عبور الحدود</string>
<string name="impassable_road_desc">حدد الطرق التي تريد تجنبها أثناء التنقل.</string>
<string name="search_categories">فئات</string>
<string name="shared_string_near">قرب</string>
@ -2075,7 +2075,7 @@
<string name="region_maps">خرائط واسعة النطاق</string>
<string name="hillshade_layer_disabled">طبقة التضاريس غير مفعلة</string>
<string name="srtm_plugin_disabled">الخطوط الكفافية معطلة</string>
<string name="nm">nmi</string>
<string name="nm">ميل بحري</string>
<string name="nm_h">ميل بحري/ساعة</string>
<string name="simulate_your_location_descr">محاكاة موقعك باستخدام طريق مقاس أو مسار GPX مسجل.</string>
<string name="routing_attr_avoid_shuttle_train_name">تجنب قطار المدينة</string>
@ -2128,11 +2128,11 @@
<string name="lang_hu_formal">الهنغارية (الرسمية)</string>
<string name="routing_attr_avoid_stairs_name">تجنب الأدراج</string>
<string name="routing_attr_avoid_stairs_description">تجنب الأدراج.</string>
<string name="routing_attr_avoid_stairs_description">تجنب الأدراج</string>
<string name="routing_attr_avoid_borders_description">تجنب عبور الحدود إلى بلد آخر.</string>
<string name="routing_attr_height_description">تحديد ارتفاع السيارة المسموح على الطرق.</string>
<string name="disable_complex_routing_descr">تعطيل 2-مرحلة التوجيه للملاحة بالسيارة.</string>
<string name="rendering_attr_alpineHiking_name">مقياس جبال الألب للتنزه (نادي جبال الألب السويسرية)</string>
<string name="rendering_attr_alpineHiking_name">مقياس جبال الألب للتنزه (SAC)</string>
<string name="rendering_attr_alpineHiking_description">تقديم مسارات وفقا لمقياس SAC.</string>
<string name="rendering_attr_hikingRoutesOSMC_name">غطاء رمز التنزه</string>
<string name="rendering_attr_hikingRoutesOSMC_description">تقديم مسارات وفقا لآثار OSMC.</string>
@ -2810,7 +2810,7 @@
<string name="south_abbreviation">ج</string>
<string name="north_abbreviation">ش</string>
<string name="optional_point_name">الإسم الإختياري للنقطة</string>
<string name="transport_nearby_routes_within">المسارات القريبة مِن</string>
<string name="transport_nearby_routes_within">المسارات القريبة في غضون</string>
<string name="enter_the_file_name">أدخل إسم الملف.</string>
<string name="map_import_error">خطأ أثناء استرجاع الخريطة</string>
<string name="map_imported_successfully">تمت عملية استيراد الخريطة</string>
@ -2911,4 +2911,33 @@
<string name="search_street">البحث عن شارع</string>
<string name="start_search_from_city">اختر المدينة أولا</string>
<string name="shared_string_restore">استعادة</string>
<string name="third_party_application">تطبيق من طرف-ثالث</string>
<string name="keep_passed_markers_descr">العلامات المضافة كمجموعة مفضلة أو نقاط طريق GPX ستظل على الخريطة. اذا كانت المجموعة غير نشطة، العلامات سوف تختفي من الخريطة.</string>
<string name="keep_passed_markers">إبقاء العلامات المتجاوزة على الخريطة</string>
<string name="more_transport_on_stop_hint">هناك المزيد من النقل في هذا الموقف.</string>
<string name="ask_for_location_permission">يرجى إعطاء إذن الموقع للتطبيق لكي يواصل.</string>
<string name="thank_you_for_feedback">شكرا على ردود الفعل</string>
<string name="poi_cannot_be_found">النقطة أو الطريق غير موجود.</string>
<string name="search_no_results_feedback">لا نتائج بحث؟
\nاخبرنا بانطباعك</string>
<string name="increase_search_radius_to">توسيع دائرة البحث بقطر %1$s</string>
<string name="send_search_query">ارسال استفسار البحث؟</string>
<string name="shared_string_world">العالم</string>
<string name="point_deleted">تم حذف %1$s نقطة</string>
<string name="coord_input_edit_point">تحرير نقطة</string>
<string name="coord_input_add_point">إضافة نقطة</string>
<string name="coord_input_save_as_track">حفظ كمسار</string>
<string name="coord_input_save_as_track_descr">لقد أضفت %1$s نقطة. اكتب اسم الملف و اضغط على \"حفظ\".</string>
<string name="error_notification_desc">يرجى إرسال لقطة شاشة من هذا الإخطار الى support@osmand.net</string>
<string name="quick_action_edit_actions">تعديل الإجراءات</string>
<string name="get_osmand_live">احصل على OsmAnd Live لفتح جميع الميزات: تحديثات يومية للخريطة مع تنزيلات غير محدودة، جميع الإضافات المدفوعة والمجانية، Wikipedia ،wikivoyage وأكثر من ذلك بكثير.</string>
<string name="osm_live_subscriptions">الاشتراكات</string>
<string name="default_price_currency_format">%1$.2f %2$s</string>
<string name="read_wikipedia_offline_description">احصل على اشتراك OsmAnd Live لقراءة مقالات Wikipedia و wikivoyage.</string>
<string name="purchase_cancelled_dialog_descr">جدد الاشتراك لتستمر في استخدام كافة الميزات:</string>
<string name="maps_you_need_descr">استنادا إلى المقالات التي قمتم بحفظها، الخرائط التالية موصى بتحميلها:</string>
<string name="paid_app">تطبيق مدفوع</string>
<string name="paid_plugin">أداة مدفوعة</string>
<string name="start_editing_card_image_text">دليل السفر العالمي المجانا الذي بامكان أي شخص التعديل عليه.</string>
</resources>

View file

@ -3168,4 +3168,5 @@ Praparcyjnaj pamiacі %4$s MB (Abmiežavańnie Android %5$s MB, Dalvik %6$s MB).
<string name="osm_live_payment_contribute_descr">Častka dachodu idzie ŭdzieĺnikam OpenStreetMap.</string>
<string name="powered_by_osmand">Pracuje na OsmAnd</string>
<string name="mapillary_menu_title_pano">Pakazać tolki 360° malunki</string>
<string name="osm_live_subscriptions">Padpiski</string>
</resources>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -2251,7 +2251,7 @@ Proportional hukommelse %4$s MB (Android grænse %5$s MB, Dalvik %6$s MB).</stri
\nFor at aktivere alle nye funktioner, er det nødvendigt at genstarte OsmAnd.</string>
<string name="osm_live_region_desc">En del af donationen vil blive sendt til OSM-brugere, som indsender ændringer til kortet i det pågældende område.</string>
<string name="osm_live_subscription_settings">Indstillinger for abonnement</string>
<string name="osm_live_ask_for_purchase">Køb først OsnAnd Live-abonnement</string>
<string name="osm_live_ask_for_purchase">Køb først OsmAnd Live-abonnement</string>
<string name="osm_live_header">"Abonnementet giver timebaserede opdateringer til alle kort i hele verden.
\nEn del af indtægten går tilbage til OSM-fællesskabet og udbetales for hvert OSM-bidrag.
@ -2795,7 +2795,7 @@ Repræsenterer område: %1$s x %2$s</string>
<string name="mapillary_menu_edit_text_hint">Indtast brugernavn</string>
<string name="mapillary_menu_descr_username">Se kun billeder tilføjet af</string>
<string name="mapillary_menu_title_username">Brugernavn</string>
<string name="mapillary_menu_filter_description">Filtrer billeder efter indsender eller dato. Kun aktiv for højere zoomniveauer.</string>
<string name="mapillary_menu_filter_description">Filtrer billeder efter indsender, dato eller type. Kun aktiv for højere zoomniveauer.</string>
<string name="mapillary_menu_descr_dates">Se kun de billeder, der er tilføjet</string>
<string name="shared_string_reload">Genindlæs</string>
<string name="mapillary_menu_descr_tile_cache">Genindlæs kortbrikker for at se opdaterede data.</string>
@ -3222,17 +3222,19 @@ Repræsenterer område: %1$s x %2$s</string>
<string name="keep_passed_markers">Behold passerede markører på kortet</string>
<string name="osm_live_plan_pricing">Plan &amp; Priser</string>
<string name="osm_live_payment_monthly_title">Betal månedligt</string>
<string name="osm_live_payment_3_months_title">Betal én gang i 3 måneder</string>
<string name="osm_live_payment_3_months_title">Betal én gang hver 3. måned</string>
<string name="osm_live_payment_annual_title">Betal en gang om året</string>
<string name="osm_live_payment_month_cost_descr">%1$s / måned</string>
<string name="osm_live_payment_month_cost_descr_ex">%1$.2f %2$s / måned</string>
<string name="osm_live_payment_discount_descr">Spar %1$s!</string>
<string name="osm_live_payment_current_subscription">Nuværende abonnement</string>
<string name="osm_live_payment_renews_monthly">Forny månedlig</string>
<string name="osm_live_payment_renews_quarterly">Forny kvartalsvis</string>
<string name="osm_live_payment_renews_annually">Forny hvert år</string>
<string name="osm_live_payment_renews_monthly">Fornyes månedligt</string>
<string name="osm_live_payment_renews_quarterly">Fornyes kvartalsvis</string>
<string name="osm_live_payment_renews_annually">Fornyes hvert år</string>
<string name="default_price_currency_format">%1$.2f %2$s</string>
<string name="osm_live_payment_header">Vælg en passende betalingsperiode:</string>
<string name="osm_live_payment_contribute_descr">En del af indtægterne går til OpenStreetMap bidragydere.</string>
<string name="osm_live_payment_contribute_descr">En del af indtægterne går til OpenStreetMap bidragsydere.</string>
<string name="powered_by_osmand">Drevet af OsmAnd</string>
<string name="osm_live_subscriptions">Abonnementer</string>
<string name="mapillary_menu_title_pano">Vis kun 360° billeder</string>
</resources>

View file

@ -1200,7 +1200,7 @@
<string name="poi_elevator_no">Ohne Aufzug</string>
<string name="poi_fuel_91ul">Avgas UL 91</string>
<string name="poi_fuel_100ll">Avgas 100 LL</string>
<string name="poi_fuel_100ll">Avgas 100LL</string>
<string name="poi_fuel_autogas">Autogas</string>
<string name="poi_fuel_jeta1">Flugzeug A-1 Kraftstoff</string>
<string name="poi_fuel_adblue">AdBlue</string>

View file

@ -3921,7 +3921,7 @@
<string name="poi_payment_contactless_no">No acepta Pagos sin contacto</string>
<string name="poi_hazard_nuclear">Peligro nuclear</string>
<string name="poi_hazard_erosion">Peligro de erosión</string>
<string name="poi_hazard_erosion">Peligro de derrumbe por erosión</string>
<string name="poi_hazard_avalanche">Peligro de avalancha</string>
<string name="poi_hazard_slippery_road">Camino resbaladizo</string>
<string name="poi_hazard_flood">Peligro de inundación</string>

View file

@ -3680,7 +3680,7 @@
<string name="poi_payment_contactless_no">No acepta Pagos sin contacto</string>
<string name="poi_hazard_nuclear">Peligro nuclear</string>
<string name="poi_hazard_erosion">Peligro de erosión</string>
<string name="poi_hazard_erosion">Peligro de derrumbe por erosión</string>
<string name="poi_hazard_avalanche">Peligro de avalancha</string>
<string name="poi_hazard_slippery_road">Camino resbaladizo</string>
<string name="poi_hazard_flood">Peligro de inundación</string>

View file

@ -3903,7 +3903,7 @@
<string name="poi_payment_contactless_no">Sin contacto: no se acepta</string>
<string name="poi_hazard_nuclear">Peligro nuclear</string>
<string name="poi_hazard_erosion">Peligro de erosión</string>
<string name="poi_hazard_erosion">Peligro de derrumbe por erosión</string>
<string name="poi_hazard_avalanche">Peligro de avalancha</string>
<string name="poi_hazard_slippery_road">Carretera resbaladiza</string>
<string name="poi_hazard_flood">Peligro de inundación</string>

View file

@ -153,7 +153,7 @@
<string name="poi_railway_platform">Tren andena</string>
<string name="poi_halt">Tren geltokia</string>
<string name="poi_subway_entrance">Metro sarrera</string>
<string name="poi_subway_station">Metro geltokia</string>
<string name="poi_subway_station">Bai</string>
<string name="poi_taxi">Taxi geltokia</string>
<string name="poi_aerodrome">Aireportua</string>
@ -444,7 +444,7 @@
<string name="poi_prison">Kartzela</string>
<string name="poi_migration">Migrazioa</string>
<string name="poi_tax_inspection">Ogasun ikuskaritza</string>
<string name="poi_capital">Hiriburua</string>
<string name="poi_capital">Bai</string>
<string name="poi_town">Herria</string>
<string name="poi_neighbourhood">Auzoa</string>
<string name="poi_place_allotments">Baratza komunitarioak</string>
@ -1541,7 +1541,7 @@
<string name="poi_diet_pescetarian_yes">Peszetarianoa</string>
<string name="poi_brewery_additional">Garagardotegiaren izena</string>
<string name="poi_microbrewery_yes">Mikro-garagardotegia</string>
<string name="poi_microbrewery_yes">Bai</string>
<string name="poi_microbrewery_no">Ez da mikro-garagardotegia</string>
<string name="poi_piste_grooming_skating">Patinajea</string>
@ -3878,8 +3878,8 @@
<string name="poi_bulk_purchase">Handizkaria</string>
<string name="poi_substation_type">Mota</string>
<string name="poi_bulk_purchase_yes">Handizkaria: bai</string>
<string name="poi_bulk_purchase_only">Handizkaria: besterik ez</string>
<string name="poi_bulk_purchase_yes">Bai</string>
<string name="poi_bulk_purchase_only">Besterik ez</string>
<string name="poi_substation_transmission">Transmisioa</string>
<string name="poi_substation_distribution">Banaketa</string>
@ -3887,7 +3887,7 @@
<string name="poi_substation_industrial">Industriala</string>
<string name="poi_substation_transition">Trantsizioa</string>
<string name="poi_substation_traction">Trakzioa</string>
<string name="poi_substation_converter"></string>
<string name="poi_substation_converter">Bihurtzailea</string>
<string name="poi_substation_compensation">Konpentsazioa</string>
<string name="poi_substation_compression">Konpresioa</string>

View file

@ -1755,7 +1755,7 @@
<string name="shared_string_dismiss">بی‌خیال</string>
<string name="use_opengl_render">استفاده از رندرگیری OpenGL</string>
<string name="use_opengl_render_descr">از رندرگیری سرعت‌یافتهٔ سخت‌افزاری OpenGL استفاده کن (شاید مصرف باتری افزایش یابد یا روی دستگاه‌های خیلی قدیمی کار نکند).</string>
<string name="lock_screen_request_explanation">%1$s به این مجوز نیاز دارد تا برای صرفه‌جویی در انرژی، بتواند نمایشگر را خاموش کند.</string>
<string name="lock_screen_request_explanation">%1$s به این مجوز نیاز دارد تا برای صرفه‌جویی در انرژی بتواند نمایشگر را خاموش کند.</string>
<string name="wake_on_voice_descr">هنگام نزدیک‌شدن به پیچ، نمایشگر روشن شود (اگر خاموش است).</string>
<string name="shared_string_never">هرگز</string>
<string name="rendering_attr_trolleybusRoutes_name">مسیرهای اتوبوس برقی</string>

View file

@ -3514,4 +3514,5 @@
<string name="poi_nuclear_explosion_type_underground_shaft">Type d\'explosion : sous-terrain, tunnel</string>
<string name="poi_nuclear_explosion_type_space">Type d\'explosion : espace (au delà de 80 km d\'altitude)</string>
<string name="poi_nuclear_explosion_type_underwater">Type d\'explosion : sous-marine</string>
<string name="poi_nuclear_explosion_type_underground_tunnel">Type d\'explosion : sous terre, tunnel</string>
</resources>

View file

@ -2737,7 +2737,7 @@ représentant la zone : %1$s x %2$s</string>
<string name="mapillary_menu_title_tile_cache">Cache des tuiles</string>
<string name="mapillary_menu_descr_dates">Afficher uniquement les images ajoutées</string>
<string name="mapillary_menu_descr_username">Afficher uniquement les images ajoutées par</string>
<string name="mapillary_menu_filter_description">Filtrer les images par date ou nom d\'utilisateur. Le filtre n\'est appliqué qu\'après avoir zoomé.</string>
<string name="mapillary_menu_filter_description">Filtrez les images par date, nom d\'utilisateur ou type. Le filtre n\'est appliqué qu\'après avoir zoomé.</string>
<string name="shared_string_reset">Réinitialiser</string>
<string name="average">Moyenne</string>
<string name="ascent_descent">Croissant / Décroissant</string>
@ -3207,8 +3207,10 @@ représentant la zone : %1$s x %2$s</string>
<string name="osm_live_payment_header">Sélectionnez la fréquence de règlement qui vous convient :</string>
<string name="osm_live_payment_contribute_descr">Une partie des revenus est reversée aux contributeurs OpenStreetMap.</string>
<string name="osm_live_plan_pricing">Plans et Tarifs</string>
<string name="osm_live_payment_renews_monthly">Abonnement mensuel</string>
<string name="osm_live_payment_renews_quarterly">Abonnement trimestriel</string>
<string name="osm_live_payment_renews_annually">Abonnement annuel</string>
<string name="osm_live_payment_renews_monthly">Abonnements mensuels</string>
<string name="osm_live_payment_renews_quarterly">Abonnements trimestriels</string>
<string name="osm_live_payment_renews_annually">Abonnements annuels</string>
<string name="powered_by_osmand">Propulsé par OsmAnd</string>
<string name="osm_live_subscriptions">Abonnements</string>
<string name="mapillary_menu_title_pano">N\'afficher que les images à 360°</string>
</resources>

View file

@ -2272,4 +2272,11 @@
<string name="poi_dispensing_yes">כן</string>
<string name="poi_observatory_type_espionage">שימוש: ריגול</string>
<string name="poi_telescope_usage_espionage">שימוש: ריגול</string>
<string name="poi_telescope_usage_education">שימוש: חינוך</string>
<string name="poi_telescope_usage_research">שימוש: מחקר</string>
<string name="poi_observatory_type_meteorological">מטאורולוגי</string>
<string name="poi_observatory_type_gravitational">כבידתי</string>
</resources>

View file

@ -967,8 +967,8 @@
<string name="poi_collection_times">Tempi di raccolta</string>
<string name="poi_email">Email</string>
<string name="poi_fax">Fax</string>
<string name="facebook">"Facebook "</string>
<string name="twitter">"Twitter "</string>
<string name="facebook">Facebook</string>
<string name="twitter">Twitter</string>
<string name="poi_skype">Skype</string>
<string name="poi_youtube">YouTube</string>
<string name="poi_instagram">Instagram</string>

View file

@ -1733,8 +1733,8 @@
<string name="first_intermediate_dest_description">Додаје прво стајање</string>
<string name="switch_osm_notes_visibility_desc">Прикажи/Сакриј ОСМ белешке на карти.</string>
<string name="shared_string_gpx_file">GPX фајл</string>
<string name="release_3_0">• Ново: Подршка за глобалне туристичке водиче без потребе за интернет конекцијом. Референтни положају су повезани на карти. Иницијални подаци са сајта Wikivoyage.
\n
<string name="release_3_0">• Ново: Подршка за глобалне туристичке водиче без потребе за интернет конекцијом. Референтни положаји су повезани на карти. Иницијални подаци са сајта Wikivoyage.
\n
\n • Википедија: нови изглед, активне везе, слике
\n
\n • Open Track UI: приказ група пролазних тачака

View file

@ -476,7 +476,7 @@ Nyttjat utrymme är {1} MB.
<string name="search_offline_geo_error">Det gick inte att tolka geo-uppsåt \'%s\'.</string>
<string name="search_osm_offline">Sök adress med hjälp av offline-kartor</string>
<string name="system_locale">System</string>
<string name="preferred_locale_descr">Välj visningsspråk (starta om OsmAnd efter språkbyte).</string>
<string name="preferred_locale_descr">Välj visningsspråk (träder i kraft när OsmAnd startas om).</string>
<string name="preferred_locale">Visningsspråk</string>
@ -3041,7 +3041,24 @@ Vänligen tillhandahåll fullständig kod</string>
<string name="send_search_query_description">Vi skickar din sökfråga: <b>\"%1$s\"</b>, såväl som din plats.<br/><br/> Vi samlar inte in personlig information, vi behöver bara sökdata för att förbättra sökalgoritmen.<br/></string>
<string name="third_party_application">Tredjepartsprogram</string>
<string name="search_street">Sök gata</string>
<string name="start_search_from_city">Börja sökning från stad</string>
<string name="start_search_from_city">Välj en stad först</string>
<string name="shared_string_restore">Återställ</string>
<string name="keep_passed_markers">Behåll passerade markörer på kartan</string>
<string name="powered_by_osmand">Drivs av OsmAnd</string>
<string name="osm_live_plan_pricing">Abonnemang &amp; Priser</string>
<string name="osm_live_payment_monthly_title">Betala månadsvis</string>
<string name="osm_live_payment_3_months_title">Betala var 3:e månad</string>
<string name="osm_live_payment_annual_title">Betala en gång om året</string>
<string name="osm_live_payment_month_cost_descr">%1$s / månad</string>
<string name="osm_live_payment_month_cost_descr_ex">%1$.2f %2$s / månad</string>
<string name="osm_live_payment_discount_descr">Spara %1$s!</string>
<string name="osm_live_payment_current_subscription">Ditt nuvarande abonnemang</string>
<string name="osm_live_payment_renews_monthly">Förnyas varje månad</string>
<string name="osm_live_payment_renews_quarterly">Förnyas kvartalsvis</string>
<string name="osm_live_payment_renews_annually">Förnyas årligen</string>
<string name="default_price_currency_format">%1$.2f %2$s</string>
<string name="osm_live_payment_header">Välj en passande betalningsperiod:</string>
<string name="osm_live_payment_contribute_descr">En del av intäkterna går till OpenStreetMap bidragsgivare.</string>
<string name="markers_remove_dialog_msg">Ta bort kartmarkör \'%s\'\?</string>
<string name="edit_map_marker">Redigera kartmarkör</string>
</resources>

View file

@ -523,7 +523,7 @@
<string name="rendering_attr_showRoadMaps_description">选择何时显示仅包含道路的地图:</string>
<string name="rendering_attr_showRoadMaps_name">仅包含道路的地图</string>
<string name="safe_mode_description">在安全模式运行应用(使用较慢的 Android 代码代替原生代码)。</string>
<string name="native_library_not_running">应用正运行在安全模式下(可在设置中关闭)。</string>
<string name="native_library_not_running">应用正运行在安全模式下(可在设置中关闭)。</string>
<string name="background_service_is_enabled_question">OsmAnd 睡眠模式服务仍在运行。是否要关闭?</string>
<string name="close_changeset">关闭修改集</string>
<string name="zxing_barcode_scanner_not_found">ZXing 条形码扫描应用未安装,是否转到应用市场中搜索?</string>
@ -680,13 +680,13 @@
<string name="local_indexes_cat_poi">兴趣点数据</string>
<string name="ttsvoice">TTS语音</string>
<string name="search_offline_clear_search">新搜索</string>
<string name="map_text_size_descr">选择地图上名称的字体大小</string>
<string name="map_text_size_descr">选择地图上名称的字体大小</string>
<string name="map_text_size">地图字体大小</string>
<string name="trace_rendering_descr">显示渲染性能</string>
<string name="trace_rendering_descr">显示渲染性能</string>
<string name="installing_new_resources">解包新数据…</string>
<string name="internet_connection_required_for_online_route">已选择在线导航服务,但网络连接不可用。</string>
<string name="tts_language_not_supported_title">语言不支持</string>
<string name="tts_language_not_supported_title">不支持的语言</string>
<string name="tts_missing_language_data_title">数据丢失</string>
<string name="gpx_option_destination_point">使用当前终点</string>
<string name="voice_stream_notification">提醒语音音频</string>
@ -1424,7 +1424,7 @@
<string name="dash_download_manage">管理</string>
<string name="rendering_attr_trainLightrailRoutes_name">铁路线路</string>
<string name="rendering_category_hide">隐藏</string>
<string name="traffic_warning_pedestrian">人行</string>
<string name="traffic_warning_pedestrian">人行</string>
<string name="rendering_category_routes">线路</string>
<string name="show_railway_warnings">铁道交口</string>
<string name="rendering_category_details">详细信息</string>
@ -1844,7 +1844,7 @@
<string name="traffic_warning_speed_limit">速度限制</string>
<string name="traffic_warning_border_control">边境管制</string>
<string name="traffic_warning_payment">收费站</string>
<string name="traffic_warning_stop">标志</string>
<string name="traffic_warning_stop">标志</string>
<string name="traffic_warning_calming">交通稳静化</string>
<string name="traffic_warning_speed_camera">测速摄像机</string>
<string name="traffic_warning">交通警告</string>
@ -2730,5 +2730,10 @@
<string name="osm_live_payment_month_cost_descr_ex">%1$.2f %2$s / 月</string>
<string name="osm_live_payment_discount_descr">节省 %1$s</string>
<string name="osm_live_payment_current_subscription">您的当前订阅</string>
<string name="open_in_browser_wiki_description"></string>
<string name="open_in_browser_wiki_description">在网页浏览器中查看文章。</string>
<string name="osm_live_subscriptions">订阅</string>
<string name="osm_live_payment_renews_monthly">每月续期</string>
<string name="osm_live_payment_renews_quarterly">每季度续期</string>
<string name="osm_live_payment_renews_annually">每年续期</string>
<string name="third_party_application">第三方应用程序</string>
</resources>