Compare commits
107 commits
Author | SHA1 | Date | |
---|---|---|---|
|
9d39e2f805 | ||
|
3ce4082997 | ||
|
f528ae4b43 | ||
|
ed0f6d6882 | ||
|
f0dc959bdc | ||
|
aa2c111e88 | ||
|
74efa9f2b8 | ||
|
92d59f6521 | ||
|
d1fb9d5129 | ||
|
22237493a6 | ||
|
3754cf7822 | ||
|
2437fb722a | ||
|
8accf25b60 | ||
|
974ad80734 | ||
|
500cb007b8 | ||
|
e6be5dcab5 | ||
|
e2d659e859 | ||
|
603f32df63 | ||
|
c1b416448f | ||
|
42604ac3b6 | ||
|
1ac919b8d3 | ||
|
4de1aa9193 | ||
|
fe9a5c43bd | ||
|
32dff8b9e2 | ||
|
72ca4055fc | ||
|
8950a05446 | ||
|
60b72f53be | ||
|
560c229277 | ||
|
c40eb96bd7 | ||
|
7f795a4e43 | ||
|
eefe1a00ac | ||
|
cb18d45e43 | ||
|
d2fe5b7e3f | ||
|
5670680d1e | ||
|
dd1195b060 | ||
|
d0d60e9d2e | ||
|
0d26b83435 | ||
|
83885c373c | ||
|
3ff1d1a39a | ||
|
5f9e931482 | ||
|
b1d1007cb6 | ||
|
6948315605 | ||
|
81687551e7 | ||
|
da89e737ec | ||
|
8c992bb4a9 | ||
|
0ca18e6d5f | ||
|
d9de7ba4b0 | ||
|
b5a27d99b8 | ||
|
a5f0fa9aac | ||
|
43564125ce | ||
|
1e23993514 | ||
|
f77f499391 | ||
|
fa17db8756 | ||
|
f1f7db7842 | ||
|
2968aad438 | ||
|
396f9354c1 | ||
|
c3818d6121 | ||
|
40c060af26 | ||
|
1629183994 | ||
|
295c6c4df4 | ||
|
fed2e356c3 | ||
|
a7efb09724 | ||
|
f1756768ce | ||
|
13037bacae | ||
|
1c6485ccea | ||
|
a2a7cb77bf | ||
|
7525c925ca | ||
|
2e9238aff8 | ||
|
658b157bb2 | ||
|
ec0ea31b53 | ||
|
d2598612ff | ||
|
6eb665c9a8 | ||
|
cbc4e1a427 | ||
|
558c0f5256 | ||
|
11ddc65409 | ||
|
109d9aa1bb | ||
|
87744e180b | ||
|
0eb64b7ecf | ||
|
cc70fcede4 | ||
|
5497bc4691 | ||
|
e65eb4cb84 | ||
|
551dd084ad | ||
|
43394ce607 | ||
|
8aeb8b16a3 | ||
|
ced9a3555e | ||
|
6bf7da4290 | ||
|
cc152a64f9 | ||
|
13b6b74e13 | ||
|
c71253a8fd | ||
|
54069a1106 | ||
|
adbc331fce | ||
|
2f1e43147d | ||
|
ae2eef75f3 | ||
|
fbec2abd19 | ||
|
0bdf2dfa8a | ||
|
45be6b1919 | ||
|
aa6b04a924 | ||
|
4a6ab0c6bb | ||
|
149b89090b | ||
|
ff7aa932a3 | ||
|
1ec3189a86 | ||
|
e3c257b255 | ||
|
9368b7383a | ||
|
b14f62822b | ||
|
ac3aeacca1 | ||
|
1813a99da1 | ||
|
684571ca34 |
182 changed files with 10462 additions and 3028 deletions
|
@ -16,13 +16,11 @@ import java.util.ArrayList;
|
|||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
|
||||
|
@ -184,9 +182,8 @@ public abstract class MapObject implements Comparable<MapObject> {
|
|||
if (lang != null && lang.length() > 0) {
|
||||
if (lang.equals("en")) {
|
||||
// for some objects like wikipedia, english name is stored 'name' tag
|
||||
if (Algorithms.isEmpty(enName)) {
|
||||
return getEnName(transliterate);
|
||||
}
|
||||
String enName = getEnName(transliterate);
|
||||
return !Algorithms.isEmpty(enName) ? enName : getName();
|
||||
} else {
|
||||
// get name
|
||||
if (names != null) {
|
||||
|
|
|
@ -5,8 +5,11 @@ import net.osmand.data.QuadRect;
|
|||
import net.osmand.util.Algorithms;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class WorldRegion implements Serializable {
|
||||
|
||||
|
@ -212,4 +215,22 @@ public class WorldRegion implements Serializable {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static List<WorldRegion> removeDuplicates(List<WorldRegion> regions) {
|
||||
List<WorldRegion> copy = new ArrayList<>(regions);
|
||||
Set<WorldRegion> duplicates = new HashSet<>();
|
||||
for (int i = 0; i < copy.size() - 1; i++) {
|
||||
WorldRegion r1 = copy.get(i);
|
||||
for (int j = i + 1; j < copy.size(); j++) {
|
||||
WorldRegion r2 = copy.get(j);
|
||||
if (r1.containsRegion(r2)) {
|
||||
duplicates.add(r2);
|
||||
} else if (r2.containsRegion(r1)) {
|
||||
duplicates.add(r1);
|
||||
}
|
||||
}
|
||||
}
|
||||
copy.removeAll(duplicates);
|
||||
return copy;
|
||||
}
|
||||
}
|
|
@ -186,7 +186,7 @@ public class RouteColorize {
|
|||
public List<RouteColorizationPoint> getResult(boolean simplify) {
|
||||
List<RouteColorizationPoint> result = new ArrayList<>();
|
||||
if (simplify) {
|
||||
result = simplify();
|
||||
result = simplify(zoom);
|
||||
} else {
|
||||
for (int i = 0; i < latitudes.length; i++) {
|
||||
result.add(new RouteColorizationPoint(i, latitudes[i], longitudes[i], values[i]));
|
||||
|
@ -200,7 +200,7 @@ public class RouteColorize {
|
|||
|
||||
public int getColorByValue(double value) {
|
||||
if (Double.isNaN(value)) {
|
||||
value = (minValue + maxValue) / 2;
|
||||
value = colorizationType == ColorizationType.SLOPE ? minValue : (minValue + maxValue) / 2;
|
||||
}
|
||||
for (int i = 0; i < palette.length - 1; i++) {
|
||||
if (value == palette[i][VALUE_INDEX])
|
||||
|
@ -242,7 +242,7 @@ public class RouteColorize {
|
|||
return rgbaToDecimal(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
private List<RouteColorizationPoint> simplify() {
|
||||
public List<RouteColorizationPoint> simplify(int zoom) {
|
||||
if (dataList == null) {
|
||||
dataList = new ArrayList<>();
|
||||
for (int i = 0; i < latitudes.length; i++) {
|
||||
|
@ -266,6 +266,8 @@ public class RouteColorize {
|
|||
List<RouteColorizationPoint> sublist = dataList.subList(prevId, currentId);
|
||||
simplified.addAll(getExtremums(sublist));
|
||||
}
|
||||
Node lastSurvivedPoint = result.get(result.size() - 1);
|
||||
simplified.add(dataList.get((int) lastSurvivedPoint.getId()));
|
||||
return simplified;
|
||||
}
|
||||
|
||||
|
|
274
OsmAnd-telegram/res/values-b+sr+Latn/strings.xml
Normal file
274
OsmAnd-telegram/res/values-b+sr+Latn/strings.xml
Normal file
|
@ -0,0 +1,274 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="shared_string_select">Označi</string>
|
||||
<string name="shared_string_enable">Omogući</string>
|
||||
<string name="altitude">Nadmorska visina</string>
|
||||
<string name="shared_string_search">Traži</string>
|
||||
<string name="shared_string_ok">U redu</string>
|
||||
<string name="shared_string_update">Ažuriraj</string>
|
||||
<string name="average_altitude">Prosečna visina</string>
|
||||
<string name="average_speed">Prosečna brzina</string>
|
||||
<string name="shared_string_map">Karta</string>
|
||||
<string name="shared_string_add">Dodaj</string>
|
||||
<string name="shared_string_hide">Sakrij</string>
|
||||
<string name="shared_string_status">Stanje</string>
|
||||
<string name="shared_string_disable">Onemogući</string>
|
||||
<string name="shared_string_save">Sačuvaj</string>
|
||||
<string name="shared_string_name">Ime</string>
|
||||
<string name="shared_string_sort">Sortiraj</string>
|
||||
<string name="shared_string_exit">Izlaz</string>
|
||||
<string name="shared_string_close">Zatvori</string>
|
||||
<string name="shared_string_all">Sve</string>
|
||||
<string name="shared_string_off">Isključeno</string>
|
||||
<string name="shared_string_install">Instaliraj</string>
|
||||
<string name="shared_string_share">Deli</string>
|
||||
<string name="shared_string_back">Nazad</string>
|
||||
<string name="shared_string_continue">Nastavi</string>
|
||||
<string name="shared_string_cancel">Otkaži</string>
|
||||
<string name="shared_string_settings">Postavke</string>
|
||||
<string name="osmand_service">Pozadinski režim</string>
|
||||
<string name="yard">yd</string>
|
||||
<string name="foot">ft</string>
|
||||
<string name="mile">mi</string>
|
||||
<string name="km">km</string>
|
||||
<string name="m">m</string>
|
||||
<string name="nm">nmi</string>
|
||||
<string name="min_mile">min/m</string>
|
||||
<string name="min_km">min/km</string>
|
||||
<string name="m_s">m/s</string>
|
||||
<string name="km_h">km/h</string>
|
||||
<string name="mile_per_hour">mph</string>
|
||||
<string name="si_kmh">Kilometara na sat</string>
|
||||
<string name="si_mph">Milja na sat</string>
|
||||
<string name="si_m_s">Metara u sekundi</string>
|
||||
<string name="si_min_km">Minuta po kilometru</string>
|
||||
<string name="si_min_m">Minuta po milji</string>
|
||||
<string name="si_mi_feet">Milje/stope</string>
|
||||
<string name="si_mi_yard">Milje/jardi</string>
|
||||
<string name="si_km_m">Kilometri/metri</string>
|
||||
<string name="si_nm">Nautičke milje</string>
|
||||
<string name="si_mi_meters">Milje/metri</string>
|
||||
<string name="shared_string_apply">Primeni</string>
|
||||
<string name="shared_string_enabled">Uključen</string>
|
||||
<string name="units_and_formats">Merne jedinice & formatiranja</string>
|
||||
<string name="unit_of_length_descr">Promeni jedinice za dužinu.</string>
|
||||
<string name="unit_of_length">Jedinice dužine</string>
|
||||
<string name="shared_string_appearance">Izgled</string>
|
||||
<string name="timeline">Vremenska linija</string>
|
||||
<string name="live_now">Uživo sada</string>
|
||||
<string name="my_location">Moja lokacija</string>
|
||||
<string name="welcome_descr"><b>OsmAnd Pratioc</b> omogućava vam da delite svoju lokaciju i vidite lokaciju drugih u OsmAndu.<br/><br/>Aplikacija koristi Telegram API, pa vam je potreban Telegram nalog.</string>
|
||||
<string name="shared_string_second_short">sek</string>
|
||||
<string name="shared_string_minute_short">min</string>
|
||||
<string name="shared_string_hour_short">č</string>
|
||||
<string name="si_nm_h">Nautičkih milja na sat (čvorovi)</string>
|
||||
<string name="nm_h">nmi/č</string>
|
||||
<string name="shared_string_welcome">Dobrodošli</string>
|
||||
<string name="shared_string_authorization_descr">Unesite vaš telefonski broj Telegrama u međunarodnom formatu</string>
|
||||
<string name="shared_string_authorization">Autorizacija</string>
|
||||
<string name="active_chats">Aktivna ćaskanja</string>
|
||||
<string name="show_users_on_map">Prikažite korisnike na mapi</string>
|
||||
<string name="install_osmand">Instalirajte OsmAnd</string>
|
||||
<string name="install_osmand_dialog_message">Prvo morate instalirati besplatnu ili plaćenu verziju OsmAnda</string>
|
||||
<string name="osmand_logo">Logo OsmAnda</string>
|
||||
<string name="process_service">Usluga OsmAnd Pratioca</string>
|
||||
<string name="sharing_location">Deljenje lokacije</string>
|
||||
<string name="share_location">Deli lokaciju</string>
|
||||
<string name="shared_string_distance">Rastojanje</string>
|
||||
<string name="osmand_service_descr">OsmAnd Pratioc radi u pozadini sa isključenim ekranom.</string>
|
||||
<string name="location_service_no_gps_available">Izaberite jednog od dobavljača lokacije da bi deliti vašu lokaciju.</string>
|
||||
<string name="gps_not_available">Uključite „Lokaciju“ u sistemskim podešavanjima</string>
|
||||
<string name="no_location_permission">Aplikaciji nedostaje dozvola za pristup podacima o lokaciji.</string>
|
||||
<string name="not_logged_in">Niste prijavljeni</string>
|
||||
<string name="gps_network_not_enabled">Uključiti „Lokaciju“\?</string>
|
||||
<string name="closing">Zatvaranje</string>
|
||||
<string name="logging_out">Odjavljivanje</string>
|
||||
<string name="initialization">Pokretanje</string>
|
||||
<string name="shared_string_logout">Odjaviti se</string>
|
||||
<string name="shared_string_login">Prijavite se</string>
|
||||
<string name="password_descr">Telegram lozinka</string>
|
||||
<string name="enter_password">Unesite lozinku</string>
|
||||
<string name="authentication_code_descr">Telegram vam je poslao kod za OsmAnd radi prijave na vaš nalog.</string>
|
||||
<string name="authentication_code">Validacioni kod</string>
|
||||
<string name="enter_code">Unesite kod</string>
|
||||
<string name="shared_string_password">Lozinka</string>
|
||||
<string name="phone_number_descr">Broj telefona u međunarodnom formatu</string>
|
||||
<string name="phone_number_title">Broj telefona</string>
|
||||
<string name="app_name">OsmAnd Onlajn GPS Pratioc</string>
|
||||
<string name="show_on_map">Prikaži na mapi</string>
|
||||
<string name="start_location_sharing">Deli lokaciju</string>
|
||||
<string name="my_location_search_hint">Pretražite: Grupu ili kontakt</string>
|
||||
<string name="location_sharing_description">Izaberite kontakte i grupe sa kojima želite da delite lokaciju.</string>
|
||||
<string name="set_time">Podesite vreme</string>
|
||||
<string name="set_time_description">Podesite vreme za koji će izabrani kontakti i grupe videti vašu lokaciju u realnom vremenu.</string>
|
||||
<string name="visible_time_for_all">Vidljivo vreme za sve</string>
|
||||
<string name="hours_format">%1$ č</string>
|
||||
<string name="minutes_format">%1$ m</string>
|
||||
<string name="hours_and_minutes_format">%1$ č %2$ m</string>
|
||||
<string name="set_visible_time_for_all">Podesite vidljivo vreme za sve</string>
|
||||
<string name="enter_authentication_code">Unesite kod za validaciju</string>
|
||||
<string name="enter_phone_number">Unesite broj telefona</string>
|
||||
<string name="do_not_have_telegram">Nemam Telegram nalog</string>
|
||||
<string name="already_registered_in_telegram">Potreban vam je registrovani Telegram nalog i broj telefona</string>
|
||||
<string name="get_telegram_after_creating_account">Tada možete da koristite ovu aplikaciju.</string>
|
||||
<string name="get_telegram_description_continue">Instalirajte Telegram i otvorite nalog.</string>
|
||||
<string name="get_telegram_account_first">Za deljenje lokacije potreban vam je Telegram nalog.</string>
|
||||
<string name="get_telegram_title">Registracija u Telegramu</string>
|
||||
<string name="shared_string_bot">Bot</string>
|
||||
<string name="shared_string_live">Uživo</string>
|
||||
<string name="open_osmand">Otvori OsmAnd</string>
|
||||
<string name="turn_off_location_sharing">Isključite deljenje lokacije</string>
|
||||
<string name="stop_sharing_all">Deljenje je uključeno (isključite)</string>
|
||||
<string name="expire_at">Ističe</string>
|
||||
<string name="sharing_time">Vreme deljenja</string>
|
||||
<string name="gps_and_location">Pozicija</string>
|
||||
<string name="send_my_location">Pošalji moju lokaciju</string>
|
||||
<string name="send_my_location_desc">Podesite minimalni interval za deljenje lokacije.</string>
|
||||
<string name="stale_location">Nepomičan</string>
|
||||
<string name="stale_location_desc">Poslednji put kada se kontakt pomerio.</string>
|
||||
<string name="location_history">Istorija lokacije</string>
|
||||
<string name="location_history_desc">Sakrijte kontakte koji se nisu pomerili u datom vremenu.</string>
|
||||
<string name="osmand_connect">Osmand veza</string>
|
||||
<string name="osmand_connect_desc">Odaberite verziju OsmAnda koju OsmAnd pratioc koristi za prikazivanje pozicija.</string>
|
||||
<string name="in_time">u %1$</string>
|
||||
<string name="shared_string_account">Nalog</string>
|
||||
<string name="connected_account">Povezani nalog</string>
|
||||
<string name="logout_help_desc">Kako isključiti OsmAnd pratioca iz Telegrama</string>
|
||||
<string name="disconnect_from_telegram">Kako isključiti OsmAnd pratioca iz Telegrama</string>
|
||||
<string name="disconnect_from_telegram_desc">Da biste opozvali pristup deljenju lokacije. Otvorite Telegram, idite na Podešavanja → Privatnost i bezbednost → Sesije i prekinete sesiju OsmAnd pratioca.</string>
|
||||
<string name="logout_no_internet_msg">Povežite se na Internet kako biste se pravilno odjavili iz Telegrama.</string>
|
||||
<string name="shared_string_group">Grupa</string>
|
||||
<string name="last_response">Poslednji odgovor</string>
|
||||
<string name="time_ago">pre</string>
|
||||
<string name="turn_off_all">Isključi sve</string>
|
||||
<string name="disable_all_sharing">Onemogući svako deljenje</string>
|
||||
<string name="disable_all_sharing_desc">Isključuje deljenje lokacije prema svim izabranim čatovima (%1$).</string>
|
||||
<string name="choose_osmand">Izaberite verziju OsmAnda koju želite da koristite</string>
|
||||
<string name="choose_osmand_desc">Izaberite verziju OsmAnda gde će se kontakti prikazati na mapi.</string>
|
||||
<string name="shared_string_sort_by">Sortiraj po</string>
|
||||
<string name="by_group">Po grupi</string>
|
||||
<string name="by_name">Po imenu</string>
|
||||
<string name="by_distance">Po udaljenosti</string>
|
||||
<string name="logout_from_osmand_telegram">Odjaviti se sa OsmAnd pratioca\?</string>
|
||||
<string name="logout_from_osmand_telegram_descr">Jeste li sigurni da se želite odjaviti sa OsmAnd pratioca tako da ne možete da delite lokaciju ili vidite lokaciju drugih\?</string>
|
||||
<string name="live_now_description">Kontakti i grupe dele lokaciju vama.</string>
|
||||
<string name="share_location_as">Deljenje lokacije kao</string>
|
||||
<string name="add_device">Dodajte uređaj</string>
|
||||
<string name="no_internet_connection">Nema internet konekcije</string>
|
||||
<string name="no_gps_connection">Nema GPS veze</string>
|
||||
<string name="location_sharing_status">Deljenje: %1$</string>
|
||||
<string name="sharing_status">Deljenje statusa</string>
|
||||
<string name="last_available_location">Poslednja dostupna lokacija</string>
|
||||
<string name="re_send_location">Ponovo pošalji lokaciju</string>
|
||||
<string name="not_found_yet">Još nije pronađeno</string>
|
||||
<string name="not_sent_yet">Još nije poslato</string>
|
||||
<string name="shared_string_later">Kasnije</string>
|
||||
<string name="go_to_settings">Idi na Podešavanja</string>
|
||||
<string name="sharing_in_background">Deljenje u pozadini</string>
|
||||
<string name="battery_optimization_description">Isključite optimizaciju baterije za OsmAnd pratilac tako da se ne isključi iznenada kad je u pozadini.</string>
|
||||
<string name="background_work">Rad u pozadini</string>
|
||||
<string name="background_work_description">Promenite podešavanja za optimizaciju baterije da biste stabilizovali deljenje lokacije.</string>
|
||||
<string name="connecting_to_the_internet">Povezivanje sa Internetom</string>
|
||||
<string name="searching_for_gps">Pozicioniranje…</string>
|
||||
<string name="initializing">Pokretanje</string>
|
||||
<string name="sending_location_messages">Lokacija se šalje</string>
|
||||
<string name="waiting_for_response_from_telegram">Čeka se odgovor iz Telegrama</string>
|
||||
<string name="not_possible_to_send_to_telegram_chats">Nije moguće poslati u Telegram četove:</string>
|
||||
<string name="successfully_sent_and_updated">Uspešno poslato i ažurirano</string>
|
||||
<string name="last_updated_location">Poslednja ažurirana lokacija:</string>
|
||||
<string name="share_location_as_description">Ako želite da povežete više uređaja sa jednim nalogom telegrama, trebate koristiti drugi uređaj da bi delili vašu lokaciju.</string>
|
||||
<string name="share_location_as_description_second_line">Možete da kreirate i vidite ID uređaja u telegram klijentu koristeći %1$ čat bot. %2$</string>
|
||||
<string name="device_name">Ime uređaja</string>
|
||||
<string name="device_name_cannot_be_empty">Ime uređaja ne može biti prazno</string>
|
||||
<string name="device_name_is_too_long">Ime uređaja predugo</string>
|
||||
<string name="enter_device_name_description">Imenujte vaš novi uređaj sa maksimalno 200 simbola.</string>
|
||||
<string name="error_adding_new_device">Nije moguće dodati novi uređaj</string>
|
||||
<string name="device_added_successfully">%1$ dodato.</string>
|
||||
<string name="enter_another_device_name">Izaberite ime koje niste već koristili</string>
|
||||
<string name="last_update_from_telegram">Poslednje ažuriranje od Telegrama</string>
|
||||
<string name="map_and_text">Mapa i tekst</string>
|
||||
<string name="shared_string_text">Tekst</string>
|
||||
<string name="send_location_as_descr">Odaberite kako će izgledati poruke sa vašom lokacijom.</string>
|
||||
<string name="send_location_as">Pošalji lokaciju kao</string>
|
||||
<string name="start_date">Početni datum</string>
|
||||
<string name="end_date">Krajnji datum</string>
|
||||
<string name="open_in_osmand">Prikaži u OsmAndu</string>
|
||||
<string name="time_on_the_move">Vreme kretanja</string>
|
||||
<string name="monitoring_is_disabled">Praćenje je onemogućeno</string>
|
||||
<string name="monitoring_is_enabled">Praćenje je omogućeno</string>
|
||||
<string name="shared_string_sent">Poslato</string>
|
||||
<string name="gps_points">GPS tačke</string>
|
||||
<string name="shared_string_collected">Prikupljeno</string>
|
||||
<string name="shared_string_date">Datum</string>
|
||||
<string name="points_size">%1$ tačaka</string>
|
||||
<string name="gps_points_in_buffer">poslato (%1$ u baferu)</string>
|
||||
<string name="please_update_osmand">Ažurirajte OsmAnd da biste videli podatke na mapi</string>
|
||||
<string name="show_gps_points_descr">Prikaži količinu prikupljenih i poslatih GPS tačaka.</string>
|
||||
<string name="show_gps_points">Pokaži GPS tačke</string>
|
||||
<string name="received_gps_points">Primljene GPKS tačke: %1$</string>
|
||||
<string name="how_it_works">Kako radi</string>
|
||||
<string name="osmand_privacy_policy">OsmAnd politika privatnosti</string>
|
||||
<string name="telegram_privacy_policy">Politika privatnosti Telegrama</string>
|
||||
<string name="shared_string_accept">Prihvati</string>
|
||||
<string name="privacy_policy_agree">Klikom na „Nastavi“ prihvatate uslove politike privatnosti Telegrama i OsmAnda.</string>
|
||||
<string name="privacy_policy_telegram_client">OsmAnd pratilac je jedan od klijenata koji koriste otvorenu platformu Telegram. Vaši kontakti mogu da koriste bilo koji drugi Telegram klijent.</string>
|
||||
<string name="privacy_policy_use_telegram">Telegram (aplikacija za razmenu poruka) koristi se za povezivanje i komunikaciju sa ljudima.</string>
|
||||
<string name="shared_string_telegram">Telegram</string>
|
||||
<string name="app_name_short">OsmAnd pratilac</string>
|
||||
<string name="timeline_description">Omogućite praćenje da biste sačuvali sve lokacije u istoriji.</string>
|
||||
<string name="location_recording_enabled">Snimanje lokacije omogućeno</string>
|
||||
<string name="disable_monitoring">Onemogućite praćenje</string>
|
||||
<string name="timeline_available_for_free_now">Vremenska linija je funkcija koja je sada dostupna besplatno.</string>
|
||||
<string name="type_contact_or_group_name">Unesite ime kontakta ili grupe</string>
|
||||
<string name="search_contacts_descr">Pretraga po svim vašim grupama i kontaktima.</string>
|
||||
<string name="search_contacts">Pretraga kontakta</string>
|
||||
<string name="bearing">Usmerenje</string>
|
||||
<string name="precision">Preciznost</string>
|
||||
<string name="direction">Smer</string>
|
||||
<string name="privacy">Privatnost</string>
|
||||
<string name="proxy">Proksi</string>
|
||||
<string name="proxy_settings">Podešavanja proksija</string>
|
||||
<string name="proxy_disconnected">Prekinut</string>
|
||||
<string name="proxy_connected">Povezan</string>
|
||||
<string name="proxy_type">Tip proksija</string>
|
||||
<string name="shared_string_connection">Veza</string>
|
||||
<string name="proxy_server">Server</string>
|
||||
<string name="proxy_port">Port</string>
|
||||
<string name="proxy_credentials">Akreditivi</string>
|
||||
<string name="proxy_username">Korisničko ime</string>
|
||||
<string name="proxy_password">Lozinka</string>
|
||||
<string name="proxy_key">Ključ</string>
|
||||
<string name="gpx_settings">GPX podešavanja</string>
|
||||
<string name="min_logging_speed_descr">Filter: nema zapisivanja ispod odabrane brzine</string>
|
||||
<string name="min_logging_speed">Minimalna brzina zapisivanja</string>
|
||||
<string name="min_logging_accuracy_descr">Filter: Nema zapisa dok se ne dostigne ova tačnost</string>
|
||||
<string name="min_logging_accuracy">Minimalna tačnost evidentiranja</string>
|
||||
<string name="min_logging_distance_descr">Filter: minimalna udaljenost za evidentiranje nove tačke</string>
|
||||
<string name="min_logging_distance">Minimalna udaljenost evidentiranja</string>
|
||||
<string name="timeline_no_data">Nema podataka</string>
|
||||
<string name="timeline_no_data_descr">Nemamo prikupljene podatke za izabrani dan</string>
|
||||
<string name="start_end_date">Početni — Krajnji datum</string>
|
||||
<string name="set_time_timeline_descr">Izaberite vreme za prikaz</string>
|
||||
<string name="shared_string_start">Početak</string>
|
||||
<string name="shared_string_end">Kraj</string>
|
||||
<string name="saved_messages">Sačuvane poruke</string>
|
||||
<string name="unit_of_speed_system">Jedinica brzine</string>
|
||||
<string name="unit_of_speed_system_descr">Definišite jedinicu brzine.</string>
|
||||
<string name="time_zone">Vremenska zona</string>
|
||||
<string name="time_zone_descr">Izaberite vremensku zonu koja će se prikazati u porukama lokacije.</string>
|
||||
<string name="buffer_time">Vreme isteka bafera</string>
|
||||
<string name="buffer_time_descr">Maksimalno vreme za skladištenje tačaka u bafer</string>
|
||||
<string name="status_widget_title">Status Tragača OsmAnda</string>
|
||||
<string name="shared_string_suggested">Predloženo</string>
|
||||
<string name="back_to_osmand">Povratak na OsmAnd</string>
|
||||
<string name="duration_ago">Pre %1$</string>
|
||||
<string name="last_response_duration">Poslednji odgovor: pre %1$</string>
|
||||
<string name="last_update_from_telegram_duration">Poslednje ažuriranje iz Telegrama: pre %1$</string>
|
||||
<string name="last_response_date">Poslednji odgovor: %1$</string>
|
||||
<string name="last_update_from_telegram_date">Poslednje ažuriranje iz Telegrama: %1$</string>
|
||||
<string name="shared_string_error_short">Greška</string>
|
||||
<string name="shared_string_export">Izvezi</string>
|
||||
<string name="logcat_buffer">Logcat bafer</string>
|
||||
<string name="logcat_buffer_descr">Proverite i podelite detaljne zapise aplikacije</string>
|
||||
<string name="send_report">Pošalji izveštaj</string>
|
||||
</resources>
|
|
@ -109,7 +109,7 @@
|
|||
<string name="get_telegram_description_continue">Kérjük, telepítse a Telegramot és hozzon létre egy fiókot.</string>
|
||||
<string name="get_telegram_after_creating_account">Utána használhatja ezt az alkalmazást.</string>
|
||||
<string name="shared_string_all">Minden</string>
|
||||
<string name="shared_string_off">Kikapcsolás</string>
|
||||
<string name="shared_string_off">Kikapcsolva</string>
|
||||
<string name="hours_and_minutes_format">%1$d óra %2$d perc</string>
|
||||
<string name="minutes_format">%1$d perc</string>
|
||||
<string name="shared_string_install">Telepítés</string>
|
||||
|
|
10
OsmAnd/res/drawable/ic_action_alert_circle.xml
Normal file
10
OsmAnd/res/drawable/ic_action_alert_circle.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M12,22C17.5228,22 22,17.5228 22,12C22,6.4771 17.5228,2 12,2C6.4771,2 2,6.4771 2,12C2,17.5228 6.4771,22 12,22ZM11,14V7H13V14H11ZM11,18V16H13V18H11Z"
|
||||
android:fillColor="#ffffff"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
18
OsmAnd/res/drawable/ic_action_cellular.xml
Normal file
18
OsmAnd/res/drawable/ic_action_cellular.xml
Normal file
|
@ -0,0 +1,18 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M20,12C20,15.4738 17.7859,18.4304 14.692,19.5358L14.9449,21.5593C19.0304,20.3022 22,16.4979 22,12C22,6.4771 17.5228,2 12,2C6.4771,2 2,6.4771 2,12C2,16.4979 4.9696,20.3022 9.0551,21.5593L9.308,19.5358C6.2141,18.4304 4,15.4738 4,12C4,7.5817 7.5817,4 12,4C16.4183,4 20,7.5817 20,12Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M18,12C18,14.4466 16.5357,16.5511 14.4356,17.485L14.1701,15.3607C15.2713,14.6482 16,13.4092 16,12C16,9.7909 14.2091,8 12,8C9.7909,8 8,9.7909 8,12C8,13.4092 8.7287,14.6482 9.8299,15.3607L9.5644,17.485C7.4643,16.5511 6,14.4466 6,12C6,8.6863 8.6863,6 12,6C15.3137,6 18,8.6863 18,12Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M14,12C14,13.1046 13.1046,14 12,14C10.8954,14 10,13.1046 10,12C10,10.8954 10.8954,10 12,10C13.1046,10 14,10.8954 14,12Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M11,15V22H13V15H11Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
15
OsmAnd/res/drawable/ic_action_cellular_disable.xml
Normal file
15
OsmAnd/res/drawable/ic_action_cellular_disable.xml
Normal file
|
@ -0,0 +1,15 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M22.0001,12C22.0001,13.9623 21.4349,15.7926 20.4583,17.337L18.9991,15.8777C19.6369,14.7291 20.0001,13.407 20.0001,12C20.0001,7.5817 16.4183,4 12.0001,4C10.5931,4 9.271,4.3632 8.1223,5.0009L6.6631,3.5417C8.2075,2.5652 10.0378,2 12.0001,2C17.5229,2 22.0001,6.4771 22.0001,12Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M18.0001,12C18.0001,12.8478 17.8242,13.6545 17.507,14.3857L15.9203,12.7989C15.9726,12.5407 16.0001,12.2736 16.0001,12C16.0001,9.7909 14.2092,8 12.0001,8C11.7265,8 11.4593,8.0275 11.2012,8.0798L9.6144,6.493C10.3456,6.1758 11.1523,6 12.0001,6C15.3138,6 18.0001,8.6863 18.0001,12Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M14.3449,16.759L14.4357,17.485C14.5806,17.4206 14.7225,17.3506 14.8611,17.2752L16.3203,18.7344C15.8138,19.0599 15.2682,19.33 14.692,19.5358L14.945,21.5593C15.9623,21.2463 16.9105,20.7753 17.7609,20.1749L21.293,23.7071L22.7072,22.2928L2.7072,2.2929L1.293,3.7071L3.8251,6.2392C2.6754,7.8677 2.0001,9.855 2.0001,12C2.0001,16.4979 4.9696,20.3022 9.0552,21.5593L9.3081,19.5358C6.2141,18.4304 4.0001,15.4738 4.0001,12C4.0001,10.4087 4.4647,8.9258 5.2657,7.6798L6.7248,9.1389C6.2626,9.9894 6.0001,10.964 6.0001,12C6.0001,14.4466 7.4644,16.5511 9.5644,17.485L9.83,15.3607C8.7288,14.6482 8.0001,13.4092 8.0001,12C8.0001,11.5256 8.0826,11.0705 8.2342,10.6483L10.0555,12.4696C10.2305,13.197 10.8031,13.7695 11.5305,13.9446L12.5859,15H11.0001V22H13.0001V15.4142L14.3449,16.759Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
9
OsmAnd/res/drawable/ic_action_cloud.xml
Normal file
9
OsmAnd/res/drawable/ic_action_cloud.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M6,18V17.9725C3.75,17.7238 2,15.8163 2,13.5C2,11.0147 4.0147,9 6.5,9C6.5998,9 6.6989,9.0033 6.7971,9.0097C7.8332,7.2109 9.7752,6 12,6C15.3137,6 18,8.6863 18,12C18,12.0574 17.9992,12.1146 17.9976,12.1716C18.3111,12.0605 18.6485,12 19,12C20.6569,12 22,13.3431 22,15C22,16.6569 20.6569,18 19,18H6Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
10
OsmAnd/res/drawable/ic_action_cloud_alert.xml
Normal file
10
OsmAnd/res/drawable/ic_action_cloud_alert.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M6,17.9725V18H19C20.6569,18 22,16.6569 22,15C22,13.3431 20.6569,12 19,12C18.6485,12 18.3111,12.0605 17.9976,12.1716C17.9992,12.1146 18,12.0574 18,12C18,8.6863 15.3137,6 12,6C9.7752,6 7.8332,7.2109 6.7971,9.0097C6.6989,9.0033 6.5998,9 6.5,9C4.0147,9 2,11.0147 2,13.5C2,15.8163 3.75,17.7238 6,17.9725ZM11,13V8H13V13H11ZM11,17V15H13V17H11Z"
|
||||
android:fillColor="#ffffff"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
9
OsmAnd/res/drawable/ic_action_cloud_upload.xml
Normal file
9
OsmAnd/res/drawable/ic_action_cloud_upload.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M14,18V15H16.5L12.5,10L8.5,15H11V18H6V17.9725C3.75,17.7238 2,15.8163 2,13.5C2,11.0147 4.0147,9 6.5,9C6.5998,9 6.6989,9.0033 6.7971,9.0097C7.8332,7.2109 9.7752,6 12,6C15.3137,6 18,8.6863 18,12C18,12.0574 17.9992,12.1146 17.9976,12.1716C18.3111,12.0605 18.6485,12 19,12C20.6569,12 22,13.3431 22,15C22,16.6569 20.6569,18 19,18H14Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
12
OsmAnd/res/drawable/ic_action_cloud_upload_colored.xml
Normal file
12
OsmAnd/res/drawable/ic_action_cloud_upload_colored.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M6,18V17.9725C3.75,17.7238 2,15.8163 2,13.5C2,11.0147 4.0147,9 6.5,9C6.5998,9 6.6989,9.0033 6.7971,9.0097C7.8332,7.2109 9.7752,6 12,6C15.3137,6 18,8.6863 18,12C18,12.0574 17.9992,12.1146 17.9976,12.1716C18.3111,12.0605 18.6485,12 19,12C20.6569,12 22,13.3431 22,15C22,16.6569 20.6569,18 19,18H6Z"
|
||||
android:fillColor="#6C19FF"/>
|
||||
<path
|
||||
android:pathData="M11,15H8.5L12.5,9.5L16.5,15H14V20H11V15Z"
|
||||
android:fillColor="#FFC30D"/>
|
||||
</vector>
|
15
OsmAnd/res/drawable/ic_action_read_from_file.xml
Normal file
15
OsmAnd/res/drawable/ic_action_read_from_file.xml
Normal file
|
@ -0,0 +1,15 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M14,2L20,8H16C14.8954,8 14,7.1046 14,6V2Z"
|
||||
android:strokeAlpha="0.5"
|
||||
android:fillColor="#ffffff"
|
||||
android:fillAlpha="0.5"/>
|
||||
<path
|
||||
android:pathData="M4,4C4,2.8954 4.8954,2 6,2H14V6C14,7.1046 14.8954,8 16,8H20V20C20,21.1046 19.1046,22 18,22H6C4.8954,22 4,21.1046 4,20V4ZM11,18H13V13H15L12,9L9,13H11V18Z"
|
||||
android:fillColor="#ffffff"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
37
OsmAnd/res/drawable/ic_action_restore.xml
Normal file
37
OsmAnd/res/drawable/ic_action_restore.xml
Normal file
|
@ -0,0 +1,37 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M12,5C12.5523,5 13,4.5523 13,4H17V8.0549C16.6717,8.0186 16.338,8 16,8C11.0294,8 7,12.0294 7,17V4H11C11,4.5523 11.4477,5 12,5Z"
|
||||
android:strokeAlpha="0.3"
|
||||
android:fillColor="#ffffff"
|
||||
android:fillAlpha="0.3"/>
|
||||
<path
|
||||
android:pathData="M7,17C7,18.0519 7.1805,19.0617 7.5121,20H7V17Z"
|
||||
android:strokeAlpha="0.3"
|
||||
android:fillColor="#ffffff"
|
||||
android:fillAlpha="0.3"/>
|
||||
<path
|
||||
android:pathData="M17,2.01L7,2C5.9,2 5,2.9 5,4V20C5,21.1 5.9,22 7,22H8.5155C8.1025,21.383 7.7638,20.7121 7.5121,20H7V4H11C11,4.5523 11.4477,5 12,5C12.5523,5 13,4.5523 13,4H17V8.0549C17.6935,8.1316 18.3632,8.287 19,8.5121V4C19,2.9 18.1,2.01 17,2.01Z"
|
||||
android:strokeAlpha="0.7"
|
||||
android:fillColor="#ffffff"
|
||||
android:fillAlpha="0.7"/>
|
||||
<path
|
||||
android:pathData="M16,21C18.2091,21 20,19.2091 20,17C20,14.7909 18.2091,13 16,13C13.7909,13 12,14.7909 12,17H14L11,20L8,17H10C10,13.6863 12.6863,11 16,11C19.3137,11 22,13.6863 22,17C22,20.3137 19.3137,23 16,23C14.598,23 13.3082,22.5191 12.2868,21.7132L13.7159,20.2841C14.3635,20.7354 15.1508,21 16,21Z"
|
||||
android:fillType="evenOdd">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:gradientRadius="6.36396"
|
||||
android:centerX="16"
|
||||
android:centerY="17"
|
||||
android:type="radial">
|
||||
<item android:offset="0" android:color="#FFFFFFFF"/>
|
||||
<item android:offset="0.8125" android:color="#FFFFFFFF"/>
|
||||
<item android:offset="1" android:color="#00FFFFFF"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
</vector>
|
15
OsmAnd/res/drawable/ic_action_user_folder.xml
Normal file
15
OsmAnd/res/drawable/ic_action_user_folder.xml
Normal file
|
@ -0,0 +1,15 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M6,3C4.8954,3 4,3.8954 4,5V7H20C20,5.8954 19.1046,5 18,5H13L11.4,3H6Z"
|
||||
android:strokeAlpha="0.5"
|
||||
android:fillColor="#ffffff"
|
||||
android:fillAlpha="0.5"/>
|
||||
<path
|
||||
android:pathData="M2,7C2,5.8954 2.8954,5 4,5H10L12,7H20C21.1046,7 22,7.8954 22,9V19C22,20.1046 21.1046,21 20,21H4C2.8954,21 2,20.1046 2,19V7ZM17,12C17,13.1046 16.1046,14 15,14C13.8954,14 13,13.1046 13,12C13,10.8954 13.8954,10 15,10C16.1046,10 17,10.8954 17,12ZM10,17.5V19H20V17.5C20,15.5 17,15 15,15C13,15 10,15.5 10,17.5Z"
|
||||
android:fillColor="#ffffff"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
7
OsmAnd/res/drawable/rectangle_rounded_left.xml
Normal file
7
OsmAnd/res/drawable/rectangle_rounded_left.xml
Normal file
|
@ -0,0 +1,7 @@
|
|||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners
|
||||
android:bottomLeftRadius="@dimen/list_item_button_padding"
|
||||
android:topLeftRadius="@dimen/list_item_button_padding" />
|
||||
<solid android:color="@color/list_background_color_dark" />
|
||||
</shape>
|
11
OsmAnd/res/drawable/ripple_rectangle_rounded_left.xml
Normal file
11
OsmAnd/res/drawable/ripple_rectangle_rounded_left.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="@color/active_buttons_and_links_trans_light">
|
||||
<item android:id="@android:id/mask">
|
||||
<shape android:shape="rectangle">
|
||||
<corners
|
||||
android:bottomLeftRadius="@dimen/list_item_button_padding"
|
||||
android:topLeftRadius="@dimen/list_item_button_padding" />
|
||||
<solid android:color="@color/active_color_primary_light" />
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
|
@ -1,15 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@+id/background">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#4d007eb3" />
|
||||
<corners android:radius="30dp" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:id="@+id/progress">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="@color/color_white" />
|
||||
<corners android:radius="30dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
|
@ -1,13 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<item android:id="@+id/thump">
|
||||
<shape android:shape="oval">
|
||||
<size
|
||||
android:width="12dp"
|
||||
android:height="12dp" />
|
||||
<solid android:color="@color/profile_icon_color_blue_light" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<size
|
||||
android:width="2dp"
|
||||
android:height="2dp" />
|
||||
<solid android:color="#17181A" />
|
||||
</shape>
|
|
@ -16,20 +16,34 @@
|
|||
android:focusable="true"
|
||||
android:orientation="vertical"
|
||||
android:clickable="true"
|
||||
tools:background="@drawable/bg_bottom_menu_dark">
|
||||
tools:ignore="UselessParent">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/snap_to_road_progress_bar"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="0dp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:background="@drawable/bg_contextmenu_shadow_top_light" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/snap_to_road_progress_bar"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="0dp"
|
||||
android:background="?attr/list_background_color"
|
||||
android:visibility="invisible"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/main_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/list_background_color">
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/up_down_row"
|
||||
|
@ -53,6 +67,19 @@
|
|||
android:background="@null"
|
||||
tools:src="@drawable/ic_action_ruler"/>
|
||||
|
||||
<include
|
||||
layout="@layout/custom_icon_radio_buttons"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="@dimen/content_padding_half"
|
||||
android:layout_marginLeft="@dimen/bottom_sheet_content_margin"
|
||||
android:layout_marginRight="@dimen/content_padding_half"
|
||||
android:layout_marginStart="@dimen/bottom_sheet_content_margin"
|
||||
android:background="@null" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/up_down_button"
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -65,6 +92,7 @@
|
|||
android:layout_marginRight="@dimen/bottom_sheet_content_margin"
|
||||
android:layout_marginStart="@dimen/bottom_sheet_content_margin"
|
||||
android:background="@null"
|
||||
android:visibility="gone"
|
||||
tools:src="@drawable/ic_action_arrow_down"/>
|
||||
|
||||
<TextView
|
||||
|
|
|
@ -659,6 +659,7 @@
|
|||
<!-- CENTER -->
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/top_controls_container"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
|
|
|
@ -62,18 +62,12 @@
|
|||
osmand:typeface="@string/font_roboto_medium"
|
||||
tools:text="Normal" />
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/seek_bar_arrival"
|
||||
style="@style/Widget.AppCompat.SeekBar.Discrete"
|
||||
<com.google.android.material.slider.Slider
|
||||
android:id="@+id/arrival_slider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/pages_item_margin"
|
||||
android:maxHeight="2dp"
|
||||
android:paddingTop="11dp"
|
||||
android:paddingBottom="11dp"
|
||||
osmand:tickMark="@drawable/seekbar_tickmark_announcement_time"
|
||||
tools:max="3"
|
||||
tools:progress="1" />
|
||||
android:layout_marginLeft="@dimen/content_padding"
|
||||
android:layout_marginRight="@dimen/content_padding" />
|
||||
|
||||
<View
|
||||
android:id="@+id/divider"
|
||||
|
|
23
OsmAnd/res/layout/custom_icon_radio_buttons.xml
Normal file
23
OsmAnd/res/layout/custom_icon_radio_buttons.xml
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/custom_radio_buttons"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/btn_bg_border_inactive"
|
||||
android:baselineAligned="false"
|
||||
android:minHeight="@dimen/dialog_button_height"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<include
|
||||
layout="@layout/custom_radio_btn_icon_item"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<include
|
||||
layout="@layout/custom_radio_btn_icon_item"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1" />
|
||||
|
||||
</LinearLayout>
|
20
OsmAnd/res/layout/custom_radio_btn_icon_item.xml
Normal file
20
OsmAnd/res/layout/custom_radio_btn_icon_item.xml
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="@dimen/content_padding_small_half"
|
||||
android:paddingBottom="@dimen/content_padding_small_half"
|
||||
android:paddingLeft="@dimen/content_padding_small"
|
||||
android:paddingRight="@dimen/content_padding_small">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="@dimen/standard_icon_size"
|
||||
android:layout_height="@dimen/standard_icon_size"
|
||||
android:layout_gravity="center"
|
||||
tools:src="@drawable/ic_action_info_dark"/>
|
||||
|
||||
</FrameLayout>
|
|
@ -2,6 +2,7 @@
|
|||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical">
|
||||
|
||||
<View
|
||||
|
@ -58,7 +59,7 @@
|
|||
android:layout_gravity="center_vertical|start"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="@dimen/default_list_text_size_large"
|
||||
android:text="Item title"/>
|
||||
tools:text="Item title"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:minHeight="50dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:minHeight="50dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp">
|
||||
|
||||
|
@ -69,7 +70,7 @@
|
|||
android:textColor="@color/color_myloc_distance"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginTop="2dp"
|
||||
android:text="Cinema"
|
||||
tools:text="Cinema"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginEnd="16dp" />
|
||||
|
||||
|
|
|
@ -13,293 +13,314 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:background="@drawable/bg_bottom_menu_dark"
|
||||
android:orientation="vertical"
|
||||
android:clickable="true"
|
||||
android:focusable="true">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/snap_to_road_progress_bar"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="0dp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/up_down_row"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/measurement_tool_up_down_row_height"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/main_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="@dimen/measurement_tool_text_button_padding"
|
||||
android:layout_marginLeft="@dimen/measurement_tool_text_button_padding"
|
||||
android:layout_marginRight="@dimen/measurement_tool_text_button_padding"
|
||||
android:layout_marginStart="@dimen/measurement_tool_text_button_padding"
|
||||
android:background="@null"
|
||||
tools:src="@drawable/ic_action_ruler"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/up_down_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="@dimen/bottom_sheet_content_margin"
|
||||
android:layout_marginLeft="@dimen/bottom_sheet_content_margin"
|
||||
android:layout_marginRight="@dimen/bottom_sheet_content_margin"
|
||||
android:layout_marginStart="@dimen/bottom_sheet_content_margin"
|
||||
android:background="@null"
|
||||
tools:src="@drawable/ic_action_arrow_down"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/measurement_distance_text_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/measurement_tool_button_padding"
|
||||
android:layout_marginEnd="@dimen/text_margin_small"
|
||||
android:layout_marginLeft="@dimen/measurement_tool_text_button_padding"
|
||||
android:layout_marginRight="@dimen/text_margin_small"
|
||||
android:layout_marginStart="@dimen/measurement_tool_text_button_padding"
|
||||
android:layout_toEndOf="@id/main_icon"
|
||||
android:layout_toRightOf="@id/main_icon"
|
||||
android:textAppearance="@style/TextAppearance.ListItemCategoryTitle"
|
||||
tools:text="724 m,"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/measurement_points_text_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/measurement_tool_button_padding"
|
||||
android:layout_toEndOf="@id/measurement_distance_text_view"
|
||||
android:layout_toRightOf="@id/measurement_distance_text_view"
|
||||
android:layout_alignEnd="@id/up_down_button"
|
||||
android:layout_alignRight="@id/up_down_button"
|
||||
android:textAppearance="@style/TextAppearance.ListItemCategoryTitle"
|
||||
tools:text="points: 3" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/distance_to_center_text_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/measurement_points_text_view"
|
||||
android:layout_alignStart="@+id/measurement_distance_text_view"
|
||||
android:layout_alignLeft="@+id/measurement_distance_text_view"
|
||||
android:maxLines="1"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="@dimen/default_desc_text_size"
|
||||
tools:text=" – 700 m" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/move_point_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="@dimen/text_margin_small"
|
||||
android:layout_marginLeft="@dimen/measurement_tool_text_margin"
|
||||
android:layout_marginRight="@dimen/text_margin_small"
|
||||
android:layout_marginStart="@dimen/measurement_tool_text_margin"
|
||||
android:layout_toEndOf="@id/main_icon"
|
||||
android:layout_toRightOf="@id/main_icon"
|
||||
android:text="@string/move_point"
|
||||
android:textAppearance="@style/TextAppearance.ListItemTitle"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/add_point_before_after_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="@dimen/text_margin_small"
|
||||
android:layout_marginLeft="@dimen/measurement_tool_text_margin"
|
||||
android:layout_marginRight="@dimen/text_margin_small"
|
||||
android:layout_marginStart="@dimen/measurement_tool_text_margin"
|
||||
android:layout_toEndOf="@id/main_icon"
|
||||
android:layout_toRightOf="@id/main_icon"
|
||||
android:textAppearance="@style/TextAppearance.ListItemTitle"
|
||||
android:visibility="gone"
|
||||
tools:text="@string/add_point_after"/>
|
||||
</RelativeLayout>
|
||||
android:focusable="true"
|
||||
tools:ignore="UselessParent">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/info_type_buttons_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<include
|
||||
layout="@layout/custom_radio_buttons"
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/measurement_tool_button_height"
|
||||
android:layout_marginStart="@dimen/content_padding"
|
||||
android:layout_marginEnd="@dimen/content_padding"
|
||||
android:layout_marginBottom="@dimen/measurement_tool_content_padding_medium" />
|
||||
android:layout_height="0dp"
|
||||
android:background="@drawable/bg_contextmenu_shadow_top_light" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/snap_to_road_progress_bar"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="0dp"
|
||||
android:background="?attr/list_background_color"
|
||||
android:visibility="invisible"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/cards_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/measurement_tool_info_cards_container_height"
|
||||
android:visibility="gone" />
|
||||
|
||||
<View
|
||||
android:id="@+id/bottom_panel_divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="?attr/dashboard_divider" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/measure_mode_controls"
|
||||
android:id="@+id/main_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/measurement_tool_controls_height"
|
||||
android:paddingTop="@dimen/measurement_tool_button_margin"
|
||||
android:paddingBottom="@dimen/measurement_tool_button_margin"
|
||||
android:paddingLeft="@dimen/measurement_tool_button_margin"
|
||||
android:paddingRight="@dimen/measurement_tool_button_margin"
|
||||
android:paddingStart="@dimen/measurement_tool_button_margin"
|
||||
android:paddingEnd="@dimen/measurement_tool_button_margin">
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/list_background_color"
|
||||
android:orientation="vertical">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1">
|
||||
<RelativeLayout
|
||||
android:id="@+id/up_down_row"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/measurement_tool_up_down_row_height"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
|
||||
<net.osmand.plus.widgets.TextViewEx
|
||||
android:id="@+id/options_button"
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/main_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="@dimen/measurement_tool_text_button_padding"
|
||||
android:layout_marginLeft="@dimen/measurement_tool_text_button_padding"
|
||||
android:layout_marginRight="@dimen/measurement_tool_text_button_padding"
|
||||
android:layout_marginStart="@dimen/measurement_tool_text_button_padding"
|
||||
android:background="@null"
|
||||
tools:src="@drawable/ic_action_ruler"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/up_down_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="@dimen/bottom_sheet_content_margin"
|
||||
android:layout_marginLeft="@dimen/bottom_sheet_content_margin"
|
||||
android:layout_marginRight="@dimen/bottom_sheet_content_margin"
|
||||
android:layout_marginStart="@dimen/bottom_sheet_content_margin"
|
||||
android:background="@null"
|
||||
tools:src="@drawable/ic_action_arrow_down"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/measurement_distance_text_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/measurement_tool_button_padding"
|
||||
android:layout_marginEnd="@dimen/text_margin_small"
|
||||
android:layout_marginLeft="@dimen/measurement_tool_text_button_padding"
|
||||
android:layout_marginRight="@dimen/text_margin_small"
|
||||
android:layout_marginStart="@dimen/measurement_tool_text_button_padding"
|
||||
android:layout_toEndOf="@id/main_icon"
|
||||
android:layout_toRightOf="@id/main_icon"
|
||||
android:textAppearance="@style/TextAppearance.ListItemCategoryTitle"
|
||||
tools:text="724 m,"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/measurement_points_text_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/measurement_tool_button_padding"
|
||||
android:layout_toEndOf="@id/measurement_distance_text_view"
|
||||
android:layout_toRightOf="@id/measurement_distance_text_view"
|
||||
android:layout_alignEnd="@id/up_down_button"
|
||||
android:layout_alignRight="@id/up_down_button"
|
||||
android:textAppearance="@style/TextAppearance.ListItemCategoryTitle"
|
||||
tools:text="points: 3" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/distance_to_center_text_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/measurement_points_text_view"
|
||||
android:layout_alignStart="@+id/measurement_distance_text_view"
|
||||
android:layout_alignLeft="@+id/measurement_distance_text_view"
|
||||
android:maxLines="1"
|
||||
android:paddingStart="@dimen/measurement_tool_text_button_padding_small"
|
||||
android:paddingLeft="@dimen/measurement_tool_text_button_padding_small"
|
||||
android:paddingEnd="@dimen/measurement_tool_text_button_padding_small"
|
||||
android:paddingRight="@dimen/measurement_tool_text_button_padding_small"
|
||||
android:text="@string/shared_string_options"
|
||||
android:textColor="?attr/color_dialog_buttons"
|
||||
osmand:typeface="@string/font_roboto_medium"/>
|
||||
</FrameLayout>
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="@dimen/default_desc_text_size"
|
||||
tools:text=" – 700 m" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/undo_point_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:padding="@dimen/measurement_tool_undo_redo_padding_small"
|
||||
android:contentDescription="@string/shared_string_undo"
|
||||
tools:src="@drawable/ic_action_undo_dark"/>
|
||||
<TextView
|
||||
android:id="@+id/move_point_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="@dimen/text_margin_small"
|
||||
android:layout_marginLeft="@dimen/measurement_tool_text_margin"
|
||||
android:layout_marginRight="@dimen/text_margin_small"
|
||||
android:layout_marginStart="@dimen/measurement_tool_text_margin"
|
||||
android:layout_toEndOf="@id/main_icon"
|
||||
android:layout_toRightOf="@id/main_icon"
|
||||
android:text="@string/move_point"
|
||||
android:textAppearance="@style/TextAppearance.ListItemTitle"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/redo_point_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/measurement_tool_button_padding"
|
||||
android:layout_marginStart="@dimen/measurement_tool_button_padding"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:padding="@dimen/measurement_tool_undo_redo_padding_small"
|
||||
android:contentDescription="@string/shared_string_redo"
|
||||
tools:src="@drawable/ic_action_redo_dark"/>
|
||||
<TextView
|
||||
android:id="@+id/add_point_before_after_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="@dimen/text_margin_small"
|
||||
android:layout_marginLeft="@dimen/measurement_tool_text_margin"
|
||||
android:layout_marginRight="@dimen/text_margin_small"
|
||||
android:layout_marginStart="@dimen/measurement_tool_text_margin"
|
||||
android:layout_toEndOf="@id/main_icon"
|
||||
android:layout_toRightOf="@id/main_icon"
|
||||
android:textAppearance="@style/TextAppearance.ListItemTitle"
|
||||
android:visibility="gone"
|
||||
tools:text="@string/add_point_after"/>
|
||||
</RelativeLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1">
|
||||
android:id="@+id/info_type_buttons_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<include
|
||||
android:id="@+id/add_point_button"
|
||||
layout="@layout/bottom_sheet_dialog_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="end"
|
||||
android:minWidth="@dimen/measurement_tool_button_width" />
|
||||
layout="@layout/custom_radio_buttons"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/measurement_tool_button_height"
|
||||
android:layout_marginStart="@dimen/content_padding"
|
||||
android:layout_marginEnd="@dimen/content_padding"
|
||||
android:layout_marginBottom="@dimen/measurement_tool_content_padding_medium" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/move_point_controls"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/measurement_tool_controls_height"
|
||||
android:visibility="gone">
|
||||
|
||||
<include
|
||||
android:id="@+id/apply_move_point_button"
|
||||
layout="@layout/bottom_sheet_dialog_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/measurement_tool_button_height"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="@dimen/measurement_tool_button_margin"
|
||||
android:layout_marginRight="@dimen/measurement_tool_button_margin"
|
||||
android:minWidth="@dimen/measurement_tool_button_width" />
|
||||
|
||||
<net.osmand.plus.widgets.TextViewEx
|
||||
android:id="@+id/cancel_move_point_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="@dimen/measurement_tool_button_margin"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:gravity="center_vertical"
|
||||
android:padding="@dimen/measurement_tool_text_button_padding_small"
|
||||
android:text="@string/shared_string_cancel"
|
||||
android:textColor="?attr/color_dialog_buttons"
|
||||
osmand:typeface="@string/font_roboto_medium"/>
|
||||
</RelativeLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/add_point_before_after_controls"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/measurement_tool_controls_height"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone">
|
||||
|
||||
<net.osmand.plus.widgets.TextViewEx
|
||||
android:id="@+id/cancel_point_before_after_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="@dimen/measurement_tool_button_margin"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:gravity="center_vertical"
|
||||
android:padding="@dimen/measurement_tool_text_button_padding_small"
|
||||
android:text="@string/shared_string_cancel"
|
||||
android:textColor="?attr/color_dialog_buttons"
|
||||
osmand:typeface="@string/font_roboto_medium"/>
|
||||
<FrameLayout
|
||||
android:id="@+id/cards_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/measurement_tool_info_cards_container_height"
|
||||
android:visibility="gone" />
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"/>
|
||||
android:id="@+id/bottom_panel_divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="?attr/dashboard_divider" />
|
||||
|
||||
<include
|
||||
android:id="@+id/apply_point_before_after_point_button"
|
||||
android:layout_height="@dimen/measurement_tool_button_height"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_width="@dimen/measurement_tool_button_width"
|
||||
android:layout_marginEnd="@dimen/measurement_tool_button_margin"
|
||||
android:layout_marginRight="@dimen/measurement_tool_button_margin"
|
||||
layout="@layout/bottom_sheet_dialog_button" />
|
||||
<LinearLayout
|
||||
android:id="@+id/measure_mode_controls"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/measurement_tool_controls_height"
|
||||
android:paddingTop="@dimen/measurement_tool_button_margin"
|
||||
android:paddingBottom="@dimen/measurement_tool_button_margin"
|
||||
android:paddingLeft="@dimen/measurement_tool_button_margin"
|
||||
android:paddingRight="@dimen/measurement_tool_button_margin"
|
||||
android:paddingStart="@dimen/measurement_tool_button_margin"
|
||||
android:paddingEnd="@dimen/measurement_tool_button_margin">
|
||||
|
||||
<include
|
||||
android:id="@+id/add_point_before_after_button"
|
||||
android:layout_height="@dimen/measurement_tool_button_height"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_width="@dimen/measurement_tool_button_width"
|
||||
android:layout_marginEnd="@dimen/measurement_tool_button_margin"
|
||||
android:layout_marginRight="@dimen/measurement_tool_button_margin"
|
||||
layout="@layout/bottom_sheet_dialog_button" />
|
||||
<FrameLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1">
|
||||
|
||||
<net.osmand.plus.widgets.TextViewEx
|
||||
android:id="@+id/options_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
android:maxLines="1"
|
||||
android:paddingStart="@dimen/measurement_tool_text_button_padding_small"
|
||||
android:paddingLeft="@dimen/measurement_tool_text_button_padding_small"
|
||||
android:paddingEnd="@dimen/measurement_tool_text_button_padding_small"
|
||||
android:paddingRight="@dimen/measurement_tool_text_button_padding_small"
|
||||
android:text="@string/shared_string_options"
|
||||
android:textColor="?attr/color_dialog_buttons"
|
||||
osmand:typeface="@string/font_roboto_medium"/>
|
||||
</FrameLayout>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/undo_point_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:padding="@dimen/measurement_tool_undo_redo_padding_small"
|
||||
android:contentDescription="@string/shared_string_undo"
|
||||
tools:src="@drawable/ic_action_undo_dark"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/redo_point_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/measurement_tool_button_padding"
|
||||
android:layout_marginStart="@dimen/measurement_tool_button_padding"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:padding="@dimen/measurement_tool_undo_redo_padding_small"
|
||||
android:contentDescription="@string/shared_string_redo"
|
||||
tools:src="@drawable/ic_action_redo_dark"/>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1">
|
||||
|
||||
<include
|
||||
android:id="@+id/add_point_button"
|
||||
layout="@layout/bottom_sheet_dialog_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="end"
|
||||
android:minWidth="@dimen/measurement_tool_button_width" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/move_point_controls"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/measurement_tool_controls_height"
|
||||
android:visibility="gone">
|
||||
|
||||
<include
|
||||
android:id="@+id/apply_move_point_button"
|
||||
layout="@layout/bottom_sheet_dialog_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/measurement_tool_button_height"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="@dimen/measurement_tool_button_margin"
|
||||
android:layout_marginRight="@dimen/measurement_tool_button_margin"
|
||||
android:minWidth="@dimen/measurement_tool_button_width" />
|
||||
|
||||
<net.osmand.plus.widgets.TextViewEx
|
||||
android:id="@+id/cancel_move_point_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="@dimen/measurement_tool_button_margin"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:gravity="center_vertical"
|
||||
android:padding="@dimen/measurement_tool_text_button_padding_small"
|
||||
android:text="@string/shared_string_cancel"
|
||||
android:textColor="?attr/color_dialog_buttons"
|
||||
osmand:typeface="@string/font_roboto_medium"/>
|
||||
</RelativeLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/add_point_before_after_controls"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/measurement_tool_controls_height"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone">
|
||||
|
||||
<net.osmand.plus.widgets.TextViewEx
|
||||
android:id="@+id/cancel_point_before_after_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="@dimen/measurement_tool_button_margin"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:gravity="center_vertical"
|
||||
android:padding="@dimen/measurement_tool_text_button_padding_small"
|
||||
android:text="@string/shared_string_cancel"
|
||||
android:textColor="?attr/color_dialog_buttons"
|
||||
osmand:typeface="@string/font_roboto_medium"/>
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<include
|
||||
android:id="@+id/apply_point_before_after_point_button"
|
||||
android:layout_height="@dimen/measurement_tool_button_height"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_width="@dimen/measurement_tool_button_width"
|
||||
android:layout_marginEnd="@dimen/measurement_tool_button_margin"
|
||||
android:layout_marginRight="@dimen/measurement_tool_button_margin"
|
||||
layout="@layout/bottom_sheet_dialog_button" />
|
||||
|
||||
<include
|
||||
android:id="@+id/add_point_before_after_button"
|
||||
android:layout_height="@dimen/measurement_tool_button_height"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_width="@dimen/measurement_tool_button_width"
|
||||
android:layout_marginEnd="@dimen/measurement_tool_button_margin"
|
||||
android:layout_marginRight="@dimen/measurement_tool_button_margin"
|
||||
layout="@layout/bottom_sheet_dialog_button" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
|
|
@ -5,8 +5,10 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/content_padding"
|
||||
android:paddingStart="@dimen/content_padding"
|
||||
android:paddingLeft="@dimen/content_padding"
|
||||
android:paddingTop="@dimen/content_padding"
|
||||
android:paddingRight="@dimen/content_padding"
|
||||
android:paddingEnd="@dimen/content_padding">
|
||||
|
||||
<View
|
||||
|
@ -43,4 +45,9 @@
|
|||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/space"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/content_padding" />
|
||||
|
||||
</LinearLayout>
|
|
@ -4,6 +4,9 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/list_background_color"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="?dialogPreferredPadding"
|
||||
android:paddingRight="?dialogPreferredPadding"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="?dialogPreferredPadding"
|
||||
android:paddingRight="?dialogPreferredPadding"
|
||||
android:paddingStart="?dialogPreferredPadding"
|
||||
android:paddingEnd="?dialogPreferredPadding">
|
||||
|
||||
|
@ -32,7 +33,7 @@
|
|||
android:id="@+id/user_name_field"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="NoName"/>
|
||||
tools:text="NoName"/>
|
||||
|
||||
|
||||
<TextView
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:osmand="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical">
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:osmand="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
|
@ -51,7 +52,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@null"
|
||||
android:text="Filling station"
|
||||
tools:text="Filling station"
|
||||
android:textColor="@color/color_white"
|
||||
android:textSize="@dimen/default_desc_text_size"/>
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
tools:text="Some title" />
|
||||
|
||||
<net.osmand.plus.widgets.TextViewEx
|
||||
android:id="@+id/description"
|
||||
android:id="@+id/title_description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/title"
|
||||
|
@ -60,6 +60,54 @@
|
|||
|
||||
</RelativeLayout>
|
||||
|
||||
<net.osmand.plus.widgets.TextViewEx
|
||||
android:id="@+id/primary_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/dialog_content_margin"
|
||||
android:letterSpacing="@dimen/description_letter_spacing"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="@dimen/content_padding"
|
||||
android:paddingLeft="@dimen/content_padding"
|
||||
android:paddingEnd="@dimen/content_padding"
|
||||
android:paddingRight="@dimen/content_padding"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="@dimen/default_list_text_size"
|
||||
android:visibility="gone"
|
||||
app:typeface="@string/font_roboto_regular"
|
||||
tools:text="@string/srtm_download_single_help_message"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<net.osmand.plus.widgets.TextViewEx
|
||||
android:id="@+id/secondary_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/content_padding_small"
|
||||
android:letterSpacing="@dimen/description_letter_spacing"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="@dimen/content_padding"
|
||||
android:paddingLeft="@dimen/content_padding"
|
||||
android:paddingEnd="@dimen/content_padding"
|
||||
android:paddingRight="@dimen/content_padding"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="@dimen/default_desc_text_size"
|
||||
android:visibility="gone"
|
||||
app:typeface="@string/font_roboto_regular"
|
||||
tools:text="@string/srtm_download_list_help_message"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<include
|
||||
layout="@layout/custom_radio_buttons"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/content_padding"
|
||||
android:layout_marginLeft="@dimen/content_padding"
|
||||
android:layout_marginEnd="@dimen/content_padding"
|
||||
android:layout_marginRight="@dimen/content_padding"
|
||||
android:layout_marginBottom="@dimen/content_padding_half"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/select_all_button"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -67,12 +115,12 @@
|
|||
android:background="?attr/selectableItemBackground"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="@dimen/bottom_sheet_list_item_height"
|
||||
android:paddingStart="@dimen/content_padding"
|
||||
android:paddingLeft="@dimen/content_padding"
|
||||
android:paddingTop="@dimen/content_padding_small"
|
||||
android:paddingEnd="@dimen/content_padding"
|
||||
android:paddingRight="@dimen/content_padding"
|
||||
android:paddingBottom="@dimen/content_padding_small"
|
||||
android:paddingStart="@dimen/content_padding"
|
||||
android:paddingEnd="@dimen/content_padding">
|
||||
android:paddingBottom="@dimen/content_padding_small">
|
||||
|
||||
<net.osmand.plus.widgets.TextViewEx
|
||||
android:id="@+id/check_box_title"
|
||||
|
|
|
@ -94,7 +94,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:background="@null"
|
||||
android:text="Split interval:"
|
||||
tools:text="Split interval:"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="@dimen/default_desc_text_size"
|
||||
osmand:typeface="@string/font_roboto_medium" />
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:background="?attr/spinnerListBackground"
|
||||
android:orientation="vertical">
|
||||
|
||||
|
@ -212,7 +213,7 @@
|
|||
android:paddingLeft="2dp"
|
||||
android:paddingEnd="0dp"
|
||||
android:paddingRight="0dp"
|
||||
android:text="Ukraine"
|
||||
tools:text="Ukraine"
|
||||
app:drawableEndCompat="@drawable/ic_action_arrow_drop_down"
|
||||
app:drawableRightCompat="@drawable/ic_action_arrow_drop_down" />
|
||||
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:osmand="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="48dp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="@dimen/list_content_padding"
|
||||
android:paddingRight="@dimen/list_content_padding"
|
||||
<LinearLayout xmlns:osmand="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="48dp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="@dimen/list_content_padding"
|
||||
android:paddingRight="@dimen/list_content_padding"
|
||||
android:paddingEnd="@dimen/list_content_padding"
|
||||
android:paddingStart="@dimen/list_content_padding">
|
||||
|
||||
|
@ -36,7 +36,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:text="Avoid selected roads"
|
||||
tools:text="Avoid selected roads"
|
||||
android:textSize="@dimen/default_list_text_size"/>
|
||||
|
||||
<TextView
|
||||
|
@ -44,7 +44,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:text="Select roads you want to avoid during navigation"
|
||||
tools:text="Select roads you want to avoid during navigation"
|
||||
android:textSize="@dimen/default_desc_text_size"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/main_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
|
@ -117,10 +118,17 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/content_padding"
|
||||
android:text="Actions"
|
||||
tools:text="Actions"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="@dimen/default_list_text_size" />
|
||||
|
||||
<include
|
||||
android:id="@+id/btn_refresh"
|
||||
layout="@layout/bottom_sheet_dialog_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/dialog_button_height"
|
||||
android:layout_marginTop="@dimen/content_padding_half" />
|
||||
|
||||
<include
|
||||
android:id="@+id/btn_backup"
|
||||
layout="@layout/bottom_sheet_dialog_button"
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:background="@drawable/popup_bg"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:background="@drawable/popup_bg"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text"
|
||||
|
@ -37,6 +38,6 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:text="UNDO"
|
||||
tools:text="UNDO"
|
||||
android:textColor="@color/popup_text_color"/>
|
||||
</LinearLayout>
|
|
@ -4123,7 +4123,11 @@
|
|||
<string name="shared_string_route_line">خط المسار</string>
|
||||
<string name="route_line_use_map_style_appearance">سيستخدم خط الطريق %1$s المحدد في نمط الخريطة المحدد: %2$s.</string>
|
||||
<string name="specify_color_for_map_mode">حدد لونًا لوضع الخريطة: %1$s.</string>
|
||||
<string name="release_4_0_beta">• تم نقل تحديثات OsmAnd Live إلى \"التنزيلات> التحديثات\"
|
||||
<string name="release_4_0_beta">• خيار مضاف لتنزيل خطوط الكنتور بالقدم
|
||||
\n
|
||||
\n• مخطط الطريق الأفقي: علامات تبويب tabs مضافة للتبديل بين النقاط أو الرسوم البيانية
|
||||
\n
|
||||
\n• تم نقل تحديثات OsmAnd Live إلى \"التنزيلات> التحديثات\"
|
||||
\n
|
||||
\n • يمكن تلوين المسارات الآن حسب الارتفاع، السرعة أو المنحدر.
|
||||
\n
|
||||
|
@ -4154,4 +4158,8 @@
|
|||
<string name="announce_when_exceeded">الإعلان عند التجاوز</string>
|
||||
<string name="user_points">نقاط المستخدم</string>
|
||||
<string name="output">الإخراج</string>
|
||||
<string name="srtm_unit_format">تنسيق وحدة خطوط الكنتور</string>
|
||||
<string name="srtm_download_list_help_message">OsmAnd يوفر بيانات الخطوط الكنتورية بالأمتار والقدم. ستحتاج إلى إعادة تنزيل الملف لتغيير التنسيق.</string>
|
||||
<string name="srtm_download_single_help_message">الرجاء تحديد التنسيق المطلوب. ستحتاج إلى إعادة تنزيل الملف لتغيير التنسيق.</string>
|
||||
<string name="shared_string_feet">قدم</string>
|
||||
</resources>
|
3911
OsmAnd/res/values-b+sr+Latn/phrases.xml
Normal file
3911
OsmAnd/res/values-b+sr+Latn/phrases.xml
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1088,7 +1088,7 @@
|
|||
<string name="lang_ja">Японская</string>
|
||||
<string name="lang_ko">Карэйская</string>
|
||||
<string name="lang_lv">Латышская</string>
|
||||
<string name="lang_lt">Летувісская</string>
|
||||
<string name="lang_lt">Летувіская</string>
|
||||
<string name="lang_mr">Маратхі</string>
|
||||
<string name="lang_no">Нарвежская (Bokmål)</string>
|
||||
<string name="lang_pl">Польская</string>
|
||||
|
|
|
@ -3837,4 +3837,8 @@
|
|||
<string name="track_has_no_altitude">Sporet indeholder ikke højdedata.</string>
|
||||
<string name="track_has_no_speed">Sporet indeholder ikke hastighedsdata.</string>
|
||||
<string name="select_another_colorization">Vælg en anden type farvelægning.</string>
|
||||
<string name="exit_number">Udgangsnummer</string>
|
||||
<string name="announce_when_exceeded">Meddelelse ved overskridelse</string>
|
||||
<string name="user_points">Bruger points</string>
|
||||
<string name="output">Resultat</string>
|
||||
</resources>
|
|
@ -4057,7 +4057,11 @@
|
|||
<string name="customize_route_line">Routenlinie anpassen</string>
|
||||
<string name="shared_string_route_line">Routenlinie</string>
|
||||
<string name="specify_color_for_map_mode">Legen Sie die Farbe für den Kartenmodus fest: %1$s.</string>
|
||||
<string name="release_4_0_beta">• OsmAnd Live-Updates nach \"Downloads > Updates\" verschoben
|
||||
<string name="release_4_0_beta">• Option zum Herunterladen von Höhenlinien in Fuß zugefügt
|
||||
\n
|
||||
\n• Routenplanung im Querformat: Registerkarten zum Umschalten zwischen Punkten oder Diagrammen hinzugefügt
|
||||
\n
|
||||
\n• OsmAnd Live-Updates nach \"Downloads > Updates\" verschoben
|
||||
\n
|
||||
\n• Tracks können nun nach Höhe, Geschwindigkeit oder Steigung eingefärbt werden.
|
||||
\n
|
||||
|
@ -4089,4 +4093,8 @@
|
|||
<string name="announce_when_exceeded">Meldung bei Überschreitung</string>
|
||||
<string name="user_points">Anwenderpunkte</string>
|
||||
<string name="output">Leistung</string>
|
||||
<string name="shared_string_feet">Fuß</string>
|
||||
<string name="srtm_unit_format">Einheit der Höhenlinien</string>
|
||||
<string name="srtm_download_list_help_message">OsmAnd liefert Höhenlinien-Daten in Metern und Fuß. Sie müssen die Datei erneut herunterladen, um das Format zu ändern.</string>
|
||||
<string name="srtm_download_single_help_message">Bitte wählen Sie das gewünschte Format. Sie müssen die Datei erneut herunterladen, um das Format zu ändern.</string>
|
||||
</resources>
|
|
@ -4054,7 +4054,11 @@
|
|||
<string name="customize_route_line">Alĝustigi linion de kurso</string>
|
||||
<string name="shared_string_route_line">Linio de kurso</string>
|
||||
<string name="route_line_use_map_style_appearance">Por la linio de kurso estos uzata %1$s difinita por la map‑stilo: %2$s.</string>
|
||||
<string name="release_4_0_beta">• ĝisdatigoj OsmAnd Live movitaj al “elŝutoj” → “ĝisdatigoj”
|
||||
<string name="release_4_0_beta">• eblo elŝuti nivelkurbojn en futoj
|
||||
\n
|
||||
\n• horizontala vido por “plani kurson”: aldonitaj langetoj por baskuli inter punktoj kaj diagramoj
|
||||
\n
|
||||
\n• ĝisdatigoj OsmAnd Live movitaj al “elŝutoj” → “ĝisdatigoj”
|
||||
\n
|
||||
\n• eblo kolorigi spurojn laŭ altitudo, rapido aŭ dekliveco
|
||||
\n
|
||||
|
@ -4085,4 +4089,8 @@
|
|||
<string name="exit_number">Numero de elirejo</string>
|
||||
<string name="user_points">Poentoj de uzanto</string>
|
||||
<string name="output">Eligo</string>
|
||||
<string name="shared_string_feet">futoj</string>
|
||||
<string name="srtm_unit_format">Unuo por nivelkurboj</string>
|
||||
<string name="srtm_download_list_help_message">OsmAnd liveras nivelkurbojn en metroj kaj en futoj. Ve devos reelŝuti la dosieron por ŝanĝi la formon.</string>
|
||||
<string name="srtm_download_single_help_message">Elektu la deziratan formon. Vi devos reelŝuti la dosieron por ŝanĝi la formon.</string>
|
||||
</resources>
|
|
@ -1460,4 +1460,5 @@
|
|||
<string name="poi_shoes_women">بانوان</string>
|
||||
<string name="poi_clothes_women">بانوان</string>
|
||||
<string name="poi_clothes_workwear">لباس کار</string>
|
||||
<string name="poi_greenhouse_horticulture">باغداری گلخانهای</string>
|
||||
</resources>
|
|
@ -3915,4 +3915,17 @@
|
|||
<string name="poi_pilates">Pilates</string>
|
||||
<string name="poi_jiu_jitsu">Ju-jitsu</string>
|
||||
<string name="poi_karate">Karaté</string>
|
||||
<string name="poi_zurkhaneh_sport">Gymnase Zurkhaneh</string>
|
||||
<string name="poi_cliff_diving">Plongeon du haut d\'une falaise</string>
|
||||
<string name="poi_hoops">Anneaux</string>
|
||||
<string name="poi_club_social">Club social</string>
|
||||
<string name="poi_plateau">Plateau</string>
|
||||
<string name="poi_speedway">Voie express</string>
|
||||
<string name="poi_gladed_yes">Satisfait : oui</string>
|
||||
<string name="poi_camp_pitch">Emplacement de camping</string>
|
||||
<string name="poi_mobile_library">Arrêt pour la bibliothèque mobile</string>
|
||||
<string name="poi_office_diplomatic">Bureau diplomatique</string>
|
||||
<string name="poi_horseshoes">Fers à cheval</string>
|
||||
<string name="poi_cycle_polo">Polo-vélo</string>
|
||||
<string name="poi_bay_filter">Type de baie</string>
|
||||
</resources>
|
|
@ -3275,7 +3275,7 @@
|
|||
<string name="add_new_profile_q">Ajouter le profil \'%1$s\' \?</string>
|
||||
<string name="save_heading">Inclure la direction</string>
|
||||
<string name="save_heading_descr">Inclure la direction de chaque point lors de l\'enregistrement d\'une trace.</string>
|
||||
<string name="rendering_attr_showCycleNodeNetworkRoutes_name">Afficher le réseau de nœuds de pistes cyclables</string>
|
||||
<string name="rendering_attr_showCycleNodeNetworkRoutes_name">Afficher les itinéraires cyclables du réseau de nœuds</string>
|
||||
<string name="rendering_value_walkingRoutesOSMCNodes_name">Nœuds de transport</string>
|
||||
<string name="personal_category_name">Personnel</string>
|
||||
<string name="ltr_or_rtl_combine_via_bold_point">%1$s • %2$s</string>
|
||||
|
@ -4040,9 +4040,13 @@
|
|||
<string name="lost_data_warning">Toutes les données non enregistrées seront perdues.</string>
|
||||
<string name="show_start_dialog">Afficher la boîte de dialogue de démarrage</string>
|
||||
<string name="trip_recording_show_start_dialog_setting">Si désactivé, l\'enregistrement débutera dès appui sur le gadget ou dans le menu (sans demande de confirmation).</string>
|
||||
<string name="release_4_0_beta">- Les mises à jour d\'OsmAnd Live ont été déplacées vers \"Téléchargements > Mises à jour\"
|
||||
<string name="release_4_0_beta">- Ajout d\'une option pour télécharger les Courbes de niveau en pieds
|
||||
\n
|
||||
\n - Les traces peuvent maintenant être colorées par altitude, vitesse ou pente.
|
||||
\n- Planification d\'itinéraires en paysage : ajout d’onglets pour basculer entre Points et Graphs
|
||||
\n
|
||||
\n- Les mises à jour d\'OsmAnd Live ont été déplacées vers \"Téléchargements > Mises à jour\"
|
||||
\n
|
||||
\n - Les traces peuvent maintenant être colorées par altitude, vitesse ou pente
|
||||
\n
|
||||
\n - Ajout d\'une option pour modifier l\'apparence de la ligne d\'itinéraire en navigation
|
||||
\n
|
||||
|
@ -4075,4 +4079,8 @@
|
|||
<string name="exit_number">Numéro de sortie</string>
|
||||
<string name="announce_when_exceeded">Annoncer en cas de dépassement</string>
|
||||
<string name="output">Sortie</string>
|
||||
<string name="srtm_download_list_help_message">OsmAnd fournit des données sur les courbes de niveau en mètres et en pieds. Vous devrez à nouveau télécharger le fichier pour modifier le format.</string>
|
||||
<string name="srtm_download_single_help_message">Veuillez sélectionner le format souhaité. Vous devrez à nouveau télécharger le fichier pour modifier le format.</string>
|
||||
<string name="srtm_unit_format">Format d\'unité pour les courbes de niveau</string>
|
||||
<string name="shared_string_feet">pieds</string>
|
||||
</resources>
|
|
@ -13,8 +13,8 @@
|
|||
<string name="shared_string_help">Súgó</string>
|
||||
<string name="accessibility_mode">Akadálymentes mód</string>
|
||||
<string name="accessibility_mode_descr">Bekapcsolja a fogyatékkal élőknek szóló funkciókat.</string>
|
||||
<string name="shared_string_on">Be</string>
|
||||
<string name="shared_string_off">Kikapcsolás</string>
|
||||
<string name="shared_string_on">Bekapcsolva</string>
|
||||
<string name="shared_string_off">Kikapcsolva</string>
|
||||
<string name="accessibility_default">Android rendszerbeállítások szerint</string>
|
||||
<string name="backToMenu">Vissza a menübe</string>
|
||||
<string name="zoomOut">Kicsinyít</string>
|
||||
|
@ -1318,7 +1318,7 @@
|
|||
<string name="shared_string_more">Bővebben…</string>
|
||||
<string name="route_descr_destination">Célpont</string>
|
||||
<string name="local_index_description">Egy meglévő elem adatainak megtekintéséhez koppintson rá, inaktiváláshoz vagy törléshez nyomja meg hosszan. Jelenlegi adatok az eszközön (%1$s szabad):</string>
|
||||
<string name="speed_limit_exceed">Sebességkorlátozás-tolerancia</string>
|
||||
<string name="speed_limit_exceed">Sebességkorlátozás túllépésének tűréshatára</string>
|
||||
<string name="speed_limit_exceed_message">Válassza ki a sebességkorlátozás tűréshatárát, amely fölött hangos figyelmeztetést fog kapni.</string>
|
||||
<string name="fav_point_emoticons_message">A Kedvenc hely át lett nevezve erre: %1$s, hogy a hangulatjeleket tartalmazó szöveget fájlba lehessen menteni.</string>
|
||||
<string name="print_route">Útvonal nyomtatása</string>
|
||||
|
@ -1470,7 +1470,7 @@
|
|||
<string name="shared_string_release">Megjelent</string>
|
||||
<string name="days_behind">napos</string>
|
||||
<string name="welmode_download_maps">Térképek letöltése</string>
|
||||
<string name="confirm_usage_speed_cameras">Néhány országban (Németország, Franciaország, Olaszország és még néhány) tilos a traffipax figyelmeztetés használata! Az OsmAnd nem vállal felelősséget a szabályok megsértéséért. Koppints az Igen gombra, ha jogosult vagy a funkció használatára.</string>
|
||||
<string name="confirm_usage_speed_cameras">Néhány országban (Németország, Franciaország, Olaszország stb.) tilos traffipax-riasztást használni! Az OsmAnd nem vállal felelősséget a szabályok megsértéséért. Koppintson az Igen gombra, ha jogosult a funkció használatára.</string>
|
||||
<string name="welcome_select_region">A jelzőtáblák és szabályok helyes értelmezéséhez jelölje ki a vezetési régiót:</string>
|
||||
<string name="welcome_text">Az OsmAnd lehetővé teszi a térképek és a navigáció offline használatát az egész világon.</string>
|
||||
<string name="current_route">Jelenlegi útvonal</string>
|
||||
|
@ -3583,7 +3583,7 @@
|
|||
<string name="uninstall_speed_cameras">Traffipaxok eltávolítása</string>
|
||||
<string name="shared_string_legal">Jogi</string>
|
||||
<string name="speed_camera_pois">Traffipax POI-k</string>
|
||||
<string name="speed_cameras_legal_descr">Bizonyos országokban és régiókban a traffipaxra figyelmeztető alkalmazások törvényileg tiltottak.
|
||||
<string name="speed_cameras_legal_descr">Bizonyos országokban és régiókban jogszabály tiltja a traffipaxra figyelmeztető alkalmazások használatát.
|
||||
\n
|
||||
\nDöntsön az ön országának törvényei alapján.
|
||||
\n
|
||||
|
@ -3592,14 +3592,14 @@
|
|||
\nVálassza az %2$s lehetőséget, hogy az összes traffipax-szal kapcsolatos adat: riasztások, értesítések, POI-k törlésre kerüljenek az OsmAnd teljes újratelepítéséig.</string>
|
||||
<string name="keep_active">Maradjanak</string>
|
||||
<string name="shared_string_uninstall">Eltávolít</string>
|
||||
<string name="speed_cameras_alert">Bizonyos országokban a traffipax riasztások törvényileg tiltottak.</string>
|
||||
<string name="speed_cameras_alert">Bizonyos országokban a törvény tiltja a traffipaxriasztást.</string>
|
||||
<string name="shared_string_bearing">Tájolás</string>
|
||||
<string name="item_deleted">%1$s törölve</string>
|
||||
<string name="speed_cameras_restart_descr">A traffipaxadatok végleges törléséhez indítsa újra az alkalmazást.</string>
|
||||
<string name="shared_string_uninstall_and_restart">Eltávolítás és Újraindítás</string>
|
||||
<string name="routing_attr_length_description">Adja meg az útvonalakon a járművekre vonatkozó hosszkorlátozást.</string>
|
||||
<string name="routing_attr_length_name">Hosszkorlátozás</string>
|
||||
<string name="speed_cameras_removed_descr">Az eszköz nem tartalmaz traffipax adatokat.</string>
|
||||
<string name="speed_cameras_removed_descr">Az eszköz nem tartalmaz traffipaxadatokat.</string>
|
||||
<string name="replace_all_desc">Az jelenlegi elemek lecserélésre kerülnek a fájlban lévőkre</string>
|
||||
<string name="keep_both_desc">Az importált elemek előtaggal lesznek hozzáadva</string>
|
||||
<string name="import_duplicates_description">Az OsmAnd már tartalmaz az importálttal megegyező nevű elemeket.
|
||||
|
@ -4044,7 +4044,11 @@
|
|||
<string name="lost_data_warning">Minden nem mentett adat törlődni fog.</string>
|
||||
<string name="show_start_dialog">Kezdő párbeszéd megjelenítése</string>
|
||||
<string name="trip_recording_show_start_dialog_setting">Ha le van tiltva, akkor a felvétel közvetlenül a widget vagy a menüelem megérintése után elindul, kihagyva a megerősítő párbeszédpanelt.</string>
|
||||
<string name="release_4_0_beta">• Az OsmAnd Live frissítések átköltöztek a „Letöltések> Frissítések” helyre
|
||||
<string name="release_4_0_beta">• Új lehetőség: szinvonalak letöltése (méter mellett) lábban
|
||||
\n
|
||||
\n• Útvonaltervezési környezet: hozzáadott fülek a pontok és grafikonok közötti váltáshoz
|
||||
\n
|
||||
\n• Az OsmAnd Live frissítések átköltöztek a „Letöltések> Frissítések” helyre
|
||||
\n
|
||||
\n• A nyomvonalak színezhetők magasság, sebesség vagy lejtés szerint.
|
||||
\n
|
||||
|
@ -4078,4 +4082,8 @@
|
|||
<string name="announce_when_exceeded">Értesítés túllépéskor</string>
|
||||
<string name="user_points">Felhasználói pontok</string>
|
||||
<string name="output">Teljesítmény</string>
|
||||
<string name="srtm_download_single_help_message">Kérjük, válassza ki a kívánt mértékegységet. A mértékegység módosításához újra le kell töltenie a fájlt.</string>
|
||||
<string name="srtm_download_list_help_message">Az OsmAnd méterben és lábban adja meg a szintvonalak magasságát. A mértékegység módosításához újra le kell töltenie a fájlt.</string>
|
||||
<string name="shared_string_feet">láb</string>
|
||||
<string name="srtm_unit_format">Szintvonalak mértékegysége</string>
|
||||
</resources>
|
|
@ -3927,4 +3927,5 @@
|
|||
<string name="poi_office_diplomatic">Sendiskrifstofa</string>
|
||||
<string name="poi_bay_filter">Gerð flóa</string>
|
||||
<string name="poi_plateau">Háslétta</string>
|
||||
<string name="poi_club_social">Félagsstarf</string>
|
||||
</resources>
|
|
@ -4052,7 +4052,11 @@
|
|||
<string name="rendering_attr_noNatureReserveBoundaries_name">Mörk náttúru</string>
|
||||
<string name="trip_recording_logging_interval_info">Millibil skráninga stillir tímabilið milli þess sem OsmAnd biður um staðsetningargögn.</string>
|
||||
<string name="trip_recording_show_start_dialog_setting">Ef þetta er óvirkt, mun upptaka hefjast strax eftir að ýtt er á hnappinn eða valmyndarfærsluna og staðfestingarglugga er þá sleppt.</string>
|
||||
<string name="release_4_0_beta">• Uppfærslur OsmAnd Live færðar í \"Sótt gögn > Uppfærslur\"
|
||||
<string name="release_4_0_beta">• Bætt við möguleika á að sækja hæðarlínur í fetum
|
||||
\n
|
||||
\n• Landslag í leiðaskipulagi: bætt við flipum til að skipta á milli punkta eða grafs
|
||||
\n
|
||||
\n• Uppfærslur OsmAnd Live færðar í \"Sótt gögn > Uppfærslur\"
|
||||
\n
|
||||
\n • Ferla er nú hægt að lita eftir hæð, hraða eða halla.
|
||||
\n
|
||||
|
@ -4091,4 +4095,8 @@
|
|||
<string name="announce_when_exceeded">Tilkynna þegar farið er yfir</string>
|
||||
<string name="user_points">Punktar notanda</string>
|
||||
<string name="map_quick_action_pattern">%1$s → …</string>
|
||||
<string name="shared_string_feet">fet</string>
|
||||
<string name="srtm_unit_format">Snið eininga hæðarlína</string>
|
||||
<string name="srtm_download_list_help_message">OsmAnd býður upp á hæðalínugögn í metrum og fetum. Þú þarft að sækja skrána aftur til að breyta sniðinu.</string>
|
||||
<string name="srtm_download_single_help_message">Veldu rétt snið eininga. Þú þarft að sækja skrána aftur til að breyta sniðinu.</string>
|
||||
</resources>
|
|
@ -4056,13 +4056,17 @@
|
|||
<string name="customize_route_line">להתאים קו מסלול אישית</string>
|
||||
<string name="shared_string_route_line">קו מסלול</string>
|
||||
<string name="specify_color_for_map_mode">לציין צבע למצב מפה: %1$s.</string>
|
||||
<string name="release_4_0_beta">• העדכונים החיים של OsmAnd הועברו אל „הורדות > עדכונים”
|
||||
<string name="release_4_0_beta">• נוספה אפשרות להוריד קווי מתאר ברגל
|
||||
\n
|
||||
\n • אפשר לצבוע מסלולים לפי גובה, מהירות או שיפוע.
|
||||
\n• תכנון מסלול אופקית: נוספו לשוניות למעבר בין נקודות לתרשימים
|
||||
\n
|
||||
\n • נוספה אפשרות לשנות את מראה קו מסלול הניווט
|
||||
\n• העדכונים החיים של OsmAnd הועברו אל „הורדות > עדכונים”
|
||||
\n
|
||||
\n • החלונית „הקלטת מסלול” עודכנה
|
||||
\n• אפשר לצבוע מסלולים לפי גובה, מהירות או שיפוע.
|
||||
\n
|
||||
\n• נוספה אפשרות לשנות את מראה קו מסלול הניווט
|
||||
\n
|
||||
\n• החלונית „הקלטת מסלול” עודכנה
|
||||
\n
|
||||
\n</string>
|
||||
<string name="osmand_live">OsmAnd חי</string>
|
||||
|
@ -4087,4 +4091,8 @@
|
|||
<string name="announce_when_exceeded">להכריז בחריגה</string>
|
||||
<string name="user_points">נקודות משתמש</string>
|
||||
<string name="output">פלט</string>
|
||||
<string name="shared_string_feet">רגל</string>
|
||||
<string name="srtm_unit_format">תצורת יחידת קווי מתאר</string>
|
||||
<string name="srtm_download_list_help_message">OsmAnd מספק נתוני קווי מתאר במטרים וברגל. יהיה עליך להוריד את הקובץ מחדש כדי לשנות את התצורה.</string>
|
||||
<string name="srtm_download_single_help_message">נא לבחור את התצורה הרצויה. יהיה עליך להוריד את הקובץ מחדש כדי לשנות את התצורה.</string>
|
||||
</resources>
|
|
@ -3843,7 +3843,7 @@ POIの更新は利用できません</string>
|
|||
<string name="subscription_expired_title">OsmAndLiveサブスクリプションの有効期限が切れました</string>
|
||||
<string name="subscription_paused_title">OsmAndLiveサブスクリプションが一時停止されました</string>
|
||||
<string name="subscription_on_hold_title">OsmAndLiveサブスクリプションは保留中です</string>
|
||||
<string name="markers_history">マーカーの履歴</string>
|
||||
<string name="markers_history">マーカー履歴</string>
|
||||
<string name="send_files_to_openstreetmap">GPXファイルをOpenStreetMapに送信</string>
|
||||
<string name="enter_text_separated">タグはカンマで区切って入力してください。</string>
|
||||
<string name="gpx_upload_public_visibility_descr">\"公開 \"状態は、追跡機能にてユーザーのGPS追跡、公開GPS追跡リスト、および生データのタイムスタンプ付き公開追跡リストに公開されることを意味します。APIを介して提供されるデータはユーザーの追跡ページを参照しません。追跡ポイントのタイムスタンプはパブリックGPS APIでは利用できず、また追跡ポイントは時系列に並んでいません。</string>
|
||||
|
|
|
@ -3927,4 +3927,5 @@
|
|||
<string name="poi_cliff_diving">Mergulho de falésia</string>
|
||||
<string name="poi_zurkhaneh_sport">Zurkhaneh</string>
|
||||
<string name="poi_bay_filter">Tipo de baía</string>
|
||||
<string name="poi_ultimate">Ultimate</string>
|
||||
</resources>
|
|
@ -4049,15 +4049,19 @@
|
|||
<string name="shared_string_route_line">Linha de rota</string>
|
||||
<string name="route_line_use_map_style_appearance">A linha de rota seria usada %1$s especificado no estilo de mapa selecionado: %2$s.</string>
|
||||
<string name="specify_color_for_map_mode">Especifique a cor para o modo de mapa: %1$s.</string>
|
||||
<string name="release_4_0_beta">• As atualizações do OsmAnd Live foram movidas para \"Downloads > Atualizações\"
|
||||
<string name="release_4_0_beta">"• Adicionada opção para baixar curvas de nível em pés
|
||||
\n
|
||||
\n• As trilhas agora podem ser coloridas por altitude, velocidade ou inclinação.
|
||||
\n • Plano de paisagem da rota: guias adicionadas para alternar entre pontos ou gráficos
|
||||
\n
|
||||
\n• Adicionada opção para alterar a aparência da linha da rota de navegação
|
||||
\n • As atualizações do OsmAnd Live foram movidas para \"Downloads> Atualizações\"
|
||||
\n
|
||||
\n• Caixa de diálogo \"Gravação de viagem\" atualizada
|
||||
\n • As trilhas agora podem ser coloridas por altitude, velocidade ou inclinação.
|
||||
\n
|
||||
\n</string>
|
||||
\n • Adicionada opção para alterar a aparência da linha da rota de navegação
|
||||
\n
|
||||
\n • Caixa de diálogo \"Gravação de viagem\" atualizada
|
||||
\n
|
||||
\n"</string>
|
||||
<string name="no_purchases">Você não tem nenhuma compra</string>
|
||||
<string name="new_device_account">Novo dispositivo / nova conta</string>
|
||||
<string name="contact_support_description">Se você tiver alguma dúvida, entre em contato conosco em %1$s.</string>
|
||||
|
@ -4081,4 +4085,8 @@
|
|||
<string name="user_points">Pontos do usuário</string>
|
||||
<string name="output">Saída</string>
|
||||
<string name="map_quick_action_pattern">%1$s → …</string>
|
||||
<string name="shared_string_feet">pés</string>
|
||||
<string name="srtm_unit_format">Formato da unidade de curvas de nível</string>
|
||||
<string name="srtm_download_list_help_message">OsmAnd fornece dados de linhas de contorno em metros e pés. Você precisará baixar novamente o arquivo para alterar o formato.</string>
|
||||
<string name="srtm_download_single_help_message">Selecione o formato necessário. Você precisará baixar novamente o arquivo para alterar o formato.</string>
|
||||
</resources>
|
|
@ -193,7 +193,7 @@
|
|||
<string name="poi_ford_stepping_stones">Ponte pedonal em pedras separadas</string>
|
||||
<string name="poi_mountain_pass">Passo de montanha</string>
|
||||
<string name="poi_gate">Portão</string>
|
||||
<string name="poi_city_wall">Muralha de cidade</string>
|
||||
<string name="poi_city_wall">Muralha/cerca de cidade</string>
|
||||
<string name="poi_lift_gate">Cancela elevatória</string>
|
||||
<string name="poi_toll_booth">Cabine de portagem</string>
|
||||
<string name="poi_border_control">Controlo aduaneiro</string>
|
||||
|
@ -411,10 +411,10 @@
|
|||
<string name="poi_water_tower">Reservatório elevado</string>
|
||||
<string name="poi_lock_gate">Comporta de eclusa</string>
|
||||
<string name="poi_waterway_turning_point">Ponto de viragem fluvial</string>
|
||||
<string name="poi_weir">Represa;Açude</string>
|
||||
<string name="poi_weir">Represa/açude;Represa;Açude</string>
|
||||
<string name="poi_dam">Barragem</string>
|
||||
<string name="poi_watermill">Moinho de água</string>
|
||||
<string name="poi_breakwater">Quebra-mar;Molhe</string>
|
||||
<string name="poi_breakwater">Quebra-mar/molhe;Quebra-mar;Molhe</string>
|
||||
<string name="poi_groyne">Espigão marítimo</string>
|
||||
<string name="poi_power_substation">Subestação</string>
|
||||
<string name="poi_power_transformer">Transformador</string>
|
||||
|
@ -565,14 +565,14 @@
|
|||
<string name="poi_capital">Sim</string>
|
||||
<string name="poi_town">Sede de concelho</string>
|
||||
<string name="poi_village">Sede de freguesia</string>
|
||||
<string name="poi_hamlet">Aldeia;Lugar</string>
|
||||
<string name="poi_hamlet">Aldeia/lugar;Aldeia;Lugar</string>
|
||||
<string name="poi_isolated_dwelling">Moradia isolada</string>
|
||||
<string name="poi_suburb">Subúrbio</string>
|
||||
<string name="poi_quarter">Zona de cidade</string>
|
||||
<string name="poi_neighbourhood">Bairro</string>
|
||||
<string name="poi_locality">Localidade</string>
|
||||
<string name="poi_place_allotments">Horta comunitária</string>
|
||||
<string name="poi_place_farm">Quinta;Fazenda</string>
|
||||
<string name="poi_place_farm">Quinta/fazenda;Quinta;Fazenda</string>
|
||||
<string name="poi_pharmacy">Farmácia</string>
|
||||
<string name="poi_hospital">Hospital</string>
|
||||
<string name="poi_doctors">Consultório médico</string>
|
||||
|
@ -637,13 +637,13 @@
|
|||
<string name="poi_australian_football">Futebol australiano</string>
|
||||
<string name="poi_base">Base jumping</string>
|
||||
<string name="poi_baseball">Beisebol</string>
|
||||
<string name="poi_basketball">Basquete</string>
|
||||
<string name="poi_basketball">Basquetebol</string>
|
||||
<string name="poi_beachvolleyball">Voleibol de praia</string>
|
||||
<string name="poi_bmx">BMX</string>
|
||||
<string name="poi_boules">Bocha</string>
|
||||
<string name="poi_bowls">Lawn bowls</string>
|
||||
<string name="poi_canadian_football">Futebol canadiano</string>
|
||||
<string name="poi_canoe">Canoa</string>
|
||||
<string name="poi_canoe">Canoagem</string>
|
||||
<string name="poi_chess">Xadrez</string>
|
||||
<string name="poi_climbing">Escalada</string>
|
||||
<string name="poi_cricket">Críquete</string>
|
||||
|
@ -694,7 +694,7 @@
|
|||
<string name="poi_boundary_stone">Marco de fronteira</string>
|
||||
<string name="poi_historic_cannon">Canhão histórico</string>
|
||||
<string name="poi_castle">Castelo</string>
|
||||
<string name="poi_city_gate">Portão da cidade</string>
|
||||
<string name="poi_city_gate">Portão/porta/arco de cidade</string>
|
||||
<string name="poi_fort">Forte</string>
|
||||
<string name="poi_fountain">Chafariz</string>
|
||||
<string name="poi_historic_ruins">Ruínas históricas</string>
|
||||
|
@ -707,7 +707,7 @@
|
|||
<string name="poi_aquarium">Aquário</string>
|
||||
<string name="poi_theme_park">Parque de diversões</string>
|
||||
<string name="poi_attraction">Atração turística</string>
|
||||
<string name="poi_tourism_yes">Objeto turístico</string>
|
||||
<string name="poi_tourism_yes">Elemento turístico</string>
|
||||
<string name="poi_attraction_amusement_ride">Atracão de feira</string>
|
||||
<string name="poi_attraction_animal">Animal (atração)</string>
|
||||
<string name="poi_attraction_big_wheel">Roda gigante</string>
|
||||
|
@ -721,7 +721,7 @@
|
|||
<string name="poi_attraction_water_slide">Tobogã aquático</string>
|
||||
<string name="poi_lodging">Alojamento</string>
|
||||
<string name="poi_hotel">Hotel</string>
|
||||
<string name="poi_guest_house">Pensão;Albergaria;Hospedaria;Estalagem;Residencial</string>
|
||||
<string name="poi_guest_house">Pensão/albergaria/residenial;Estalagem;Residencial;Albergaria;Hospedaria;Casa de hóspedes</string>
|
||||
<string name="poi_hostel">Hostel</string>
|
||||
<string name="poi_motel">Hotel estrada</string>
|
||||
<string name="poi_alpine_hut">Abrigo de montanha</string>
|
||||
|
@ -890,7 +890,7 @@
|
|||
<string name="poi_theatre_genre_circus">Circo</string>
|
||||
<string name="poi_gallery">Galeria de arte</string>
|
||||
<string name="poi_dance_floor">Pista de dança</string>
|
||||
<string name="poi_nightclub">Discoteca;Danceteria</string>
|
||||
<string name="poi_nightclub">Discoteca/danceteria;Discoteca;Danceteria</string>
|
||||
<string name="poi_stripclub">Clube de striptease</string>
|
||||
<string name="poi_ski_resort">Resort de esqui</string>
|
||||
<string name="poi_beach_resort">Resort de praia</string>
|
||||
|
@ -912,9 +912,9 @@
|
|||
<string name="poi_restaurant">Restaurante</string>
|
||||
<string name="poi_fast_food">Comida rápida</string>
|
||||
<string name="poi_bar">Bar</string>
|
||||
<string name="poi_pub">Taberna</string>
|
||||
<string name="poi_pub">Taberna/pub/tasca;Taberna;Tasco;Tasca;Pub;Boteco;Buteco;Botequim</string>
|
||||
<string name="poi_food_court">Praça de alimentação</string>
|
||||
<string name="poi_drinking_water">Bebedouro (água potável para beber)</string>
|
||||
<string name="poi_drinking_water">Bebedouro (água potável)</string>
|
||||
<string name="poi_barbecue">Churrasqueira</string>
|
||||
<string name="poi_craft_agricultural_engines">Máquinas agrícolas</string>
|
||||
<string name="poi_craft_basket_maker">Cesteiro</string>
|
||||
|
@ -977,8 +977,8 @@
|
|||
<string name="poi_car_pooling">Ponto de boleia solidária de carro</string>
|
||||
<string name="poi_boat_sharing">Ponto de barcos partilhados</string>
|
||||
<string name="poi_dock">Doca</string>
|
||||
<string name="poi_cutline">Linha de corte florestal;Atalhada;Linha corta-fogo</string>
|
||||
<string name="poi_toilets">Casa de banho;Banheiros</string>
|
||||
<string name="poi_cutline">Linha de corte florestal/atalhada;Atalhada;Linha corta-fogo;Linha de corte florestal</string>
|
||||
<string name="poi_toilets">Casa de banho;Banheiros;WC</string>
|
||||
<string name="poi_shower">Chuveiros públicos</string>
|
||||
<string name="poi_sauna">Sauna</string>
|
||||
<string name="poi_brothel">Bordel</string>
|
||||
|
@ -1002,7 +1002,7 @@
|
|||
<string name="poi_ridge">Cumeeira</string>
|
||||
<string name="poi_glacier">Glaciar</string>
|
||||
<string name="poi_sinkhole">Sumidouro</string>
|
||||
<string name="poi_waterfall">Queda de água;Cascata;Salto;Catarata</string>
|
||||
<string name="poi_waterfall">Queda de água/cascata/catarata/salto;Cascata;Salto;Catarata;Queda de água</string>
|
||||
<string name="poi_river">Rio</string>
|
||||
<string name="poi_stream">Ribeiro(a)</string>
|
||||
<string name="poi_rapids">Rápidos</string>
|
||||
|
@ -1246,8 +1246,8 @@
|
|||
<string name="poi_fee_no">Não</string>
|
||||
<string name="poi_drinking_water_yes">Sim</string>
|
||||
<string name="poi_drinking_water_no">Não</string>
|
||||
<string name="poi_supervised_yes">Vigiado</string>
|
||||
<string name="poi_supervised_no">Não vigiado</string>
|
||||
<string name="poi_supervised_yes">Vigiado: sim</string>
|
||||
<string name="poi_supervised_no">Vigiado: não</string>
|
||||
<string name="poi_seasonal_yes">Sim</string>
|
||||
<string name="poi_seasonal_no">Não</string>
|
||||
<string name="poi_seasonal_dry_season">Estação seca</string>
|
||||
|
@ -1433,7 +1433,7 @@
|
|||
<string name="poi_horse_riding">Centro equestre</string>
|
||||
<string name="poi_leisure_common">Área de lazer comum</string>
|
||||
<string name="poi_garden">Jardim</string>
|
||||
<string name="poi_heath">Charneca;Mato de vegetação rasteira</string>
|
||||
<string name="poi_heath">Charneca/mato de vegetação rasteira;Charneca;Mato de vegetação rasteira</string>
|
||||
<string name="poi_grass">Relvado</string>
|
||||
<string name="poi_grassland">Pradaria</string>
|
||||
<string name="poi_scrub">Matagal</string>
|
||||
|
@ -1771,7 +1771,7 @@
|
|||
<string name="poi_vending_toys">Brinquedos</string>
|
||||
<string name="poi_vending_ice_cream">Gelados</string>
|
||||
<string name="poi_vending_sim_cards">Cartões de telemóvel (SIM)</string>
|
||||
<string name="poi_branch">Filial;Sucursal</string>
|
||||
<string name="poi_branch">Filial/sucursal;Filial;Sucursal</string>
|
||||
<string name="poi_memorial_war">Memorial de guerra</string>
|
||||
<string name="poi_memorial_plaque">Placa comemorativa</string>
|
||||
<string name="poi_memorial_statue">Estátua</string>
|
||||
|
@ -2242,7 +2242,7 @@
|
|||
<string name="poi_step_condition_even">Condição dos degraus: regular</string>
|
||||
<string name="poi_step_condition_uneven">Condição dos degraus: irregular</string>
|
||||
<string name="poi_step_condition_rough">Condição dos degraus: acidentada</string>
|
||||
<string name="poi_cairn">Moledro;Moledo;Melédro;Mariola</string>
|
||||
<string name="poi_cairn">Moledro/mariola;Moledo;Melédro;Mariola;Moledro</string>
|
||||
<string name="poi_defibrillator">Desfibrilhador</string>
|
||||
<string name="poi_defibrillator_yes">Desfibrilhador: sim</string>
|
||||
<string name="poi_tomb_war_grave">Tipo: túmulo de guerra</string>
|
||||
|
@ -2360,7 +2360,7 @@
|
|||
<string name="poi_fortification_type_sconce">Tipo de fortificação: arandela</string>
|
||||
<string name="poi_fortification_type_ring_ditch">Tipo de fortificação: vala circular</string>
|
||||
<string name="poi_pa">Pa (assentamento fortificado maori)</string>
|
||||
<string name="poi_historic_farm">Quinta histórica;Fazenda histórica</string>
|
||||
<string name="poi_historic_farm">Quinta/fazenda histórica;Quinta histórica;Fazenda histórica</string>
|
||||
<string name="poi_historic_railway_station">Estação ferroviária histórica</string>
|
||||
<string name="poi_historic_threshing_floor">Eira histórica</string>
|
||||
<string name="poi_historic_gallows">Forca histórica</string>
|
||||
|
@ -2476,7 +2476,7 @@
|
|||
<string name="poi_health_specialty_behavior_yes">Comportamental</string>
|
||||
<string name="poi_health_specialty_palliative_medicine_yes">Medicina paliativa</string>
|
||||
<string name="poi_building_type_pyramid">Tipo de edifício: pirâmide</string>
|
||||
<string name="poi_fitness_centre">Ginásio;Academia desportiva</string>
|
||||
<string name="poi_fitness_centre">Ginásio/academia desportiva;Ginásio;Academia desportiva</string>
|
||||
<string name="poi_fitness">Exercício físico</string>
|
||||
<string name="poi_billiards">Bilhar</string>
|
||||
<string name="poi_microwave_oven_yes">Forno microondas: sim</string>
|
||||
|
@ -2866,9 +2866,9 @@
|
|||
<string name="poi_aquaculture_mussels">Aquicultura: mexilhões</string>
|
||||
<string name="poi_mdf">Rede de distribuição principal (MDF)</string>
|
||||
<string name="poi_min_age">Idade mínima</string>
|
||||
<string name="poi_organic_yes">Sim</string>
|
||||
<string name="poi_organic_no">Não</string>
|
||||
<string name="poi_organic_only">Unicamente</string>
|
||||
<string name="poi_organic_yes">Produtos orgânicos: sim</string>
|
||||
<string name="poi_organic_no">Produtos orgânicos: não</string>
|
||||
<string name="poi_organic_only">Produtos orgânicos: unicamente</string>
|
||||
<string name="poi_traffic_mirror">Espelho de tráfego</string>
|
||||
<string name="poi_diplomatic_consulate">Consulado</string>
|
||||
<string name="poi_diplomatic_consulate_general">Consulado geral</string>
|
||||
|
@ -3082,7 +3082,7 @@
|
|||
<string name="poi_nutrition_supplements">Suplementos alimentares</string>
|
||||
<string name="poi_photo_studio">Estúdio de fotografia</string>
|
||||
<string name="poi_cliff">Penhasco</string>
|
||||
<string name="poi_animal_keeping">Cativeiro de animais;Refúgio de animais</string>
|
||||
<string name="poi_animal_keeping">Refúgio de animais;Cativeiro de animais</string>
|
||||
<string name="poi_animal_keeping_horse">Cativeiro de animais: cavalos</string>
|
||||
<string name="poi_animal_keeping_sheep">Cativeiro de animais: ovelhas</string>
|
||||
<string name="poi_animal_keeping_type_paddock">Tipo: cercado</string>
|
||||
|
@ -3118,7 +3118,7 @@
|
|||
<string name="poi_stands">Estandes</string>
|
||||
<string name="poi_motorcycle_sales_yes">Vendas</string>
|
||||
<string name="poi_motorcycle_sales_no">Vendas: não</string>
|
||||
<string name="poi_motorcycle_sales_yes_used">Vendas: sim; usados</string>
|
||||
<string name="poi_motorcycle_sales_yes_used">Vendas: sim, usados</string>
|
||||
<string name="poi_motorcycle_sales_used">Vendas: usados</string>
|
||||
<string name="poi_motorcycle_rental_yes">Aluguer</string>
|
||||
<string name="poi_motorcycle_rental_no">Aluguer: não</string>
|
||||
|
@ -3203,7 +3203,7 @@
|
|||
<string name="poi_government_transportation">Instituição governamental de transportes</string>
|
||||
<string name="poi_government_legislative">Instituição legislativa governamental</string>
|
||||
<string name="poi_vhf">Canal VHF</string>
|
||||
<string name="poi_gorge">Desfiladeiro;Canhão</string>
|
||||
<string name="poi_gorge">Desfiladeiro/canhão;Desfiladeiro;Canhão</string>
|
||||
<string name="poi_couloir">Ravina</string>
|
||||
<string name="poi_mountain_area">Área montanhosa</string>
|
||||
<string name="poi_surface_clay">Argila</string>
|
||||
|
@ -3572,7 +3572,7 @@
|
|||
<string name="poi_changing_table_yes">Mesa muda-fraldas: sim</string>
|
||||
<string name="poi_changing_table_no">Mesa muda-fraldas: não</string>
|
||||
<string name="poi_changing_table_limited">Mesa muda-fraldas: limitada</string>
|
||||
<string name="poi_changing_table_location_room">Mesa muda-fraldas; sala</string>
|
||||
<string name="poi_changing_table_location_room">Mesa muda-fraldas: sala</string>
|
||||
<string name="poi_changing_table_location_male_toilet">Local da mesa muda-fraldas: WC masculino</string>
|
||||
<string name="poi_changing_table_location_female_toilet">Local da mesa muda-fraldas: WC feminino</string>
|
||||
<string name="poi_changing_table_location_unisex_toilet">Local da mesa muda-fraldas: WC unissexo</string>
|
||||
|
@ -3746,7 +3746,7 @@
|
|||
<string name="poi_community_gender_male">Sexo comunitário: masculino</string>
|
||||
<string name="poi_community_gender_mixed">Sexo comunitário: misto</string>
|
||||
<string name="poi_grave">Sepultura</string>
|
||||
<string name="poi_parking_space">Espaço de estacionamento</string>
|
||||
<string name="poi_parking_space">Lugar de estacionamento (1 veículo)</string>
|
||||
<string name="poi_url">URL</string>
|
||||
<string name="poi_volcano_type">Tipo</string>
|
||||
<string name="poi_volcano_status">Estado</string>
|
||||
|
@ -3810,7 +3810,7 @@
|
|||
<string name="poi_traffic_signals_vibration">Vibração</string>
|
||||
<string name="poi_city_block">Quarteirão</string>
|
||||
<string name="poi_borough">Município</string>
|
||||
<string name="poi_give_box">Caixa livre;Caixa de donativos;Give-box</string>
|
||||
<string name="poi_give_box">Caixa livre/de donativos;Give-box;Caixa livre;Caixa de donativos</string>
|
||||
<string name="poi_traffic_signals_arrow_no">Seta: não</string>
|
||||
<string name="poi_elevator">Elevador</string>
|
||||
<string name="poi_departures_board_timetable">Horário</string>
|
||||
|
@ -3917,8 +3917,8 @@
|
|||
<string name="poi_mobile_library">Posição de paragem da biblioteca itinerante</string>
|
||||
<string name="poi_piste_status_closed">Estado da pista: fechada</string>
|
||||
<string name="poi_piste_status_open">Estado da pista: aberta</string>
|
||||
<string name="poi_patrolled_no">Vigiado: não</string>
|
||||
<string name="poi_patrolled_yes">Vigiado: sim</string>
|
||||
<string name="poi_patrolled_no">Supervisionado: não</string>
|
||||
<string name="poi_patrolled_yes">Supervisionado: sim</string>
|
||||
<string name="poi_piste_name">Nome da pista</string>
|
||||
<string name="poi_piste_ski_jump">Salto com esqui</string>
|
||||
<string name="poi_wildlife_crossing">Passagem de vida selvagem</string>
|
||||
|
|
|
@ -371,7 +371,7 @@
|
|||
<string name="poi_error_io_error_template">Erro de entrada/saída na execução da ação {0}.</string>
|
||||
<string name="poi_error_info_not_loaded">As informações sobre o objeto não foram carregadas</string>
|
||||
<string name="poi_dialog_opening_hours">Aberto</string>
|
||||
<string name="poi_dialog_comment">Comentário</string>
|
||||
<string name="poi_dialog_comment">Comentar</string>
|
||||
<string name="poi_dialog_comment_default">Alterar POI</string>
|
||||
<string name="poi_dialog_other_tags_message">Todas as outras etiquetas são preservadas</string>
|
||||
<string name="default_buttons_commit">Enviar</string>
|
||||
|
@ -678,9 +678,9 @@
|
|||
<string name="loading_builds">A carregar compilações OsmAnd…</string>
|
||||
<string name="select_build_to_install">Selecionar a compilação OsmAnd a instalar</string>
|
||||
<string name="contribution_activity">Instalar versão</string>
|
||||
<string name="navigate_point_format_D">DDD.DDDDD</string>
|
||||
<string name="navigate_point_format_DM">DDD MM.MMM</string>
|
||||
<string name="navigate_point_format_DMS">DDD MM SS.S</string>
|
||||
<string name="navigate_point_format_D">GGG.GGGGG</string>
|
||||
<string name="navigate_point_format_DM">GGG MM.MMM</string>
|
||||
<string name="navigate_point_format_DMS">GGG MM SS.S</string>
|
||||
<string name="rendering_attr_noPolygons_description">Tornar transparentes todas as características do terreno no mapa.</string>
|
||||
<string name="rendering_attr_noPolygons_name">Polígonos</string>
|
||||
<string name="rendering_attr_appMode_name">Modo de visualização</string>
|
||||
|
@ -920,7 +920,7 @@
|
|||
<string name="srtm_plugin_name">Curvas de nível</string>
|
||||
<string name="download_select_map_types">Outros mapas</string>
|
||||
<string name="download_srtm_maps">Curvas de nível</string>
|
||||
<string name="rendering_attr_noAdminboundaries_name">Limites</string>
|
||||
<string name="rendering_attr_noAdminboundaries_name">Fronteiras regionais</string>
|
||||
<string name="rendering_attr_noAdminboundaries_description">Ocultar a visualização de limites regionais (níveis de administração 5 – 9).</string>
|
||||
<string name="recording_context_menu_show">Ver</string>
|
||||
<string name="item_unchecked">desmarcado</string>
|
||||
|
@ -1018,10 +1018,10 @@
|
|||
<string name="av_camera_focus_auto">Focagem automática</string>
|
||||
<string name="av_camera_focus_hiperfocal">Foco hiperfocal</string>
|
||||
<string name="av_camera_focus_edof">Profundidade de campo alargada (EDOF)</string>
|
||||
<string name="av_camera_focus_infinity">O foco está definido como infinito</string>
|
||||
<string name="av_camera_focus_infinity">Focar infinito</string>
|
||||
<string name="av_camera_focus_macro">Focagem macro (close-up)</string>
|
||||
<string name="av_camera_focus_continuous">A câmara tenta focar continuadamente</string>
|
||||
<string name="av_photo_play_sound">Reproduzir o som do obturador da câmara</string>
|
||||
<string name="av_photo_play_sound">Reproduzir som ao tirar fotografias</string>
|
||||
<string name="av_photo_play_sound_descr">Definir som ou silêncio ao fotografar.</string>
|
||||
<string name="driving_region_canada">Canadá</string>
|
||||
<string name="about_version">Versão:</string>
|
||||
|
@ -1435,7 +1435,7 @@
|
|||
<string name="rendering_attr_coloredBuildings_name">Colorir edifícios por tipo</string>
|
||||
<string name="rendering_attr_alpineHiking_name">Escala de montanhismo (SAC)</string>
|
||||
<string name="rendering_attr_hikingRoutesOSMC_name">Camada superior de símbolos de montanhismo</string>
|
||||
<string name="rendering_attr_showCycleRoutes_name">Mostrar rotas para bicicletas</string>
|
||||
<string name="rendering_attr_showCycleRoutes_name">Rotas para bicicletas</string>
|
||||
<string name="rendering_attr_hideBuildings_name">Edifícios</string>
|
||||
<string name="rendering_attr_trolleybusRoutes_name">Rotas de troleicarros</string>
|
||||
<string name="favorite_category_dublicate_message">Por favor, use um nome de categoria que ainda não exista.</string>
|
||||
|
@ -1513,7 +1513,7 @@
|
|||
<string name="share_osm_edits_subject">Edições OSM partilhadas via OsmAnd</string>
|
||||
<string name="read_more">Ler mais</string>
|
||||
<string name="whats_new">Novidades</string>
|
||||
<string name="rendering_attr_hideProposed_name">Objetos planeados</string>
|
||||
<string name="rendering_attr_hideProposed_name">Elementos com construção planeada</string>
|
||||
<string name="shared_string_upload">Enviar</string>
|
||||
<string name="osm_edit_created_poi">POI OSM adicionado</string>
|
||||
<string name="world_map_download_descr">Mapa base mundial (cobrindo o mundo inteiro em baixo nível de ampliação) ausente ou ultrapassado. Por favor, considere descarregá-lo para uma visão global.</string>
|
||||
|
@ -1754,7 +1754,7 @@
|
|||
<string name="map_markers">Marcadores</string>
|
||||
<string name="map_marker">Marcador de mapa</string>
|
||||
<string name="consider_turning_polygons_off">É recomendável desativar a renderização de polígono.</string>
|
||||
<string name="rendering_attr_showMtbRoutes_name">Mostrar trilhos de bicicletas de montanha</string>
|
||||
<string name="rendering_attr_showMtbRoutes_name">Trilhos de bicicletas BTT</string>
|
||||
<string name="show_polygons">Mostrar polígonos</string>
|
||||
<string name="find_parking">Encontrar estacionamento</string>
|
||||
<string name="shared_string_status">Situação</string>
|
||||
|
@ -1768,7 +1768,7 @@
|
|||
<string name="road_blocked">Estrada bloqueada</string>
|
||||
<string name="shared_string_select">Selecionar</string>
|
||||
<string name="switch_start_finish">Inverter ponto de partida e destino</string>
|
||||
<string name="rendering_attr_hideIcons_name">Ícones POI</string>
|
||||
<string name="rendering_attr_hideIcons_name">Ícones dos POI</string>
|
||||
<string name="item_removed">Item removido</string>
|
||||
<string name="n_items_removed">Itens removidos</string>
|
||||
<string name="shared_string_undo_all">Desfazer tudo</string>
|
||||
|
@ -1776,7 +1776,7 @@
|
|||
<string name="starting_point">Ponto de partida</string>
|
||||
<string name="rec_split">Divisão das gravações</string>
|
||||
<string name="rec_split_title">Usar divisão das gravações</string>
|
||||
<string name="rec_split_clip_length">Duração do recorte</string>
|
||||
<string name="rec_split_clip_length">Duração da divisão</string>
|
||||
<string name="rec_split_clip_length_desc">Limite de tempo máximo para clipes gravados.</string>
|
||||
<string name="lang_mk">Macedónio</string>
|
||||
<string name="lang_sh">Servo-Croata</string>
|
||||
|
@ -2013,7 +2013,7 @@
|
|||
<string name="routing_attr_height_obstacles_name">Utilizar dados de elevação</string>
|
||||
<string name="rendering_attr_depthContours_description">Mostrar pontos e contornos de profundidade.</string>
|
||||
<string name="rendering_attr_depthContours_name">Contornos de profundidade náuticos</string>
|
||||
<string name="show_transparency_seekbar">Mostra a transparência da barra de navegação</string>
|
||||
<string name="show_transparency_seekbar">Mostrar barra deslizante de transparência</string>
|
||||
<string name="shared_string_widgets">Widgets</string>
|
||||
<string name="rendering_attr_hideUnderground_name">Objetos subterrâneos</string>
|
||||
<string name="auto_split_recording_title">Dividir automaticamente as gravações após quebras</string>
|
||||
|
@ -2026,7 +2026,7 @@
|
|||
<string name="rendering_attr_contourWidth_description">Espessura das curvas de nível</string>
|
||||
<string name="rendering_attr_contourWidth_name">Espessura das curvas de nível</string>
|
||||
<string name="rendering_attr_hideWaterPolygons_description">Água</string>
|
||||
<string name="rendering_attr_hideWaterPolygons_name">Ocultar água</string>
|
||||
<string name="rendering_attr_hideWaterPolygons_name">Corpos de água largos</string>
|
||||
<string name="routing_attr_allow_motorway_name">Utilizar autoestradas</string>
|
||||
<string name="routing_attr_allow_motorway_description">Permitir autoestradas.</string>
|
||||
<string name="wiki_around">Artigos da Wikipédia próximos</string>
|
||||
|
@ -2127,7 +2127,7 @@
|
|||
<string name="get_osmand_live">Adquira o OsmAnd Live para desbloquear todas as funcionalidades: atualizações diárias de mapas com descarregamentos ilimitados, todas as extensões pagas e gratuitas, Wikipédia, Wikivoyage e muito mais.</string>
|
||||
<string name="unirs_render_descr">Alteração do estilo padrão para aumentar o contraste de caminhos pedestres e ciclovias. Usa cores clássicas do Mapnik.</string>
|
||||
<string name="shared_string_bookmark">Favorito</string>
|
||||
<string name="hide_full_description">Esconder descrição completa</string>
|
||||
<string name="hide_full_description">Ocultar descrição completa</string>
|
||||
<string name="show_full_description">Mostrar a descrição completa</string>
|
||||
<string name="thank_you_for_feedback">Obrigado pelos seus comentários</string>
|
||||
<string name="search_street">Procurar rua</string>
|
||||
|
@ -2261,7 +2261,7 @@
|
|||
<string name="first_intermediate_dest_description">Adicionar paragem inicial</string>
|
||||
<string name="subsequent_dest_description">Mover destino para cima e criar destino</string>
|
||||
<string name="show_closed_notes">Mostrar notas fechadas</string>
|
||||
<string name="switch_osm_notes_visibility_desc">Mostrar ou ocultar notas do OpenStreetMap no mapa.</string>
|
||||
<string name="switch_osm_notes_visibility_desc">Mostrar notas do OpenStreetMap.</string>
|
||||
<string name="gpx_file_desc">GPX - adequado para exportar para o JOSM ou outros editores do OSM.</string>
|
||||
<string name="osc_file_desc">OSC - adequado para exportar para o OSM.</string>
|
||||
<string name="shared_string_gpx_file">Ficheiro GPX</string>
|
||||
|
@ -3293,7 +3293,7 @@
|
|||
<string name="empty_filename">O nome do ficheiro está vazio</string>
|
||||
<string name="shared_string_revert">Reverter</string>
|
||||
<string name="quick_action_directions_from_desc">Um botão para centrar o ecrã no ponto de partida. Em seguida, solicitará para definir o destino ou acionar o cálculo da rota.</string>
|
||||
<string name="rendering_attr_showCycleNodeNetworkRoutes_name">Mostrar nós da rede de ciclovias</string>
|
||||
<string name="rendering_attr_showCycleNodeNetworkRoutes_name">Nós da rede de ciclovias</string>
|
||||
<string name="clear_confirmation_msg">Limpar %1$s\?</string>
|
||||
<string name="download_map_dialog">Diálogo de descarregar mapas</string>
|
||||
<string name="dialogs_and_notifications_title">Diálogos e notificações</string>
|
||||
|
@ -3440,9 +3440,9 @@
|
|||
<string name="create_custom_categories_list_promo">Altere a ordenação da lista e oculte categorias. Pode importar ou exportar todas as alterações com perfis.</string>
|
||||
<string name="rearrange_categories">Reorganizar categorias</string>
|
||||
<string name="osm_authorization_success">Autorização bem sucedida</string>
|
||||
<string name="multimedia_photo_play_sound">Som do obturador da câmara</string>
|
||||
<string name="multimedia_photo_play_sound">Som ao tirar fotografias</string>
|
||||
<string name="multimedia_use_system_camera">Usar aplicação do sistema</string>
|
||||
<string name="multimedia_rec_split_title">Divisão de gravação</string>
|
||||
<string name="multimedia_rec_split_title">Dividir gravações</string>
|
||||
<string name="reset_plugin_to_default">Repor configurações originais da extensão</string>
|
||||
<string name="monitoring_min_distance">Deslocamento mínimo</string>
|
||||
<string name="monitoring_min_accuracy">Precisão mínima</string>
|
||||
|
@ -3454,7 +3454,7 @@
|
|||
<string name="live_monitoring_time_buffer">Memória intermédia</string>
|
||||
<string name="monitoring_min_distance_descr_recommendation">Recomendação: uma configuração de 5 metros pode funcionar bem se não precisar capturar detalhes mais refinados do que isso e não quer capturar dados explicitamente enquanto estiver parado.</string>
|
||||
<string name="monitoring_min_distance_descr_side_effect">Efeitos colaterais: os períodos em que está parado não são registados em absoluto ou em apenas um ponto cada. Pequenos movimentos (no mundo real, por exemplo de lado, para marcar um possível desvio na sua viagem) podem ser filtrados. O seu ficheiro contém menos informações para pós-processamento e possui estatísticas piores ao filtrar pontos obviamente redundantes no tempo de gravação, mantendo potencialmente os artefactos causados por má receção ou efeitos do chipset GPS.</string>
|
||||
<string name="monitoring_min_distance_descr">Este filtro evita que sejam gravados pontos duplicados onde ocorrer muito pouco movimento real, cria uma aparência espacial mais agradável dos trilhos que não são processados posteriormente.</string>
|
||||
<string name="monitoring_min_distance_descr">Este filtro evita que sejam gravados pontos duplicados quando houver muito pouco movimento real e cria uma aparência espacial mais agradável dos trilhos que não são processados posteriormente.</string>
|
||||
<string name="monitoring_min_accuracy_descr_remark">Observação: se o GPS estava desligado imediatamente antes de uma gravação, o primeiro ponto medido pode ter uma precisão diminuída; portanto, no nosso código, podemos esperar um segundo antes da gravação de um ponto (ou gravar o melhor de três pontos consecutivos, etc.), mas isso ainda não foi implementado.</string>
|
||||
<string name="monitoring_min_accuracy_descr_recommendation">Recomendação: é difícil prever o que será gravado e o que não será, talvez seja melhor desativar este filtro.</string>
|
||||
<string name="monitoring_min_accuracy_descr_side_effect">Efeito colateral: como resultado da filtragem por precisão, os pontos podem estar totalmente ausentes por ex. debaixo de pontes, sob árvores, entre prédios altos ou com certas condições climáticas.</string>
|
||||
|
@ -3501,7 +3501,7 @@
|
|||
<string name="settings_item_import_error">Não foi possível importar de \'%1$s\'.</string>
|
||||
<string name="ui_customization_description">Personalize a quantidade de itens em \"Gaveta\", \"Configurar Mapa\" e \"Menu de Contexto\".
|
||||
\n
|
||||
\nDesative as extensões não utilizados para ocultar todos os seus controlos. %1$s.</string>
|
||||
\nDesative as extensões não utilizadas para ocultar todos os seus controlos. %1$s.</string>
|
||||
<string name="ui_customization_short_descr">Itens da gaveta, menu de contexto</string>
|
||||
<string name="ui_customization">Personalização da interface</string>
|
||||
<string name="shared_string_drawer">Gaveta</string>
|
||||
|
@ -3704,7 +3704,7 @@
|
|||
<string name="add_to_a_track">Adicionar a um trilho</string>
|
||||
<string name="add_hidden_group_info">O ponto adicionado não será visível no mapa, já que o grupo selecionado está escondido, pode encontrá-lo em \"%s\".</string>
|
||||
<string name="track_show_start_finish_icons">Mostrar ícones de início e fim</string>
|
||||
<string name="select_track_width">Selecionar espessura da linha do trilho</string>
|
||||
<string name="select_track_width">Espessura da linha do trilho</string>
|
||||
<string name="gpx_split_interval_descr">Selecione o intervalo em que as marcas com distância ou tempo no trilho serão mostradas.</string>
|
||||
<string name="gpx_split_interval_none_descr">Selecione a opção de divisão desejada: por tempo ou por distância.</string>
|
||||
<string name="shared_string_custom">Personalizado</string>
|
||||
|
@ -3960,13 +3960,17 @@
|
|||
<string name="routing_engine_vehicle_type_cycling_electric">Bicicleta elétrica</string>
|
||||
<string name="live_update_frequency_hour_variant">As atualizações ao mapa serão verificadas a todas as horas. Próxima %1$s em %2$s.</string>
|
||||
<string name="live_update_delete_updates_msg">Tem a certeza que quer eliminar todas as %s atualizações OsmAnd Live\?</string>
|
||||
<string name="release_4_0_beta">• Atualizações OsmAnd Live movidas para \"Descarregamentos > Atualizações\"
|
||||
<string name="release_4_0_beta">• Adicionada opção para descarregar curvas de nível em pés.
|
||||
\n
|
||||
\n• Planear rota: adicionadas abas para alternar entre pontos ou gráficos.
|
||||
\n
|
||||
\n• Atualizações OsmAnd Live movidas para \"Descarregamentos > Atualizações\"
|
||||
\n
|
||||
\n• Os trilhos podem ser agora coloridos conforme a altitude, velocidade e declive.
|
||||
\n
|
||||
\n• Adicionada opção para alterar a aparência da linha de rota de navegação
|
||||
\n• Adicionada opção para alterar a aparência da linha de rota de navegação.
|
||||
\n
|
||||
\n• Janela de diálogo \"Gravação do trilho\" atualizada
|
||||
\n• Janela de diálogo \"Gravação do trilho\" atualizada.
|
||||
\n
|
||||
\n</string>
|
||||
<string name="routing_attr_height_obstacles_description">O roteamento pode evitar subidas íngremes.</string>
|
||||
|
@ -4025,7 +4029,7 @@
|
|||
<string name="no_purchases">Não tem compras</string>
|
||||
<string name="contact_support_description">Se tiver alguma dúvida, contacte-nos em %1$s.</string>
|
||||
<string name="announcement_time_intervals">Intervalos de tempo e distância</string>
|
||||
<string name="map_widget_distance_by_tap">Distância por toque</string>
|
||||
<string name="map_widget_distance_by_tap">Distância com 2 dedos</string>
|
||||
<string name="contact_support">Contacte o suporte</string>
|
||||
<string name="troubleshooting_description">Por favor siga este link se tiver algum problema com assinaturas.</string>
|
||||
<string name="update_all_maps_added">Atualizar todos os mapas para %1$s\?</string>
|
||||
|
@ -4089,4 +4093,8 @@
|
|||
<string name="announce_when_exceeded">Anunciar quando ultrapassado</string>
|
||||
<string name="user_points">Pontos do utilizador</string>
|
||||
<string name="output">Saída</string>
|
||||
<string name="shared_string_feet">pés</string>
|
||||
<string name="srtm_download_list_help_message">O OsmAnd fornece dados de curvas de nível em metros e pés. Terá de descarregar novamente o ficheiro para alterar o formato.</string>
|
||||
<string name="srtm_unit_format">Formato de unidades de curvas de nível</string>
|
||||
<string name="srtm_download_single_help_message">Selecione o formato necessário. Terá de descarregar novamente o ficheiro para alterar o formato.</string>
|
||||
</resources>
|
|
@ -2872,7 +2872,7 @@
|
|||
<string name="sit_on_the_stop">Посадка на остановке</string>
|
||||
<string name="use_osm_live_public_transport_description">Включить общественный транспорт с учётом автообновлений OsmAnd Live.</string>
|
||||
<string name="use_osm_live_public_transport">Общественный транспорт OsmAnd Live</string>
|
||||
<string name="transfers_size">%1$d пересадки</string>
|
||||
<string name="transfers_size">пересадки: %1$d</string>
|
||||
<string name="rendering_attr_surface_unpaved_name">Грунтовая</string>
|
||||
<string name="rendering_attr_surface_sand_name">Песок</string>
|
||||
<string name="rendering_attr_surface_grass_name">Трава</string>
|
||||
|
@ -3263,7 +3263,7 @@
|
|||
<string name="selected_profile">Выбранный профиль</string>
|
||||
<string name="personal_category_name">Персональный</string>
|
||||
<string name="shared_string_downloading_formatted">Скачивание %s</string>
|
||||
<string name="rendering_value_thick_name">Толсто</string>
|
||||
<string name="rendering_value_thick_name">Толстая</string>
|
||||
<string name="default_speed_dialog_msg">Используется для оценки времени прибытия для неизвестного типа дорог и ограничения скорости для всех дорог (может изменить маршрут)</string>
|
||||
<string name="routing_attr_allow_intermediate_name">Разрешить промежуточные маршруты</string>
|
||||
<string name="routing_attr_allow_advanced_name">Разрешить расширенные маршруты</string>
|
||||
|
@ -3302,7 +3302,7 @@
|
|||
<string name="rendering_attr_piste_difficulty_connection_name">Соединение</string>
|
||||
<string name="simulate_your_location_gpx_descr">Симулировать свою позицию используя записанный GPX трек.</string>
|
||||
<string name="route_start_point">Начало маршрута</string>
|
||||
<string name="shared_string_revert">Вернуться</string>
|
||||
<string name="shared_string_revert">Сброс</string>
|
||||
<string name="suggested_maps_descr">Эти карты необходимо использовать с плагином.</string>
|
||||
<string name="added_profiles">Добавленные профили</string>
|
||||
<string name="added_profiles_descr">Профили, добавленные плагином</string>
|
||||
|
@ -4056,7 +4056,11 @@
|
|||
<string name="next_billing_date">Следующая дата оплаты: %1$s</string>
|
||||
<string name="osmand_live">OsmAnd Live</string>
|
||||
<string name="annual_subscription">Годовая подписка</string>
|
||||
<string name="release_4_0_beta">• Обновления OsmAnd Live перемещены в «Загрузка карт» → «Обновления».
|
||||
<string name="release_4_0_beta">• Добавлена возможность скачать контурные линии в футах.
|
||||
\n
|
||||
\n• Планирование маршрута: добавлены вкладки для переключения между точками и графиками.
|
||||
\n
|
||||
\n• Обновления OsmAnd Live перемещены в «Загрузка карт» → «Обновления».
|
||||
\n
|
||||
\n• Теперь треки можно раскрашивать по высоте, скорости или уклону.
|
||||
\n
|
||||
|
@ -4087,4 +4091,8 @@
|
|||
<string name="output">Вывод</string>
|
||||
<string name="map_quick_action_pattern">%1$s → …</string>
|
||||
<string name="exit_number">Номер съезда</string>
|
||||
<string name="srtm_unit_format">Формат единиц на контурных линиях</string>
|
||||
<string name="srtm_download_single_help_message">Выберите необходимый формат. Для изменения формата потребуется повторно загрузить файл.</string>
|
||||
<string name="shared_string_feet">футы</string>
|
||||
<string name="srtm_download_list_help_message">OsmAnd предоставляет данные изолиний в метрах и футах. Вам нужно будет повторно загрузить файл, чтобы изменить формат.</string>
|
||||
</resources>
|
|
@ -4045,7 +4045,11 @@
|
|||
<string name="lost_data_warning">Všetky neuložené údaje budú stratené.</string>
|
||||
<string name="show_start_dialog">Zobraziť úvodné okno</string>
|
||||
<string name="trip_recording_show_start_dialog_setting">Ak je vypnuté, záznam začne hneď po stlačení nástroja alebo položky v menu a preskočí okno nastavenia.</string>
|
||||
<string name="release_4_0_beta">• Aktualizácie OsmAnd Live presunuté do \"Sťahovania > Aktualizácie\"
|
||||
<string name="release_4_0_beta">• Pridaná možnosť stiahnutia Vrstevníc v stopách
|
||||
\n
|
||||
\n• Plánovať trasy: pridané prepínače medzi bodmi a grafmi
|
||||
\n
|
||||
\n• Aktualizácie OsmAnd Live presunuté do \"Sťahovania > Aktualizácie\"
|
||||
\n
|
||||
\n • Stopy je teraz možné vyfarbiť podľa nadmorskej výšky, rýchlosti alebo sklonu svahu.
|
||||
\n
|
||||
|
@ -4081,4 +4085,8 @@
|
|||
<string name="user_points">Body používateľa</string>
|
||||
<string name="output">Výstup</string>
|
||||
<string name="map_quick_action_pattern">%1$s → …</string>
|
||||
<string name="shared_string_feet">stopy</string>
|
||||
<string name="srtm_unit_format">Formát jednotiek vrstevníc</string>
|
||||
<string name="srtm_download_list_help_message">OsmAnd poskytuje údaje vrstevníc v metroch a stopách. Budete musieť znovu stiahnuť súbor pre zmenu formátu.</string>
|
||||
<string name="srtm_download_single_help_message">Prosím zvoľte požadovaný formát. Budete musieť znovu stiahnuť súbor pre zmenu formátu.</string>
|
||||
</resources>
|
|
@ -4072,7 +4072,11 @@
|
|||
<string name="in_grace_period">Ek süre içinde</string>
|
||||
<string name="on_hold">Beklemede</string>
|
||||
<string name="expired">Süresi doldu</string>
|
||||
<string name="release_4_0_beta">• OsmAnd Live güncellemeleri \"İndirmeler> Güncellemeler\" bölümüne taşındı
|
||||
<string name="release_4_0_beta">• Eş yükselti eğrilerini fit cinsinden indirme seçeneği eklendi
|
||||
\n
|
||||
\n • Güzergah Planla görünümü: noktalar veya grafikler arasında geçiş yapmak için sekmeler eklendi
|
||||
\n
|
||||
\n • OsmAnd Live güncellemeleri \"İndirmeler > Güncellemeler\" bölümüne taşındı
|
||||
\n
|
||||
\n • Yollar artık rakım, hız veya eğime göre renklendirilebilir
|
||||
\n
|
||||
|
@ -4087,4 +4091,8 @@
|
|||
<string name="user_points">Kullanıcı puanları</string>
|
||||
<string name="output">Çıkış</string>
|
||||
<string name="map_quick_action_pattern">%1$s → …</string>
|
||||
<string name="shared_string_feet">fit</string>
|
||||
<string name="srtm_unit_format">Eş yükselti eğrileri birimi biçimi</string>
|
||||
<string name="srtm_download_list_help_message">OsmAnd, metre ve fit cinsinden eş yükselti eğrileri verileri sağlar. Biçimi değiştirmek için dosyayı yeniden indirmeniz gerekecek.</string>
|
||||
<string name="srtm_download_single_help_message">Lütfen gerekli biçimi seçin. Biçimi değiştirmek için dosyayı yeniden indirmeniz gerekecek.</string>
|
||||
</resources>
|
|
@ -4053,7 +4053,11 @@
|
|||
<string name="shared_string_route_line">Лінія маршруту</string>
|
||||
<string name="route_line_use_map_style_appearance">Лінія маршруту застосовуватиме %1$s, вказаний у вибраному стилі мапи: %2$s.</string>
|
||||
<string name="specify_color_for_map_mode">Вкажіть колір для режиму мапи: %1$s.</string>
|
||||
<string name="release_4_0_beta">• Оновлення OsmAnd Live переміщено до «Завантаження >Оновлення»
|
||||
<string name="release_4_0_beta">• Додана можливість завантаження контурних ліній у футах
|
||||
\n
|
||||
\n• Ландшафтне планування маршруту: додані вкладки для перемикання між точками або графіками
|
||||
\n
|
||||
\n• Оновлення OsmAnd Live переміщено до «Завантаження >Оновлення»
|
||||
\n
|
||||
\n• Тепер треки можуть бути забарвлені за висотою, швидкістю або нахилом.
|
||||
\n
|
||||
|
@ -4084,4 +4088,8 @@
|
|||
<string name="announce_when_exceeded">Повідомляти про перевищення</string>
|
||||
<string name="user_points">Користувацькі точки</string>
|
||||
<string name="output">Вивід</string>
|
||||
<string name="srtm_download_list_help_message">OsmAnd надає дані горизонталей в метрах і футах. Щоб змінити формат, потрібно повторно завантажити файл.</string>
|
||||
<string name="srtm_unit_format">Формат одиниць вимірювання горизонталей</string>
|
||||
<string name="shared_string_feet">фути</string>
|
||||
<string name="srtm_download_single_help_message">Виберіть потрібний формат. Щоб змінити формат, потрібно повторно завантажувати файл.</string>
|
||||
</resources>
|
|
@ -4047,7 +4047,11 @@
|
|||
<string name="shared_string_route_line">路線</string>
|
||||
<string name="route_line_use_map_style_appearance">路線將會使用 %1$s 在選定的地圖樣式上指定的:%2$s。</string>
|
||||
<string name="specify_color_for_map_mode">指定地圖模式的顏色:%1$s。</string>
|
||||
<string name="release_4_0_beta">• OsmAnd Live 更新移動至「下載 > 更新」
|
||||
<string name="release_4_0_beta">• 新增以英呎為單位下載等高線
|
||||
\n
|
||||
\n • 規劃路線樣式:新增切換點與圖形的分頁
|
||||
\n
|
||||
\n • OsmAnd Live 更新移動至「下載 > 更新」
|
||||
\n
|
||||
\n • 軌跡現在可以使用海拔、速度或坡度來填色
|
||||
\n
|
||||
|
@ -4079,4 +4083,8 @@
|
|||
<string name="user_points">使用者點</string>
|
||||
<string name="output">輸出</string>
|
||||
<string name="map_quick_action_pattern">%1$s → …</string>
|
||||
<string name="shared_string_feet">英呎</string>
|
||||
<string name="srtm_unit_format">等高線單位格式</string>
|
||||
<string name="srtm_download_list_help_message">OsmAnd 提供以公尺與英呎為單位的等高線資料。您將必須重新下載檔案以變更格式。</string>
|
||||
<string name="srtm_download_single_help_message">請選取需要格式。您將必須重新下載檔案以變更格式。</string>
|
||||
</resources>
|
|
@ -12,12 +12,21 @@
|
|||
|
||||
-->
|
||||
|
||||
<string name="shared_string_max_height">Max. height</string>
|
||||
<string name="shared_string_min_height">Min. height</string>
|
||||
<string name="route_line_use_gradient_coloring">Route line will be colorized depending on the elevation profile of the route.</string>
|
||||
<string name="output">Output</string>
|
||||
<string name="user_points">User points</string>
|
||||
<string name="announce_when_exceeded">Announce when exceeded</string>
|
||||
<string name="exit_number">Exit number</string>
|
||||
<string name="srtm_download_single_help_message">Please select the needed format. You will need to re-download the file to change the format.</string>
|
||||
<string name="srtm_download_list_help_message">OsmAnd provides contour lines data in meters and feet. You will need to re-download the file to change the format.</string>
|
||||
<string name="srtm_unit_format">Contour lines unit format</string>
|
||||
<string name="shared_string_feet">feet</string>
|
||||
<string name="update_all_maps_added">Update all maps added to %1$s?</string>
|
||||
<string name="release_4_0_beta">
|
||||
• Added option to download Contour lines in feet\n\n
|
||||
• Plan Route landscape: added tabs to switch between points or graphs\n\n
|
||||
• OsmAnd Live updates moved to \"Downloads > Updates\"\n\n
|
||||
• Tracks now could be colorizing by altitude, speed, or slope.\n\n
|
||||
• Added option to change the appearance of the navigation route line\n\n
|
||||
|
|
|
@ -3,10 +3,10 @@ package net.osmand;
|
|||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.os.AsyncTask;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import net.osmand.osm.io.NetworkUtils;
|
||||
import net.osmand.plus.OsmandApplication;
|
||||
|
@ -38,6 +38,8 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
public class AndroidNetworkUtils {
|
||||
|
@ -46,23 +48,43 @@ public class AndroidNetworkUtils {
|
|||
private static final Log LOG = PlatformUtil.getLog(AndroidNetworkUtils.class);
|
||||
|
||||
public interface OnRequestResultListener {
|
||||
void onResult(String result);
|
||||
void onResult(@Nullable String result, @Nullable String error);
|
||||
}
|
||||
|
||||
public interface OnFilesUploadCallback {
|
||||
@Nullable
|
||||
Map<String, String> getAdditionalParams(@NonNull File file);
|
||||
void onFileUploadProgress(@NonNull File file, int percent);
|
||||
void onFileUploadDone(@NonNull File file);
|
||||
void onFilesUploadDone(@NonNull Map<File, String> errors);
|
||||
}
|
||||
|
||||
public interface OnFilesDownloadCallback {
|
||||
@Nullable
|
||||
Map<String, String> getAdditionalParams(@NonNull File file);
|
||||
void onFileDownloadProgress(@NonNull File file, int percent);
|
||||
@WorkerThread
|
||||
void onFileDownloadedAsync(@NonNull File file);
|
||||
|
||||
void onFileDownloadDone(@NonNull File file);
|
||||
void onFilesDownloadDone(@NonNull Map<File, String> errors);
|
||||
}
|
||||
|
||||
public static class RequestResponse {
|
||||
private Request request;
|
||||
private String response;
|
||||
private final Request request;
|
||||
private final String response;
|
||||
private final String error;
|
||||
|
||||
RequestResponse(@NonNull Request request, @Nullable String response) {
|
||||
this.request = request;
|
||||
this.response = response;
|
||||
this.error = null;
|
||||
}
|
||||
|
||||
RequestResponse(@NonNull Request request, @Nullable String response, @Nullable String error) {
|
||||
this.request = request;
|
||||
this.response = response;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public Request getRequest() {
|
||||
|
@ -72,41 +94,70 @@ public class AndroidNetworkUtils {
|
|||
public String getResponse() {
|
||||
return response;
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnRequestsResultListener {
|
||||
void onResult(@NonNull List<RequestResponse> results);
|
||||
public interface OnSendRequestsListener {
|
||||
void onRequestSent(@NonNull RequestResponse response);
|
||||
void onRequestsSent(@NonNull List<RequestResponse> results);
|
||||
}
|
||||
|
||||
public static void sendRequestsAsync(final OsmandApplication ctx,
|
||||
final List<Request> requests,
|
||||
final OnRequestsResultListener listener) {
|
||||
public static void sendRequestsAsync(@Nullable final OsmandApplication ctx,
|
||||
@NonNull final List<Request> requests,
|
||||
@Nullable final OnSendRequestsListener listener) {
|
||||
sendRequestsAsync(ctx, requests, listener, AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
new AsyncTask<Void, Void, List<RequestResponse>>() {
|
||||
public static void sendRequestsAsync(@Nullable final OsmandApplication ctx,
|
||||
@NonNull final List<Request> requests,
|
||||
@Nullable final OnSendRequestsListener listener,
|
||||
final Executor executor) {
|
||||
|
||||
new AsyncTask<Void, RequestResponse, List<RequestResponse>>() {
|
||||
|
||||
@Override
|
||||
protected List<RequestResponse> doInBackground(Void... params) {
|
||||
List<RequestResponse> responses = new ArrayList<>();
|
||||
for (Request request : requests) {
|
||||
RequestResponse requestResponse;
|
||||
try {
|
||||
String response = sendRequest(ctx, request.getUrl(), request.getParameters(),
|
||||
request.getUserOperation(), request.isToastAllowed(), request.isPost());
|
||||
responses.add(new RequestResponse(request, response));
|
||||
final String[] response = {null, null};
|
||||
sendRequest(ctx, request.getUrl(), request.getParameters(),
|
||||
request.getUserOperation(), request.isToastAllowed(), request.isPost(), new OnRequestResultListener() {
|
||||
@Override
|
||||
public void onResult(@Nullable String result, @Nullable String error) {
|
||||
response[0] = result;
|
||||
response[1] = error;
|
||||
}
|
||||
});
|
||||
requestResponse = new RequestResponse(request, response[0], response[1]);
|
||||
} catch (Exception e) {
|
||||
responses.add(new RequestResponse(request, null));
|
||||
requestResponse = new RequestResponse(request, null, "Unexpected error");
|
||||
}
|
||||
responses.add(requestResponse);
|
||||
publishProgress(requestResponse);
|
||||
}
|
||||
return responses;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(@NonNull List<RequestResponse> results) {
|
||||
protected void onProgressUpdate(RequestResponse... values) {
|
||||
if (listener != null) {
|
||||
listener.onResult(results);
|
||||
listener.onRequestSent(values[0]);
|
||||
}
|
||||
}
|
||||
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
|
||||
@Override
|
||||
protected void onPostExecute(@NonNull List<RequestResponse> results) {
|
||||
if (listener != null) {
|
||||
listener.onRequestsSent(results);
|
||||
}
|
||||
}
|
||||
|
||||
}.executeOnExecutor(executor, (Void) null);
|
||||
}
|
||||
|
||||
public static void sendRequestAsync(final OsmandApplication ctx,
|
||||
|
@ -116,26 +167,45 @@ public class AndroidNetworkUtils {
|
|||
final boolean toastAllowed,
|
||||
final boolean post,
|
||||
final OnRequestResultListener listener) {
|
||||
sendRequestAsync(ctx, url, parameters, userOperation, toastAllowed, post, listener,
|
||||
AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
new AsyncTask<Void, Void, String>() {
|
||||
public static void sendRequestAsync(final OsmandApplication ctx,
|
||||
final String url,
|
||||
final Map<String, String> parameters,
|
||||
final String userOperation,
|
||||
final boolean toastAllowed,
|
||||
final boolean post,
|
||||
final OnRequestResultListener listener,
|
||||
final Executor executor) {
|
||||
new AsyncTask<Void, Void, String[]>() {
|
||||
|
||||
@Override
|
||||
protected String doInBackground(Void... params) {
|
||||
protected String[] doInBackground(Void... params) {
|
||||
final String[] res = {null, null};
|
||||
try {
|
||||
return sendRequest(ctx, url, parameters, userOperation, toastAllowed, post);
|
||||
sendRequest(ctx, url, parameters, userOperation, toastAllowed, post, new OnRequestResultListener() {
|
||||
@Override
|
||||
public void onResult(@Nullable String result, @Nullable String error) {
|
||||
res[0] = result;
|
||||
res[1] = error;
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
// ignore
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(String response) {
|
||||
protected void onPostExecute(String[] response) {
|
||||
if (listener != null) {
|
||||
listener.onResult(response);
|
||||
listener.onResult(response[0], response[1]);
|
||||
}
|
||||
}
|
||||
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
|
||||
}.executeOnExecutor(executor, (Void) null);
|
||||
}
|
||||
|
||||
public static void downloadFileAsync(final String url,
|
||||
|
@ -146,7 +216,7 @@ public class AndroidNetworkUtils {
|
|||
|
||||
@Override
|
||||
protected String doInBackground(Void... params) {
|
||||
return downloadFile(url, fileToSave);
|
||||
return downloadFile(url, fileToSave, false, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -158,11 +228,105 @@ public class AndroidNetworkUtils {
|
|||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
|
||||
}
|
||||
|
||||
public static String sendRequest(OsmandApplication ctx, String url, Map<String, String> parameters,
|
||||
String userOperation, boolean toastAllowed, boolean post) {
|
||||
public static void downloadFilesAsync(final @NonNull String url,
|
||||
final @NonNull List<File> files,
|
||||
final @NonNull Map<String, String> parameters,
|
||||
final @Nullable OnFilesDownloadCallback callback) {
|
||||
downloadFilesAsync(url, files, parameters, callback, AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
public static void downloadFilesAsync(final @NonNull String url,
|
||||
final @NonNull List<File> files,
|
||||
final @NonNull Map<String, String> parameters,
|
||||
final @Nullable OnFilesDownloadCallback callback,
|
||||
final Executor executor) {
|
||||
|
||||
new AsyncTask<Void, Object, Map<File, String>>() {
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
protected Map<File, String> doInBackground(Void... v) {
|
||||
Map<File, String> errors = new HashMap<>();
|
||||
for (final File file : files) {
|
||||
final int[] progressValue = {0};
|
||||
publishProgress(file, 0);
|
||||
IProgress progress = null;
|
||||
if (callback != null) {
|
||||
progress = new NetworkProgress() {
|
||||
@Override
|
||||
public void progress(int deltaWork) {
|
||||
progressValue[0] += deltaWork;
|
||||
publishProgress(file, progressValue[0]);
|
||||
}
|
||||
};
|
||||
}
|
||||
try {
|
||||
Map<String, String> params = new HashMap<>(parameters);
|
||||
if (callback != null) {
|
||||
Map<String, String> additionalParams = callback.getAdditionalParams(file);
|
||||
if (additionalParams != null) {
|
||||
params.putAll(additionalParams);
|
||||
}
|
||||
}
|
||||
boolean firstPrm = !url.contains("?");
|
||||
StringBuilder sb = new StringBuilder(url);
|
||||
for (Entry<String, String> entry : params.entrySet()) {
|
||||
sb.append(firstPrm ? "?" : "&").append(entry.getKey()).append("=").append(URLEncoder.encode(entry.getValue(), "UTF-8"));
|
||||
firstPrm = false;
|
||||
}
|
||||
String res = downloadFile(sb.toString(), file, true, progress);
|
||||
if (res != null) {
|
||||
errors.put(file, res);
|
||||
} else {
|
||||
if (callback != null) {
|
||||
callback.onFileDownloadedAsync(file);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
errors.put(file, e.getMessage());
|
||||
}
|
||||
publishProgress(file, -1);
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(Object... objects) {
|
||||
if (callback != null) {
|
||||
File file = (File) objects[0];
|
||||
Integer progress = (Integer) objects[1];
|
||||
if (progress >= 0) {
|
||||
callback.onFileDownloadProgress(file, progress);
|
||||
} else {
|
||||
callback.onFileDownloadDone(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(@NonNull Map<File, String> errors) {
|
||||
if (callback != null) {
|
||||
callback.onFilesDownloadDone(errors);
|
||||
}
|
||||
}
|
||||
|
||||
}.executeOnExecutor(executor, (Void) null);
|
||||
}
|
||||
|
||||
public static String sendRequest(@Nullable OsmandApplication ctx, @NonNull String url,
|
||||
@Nullable Map<String, String> parameters,
|
||||
@Nullable String userOperation, boolean toastAllowed, boolean post) {
|
||||
return sendRequest(ctx, url, parameters, userOperation, toastAllowed, post, null);
|
||||
}
|
||||
|
||||
public static String sendRequest(@Nullable OsmandApplication ctx, @NonNull String url,
|
||||
@Nullable Map<String, String> parameters,
|
||||
@Nullable String userOperation, boolean toastAllowed, boolean post,
|
||||
@Nullable OnRequestResultListener listener) {
|
||||
String result = null;
|
||||
String error = null;
|
||||
HttpURLConnection connection = null;
|
||||
try {
|
||||
|
||||
String params = null;
|
||||
if (parameters != null && parameters.size() > 0) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
@ -177,7 +341,7 @@ public class AndroidNetworkUtils {
|
|||
String paramsSeparator = url.indexOf('?') == -1 ? "?" : "&";
|
||||
connection = NetworkUtils.getHttpURLConnection(params == null || post ? url : url + paramsSeparator + params);
|
||||
connection.setRequestProperty("Accept-Charset", "UTF-8");
|
||||
connection.setRequestProperty("User-Agent", Version.getFullVersion(ctx));
|
||||
connection.setRequestProperty("User-Agent", ctx != null ? Version.getFullVersion(ctx) : "OsmAnd");
|
||||
connection.setConnectTimeout(15000);
|
||||
if (params != null && post) {
|
||||
connection.setDoInput(true);
|
||||
|
@ -192,67 +356,66 @@ public class AndroidNetworkUtils {
|
|||
output.write(params.getBytes("UTF-8"));
|
||||
output.flush();
|
||||
output.close();
|
||||
|
||||
} else {
|
||||
|
||||
connection.setRequestMethod("GET");
|
||||
connection.connect();
|
||||
}
|
||||
|
||||
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
|
||||
if (toastAllowed) {
|
||||
String msg = userOperation
|
||||
+ " " + ctx.getString(R.string.failed_op) + ": " + connection.getResponseMessage();
|
||||
showToast(ctx, msg);
|
||||
if (ctx != null) {
|
||||
error = (!Algorithms.isEmpty(userOperation) ? userOperation + " " : "")
|
||||
+ ctx.getString(R.string.failed_op) + ": " + connection.getResponseMessage();
|
||||
} else {
|
||||
error = (!Algorithms.isEmpty(userOperation) ? userOperation + " " : "")
|
||||
+ "failed: " + connection.getResponseMessage();
|
||||
}
|
||||
if (toastAllowed && ctx != null) {
|
||||
showToast(ctx, error);
|
||||
}
|
||||
InputStream errorStream = connection.getErrorStream();
|
||||
if (errorStream != null) {
|
||||
error = streamToString(errorStream);
|
||||
}
|
||||
} else {
|
||||
StringBuilder responseBody = new StringBuilder();
|
||||
responseBody.setLength(0);
|
||||
InputStream i = connection.getInputStream();
|
||||
if (i != null) {
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(i, "UTF-8"), 256);
|
||||
String s;
|
||||
boolean f = true;
|
||||
while ((s = in.readLine()) != null) {
|
||||
if (!f) {
|
||||
responseBody.append("\n");
|
||||
} else {
|
||||
f = false;
|
||||
}
|
||||
responseBody.append(s);
|
||||
}
|
||||
try {
|
||||
in.close();
|
||||
i.close();
|
||||
} catch (Exception e) {
|
||||
// ignore exception
|
||||
}
|
||||
}
|
||||
return responseBody.toString();
|
||||
result = streamToString(connection.getInputStream());
|
||||
}
|
||||
|
||||
} catch (NullPointerException e) {
|
||||
// that's tricky case why NPE is thrown to fix that problem httpClient could be used
|
||||
if (toastAllowed) {
|
||||
String msg = ctx.getString(R.string.auth_failed);
|
||||
showToast(ctx, msg);
|
||||
if (ctx != null) {
|
||||
error = ctx.getString(R.string.auth_failed);
|
||||
} else {
|
||||
error = "Authorization failed";
|
||||
}
|
||||
if (toastAllowed && ctx != null) {
|
||||
showToast(ctx, error);
|
||||
}
|
||||
} catch (MalformedURLException e) {
|
||||
if (toastAllowed) {
|
||||
showToast(ctx, MessageFormat.format(ctx.getResources().getString(R.string.shared_string_action_template)
|
||||
+ ": " + ctx.getResources().getString(R.string.shared_string_unexpected_error), userOperation));
|
||||
if (ctx != null) {
|
||||
error = MessageFormat.format(ctx.getResources().getString(R.string.shared_string_action_template)
|
||||
+ ": " + ctx.getResources().getString(R.string.shared_string_unexpected_error), userOperation);
|
||||
} else {
|
||||
error = "Action " + userOperation + ": Unexpected error";
|
||||
}
|
||||
if (toastAllowed && ctx != null) {
|
||||
showToast(ctx, error);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (toastAllowed) {
|
||||
showToast(ctx, MessageFormat.format(ctx.getResources().getString(R.string.shared_string_action_template)
|
||||
+ ": " + ctx.getResources().getString(R.string.shared_string_io_error), userOperation));
|
||||
if (ctx != null) {
|
||||
error = MessageFormat.format(ctx.getResources().getString(R.string.shared_string_action_template)
|
||||
+ ": " + ctx.getResources().getString(R.string.shared_string_io_error), userOperation);
|
||||
} else {
|
||||
error = "Action " + userOperation + ": I/O error";
|
||||
}
|
||||
if (toastAllowed && ctx != null) {
|
||||
showToast(ctx, error);
|
||||
}
|
||||
} finally {
|
||||
if (connection != null) {
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
if (listener != null) {
|
||||
listener.onResult(result, error);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -277,43 +440,81 @@ public class AndroidNetworkUtils {
|
|||
return res;
|
||||
}
|
||||
|
||||
public static String downloadFile(@NonNull String url, @NonNull File fileToSave) {
|
||||
public static String downloadFile(@NonNull String url, @NonNull File fileToSave, boolean gzip, @Nullable IProgress progress) {
|
||||
String error = null;
|
||||
try {
|
||||
URLConnection connection = NetworkUtils.getHttpURLConnection(url);
|
||||
HttpURLConnection connection = NetworkUtils.getHttpURLConnection(url);
|
||||
connection.setConnectTimeout(CONNECTION_TIMEOUT);
|
||||
connection.setReadTimeout(CONNECTION_TIMEOUT);
|
||||
BufferedInputStream inputStream = new BufferedInputStream(connection.getInputStream(), 8 * 1024);
|
||||
fileToSave.getParentFile().mkdirs();
|
||||
OutputStream stream = null;
|
||||
try {
|
||||
stream = new FileOutputStream(fileToSave);
|
||||
Algorithms.streamCopy(inputStream, stream);
|
||||
stream.flush();
|
||||
} finally {
|
||||
Algorithms.closeStream(inputStream);
|
||||
Algorithms.closeStream(stream);
|
||||
if (gzip) {
|
||||
connection.setRequestProperty("Accept-Encoding", "deflate, gzip");
|
||||
}
|
||||
connection.connect();
|
||||
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
|
||||
return streamToString(connection.getErrorStream());
|
||||
} else {
|
||||
InputStream inputStream = gzip
|
||||
? new GZIPInputStream(connection.getInputStream())
|
||||
: new BufferedInputStream(connection.getInputStream(), 8 * 1024);
|
||||
fileToSave.getParentFile().mkdirs();
|
||||
OutputStream stream = null;
|
||||
try {
|
||||
stream = new FileOutputStream(fileToSave);
|
||||
Algorithms.streamCopy(inputStream, stream, progress, 1024);
|
||||
stream.flush();
|
||||
} finally {
|
||||
Algorithms.closeStream(inputStream);
|
||||
Algorithms.closeStream(stream);
|
||||
}
|
||||
}
|
||||
} catch (UnknownHostException e) {
|
||||
error = e.getMessage();
|
||||
LOG.error("UnknownHostException, cannot download file " + url + " " + error);
|
||||
} catch (Exception e) {
|
||||
error = e.getMessage();
|
||||
LOG.warn("Cannot download file : " + url, e);
|
||||
LOG.warn("Cannot download file: " + url, e);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
private static String streamToString(InputStream inputStream) throws IOException {
|
||||
StringBuilder result = new StringBuilder();
|
||||
if (inputStream != null) {
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"), 256);
|
||||
String buffer;
|
||||
boolean f = true;
|
||||
while ((buffer = in.readLine()) != null) {
|
||||
if (!f) {
|
||||
result.append("\n");
|
||||
} else {
|
||||
f = false;
|
||||
}
|
||||
result.append(buffer);
|
||||
}
|
||||
try {
|
||||
in.close();
|
||||
inputStream.close();
|
||||
} catch (Exception e) {
|
||||
// ignore exception
|
||||
}
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private static final String BOUNDARY = "CowMooCowMooCowCowCow";
|
||||
|
||||
public static String uploadFile(@NonNull String urlText, @NonNull File file, boolean gzip,
|
||||
@NonNull Map<String, String> additionalParams, @Nullable Map<String, String> headers) throws IOException {
|
||||
return uploadFile(urlText, new FileInputStream(file), file.getName(), gzip, additionalParams, headers);
|
||||
@NonNull Map<String, String> additionalParams,
|
||||
@Nullable Map<String, String> headers,
|
||||
@Nullable IProgress progress) throws IOException {
|
||||
return uploadFile(urlText, new FileInputStream(file), file.getName(), gzip, additionalParams, headers, progress);
|
||||
}
|
||||
|
||||
public static String uploadFile(@NonNull String urlText, @NonNull InputStream inputStream, @NonNull String fileName, boolean gzip,
|
||||
Map<String, String> additionalParams, @Nullable Map<String, String> headers) {
|
||||
URL url;
|
||||
public static String uploadFile(@NonNull String urlText, @NonNull InputStream inputStream,
|
||||
@NonNull String fileName, boolean gzip,
|
||||
@NonNull Map<String, String> additionalParams,
|
||||
@Nullable Map<String, String> headers,
|
||||
@Nullable IProgress progress) {
|
||||
try {
|
||||
boolean firstPrm = !urlText.contains("?");
|
||||
StringBuilder sb = new StringBuilder(urlText);
|
||||
|
@ -324,7 +525,7 @@ public class AndroidNetworkUtils {
|
|||
urlText = sb.toString();
|
||||
|
||||
LOG.info("Start uploading file to " + urlText + " " + fileName);
|
||||
url = new URL(urlText);
|
||||
URL url = new URL(urlText);
|
||||
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setDoInput(true);
|
||||
|
@ -350,11 +551,11 @@ public class AndroidNetworkUtils {
|
|||
ous.flush();
|
||||
if (gzip) {
|
||||
GZIPOutputStream gous = new GZIPOutputStream(ous, 1024);
|
||||
Algorithms.streamCopy(bis, gous);
|
||||
Algorithms.streamCopy(bis, gous, progress, 1024);
|
||||
gous.flush();
|
||||
gous.finish();
|
||||
} else {
|
||||
Algorithms.streamCopy(bis, ous);
|
||||
Algorithms.streamCopy(bis, ous, progress, 1024);
|
||||
}
|
||||
|
||||
ous.write(("\r\n--" + BOUNDARY + "--\r\n").getBytes());
|
||||
|
@ -365,6 +566,10 @@ public class AndroidNetworkUtils {
|
|||
LOG.info("Finish uploading file " + fileName);
|
||||
LOG.info("Response code and message : " + conn.getResponseCode() + " " + conn.getResponseMessage());
|
||||
if (conn.getResponseCode() != 200) {
|
||||
InputStream errorStream = conn.getErrorStream();
|
||||
if (errorStream != null) {
|
||||
return streamToString(errorStream);
|
||||
}
|
||||
return conn.getResponseMessage();
|
||||
}
|
||||
InputStream is = conn.getInputStream();
|
||||
|
@ -399,6 +604,16 @@ public class AndroidNetworkUtils {
|
|||
final @NonNull Map<String, String> parameters,
|
||||
final @Nullable Map<String, String> headers,
|
||||
final OnFilesUploadCallback callback) {
|
||||
uploadFilesAsync(url, files, gzip, parameters, headers, callback, AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
public static void uploadFilesAsync(final @NonNull String url,
|
||||
final @NonNull List<File> files,
|
||||
final boolean gzip,
|
||||
final @NonNull Map<String, String> parameters,
|
||||
final @Nullable Map<String, String> headers,
|
||||
final OnFilesUploadCallback callback,
|
||||
final Executor executor) {
|
||||
|
||||
new AsyncTask<Void, Object, Map<File, String>>() {
|
||||
|
||||
|
@ -406,8 +621,19 @@ public class AndroidNetworkUtils {
|
|||
@NonNull
|
||||
protected Map<File, String> doInBackground(Void... v) {
|
||||
Map<File, String> errors = new HashMap<>();
|
||||
for (File file : files) {
|
||||
for (final File file : files) {
|
||||
final int[] progressValue = {0};
|
||||
publishProgress(file, 0);
|
||||
IProgress progress = null;
|
||||
if (callback != null) {
|
||||
progress = new NetworkProgress() {
|
||||
@Override
|
||||
public void progress(int deltaWork) {
|
||||
progressValue[0] += deltaWork;
|
||||
publishProgress(file, progressValue[0]);
|
||||
}
|
||||
};
|
||||
}
|
||||
try {
|
||||
Map<String, String> params = new HashMap<>(parameters);
|
||||
if (callback != null) {
|
||||
|
@ -416,14 +642,14 @@ public class AndroidNetworkUtils {
|
|||
params.putAll(additionalParams);
|
||||
}
|
||||
}
|
||||
String res = uploadFile(url, file, gzip, params, headers);
|
||||
String res = uploadFile(url, file, gzip, params, headers, progress);
|
||||
if (res != null) {
|
||||
errors.put(file, res);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
errors.put(file, e.getMessage());
|
||||
}
|
||||
publishProgress(file, 100);
|
||||
publishProgress(file, -1);
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
@ -431,7 +657,13 @@ public class AndroidNetworkUtils {
|
|||
@Override
|
||||
protected void onProgressUpdate(Object... objects) {
|
||||
if (callback != null) {
|
||||
callback.onFileUploadProgress((File) objects[0], (Integer) objects[1]);
|
||||
File file = (File) objects[0];
|
||||
Integer progress = (Integer) objects[1];
|
||||
if (progress >= 0) {
|
||||
callback.onFileUploadProgress(file, progress);
|
||||
} else {
|
||||
callback.onFileUploadDone(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -442,7 +674,7 @@ public class AndroidNetworkUtils {
|
|||
}
|
||||
}
|
||||
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
|
||||
}.executeOnExecutor(executor, (Void) null);
|
||||
}
|
||||
|
||||
private static void showToast(OsmandApplication ctx, String message) {
|
||||
|
@ -450,11 +682,11 @@ public class AndroidNetworkUtils {
|
|||
}
|
||||
|
||||
public static class Request {
|
||||
private String url;
|
||||
private Map<String, String> parameters;
|
||||
private String userOperation;
|
||||
private boolean toastAllowed;
|
||||
private boolean post;
|
||||
private final String url;
|
||||
private final Map<String, String> parameters;
|
||||
private final String userOperation;
|
||||
private final boolean toastAllowed;
|
||||
private final boolean post;
|
||||
|
||||
public Request(String url, Map<String, String> parameters, String userOperation, boolean toastAllowed, boolean post) {
|
||||
this.url = url;
|
||||
|
@ -484,4 +716,39 @@ public class AndroidNetworkUtils {
|
|||
return post;
|
||||
}
|
||||
}
|
||||
|
||||
private abstract static class NetworkProgress implements IProgress {
|
||||
@Override
|
||||
public void startTask(String taskName, int work) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startWork(int work) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract void progress(int deltaWork);
|
||||
|
||||
@Override
|
||||
public void remaining(int remainingWork) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finishTask() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIndeterminate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInterrupted() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGeneralProgress(String genProgress) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,8 @@ import android.graphics.drawable.ShapeDrawable;
|
|||
import android.graphics.drawable.StateListDrawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.os.IBinder;
|
||||
import android.os.PowerManager;
|
||||
import android.os.StatFs;
|
||||
|
@ -264,6 +266,11 @@ public class AndroidUtils {
|
|||
return "";
|
||||
}
|
||||
|
||||
public static String getFreeSpace(Context ctx, File dir) {
|
||||
long size = AndroidUtils.getAvailableSpace(dir);
|
||||
return AndroidUtils.formatSize(ctx, size);
|
||||
}
|
||||
|
||||
public static View findParentViewById(View view, int id) {
|
||||
ViewParent viewParent = view.getParent();
|
||||
|
||||
|
@ -856,11 +863,39 @@ public class AndroidUtils {
|
|||
return result;
|
||||
}
|
||||
|
||||
public static long getAvailableSpace(@NonNull OsmandApplication app) {
|
||||
return getAvailableSpace(app.getAppPath(null));
|
||||
}
|
||||
|
||||
public static long getTotalSpace(@NonNull OsmandApplication app) {
|
||||
return getTotalSpace(app.getAppPath(null));
|
||||
}
|
||||
|
||||
public static long getAvailableSpace(@Nullable File dir) {
|
||||
if (dir != null && dir.canRead()) {
|
||||
try {
|
||||
StatFs fs = new StatFs(dir.getAbsolutePath());
|
||||
return fs.getAvailableBlocksLong() * fs.getBlockSize();
|
||||
if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
return fs.getAvailableBlocksLong() * fs.getBlockSizeLong();
|
||||
} else {
|
||||
return fs.getAvailableBlocks() * fs.getBlockSize();
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.error(e);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static long getTotalSpace(@Nullable File dir) {
|
||||
if (dir != null && dir.canRead()) {
|
||||
try {
|
||||
StatFs fs = new StatFs(dir.getAbsolutePath());
|
||||
if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
return fs.getBlockCountLong() * fs.getBlockSizeLong();
|
||||
} else {
|
||||
return fs.getBlockCount() * fs.getBlockSize();
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.error(e);
|
||||
}
|
||||
|
@ -887,13 +922,6 @@ public class AndroidUtils {
|
|||
return -1;
|
||||
}
|
||||
|
||||
public static float getUsedSpaceGb(File dir) {
|
||||
if (dir.canRead()) {
|
||||
return getTotalSpaceGb(dir) - getFreeSpaceGb(dir);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static CharSequence getStyledString(CharSequence baseString, CharSequence stringToInsertAndStyle,
|
||||
CharacterStyle baseStyle, CharacterStyle replaceStyle) {
|
||||
int indexOfPlaceholder = baseString.toString().indexOf(STRING_PLACEHOLDER);
|
||||
|
|
|
@ -184,7 +184,8 @@ public class AnalyticsHelper extends SQLiteOpenHelper {
|
|||
|
||||
String jsonStr = json.toString();
|
||||
InputStream inputStream = new ByteArrayInputStream(jsonStr.getBytes());
|
||||
String res = AndroidNetworkUtils.uploadFile(ANALYTICS_UPLOAD_URL, inputStream, ANALYTICS_FILE_NAME, true, additionalData, null);
|
||||
String res = AndroidNetworkUtils.uploadFile(ANALYTICS_UPLOAD_URL, inputStream,
|
||||
ANALYTICS_FILE_NAME, true, additionalData, null, null);
|
||||
if (res != null) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import net.osmand.osm.MapPoiTypes;
|
|||
import net.osmand.plus.activities.LocalIndexHelper;
|
||||
import net.osmand.plus.activities.LocalIndexInfo;
|
||||
import net.osmand.plus.activities.SavingTrackHelper;
|
||||
import net.osmand.plus.backup.BackupHelper;
|
||||
import net.osmand.plus.base.MapViewTrackingUtilities;
|
||||
import net.osmand.plus.download.DownloadActivity;
|
||||
import net.osmand.plus.download.ui.AbstractLoadLocalIndexTask;
|
||||
|
@ -39,7 +40,6 @@ import net.osmand.plus.helpers.DayNightHelper;
|
|||
import net.osmand.plus.helpers.LockHelper;
|
||||
import net.osmand.plus.helpers.WaypointHelper;
|
||||
import net.osmand.plus.inapp.InAppPurchaseHelperImpl;
|
||||
import net.osmand.plus.itinerary.ItineraryHelper;
|
||||
import net.osmand.plus.liveupdates.LiveUpdatesHelper;
|
||||
import net.osmand.plus.mapmarkers.MapMarkersDbHelper;
|
||||
import net.osmand.plus.mapmarkers.MapMarkersHelper;
|
||||
|
@ -472,7 +472,7 @@ public class AppInitializer implements IProgress {
|
|||
app.osmOAuthHelper = startupInit(new OsmOAuthHelper(app), OsmOAuthHelper.class);
|
||||
app.oprAuthHelper = startupInit(new OprAuthHelper(app), OprAuthHelper.class);
|
||||
app.onlineRoutingHelper = startupInit(new OnlineRoutingHelper(app), OnlineRoutingHelper.class);
|
||||
app.itineraryHelper = startupInit(new ItineraryHelper(app), ItineraryHelper.class);
|
||||
app.backupHelper = startupInit(new BackupHelper(app), BackupHelper.class);
|
||||
|
||||
initOpeningHoursParser();
|
||||
}
|
||||
|
@ -685,7 +685,7 @@ public class AppInitializer implements IProgress {
|
|||
// restore backuped favorites to normal file
|
||||
restoreBackupForFavoritesFiles();
|
||||
notifyEvent(InitEvents.RESTORE_BACKUPS);
|
||||
app.itineraryHelper.syncAllGroupsAsync();
|
||||
app.mapMarkersHelper.syncAllGroupsAsync();
|
||||
app.searchUICore.initSearchUICore();
|
||||
|
||||
checkLiveUpdatesAlerts();
|
||||
|
|
|
@ -445,6 +445,9 @@ public class ContextMenuAdapter {
|
|||
ImageView imageView = (ImageView) convertView.findViewById(R.id.secondary_icon);
|
||||
imageView.setImageDrawable(drawable);
|
||||
imageView.setVisibility(View.VISIBLE);
|
||||
if (secondaryDrawable == R.drawable.ic_action_additional_option) {
|
||||
UiUtilities.rotateImageByLayoutDirection(imageView);
|
||||
}
|
||||
} else {
|
||||
ImageView imageView = (ImageView) convertView.findViewById(R.id.secondary_icon);
|
||||
if (imageView != null) {
|
||||
|
|
|
@ -225,7 +225,7 @@ public class CustomRegion extends WorldRegion {
|
|||
&& app.getSettings().isInternetConnectionAvailable()) {
|
||||
OnRequestResultListener resultListener = new OnRequestResultListener() {
|
||||
@Override
|
||||
public void onResult(String result) {
|
||||
public void onResult(@Nullable String result, @Nullable String error) {
|
||||
if (!Algorithms.isEmpty(result)) {
|
||||
if ("json".equalsIgnoreCase(dynamicDownloadItems.format)) {
|
||||
dynamicItemsJson = mapJsonItems(result);
|
||||
|
|
|
@ -17,7 +17,7 @@ import net.osmand.data.FavouritePoint;
|
|||
import net.osmand.data.LatLon;
|
||||
import net.osmand.plus.GeocodingLookupService.AddressLookupRequest;
|
||||
import net.osmand.plus.mapmarkers.MapMarkersHelper;
|
||||
import net.osmand.plus.itinerary.ItineraryGroup;
|
||||
import net.osmand.plus.mapmarkers.MapMarkersGroup;
|
||||
import net.osmand.plus.api.SQLiteAPI.SQLiteConnection;
|
||||
import net.osmand.plus.api.SQLiteAPI.SQLiteCursor;
|
||||
import net.osmand.util.Algorithms;
|
||||
|
@ -146,6 +146,14 @@ public class FavouritesDbHelper {
|
|||
}
|
||||
}
|
||||
|
||||
public long getLastUploadedTime() {
|
||||
return context.getSettings().FAVORITES_LAST_UPLOADED_TIME.get();
|
||||
}
|
||||
|
||||
public void setLastUploadedTime(long time) {
|
||||
context.getSettings().FAVORITES_LAST_UPLOADED_TIME.set(time);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Drawable getColoredIconForGroup(String groupName) {
|
||||
String groupIdName = FavoriteGroup.convertDisplayNameToGroupIdName(context, groupName);
|
||||
|
@ -272,15 +280,16 @@ public class FavouritesDbHelper {
|
|||
}
|
||||
|
||||
private void runSyncWithMarkers(FavoriteGroup favGroup) {
|
||||
ItineraryGroup group = context.getItineraryHelper().getMarkersGroup(favGroup);
|
||||
MapMarkersHelper helper = context.getMapMarkersHelper();
|
||||
MapMarkersGroup group = helper.getMarkersGroup(favGroup);
|
||||
if (group != null) {
|
||||
context.getItineraryHelper().runSynchronization(group);
|
||||
helper.runSynchronization(group);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean removeFromMarkers(FavoriteGroup favGroup) {
|
||||
MapMarkersHelper helper = context.getMapMarkersHelper();
|
||||
ItineraryGroup group = context.getItineraryHelper().getMarkersGroup(favGroup);
|
||||
MapMarkersGroup group = helper.getMarkersGroup(favGroup);
|
||||
if (group != null) {
|
||||
helper.removeMarkersGroup(group);
|
||||
return true;
|
||||
|
@ -289,7 +298,8 @@ public class FavouritesDbHelper {
|
|||
}
|
||||
|
||||
private void addToMarkers(FavoriteGroup favGroup) {
|
||||
context.getItineraryHelper().addOrEnableGroup(favGroup);
|
||||
MapMarkersHelper helper = context.getMapMarkersHelper();
|
||||
helper.addOrEnableGroup(favGroup);
|
||||
}
|
||||
|
||||
private File getInternalFile() {
|
||||
|
|
|
@ -18,7 +18,7 @@ import androidx.annotation.Nullable;
|
|||
|
||||
public class GPXDatabase {
|
||||
|
||||
private static final int DB_VERSION = 11;
|
||||
private static final int DB_VERSION = 12;
|
||||
private static final String DB_NAME = "gpx_database";
|
||||
|
||||
private static final String GPX_TABLE_NAME = "gpxTable";
|
||||
|
@ -48,6 +48,7 @@ public class GPXDatabase {
|
|||
|
||||
private static final String GPX_COL_COLOR = "color";
|
||||
private static final String GPX_COL_FILE_LAST_MODIFIED_TIME = "fileLastModifiedTime";
|
||||
private static final String GPX_COL_FILE_LAST_UPLOADED_TIME = "fileLastUploadedTime";
|
||||
|
||||
private static final String GPX_COL_SPLIT_TYPE = "splitType";
|
||||
private static final String GPX_COL_SPLIT_INTERVAL = "splitInterval";
|
||||
|
@ -98,6 +99,7 @@ public class GPXDatabase {
|
|||
GPX_COL_WPT_POINTS + " int, " +
|
||||
GPX_COL_COLOR + " TEXT, " +
|
||||
GPX_COL_FILE_LAST_MODIFIED_TIME + " long, " +
|
||||
GPX_COL_FILE_LAST_UPLOADED_TIME + " long, " +
|
||||
GPX_COL_SPLIT_TYPE + " int, " +
|
||||
GPX_COL_SPLIT_INTERVAL + " double, " +
|
||||
GPX_COL_API_IMPORTED + " int, " + // 1 = true, 0 = false
|
||||
|
@ -133,6 +135,7 @@ public class GPXDatabase {
|
|||
GPX_COL_WPT_POINTS + ", " +
|
||||
GPX_COL_COLOR + ", " +
|
||||
GPX_COL_FILE_LAST_MODIFIED_TIME + ", " +
|
||||
GPX_COL_FILE_LAST_UPLOADED_TIME + ", " +
|
||||
GPX_COL_SPLIT_TYPE + ", " +
|
||||
GPX_COL_SPLIT_INTERVAL + ", " +
|
||||
GPX_COL_API_IMPORTED + ", " +
|
||||
|
@ -184,6 +187,7 @@ public class GPXDatabase {
|
|||
private int splitType;
|
||||
private double splitInterval;
|
||||
private long fileLastModifiedTime;
|
||||
private long fileLastUploadedTime;
|
||||
private boolean apiImported;
|
||||
private boolean showAsMarkers;
|
||||
private boolean joinSegments;
|
||||
|
@ -200,6 +204,11 @@ public class GPXDatabase {
|
|||
this.color = color;
|
||||
}
|
||||
|
||||
public GpxDataItem(File file, long fileLastUploadedTime) {
|
||||
this.file = file;
|
||||
this.fileLastUploadedTime = fileLastUploadedTime;
|
||||
}
|
||||
|
||||
public GpxDataItem(File file, @NonNull GPXFile gpxFile) {
|
||||
this.file = file;
|
||||
readGpxParams(gpxFile);
|
||||
|
@ -263,6 +272,10 @@ public class GPXDatabase {
|
|||
return fileLastModifiedTime;
|
||||
}
|
||||
|
||||
public long getFileLastUploadedTime() {
|
||||
return fileLastUploadedTime;
|
||||
}
|
||||
|
||||
public int getSplitType() {
|
||||
return splitType;
|
||||
}
|
||||
|
@ -441,10 +454,13 @@ public class GPXDatabase {
|
|||
db.execSQL("UPDATE " + GPX_TABLE_NAME + " SET " + GPX_COL_SHOW_START_FINISH + " = ? " +
|
||||
"WHERE " + GPX_COL_SHOW_START_FINISH + " IS NULL", new Object[]{1});
|
||||
}
|
||||
if (oldVersion < 12) {
|
||||
db.execSQL("ALTER TABLE " + GPX_TABLE_NAME + " ADD " + GPX_COL_FILE_LAST_UPLOADED_TIME + " long");
|
||||
}
|
||||
db.execSQL("CREATE INDEX IF NOT EXISTS " + GPX_INDEX_NAME_DIR + " ON " + GPX_TABLE_NAME + " (" + GPX_COL_NAME + ", " + GPX_COL_DIR + ");");
|
||||
}
|
||||
|
||||
private boolean updateLastModifiedTime(GpxDataItem item) {
|
||||
private boolean updateLastModifiedTime(@NonNull GpxDataItem item) {
|
||||
SQLiteConnection db = openConnection(false);
|
||||
if (db != null) {
|
||||
try {
|
||||
|
@ -464,6 +480,25 @@ public class GPXDatabase {
|
|||
return false;
|
||||
}
|
||||
|
||||
public boolean updateLastUploadedTime(@NonNull GpxDataItem item, long fileLastUploadedTime) {
|
||||
SQLiteConnection db = openConnection(false);
|
||||
if (db != null) {
|
||||
try {
|
||||
String fileName = getFileName(item.file);
|
||||
String fileDir = getFileDir(item.file);
|
||||
db.execSQL("UPDATE " + GPX_TABLE_NAME + " SET " +
|
||||
GPX_COL_FILE_LAST_UPLOADED_TIME + " = ? " +
|
||||
" WHERE " + GPX_COL_NAME + " = ? AND " + GPX_COL_DIR + " = ?",
|
||||
new Object[] { fileLastUploadedTime, fileName, fileDir });
|
||||
item.fileLastUploadedTime = fileLastUploadedTime;
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean rename(@Nullable GpxDataItem item, File currentFile, File newFile) {
|
||||
SQLiteConnection db = openConnection(false);
|
||||
if (db != null){
|
||||
|
@ -721,11 +756,11 @@ public class GPXDatabase {
|
|||
String gradientScaleType = item.gradientScaleType != null ? item.gradientScaleType.getTypeName() : null;
|
||||
if (a != null) {
|
||||
db.execSQL(
|
||||
"INSERT INTO " + GPX_TABLE_NAME + " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
"INSERT INTO " + GPX_TABLE_NAME + " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
new Object[] {fileName, fileDir, a.totalDistance, a.totalTracks, a.startTime, a.endTime,
|
||||
a.timeSpan, a.timeMoving, a.totalDistanceMoving, a.diffElevationUp, a.diffElevationDown,
|
||||
a.avgElevation, a.minElevation, a.maxElevation, a.maxSpeed, a.avgSpeed, a.points, a.wptPoints,
|
||||
color, item.file.lastModified(), item.splitType, item.splitInterval, item.apiImported ? 1 : 0,
|
||||
color, item.file.lastModified(), item.fileLastUploadedTime, item.splitType, item.splitInterval, item.apiImported ? 1 : 0,
|
||||
Algorithms.encodeStringSet(item.analysis.wptCategoryNames), item.showAsMarkers ? 1 : 0,
|
||||
item.joinSegments ? 1 : 0, item.showArrows ? 1 : 0, item.showStartFinish ? 1 : 0, item.width,
|
||||
item.gradientSpeedPalette, item.gradientAltitudePalette, item.gradientSlopePalette, gradientScaleType});
|
||||
|
@ -735,6 +770,7 @@ public class GPXDatabase {
|
|||
GPX_COL_DIR + ", " +
|
||||
GPX_COL_COLOR + ", " +
|
||||
GPX_COL_FILE_LAST_MODIFIED_TIME + ", " +
|
||||
GPX_COL_FILE_LAST_UPLOADED_TIME + ", " +
|
||||
GPX_COL_SPLIT_TYPE + ", " +
|
||||
GPX_COL_SPLIT_INTERVAL + ", " +
|
||||
GPX_COL_API_IMPORTED + ", " +
|
||||
|
@ -747,8 +783,8 @@ public class GPXDatabase {
|
|||
GPX_COL_GRADIENT_ALTITUDE_COLOR + ", " +
|
||||
GPX_COL_GRADIENT_SLOPE_COLOR + ", " +
|
||||
GPX_COL_GRADIENT_SCALE_TYPE +
|
||||
") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
new Object[] {fileName, fileDir, color, 0, item.splitType, item.splitInterval,
|
||||
") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
new Object[] {fileName, fileDir, color, 0, item.fileLastUploadedTime, item.splitType, item.splitInterval,
|
||||
item.apiImported ? 1 : 0, item.showAsMarkers ? 1 : 0, item.joinSegments ? 1 : 0,
|
||||
item.showArrows ? 1 : 0, item.showStartFinish ? 1 : 0, item.width,
|
||||
Algorithms.gradientPaletteToString(item.gradientSpeedPalette),
|
||||
|
@ -828,19 +864,20 @@ public class GPXDatabase {
|
|||
int wptPoints = (int)query.getInt(17);
|
||||
String color = query.getString(18);
|
||||
long fileLastModifiedTime = query.getLong(19);
|
||||
int splitType = (int)query.getInt(20);
|
||||
double splitInterval = query.getDouble(21);
|
||||
boolean apiImported = query.getInt(22) == 1;
|
||||
String wptCategoryNames = query.getString(23);
|
||||
boolean showAsMarkers = query.getInt(24) == 1;
|
||||
boolean joinSegments = query.getInt(25) == 1;
|
||||
boolean showArrows = query.getInt(26) == 1;
|
||||
boolean showStartFinish = query.getInt(27) == 1;
|
||||
String width = query.getString(28);
|
||||
String gradientSpeedPalette = query.getString(29);
|
||||
String gradientAltitudePalette = query.getString(30);
|
||||
String gradientSlopePalette = query.getString(31);
|
||||
String gradientScaleType = query.getString(32);
|
||||
long fileLastUploadedTime = query.getLong(20);
|
||||
int splitType = (int)query.getInt(21);
|
||||
double splitInterval = query.getDouble(22);
|
||||
boolean apiImported = query.getInt(23) == 1;
|
||||
String wptCategoryNames = query.getString(24);
|
||||
boolean showAsMarkers = query.getInt(25) == 1;
|
||||
boolean joinSegments = query.getInt(26) == 1;
|
||||
boolean showArrows = query.getInt(27) == 1;
|
||||
boolean showStartFinish = query.getInt(28) == 1;
|
||||
String width = query.getString(29);
|
||||
String gradientSpeedPalette = query.getString(30);
|
||||
String gradientAltitudePalette = query.getString(31);
|
||||
String gradientSlopePalette = query.getString(32);
|
||||
String gradientScaleType = query.getString(33);
|
||||
|
||||
GPXTrackAnalysis a = new GPXTrackAnalysis();
|
||||
a.totalDistance = totalDistance;
|
||||
|
@ -873,6 +910,7 @@ public class GPXDatabase {
|
|||
GpxDataItem item = new GpxDataItem(new File(dir, fileName), a);
|
||||
item.color = parseColor(color);
|
||||
item.fileLastModifiedTime = fileLastModifiedTime;
|
||||
item.fileLastUploadedTime = fileLastUploadedTime;
|
||||
item.splitType = splitType;
|
||||
item.splitInterval = splitInterval;
|
||||
item.apiImported = apiImported;
|
||||
|
|
|
@ -78,6 +78,12 @@ public class GpxDbHelper {
|
|||
return res;
|
||||
}
|
||||
|
||||
public boolean updateLastUploadedTime(GpxDataItem item, long fileLastUploadedTime) {
|
||||
boolean res = db.updateLastUploadedTime(item, fileLastUploadedTime);
|
||||
putToCache(item);
|
||||
return res;
|
||||
}
|
||||
|
||||
public boolean updateGradientScalePalette(@NonNull GpxDataItem item, @NonNull GradientScaleType gradientScaleType, int[] palette) {
|
||||
boolean res = db.updateGradientScaleColor(item, gradientScaleType, palette);
|
||||
putToCache(item);
|
||||
|
|
|
@ -30,7 +30,8 @@ import net.osmand.plus.helpers.GpxUiHelper.GPXDataSetType;
|
|||
import net.osmand.plus.helpers.GpxUiHelper.GPXInfo;
|
||||
import net.osmand.plus.helpers.SearchHistoryHelper;
|
||||
import net.osmand.plus.helpers.enums.MetricsConstants;
|
||||
import net.osmand.plus.itinerary.ItineraryGroup;
|
||||
import net.osmand.plus.mapmarkers.MapMarkersGroup;
|
||||
import net.osmand.plus.mapmarkers.MapMarkersHelper;
|
||||
import net.osmand.plus.routing.GPXRouteParams.GPXRouteParamsBuilder;
|
||||
import net.osmand.plus.track.GpxSplitType;
|
||||
import net.osmand.util.Algorithms;
|
||||
|
@ -291,10 +292,12 @@ public class GpxSelectionHelper {
|
|||
return group;
|
||||
}
|
||||
|
||||
private String getGroupName(GPXFile g) {
|
||||
public String getGroupName(GPXFile g) {
|
||||
String name = g.path;
|
||||
if (g.showCurrentTrack) {
|
||||
name = getString(R.string.shared_string_currently_recording_track);
|
||||
} else if (Algorithms.isEmpty(name)) {
|
||||
name = getString(R.string.current_route);
|
||||
} else {
|
||||
int i = name.lastIndexOf('/');
|
||||
if (i >= 0) {
|
||||
|
@ -795,7 +798,7 @@ public class GpxSelectionHelper {
|
|||
boolean addToHistory) {
|
||||
GpxDataItem dataItem = app.getGpxDbHelper().getItem(new File(gpx.path));
|
||||
if (canAddToMarkers && show && dataItem != null && dataItem.isShowAsMarkers()) {
|
||||
app.getItineraryHelper().addOrEnableGroup(gpx);
|
||||
app.getMapMarkersHelper().addOrEnableGroup(gpx);
|
||||
}
|
||||
return selectGpxFile(gpx, dataItem, show, notShowNavigationDialog, syncGroup, selectedByUser, addToHistory);
|
||||
}
|
||||
|
@ -822,9 +825,10 @@ public class GpxSelectionHelper {
|
|||
}
|
||||
|
||||
private void syncGpxWithMarkers(GPXFile gpxFile) {
|
||||
ItineraryGroup group = app.getItineraryHelper().getMarkersGroup(gpxFile);
|
||||
MapMarkersHelper mapMarkersHelper = app.getMapMarkersHelper();
|
||||
MapMarkersGroup group = mapMarkersHelper.getMarkersGroup(gpxFile);
|
||||
if (group != null) {
|
||||
app.getItineraryHelper().runSynchronization(group);
|
||||
mapMarkersHelper.runSynchronization(group);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -52,6 +52,7 @@ import net.osmand.plus.activities.SavingTrackHelper;
|
|||
import net.osmand.plus.activities.actions.OsmAndDialogs;
|
||||
import net.osmand.plus.api.SQLiteAPI;
|
||||
import net.osmand.plus.api.SQLiteAPIImpl;
|
||||
import net.osmand.plus.backup.BackupHelper;
|
||||
import net.osmand.plus.base.MapViewTrackingUtilities;
|
||||
import net.osmand.plus.download.DownloadIndexesThread;
|
||||
import net.osmand.plus.download.DownloadService;
|
||||
|
@ -66,7 +67,6 @@ import net.osmand.plus.helpers.WaypointHelper;
|
|||
import net.osmand.plus.helpers.enums.DrivingRegion;
|
||||
import net.osmand.plus.helpers.enums.MetricsConstants;
|
||||
import net.osmand.plus.inapp.InAppPurchaseHelper;
|
||||
import net.osmand.plus.itinerary.ItineraryHelper;
|
||||
import net.osmand.plus.mapmarkers.MapMarkersDbHelper;
|
||||
import net.osmand.plus.mapmarkers.MapMarkersHelper;
|
||||
import net.osmand.plus.measurementtool.MeasurementEditingContext;
|
||||
|
@ -168,7 +168,7 @@ public class OsmandApplication extends MultiDexApplication {
|
|||
OprAuthHelper oprAuthHelper;
|
||||
MeasurementEditingContext measurementEditingContext;
|
||||
OnlineRoutingHelper onlineRoutingHelper;
|
||||
ItineraryHelper itineraryHelper;
|
||||
BackupHelper backupHelper;
|
||||
|
||||
private Map<String, Builder> customRoutingConfigs = new ConcurrentHashMap<>();
|
||||
private File externalStorageDirectory;
|
||||
|
@ -470,8 +470,8 @@ public class OsmandApplication extends MultiDexApplication {
|
|||
return onlineRoutingHelper;
|
||||
}
|
||||
|
||||
public ItineraryHelper getItineraryHelper() {
|
||||
return itineraryHelper;
|
||||
public BackupHelper getBackupHelper() {
|
||||
return backupHelper;
|
||||
}
|
||||
|
||||
public TransportRoutingHelper getTransportRoutingHelper() {
|
||||
|
|
|
@ -5,6 +5,7 @@ import android.app.ProgressDialog;
|
|||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnCancelListener;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.widget.ProgressBar;
|
||||
|
@ -204,7 +205,8 @@ public class ProgressImplementation implements IProgress {
|
|||
work = -1;
|
||||
progress = 0;
|
||||
if (taskName != null) {
|
||||
message = context.getResources().getString(R.string.finished_task) +" : "+ taskName; //$NON-NLS-1$
|
||||
Resources resources = context.getResources();
|
||||
message = resources.getString(R.string.ltr_or_rtl_combine_via_colon, resources.getString(R.string.finished_task), taskName);
|
||||
mViewUpdateHandler.sendEmptyMessage(HANDLER_START_TASK);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ import net.osmand.AndroidUtils;
|
|||
import net.osmand.plus.FavouritesDbHelper;
|
||||
import net.osmand.plus.FavouritesDbHelper.FavoriteGroup;
|
||||
import net.osmand.plus.mapmarkers.MapMarkersHelper;
|
||||
import net.osmand.plus.itinerary.ItineraryGroup;
|
||||
import net.osmand.plus.mapmarkers.MapMarkersGroup;
|
||||
import net.osmand.plus.OsmandApplication;
|
||||
import net.osmand.plus.R;
|
||||
import net.osmand.plus.UiUtilities;
|
||||
|
@ -179,7 +179,7 @@ public class EditFavoriteGroupDialogFragment extends MenuBottomSheetDialogFragme
|
|||
|
||||
final MapMarkersHelper markersHelper = app.getMapMarkersHelper();
|
||||
final FavoriteGroup favGroup = this.group;
|
||||
final ItineraryGroup markersGr = app.getItineraryHelper().getMarkersGroup(this.group);
|
||||
final MapMarkersGroup markersGr = markersHelper.getMarkersGroup(this.group);
|
||||
final boolean synced = markersGr != null;
|
||||
|
||||
BaseBottomSheetItem markersGroupItem = new SimpleBottomSheetItem.Builder()
|
||||
|
@ -192,7 +192,7 @@ public class EditFavoriteGroupDialogFragment extends MenuBottomSheetDialogFragme
|
|||
if (synced) {
|
||||
markersHelper.removeMarkersGroup(markersGr);
|
||||
} else {
|
||||
app.getItineraryHelper().addOrEnableGroup(favGroup);
|
||||
markersHelper.addOrEnableGroup(favGroup);
|
||||
}
|
||||
dismiss();
|
||||
MapActivity.launchMapActivityMoveToTop(getActivity());
|
||||
|
|
|
@ -526,7 +526,7 @@ public class FavoritesTreeFragment extends OsmandExpandableListFragment implemen
|
|||
for (Map.Entry<String, Set<FavouritePoint>> entry : favoritesSelected.entrySet()) {
|
||||
FavoriteGroup group = helper.getGroup(entry.getKey());
|
||||
if (group != null && entry.getValue().size() == group.getPoints().size()) {
|
||||
getMyApplication().getItineraryHelper().addOrEnableGroup(group);
|
||||
markersHelper.addOrEnableGroup(group);
|
||||
} else {
|
||||
for (FavouritePoint fp : entry.getValue()) {
|
||||
points.add(new LatLon(fp.getLatitude(), fp.getLongitude()));
|
||||
|
|
|
@ -13,6 +13,7 @@ import net.osmand.plus.OsmandApplication;
|
|||
import net.osmand.plus.R;
|
||||
import net.osmand.plus.SQLiteTileSource;
|
||||
import net.osmand.plus.Version;
|
||||
import net.osmand.plus.download.SrtmDownloadItem;
|
||||
import net.osmand.plus.download.ui.AbstractLoadLocalIndexTask;
|
||||
import net.osmand.plus.voice.JSMediaCommandPlayerImpl;
|
||||
import net.osmand.plus.voice.JSTTSCommandPlayerImpl;
|
||||
|
@ -143,8 +144,6 @@ public class LocalIndexHelper {
|
|||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public List<LocalIndexInfo> getLocalIndexInfos(String downloadName) {
|
||||
List<LocalIndexInfo> list = new ArrayList<>();
|
||||
LocalIndexInfo info = getLocalIndexInfo(LocalIndexType.MAP_DATA, downloadName, false, false);
|
||||
|
@ -313,7 +312,7 @@ public class LocalIndexHelper {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void loadTravelData(File mapPath, List<LocalIndexInfo> result, AbstractLoadLocalIndexTask loadTask) {
|
||||
if (mapPath.canRead()) {
|
||||
for (File mapFile : listFilesSorted(mapPath)) {
|
||||
|
@ -333,14 +332,15 @@ public class LocalIndexHelper {
|
|||
if (mapPath.canRead()) {
|
||||
for (File mapFile : listFilesSorted(mapPath)) {
|
||||
if (mapFile.isFile() && mapFile.getName().endsWith(IndexConstants.BINARY_MAP_INDEX_EXT)) {
|
||||
String fileName = mapFile.getName();
|
||||
LocalIndexType lt = LocalIndexType.MAP_DATA;
|
||||
if (mapFile.getName().endsWith(IndexConstants.BINARY_SRTM_MAP_INDEX_EXT)) {
|
||||
if (SrtmDownloadItem.isSrtmFile(fileName)) {
|
||||
lt = LocalIndexType.SRTM_DATA;
|
||||
} else if (mapFile.getName().endsWith(IndexConstants.BINARY_WIKI_MAP_INDEX_EXT)) {
|
||||
} else if (fileName.endsWith(IndexConstants.BINARY_WIKI_MAP_INDEX_EXT)) {
|
||||
lt = LocalIndexType.WIKI_DATA;
|
||||
}
|
||||
LocalIndexInfo info = new LocalIndexInfo(lt, mapFile, backup, app);
|
||||
if (loadedMaps.containsKey(mapFile.getName()) && !backup) {
|
||||
if (loadedMaps.containsKey(fileName) && !backup) {
|
||||
info.setLoaded(true);
|
||||
}
|
||||
updateDescription(info);
|
||||
|
@ -403,7 +403,7 @@ public class LocalIndexHelper {
|
|||
if (fileName.endsWith(IndexConstants.SQLITE_EXT)) {
|
||||
return fileName.substring(0, fileName.length() - IndexConstants.SQLITE_EXT.length());
|
||||
}
|
||||
if (localIndexInfo.getType() == TRAVEL_DATA &&
|
||||
if (localIndexInfo.getType() == TRAVEL_DATA &&
|
||||
fileName.endsWith(IndexConstants.BINARY_WIKIVOYAGE_MAP_INDEX_EXT)) {
|
||||
return fileName.substring(0, fileName.length() - IndexConstants.BINARY_WIKIVOYAGE_MAP_INDEX_EXT.length());
|
||||
}
|
||||
|
@ -430,5 +430,4 @@ public class LocalIndexHelper {
|
|||
return fileName;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ import android.media.MediaRecorder;
|
|||
import android.media.SoundPool;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.StatFs;
|
||||
import android.provider.MediaStore;
|
||||
import android.view.Display;
|
||||
import android.view.KeyEvent;
|
||||
|
@ -1607,13 +1606,7 @@ public class AudioVideoNotesPlugin extends OsmandPlugin {
|
|||
double bitrate = (((p.videoBitRate + p.audioBitRate) / 8f) * 60f) / (1 << 30); // gigabytes per minute
|
||||
double clipSpace = bitrate * AV_RS_CLIP_LENGTH.get();
|
||||
double storageSize = AV_RS_STORAGE_SIZE.get();
|
||||
|
||||
double availableSpace = storageSize;
|
||||
File dir = app.getAppPath("").getParentFile();
|
||||
if (dir.canRead()) {
|
||||
StatFs fs = new StatFs(dir.getAbsolutePath());
|
||||
availableSpace = (double) (fs.getAvailableBlocks()) * fs.getBlockSize() / (1 << 30) - clipSpace;
|
||||
}
|
||||
double availableSpace = (double) AndroidUtils.getAvailableSpace(app) / (1 << 30) - clipSpace;
|
||||
|
||||
if (usedSpace + clipSpace > storageSize || clipSpace > availableSpace) {
|
||||
Arrays.sort(files, new Comparator<File>() {
|
||||
|
|
|
@ -11,7 +11,6 @@ import android.media.CamcorderProfile;
|
|||
import android.media.MediaRecorder;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.StatFs;
|
||||
import android.text.SpannableString;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
@ -42,7 +41,6 @@ import net.osmand.plus.widgets.style.CustomTypefaceSpan;
|
|||
|
||||
import org.apache.commons.logging.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -381,16 +379,7 @@ public class MultimediaNotesFragment extends BaseSettingsFragment implements Cop
|
|||
private void setupStorageSizePref(AudioVideoNotesPlugin plugin) {
|
||||
ListPreferenceEx storageSize = (ListPreferenceEx) findPreference(plugin.AV_RS_STORAGE_SIZE.getId());
|
||||
|
||||
File dir = app.getAppPath("").getParentFile();
|
||||
long size = 0;
|
||||
if (dir.canRead()) {
|
||||
try {
|
||||
StatFs fs = new StatFs(dir.getAbsolutePath());
|
||||
size = ((long) fs.getBlockSize() * (long) fs.getBlockCount()) / (1 << 30);
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.error(e);
|
||||
}
|
||||
}
|
||||
long size = AndroidUtils.getTotalSpace(app) / (1 << 30);
|
||||
if (size > 0) {
|
||||
int value = 1;
|
||||
ArrayList<Integer> gbList = new ArrayList<>();
|
||||
|
|
651
OsmAnd/src/net/osmand/plus/backup/BackupHelper.java
Normal file
651
OsmAnd/src/net/osmand/plus/backup/BackupHelper.java
Normal file
|
@ -0,0 +1,651 @@
|
|||
package net.osmand.plus.backup;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.AsyncTask;
|
||||
import android.provider.Settings;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import net.osmand.AndroidNetworkUtils;
|
||||
import net.osmand.AndroidNetworkUtils.OnFilesDownloadCallback;
|
||||
import net.osmand.AndroidNetworkUtils.OnFilesUploadCallback;
|
||||
import net.osmand.AndroidNetworkUtils.OnRequestResultListener;
|
||||
import net.osmand.AndroidNetworkUtils.OnSendRequestsListener;
|
||||
import net.osmand.AndroidNetworkUtils.Request;
|
||||
import net.osmand.AndroidNetworkUtils.RequestResponse;
|
||||
import net.osmand.AndroidUtils;
|
||||
import net.osmand.IndexConstants;
|
||||
import net.osmand.plus.FavouritesDbHelper;
|
||||
import net.osmand.plus.GPXDatabase.GpxDataItem;
|
||||
import net.osmand.plus.GpxDbHelper;
|
||||
import net.osmand.plus.OsmandApplication;
|
||||
import net.osmand.plus.inapp.InAppPurchaseHelper;
|
||||
import net.osmand.plus.inapp.InAppPurchases.InAppSubscription;
|
||||
import net.osmand.plus.settings.backend.OsmandSettings;
|
||||
import net.osmand.util.Algorithms;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class BackupHelper {
|
||||
|
||||
private final OsmandApplication app;
|
||||
private final OsmandSettings settings;
|
||||
private final FavouritesDbHelper favouritesHelper;
|
||||
private final GpxDbHelper gpxHelper;
|
||||
|
||||
private static final ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(1, 1, 0L,
|
||||
TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
|
||||
|
||||
private static final String SERVER_URL = "https://osmand.net";
|
||||
|
||||
private static final String USER_REGISTER_URL = SERVER_URL + "/userdata/user-register";
|
||||
private static final String DEVICE_REGISTER_URL = SERVER_URL + "/userdata/device-register";
|
||||
private static final String UPLOAD_FILE_URL = SERVER_URL + "/userdata/upload-file";
|
||||
private static final String LIST_FILES_URL = SERVER_URL + "/userdata/list-files";
|
||||
private static final String DOWNLOAD_FILE_URL = SERVER_URL + "/userdata/download-file";
|
||||
private static final String DELETE_FILE_URL = SERVER_URL + "/userdata/delete-file";
|
||||
|
||||
public final static int STATUS_SUCCESS = 0;
|
||||
public final static int STATUS_PARSE_JSON_ERROR = 1;
|
||||
public final static int STATUS_EMPTY_RESPONSE_ERROR = 2;
|
||||
public final static int STATUS_SERVER_ERROR = 3;
|
||||
|
||||
public interface OnRegisterUserListener {
|
||||
void onRegisterUser(int status, @Nullable String message);
|
||||
}
|
||||
|
||||
public interface OnRegisterDeviceListener {
|
||||
void onRegisterDevice(int status, @Nullable String message);
|
||||
}
|
||||
|
||||
public interface OnDownloadFileListListener {
|
||||
void onDownloadFileList(int status, @Nullable String message, @NonNull List<UserFile> userFiles);
|
||||
}
|
||||
|
||||
public interface OnCollectLocalFilesListener {
|
||||
void onFileCollected(@NonNull GpxFileInfo fileInfo);
|
||||
void onFilesCollected(@NonNull List<GpxFileInfo> fileInfos);
|
||||
}
|
||||
|
||||
public interface OnGenerateBackupInfoListener {
|
||||
void onBackupInfoGenerated(@Nullable BackupInfo backupInfo, @Nullable String error);
|
||||
}
|
||||
|
||||
public interface OnUploadFilesListener {
|
||||
void onFileUploadProgress(@NonNull File file, int progress);
|
||||
void onFileUploadDone(@NonNull File file);
|
||||
void onFilesUploadDone(@NonNull Map<File, String> errors);
|
||||
}
|
||||
|
||||
public interface OnDeleteFilesListener {
|
||||
void onFileDeleteProgress(@NonNull UserFile file);
|
||||
void onFilesDeleteDone(@NonNull Map<UserFile, String> errors);
|
||||
}
|
||||
|
||||
public interface OnDownloadFileListener {
|
||||
void onFileDownloadProgress(@NonNull UserFile userFile, int progress);
|
||||
|
||||
@WorkerThread
|
||||
void onFileDownloadedAsync(@NonNull File file);
|
||||
void onFileDownloaded(@NonNull File file);
|
||||
void onFilesDownloadDone(@NonNull Map<File, String> errors);
|
||||
}
|
||||
|
||||
public static class BackupInfo {
|
||||
public List<UserFile> filesToDownload = new ArrayList<>();
|
||||
public List<GpxFileInfo> filesToUpload = new ArrayList<>();
|
||||
public List<UserFile> filesToDelete = new ArrayList<>();
|
||||
public List<Pair<GpxFileInfo, UserFile>> filesToMerge = new ArrayList<>();
|
||||
}
|
||||
|
||||
public BackupHelper(@NonNull OsmandApplication app) {
|
||||
this.app = app;
|
||||
this.settings = app.getSettings();
|
||||
this.favouritesHelper = app.getFavorites();
|
||||
this.gpxHelper = app.getGpxDbHelper();
|
||||
}
|
||||
|
||||
@SuppressLint("HardwareIds")
|
||||
private String getAndroidId() {
|
||||
try {
|
||||
return Settings.Secure.getString(app.getContentResolver(), Settings.Secure.ANDROID_ID);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isTokenValid(@NonNull String token) {
|
||||
return token.matches("[0-9]+");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getOrderId() {
|
||||
InAppPurchaseHelper purchaseHelper = app.getInAppPurchaseHelper();
|
||||
InAppSubscription purchasedSubscription = purchaseHelper.getAnyPurchasedSubscription();
|
||||
return purchasedSubscription != null ? purchasedSubscription.getOrderId() : null;
|
||||
}
|
||||
|
||||
public String getDeviceId() {
|
||||
return settings.BACKUP_DEVICE_ID.get();
|
||||
}
|
||||
|
||||
public String getAccessToken() {
|
||||
return settings.BACKUP_ACCESS_TOKEN.get();
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return settings.BACKUP_USER_EMAIL.get();
|
||||
}
|
||||
|
||||
public boolean isRegistered() {
|
||||
return !Algorithms.isEmpty(getDeviceId()) && !Algorithms.isEmpty(getAccessToken());
|
||||
}
|
||||
|
||||
private void checkRegistered() throws UserNotRegisteredException {
|
||||
if (Algorithms.isEmpty(getDeviceId()) || Algorithms.isEmpty(getAccessToken())) {
|
||||
throw new UserNotRegisteredException();
|
||||
}
|
||||
}
|
||||
|
||||
public void registerUser(@NonNull String email, @Nullable final OnRegisterUserListener listener) {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("email", email);
|
||||
String orderId = getOrderId();
|
||||
if (!Algorithms.isEmpty(orderId)) {
|
||||
params.put("orderid", orderId);
|
||||
}
|
||||
params.put("deviceid", app.getUserAndroidId());
|
||||
AndroidNetworkUtils.sendRequestAsync(app, USER_REGISTER_URL, params, "Register user", false, true, new OnRequestResultListener() {
|
||||
@Override
|
||||
public void onResult(@Nullable String resultJson, @Nullable String error) {
|
||||
int status;
|
||||
String message;
|
||||
if (!Algorithms.isEmpty(error)) {
|
||||
message = "User registration error: " + parseServerError(error);
|
||||
status = STATUS_SERVER_ERROR;
|
||||
} else if (!Algorithms.isEmpty(resultJson)) {
|
||||
try {
|
||||
JSONObject result = new JSONObject(resultJson);
|
||||
if (result.has("status") && "ok".equals(result.getString("status"))) {
|
||||
message = "You have been registered successfully. Please check for email with activation code.";
|
||||
status = STATUS_SUCCESS;
|
||||
} else {
|
||||
message = "User registration error: unknown";
|
||||
status = STATUS_SERVER_ERROR;
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
message = "User registration error: json parsing";
|
||||
status = STATUS_PARSE_JSON_ERROR;
|
||||
}
|
||||
} else {
|
||||
message = "User registration error: empty response";
|
||||
status = STATUS_EMPTY_RESPONSE_ERROR;
|
||||
}
|
||||
if (listener != null) {
|
||||
listener.onRegisterUser(status, message);
|
||||
}
|
||||
}
|
||||
}, EXECUTOR);
|
||||
}
|
||||
|
||||
public void registerDevice(String token, @Nullable final OnRegisterDeviceListener listener) {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("email", getEmail());
|
||||
String orderId = getOrderId();
|
||||
if (orderId != null) {
|
||||
params.put("orderid", orderId);
|
||||
}
|
||||
String androidId = getAndroidId();
|
||||
if (!Algorithms.isEmpty(androidId)) {
|
||||
params.put("deviceid", androidId);
|
||||
}
|
||||
params.put("token", token);
|
||||
AndroidNetworkUtils.sendRequestAsync(app, DEVICE_REGISTER_URL, params, "Register device", false, true, new OnRequestResultListener() {
|
||||
@Override
|
||||
public void onResult(@Nullable String resultJson, @Nullable String error) {
|
||||
int status;
|
||||
String message;
|
||||
if (!Algorithms.isEmpty(error)) {
|
||||
message = "Device registration error: " + parseServerError(error);
|
||||
status = STATUS_SERVER_ERROR;
|
||||
} else if (!Algorithms.isEmpty(resultJson)) {
|
||||
try {
|
||||
JSONObject result = new JSONObject(resultJson);
|
||||
settings.BACKUP_DEVICE_ID.set(result.getString("id"));
|
||||
settings.BACKUP_USER_ID.set(result.getString("userid"));
|
||||
settings.BACKUP_NATIVE_DEVICE_ID.set(result.getString("deviceid"));
|
||||
settings.BACKUP_ACCESS_TOKEN.set(result.getString("accesstoken"));
|
||||
settings.BACKUP_ACCESS_TOKEN_UPDATE_TIME.set(result.getString("udpatetime"));
|
||||
|
||||
message = "Device have been registered successfully";
|
||||
status = STATUS_SUCCESS;
|
||||
} catch (JSONException e) {
|
||||
message = "Device registration error: json parsing";
|
||||
status = STATUS_PARSE_JSON_ERROR;
|
||||
}
|
||||
} else {
|
||||
message = "Device registration error: empty response";
|
||||
status = STATUS_EMPTY_RESPONSE_ERROR;
|
||||
}
|
||||
if (listener != null) {
|
||||
listener.onRegisterDevice(status, message);
|
||||
}
|
||||
}
|
||||
}, EXECUTOR);
|
||||
}
|
||||
|
||||
public void uploadFiles(@NonNull List<GpxFileInfo> gpxFiles, @Nullable final OnUploadFilesListener listener) throws UserNotRegisteredException {
|
||||
checkRegistered();
|
||||
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("deviceid", getDeviceId());
|
||||
params.put("accessToken", getAccessToken());
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("Accept-Encoding", "deflate, gzip");
|
||||
|
||||
final Map<File, GpxFileInfo> gpxInfos = new HashMap<>();
|
||||
for (GpxFileInfo gpxFile : gpxFiles) {
|
||||
gpxInfos.put(gpxFile.file, gpxFile);
|
||||
}
|
||||
final File favoritesFile = favouritesHelper.getExternalFile();
|
||||
AndroidNetworkUtils.uploadFilesAsync(UPLOAD_FILE_URL, new ArrayList<>(gpxInfos.keySet()), true, params, headers, new OnFilesUploadCallback() {
|
||||
@Nullable
|
||||
@Override
|
||||
public Map<String, String> getAdditionalParams(@NonNull File file) {
|
||||
Map<String, String> additionaParams = new HashMap<>();
|
||||
GpxFileInfo gpxFileInfo = gpxInfos.get(file);
|
||||
if (gpxFileInfo != null) {
|
||||
additionaParams.put("name", gpxFileInfo.getFileName(true));
|
||||
additionaParams.put("type", Algorithms.getFileExtension(file));
|
||||
gpxFileInfo.uploadTime = System.currentTimeMillis();
|
||||
additionaParams.put("clienttime", String.valueOf(gpxFileInfo.uploadTime));
|
||||
}
|
||||
return additionaParams;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFileUploadProgress(@NonNull File file, int progress) {
|
||||
if (listener != null) {
|
||||
listener.onFileUploadProgress(file, progress);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFileUploadDone(@NonNull File file) {
|
||||
if (listener != null) {
|
||||
GpxFileInfo gpxFileInfo = gpxInfos.get(file);
|
||||
if (gpxFileInfo != null) {
|
||||
if (file.equals(favoritesFile)) {
|
||||
favouritesHelper.setLastUploadedTime(gpxFileInfo.uploadTime);
|
||||
} else {
|
||||
GpxDataItem gpxItem = gpxHelper.getItem(file);
|
||||
if (gpxItem != null) {
|
||||
gpxHelper.updateLastUploadedTime(gpxItem, gpxFileInfo.uploadTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
listener.onFileUploadDone(file);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFilesUploadDone(@NonNull Map<File, String> errors) {
|
||||
if (errors.isEmpty()) {
|
||||
settings.BACKUP_LAST_UPLOADED_TIME.set(System.currentTimeMillis() + 1);
|
||||
}
|
||||
if (listener != null) {
|
||||
listener.onFilesUploadDone(resolveServerErrors(errors));
|
||||
}
|
||||
}
|
||||
}, EXECUTOR);
|
||||
}
|
||||
|
||||
public void deleteFiles(@NonNull List<UserFile> userFiles, @Nullable final OnDeleteFilesListener listener) throws UserNotRegisteredException {
|
||||
checkRegistered();
|
||||
|
||||
Map<String, String> commonParameters = new HashMap<>();
|
||||
commonParameters.put("deviceid", getDeviceId());
|
||||
commonParameters.put("accessToken", getAccessToken());
|
||||
|
||||
final List<Request> requests = new ArrayList<>();
|
||||
final Map<Request, UserFile> filesMap = new HashMap<>();
|
||||
for (UserFile userFile : userFiles) {
|
||||
Map<String, String> parameters = new HashMap<>(commonParameters);
|
||||
parameters.put("name", userFile.getName());
|
||||
parameters.put("type", userFile.getType());
|
||||
Request r = new Request(DELETE_FILE_URL, parameters, null, false, true);
|
||||
requests.add(r);
|
||||
filesMap.put(r, userFile);
|
||||
}
|
||||
AndroidNetworkUtils.sendRequestsAsync(null, requests, new OnSendRequestsListener() {
|
||||
@Override
|
||||
public void onRequestSent(@NonNull RequestResponse response) {
|
||||
if (listener != null) {
|
||||
UserFile userFile = filesMap.get(response.getRequest());
|
||||
if (userFile != null) {
|
||||
listener.onFileDeleteProgress(userFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestsSent(@NonNull List<RequestResponse> results) {
|
||||
if (listener != null) {
|
||||
Map<UserFile, String> errors = new HashMap<>();
|
||||
for (RequestResponse response : results) {
|
||||
UserFile userFile = filesMap.get(response.getRequest());
|
||||
if (userFile != null) {
|
||||
boolean success;
|
||||
String message = null;
|
||||
String errorStr = response.getError();
|
||||
if (!Algorithms.isEmpty(errorStr)) {
|
||||
message = parseServerError(errorStr);
|
||||
success = false;
|
||||
} else {
|
||||
String responseStr = response.getResponse();
|
||||
try {
|
||||
JSONObject result = new JSONObject(responseStr);
|
||||
if (result.has("status") && "ok".equals(result.getString("status"))) {
|
||||
success = true;
|
||||
} else {
|
||||
message = "Unknown error";
|
||||
success = false;
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
message = "Json parsing error";
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
if (!success) {
|
||||
errors.put(userFile, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
listener.onFilesDeleteDone(errors);
|
||||
}
|
||||
}
|
||||
}, EXECUTOR);
|
||||
}
|
||||
|
||||
public void downloadFileList(@Nullable final OnDownloadFileListListener listener) throws UserNotRegisteredException {
|
||||
checkRegistered();
|
||||
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("deviceid", getDeviceId());
|
||||
params.put("accessToken", getAccessToken());
|
||||
AndroidNetworkUtils.sendRequestAsync(app, LIST_FILES_URL, params, "Download file list", false, false, new OnRequestResultListener() {
|
||||
@Override
|
||||
public void onResult(@Nullable String resultJson, @Nullable String error) {
|
||||
int status;
|
||||
String message;
|
||||
List<UserFile> userFiles = new ArrayList<>();
|
||||
if (!Algorithms.isEmpty(error)) {
|
||||
status = STATUS_SERVER_ERROR;
|
||||
message = "Download file list error: " + parseServerError(error);
|
||||
} else if (!Algorithms.isEmpty(resultJson)) {
|
||||
try {
|
||||
JSONObject result = new JSONObject(resultJson);
|
||||
String totalZipSize = result.getString("totalZipSize");
|
||||
String totalFiles = result.getString("totalFiles");
|
||||
String totalFileVersions = result.getString("totalFileVersions");
|
||||
JSONArray files = result.getJSONArray("uniqueFiles");
|
||||
for (int i = 0; i < files.length(); i++) {
|
||||
userFiles.add(new UserFile(files.getJSONObject(i)));
|
||||
}
|
||||
|
||||
status = STATUS_SUCCESS;
|
||||
message = "Total files: " + totalFiles + "\n" +
|
||||
"Total zip size: " + AndroidUtils.formatSize(app, Long.parseLong(totalZipSize)) + "\n" +
|
||||
"Total file versions: " + totalFileVersions;
|
||||
} catch (JSONException | ParseException e) {
|
||||
status = STATUS_PARSE_JSON_ERROR;
|
||||
message = "Download file list error: json parsing";
|
||||
}
|
||||
} else {
|
||||
status = STATUS_EMPTY_RESPONSE_ERROR;
|
||||
message = "Download file list error: empty response";
|
||||
}
|
||||
if (listener != null) {
|
||||
listener.onDownloadFileList(status, message, userFiles);
|
||||
}
|
||||
}
|
||||
}, EXECUTOR);
|
||||
}
|
||||
|
||||
public void downloadFiles(@NonNull final Map<File, UserFile> filesMap, @Nullable final OnDownloadFileListener listener) throws UserNotRegisteredException {
|
||||
checkRegistered();
|
||||
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("deviceid", getDeviceId());
|
||||
params.put("accessToken", getAccessToken());
|
||||
AndroidNetworkUtils.downloadFilesAsync(DOWNLOAD_FILE_URL,
|
||||
new ArrayList<>(filesMap.keySet()), params, new OnFilesDownloadCallback() {
|
||||
@Nullable
|
||||
@Override
|
||||
public Map<String, String> getAdditionalParams(@NonNull File file) {
|
||||
UserFile userFile = filesMap.get(file);
|
||||
Map<String, String> additionaParams = new HashMap<>();
|
||||
additionaParams.put("name", userFile.getName());
|
||||
additionaParams.put("type", userFile.getType());
|
||||
return additionaParams;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFileDownloadProgress(@NonNull File file, int percent) {
|
||||
if (listener != null) {
|
||||
listener.onFileDownloadProgress(filesMap.get(file), percent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFileDownloadDone(@NonNull File file) {
|
||||
if (listener != null) {
|
||||
listener.onFileDownloaded(file);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFileDownloadedAsync(@NonNull File file) {
|
||||
if (listener != null) {
|
||||
listener.onFileDownloadedAsync(file);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFilesDownloadDone(@NonNull Map<File, String> errors) {
|
||||
if (listener != null) {
|
||||
listener.onFilesDownloadDone(resolveServerErrors(errors));
|
||||
}
|
||||
}
|
||||
}, EXECUTOR);
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
public void collectLocalFiles(@Nullable final OnCollectLocalFilesListener listener) {
|
||||
AsyncTask<Void, GpxFileInfo, List<GpxFileInfo>> task = new AsyncTask<Void, GpxFileInfo, List<GpxFileInfo>>() {
|
||||
|
||||
private final OnCollectLocalFilesListener internalListener = new OnCollectLocalFilesListener() {
|
||||
@Override
|
||||
public void onFileCollected(@NonNull GpxFileInfo fileInfo) {
|
||||
publishProgress(fileInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFilesCollected(@NonNull List<GpxFileInfo> fileInfos) {
|
||||
}
|
||||
};
|
||||
|
||||
private void loadGPXData(@NonNull File mapPath, @NonNull List<GpxFileInfo> result,
|
||||
@Nullable OnCollectLocalFilesListener listener) {
|
||||
if (mapPath.canRead()) {
|
||||
loadGPXFolder(mapPath, result, "", listener);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadGPXFolder(@NonNull File mapPath, @NonNull List<GpxFileInfo> result,
|
||||
@NonNull String gpxSubfolder, @Nullable OnCollectLocalFilesListener listener) {
|
||||
File[] listFiles = mapPath.listFiles();
|
||||
if (listFiles != null) {
|
||||
for (File gpxFile : listFiles) {
|
||||
if (gpxFile.isDirectory()) {
|
||||
String sub = gpxSubfolder.length() == 0 ? gpxFile.getName() : gpxSubfolder + "/"
|
||||
+ gpxFile.getName();
|
||||
loadGPXFolder(gpxFile, result, sub, listener);
|
||||
} else if (gpxFile.isFile() && gpxFile.getName().toLowerCase().endsWith(IndexConstants.GPX_FILE_EXT)) {
|
||||
GpxFileInfo info = new GpxFileInfo();
|
||||
info.subfolder = gpxSubfolder;
|
||||
info.file = gpxFile;
|
||||
GpxDataItem gpxItem = gpxHelper.getItem(gpxFile);
|
||||
if (gpxItem != null) {
|
||||
info.uploadTime = gpxItem.getFileLastUploadedTime();
|
||||
}
|
||||
result.add(info);
|
||||
if (listener != null) {
|
||||
listener.onFileCollected(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<GpxFileInfo> doInBackground(Void... voids) {
|
||||
List<GpxFileInfo> result = new ArrayList<>();
|
||||
|
||||
GpxFileInfo favInfo = new GpxFileInfo();
|
||||
favInfo.subfolder = "";
|
||||
favInfo.file = favouritesHelper.getExternalFile();
|
||||
favInfo.uploadTime = favouritesHelper.getLastUploadedTime();
|
||||
result.add(favInfo);
|
||||
if (listener != null) {
|
||||
listener.onFileCollected(favInfo);
|
||||
}
|
||||
|
||||
loadGPXData(app.getAppPath(IndexConstants.GPX_INDEX_DIR), result, internalListener);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(GpxFileInfo... fileInfos) {
|
||||
if (listener != null) {
|
||||
listener.onFileCollected(fileInfos[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(List<GpxFileInfo> fileInfos) {
|
||||
if (listener != null) {
|
||||
listener.onFilesCollected(fileInfos);
|
||||
}
|
||||
}
|
||||
};
|
||||
task.executeOnExecutor(EXECUTOR);
|
||||
}
|
||||
|
||||
private Map<File, String> resolveServerErrors(@NonNull Map<File, String> errors) {
|
||||
Map<File, String> resolvedErrors = new HashMap<>();
|
||||
for (Entry<File, String> fileError : errors.entrySet()) {
|
||||
File file = fileError.getKey();
|
||||
String errorStr = fileError.getValue();
|
||||
try {
|
||||
JSONObject errorJson = new JSONObject(errorStr);
|
||||
JSONObject error = errorJson.getJSONObject("error");
|
||||
errorStr = "Error " + error.getInt("errorCode") + " (" + error.getString("message") + ")";
|
||||
} catch (JSONException e) {
|
||||
// ignore
|
||||
}
|
||||
resolvedErrors.put(file, errorStr);
|
||||
}
|
||||
return resolvedErrors;
|
||||
}
|
||||
|
||||
private String parseServerError(@NonNull String error) {
|
||||
try {
|
||||
JSONObject resultError = new JSONObject(error);
|
||||
if (resultError.has("error")) {
|
||||
JSONObject errorObj = resultError.getJSONObject("error");
|
||||
return errorObj.getInt("errorCode") + " (" + errorObj.getString("message") + ")";
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
// ignore
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
public void generateBackupInfo(@NonNull final List<GpxFileInfo> localFiles, @NonNull final List<UserFile> remoteFiles,
|
||||
@Nullable final OnGenerateBackupInfoListener listener) {
|
||||
|
||||
final long backupLastUploadedTime = settings.BACKUP_LAST_UPLOADED_TIME.get();
|
||||
|
||||
AsyncTask<Void, Void, BackupInfo> task = new AsyncTask<Void, Void, BackupInfo>() {
|
||||
@Override
|
||||
protected BackupInfo doInBackground(Void... voids) {
|
||||
BackupInfo info = new BackupInfo();
|
||||
for (UserFile remoteFile : remoteFiles) {
|
||||
boolean hasLocalFile = false;
|
||||
for (GpxFileInfo localFile : localFiles) {
|
||||
if (remoteFile.getName().equals(localFile.getFileName(true))) {
|
||||
hasLocalFile = true;
|
||||
long remoteUploadTime = remoteFile.getClienttimems();
|
||||
long localUploadTime = localFile.uploadTime;
|
||||
long localModifiedTime = localFile.file.lastModified();
|
||||
if (remoteUploadTime == localUploadTime) {
|
||||
if (localUploadTime < localModifiedTime) {
|
||||
info.filesToUpload.add(localFile);
|
||||
}
|
||||
} else {
|
||||
info.filesToMerge.add(new Pair<>(localFile, remoteFile));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasLocalFile) {
|
||||
if (backupLastUploadedTime > 0 && backupLastUploadedTime >= remoteFile.getClienttimems()) {
|
||||
info.filesToDelete.add(remoteFile);
|
||||
} else {
|
||||
info.filesToDownload.add(remoteFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (GpxFileInfo localFile : localFiles) {
|
||||
boolean hasRemoteFile = false;
|
||||
for (UserFile remoteFile : remoteFiles) {
|
||||
if (localFile.getFileName(true).equals(remoteFile.getName())) {
|
||||
hasRemoteFile = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasRemoteFile) {
|
||||
info.filesToUpload.add(localFile);
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(BackupInfo backupInfo) {
|
||||
if (listener != null) {
|
||||
listener.onBackupInfoGenerated(backupInfo, null);
|
||||
}
|
||||
}
|
||||
};
|
||||
task.executeOnExecutor(EXECUTOR);
|
||||
}
|
||||
}
|
359
OsmAnd/src/net/osmand/plus/backup/BackupTask.java
Normal file
359
OsmAnd/src/net/osmand/plus/backup/BackupTask.java
Normal file
|
@ -0,0 +1,359 @@
|
|||
package net.osmand.plus.backup;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import net.osmand.AndroidUtils;
|
||||
import net.osmand.GPXUtilities;
|
||||
import net.osmand.GPXUtilities.GPXFile;
|
||||
import net.osmand.plus.GPXDatabase.GpxDataItem;
|
||||
import net.osmand.plus.OsmandApplication;
|
||||
import net.osmand.plus.ProgressImplementation;
|
||||
import net.osmand.plus.backup.BackupHelper.BackupInfo;
|
||||
import net.osmand.plus.backup.BackupHelper.OnDeleteFilesListener;
|
||||
import net.osmand.plus.backup.BackupHelper.OnDownloadFileListener;
|
||||
import net.osmand.plus.backup.BackupHelper.OnUploadFilesListener;
|
||||
import net.osmand.plus.importfiles.FavoritesImportTask;
|
||||
import net.osmand.util.Algorithms;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Stack;
|
||||
|
||||
import static net.osmand.IndexConstants.GPX_INDEX_DIR;
|
||||
import static net.osmand.IndexConstants.TEMP_DIR;
|
||||
|
||||
public class BackupTask {
|
||||
|
||||
private final OsmandApplication app;
|
||||
private final BackupHelper backupHelper;
|
||||
|
||||
private final OnBackupListener listener;
|
||||
private final WeakReference<Context> contextRef;
|
||||
private ProgressImplementation progress;
|
||||
|
||||
private final BackupInfo backupInfo;
|
||||
private Map<File, String> uploadErrors;
|
||||
private Map<File, String> downloadErrors;
|
||||
private Map<UserFile, String> deleteErrors;
|
||||
private String error;
|
||||
|
||||
private final TaskType[] backupTasks = {TaskType.UPLOAD_FILES, TaskType.DELETE_FILES};
|
||||
private final TaskType[] restoreTasks = {TaskType.DOWNLOAD_FILES};
|
||||
|
||||
private Stack<TaskType> runningTasks = new Stack<>();
|
||||
|
||||
private enum TaskType {
|
||||
UPLOAD_FILES,
|
||||
DOWNLOAD_FILES,
|
||||
DELETE_FILES
|
||||
}
|
||||
|
||||
public interface OnBackupListener {
|
||||
void onBackupDone(@Nullable Map<File, String> uploadErrors,
|
||||
@Nullable Map<File, String> downloadErrors,
|
||||
@Nullable Map<UserFile, String> deleteErrors, @Nullable String error);
|
||||
}
|
||||
|
||||
public BackupTask(@NonNull BackupInfo backupInfo, @NonNull Context context, @Nullable OnBackupListener listener) {
|
||||
this.contextRef = new WeakReference<>(context);
|
||||
this.app = (OsmandApplication) context.getApplicationContext();
|
||||
this.backupHelper = app.getBackupHelper();
|
||||
this.backupInfo = backupInfo;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public BackupInfo getBackupInfo() {
|
||||
return backupInfo;
|
||||
}
|
||||
|
||||
public Map<File, String> getUploadErrors() {
|
||||
return uploadErrors;
|
||||
}
|
||||
|
||||
public Map<File, String> getDownloadErrors() {
|
||||
return downloadErrors;
|
||||
}
|
||||
|
||||
public Map<UserFile, String> getDeleteErrors() {
|
||||
return deleteErrors;
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public boolean runBackup() {
|
||||
if (!runningTasks.empty()) {
|
||||
return false;
|
||||
}
|
||||
initBackupTasks();
|
||||
return runTasks();
|
||||
}
|
||||
|
||||
public boolean runRestore() {
|
||||
if (!runningTasks.empty()) {
|
||||
return false;
|
||||
}
|
||||
initRestoreTasks();
|
||||
return runTasks();
|
||||
}
|
||||
|
||||
private void initBackupTasks() {
|
||||
initData();
|
||||
Stack<TaskType> tasks = new Stack<>();
|
||||
for (int i = backupTasks.length - 1; i >= 0; i--) {
|
||||
tasks.push(backupTasks[i]);
|
||||
}
|
||||
this.runningTasks = tasks;
|
||||
onBackupTasksInit();
|
||||
}
|
||||
|
||||
private void initRestoreTasks() {
|
||||
initData();
|
||||
Stack<TaskType> tasks = new Stack<>();
|
||||
for (int i = restoreTasks.length - 1; i >= 0; i--) {
|
||||
tasks.push(restoreTasks[i]);
|
||||
}
|
||||
this.runningTasks = tasks;
|
||||
onRestoreTasksInit();
|
||||
}
|
||||
|
||||
private void initData() {
|
||||
uploadErrors = null;
|
||||
downloadErrors = null;
|
||||
deleteErrors = null;
|
||||
error = null;
|
||||
}
|
||||
|
||||
private boolean runTasks() {
|
||||
if (runningTasks.empty()) {
|
||||
return false;
|
||||
} else {
|
||||
TaskType taskType = runningTasks.pop();
|
||||
runTask(taskType);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void runTask(@NonNull TaskType taskType) {
|
||||
switch (taskType) {
|
||||
case UPLOAD_FILES:
|
||||
doUploadFiles();
|
||||
break;
|
||||
case DOWNLOAD_FILES:
|
||||
doDownloadFiles();
|
||||
break;
|
||||
case DELETE_FILES:
|
||||
doDeleteFiles();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void onTaskFinished(@NonNull TaskType taskType) {
|
||||
if (!runTasks()) {
|
||||
onTasksDone();
|
||||
}
|
||||
}
|
||||
|
||||
private void doUploadFiles() {
|
||||
if (Algorithms.isEmpty(backupInfo.filesToUpload)) {
|
||||
onTaskFinished(TaskType.UPLOAD_FILES);
|
||||
return;
|
||||
}
|
||||
onTaskProgressUpdate("Upload files...");
|
||||
try {
|
||||
backupHelper.uploadFiles(backupInfo.filesToUpload, new OnUploadFilesListener() {
|
||||
@Override
|
||||
public void onFileUploadProgress(@NonNull File file, int progress) {
|
||||
if (progress == 0) {
|
||||
onTaskProgressUpdate(file.getName(), (int) (file.length() / 1024));
|
||||
} else {
|
||||
onTaskProgressUpdate(progress);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFileUploadDone(@NonNull File file) {
|
||||
onTaskProgressDone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFilesUploadDone(@NonNull Map<File, String> errors) {
|
||||
uploadErrors = errors;
|
||||
onTaskFinished(TaskType.UPLOAD_FILES);
|
||||
}
|
||||
});
|
||||
} catch (UserNotRegisteredException e) {
|
||||
onError("User is not registered");
|
||||
}
|
||||
}
|
||||
|
||||
private void doDownloadFiles() {
|
||||
if (Algorithms.isEmpty(backupInfo.filesToDownload)) {
|
||||
onTaskFinished(TaskType.DOWNLOAD_FILES);
|
||||
return;
|
||||
}
|
||||
onTaskProgressUpdate("Download files...");
|
||||
File favoritesFile = app.getFavorites().getExternalFile();
|
||||
String favoritesFileName = favoritesFile.getName();
|
||||
File tempFavoritesFile = null;
|
||||
final Map<File, UserFile> filesMap = new HashMap<>();
|
||||
for (UserFile userFile : backupInfo.filesToDownload) {
|
||||
File file;
|
||||
String fileName = userFile.getName();
|
||||
if (favoritesFileName.equals(fileName)) {
|
||||
file = new File(app.getAppPath(TEMP_DIR), fileName);
|
||||
tempFavoritesFile = file;
|
||||
} else {
|
||||
file = new File(app.getAppPath(GPX_INDEX_DIR), fileName);
|
||||
}
|
||||
filesMap.put(file, userFile);
|
||||
}
|
||||
final File finalTempFavoritesFile = tempFavoritesFile;
|
||||
try {
|
||||
backupHelper.downloadFiles(filesMap, new OnDownloadFileListener() {
|
||||
@Override
|
||||
public void onFileDownloadProgress(@NonNull UserFile userFile, int progress) {
|
||||
if (progress == 0) {
|
||||
onTaskProgressUpdate(new File(userFile.getName()).getName(), userFile.getFilesize() / 1024);
|
||||
} else {
|
||||
onTaskProgressUpdate(progress);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFileDownloaded(@NonNull File file) {
|
||||
onTaskProgressDone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFileDownloadedAsync(@NonNull File file) {
|
||||
UserFile userFile = filesMap.get(file);
|
||||
long userFileTime = userFile.getClienttimems();
|
||||
if (file.equals(finalTempFavoritesFile)) {
|
||||
GPXFile gpxFile = GPXUtilities.loadGPXFile(finalTempFavoritesFile);
|
||||
FavoritesImportTask.mergeFavorites(app, gpxFile, "", false);
|
||||
finalTempFavoritesFile.delete();
|
||||
app.getFavorites().getExternalFile().setLastModified(userFileTime);
|
||||
} else {
|
||||
file.setLastModified(userFileTime);
|
||||
GpxDataItem item = new GpxDataItem(file, userFileTime);
|
||||
app.getGpxDbHelper().add(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFilesDownloadDone(@NonNull Map<File, String> errors) {
|
||||
downloadErrors = errors;
|
||||
onTaskFinished(TaskType.DOWNLOAD_FILES);
|
||||
}
|
||||
});
|
||||
} catch (UserNotRegisteredException e) {
|
||||
onError("User is not registered");
|
||||
}
|
||||
}
|
||||
|
||||
private void doDeleteFiles() {
|
||||
if (Algorithms.isEmpty(backupInfo.filesToDelete)) {
|
||||
onTaskFinished(TaskType.DELETE_FILES);
|
||||
return;
|
||||
}
|
||||
onTaskProgressUpdate("Delete files...");
|
||||
try {
|
||||
backupHelper.deleteFiles(backupInfo.filesToDelete, new OnDeleteFilesListener() {
|
||||
@Override
|
||||
public void onFileDeleteProgress(@NonNull UserFile userFile) {
|
||||
onTaskProgressUpdate(userFile.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFilesDeleteDone(@NonNull Map<UserFile, String> errors) {
|
||||
deleteErrors = errors;
|
||||
onTaskFinished(TaskType.DELETE_FILES);
|
||||
}
|
||||
});
|
||||
} catch (UserNotRegisteredException e) {
|
||||
onError("User is not registered");
|
||||
}
|
||||
}
|
||||
|
||||
private void onBackupTasksInit() {
|
||||
Context ctx = contextRef.get();
|
||||
if (ctx instanceof Activity && AndroidUtils.isActivityNotDestroyed((Activity) ctx)) {
|
||||
progress = ProgressImplementation.createProgressDialog(ctx,
|
||||
"Backup data", "Initializing...", ProgressDialog.STYLE_HORIZONTAL);
|
||||
}
|
||||
}
|
||||
|
||||
private void onRestoreTasksInit() {
|
||||
Context ctx = contextRef.get();
|
||||
if (ctx instanceof Activity && AndroidUtils.isActivityNotDestroyed((Activity) ctx)) {
|
||||
progress = ProgressImplementation.createProgressDialog(ctx,
|
||||
"Restore data", "Initializing...", ProgressDialog.STYLE_HORIZONTAL);
|
||||
}
|
||||
}
|
||||
|
||||
private void onTaskProgressUpdate(Object... objects) {
|
||||
Context ctx = contextRef.get();
|
||||
if (ctx instanceof Activity && AndroidUtils.isActivityNotDestroyed((Activity) ctx) && progress != null) {
|
||||
if (objects != null) {
|
||||
if (objects.length == 1) {
|
||||
if (objects[0] instanceof String) {
|
||||
progress.startTask((String) objects[0], -1);
|
||||
} else if (objects[0] instanceof Integer) {
|
||||
int progressValue = (Integer) objects[0];
|
||||
if (progressValue >= 0) {
|
||||
progress.progress(progressValue);
|
||||
} else {
|
||||
progress.finishTask();
|
||||
}
|
||||
}
|
||||
} else if (objects.length == 2) {
|
||||
progress.startTask((String) objects[0], (Integer) objects[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onTaskProgressDone() {
|
||||
Context ctx = contextRef.get();
|
||||
if (ctx instanceof Activity && AndroidUtils.isActivityNotDestroyed((Activity) ctx) && progress != null) {
|
||||
progress.finishTask();
|
||||
}
|
||||
}
|
||||
|
||||
private void onError(@NonNull String message) {
|
||||
this.error = message;
|
||||
runningTasks.clear();
|
||||
onTasksDone();
|
||||
}
|
||||
|
||||
private void onTasksDone() {
|
||||
if (listener != null) {
|
||||
listener.onBackupDone(uploadErrors, downloadErrors, deleteErrors, error);
|
||||
}
|
||||
Context ctx = contextRef.get();
|
||||
if (ctx instanceof Activity && AndroidUtils.isActivityNotDestroyed((Activity) ctx) && progress != null) {
|
||||
progress.finishTask();
|
||||
app.runInUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if (progress.getDialog().isShowing()) {
|
||||
progress.getDialog().dismiss();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
//ignored
|
||||
}
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
}
|
65
OsmAnd/src/net/osmand/plus/backup/GpxFileInfo.java
Normal file
65
OsmAnd/src/net/osmand/plus/backup/GpxFileInfo.java
Normal file
|
@ -0,0 +1,65 @@
|
|||
package net.osmand.plus.backup;
|
||||
|
||||
import net.osmand.util.Algorithms;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class GpxFileInfo {
|
||||
public File file;
|
||||
public String subfolder;
|
||||
public long uploadTime = 0;
|
||||
|
||||
private String name = null;
|
||||
private int sz = -1;
|
||||
private String fileName = null;
|
||||
|
||||
public String getName() {
|
||||
if (name == null) {
|
||||
name = formatName(file.getName());
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
private String formatName(String name) {
|
||||
int ext = name.lastIndexOf('.');
|
||||
if (ext != -1) {
|
||||
name = name.substring(0, ext);
|
||||
}
|
||||
return name.replace('_', ' ');
|
||||
}
|
||||
|
||||
// Usage: AndroidUtils.formatSize(v.getContext(), getSize() * 1024l);
|
||||
public int getSize() {
|
||||
if (sz == -1) {
|
||||
if (file == null) {
|
||||
return -1;
|
||||
}
|
||||
sz = (int) ((file.length() + 512) >> 10);
|
||||
}
|
||||
return sz;
|
||||
}
|
||||
|
||||
public long getFileDate() {
|
||||
if (file == null) {
|
||||
return 0;
|
||||
}
|
||||
return file.lastModified();
|
||||
}
|
||||
|
||||
public String getFileName(boolean includeSubfolder) {
|
||||
String result;
|
||||
if (fileName != null) {
|
||||
result = fileName;
|
||||
} else {
|
||||
if (file == null) {
|
||||
result = "";
|
||||
} else {
|
||||
result = fileName = file.getName();
|
||||
}
|
||||
}
|
||||
if (includeSubfolder && !Algorithms.isEmpty(subfolder)) {
|
||||
result = subfolder + "/" + result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
211
OsmAnd/src/net/osmand/plus/backup/PrepareBackupTask.java
Normal file
211
OsmAnd/src/net/osmand/plus/backup/PrepareBackupTask.java
Normal file
|
@ -0,0 +1,211 @@
|
|||
package net.osmand.plus.backup;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import net.osmand.AndroidUtils;
|
||||
import net.osmand.plus.OsmandApplication;
|
||||
import net.osmand.plus.ProgressImplementation;
|
||||
import net.osmand.plus.backup.BackupHelper.BackupInfo;
|
||||
import net.osmand.plus.backup.BackupHelper.OnCollectLocalFilesListener;
|
||||
import net.osmand.plus.backup.BackupHelper.OnDownloadFileListListener;
|
||||
import net.osmand.plus.backup.BackupHelper.OnGenerateBackupInfoListener;
|
||||
import net.osmand.util.Algorithms;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
|
||||
public class PrepareBackupTask {
|
||||
|
||||
private final OsmandApplication app;
|
||||
private final BackupHelper backupHelper;
|
||||
|
||||
private final OnPrepareBackupListener listener;
|
||||
private final WeakReference<Context> contextRef;
|
||||
private ProgressImplementation progress;
|
||||
|
||||
private BackupInfo result;
|
||||
private List<UserFile> userFiles;
|
||||
private List<GpxFileInfo> fileInfos;
|
||||
private String error;
|
||||
|
||||
private Stack<TaskType> runningTasks = new Stack<>();
|
||||
|
||||
private enum TaskType {
|
||||
COLLECT_LOCAL_FILES,
|
||||
COLLECT_REMOTE_FILES,
|
||||
GENERATE_BACKUP_INFO
|
||||
}
|
||||
|
||||
public interface OnPrepareBackupListener {
|
||||
void onBackupPrepared(@Nullable BackupInfo backupInfo, @Nullable String error);
|
||||
}
|
||||
|
||||
public PrepareBackupTask(@NonNull Context context, @Nullable OnPrepareBackupListener listener) {
|
||||
this.contextRef = new WeakReference<>(context);
|
||||
this.app = (OsmandApplication) context.getApplicationContext();
|
||||
this.backupHelper = app.getBackupHelper();
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public BackupInfo getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public boolean prepare() {
|
||||
if (!runningTasks.empty()) {
|
||||
return false;
|
||||
}
|
||||
initTasks();
|
||||
return runTasks();
|
||||
}
|
||||
|
||||
private void initTasks() {
|
||||
result = null;
|
||||
userFiles = null;
|
||||
fileInfos = null;
|
||||
error = null;
|
||||
Stack<TaskType> tasks = new Stack<>();
|
||||
TaskType[] types = TaskType.values();
|
||||
for (int i = types.length - 1; i >= 0; i--) {
|
||||
tasks.push(types[i]);
|
||||
}
|
||||
this.runningTasks = tasks;
|
||||
onTasksInit();
|
||||
}
|
||||
|
||||
private boolean runTasks() {
|
||||
if (runningTasks.empty()) {
|
||||
return false;
|
||||
} else {
|
||||
TaskType taskType = runningTasks.pop();
|
||||
runTask(taskType);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void runTask(@NonNull TaskType taskType) {
|
||||
switch (taskType) {
|
||||
case COLLECT_LOCAL_FILES:
|
||||
doCollectLocalFiles();
|
||||
break;
|
||||
case COLLECT_REMOTE_FILES:
|
||||
doCollectRemoteFiles();
|
||||
break;
|
||||
case GENERATE_BACKUP_INFO:
|
||||
doGenerateBackupInfo();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void onTaskFinished(@NonNull TaskType taskType) {
|
||||
if (!runTasks()) {
|
||||
onTasksDone();
|
||||
}
|
||||
}
|
||||
|
||||
private void doCollectLocalFiles() {
|
||||
onTaskProgressUpdate("Collecting local info...");
|
||||
backupHelper.collectLocalFiles(new OnCollectLocalFilesListener() {
|
||||
@Override
|
||||
public void onFileCollected(@NonNull GpxFileInfo fileInfo) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFilesCollected(@NonNull List<GpxFileInfo> fileInfos) {
|
||||
PrepareBackupTask.this.fileInfos = fileInfos;
|
||||
onTaskFinished(TaskType.COLLECT_LOCAL_FILES);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void doCollectRemoteFiles() {
|
||||
onTaskProgressUpdate("Downloading remote info...");
|
||||
try {
|
||||
backupHelper.downloadFileList(new OnDownloadFileListListener() {
|
||||
@Override
|
||||
public void onDownloadFileList(int status, @Nullable String message, @NonNull List<UserFile> userFiles) {
|
||||
if (status == BackupHelper.STATUS_SUCCESS) {
|
||||
PrepareBackupTask.this.userFiles = userFiles;
|
||||
} else {
|
||||
onError(!Algorithms.isEmpty(message) ? message : "Download file list error: " + status);
|
||||
}
|
||||
onTaskFinished(TaskType.COLLECT_REMOTE_FILES);
|
||||
}
|
||||
});
|
||||
} catch (UserNotRegisteredException e) {
|
||||
onError("User is not registered");
|
||||
}
|
||||
}
|
||||
|
||||
private void doGenerateBackupInfo() {
|
||||
if (fileInfos == null || userFiles == null) {
|
||||
onTaskFinished(TaskType.GENERATE_BACKUP_INFO);
|
||||
return;
|
||||
}
|
||||
onTaskProgressUpdate("Generating backup info...");
|
||||
backupHelper.generateBackupInfo(fileInfos, userFiles, new OnGenerateBackupInfoListener() {
|
||||
@Override
|
||||
public void onBackupInfoGenerated(@Nullable BackupInfo backupInfo, @Nullable String error) {
|
||||
if (Algorithms.isEmpty(error)) {
|
||||
PrepareBackupTask.this.result = backupInfo;
|
||||
} else {
|
||||
onError(error);
|
||||
}
|
||||
onTaskFinished(TaskType.GENERATE_BACKUP_INFO);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onTasksInit() {
|
||||
Context ctx = contextRef.get();
|
||||
if (ctx instanceof Activity && AndroidUtils.isActivityNotDestroyed((Activity) ctx)) {
|
||||
progress = ProgressImplementation.createProgressDialog(ctx,
|
||||
"Prepare backup", "Initializing...", ProgressDialog.STYLE_HORIZONTAL);
|
||||
}
|
||||
}
|
||||
|
||||
private void onTaskProgressUpdate(String message) {
|
||||
Context ctx = contextRef.get();
|
||||
if (ctx instanceof Activity && AndroidUtils.isActivityNotDestroyed((Activity) ctx) && progress != null) {
|
||||
progress.startTask(message, -1);
|
||||
}
|
||||
}
|
||||
|
||||
private void onError(@NonNull String message) {
|
||||
this.error = message;
|
||||
runningTasks.clear();
|
||||
onTasksDone();
|
||||
}
|
||||
|
||||
private void onTasksDone() {
|
||||
if (listener != null) {
|
||||
listener.onBackupPrepared(result, error);
|
||||
}
|
||||
Context ctx = contextRef.get();
|
||||
if (ctx instanceof Activity && AndroidUtils.isActivityNotDestroyed((Activity) ctx) && progress != null) {
|
||||
progress.finishTask();
|
||||
app.runInUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if (progress.getDialog().isShowing()) {
|
||||
progress.getDialog().dismiss();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
//ignored
|
||||
}
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
}
|
102
OsmAnd/src/net/osmand/plus/backup/UserFile.java
Normal file
102
OsmAnd/src/net/osmand/plus/backup/UserFile.java
Normal file
|
@ -0,0 +1,102 @@
|
|||
package net.osmand.plus.backup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
public class UserFile {
|
||||
|
||||
private int userid;
|
||||
private long id;
|
||||
private int deviceid;
|
||||
private int filesize;
|
||||
private String type;
|
||||
private String name;
|
||||
private Date updatetime;
|
||||
private long updatetimems;
|
||||
private Date clienttime;
|
||||
private long clienttimems;
|
||||
private int zipSize;
|
||||
|
||||
public UserFile(@NonNull JSONObject json) throws JSONException, ParseException {
|
||||
if (json.has("userid")) {
|
||||
userid = json.getInt("userid");
|
||||
}
|
||||
if (json.has("id")) {
|
||||
id = json.getLong("id");
|
||||
}
|
||||
if (json.has("deviceid")) {
|
||||
deviceid = json.getInt("deviceid");
|
||||
}
|
||||
if (json.has("filesize")) {
|
||||
filesize = json.getInt("filesize");
|
||||
}
|
||||
if (json.has("type")) {
|
||||
type = json.getString("type");
|
||||
}
|
||||
if (json.has("name")) {
|
||||
name = json.getString("name");
|
||||
}
|
||||
if (json.has("updatetimems")) {
|
||||
updatetimems = json.getLong("updatetimems");
|
||||
updatetime = new Date(updatetimems);
|
||||
}
|
||||
if (json.has("clienttimems")) {
|
||||
clienttimems = json.getLong("clienttimems");
|
||||
clienttime = new Date(clienttimems);
|
||||
}
|
||||
if (json.has("zipSize")) {
|
||||
zipSize = json.getInt("zipSize");
|
||||
}
|
||||
}
|
||||
|
||||
public int getUserid() {
|
||||
return userid;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public int getDeviceid() {
|
||||
return deviceid;
|
||||
}
|
||||
|
||||
public int getFilesize() {
|
||||
return filesize;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Date getUpdatetime() {
|
||||
return updatetime;
|
||||
}
|
||||
|
||||
public long getUpdatetimems() {
|
||||
return updatetimems;
|
||||
}
|
||||
|
||||
public Date getClienttime() {
|
||||
return clienttime;
|
||||
}
|
||||
|
||||
public long getClienttimems() {
|
||||
return clienttimems;
|
||||
}
|
||||
|
||||
public int getZipSize() {
|
||||
return zipSize;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package net.osmand.plus.backup;
|
||||
|
||||
public class UserNotRegisteredException extends Exception {
|
||||
private static final long serialVersionUID = -8005954380280822845L;
|
||||
|
||||
public UserNotRegisteredException() {
|
||||
super("User is not resistered");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
package net.osmand.plus.base;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
|
||||
import net.osmand.AndroidUtils;
|
||||
import net.osmand.plus.R;
|
||||
import net.osmand.plus.widgets.multistatetoggle.RadioItem;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class ModeSelectionBottomSheet extends SelectionBottomSheet {
|
||||
|
||||
public static final String TAG = ModeSelectionBottomSheet.class.getSimpleName();
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
showElements(primaryDescription, toggleContainer);
|
||||
hideElements(checkBox, checkBoxTitle, titleDescription,
|
||||
secondaryDescription, selectedSize, selectAllButton);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItemView(SelectableItem item, View view) {
|
||||
ImageView ivIcon = view.findViewById(R.id.icon);
|
||||
TextView tvTitle = view.findViewById(R.id.title);
|
||||
TextView tvDescr = view.findViewById(R.id.description);
|
||||
|
||||
Drawable icon = uiUtilities.getIcon(item.getIconId(), activeColorRes);
|
||||
ivIcon.setImageDrawable(icon);
|
||||
tvTitle.setText(item.getTitle());
|
||||
tvDescr.setText(item.getDescription());
|
||||
tvDescr.setTextColor(ContextCompat.getColor(app, AndroidUtils.getSecondaryTextColorId(nightMode)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getItemLayoutId() {
|
||||
return R.layout.bottom_sheet_item_with_descr_56dp;
|
||||
}
|
||||
|
||||
public void setItem(SelectableItem item) {
|
||||
setItems(Collections.singletonList(item));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public List<SelectableItem> getSelectedItems() {
|
||||
return allItems;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldShowDivider() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static ModeSelectionBottomSheet showInstance(@NonNull AppCompatActivity activity,
|
||||
@NonNull SelectableItem previewItem,
|
||||
@NonNull List<RadioItem> radioItems,
|
||||
boolean usedOnMap) {
|
||||
ModeSelectionBottomSheet fragment = new ModeSelectionBottomSheet();
|
||||
fragment.setUsedOnMap(usedOnMap);
|
||||
fragment.setModes(radioItems);
|
||||
fragment.setItems(Collections.singletonList(previewItem));
|
||||
FragmentManager fm = activity.getSupportFragmentManager();
|
||||
fragment.show(fm, TAG);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,180 @@
|
|||
package net.osmand.plus.base;
|
||||
|
||||
import android.content.res.ColorStateList;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.widget.CompoundButtonCompat;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
|
||||
import net.osmand.AndroidUtils;
|
||||
import net.osmand.plus.R;
|
||||
import net.osmand.plus.helpers.AndroidUiHelper;
|
||||
import net.osmand.util.Algorithms;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static net.osmand.view.ThreeStateCheckbox.State.CHECKED;
|
||||
import static net.osmand.view.ThreeStateCheckbox.State.MISC;
|
||||
import static net.osmand.view.ThreeStateCheckbox.State.UNCHECKED;
|
||||
|
||||
public class MultipleSelectionBottomSheet extends SelectionBottomSheet {
|
||||
|
||||
public static final String TAG = MultipleSelectionBottomSheet.class.getSimpleName();
|
||||
|
||||
private final List<SelectableItem> selectedItems = new ArrayList<>();
|
||||
private SelectionUpdateListener selectionUpdateListener;
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
selectAllButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
checkBox.performClick();
|
||||
boolean checked = checkBox.getState() == CHECKED;
|
||||
if (checked) {
|
||||
selectedItems.addAll(allItems);
|
||||
} else {
|
||||
selectedItems.clear();
|
||||
}
|
||||
onSelectedItemsChanged();
|
||||
updateItemsSelection(checked);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldShowDivider() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItemView(final SelectableItem item, View view) {
|
||||
boolean checked = selectedItems.contains(item);
|
||||
ImageView imageView = view.findViewById(R.id.icon);
|
||||
TextView title = view.findViewById(R.id.title);
|
||||
TextView description = view.findViewById(R.id.description);
|
||||
final CheckBox checkBox = view.findViewById(R.id.compound_button);
|
||||
AndroidUiHelper.setVisibility(View.VISIBLE, imageView, title, description, checkBox);
|
||||
|
||||
checkBox.setChecked(checked);
|
||||
CompoundButtonCompat.setButtonTintList(checkBox, AndroidUtils.createCheckedColorStateList(app, secondaryColorRes, activeColorRes));
|
||||
|
||||
view.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
boolean checked = !checkBox.isChecked();
|
||||
checkBox.setChecked(checked);
|
||||
if (checked) {
|
||||
selectedItems.add(item);
|
||||
} else {
|
||||
selectedItems.remove(item);
|
||||
}
|
||||
onSelectedItemsChanged();
|
||||
}
|
||||
});
|
||||
title.setText(item.getTitle());
|
||||
description.setText(item.getDescription());
|
||||
imageView.setImageDrawable(uiUtilities.getIcon(item.getIconId(), activeColorRes));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getItemLayoutId() {
|
||||
return R.layout.bottom_sheet_item_with_descr_and_checkbox_56dp;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void notifyUiCreated() {
|
||||
onSelectedItemsChanged();
|
||||
super.notifyUiCreated();
|
||||
}
|
||||
|
||||
private void onSelectedItemsChanged() {
|
||||
updateSelectAllButton();
|
||||
updateSelectedSizeView();
|
||||
updateApplyButtonEnable();
|
||||
if (selectionUpdateListener != null) {
|
||||
selectionUpdateListener.onSelectionUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSelectAllButton() {
|
||||
String checkBoxTitle;
|
||||
if (Algorithms.isEmpty(selectedItems)) {
|
||||
checkBox.setState(UNCHECKED);
|
||||
checkBoxTitle = getString(R.string.shared_string_select_all);
|
||||
} else {
|
||||
checkBox.setState(selectedItems.containsAll(allItems) ? CHECKED : MISC);
|
||||
checkBoxTitle = getString(R.string.shared_string_deselect_all);
|
||||
}
|
||||
int checkBoxColor = checkBox.getState() == UNCHECKED ? secondaryColorRes : activeColorRes;
|
||||
CompoundButtonCompat.setButtonTintList(checkBox, ColorStateList.valueOf(ContextCompat.getColor(app, checkBoxColor)));
|
||||
this.checkBoxTitle.setText(checkBoxTitle);
|
||||
}
|
||||
|
||||
private void updateSelectedSizeView() {
|
||||
String selected = String.valueOf(selectedItems.size());
|
||||
String all = String.valueOf(allItems.size());
|
||||
selectedSize.setText(getString(R.string.ltr_or_rtl_combine_via_slash, selected, all));
|
||||
}
|
||||
|
||||
private void updateApplyButtonEnable() {
|
||||
boolean noEmptySelection = !Algorithms.isEmpty(selectedItems);
|
||||
rightButton.setEnabled(noEmptySelection);
|
||||
}
|
||||
|
||||
private void updateItemsSelection(boolean checked) {
|
||||
for (SelectableItem item : allItems) {
|
||||
View v = listViews.get(item);
|
||||
CheckBox checkBox = v != null ? (CheckBox) v.findViewById(R.id.compound_button) : null;
|
||||
if (checkBox != null) {
|
||||
checkBox.setChecked(checked);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void setSelectedItems(List<SelectableItem> selected) {
|
||||
selectedItems.clear();
|
||||
if (!Algorithms.isEmpty(selected)) {
|
||||
selectedItems.addAll(selected);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public List<SelectableItem> getSelectedItems() {
|
||||
return selectedItems;
|
||||
}
|
||||
|
||||
public void setSelectionUpdateListener(SelectionUpdateListener selectionUpdateListener) {
|
||||
this.selectionUpdateListener = selectionUpdateListener;
|
||||
}
|
||||
|
||||
public static MultipleSelectionBottomSheet showInstance(@NonNull AppCompatActivity activity,
|
||||
@NonNull List<SelectableItem> items,
|
||||
@Nullable List<SelectableItem> selected,
|
||||
boolean usedOnMap) {
|
||||
MultipleSelectionBottomSheet fragment = new MultipleSelectionBottomSheet();
|
||||
fragment.setUsedOnMap(usedOnMap);
|
||||
fragment.setItems(items);
|
||||
fragment.setSelectedItems(selected);
|
||||
FragmentManager fm = activity.getSupportFragmentManager();
|
||||
fragment.show(fm, TAG);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
public interface SelectionUpdateListener {
|
||||
void onSelectionUpdate();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package net.osmand.plus.base;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
|
||||
import net.osmand.plus.widgets.multistatetoggle.RadioItem;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class MultipleSelectionWithModeBottomSheet extends MultipleSelectionBottomSheet {
|
||||
|
||||
public static final String TAG = MultipleSelectionWithModeBottomSheet.class.getSimpleName();
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
showElements(secondaryDescription, toggleContainer, checkBox,
|
||||
checkBoxTitle, titleDescription, selectedSize, selectAllButton);
|
||||
}
|
||||
|
||||
public static MultipleSelectionWithModeBottomSheet showInstance(@NonNull AppCompatActivity activity,
|
||||
@NonNull List<SelectableItem> items,
|
||||
@Nullable List<SelectableItem> selected,
|
||||
@NonNull List<RadioItem> modes,
|
||||
boolean usedOnMap) {
|
||||
MultipleSelectionWithModeBottomSheet fragment = new MultipleSelectionWithModeBottomSheet();
|
||||
fragment.setUsedOnMap(usedOnMap);
|
||||
fragment.setItems(items);
|
||||
fragment.setSelectedItems(selected);
|
||||
fragment.setModes(modes);
|
||||
FragmentManager fm = activity.getSupportFragmentManager();
|
||||
fragment.show(fm, TAG);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,301 +0,0 @@
|
|||
package net.osmand.plus.base;
|
||||
|
||||
import android.content.res.ColorStateList;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.widget.CompoundButtonCompat;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
|
||||
import net.osmand.AndroidUtils;
|
||||
import net.osmand.plus.OsmandApplication;
|
||||
import net.osmand.plus.R;
|
||||
import net.osmand.plus.UiUtilities;
|
||||
import net.osmand.plus.base.bottomsheetmenu.BaseBottomSheetItem;
|
||||
import net.osmand.plus.base.bottomsheetmenu.BottomSheetItemWithCompoundButton;
|
||||
import net.osmand.plus.base.bottomsheetmenu.BottomSheetItemWithCompoundButton.Builder;
|
||||
import net.osmand.plus.base.bottomsheetmenu.SimpleBottomSheetItem;
|
||||
import net.osmand.plus.base.bottomsheetmenu.simpleitems.SimpleDividerItem;
|
||||
import net.osmand.util.Algorithms;
|
||||
import net.osmand.view.ThreeStateCheckbox;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static net.osmand.view.ThreeStateCheckbox.State.CHECKED;
|
||||
import static net.osmand.view.ThreeStateCheckbox.State.MISC;
|
||||
import static net.osmand.view.ThreeStateCheckbox.State.UNCHECKED;
|
||||
|
||||
public class SelectMultipleItemsBottomSheet extends MenuBottomSheetDialogFragment {
|
||||
|
||||
private OsmandApplication app;
|
||||
private UiUtilities uiUtilities;
|
||||
|
||||
private TextView title;
|
||||
private TextView description;
|
||||
private TextView applyButtonTitle;
|
||||
private TextView checkBoxTitle;
|
||||
private TextView selectedSize;
|
||||
private ThreeStateCheckbox checkBox;
|
||||
|
||||
private int activeColorRes;
|
||||
private int secondaryColorRes;
|
||||
|
||||
private final List<SelectableItem> allItems = new ArrayList<>();
|
||||
private final List<SelectableItem> selectedItems = new ArrayList<>();
|
||||
private SelectionUpdateListener selectionUpdateListener;
|
||||
private OnApplySelectionListener onApplySelectionListener;
|
||||
|
||||
public static final String TAG = SelectMultipleItemsBottomSheet.class.getSimpleName();
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
|
||||
View mainView = super.onCreateView(inflater, parent, savedInstanceState);
|
||||
onSelectedItemsChanged();
|
||||
return mainView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createMenuItems(Bundle savedInstanceState) {
|
||||
app = requiredMyApplication();
|
||||
uiUtilities = app.getUIUtilities();
|
||||
activeColorRes = nightMode ? R.color.icon_color_active_dark : R.color.icon_color_active_light;
|
||||
secondaryColorRes = nightMode ? R.color.icon_color_secondary_dark : R.color.icon_color_secondary_light;
|
||||
|
||||
items.add(createTitleItem());
|
||||
items.add(new SimpleDividerItem(app));
|
||||
createListItems();
|
||||
}
|
||||
|
||||
private BaseBottomSheetItem createTitleItem() {
|
||||
LayoutInflater themedInflater = UiUtilities.getInflater(requireContext(), nightMode);
|
||||
View view = themedInflater.inflate(R.layout.settings_group_title, null);
|
||||
|
||||
checkBox = view.findViewById(R.id.check_box);
|
||||
checkBoxTitle = view.findViewById(R.id.check_box_title);
|
||||
description = view.findViewById(R.id.description);
|
||||
selectedSize = view.findViewById(R.id.selected_size);
|
||||
title = view.findViewById(R.id.title);
|
||||
View selectAllButton = view.findViewById(R.id.select_all_button);
|
||||
selectAllButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
checkBox.performClick();
|
||||
boolean checked = checkBox.getState() == CHECKED;
|
||||
if (checked) {
|
||||
selectedItems.addAll(allItems);
|
||||
} else {
|
||||
selectedItems.clear();
|
||||
}
|
||||
onSelectedItemsChanged();
|
||||
updateItems(checked);
|
||||
}
|
||||
});
|
||||
return new SimpleBottomSheetItem.Builder().setCustomView(view).create();
|
||||
}
|
||||
|
||||
private void createListItems() {
|
||||
for (final SelectableItem item : allItems) {
|
||||
boolean checked = selectedItems.contains(item);
|
||||
final BottomSheetItemWithCompoundButton[] uiItem = new BottomSheetItemWithCompoundButton[1];
|
||||
final Builder builder = (BottomSheetItemWithCompoundButton.Builder) new Builder();
|
||||
builder.setChecked(checked)
|
||||
.setButtonTintList(AndroidUtils.createCheckedColorStateList(app, secondaryColorRes, activeColorRes))
|
||||
.setLayoutId(R.layout.bottom_sheet_item_with_descr_and_checkbox_56dp)
|
||||
.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
boolean checked = !uiItem[0].isChecked();
|
||||
uiItem[0].setChecked(checked);
|
||||
SelectableItem tag = (SelectableItem) uiItem[0].getTag();
|
||||
if (checked) {
|
||||
selectedItems.add(tag);
|
||||
} else {
|
||||
selectedItems.remove(tag);
|
||||
}
|
||||
onSelectedItemsChanged();
|
||||
}
|
||||
})
|
||||
.setTag(item);
|
||||
setupListItem(builder, item);
|
||||
uiItem[0] = builder.create();
|
||||
items.add(uiItem[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setupRightButton() {
|
||||
super.setupRightButton();
|
||||
applyButtonTitle = rightButton.findViewById(R.id.button_text);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRightBottomButtonClick() {
|
||||
if (onApplySelectionListener != null) {
|
||||
onApplySelectionListener.onSelectionApplied(selectedItems);
|
||||
}
|
||||
dismiss();
|
||||
}
|
||||
|
||||
private void onSelectedItemsChanged() {
|
||||
updateSelectAllButton();
|
||||
updateSelectedSizeView();
|
||||
updateApplyButtonEnable();
|
||||
if (selectionUpdateListener != null) {
|
||||
selectionUpdateListener.onSelectionUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getRightBottomButtonTextId() {
|
||||
return R.string.shared_string_apply;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean useVerticalButtons() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setupListItem(Builder builder, SelectableItem item) {
|
||||
builder.setTitle(item.title);
|
||||
builder.setDescription(item.description);
|
||||
builder.setIcon(uiUtilities.getIcon(item.iconId, activeColorRes));
|
||||
}
|
||||
|
||||
private void updateSelectAllButton() {
|
||||
String checkBoxTitle;
|
||||
if (Algorithms.isEmpty(selectedItems)) {
|
||||
checkBox.setState(UNCHECKED);
|
||||
checkBoxTitle = getString(R.string.shared_string_select_all);
|
||||
} else {
|
||||
checkBox.setState(selectedItems.containsAll(allItems) ? CHECKED : MISC);
|
||||
checkBoxTitle = getString(R.string.shared_string_deselect_all);
|
||||
}
|
||||
int checkBoxColor = checkBox.getState() == UNCHECKED ? secondaryColorRes : activeColorRes;
|
||||
CompoundButtonCompat.setButtonTintList(checkBox, ColorStateList.valueOf(ContextCompat.getColor(app, checkBoxColor)));
|
||||
this.checkBoxTitle.setText(checkBoxTitle);
|
||||
}
|
||||
|
||||
private void updateSelectedSizeView() {
|
||||
String selected = String.valueOf(selectedItems.size());
|
||||
String all = String.valueOf(allItems.size());
|
||||
selectedSize.setText(getString(R.string.ltr_or_rtl_combine_via_slash, selected, all));
|
||||
}
|
||||
|
||||
private void updateApplyButtonEnable() {
|
||||
if (Algorithms.isEmpty(selectedItems)) {
|
||||
rightButton.setEnabled(false);
|
||||
} else {
|
||||
rightButton.setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateItems(boolean checked) {
|
||||
for (BaseBottomSheetItem item : items) {
|
||||
if (item instanceof BottomSheetItemWithCompoundButton) {
|
||||
((BottomSheetItemWithCompoundButton) item).setChecked(checked);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setTitle(@NonNull String title) {
|
||||
this.title.setText(title);
|
||||
}
|
||||
|
||||
public void setDescription(@NonNull String description) {
|
||||
this.description.setText(description);
|
||||
}
|
||||
|
||||
public void setConfirmButtonTitle(@NonNull String confirmButtonTitle) {
|
||||
applyButtonTitle.setText(confirmButtonTitle);
|
||||
}
|
||||
|
||||
private void setItems(List<SelectableItem> allItems) {
|
||||
if (!Algorithms.isEmpty(allItems)) {
|
||||
this.allItems.addAll(allItems);
|
||||
}
|
||||
}
|
||||
|
||||
private void setSelectedItems(List<SelectableItem> selected) {
|
||||
if (!Algorithms.isEmpty(selected)) {
|
||||
this.selectedItems.addAll(selected);
|
||||
}
|
||||
}
|
||||
|
||||
public List<SelectableItem> getSelectedItems() {
|
||||
return selectedItems;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
if (requireActivity().isChangingConfigurations()) {
|
||||
dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
public static SelectMultipleItemsBottomSheet showInstance(@NonNull AppCompatActivity activity,
|
||||
@NonNull List<SelectableItem> items,
|
||||
@Nullable List<SelectableItem> selected,
|
||||
boolean usedOnMap) {
|
||||
SelectMultipleItemsBottomSheet fragment = new SelectMultipleItemsBottomSheet();
|
||||
fragment.setUsedOnMap(usedOnMap);
|
||||
fragment.setItems(items);
|
||||
fragment.setSelectedItems(selected);
|
||||
FragmentManager fm = activity.getSupportFragmentManager();
|
||||
fragment.show(fm, TAG);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
public void setSelectionUpdateListener(SelectionUpdateListener selectionUpdateListener) {
|
||||
this.selectionUpdateListener = selectionUpdateListener;
|
||||
}
|
||||
|
||||
public void setOnApplySelectionListener(OnApplySelectionListener onApplySelectionListener) {
|
||||
this.onApplySelectionListener = onApplySelectionListener;
|
||||
}
|
||||
|
||||
public interface SelectionUpdateListener {
|
||||
void onSelectionUpdate();
|
||||
}
|
||||
|
||||
public interface OnApplySelectionListener {
|
||||
void onSelectionApplied(List<SelectableItem> selectedItems);
|
||||
}
|
||||
|
||||
public static class SelectableItem {
|
||||
private String title;
|
||||
private String description;
|
||||
private int iconId;
|
||||
private Object object;
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public void setIconId(int iconId) {
|
||||
this.iconId = iconId;
|
||||
}
|
||||
|
||||
public void setObject(Object object) {
|
||||
this.object = object;
|
||||
}
|
||||
|
||||
public Object getObject() {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
299
OsmAnd/src/net/osmand/plus/base/SelectionBottomSheet.java
Normal file
299
OsmAnd/src/net/osmand/plus/base/SelectionBottomSheet.java
Normal file
|
@ -0,0 +1,299 @@
|
|||
package net.osmand.plus.base;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.LinearLayout.LayoutParams;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import net.osmand.plus.OsmandApplication;
|
||||
import net.osmand.plus.R;
|
||||
import net.osmand.plus.UiUtilities;
|
||||
import net.osmand.plus.base.bottomsheetmenu.BaseBottomSheetItem;
|
||||
import net.osmand.plus.base.bottomsheetmenu.SimpleBottomSheetItem;
|
||||
import net.osmand.plus.base.bottomsheetmenu.simpleitems.SimpleDividerItem;
|
||||
import net.osmand.plus.helpers.AndroidUiHelper;
|
||||
import net.osmand.plus.widgets.multistatetoggle.MultiStateToggleButton;
|
||||
import net.osmand.plus.widgets.multistatetoggle.RadioItem;
|
||||
import net.osmand.plus.widgets.multistatetoggle.TextToggleButton;
|
||||
import net.osmand.util.Algorithms;
|
||||
import net.osmand.view.ThreeStateCheckbox;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class SelectionBottomSheet extends MenuBottomSheetDialogFragment {
|
||||
|
||||
protected OsmandApplication app;
|
||||
protected LayoutInflater inflater;
|
||||
protected UiUtilities uiUtilities;
|
||||
|
||||
protected TextView title;
|
||||
protected TextView titleDescription;
|
||||
protected TextView primaryDescription;
|
||||
protected TextView secondaryDescription;
|
||||
protected TextView selectedSize;
|
||||
protected LinearLayout toggleContainer;
|
||||
protected MultiStateToggleButton radioGroup;
|
||||
protected View selectAllButton;
|
||||
protected TextView checkBoxTitle;
|
||||
protected ThreeStateCheckbox checkBox;
|
||||
protected LinearLayout listContainer;
|
||||
protected TextView applyButtonTitle;
|
||||
|
||||
protected int activeColorRes;
|
||||
protected int secondaryColorRes;
|
||||
|
||||
private DialogStateListener dialogStateListener;
|
||||
private OnApplySelectionListener onApplySelectionListener;
|
||||
|
||||
protected List<SelectableItem> allItems = new ArrayList<>();
|
||||
protected Map<SelectableItem, View> listViews = new HashMap<>();
|
||||
private List<RadioItem> modes;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
|
||||
View mainView = super.onCreateView(inflater, parent, savedInstanceState);
|
||||
createSelectionListIfPossible();
|
||||
notifyUiCreated();
|
||||
return mainView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createMenuItems(Bundle savedInstanceState) {
|
||||
app = requiredMyApplication();
|
||||
uiUtilities = app.getUIUtilities();
|
||||
inflater = UiUtilities.getInflater(requireContext(), nightMode);
|
||||
activeColorRes = nightMode ? R.color.icon_color_active_dark : R.color.icon_color_active_light;
|
||||
secondaryColorRes = nightMode ? R.color.icon_color_secondary_dark : R.color.icon_color_secondary_light;
|
||||
|
||||
items.add(createHeaderView());
|
||||
if (shouldShowDivider()) {
|
||||
items.add(new SimpleDividerItem(app));
|
||||
}
|
||||
items.add(createSelectionView());
|
||||
}
|
||||
|
||||
private BaseBottomSheetItem createHeaderView() {
|
||||
View view = inflater.inflate(R.layout.settings_group_title, null);
|
||||
|
||||
title = view.findViewById(R.id.title);
|
||||
titleDescription = view.findViewById(R.id.title_description);
|
||||
primaryDescription = view.findViewById(R.id.primary_description);
|
||||
secondaryDescription = view.findViewById(R.id.secondary_description);
|
||||
selectedSize = view.findViewById(R.id.selected_size);
|
||||
toggleContainer = view.findViewById(R.id.custom_radio_buttons);
|
||||
radioGroup = new TextToggleButton(app, toggleContainer, nightMode);
|
||||
selectAllButton = view.findViewById(R.id.select_all_button);
|
||||
checkBoxTitle = view.findViewById(R.id.check_box_title);
|
||||
checkBox = view.findViewById(R.id.check_box);
|
||||
|
||||
if (modes != null) {
|
||||
radioGroup.setItems(modes);
|
||||
}
|
||||
|
||||
return new SimpleBottomSheetItem.Builder().setCustomView(view).create();
|
||||
}
|
||||
|
||||
private BaseBottomSheetItem createSelectionView() {
|
||||
Context themedCtx = UiUtilities.getThemedContext(requireContext(), nightMode);
|
||||
listContainer = new LinearLayout(themedCtx);
|
||||
listContainer.setLayoutParams(new LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
listContainer.setOrientation(LinearLayout.VERTICAL);
|
||||
return new SimpleBottomSheetItem.Builder().setCustomView(listContainer).create();
|
||||
}
|
||||
|
||||
public void setTitle(@NonNull String title) {
|
||||
this.title.setText(title);
|
||||
}
|
||||
|
||||
public void setTitleDescription(@NonNull String description) {
|
||||
titleDescription.setText(description);
|
||||
}
|
||||
|
||||
public void setPrimaryDescription(@NonNull String description) {
|
||||
primaryDescription.setText(description);
|
||||
}
|
||||
|
||||
public void setSecondaryDescription(@NonNull String description) {
|
||||
secondaryDescription.setText(description);
|
||||
}
|
||||
|
||||
public void setApplyButtonTitle(@NonNull String title) {
|
||||
applyButtonTitle.setText(title);
|
||||
}
|
||||
|
||||
public void setModes(@NonNull List<RadioItem> modes) {
|
||||
this.modes = modes;
|
||||
if (radioGroup != null) {
|
||||
radioGroup.setItems(modes);
|
||||
}
|
||||
}
|
||||
|
||||
public void setSelectedMode(@NonNull RadioItem mode) {
|
||||
radioGroup.setSelectedItem(mode);
|
||||
}
|
||||
|
||||
public void setItems(List<SelectableItem> allItems) {
|
||||
this.allItems.clear();
|
||||
if (!Algorithms.isEmpty(allItems)) {
|
||||
this.allItems.addAll(allItems);
|
||||
createSelectionListIfPossible();
|
||||
}
|
||||
}
|
||||
|
||||
public void setDialogStateListener(DialogStateListener dialogStateListener) {
|
||||
this.dialogStateListener = dialogStateListener;
|
||||
}
|
||||
|
||||
public void setOnApplySelectionListener(OnApplySelectionListener onApplySelectionListener) {
|
||||
this.onApplySelectionListener = onApplySelectionListener;
|
||||
}
|
||||
|
||||
private void createSelectionListIfPossible() {
|
||||
if (listContainer != null && allItems != null) {
|
||||
recreateList();
|
||||
}
|
||||
}
|
||||
|
||||
private void recreateList() {
|
||||
listViews.clear();
|
||||
listContainer.removeAllViews();
|
||||
for (SelectableItem item : allItems) {
|
||||
setupItemView(item, inflater.inflate(getItemLayoutId(), null));
|
||||
}
|
||||
}
|
||||
|
||||
private void setupItemView(SelectableItem item, View view) {
|
||||
updateItemView(item, view);
|
||||
listViews.put(item, view);
|
||||
listContainer.addView(view);
|
||||
}
|
||||
|
||||
public List<SelectableItem> getAllItems() {
|
||||
return allItems;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public abstract List<SelectableItem> getSelectedItems();
|
||||
|
||||
protected abstract void updateItemView(SelectableItem item, View view);
|
||||
|
||||
protected abstract int getItemLayoutId();
|
||||
|
||||
protected abstract boolean shouldShowDivider();
|
||||
|
||||
protected void notifyUiCreated() {
|
||||
if (dialogStateListener != null) {
|
||||
dialogStateListener.onDialogCreated();
|
||||
}
|
||||
}
|
||||
|
||||
protected void showElements(View... views) {
|
||||
AndroidUiHelper.setVisibility(View.VISIBLE, views);
|
||||
}
|
||||
|
||||
protected void hideElements(View... views) {
|
||||
AndroidUiHelper.setVisibility(View.GONE, views);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setupRightButton() {
|
||||
super.setupRightButton();
|
||||
applyButtonTitle = rightButton.findViewById(R.id.button_text);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRightBottomButtonClick() {
|
||||
if (onApplySelectionListener != null) {
|
||||
onApplySelectionListener.onSelectionApplied(getSelectedItems());
|
||||
}
|
||||
dismiss();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getRightBottomButtonTextId() {
|
||||
return R.string.shared_string_apply;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean useVerticalButtons() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
if (requireActivity().isChangingConfigurations()) {
|
||||
dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
if (dialogStateListener != null) {
|
||||
dialogStateListener.onCloseDialog();
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
public interface DialogStateListener {
|
||||
void onDialogCreated();
|
||||
void onCloseDialog();
|
||||
}
|
||||
|
||||
public interface OnApplySelectionListener {
|
||||
void onSelectionApplied(List<SelectableItem> selectedItems);
|
||||
}
|
||||
|
||||
public static class SelectableItem {
|
||||
private String title;
|
||||
private String description;
|
||||
private int iconId;
|
||||
private Object object;
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public int getIconId() {
|
||||
return iconId;
|
||||
}
|
||||
|
||||
public Object getObject() {
|
||||
return object;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public void setIconId(int iconId) {
|
||||
this.iconId = iconId;
|
||||
}
|
||||
|
||||
public void setObject(Object object) {
|
||||
this.object = object;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -32,6 +32,10 @@ public class BaseBottomSheetItem {
|
|||
return tag;
|
||||
}
|
||||
|
||||
public void setTag(Object tag) {
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
public BaseBottomSheetItem(View view,
|
||||
@LayoutRes int layoutId,
|
||||
Object tag,
|
||||
|
|
|
@ -11,7 +11,6 @@ import android.content.DialogInterface;
|
|||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.StatFs;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
|
@ -34,10 +33,10 @@ import net.osmand.FileUtils;
|
|||
import net.osmand.PlatformUtil;
|
||||
import net.osmand.ValueHolder;
|
||||
import net.osmand.plus.OsmandApplication;
|
||||
import net.osmand.plus.settings.backend.OsmandSettings;
|
||||
import net.osmand.plus.ProgressImplementation;
|
||||
import net.osmand.plus.R;
|
||||
import net.osmand.plus.download.DownloadActivity;
|
||||
import net.osmand.plus.settings.backend.OsmandSettings;
|
||||
import net.osmand.util.Algorithms;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
|
@ -93,18 +92,6 @@ public class DashChooseAppDirFragment {
|
|||
selectePathTemp = null;
|
||||
}
|
||||
|
||||
private String getFreeSpace(File dir) {
|
||||
if (dir.canRead()) {
|
||||
try {
|
||||
StatFs fs = new StatFs(dir.getAbsolutePath());
|
||||
return AndroidUtils.formatSize(activity, (long) fs.getAvailableBlocks() * fs.getBlockSize());
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.error(e);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public void updateView() {
|
||||
if (type == OsmandSettings.EXTERNAL_STORAGE_TYPE_INTERNAL_FILE) {
|
||||
locationPath.setText(R.string.storage_directory_internal_app);
|
||||
|
@ -117,7 +104,7 @@ public class DashChooseAppDirFragment {
|
|||
} else if (type == OsmandSettings.EXTERNAL_STORAGE_TYPE_SPECIFIED) {
|
||||
locationPath.setText(R.string.storage_directory_manual);
|
||||
}
|
||||
locationDesc.setText(selectedFile.getAbsolutePath() + " \u2022 " + getFreeSpace(selectedFile));
|
||||
locationDesc.setText(selectedFile.getAbsolutePath() + " \u2022 " + AndroidUtils.getFreeSpace(activity, selectedFile));
|
||||
boolean copyFiles = !currentAppFile.getAbsolutePath().equals(selectedFile.getAbsolutePath()) && !mapsCopied;
|
||||
warningReadonly.setVisibility(copyFiles ? View.VISIBLE : View.GONE);
|
||||
if (copyFiles) {
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
package net.osmand.plus.development;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Patterns;
|
||||
import android.util.Pair;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
|
@ -17,56 +14,58 @@ import androidx.annotation.NonNull;
|
|||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import net.osmand.AndroidNetworkUtils;
|
||||
import net.osmand.AndroidNetworkUtils.OnFilesUploadCallback;
|
||||
import net.osmand.AndroidNetworkUtils.OnRequestResultListener;
|
||||
import net.osmand.AndroidUtils;
|
||||
import net.osmand.IndexConstants;
|
||||
import net.osmand.plus.OsmandApplication;
|
||||
import net.osmand.plus.ProgressImplementation;
|
||||
import net.osmand.plus.R;
|
||||
import net.osmand.plus.UiUtilities;
|
||||
import net.osmand.plus.UiUtilities.DialogButtonType;
|
||||
import net.osmand.plus.activities.OsmandActionBarActivity;
|
||||
import net.osmand.plus.backup.BackupHelper;
|
||||
import net.osmand.plus.backup.BackupHelper.BackupInfo;
|
||||
import net.osmand.plus.backup.BackupHelper.OnRegisterUserListener;
|
||||
import net.osmand.plus.backup.BackupTask;
|
||||
import net.osmand.plus.backup.BackupTask.OnBackupListener;
|
||||
import net.osmand.plus.backup.GpxFileInfo;
|
||||
import net.osmand.plus.backup.PrepareBackupTask;
|
||||
import net.osmand.plus.backup.PrepareBackupTask.OnPrepareBackupListener;
|
||||
import net.osmand.plus.backup.UserFile;
|
||||
import net.osmand.plus.settings.backend.OsmandSettings;
|
||||
import net.osmand.plus.widgets.OsmandTextFieldBoxes;
|
||||
import net.osmand.util.Algorithms;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.text.DateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
public class TestBackupActivity extends OsmandActionBarActivity {
|
||||
|
||||
// TODO pass actual sub order id!
|
||||
private static final String TEST_ORDER_ID = "";
|
||||
private static final DateFormat DF = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM);
|
||||
|
||||
private OsmandApplication app;
|
||||
private OsmandSettings settings;
|
||||
private BackupHelper backupHelper;
|
||||
|
||||
private ProgressBar progressBar;
|
||||
private View buttonRegister;
|
||||
private View buttonVerify;
|
||||
private View buttonRefresh;
|
||||
private View buttonBackup;
|
||||
private View buttonRestore;
|
||||
private EditText emailEditText;
|
||||
private OsmandTextFieldBoxes tokenEdit;
|
||||
private EditText tokenEditText;
|
||||
private TextView infoView;
|
||||
|
||||
public interface OnResultListener {
|
||||
void onResult(boolean success, @Nullable String result);
|
||||
}
|
||||
private BackupInfo backupInfo;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
app = getMyApplication();
|
||||
settings = app.getSettings();
|
||||
backupHelper = app.getBackupHelper();
|
||||
final WeakReference<TestBackupActivity> activityRef = new WeakReference<>(this);
|
||||
|
||||
boolean nightMode = !app.getSettings().isLightContent();
|
||||
|
@ -94,11 +93,14 @@ public class TestBackupActivity extends OsmandActionBarActivity {
|
|||
UiUtilities.setupDialogButton(nightMode, buttonRegister, DialogButtonType.PRIMARY, "Register");
|
||||
buttonVerify = findViewById(R.id.btn_verify);
|
||||
UiUtilities.setupDialogButton(nightMode, buttonVerify, DialogButtonType.PRIMARY, "Verify");
|
||||
buttonRefresh = findViewById(R.id.btn_refresh);
|
||||
UiUtilities.setupDialogButton(nightMode, buttonRefresh, DialogButtonType.PRIMARY, "Refresh");
|
||||
buttonBackup = findViewById(R.id.btn_backup);
|
||||
UiUtilities.setupDialogButton(nightMode, buttonBackup, DialogButtonType.PRIMARY, "Backup");
|
||||
buttonRestore = findViewById(R.id.btn_restore);
|
||||
UiUtilities.setupDialogButton(nightMode, buttonRestore, DialogButtonType.PRIMARY, "Restore");
|
||||
|
||||
tokenEdit = findViewById(R.id.edit_token_label);
|
||||
tokenEditText = findViewById(R.id.edit_token);
|
||||
infoView = findViewById(R.id.text_info);
|
||||
progressBar = findViewById(R.id.progress_bar);
|
||||
|
@ -109,23 +111,33 @@ public class TestBackupActivity extends OsmandActionBarActivity {
|
|||
if (!Algorithms.isEmpty(email)) {
|
||||
emailEditText.setText(email);
|
||||
}
|
||||
if (backupHelper.isRegistered()) {
|
||||
tokenEdit.setVisibility(View.GONE);
|
||||
buttonVerify.setVisibility(View.GONE);
|
||||
} else {
|
||||
tokenEdit.setVisibility(View.VISIBLE);
|
||||
buttonVerify.setVisibility(View.VISIBLE);
|
||||
}
|
||||
buttonRegister.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
String email = emailEditText.getText().toString();
|
||||
if (isEmailValid(email)) {
|
||||
if (AndroidUtils.isValidEmail(email)) {
|
||||
buttonRegister.setEnabled(false);
|
||||
settings.BACKUP_USER_EMAIL.set(email);
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
registerUser(email, new OnResultListener() {
|
||||
backupHelper.registerUser(email, new OnRegisterUserListener() {
|
||||
@Override
|
||||
public void onResult(boolean success, @Nullable String result) {
|
||||
public void onRegisterUser(int status, @Nullable String message) {
|
||||
TestBackupActivity a = activityRef.get();
|
||||
if (AndroidUtils.isActivityNotDestroyed(a)) {
|
||||
a.progressBar.setVisibility(View.GONE);
|
||||
a.buttonRegister.setEnabled(!success);
|
||||
a.buttonVerify.setEnabled(success);
|
||||
a.buttonRegister.setEnabled(status != BackupHelper.STATUS_SUCCESS);
|
||||
a.tokenEdit.setVisibility(View.VISIBLE);
|
||||
a.buttonVerify.setVisibility(View.VISIBLE);
|
||||
a.buttonVerify.setEnabled(status == BackupHelper.STATUS_SUCCESS);
|
||||
a.tokenEditText.requestFocus();
|
||||
a.infoView.setText(message);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -139,17 +151,23 @@ public class TestBackupActivity extends OsmandActionBarActivity {
|
|||
@Override
|
||||
public void onClick(View v) {
|
||||
String token = tokenEditText.getText().toString();
|
||||
if (isTokenValid(token)) {
|
||||
if (BackupHelper.isTokenValid(token)) {
|
||||
buttonVerify.setEnabled(false);
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
registerDevice(token, new OnResultListener() {
|
||||
backupHelper.registerDevice(token, new BackupHelper.OnRegisterDeviceListener() {
|
||||
|
||||
@Override
|
||||
public void onResult(boolean success, @Nullable String result) {
|
||||
public void onRegisterDevice(int status, @Nullable String message) {
|
||||
TestBackupActivity a = activityRef.get();
|
||||
if (AndroidUtils.isActivityNotDestroyed(a)) {
|
||||
a.progressBar.setVisibility(View.GONE);
|
||||
a.buttonVerify.setEnabled(!success);
|
||||
a.loadBackupInfo();
|
||||
a.buttonVerify.setEnabled(status != BackupHelper.STATUS_SUCCESS);
|
||||
if (status == BackupHelper.STATUS_SUCCESS) {
|
||||
a.tokenEdit.setVisibility(View.GONE);
|
||||
a.buttonVerify.setVisibility(View.GONE);
|
||||
a.prepareBackup();
|
||||
}
|
||||
a.infoView.setText(message);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -160,262 +178,177 @@ public class TestBackupActivity extends OsmandActionBarActivity {
|
|||
}
|
||||
}
|
||||
});
|
||||
buttonRefresh.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
prepareBackup();
|
||||
}
|
||||
});
|
||||
buttonBackup.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
uploadFiles();
|
||||
if (backupInfo != null) {
|
||||
buttonBackup.setEnabled(false);
|
||||
BackupTask task = new BackupTask(backupInfo, TestBackupActivity.this, new OnBackupListener() {
|
||||
@Override
|
||||
public void onBackupDone(@Nullable Map<File, String> uploadErrors, @Nullable Map<File, String> downloadErrors,
|
||||
@Nullable Map<UserFile, String> deleteErrors, @Nullable String error) {
|
||||
TestBackupActivity a = activityRef.get();
|
||||
if (AndroidUtils.isActivityNotDestroyed(a)) {
|
||||
String description;
|
||||
if (error != null) {
|
||||
description = error;
|
||||
} else if (uploadErrors == null && deleteErrors == null) {
|
||||
description = "No data";
|
||||
} else {
|
||||
description = getBackupErrorsDescription(uploadErrors, downloadErrors, deleteErrors, error);
|
||||
}
|
||||
a.infoView.setText(description);
|
||||
a.infoView.requestFocus();
|
||||
a.buttonBackup.setEnabled(true);
|
||||
if (Algorithms.isEmpty(description)) {
|
||||
a.prepareBackup();
|
||||
} else {
|
||||
a.backupInfo = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
task.runBackup();
|
||||
}
|
||||
}
|
||||
});
|
||||
buttonRestore.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
}
|
||||
});
|
||||
|
||||
loadBackupInfo();
|
||||
}
|
||||
|
||||
private void loadBackupInfo() {
|
||||
if (!Algorithms.isEmpty(getDeviceId()) && !Algorithms.isEmpty(getAccessToken())) {
|
||||
final WeakReference<TestBackupActivity> activityRef = new WeakReference<>(this);
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
loadBackupInfo(new OnResultListener() {
|
||||
@Override
|
||||
public void onResult(boolean success, @Nullable String result) {
|
||||
TestBackupActivity a = activityRef.get();
|
||||
if (AndroidUtils.isActivityNotDestroyed(a)) {
|
||||
a.progressBar.setVisibility(View.GONE);
|
||||
a.infoView.setText(result);
|
||||
a.infoView.requestFocus();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isEmailValid(CharSequence target) {
|
||||
return (!TextUtils.isEmpty(target) && Patterns.EMAIL_ADDRESS.matcher(target).matches());
|
||||
}
|
||||
|
||||
private String getOrderId() {
|
||||
return TEST_ORDER_ID;
|
||||
}
|
||||
|
||||
private String getDeviceId() {
|
||||
return settings.BACKUP_DEVICE_ID.get();
|
||||
}
|
||||
|
||||
private String getAccessToken() {
|
||||
return settings.BACKUP_ACCESS_TOKEN.get();
|
||||
}
|
||||
|
||||
private void registerUser(@NonNull String email, @Nullable final OnResultListener listener) {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("email", email);
|
||||
params.put("orderid", getOrderId());
|
||||
params.put("deviceid", app.getUserAndroidId());
|
||||
AndroidNetworkUtils.sendRequestAsync(app, "https://osmand.net/userdata/user-register", params, "Register user", true, true, new OnRequestResultListener() {
|
||||
@Override
|
||||
public void onResult(String resultJson) {
|
||||
boolean success = false;
|
||||
if (!Algorithms.isEmpty(resultJson)) {
|
||||
try {
|
||||
// {"status":"ok"}
|
||||
JSONObject result = new JSONObject(resultJson);
|
||||
String status = result.getString("status");
|
||||
success = status.equals("ok");
|
||||
app.showToastMessage(success
|
||||
? "You have been registered successfully. Please check for email with activation code."
|
||||
: "User registration error: " + status);
|
||||
} catch (JSONException e) {
|
||||
app.showToastMessage("User registration error: json parsing");
|
||||
}
|
||||
} else {
|
||||
app.showToastMessage("User registration error: empty response");
|
||||
}
|
||||
if (listener != null) {
|
||||
listener.onResult(success, resultJson);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void registerDevice(String token, @Nullable final OnResultListener listener) {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("email", settings.BACKUP_USER_EMAIL.get());
|
||||
params.put("orderid", getOrderId());
|
||||
params.put("deviceid", app.getUserAndroidId());
|
||||
params.put("token", token);
|
||||
AndroidNetworkUtils.sendRequestAsync(app, "https://osmand.net/userdata/device-register", params, "Register device", true, true, new OnRequestResultListener() {
|
||||
@Override
|
||||
public void onResult(String resultJson) {
|
||||
boolean success = false;
|
||||
if (!Algorithms.isEmpty(resultJson)) {
|
||||
try {
|
||||
/*
|
||||
{
|
||||
"id": 1034,
|
||||
"userid": 1033,
|
||||
"deviceid": "2fa8080d2985a777",
|
||||
"orderid": "460000687003939",
|
||||
"accesstoken": "4bc0a61f-397a-4c3e-9ffc-db382ec00372",
|
||||
"udpatetime": "Apr 11, 2021, 11:32:20 AM"
|
||||
}
|
||||
*/
|
||||
JSONObject result = new JSONObject(resultJson);
|
||||
settings.BACKUP_DEVICE_ID.set(result.getString("id"));
|
||||
settings.BACKUP_USER_ID.set(result.getString("userid"));
|
||||
settings.BACKUP_NATIVE_DEVICE_ID.set(result.getString("deviceid"));
|
||||
settings.BACKUP_ACCESS_TOKEN.set(result.getString("accesstoken"));
|
||||
settings.BACKUP_ACCESS_TOKEN_UPDATE_TIME.set(result.getString("udpatetime"));
|
||||
success = true;
|
||||
app.showToastMessage("Device have been registered successfully");
|
||||
} catch (JSONException e) {
|
||||
app.showToastMessage("Device registration error: json parsing");
|
||||
}
|
||||
} else {
|
||||
app.showToastMessage("Device registration error: empty response");
|
||||
}
|
||||
if (listener != null) {
|
||||
listener.onResult(success, resultJson);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void uploadFiles() {
|
||||
LoadGpxTask loadGpxTask = new LoadGpxTask(this, new LoadGpxTask.OnLoadGpxListener() {
|
||||
@Override
|
||||
public void onLoadGpxDone(@NonNull List<GpxInfo> result) {
|
||||
uploadFiles(result);
|
||||
}
|
||||
});
|
||||
loadGpxTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, this);
|
||||
}
|
||||
|
||||
private void uploadFiles(List<GpxInfo> gpxFiles) {
|
||||
//{"status":"ok"}
|
||||
final WeakReference<TestBackupActivity> activityRef = new WeakReference<>(this);
|
||||
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("deviceid", getDeviceId());
|
||||
params.put("accessToken", getAccessToken());
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("Accept-Encoding", "deflate, gzip");
|
||||
|
||||
final Map<File, GpxInfo> gpxInfos = new HashMap<>();
|
||||
for (GpxInfo gpxFile : gpxFiles) {
|
||||
gpxInfos.put(gpxFile.file, gpxFile);
|
||||
}
|
||||
final List<File> files = new ArrayList<>(gpxInfos.keySet());
|
||||
File favoritesFile = app.getFavorites().getExternalFile();
|
||||
files.add(favoritesFile);
|
||||
|
||||
final ProgressImplementation progress = ProgressImplementation.createProgressDialog(this,
|
||||
"Create backup", "Uploading " + files.size() + " file(s) to server", ProgressDialog.STYLE_HORIZONTAL);
|
||||
|
||||
AndroidNetworkUtils.uploadFilesAsync("https://osmand.net/userdata/upload-file", files, true, params, headers, new OnFilesUploadCallback() {
|
||||
@Nullable
|
||||
@Override
|
||||
public Map<String, String> getAdditionalParams(@NonNull File file) {
|
||||
GpxInfo gpxInfo = gpxInfos.get(file);
|
||||
Map<String, String> additionaParams = new HashMap<>();
|
||||
additionaParams.put("name", gpxInfo == null ? file.getName() : gpxInfo.getFileName(true));
|
||||
additionaParams.put("type", Algorithms.getFileExtension(file));
|
||||
return additionaParams;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFileUploadProgress(@NonNull File file, int percent) {
|
||||
Activity a = activityRef.get();
|
||||
if (AndroidUtils.isActivityNotDestroyed(a)) {
|
||||
if (percent < 100) {
|
||||
progress.startTask(file.getName(), percent);
|
||||
} else {
|
||||
progress.finishTask();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFilesUploadDone(@NonNull Map<File, String> errors) {
|
||||
Activity a = activityRef.get();
|
||||
if (AndroidUtils.isActivityNotDestroyed(a)) {
|
||||
app.runInUIThread(new Runnable() {
|
||||
if (backupInfo != null) {
|
||||
buttonRestore.setEnabled(false);
|
||||
BackupTask task = new BackupTask(backupInfo, TestBackupActivity.this, new OnBackupListener() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if (progress.getDialog().isShowing()) {
|
||||
progress.getDialog().dismiss();
|
||||
public void onBackupDone(@Nullable Map<File, String> uploadErrors, @Nullable Map<File, String> downloadErrors,
|
||||
@Nullable Map<UserFile, String> deleteErrors, @Nullable String error) {
|
||||
TestBackupActivity a = activityRef.get();
|
||||
if (AndroidUtils.isActivityNotDestroyed(a)) {
|
||||
String description;
|
||||
if (error != null) {
|
||||
description = error;
|
||||
} else if (uploadErrors == null && downloadErrors == null) {
|
||||
description = "No data";
|
||||
} else {
|
||||
description = getBackupErrorsDescription(uploadErrors, downloadErrors, deleteErrors, error);
|
||||
}
|
||||
a.infoView.setText(description);
|
||||
a.infoView.requestFocus();
|
||||
a.buttonRestore.setEnabled(true);
|
||||
if (Algorithms.isEmpty(description)) {
|
||||
a.prepareBackup();
|
||||
} else {
|
||||
a.backupInfo = null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
//ignored
|
||||
}
|
||||
}
|
||||
}, 300);
|
||||
app.showToastMessage("Uploaded " + (files.size() - errors.size() + " files" +
|
||||
(errors.size() > 0 ? ". Errors: " + errors.size() : "")));
|
||||
loadBackupInfo();
|
||||
});
|
||||
task.runRestore();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
prepareBackup();
|
||||
}
|
||||
|
||||
private void loadBackupInfo(@Nullable final OnResultListener listener) {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("deviceid", getDeviceId());
|
||||
params.put("accessToken", getAccessToken());
|
||||
AndroidNetworkUtils.sendRequestAsync(app, "https://osmand.net/userdata/list-files", params, "Get backup info", true, false, new OnRequestResultListener() {
|
||||
private String getBackupErrorsDescription(@Nullable Map<File, String> uploadErrors, @Nullable Map<File, String> downloadErrors, @Nullable Map<UserFile, String> deleteErrors, @Nullable String error) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (!Algorithms.isEmpty(uploadErrors)) {
|
||||
sb.append("--- Upload errors ---").append("\n\n");
|
||||
for (Entry<File, String> uploadEntry : uploadErrors.entrySet()) {
|
||||
sb.append(uploadEntry.getKey().getName()).append(": ").append(uploadEntry.getValue()).append("\n\n");
|
||||
}
|
||||
}
|
||||
if (!Algorithms.isEmpty(downloadErrors)) {
|
||||
sb.append("--- Download errors ---").append("\n\n");
|
||||
for (Entry<File, String> downloadEntry : downloadErrors.entrySet()) {
|
||||
sb.append(downloadEntry.getKey().getName()).append(": ").append(downloadEntry.getValue()).append("\n\n");
|
||||
}
|
||||
}
|
||||
if (!Algorithms.isEmpty(deleteErrors)) {
|
||||
sb.append("--- Delete errors ---").append("\n\n");
|
||||
for (Entry<UserFile, String> deleteEntry : deleteErrors.entrySet()) {
|
||||
sb.append(deleteEntry.getKey().getName()).append(": ").append(deleteEntry.getValue()).append("\n\n");
|
||||
}
|
||||
}
|
||||
return sb.length() == 0 ? "OK" : sb.toString();
|
||||
}
|
||||
|
||||
private String getBackupDescription(@NonNull BackupInfo backupInfo) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (!Algorithms.isEmpty(backupInfo.filesToUpload)) {
|
||||
sb.append("\n").append("--- Upload ---").append("\n\n");
|
||||
for (GpxFileInfo info : backupInfo.filesToUpload) {
|
||||
sb.append(info.getFileName(true))
|
||||
.append(" L: ").append(DF.format(new Date(info.getFileDate())))
|
||||
.append(" U: ").append(DF.format(new Date(info.uploadTime)))
|
||||
.append("\n\n");
|
||||
}
|
||||
}
|
||||
if (!Algorithms.isEmpty(backupInfo.filesToDownload)) {
|
||||
sb.append("\n").append("--- Download ---").append("\n\n");
|
||||
for (UserFile userFile : backupInfo.filesToDownload) {
|
||||
sb.append(userFile.getName())
|
||||
.append(" R: ").append(DF.format(new Date(userFile.getClienttimems())))
|
||||
.append("\n\n");
|
||||
}
|
||||
}
|
||||
if (!Algorithms.isEmpty(backupInfo.filesToDelete)) {
|
||||
sb.append("\n").append("--- Delete ---").append("\n\n");
|
||||
for (UserFile userFile : backupInfo.filesToDelete) {
|
||||
sb.append(userFile.getName())
|
||||
.append(" R: ").append(DF.format(new Date(userFile.getClienttimems())))
|
||||
.append("\n\n");
|
||||
}
|
||||
}
|
||||
if (!Algorithms.isEmpty(backupInfo.filesToMerge)) {
|
||||
sb.append("\n").append("--- Conflicts ---").append("\n\n");
|
||||
for (Pair<GpxFileInfo, UserFile> localRemote : backupInfo.filesToMerge) {
|
||||
GpxFileInfo local = localRemote.first;
|
||||
UserFile remote = localRemote.second;
|
||||
sb.append(local.getFileName(true))
|
||||
.append(" L: ").append(DF.format(new Date(local.getFileDate())))
|
||||
.append(" U: ").append(DF.format(new Date(local.uploadTime)))
|
||||
.append(" R: ").append(DF.format(new Date(remote.getClienttimems())))
|
||||
.append("\n\n");
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void prepareBackup() {
|
||||
final WeakReference<TestBackupActivity> activityRef = new WeakReference<>(this);
|
||||
buttonRefresh.setEnabled(false);
|
||||
PrepareBackupTask prepareBackupTask = new PrepareBackupTask(this, new OnPrepareBackupListener() {
|
||||
@Override
|
||||
public void onResult(String resultJson) {
|
||||
boolean success = false;
|
||||
StringBuilder resultString = new StringBuilder();
|
||||
if (!Algorithms.isEmpty(resultJson)) {
|
||||
try {
|
||||
/*
|
||||
{
|
||||
"totalZipSize": 21792,
|
||||
"totalFileSize": 185920,
|
||||
"totalFiles": 1,
|
||||
"totalFileVersions": 2,
|
||||
"uniqueFiles": [
|
||||
{
|
||||
"userid": 1033,
|
||||
"id": 7,
|
||||
"deviceid": 1034,
|
||||
"filesize": 92960,
|
||||
"type": "gpx",
|
||||
"name": "test/Day 2.gpx",
|
||||
"updatetime": "Apr 11, 2021, 1:49:01 PM",
|
||||
"updatetimems": 1618141741822,
|
||||
"zipSize": 10896
|
||||
}
|
||||
],
|
||||
"deviceid": 1034
|
||||
}
|
||||
*/
|
||||
JSONObject result = new JSONObject(resultJson);
|
||||
String totalZipSize = result.getString("totalZipSize");
|
||||
String totalFiles = result.getString("totalFiles");
|
||||
String totalFileVersions = result.getString("totalFileVersions");
|
||||
JSONArray files = result.getJSONArray("uniqueFiles");
|
||||
resultString.append("Total files: ").append(totalFiles).append("\n");
|
||||
resultString.append("Total zip size: ").append(AndroidUtils.formatSize(app, Long.parseLong(totalZipSize))).append("\n");
|
||||
resultString.append("Total file versions: ").append(totalFileVersions);
|
||||
|
||||
success = true;
|
||||
} catch (JSONException e) {
|
||||
public void onBackupPrepared(@Nullable BackupInfo backupInfo, @Nullable String error) {
|
||||
TestBackupActivity.this.backupInfo = backupInfo;
|
||||
TestBackupActivity a = activityRef.get();
|
||||
if (AndroidUtils.isActivityNotDestroyed(a)) {
|
||||
String description = "Last uploaded: " + DF.format(new Date(settings.BACKUP_LAST_UPLOADED_TIME.get())) + "\n\n";
|
||||
if (error != null) {
|
||||
description += error;
|
||||
} else if (backupInfo == null) {
|
||||
description += "No data";
|
||||
} else {
|
||||
description += "Files to upload: " + backupInfo.filesToUpload.size()
|
||||
+ "\nFiles to download: " + backupInfo.filesToDownload.size()
|
||||
+ "\nFiles to delete: " + backupInfo.filesToDelete.size()
|
||||
+ "\nConflicts: " + backupInfo.filesToMerge.size()
|
||||
+ "\n" + getBackupDescription(backupInfo);
|
||||
}
|
||||
}
|
||||
if (listener != null) {
|
||||
listener.onResult(success, resultString.toString());
|
||||
a.infoView.setText(description);
|
||||
a.infoView.requestFocus();
|
||||
a.buttonRefresh.setEnabled(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isTokenValid(String token) {
|
||||
return token.matches("[0-9]+");
|
||||
prepareBackupTask.prepare();
|
||||
}
|
||||
|
||||
private int resolveResourceId(final Activity activity, final int attr) {
|
||||
|
@ -423,173 +356,4 @@ public class TestBackupActivity extends OsmandActionBarActivity {
|
|||
activity.getTheme().resolveAttribute(attr, typedvalueattr, true);
|
||||
return typedvalueattr.resourceId;
|
||||
}
|
||||
|
||||
private static class LoadGpxTask extends AsyncTask<Activity, GpxInfo, List<GpxInfo>> {
|
||||
|
||||
private final OsmandApplication app;
|
||||
private final OnLoadGpxListener listener;
|
||||
private final WeakReference<Activity> activityRef;
|
||||
private List<GpxInfo> result;
|
||||
private ProgressImplementation progress;
|
||||
|
||||
interface OnLoadGpxListener {
|
||||
void onLoadGpxDone(@NonNull List<GpxInfo> result);
|
||||
}
|
||||
|
||||
LoadGpxTask(@NonNull Activity activity, @Nullable OnLoadGpxListener listener) {
|
||||
this.activityRef = new WeakReference<>(activity);
|
||||
this.app = (OsmandApplication) activity.getApplication();
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public List<GpxInfo> getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected List<GpxInfo> doInBackground(Activity... params) {
|
||||
List<GpxInfo> result = new ArrayList<>();
|
||||
loadGPXData(app.getAppPath(IndexConstants.GPX_INDEX_DIR), result, this);
|
||||
return result;
|
||||
}
|
||||
|
||||
public void loadFile(GpxInfo... loaded) {
|
||||
publishProgress(loaded);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
Activity a = activityRef.get();
|
||||
if (AndroidUtils.isActivityNotDestroyed(a)) {
|
||||
progress = ProgressImplementation.createProgressDialog(a,
|
||||
"Create backup", "Collecting gpx files...", ProgressDialog.STYLE_HORIZONTAL);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(GpxInfo... values) {
|
||||
Activity a = activityRef.get();
|
||||
if (AndroidUtils.isActivityNotDestroyed(a)) {
|
||||
progress.startTask(values[0].getFileName(true), -1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(@NonNull List<GpxInfo> result) {
|
||||
this.result = result;
|
||||
if (listener != null) {
|
||||
listener.onLoadGpxDone(result);
|
||||
}
|
||||
Activity a = activityRef.get();
|
||||
if (AndroidUtils.isActivityNotDestroyed(a)) {
|
||||
progress.finishTask();
|
||||
app.runInUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if (progress.getDialog().isShowing()) {
|
||||
progress.getDialog().dismiss();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
//ignored
|
||||
}
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadGPXData(File mapPath, List<GpxInfo> result, LoadGpxTask loadTask) {
|
||||
if (mapPath.canRead()) {
|
||||
List<GpxInfo> progress = new ArrayList<>();
|
||||
loadGPXFolder(mapPath, result, loadTask, progress, "");
|
||||
if (!progress.isEmpty()) {
|
||||
loadTask.loadFile(progress.toArray(new GpxInfo[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadGPXFolder(File mapPath, List<GpxInfo> result, LoadGpxTask loadTask, List<GpxInfo> progress,
|
||||
String gpxSubfolder) {
|
||||
File[] listFiles = mapPath.listFiles();
|
||||
if (listFiles != null) {
|
||||
for (File gpxFile : listFiles) {
|
||||
if (gpxFile.isDirectory()) {
|
||||
String sub = gpxSubfolder.length() == 0 ? gpxFile.getName() : gpxSubfolder + "/"
|
||||
+ gpxFile.getName();
|
||||
loadGPXFolder(gpxFile, result, loadTask, progress, sub);
|
||||
} else if (gpxFile.isFile() && gpxFile.getName().toLowerCase().endsWith(IndexConstants.GPX_FILE_EXT)) {
|
||||
GpxInfo info = new GpxInfo();
|
||||
info.subfolder = gpxSubfolder;
|
||||
info.file = gpxFile;
|
||||
result.add(info);
|
||||
progress.add(info);
|
||||
if (progress.size() > 7) {
|
||||
loadTask.loadFile(progress.toArray(new GpxInfo[0]));
|
||||
progress.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class GpxInfo {
|
||||
public File file;
|
||||
public String subfolder;
|
||||
|
||||
private String name = null;
|
||||
private int sz = -1;
|
||||
private String fileName = null;
|
||||
|
||||
public String getName() {
|
||||
if (name == null) {
|
||||
name = formatName(file.getName());
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
private String formatName(String name) {
|
||||
int ext = name.lastIndexOf('.');
|
||||
if (ext != -1) {
|
||||
name = name.substring(0, ext);
|
||||
}
|
||||
return name.replace('_', ' ');
|
||||
}
|
||||
|
||||
// Usage: AndroidUtils.formatSize(v.getContext(), getSize() * 1024l);
|
||||
public int getSize() {
|
||||
if (sz == -1) {
|
||||
if (file == null) {
|
||||
return -1;
|
||||
}
|
||||
sz = (int) ((file.length() + 512) >> 10);
|
||||
}
|
||||
return sz;
|
||||
}
|
||||
|
||||
public long getFileDate() {
|
||||
if (file == null) {
|
||||
return 0;
|
||||
}
|
||||
return file.lastModified();
|
||||
}
|
||||
|
||||
public String getFileName(boolean includeSubfolder) {
|
||||
String result;
|
||||
if (fileName != null) {
|
||||
result = fileName;
|
||||
} else {
|
||||
if (file == null) {
|
||||
result = "";
|
||||
} else {
|
||||
result = fileName = file.getName();
|
||||
}
|
||||
}
|
||||
if (includeSubfolder && !Algorithms.isEmpty(subfolder)) {
|
||||
result = subfolder + "/" + result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -652,7 +652,7 @@ public class DownloadActivity extends AbstractDownloadActivity implements Downlo
|
|||
TextView messageTextView = (TextView) view.findViewById(R.id.leftTextView);
|
||||
ProgressBar sizeProgress = (ProgressBar) view.findViewById(R.id.progressBar);
|
||||
|
||||
File dir = activity.getMyApplication().getAppPath("").getParentFile();
|
||||
File dir = activity.getMyApplication().getAppPath(null);
|
||||
String size = "";
|
||||
int percent = 0;
|
||||
if (dir.canRead()) {
|
||||
|
|
|
@ -10,6 +10,7 @@ import net.osmand.map.OsmandRegions;
|
|||
import net.osmand.plus.OsmandApplication;
|
||||
import net.osmand.plus.R;
|
||||
import net.osmand.plus.Version;
|
||||
import net.osmand.plus.activities.LocalIndexInfo;
|
||||
import net.osmand.plus.helpers.FileNameTranslationHelper;
|
||||
import net.osmand.util.Algorithms;
|
||||
|
||||
|
@ -27,11 +28,12 @@ import java.util.Locale;
|
|||
import java.util.Map;
|
||||
|
||||
import static net.osmand.IndexConstants.BINARY_MAP_INDEX_EXT;
|
||||
import static net.osmand.plus.activities.LocalIndexHelper.LocalIndexType.SRTM_DATA;
|
||||
|
||||
public class DownloadActivityType {
|
||||
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd.MM.yyyy", Locale.US);
|
||||
private static Map<String, DownloadActivityType> byTag = new HashMap<>();
|
||||
|
||||
|
||||
public static final DownloadActivityType NORMAL_FILE =
|
||||
new DownloadActivityType(R.string.download_regular_maps, "map", 10);
|
||||
public static final DownloadActivityType VOICE_FILE =
|
||||
|
@ -83,7 +85,7 @@ public class DownloadActivityType {
|
|||
iconResource = R.drawable.ic_map;
|
||||
}
|
||||
|
||||
public int getStringResource(){
|
||||
public int getStringResource() {
|
||||
return stringResource;
|
||||
}
|
||||
|
||||
|
@ -101,7 +103,7 @@ public class DownloadActivityType {
|
|||
|
||||
public static boolean isCountedInDownloads(IndexItem es) {
|
||||
DownloadActivityType tp = es.getType();
|
||||
if(tp == NORMAL_FILE || tp == ROADS_FILE){
|
||||
if (tp == NORMAL_FILE || tp == ROADS_FILE) {
|
||||
if (!es.extra) {
|
||||
return true;
|
||||
}
|
||||
|
@ -120,17 +122,17 @@ public class DownloadActivityType {
|
|||
public static Collection<DownloadActivityType> values() {
|
||||
return byTag.values();
|
||||
}
|
||||
|
||||
|
||||
protected static String addVersionToExt(String ext, int version) {
|
||||
return "_" + version + ext;
|
||||
}
|
||||
|
||||
|
||||
public boolean isAccepted(String fileName) {
|
||||
if(NORMAL_FILE == this) {
|
||||
return fileName.endsWith(addVersionToExt(IndexConstants.BINARY_MAP_INDEX_EXT_ZIP, IndexConstants.BINARY_MAP_VERSION))
|
||||
if (NORMAL_FILE == this) {
|
||||
return fileName.endsWith(addVersionToExt(IndexConstants.BINARY_MAP_INDEX_EXT_ZIP, IndexConstants.BINARY_MAP_VERSION))
|
||||
|| fileName.endsWith(IndexConstants.EXTRA_ZIP_EXT)
|
||||
|| fileName.endsWith(IndexConstants.SQLITE_EXT);
|
||||
} else if(ROADS_FILE == this) {
|
||||
} else if (ROADS_FILE == this) {
|
||||
return fileName.endsWith(addVersionToExt(IndexConstants.BINARY_ROAD_MAP_INDEX_EXT_ZIP, IndexConstants.BINARY_MAP_VERSION));
|
||||
} else if (VOICE_FILE == this) {
|
||||
return fileName.endsWith(addVersionToExt(IndexConstants.VOICE_INDEX_EXT_ZIP, IndexConstants.VOICE_VERSION));
|
||||
|
@ -145,8 +147,9 @@ public class DownloadActivityType {
|
|||
return fileName.endsWith(addVersionToExt(IndexConstants.BINARY_TRAVEL_GUIDE_MAP_INDEX_EXT_ZIP,
|
||||
IndexConstants.BINARY_MAP_VERSION));
|
||||
} else if (SRTM_COUNTRY_FILE == this) {
|
||||
return fileName.endsWith(addVersionToExt(IndexConstants.BINARY_SRTM_MAP_INDEX_EXT_ZIP,
|
||||
IndexConstants.BINARY_MAP_VERSION));
|
||||
boolean srtm = fileName.endsWith(addVersionToExt(IndexConstants.BINARY_SRTM_MAP_INDEX_EXT_ZIP, IndexConstants.BINARY_MAP_VERSION));
|
||||
boolean srtmf = fileName.endsWith(addVersionToExt(IndexConstants.BINARY_SRTM_FEET_MAP_INDEX_EXT_ZIP, IndexConstants.BINARY_MAP_VERSION));
|
||||
return srtm || srtmf;
|
||||
} else if (HILLSHADE_FILE == this) {
|
||||
return fileName.endsWith(IndexConstants.SQLITE_EXT);
|
||||
} else if (SLOPE_FILE == this) {
|
||||
|
@ -160,7 +163,7 @@ public class DownloadActivityType {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public File getDownloadFolder(OsmandApplication ctx, IndexItem indexItem) {
|
||||
if (NORMAL_FILE == this) {
|
||||
if (indexItem.fileName.endsWith(IndexConstants.SQLITE_EXT)) {
|
||||
|
@ -196,17 +199,17 @@ public class DownloadActivityType {
|
|||
}
|
||||
|
||||
public boolean isZipStream(OsmandApplication ctx, IndexItem indexItem) {
|
||||
return HILLSHADE_FILE != this && SLOPE_FILE != this && SQLITE_FILE != this && WIKIVOYAGE_FILE != this && GPX_FILE != this;
|
||||
return HILLSHADE_FILE != this && SLOPE_FILE != this && SQLITE_FILE != this && WIKIVOYAGE_FILE != this && GPX_FILE != this;
|
||||
}
|
||||
|
||||
public boolean isZipFolder(OsmandApplication ctx, IndexItem indexItem) {
|
||||
return this == VOICE_FILE;
|
||||
}
|
||||
|
||||
|
||||
public boolean preventMediaIndexing(OsmandApplication ctx, IndexItem indexItem) {
|
||||
return this == VOICE_FILE && indexItem.fileName.endsWith(IndexConstants.VOICE_INDEX_EXT_ZIP);
|
||||
}
|
||||
|
||||
|
||||
public String getUnzipExtension(OsmandApplication ctx, IndexItem indexItem) {
|
||||
if (NORMAL_FILE == this) {
|
||||
if (indexItem.fileName.endsWith(IndexConstants.BINARY_MAP_INDEX_EXT_ZIP)) {
|
||||
|
@ -217,7 +220,7 @@ public class DownloadActivityType {
|
|||
return IndexConstants.EXTRA_EXT;
|
||||
} else if (indexItem.fileName.endsWith(IndexConstants.SQLITE_EXT)) {
|
||||
return IndexConstants.SQLITE_EXT;
|
||||
} else if (indexItem.fileName.endsWith(IndexConstants.ANYVOICE_INDEX_EXT_ZIP)){
|
||||
} else if (indexItem.fileName.endsWith(IndexConstants.ANYVOICE_INDEX_EXT_ZIP)) {
|
||||
return "";
|
||||
}
|
||||
} else if (ROADS_FILE == this) {
|
||||
|
@ -227,7 +230,7 @@ public class DownloadActivityType {
|
|||
} else if (FONT_FILE == this) {
|
||||
return IndexConstants.FONT_INDEX_EXT;
|
||||
} else if (SRTM_COUNTRY_FILE == this) {
|
||||
return IndexConstants.BINARY_SRTM_MAP_INDEX_EXT;
|
||||
return SrtmDownloadItem.getExtension(indexItem);
|
||||
} else if (WIKIPEDIA_FILE == this) {
|
||||
return IndexConstants.BINARY_WIKI_MAP_INDEX_EXT;
|
||||
} else if (WIKIVOYAGE_FILE == this) {
|
||||
|
@ -249,9 +252,9 @@ public class DownloadActivityType {
|
|||
}
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
||||
public String getUrlSuffix(OsmandApplication ctx) {
|
||||
if (this== ROADS_FILE) {
|
||||
if (this == ROADS_FILE) {
|
||||
return "&road=yes";
|
||||
} else if (this == LIVE_UPDATES_FILE) {
|
||||
return "&aosmc=yes";
|
||||
|
@ -280,7 +283,7 @@ public class DownloadActivityType {
|
|||
public String getBaseUrl(OsmandApplication ctx, String fileName) {
|
||||
String url = "https://" + IndexConstants.INDEX_DOWNLOAD_DOMAIN + "/download?event=2&"
|
||||
+ Version.getVersionAsURLParam(ctx) + "&file=" + encode(fileName);
|
||||
if(this == LIVE_UPDATES_FILE && fileName.length() > 16) {
|
||||
if (this == LIVE_UPDATES_FILE && fileName.length() > 16) {
|
||||
// DATE_AND_EXT_STR_LEN = "_18_06_02.obf.gz".length()
|
||||
String region = fileName.substring(0, fileName.length() - 16).toLowerCase();
|
||||
url += "®ion=" + encode(region);
|
||||
|
@ -343,7 +346,7 @@ public class DownloadActivityType {
|
|||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
public String getVisibleName(DownloadItem downloadItem, Context ctx, OsmandRegions osmandRegions, boolean includingParent) {
|
||||
if (this == VOICE_FILE) {
|
||||
String fileName = downloadItem.getFileName();
|
||||
|
@ -386,7 +389,7 @@ public class DownloadActivityType {
|
|||
|
||||
return osmandRegions.getLocaleName(basename, includingParent);
|
||||
}
|
||||
|
||||
|
||||
public String getTargetFileName(IndexItem item) {
|
||||
String fileName = item.fileName;
|
||||
// if(fileName.endsWith(IndexConstants.VOICE_INDEX_EXT_ZIP) ||
|
||||
|
@ -426,7 +429,7 @@ public class DownloadActivityType {
|
|||
}
|
||||
String baseNameWithoutVersion = fileName.substring(0, l);
|
||||
if (this == SRTM_COUNTRY_FILE) {
|
||||
return baseNameWithoutVersion + IndexConstants.BINARY_SRTM_MAP_INDEX_EXT;
|
||||
return baseNameWithoutVersion + SrtmDownloadItem.getExtension(item);
|
||||
}
|
||||
if (this == WIKIPEDIA_FILE) {
|
||||
return baseNameWithoutVersion + IndexConstants.BINARY_WIKI_MAP_INDEX_EXT;
|
||||
|
@ -490,7 +493,7 @@ public class DownloadActivityType {
|
|||
return fileName.substring(0, l);
|
||||
}
|
||||
if (this == LIVE_UPDATES_FILE) {
|
||||
if(fileName.indexOf('.') > 0){
|
||||
if (fileName.indexOf('.') > 0) {
|
||||
return fileName.substring(0, fileName.indexOf('.'));
|
||||
}
|
||||
return fileName;
|
||||
|
@ -498,10 +501,10 @@ public class DownloadActivityType {
|
|||
int ls = fileName.lastIndexOf('_');
|
||||
if (ls >= 0) {
|
||||
return fileName.substring(0, ls);
|
||||
} else if(fileName.indexOf('.') > 0){
|
||||
} else if (fileName.indexOf('.') > 0) {
|
||||
return fileName.substring(0, fileName.indexOf('.'));
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import net.osmand.osm.io.NetworkUtils;
|
|||
import net.osmand.plus.OsmandApplication;
|
||||
import net.osmand.plus.R;
|
||||
import net.osmand.plus.Version;
|
||||
import net.osmand.plus.download.IndexItem.DownloadEntry;
|
||||
import net.osmand.plus.helpers.FileNameTranslationHelper;
|
||||
import net.osmand.util.Algorithms;
|
||||
|
||||
|
@ -199,33 +200,35 @@ public class DownloadFileHelper {
|
|||
public boolean isWifiConnected(){
|
||||
return ctx.getSettings().isWifiConnected();
|
||||
}
|
||||
|
||||
public boolean downloadFile(IndexItem.DownloadEntry de, IProgress progress,
|
||||
List<File> toReIndex, DownloadFileShowWarning showWarningCallback, boolean forceWifi) throws InterruptedException {
|
||||
|
||||
public boolean downloadFile(IndexItem.DownloadEntry de, IProgress progress,
|
||||
List<File> toReIndex, DownloadFileShowWarning showWarningCallback, boolean forceWifi) throws InterruptedException {
|
||||
try {
|
||||
final List<InputStream> downloadInputStreams = new ArrayList<InputStream>();
|
||||
URL url = new URL(de.urlToDownload); //$NON-NLS-1$
|
||||
log.info("Url downloading " + de.urlToDownload);
|
||||
downloadInputStreams.add(getInputStreamToDownload(url, forceWifi));
|
||||
de.fileToDownload = de.targetFile;
|
||||
if(!de.unzipFolder) {
|
||||
de.fileToDownload = new File(de.targetFile.getParentFile(), de.targetFile.getName() +".download");
|
||||
if (!de.unzipFolder) {
|
||||
de.fileToDownload = new File(de.targetFile.getParentFile(), de.targetFile.getName() + ".download");
|
||||
}
|
||||
unzipFile(de, progress, downloadInputStreams);
|
||||
if(!de.targetFile.getAbsolutePath().equals(de.fileToDownload.getAbsolutePath())){
|
||||
boolean successfull = Algorithms.removeAllFiles(de.targetFile);
|
||||
if (successfull) {
|
||||
if (!de.targetFile.getAbsolutePath().equals(de.fileToDownload.getAbsolutePath())) {
|
||||
boolean successful = Algorithms.removeAllFiles(de.targetFile);
|
||||
if (successful) {
|
||||
ctx.getResourceManager().closeFile(de.targetFile.getName());
|
||||
}
|
||||
|
||||
|
||||
boolean renamed = de.fileToDownload.renameTo(de.targetFile);
|
||||
if(!renamed) {
|
||||
if (!renamed) {
|
||||
showWarningCallback.showWarning(ctx.getString(R.string.shared_string_io_error) + ": old file can't be deleted");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (de.type == DownloadActivityType.VOICE_FILE){
|
||||
if (de.type == DownloadActivityType.VOICE_FILE) {
|
||||
copyVoiceConfig(de);
|
||||
} else if (de.type == DownloadActivityType.SRTM_COUNTRY_FILE) {
|
||||
removePreviousSrtmFile(de);
|
||||
}
|
||||
toReIndex.add(de.targetFile);
|
||||
return true;
|
||||
|
@ -238,6 +241,26 @@ public class DownloadFileHelper {
|
|||
}
|
||||
}
|
||||
|
||||
private void removePreviousSrtmFile(DownloadEntry entry) {
|
||||
String meterExt = IndexConstants.BINARY_SRTM_MAP_INDEX_EXT;
|
||||
String feetExt = IndexConstants.BINARY_SRTM_FEET_MAP_INDEX_EXT;
|
||||
|
||||
String fileName = entry.targetFile.getAbsolutePath();
|
||||
if (fileName.endsWith(meterExt)) {
|
||||
fileName = fileName.replace(meterExt, feetExt);
|
||||
} else if (fileName.endsWith(feetExt)) {
|
||||
fileName = fileName.replace(feetExt, meterExt);
|
||||
}
|
||||
|
||||
File previous = new File(fileName);
|
||||
if (previous != null && previous.exists()) {
|
||||
boolean successful = Algorithms.removeAllFiles(previous);
|
||||
if (successful) {
|
||||
ctx.getResourceManager().closeFile(previous.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void copyVoiceConfig(IndexItem.DownloadEntry de) {
|
||||
File f = ctx.getAppPath("/voice/" + de.baseName + "/_config.p");
|
||||
if (f.exists()) try {
|
||||
|
@ -386,23 +409,20 @@ public class DownloadFileHelper {
|
|||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
int av = 0;
|
||||
for(int i = currentRead; i < delegate.length; i++) {
|
||||
for (int i = currentRead; i < delegate.length; i++) {
|
||||
av += delegate[i].available();
|
||||
}
|
||||
return av;
|
||||
}
|
||||
|
||||
|
||||
public int getAndClearReadCount() {
|
||||
int last = count;
|
||||
count = 0;
|
||||
return last;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ import android.net.TrafficStats;
|
|||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.AsyncTask.Status;
|
||||
import android.os.StatFs;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
|
@ -17,13 +16,12 @@ import androidx.annotation.UiThread;
|
|||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import net.osmand.AndroidNetworkUtils;
|
||||
import net.osmand.AndroidUtils;
|
||||
import net.osmand.IndexConstants;
|
||||
import net.osmand.PlatformUtil;
|
||||
import net.osmand.map.WorldRegion;
|
||||
import net.osmand.map.WorldRegion.RegionParams;
|
||||
import net.osmand.plus.OsmandApplication;
|
||||
import net.osmand.plus.settings.backend.OsmandSettings;
|
||||
import net.osmand.plus.settings.backend.OsmandPreference;
|
||||
import net.osmand.plus.R;
|
||||
import net.osmand.plus.Version;
|
||||
import net.osmand.plus.base.BasicProgressAsyncTask;
|
||||
|
@ -31,6 +29,8 @@ import net.osmand.plus.download.DownloadFileHelper.DownloadFileShowWarning;
|
|||
import net.osmand.plus.helpers.DatabaseHelper;
|
||||
import net.osmand.plus.notifications.OsmandNotification;
|
||||
import net.osmand.plus.resources.ResourceManager;
|
||||
import net.osmand.plus.settings.backend.OsmandPreference;
|
||||
import net.osmand.plus.settings.backend.OsmandSettings;
|
||||
import net.osmand.util.Algorithms;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
|
@ -240,9 +240,12 @@ public class DownloadIndexesThread {
|
|||
}
|
||||
|
||||
public void cancelDownload(DownloadItem item) {
|
||||
if (item instanceof MultipleIndexItem) {
|
||||
MultipleIndexItem multipleIndexItem = (MultipleIndexItem) item;
|
||||
cancelDownload(multipleIndexItem.getAllIndexes());
|
||||
if (item instanceof MultipleDownloadItem) {
|
||||
MultipleDownloadItem multipleDownloadItem = (MultipleDownloadItem) item;
|
||||
cancelDownload(multipleDownloadItem.getAllIndexes());
|
||||
} else if (item instanceof SrtmDownloadItem) {
|
||||
IndexItem indexItem = ((SrtmDownloadItem) item).getIndexItem();
|
||||
cancelDownload(indexItem);
|
||||
} else if (item instanceof IndexItem) {
|
||||
IndexItem indexItem = (IndexItem) item;
|
||||
cancelDownload(indexItem);
|
||||
|
@ -299,19 +302,8 @@ public class DownloadIndexesThread {
|
|||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public double getAvailableSpace() {
|
||||
File dir = app.getAppPath("").getParentFile();
|
||||
double asz = -1;
|
||||
if (dir.canRead()) {
|
||||
try {
|
||||
StatFs fs = new StatFs(dir.getAbsolutePath());
|
||||
asz = (((long) fs.getAvailableBlocks()) * fs.getBlockSize()) / (1 << 20);
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.error(e);
|
||||
}
|
||||
}
|
||||
return asz;
|
||||
return AndroidUtils.getAvailableSpace(app) / (1 << 20);
|
||||
}
|
||||
|
||||
/// PRIVATE IMPL
|
||||
|
|
|
@ -3,12 +3,14 @@ package net.osmand.plus.download;
|
|||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import net.osmand.map.OsmandRegions;
|
||||
import net.osmand.plus.OsmandApplication;
|
||||
import net.osmand.plus.R;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.DateFormat;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
|
@ -55,6 +57,12 @@ public abstract class DownloadItem {
|
|||
return type.getBasename(this);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public abstract List<File> getDownloadedFiles(@NonNull OsmandApplication app);
|
||||
|
||||
@Nullable
|
||||
public abstract String getAdditionalDescription(Context ctx);
|
||||
|
||||
protected abstract double getSizeToDownloadInMb();
|
||||
|
||||
public abstract double getArchiveSizeMB();
|
||||
|
@ -69,8 +77,7 @@ public abstract class DownloadItem {
|
|||
|
||||
public abstract String getFileName();
|
||||
|
||||
@NonNull
|
||||
public abstract List<File> getDownloadedFiles(@NonNull OsmandApplication app);
|
||||
public abstract String getDate(@NonNull DateFormat dateFormat, boolean remote);
|
||||
|
||||
@NonNull
|
||||
public static String getFormattedMb(@NonNull Context ctx, double sizeInMb) {
|
||||
|
|
|
@ -1,5 +1,26 @@
|
|||
package net.osmand.plus.download;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.res.AssetManager;
|
||||
import android.provider.Settings.Secure;
|
||||
|
||||
import net.osmand.AndroidUtils;
|
||||
import net.osmand.IndexConstants;
|
||||
import net.osmand.PlatformUtil;
|
||||
import net.osmand.osm.io.NetworkUtils;
|
||||
import net.osmand.plus.OsmandApplication;
|
||||
import net.osmand.plus.settings.backend.OsmandSettings;
|
||||
import net.osmand.util.Algorithms;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
import org.xmlpull.v1.XmlPullParserFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
@ -11,29 +32,9 @@ import java.util.Comparator;
|
|||
import java.util.List;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
import net.osmand.AndroidUtils;
|
||||
import net.osmand.IndexConstants;
|
||||
import net.osmand.PlatformUtil;
|
||||
import net.osmand.osm.io.NetworkUtils;
|
||||
import net.osmand.plus.OsmandApplication;
|
||||
import net.osmand.plus.settings.backend.OsmandSettings;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
import org.xmlpull.v1.XmlPullParserFactory;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.res.AssetManager;
|
||||
import android.provider.Settings.Secure;
|
||||
|
||||
public class DownloadOsmandIndexesHelper {
|
||||
private final static Log log = PlatformUtil.getLog(DownloadOsmandIndexesHelper.class);
|
||||
|
||||
|
||||
public static class IndexFileList implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
|
@ -41,29 +42,29 @@ public class DownloadOsmandIndexesHelper {
|
|||
IndexItem basemap;
|
||||
ArrayList<IndexItem> indexFiles = new ArrayList<IndexItem>();
|
||||
private String mapversion;
|
||||
|
||||
private Comparator<IndexItem> comparator = new Comparator<IndexItem>(){
|
||||
|
||||
private Comparator<IndexItem> comparator = new Comparator<IndexItem>() {
|
||||
@Override
|
||||
public int compare(IndexItem o1, IndexItem o2) {
|
||||
String object1 = o1.getFileName();
|
||||
String object2 = o2.getFileName();
|
||||
if(object1.endsWith(IndexConstants.ANYVOICE_INDEX_EXT_ZIP)){
|
||||
if(object2.endsWith(IndexConstants.ANYVOICE_INDEX_EXT_ZIP)){
|
||||
if (object1.endsWith(IndexConstants.ANYVOICE_INDEX_EXT_ZIP)) {
|
||||
if (object2.endsWith(IndexConstants.ANYVOICE_INDEX_EXT_ZIP)) {
|
||||
return object1.compareTo(object2);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
} else if(object2.endsWith(IndexConstants.ANYVOICE_INDEX_EXT_ZIP)){
|
||||
} else if (object2.endsWith(IndexConstants.ANYVOICE_INDEX_EXT_ZIP)) {
|
||||
return 1;
|
||||
}
|
||||
return object1.compareTo(object2);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public void setDownloadedFromInternet(boolean downloadedFromInternet) {
|
||||
this.downloadedFromInternet = downloadedFromInternet;
|
||||
}
|
||||
|
||||
|
||||
public boolean isDownloadedFromInternet() {
|
||||
return downloadedFromInternet;
|
||||
}
|
||||
|
@ -75,12 +76,12 @@ public class DownloadOsmandIndexesHelper {
|
|||
@SuppressLint("DefaultLocale")
|
||||
public void add(IndexItem indexItem) {
|
||||
indexFiles.add(indexItem);
|
||||
if(indexItem.getFileName().toLowerCase().startsWith("world_basemap")) {
|
||||
if (indexItem.getFileName().toLowerCase().startsWith("world_basemap")) {
|
||||
basemap = indexItem;
|
||||
}
|
||||
}
|
||||
|
||||
public void sort(){
|
||||
|
||||
public void sort() {
|
||||
Collections.sort(indexFiles, comparator);
|
||||
}
|
||||
|
||||
|
@ -91,7 +92,7 @@ public class DownloadOsmandIndexesHelper {
|
|||
public List<IndexItem> getIndexFiles() {
|
||||
return indexFiles;
|
||||
}
|
||||
|
||||
|
||||
public IndexItem getBasemap() {
|
||||
return basemap;
|
||||
}
|
||||
|
@ -106,7 +107,7 @@ public class DownloadOsmandIndexesHelper {
|
|||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static IndexFileList getIndexesList(OsmandApplication app) {
|
||||
PackageManager pm = app.getPackageManager();
|
||||
|
@ -141,16 +142,16 @@ public class DownloadOsmandIndexesHelper {
|
|||
}
|
||||
|
||||
private static void listVoiceAssets(IndexFileList result, AssetManager amanager, PackageManager pm,
|
||||
OsmandSettings settings) {
|
||||
OsmandSettings settings) {
|
||||
try {
|
||||
File voicePath = settings.getContext().getAppPath(IndexConstants.VOICE_INDEX_DIR);
|
||||
File voicePath = settings.getContext().getAppPath(IndexConstants.VOICE_INDEX_DIR);
|
||||
// list = amanager.list("voice");
|
||||
String date = "";
|
||||
long dateModified = System.currentTimeMillis();
|
||||
try {
|
||||
OsmandApplication app = settings.getContext();
|
||||
ApplicationInfo appInfo = pm.getApplicationInfo(app.getPackageName(), 0);
|
||||
dateModified = new File(appInfo.sourceDir).lastModified();
|
||||
dateModified = new File(appInfo.sourceDir).lastModified();
|
||||
date = AndroidUtils.formatDate((Context) settings.getContext(), dateModified);
|
||||
} catch (NameNotFoundException e) {
|
||||
log.error(e);
|
||||
|
@ -177,17 +178,17 @@ public class DownloadOsmandIndexesHelper {
|
|||
log.error("Error while loading tts files from assets", e); //$NON-NLS-1$
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static IndexFileList downloadIndexesListFromInternet(OsmandApplication ctx){
|
||||
|
||||
private static IndexFileList downloadIndexesListFromInternet(OsmandApplication ctx) {
|
||||
try {
|
||||
IndexFileList result = new IndexFileList();
|
||||
log.debug("Start loading list of index files"); //$NON-NLS-1$
|
||||
try {
|
||||
String strUrl = ctx.getAppCustomization().getIndexesUrl();
|
||||
long nd = ctx.getAppInitializer().getFirstInstalledDays();
|
||||
if(nd > 0) {
|
||||
strUrl += "&nd=" + nd;
|
||||
if (nd > 0) {
|
||||
strUrl += "&nd=" + nd;
|
||||
}
|
||||
strUrl += "&ns=" + ctx.getAppInitializer().getNumberOfStarts();
|
||||
try {
|
||||
|
@ -202,12 +203,12 @@ public class DownloadOsmandIndexesHelper {
|
|||
GZIPInputStream gzin = new GZIPInputStream(in);
|
||||
parser.setInput(gzin, "UTF-8"); //$NON-NLS-1$
|
||||
int next;
|
||||
while((next = parser.next()) != XmlPullParser.END_DOCUMENT) {
|
||||
while ((next = parser.next()) != XmlPullParser.END_DOCUMENT) {
|
||||
if (next == XmlPullParser.START_TAG) {
|
||||
DownloadActivityType tp = DownloadActivityType.getIndexType(parser.getAttributeValue(null, "type"));
|
||||
if (tp != null) {
|
||||
IndexItem it = tp.parseIndexItem(ctx, parser);
|
||||
if(it != null) {
|
||||
if (it != null) {
|
||||
result.add(it);
|
||||
}
|
||||
} else if ("osmand_regions".equals(parser.getName())) {
|
||||
|
@ -226,7 +227,7 @@ public class DownloadOsmandIndexesHelper {
|
|||
log.error("Error while loading indexes from repository", e); //$NON-NLS-1$
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
if (result.isAcceptable()) {
|
||||
return result;
|
||||
} else {
|
||||
|
@ -239,19 +240,19 @@ public class DownloadOsmandIndexesHelper {
|
|||
}
|
||||
|
||||
public static class AssetIndexItem extends IndexItem {
|
||||
|
||||
|
||||
private final String assetName;
|
||||
private final String destFile;
|
||||
private final long dateModified;
|
||||
|
||||
public AssetIndexItem(String fileName, String description, String date,
|
||||
long dateModified, String size, long sizeL, String assetName, String destFile, DownloadActivityType type) {
|
||||
long dateModified, String size, long sizeL, String assetName, String destFile, DownloadActivityType type) {
|
||||
super(fileName, description, dateModified, size, sizeL, sizeL, type);
|
||||
this.dateModified = dateModified;
|
||||
this.assetName = assetName;
|
||||
this.destFile = destFile;
|
||||
}
|
||||
|
||||
|
||||
public long getDateModified() {
|
||||
return dateModified;
|
||||
}
|
||||
|
@ -261,7 +262,7 @@ public class DownloadOsmandIndexesHelper {
|
|||
return new DownloadEntry(assetName, destFile, dateModified);
|
||||
}
|
||||
|
||||
public String getDestFile(){
|
||||
public String getDestFile() {
|
||||
return destFile;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,12 +25,11 @@ import java.io.InputStream;
|
|||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static net.osmand.plus.download.DownloadResourceGroup.DownloadResourceGroupType.REGION_MAPS;
|
||||
|
||||
|
@ -117,6 +116,16 @@ public class DownloadResources extends DownloadResourceGroup {
|
|||
return res;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public List<DownloadItem> getDownloadItems(WorldRegion region) {
|
||||
DownloadResourceGroup group = getRegionMapsGroup(region);
|
||||
if (group != null) {
|
||||
return group.getIndividualDownloadItems();
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public List<IndexItem> getIndexItems(WorldRegion region) {
|
||||
if (groupByRegion != null) {
|
||||
List<IndexItem> res = groupByRegion.get(region);
|
||||
|
@ -124,7 +133,7 @@ public class DownloadResources extends DownloadResourceGroup {
|
|||
return res;
|
||||
}
|
||||
}
|
||||
return new LinkedList<>();
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public void updateLoadedFiles() {
|
||||
|
@ -471,30 +480,60 @@ public class DownloadResources extends DownloadResourceGroup {
|
|||
addGroup(otherGroup);
|
||||
|
||||
createHillshadeSRTMGroups();
|
||||
collectMultipleIndexesItems();
|
||||
replaceIndividualSrtmWithGroups(region);
|
||||
createMultipleDownloadItems(region);
|
||||
trimEmptyGroups();
|
||||
updateLoadedFiles();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void collectMultipleIndexesItems() {
|
||||
collectMultipleIndexesItems(region);
|
||||
private void replaceIndividualSrtmWithGroups(@NonNull WorldRegion region) {
|
||||
DownloadResourceGroup group = getRegionMapsGroup(region);
|
||||
if (group != null) {
|
||||
boolean useMetersByDefault = SrtmDownloadItem.isUseMetricByDefault(app);
|
||||
boolean listModified = false;
|
||||
DownloadActivityType srtmType = DownloadActivityType.SRTM_COUNTRY_FILE;
|
||||
List<DownloadItem> individualItems = group.getIndividualDownloadItems();
|
||||
if (isListContainsType(individualItems, srtmType)) {
|
||||
List<IndexItem> srtmIndexes = new ArrayList<>();
|
||||
for (DownloadItem item : individualItems) {
|
||||
if (item.getType() == srtmType && item instanceof IndexItem) {
|
||||
srtmIndexes.add((IndexItem) item);
|
||||
}
|
||||
}
|
||||
if (srtmIndexes.size() > 1) {
|
||||
individualItems.removeAll(srtmIndexes);
|
||||
group.addItem(new SrtmDownloadItem(srtmIndexes, useMetersByDefault));
|
||||
}
|
||||
listModified = true;
|
||||
}
|
||||
if (listModified) {
|
||||
sortDownloadItems(individualItems);
|
||||
}
|
||||
}
|
||||
|
||||
List<WorldRegion> subRegions = region.getSubregions();
|
||||
if (!Algorithms.isEmpty(subRegions)) {
|
||||
for (WorldRegion subRegion : subRegions) {
|
||||
replaceIndividualSrtmWithGroups(subRegion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void collectMultipleIndexesItems(@NonNull WorldRegion region) {
|
||||
private void createMultipleDownloadItems(@NonNull WorldRegion region) {
|
||||
List<WorldRegion> subRegions = region.getSubregions();
|
||||
if (Algorithms.isEmpty(subRegions)) return;
|
||||
|
||||
DownloadResourceGroup group = getRegionMapsGroup(region);
|
||||
if (group != null) {
|
||||
boolean listModified = false;
|
||||
List<IndexItem> indexesList = group.getIndividualResources();
|
||||
List<WorldRegion> regionsToCollect = removeDuplicateRegions(subRegions);
|
||||
List<DownloadItem> downloadItems = group.getIndividualDownloadItems();
|
||||
List<WorldRegion> uniqueSubRegions = WorldRegion.removeDuplicates(subRegions);
|
||||
for (DownloadActivityType type : DownloadActivityType.values()) {
|
||||
if (!doesListContainIndexWithType(indexesList, type)) {
|
||||
List<IndexItem> indexesFromSubRegions = collectIndexesOfType(regionsToCollect, type);
|
||||
if (indexesFromSubRegions != null) {
|
||||
group.addItem(new MultipleIndexItem(region, indexesFromSubRegions, type));
|
||||
if (!isListContainsType(downloadItems, type)) {
|
||||
List<DownloadItem> itemsFromSubRegions = collectItemsOfType(uniqueSubRegions, type);
|
||||
if (itemsFromSubRegions != null) {
|
||||
group.addItem(new MultipleDownloadItem(region, itemsFromSubRegions, type));
|
||||
listModified = true;
|
||||
}
|
||||
}
|
||||
|
@ -504,7 +543,7 @@ public class DownloadResources extends DownloadResourceGroup {
|
|||
}
|
||||
}
|
||||
for (WorldRegion subRegion : subRegions) {
|
||||
collectMultipleIndexesItems(subRegion);
|
||||
createMultipleDownloadItems(subRegion);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -517,43 +556,21 @@ public class DownloadResources extends DownloadResourceGroup {
|
|||
}
|
||||
|
||||
@Nullable
|
||||
private List<IndexItem> collectIndexesOfType(@NonNull List<WorldRegion> regions,
|
||||
@NonNull DownloadActivityType type) {
|
||||
List<IndexItem> collectedIndexes = new ArrayList<>();
|
||||
private List<DownloadItem> collectItemsOfType(@NonNull List<WorldRegion> regions,
|
||||
@NonNull DownloadActivityType type) {
|
||||
List<DownloadItem> collectedItems = new ArrayList<>();
|
||||
for (WorldRegion region : regions) {
|
||||
List<IndexItem> regionIndexes = getIndexItems(region);
|
||||
boolean found = false;
|
||||
if (regionIndexes != null) {
|
||||
for (IndexItem index : regionIndexes) {
|
||||
if (index.getType() == type) {
|
||||
found = true;
|
||||
collectedIndexes.add(index);
|
||||
break;
|
||||
}
|
||||
for (DownloadItem item : getDownloadItems(region)) {
|
||||
if (item.getType() == type) {
|
||||
found = true;
|
||||
collectedItems.add(item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) return null;
|
||||
}
|
||||
return collectedIndexes;
|
||||
}
|
||||
|
||||
private List<WorldRegion> removeDuplicateRegions(List<WorldRegion> regions) {
|
||||
Set<WorldRegion> duplicates = new HashSet<>();
|
||||
for (int i = 0; i < regions.size() - 1; i++) {
|
||||
WorldRegion r1 = regions.get(i);
|
||||
for (int j = i + 1; j < regions.size(); j++) {
|
||||
WorldRegion r2 = regions.get(j);
|
||||
if (r1.containsRegion(r2)) {
|
||||
duplicates.add(r2);
|
||||
} else if (r2.containsRegion(r1)) {
|
||||
duplicates.add(r1);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (WorldRegion region : duplicates) {
|
||||
regions.remove(region);
|
||||
}
|
||||
return regions;
|
||||
return collectedItems;
|
||||
}
|
||||
|
||||
private void buildRegionsGroups(WorldRegion region, DownloadResourceGroup group) {
|
||||
|
@ -680,11 +697,11 @@ public class DownloadResources extends DownloadResourceGroup {
|
|||
&& isIndexItemDownloaded(downloadThread, type, downloadRegion.getSuperregion(), res);
|
||||
}
|
||||
|
||||
private boolean doesListContainIndexWithType(List<IndexItem> indexItems,
|
||||
DownloadActivityType type) {
|
||||
if (indexItems != null) {
|
||||
for (IndexItem indexItem : indexItems) {
|
||||
if (indexItem.getType() == type) {
|
||||
private boolean isListContainsType(List<DownloadItem> items,
|
||||
DownloadActivityType type) {
|
||||
if (items != null) {
|
||||
for (DownloadItem item : items) {
|
||||
if (item.getType() == type) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue