Merge pull request #10504 from osmandapp/master

update test branch
This commit is contained in:
Hardy 2021-01-01 01:06:29 +01:00 committed by GitHub
commit 04c7208d23
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
58 changed files with 2983 additions and 1950 deletions

View file

@ -1727,13 +1727,12 @@ public class BinaryMapIndexReader {
double half16t = MapUtils.getDistance(lat, MapUtils.getLongitudeFromTile(16, ((int) dx) + 0.5),
lat, MapUtils.getLongitudeFromTile(16, (int) dx));
double cf31 = ((double) radiusMeters / (half16t * 2)) * (1 << 15);
int y31 = MapUtils.get31TileNumberY(lat);
int x31 = MapUtils.get31TileNumberX(lon);
left = (int) (x31 - cf31);
right = (int) (x31 + cf31);
top = (int) (y31 - cf31);
bottom = (int) (y31 + cf31);
y = MapUtils.get31TileNumberY(lat);
x = MapUtils.get31TileNumberX(lon);
left = (int) (x - cf31);
right = (int) (x + cf31);
top = (int) (y - cf31);
bottom = (int) (y + cf31);
}
public boolean publish(T obj) {

View file

@ -764,6 +764,7 @@ public class CommonWords {
addFrequentlyUsed("martiri");
addFrequentlyUsed("verdi");
addFrequentlyUsed("augusta");
addFrequentlyUsed("neuburger");

View file

@ -44,6 +44,7 @@ public class Amenity extends MapObject {
public static final String IS_AGGR_PART = "is_aggr_part";
public static final String CONTENT_JSON = "content_json";
public static final String ROUTE_ID = "route_id";
public static final String ROUTE_SOURCE = "route_source";
private String subType;

View file

@ -16,11 +16,13 @@ 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;

View file

@ -78,6 +78,10 @@ public class Algorithms {
return map == null || map.size() == 0;
}
public static String emptyIfNull(String s) {
return s == null ? "" : s;
}
public static boolean isEmpty(CharSequence s) {
return s == null || s.length() == 0;
}
@ -86,6 +90,10 @@ public class Algorithms {
return s == null || s.trim().length() == 0;
}
public static int hash(Object... values) {
return Arrays.hashCode(values);
}
public static boolean stringsEqual(String s1, String s2) {
if (s1 == null && s2 == null) {
return true;

View file

@ -109,61 +109,6 @@
android:layout_marginEnd="@dimen/content_padding"
android:layout_marginStart="@dimen/content_padding" />
<LinearLayout
android:id="@+id/split_color_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<LinearLayout
android:id="@+id/split_interval_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.6"
android:minHeight="@dimen/card_row_min_height"
android:paddingLeft="@dimen/content_padding"
android:paddingStart="@dimen/content_padding"
android:paddingRight="0dp"
android:paddingEnd="0dp"
android:paddingTop="@dimen/content_padding_half"
android:paddingBottom="@dimen/content_padding_half"
android:background="?attr/selectableItemBackground"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:id="@+id/split_interval_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textColor="?android:textColorPrimary"
android:textSize="@dimen/default_list_text_size"
android:text="@string/gpx_split_interval"/>
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/split_interval_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/content_padding_half"
android:paddingRight="@dimen/content_padding_half"
android:textColor="?android:textColorPrimary"
android:textSize="@dimen/default_list_text_size"
osmand:typeface="@string/font_roboto_medium"
tools:text="10 km"
android:paddingEnd="@dimen/content_padding_half"
android:paddingStart="@dimen/content_padding_half" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/split_interval_arrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
osmand:srcCompat="@drawable/ic_action_arrow_drop_down"/>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/appearance_view"
android:layout_width="match_parent"

View file

@ -0,0 +1,127 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:osmand="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/color_transparent">
<LinearLayout
android:id="@+id/main_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/context_menu_top_shadow" />
<LinearLayout
android:id="@+id/route_menu_top_shadow_all"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/list_background_color"
android:orientation="vertical">
<View
android:layout_width="@dimen/content_padding"
android:layout_height="2dp"
android:layout_gravity="center"
android:layout_marginTop="@dimen/context_menu_padding_margin_tiny"
android:layout_marginBottom="@dimen/list_item_button_padding"
android:background="?attr/bg_dash_line" />
<LinearLayout
android:id="@+id/context_menu_top_shadow_all"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:id="@+id/context_menu_top_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="@dimen/context_menu_padding_margin_default"
android:paddingLeft="@dimen/context_menu_padding_margin_default"
android:paddingEnd="@dimen/context_menu_padding_margin_default"
android:paddingRight="@dimen/context_menu_padding_margin_default">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/context_menu_first_line_top_margin"
android:layout_marginEnd="@dimen/context_menu_padding_margin_default"
android:layout_marginRight="@dimen/context_menu_padding_margin_default"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/title"
style="@style/TextAppearance.ContextMenuTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="@string/search_address_building" />
<TextView
android:id="@+id/description"
style="@style/TextAppearance.ContextMenuSubtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
tools:text="@string/amenity_type_finance" />
</LinearLayout>
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/context_menu_icon_view"
android:layout_width="@dimen/map_widget_icon"
android:layout_height="@dimen/map_widget_icon"
android:layout_marginTop="@dimen/context_menu_padding_margin_default"
android:tint="?attr/default_icon_color"
osmand:srcCompat="@drawable/ic_action_polygom_dark" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
<FrameLayout
android:id="@+id/bottom_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/activity_background_basic"
android:foreground="@drawable/bg_contextmenu_shadow"
android:foregroundGravity="top|fill_horizontal">
<net.osmand.plus.LockableScrollView
android:id="@+id/route_menu_bottom_scroll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<LinearLayout
android:id="@+id/route_menu_cards_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="@dimen/context_menu_action_buttons_height" />
</net.osmand.plus.LockableScrollView>
</FrameLayout>
</LinearLayout>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_navigation"
android:layout_width="match_parent"
android:layout_height="@dimen/context_menu_action_buttons_height"
android:layout_gravity="bottom"
android:background="?attr/wikivoyage_card_bg_color"
osmand:itemBackground="?attr/wikivoyage_card_bg_color"
osmand:itemIconTint="@color/bottom_navigation_color_selector_light"
osmand:itemTextColor="@color/bottom_navigation_color_selector_light"
osmand:labelVisibilityMode="labeled"
osmand:menu="@menu/track_menu_bottom_navigation" />
</FrameLayout>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
</LinearLayout>

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<!-- <item-->
<!-- android:id="@+id/action_overview"-->
<!-- android:icon="@drawable/ic_action_trail_overview"-->
<!-- android:title="@string/shared_string_overview" />-->
<item
android:id="@+id/action_track"
android:icon="@drawable/ic_action_polygom_dark"
android:title="@string/shared_string_gpx_track" />
<item
android:id="@+id/action_points"
android:icon="@drawable/ic_action_waypoint"
android:title="@string/shared_string_gpx_points" />
<!-- <item-->
<!-- android:id="@+id/action_options"-->
<!-- android:icon="@drawable/ic_overflow_menu_white"-->
<!-- android:title="@string/shared_string_options" />-->
</menu>

View file

@ -2374,7 +2374,7 @@
<string name="poi_street_cabinet_postal_service">نوع الكبينة/الخزانة: خدمة بريدية</string>
<string name="poi_street_cabinet_gas">نوع الكبينة/الخزانة: غاز</string>
<string name="poi_street_cabinet_telecom">نوع الكبينة/الخزانة: اتصالات</string>
<string name="poi_street_cabinet_power">نوع الكبينة/الخزانة: طاقة</string>
<string name="poi_street_cabinet_power">نوع الكبينة/الخزانة: كهرباء</string>
<string name="poi_in_service_yes">في الخدمة: نعم</string>
<string name="poi_fire_hydrant_style_water_source_cistern">صهريج</string>
<string name="poi_water_source_stream">مجرى</string>

View file

@ -740,7 +740,7 @@
<string name="layer_overlay">الخريطة العلوية…</string>
<string name="shared_string_none">بدون</string>
<string name="map_overlay">الخريطة العلوية</string>
<string name="map_overlay_descr">اختيار خريطة التراكب</string>
<string name="map_overlay_descr">اختيار خريطة علوية أو سفلية</string>
<string name="poi_dialog_other_tags_message">يتم الاحتفاظ بكافة العلامات الأخرى</string>
<string name="amenity_type_seamark">سيمارك</string>
<string name="poi_dialog_comment_default">متغير POI</string>
@ -2379,12 +2379,12 @@
\n• يدعم نقاط وسيطة خلال مسارك
\n• إعادة تلقائية للتوجيه كلما انحرفت عن الطريق
\n• البحث عن الأماكن حسب العنوان، النوع (مثل: مطعم، فندق، محطة وقود، متحف)،أو حسب الإحداثيات الجغرافية</string>
<string name="osmand_plus_extended_description_part3">عرض الخريطة
\n• عرض موقعك والتوجيه
\n• محاذاة اختيارية للصورة وفق البوصلة أو توجيه الحركة
\n• حفظ أهم أماكنك المفضلة
\n• عرض النقاط المهمة من حولك (POI)
\n• عرض متخصص لبيانات خرائط على الإنترنت، الرؤية من الأقمار الصناعية (من بينج) وتراكب طبقات خرائط مختلفة كالسياحة ومسارات GPX للملاحة وطبقات إضافية مع شفافية قابلة للتعديل
<string name="osmand_plus_extended_description_part3">عرض الخريطة
\n• عرض موقعك والتوجيه
\n• محاذاة اختيارية للصورة وفق البوصلة أو توجيه الحركة
\n• حفظ أهم أماكنك المفضلة
\n• عرض نقاط الاهتمام من حولك
\n• عرض متخصص لبيانات خرائط على الإنترنت، الرؤية من الأقمار الصناعية (من بينج) وتراكب طبقات خرائط مختلفة كالسياحة ومسارات GPX للملاحة وطبقات إضافية مع شفافية قابلة للتعديل
\n• عرض اختياري لأسماء الأماكن باللغة الإنكليزية، اللغة المحلية، أو عبر الإملاء الصوتي
\n</string>
<string name="osmand_plus_extended_description_part4">استخدام OSM وبيانات ويكيبيديا

View file

@ -2888,4 +2888,9 @@
<string name="poi_cuisine_cajun">Cajun</string>
<string name="poi_cuisine_burrito">Burritos</string>
<string name="poi_cuisine_waffle">Gofres</string>
<string name="poi_motorcycle_clothes_no">Roba de motorista: no</string>
<string name="poi_motorcycle_clothes_yes">Roba de motorista</string>
<string name="poi_motorcycle_tyres_no">Pneumàtics: no</string>
<string name="poi_motorcycle_tyres_yes">Pneumàtics</string>
<string name="poi_motorcycle_parts_no">Recanvis: no</string>
</resources>

View file

@ -3925,4 +3925,12 @@
\n
\n</string>
<string name="reverse_all_points">Revertir tots els punts</string>
<string name="profile_by_default_description">Seleccioneu el perfil que s\'utilitzarà en iniciar l\'aplicació.</string>
<string name="shared_string_last_used">Darrera utilització</string>
<string name="routing_attr_prefer_hiking_routes_description">Prioritza les rutes de senderisme</string>
<string name="routing_attr_prefer_hiking_routes_name">Prioritza les rutes de senderisme</string>
<string name="routing_attr_allow_streams_description">Permet rierols i torrents</string>
<string name="routing_attr_allow_streams_name">Permet rierols i torrents</string>
<string name="routing_attr_allow_intermittent_description">Permet les vies navegables no permanents</string>
<string name="routing_attr_allow_intermittent_name">Permet les vies navegables no permanents</string>
</resources>

View file

@ -3901,4 +3901,5 @@
<string name="poi_waste_transfer_station">Müllumladestation</string>
<string name="poi_weightbridge">Fahrzeugwaage</string>
<string name="poi_ranger_station">Rangerstation</string>
<string name="poi_swimming_area">Schwimmbereich</string>
</resources>

View file

@ -3543,18 +3543,18 @@
<string name="shared_string_min">Min</string>
<string name="n_items_of_z">%1$s de %2$s</string>
<string name="shared_string_terrain">Terrain</string>
<string name="hillshade_description">Carte ombrée utilisant des nuances sombres pour visualiser les pistes, les sommets et les plaines.</string>
<string name="slope_description">La piste est colorée selon sa pente.</string>
<string name="hillshade_description">Carte ombrée utilisant des nuances sombres pour visualiser les pentes, les sommets et les plaines.</string>
<string name="slope_description">La pente est colorée selon l\'inclinaison du terrain.</string>
<string name="terrain_slider_description">Définissez les niveaux de zoom minimum et maximum pour lesquels la couche sera affichée.</string>
<string name="hillshade_download_description">Des cartes supplémentaires sont nécessaires pour afficher l\'ombrage du relief sur la carte.</string>
<string name="slope_download_description">Des cartes supplémentaires sont nécessaires pour afficher les pistes sur la carte.</string>
<string name="slope_read_more">Vous pouvez en apprendre plus sur les pistes dans %1$s.</string>
<string name="slope_download_description">Des cartes supplémentaires sont nécessaires pour afficher les pentes sur la carte.</string>
<string name="slope_read_more">Vous pouvez en savoir plus sur les pentes dans %1$s.</string>
<string name="shared_string_transparency">Transparence</string>
<string name="shared_string_zoom_levels">Niveaux de zoom</string>
<string name="shared_string_legend">Légende</string>
<string name="terrain_empty_state_text">Permet d\'afficher l\'ombrage du relief ou les pistes. Vous pouvez en apprendre plus sur ces types de cartes sur notre site.</string>
<string name="terrain_empty_state_text">Active l\'affichage de l\'ombrage du relief et l\'inclinaison. Vous pouvez en savoir plus sur ces types de cartes sur notre site.</string>
<string name="shared_string_hillshade">Ombrage du relief</string>
<string name="download_slope_maps">Pistes</string>
<string name="download_slope_maps">Pentes</string>
<string name="quick_action_show_hide_terrain">Affiche ou masque le terrain</string>
<string name="quick_action_terrain_hide">Masquer le terrain</string>
<string name="quick_action_terrain_show">Afficher le terrain</string>
@ -4004,4 +4004,6 @@
<string name="routing_attr_prefer_hiking_routes_description">Pirivilégier les itinéraires de randonnée</string>
<string name="routing_attr_allow_intermittent_description">Autoriser les voies navigables intermittentes</string>
<string name="routing_attr_allow_intermittent_name">Autoriser les voies navigables intermittentes</string>
<string name="routing_attr_allow_streams_description">Autoriser les cours deau et les drains</string>
<string name="routing_attr_allow_streams_name">Autoriser les cours deau et les drains</string>
</resources>

View file

@ -3890,4 +3890,5 @@
<string name="poi_water_source_tap">Csap</string>
<string name="poi_water_source_water_works">Vízmű</string>
<string name="poi_water_source_tube_well">Csöves kút</string>
<string name="poi_swimming_area">Fürdésre kijelölt terület</string>
</resources>

View file

@ -3943,4 +3943,7 @@
<string name="shared_string_resources">Ressurser</string>
<string name="profile_by_default_description">Velg profilen som skal brukes når programmet starter.</string>
<string name="shared_string_last_used">Sist brukt</string>
<string name="routing_attr_allow_streams_name">Tillat bekker og avløp</string>
<string name="routing_attr_allow_intermittent_name">Tillat periodiske vannveier</string>
<string name="routing_attr_allow_intermittent_description">Tillat periodiske vannveier</string>
</resources>

View file

@ -3793,7 +3793,7 @@
<string name="please_provide_point_name_error">Podaj nazwę punktu</string>
<string name="quick_action_remove_next_destination_descr">Usuwa następny cel na trasie. Jeśli jest to miejsce docelowe, nawigacja zostanie zatrzymana.</string>
<string name="search_download_wikipedia_maps">Pobierz mapy Wikipedii</string>
<string name="plugin_wikipedia_description">Uzyskaj informacje o ciekawych miejscach z Wikipedii, kieszonkowego przewodnika offline zawierającego artykuły o miejscach i celach.</string>
<string name="plugin_wikipedia_description">Uzyskaj informacje o ciekawych miejscach z Wikipedii, kieszonkowego przewodnika offline zawierającego artykuły o miejscach i celach podróży.</string>
<string name="app_mode_enduro_motorcycle">Motocykl Enduro</string>
<string name="app_mode_motor_scooter">Skuter</string>
<string name="routing_attr_length_description">Określ długość pojazdu dozwoloną na trasach.</string>
@ -3913,11 +3913,11 @@
<string name="start_finish_icons">Ikony startu i końca</string>
<string name="contour_lines_thanks">Dziękujemy za zakup \"Linii konturowych\"</string>
<string name="osm_live_payment_desc_hw">Subskrypcja jest naliczana za wybrany okres. Anuluj go w AppGallery w dowolnym momencie.</string>
<string name="osm_live_payment_subscription_management_hw">Płatność zostanie pobrana z konta AppGallery po potwierdzeniu zakupu.
<string name="osm_live_payment_subscription_management_hw">Twoje konto AppGallery jest obciążane po potwierdzeniu zakupu.
\n
\nSubskrypcja jest automatycznie odnawiana, chyba że zostanie anulowana przed datą odnowienia. Twoje konto zostanie obciążone opłatą za okres odnowienia (miesiąc/trzy miesiące/rok) tylko w dniu odnowienia.
\nSubskrypcja przedłuża się automatycznie, chyba że zostanie anulowana przed datą odnowienia. Twoje konto zostanie obciążone za okres odnowienia (miesiąc/trzy miesiące/rok) tylko w dniu odnowienia.
\n
\nMożesz zarządzać subskrypcjami i anulować je, przechodząc do ustawień Galerii aplikacji.</string>
\nMożesz zarządzać swoimi subskrypcjami i anulować je w ustawieniach AppGallery.</string>
<string name="routing_attr_avoid_footways_description">Unikaj chodników</string>
<string name="routing_attr_avoid_footways_name">Unikaj chodników</string>
<string name="development">Rozwój</string>
@ -4024,4 +4024,12 @@
<string name="profile_type_osmand_string">Profil OsmAnd</string>
<string name="profile_type_user_string">Profil użytkownika</string>
<string name="reverse_all_points">Odwróć wszystkie punkty</string>
<string name="profile_by_default_description">Wybierz profil, który będzie używany przy uruchomieniu aplikacji.</string>
<string name="shared_string_last_used">Ostatnio używane</string>
<string name="routing_attr_prefer_hiking_routes_description">Preferowane szlaki turystyczne</string>
<string name="routing_attr_prefer_hiking_routes_name">Preferuj szlaki turystyczne</string>
<string name="routing_attr_allow_streams_description">Zezwalaj na strumienie i dreny</string>
<string name="routing_attr_allow_streams_name">Zezwalaj na strumienie i dreny</string>
<string name="routing_attr_allow_intermittent_description">Zezwalaj na przerywane drogi wodne</string>
<string name="routing_attr_allow_intermittent_name">Zezwalaj na przerywane drogi wodne</string>
</resources>

View file

@ -194,7 +194,7 @@
<string name="poi_fuel_biogas">Biogás</string>
<string name="poi_fuel_lh2">Hidrogênio líquido</string>
<string name="poi_fuel_electricity">Eletricidade</string>
<string name="poi_electricity_combined_charging">Eletroposto</string>
<string name="poi_electricity_combined_charging">Eletroposto;Estação de carregamento de veículos elétricos; Estação de carregamento de VE; Ponto de recarga elétrica; Ponto de carga; Estação de recarga eletrônica; Equipamento de abastecimento de veículos elétricos</string>
<string name="poi_vehicle_ramp">Rampa de veículo</string>
<string name="poi_compressed_air">Ar comprimido</string>
<string name="poi_parking">Estacionamento</string>
@ -3898,4 +3898,5 @@
<string name="poi_ranger_station">Posto de guarda florestal</string>
<string name="poi_waste_transfer_station">Estação de transferência de resíduos</string>
<string name="poi_lavoir">Lavandaria pública</string>
<string name="poi_swimming_area">Área de natação</string>
</resources>

View file

@ -3886,4 +3886,5 @@
<string name="poi_lavoir">Умывальник</string>
<string name="poi_waste_transfer_station">Станция перекачки отходов</string>
<string name="poi_ranger_station">Станция рейнджеров</string>
<string name="poi_swimming_area">Место для купания</string>
</resources>

View file

@ -4001,4 +4001,8 @@
<string name="plan_route_split_after">Разделить после</string>
<string name="profile_type_user_string">Профиль пользователя</string>
<string name="profile_type_osmand_string">Профиль OsmAnd</string>
<string name="profile_by_default_description">Выберите профиль, который будет использоваться при запуске приложения.</string>
<string name="shared_string_last_used">Последний раз использовалось</string>
<string name="routing_attr_allow_intermittent_description">Разрешить прерывистые водные пути</string>
<string name="routing_attr_allow_intermittent_name">Разрешить прерывистые водные пути</string>
</resources>

View file

@ -2915,4 +2915,63 @@ Vänligen tillhandahåll fullständig kod</string>
<string name="shared_string_languages">Språk</string>
<string name="shared_string_language">Språk</string>
<string name="shared_string_all_languages">Alla språk</string>
<string name="release_3_9">• La till inställning för att exportera och importera all data, inklusive inställningar, resurser, mina platser
\n
\n • Planera rutt: Grafer för seggraphs for track segments with route, and added the ability to create and edit multiple track segments
\n
\n • Added OAuth authentication method for OpenStreetMap, improved UI of OSM dialogs
\n
\n • Support custom colors for favorites and track waypoints
\n
\n</string>
<string name="reverse_all_points">Vänd alla punkter</string>
<string name="profile_by_default_description">Välj den profil som ska användas när appen startas.</string>
<string name="shared_string_last_used">Senast använd</string>
<string name="routing_attr_prefer_hiking_routes_description">Föredra vandringsleder</string>
<string name="routing_attr_prefer_hiking_routes_name">Föredra vandringsleder</string>
<string name="routing_attr_allow_streams_description">Tillåt strömmar och torrläggningar</string>
<string name="routing_attr_allow_streams_name">Tillåt strömmar och torrläggningar</string>
<string name="routing_attr_allow_intermittent_description">Tillåt vägar över vatten</string>
<string name="routing_attr_allow_intermittent_name">Tillåt vägar över vatten</string>
<string name="gpx_upload_private_visibility_descr">\"Privat\" betyder att spåret inte visas i några offentliga listor, men spårpunkter från det i okronologisk ordning är tillgängliga via det offentliga GPS-API utan tidsstämplar.</string>
<string name="gpx_upload_identifiable_visibility_descr">\"Identifierbart\" betyder att spårningen kommer att visas offentligt i dina GPS-spår och i offentliga GPS-spårningar, dvs. andra användare kommer att kunna komma åt grundata kring spårningen och associera den med ditt användarnamn. Offentliga tidsstämplade tracepoint-data från GPS-API som serveras via trackpoints API refererar till din ursprungliga spårningssida.</string>
<string name="gpx_upload_trackable_visibility_descr">\"Spårbar\" betyder att spåret inte visas i några offentliga listor, men bearbetade spårpunkter tillsammans med respektive tidsstämplar (som inte kan kopplas direkt till dig) görs genom nedladdningar från det offentliga GPS-API: et.</string>
<string name="osm_edit_close_note">Stäng OSM Anteckningar</string>
<string name="osm_edit_comment_note">Kommentera OSM Anteckningar</string>
<string name="osm_login_descr">Du kan logga in antingen med OAuth metoden (högre säkerhet) eller genom att använda ditt användarnamn och lösenord.</string>
<string name="shared_string_add_photo">Lägg till bild</string>
<string name="register_on_openplacereviews">Registrera dig hos
\nOpenPlaceReviews.org</string>
<string name="register_on_openplacereviews_desc">Bilder tillhandahålls av OpenPlaceReviews.org\'s öppen data projekt. För att kunna ladda upp dina bilder behöver du registrera dig på deras webbsida.</string>
<string name="register_opr_create_new_account">Registrera ett nytt konto</string>
<string name="register_opr_have_account">Jag har redan ett konto</string>
<string name="shared_string_search_history">Sökhistorik</string>
<string name="app_mode_kayak">Kajak</string>
<string name="app_mode_motorboat">Motorbåt</string>
<string name="cannot_upload_image">Uppladdning av bild misslyckades, vänligen försök igen senare</string>
<string name="select_picture">Välj bild</string>
<string name="shared_string_resources">Resurser</string>
<string name="approximate_file_size">Uppskattad filstorlek</string>
<string name="select_data_to_export">Vänligen välj vilken data som önskas exporteras till filen.</string>
<string name="file_size_needed_for_import">Erfordras för importering</string>
<string name="export_not_enough_space_descr">Din enhet har bara %1$s ledigt lagringsutrymme. Vänligen frigör utrymme alternativt avmarkera några föremål som skall exporteras.</string>
<string name="export_not_enough_space">Det finns inte tillräckligt med lagringsutrymme</string>
<string name="select_items_for_import">Välj vilka föremål som skall importeras.</string>
<string name="select_groups_for_import">Välj vilka grupper som skall importeras.</string>
<string name="add_to_mapillary">Lägg till Mapillary</string>
<string name="add_to_opr">Lägg till ÖppnaPlattsRecensioner (OPR)</string>
<string name="use_dev_url_descr">Byt till att använda dev.openstreetmap.org istället för openstreetmap.org för att testa uppladdning av OSM Anteckningar / Sevärdheter / GPX.</string>
<string name="use_dev_url">Använd dev.openstreetmap.org</string>
<string name="app_mode_light_aircraft">Mindre flyplan</string>
<string name="add_photos_descr">OsmAnd visar bilder från flertalet källor:
\nOpenPlaceReviews - Foton av sevärdheter;
\nMapillary - Bilder på gatunivå;
\nWeb/Wikimedia - Foton av sevärdheter enligt OpenStreetMap-data.</string>
<string name="elevation_data">Du kan använda höjddata för att ta hänsyn till upp- / nedstigning på din resa</string>
<string name="plan_route_join_segments">Sammanslå segment</string>
<string name="plan_route_split_before">Dela innan</string>
<string name="plan_route_split_after">Dela efter</string>
<string name="plan_route_add_new_segment">Lägg till ett nytt segment</string>
<string name="profile_type_osmand_string">OsmAnd profil</string>
<string name="profile_type_user_string">Användarprofil</string>
</resources>

View file

@ -198,7 +198,7 @@
<string name="poi_fuel_e85">85% Етанолу (Е85)</string>
<string name="poi_fuel_lh2">Рідкий водень</string>
<string name="poi_fuel_electricity">Електроенергія</string>
<string name="poi_electricity_combined_charging">Станція зарядки</string>
<string name="poi_electricity_combined_charging">Зарядна станція;Зарядна станція для електромобіля;Зарядна станція для електромобілів;точка підзарядки електроенергією;Точка зарядки; Електронна зарядна станція;Обладнання для електромобілів</string>
<string name="poi_vehicle_ramp">Рампа для огляду/перевірки машин</string>
<string name="poi_compressed_air">Стиснене повітря</string>
<string name="poi_parking">Стоянка</string>
@ -3886,4 +3886,5 @@
<string name="poi_weightbridge">Автомобільні ваги</string>
<string name="poi_lavoir">Громадська пральня</string>
<string name="poi_waste_transfer_station">Станція перевезення відходів</string>
<string name="poi_swimming_area">Плавальний майданчик</string>
</resources>

View file

@ -3897,4 +3897,5 @@
<string name="poi_ranger_station">護林員站</string>
<string name="poi_lavoir">公共洗衣區</string>
<string name="poi_waste_transfer_station">垃圾站</string>
<string name="poi_swimming_area">游泳區</string>
</resources>

View file

@ -384,7 +384,7 @@
<string name="poi_fuel_electricity">Electricity</string>
<string name="poi_aeroway_fuel">Aircraft fuel station</string>
<string name="poi_waterway_fuel">Gas station for boats</string>
<string name="poi_electricity_combined_charging">Charging station</string>
<string name="poi_electricity_combined_charging">Charging station;Electric vehicle charging station;EV charging station;Electric recharging point;Charge point;Electronic charging station;Electric vehicle supply equipment</string>
<string name="poi_fuel_wood">Fuel: wood</string>
<string name="poi_fuel_charcoal">Fuel: charcoal</string>
<string name="poi_fuel_coal">Fuel: coal</string>
@ -4326,5 +4326,11 @@
<string name="poi_lavoir">Lavoir</string>
<string name="poi_swimming_area">Swimming area</string>
<string name="poi_wildlife_crossing">Wildlife crossing</string>
<string name="poi_wildlife_crossing_bat_bridge">Bat bridge</string>
<string name="poi_wildlife_crossing_bat_tunnel">Bat tunnel</string>
</resources>

View file

@ -43,6 +43,7 @@ import net.osmand.plus.myplaces.TrackPointFragment;
import net.osmand.plus.myplaces.TrackSegmentFragment;
import net.osmand.plus.settings.backend.OsmAndAppCustomization;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.track.TrackDisplayHelper;
import net.osmand.plus.views.AddGpxPointBottomSheetHelper.NewGpxPoint;
import java.io.File;
@ -57,18 +58,14 @@ public class TrackActivity extends TabActivity {
public static final String OPEN_TRACKS_LIST = "OPEN_TRACKS_LIST";
public static final String CURRENT_RECORDING = "CURRENT_RECORDING";
public static final String SHOW_TEMPORARILY = "SHOW_TEMPORARILY";
protected List<WeakReference<Fragment>> fragList = new ArrayList<>();
private OsmandApplication app;
private TrackDisplayHelper displayHelper;
private TrackBitmapDrawer trackBitmapDrawer;
private File file = null;
private GPXFile gpxFile;
private GpxDataItem gpxDataItem;
private LockableViewPager viewPager;
private long modifiedTime = -1;
private final List<WeakReference<Fragment>> fragList = new ArrayList<>();
private List<GpxDisplayGroup> displayGroups;
private List<GpxDisplayGroup> originalGroups = new ArrayList<>();
private boolean stopped = false;
private boolean openPointsTab = false;
private boolean openTracksList = false;
@ -85,14 +82,15 @@ public class TrackActivity extends TabActivity {
finish();
return;
}
displayHelper = new TrackDisplayHelper(app);
if (intent.hasExtra(TRACK_FILE_NAME)) {
file = new File(intent.getStringExtra(TRACK_FILE_NAME));
displayHelper.setFile(new File(intent.getStringExtra(TRACK_FILE_NAME)));
}
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
if (file != null) {
String fn = file.getName().replace(IndexConstants.GPX_FILE_EXT, "").replace("/", " ").replace("_", " ");
if (getFile() != null) {
String fn = getFile().getName().replace(IndexConstants.GPX_FILE_EXT, "").replace("/", " ").replace("_", " ");
actionBar.setTitle(fn);
} else {
actionBar.setTitle(getString(R.string.shared_string_currently_recording_track));
@ -109,11 +107,19 @@ public class TrackActivity extends TabActivity {
setContentView(R.layout.track_content);
}
public TrackDisplayHelper getDisplayHelper() {
return displayHelper;
}
@Nullable
public TrackBitmapDrawer getTrackBitmapDrawer() {
return trackBitmapDrawer;
}
public File getFile() {
return displayHelper.getFile();
}
public void addPoint(PointDescription pointDescription) {
Intent currentIntent = getIntent();
if (currentIntent != null) {
@ -122,7 +128,7 @@ public class TrackActivity extends TabActivity {
final OsmandSettings settings = app.getSettings();
GPXFile gpx = getGpx();
LatLon location = settings.getLastKnownMapLocation();
QuadRect rect = getRect();
QuadRect rect = displayHelper.getRect();
NewGpxPoint newGpxPoint = new NewGpxPoint(gpx, pointDescription, rect);
if (gpx != null && location != null) {
settings.setMapLocationToShow(location.getLatitude(), location.getLongitude(),
@ -152,53 +158,20 @@ public class TrackActivity extends TabActivity {
}
}
public QuadRect getRect() {
if (getGpx() != null) {
return getGpx().getRect();
} else {
return new QuadRect(0, 0, 0, 0);
}
}
protected void setGpxDataItem(GpxDataItem gpxDataItem) {
this.gpxDataItem = gpxDataItem;
displayHelper.setGpxDataItem(gpxDataItem);
}
protected void setGpx(GPXFile result) {
this.gpxFile = result;
if (file == null) {
this.gpxFile = getMyApplication().getSavingTrackHelper().getCurrentGpx();
}
displayHelper.setGpx(result);
}
public List<GpxDisplayGroup> getGpxFile(boolean useDisplayGroups) {
if (gpxFile == null) {
return new ArrayList<>();
}
if (gpxFile.modifiedTime != modifiedTime) {
modifiedTime = gpxFile.modifiedTime;
GpxSelectionHelper selectedGpxHelper = ((OsmandApplication) getApplication()).getSelectedGpxHelper();
displayGroups = selectedGpxHelper.collectDisplayGroups(gpxFile);
originalGroups.clear();
for (GpxDisplayGroup g : displayGroups) {
originalGroups.add(g.cloneInstance());
}
if (file != null) {
SelectedGpxFile sf = selectedGpxHelper.getSelectedFileByPath(gpxFile.path);
if (sf != null && file != null && sf.getDisplayGroups(app) != null) {
displayGroups = sf.getDisplayGroups(app);
}
}
}
if (useDisplayGroups) {
return displayGroups;
} else {
return originalGroups;
}
return displayHelper.getGpxFile(useDisplayGroups);
}
@Override
public void onAttachFragment(Fragment fragment) {
public void onAttachFragment(@NonNull Fragment fragment) {
fragList.add(new WeakReference<>(fragment));
if (trackBitmapDrawer != null && fragment instanceof TrackBitmapDrawerListener) {
trackBitmapDrawer.addListener((TrackBitmapDrawerListener) fragment);
@ -218,7 +191,7 @@ public class TrackActivity extends TabActivity {
}
@Override
protected void onSaveInstanceState(Bundle outState) {
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
if (viewPager.getCurrentItem() == 1) {
outState.putBoolean(OPEN_POINTS_TAB, true);
@ -266,15 +239,6 @@ public class TrackActivity extends TabActivity {
return false;
}
public void updateSplitView() {
for (WeakReference<Fragment> f : fragList) {
Fragment frag = f.get();
if (frag instanceof TrackSegmentFragment) {
((TrackSegmentFragment) frag).updateSplitView();
}
}
}
public void updateHeader(Fragment sender) {
for (WeakReference<Fragment> f : fragList) {
Fragment frag = f.get();
@ -323,23 +287,23 @@ public class TrackActivity extends TabActivity {
@Nullable
public GPXFile getGpx() {
return gpxFile;
return displayHelper.getGpx();
}
@Nullable
public GpxDataItem getGpxDataItem() {
return gpxDataItem;
return displayHelper.getGpxDataItem();
}
private void onGPXFileReady(@Nullable GPXFile gpxFile) {
public void onGPXFileReady(@Nullable GPXFile gpxFile) {
setGpx(gpxFile);
setGpxDataItem(file != null ? app.getGpxDbHelper().getItem(file) : null);
setGpxDataItem(getFile() != null ? app.getGpxDbHelper().getItem(getFile()) : null);
WindowManager mgr = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
if (gpxFile != null && mgr != null) {
DisplayMetrics dm = new DisplayMetrics();
mgr.getDefaultDisplay().getMetrics(dm);
trackBitmapDrawer = new TrackBitmapDrawer(app, gpxFile, getGpxDataItem(), getRect(), dm.density, dm.widthPixels, AndroidUtils.dpToPx(app, 152f));
trackBitmapDrawer = new TrackBitmapDrawer(app, gpxFile, getGpxDataItem(), displayHelper.getRect(), dm.density, dm.widthPixels, AndroidUtils.dpToPx(app, 152f));
}
for (WeakReference<Fragment> f : fragList) {
@ -406,23 +370,6 @@ public class TrackActivity extends TabActivity {
}
}
public boolean setJoinSegments(boolean joinSegments) {
if (gpxDataItem != null) {
boolean updated = app.getGpxDbHelper().updateJoinSegments(gpxDataItem, joinSegments);
SelectedGpxFile selectedGpxFile = app.getSelectedGpxHelper().getSelectedFileByPath(gpxFile.path);
if (updated && selectedGpxFile != null) {
selectedGpxFile.setJoinSegments(joinSegments);
}
return updated;
}
return false;
}
public boolean isJoinSegments() {
return gpxDataItem != null && gpxDataItem.isJoinSegments();
}
private static class GPXFileLoaderTask extends AsyncTask<Void, Void, GPXFile> {
private OsmandApplication app;
@ -437,7 +384,7 @@ public class TrackActivity extends TabActivity {
GPXFileLoaderTask(@NonNull TrackActivity activity) {
this.activityRef = new WeakReference<>(activity);
app = activity.getMyApplication();
file = activity.file;
file = activity.getDisplayHelper().getFile();
}
protected void onPreExecute() {
@ -486,11 +433,11 @@ public class TrackActivity extends TabActivity {
if (activity != null) {
activity.setSupportProgressBarIndeterminateVisibility(false);
if (result != null) {
final GpxSelectionHelper helper = app.getSelectedGpxHelper();
GpxSelectionHelper helper = app.getSelectedGpxHelper();
if (showTemporarily) {
helper.selectGpxFile(result, false, false);
} else {
final SelectedGpxFile selectedGpx = helper.getSelectedFileByPath(result.path);
SelectedGpxFile selectedGpx = helper.getSelectedFileByPath(result.path);
if (selectedGpx != null && result.error == null) {
selectedGpx.setGpxFile(result, app);
}

View file

@ -70,7 +70,7 @@ public abstract class ContextMenuFragment extends BaseOsmAndFragment
private OnLayoutChangeListener containerLayoutListener;
private View topShadow;
private ViewGroup topView;
private View bottomScrollView;
private ViewGroup bottomScrollView;
private LinearLayout cardsContainer;
private FrameLayout bottomContainer;
@ -259,7 +259,7 @@ public abstract class ContextMenuFragment extends BaseOsmAndFragment
return bottomContainer;
}
public View getBottomScrollView() {
public ViewGroup getBottomScrollView() {
return bottomScrollView;
}

View file

@ -17,6 +17,7 @@ import net.osmand.plus.GpxSelectionHelper;
import net.osmand.plus.GpxSelectionHelper.SelectedGpxFile;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.Version;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.activities.TrackActivity;
import net.osmand.plus.helpers.GpxUiHelper;
@ -25,6 +26,7 @@ import net.osmand.plus.mapcontextmenu.builders.SelectedGpxMenuBuilder;
import net.osmand.plus.myplaces.SaveCurrentTrackTask;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.track.SaveGpxAsyncTask.SaveGpxListener;
import net.osmand.plus.track.TrackMenuFragment;
import net.osmand.util.Algorithms;
import java.io.File;
@ -45,15 +47,21 @@ public class SelectedGpxMenuController extends MenuController {
leftTitleButtonController = new TitleButtonController() {
@Override
public void buttonPressed() {
Intent intent = new Intent(mapActivity, mapActivity.getMyApplication().getAppCustomization().getTrackActivity());
OsmandApplication app = mapActivity.getMyApplication();
SelectedGpxFile selectedGpxFile = selectedGpxPoint.getSelectedGpxFile();
if (selectedGpxFile.isShowCurrentTrack()) {
intent.putExtra(TrackActivity.CURRENT_RECORDING, true);
if (Version.isDeveloperVersion(app)) {
mapActivity.getContextMenu().hide(false);
TrackMenuFragment.showInstance(mapActivity, selectedGpxFile.getGpxFile().path, selectedGpxFile.isShowCurrentTrack());
} else {
intent.putExtra(TrackActivity.TRACK_FILE_NAME, selectedGpxFile.getGpxFile().path);
Intent intent = new Intent(mapActivity, mapActivity.getMyApplication().getAppCustomization().getTrackActivity());
if (selectedGpxFile.isShowCurrentTrack()) {
intent.putExtra(TrackActivity.CURRENT_RECORDING, true);
} else {
intent.putExtra(TrackActivity.TRACK_FILE_NAME, selectedGpxFile.getGpxFile().path);
}
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
mapActivity.startActivity(intent);
}
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
mapActivity.startActivity(intent);
}
};
leftTitleButtonController.caption = mapActivity.getString(R.string.shared_string_open_track);

View file

@ -427,7 +427,7 @@ public class MapMarkersGroupsAdapter extends RecyclerView.Adapter<RecyclerView.V
@Override
public void onClick(View v) {
if (mapActivity.getSupportFragmentManager() != null) {
WikivoyageArticleDialogFragment.showInstanceByTitle(app, mapActivity.getSupportFragmentManager(), article.getTitle(), article.getLang());
WikivoyageArticleDialogFragment.showInstance(app, mapActivity.getSupportFragmentManager(), article.generateIdentifier(), article.getLang());
}
}
};

View file

@ -1211,7 +1211,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
SelectedGpxFile selectedGpxFile = mapActivity.getMyApplication().getSelectedGpxHelper()
.getSelectedFileByPath(gpxFile.path);
boolean showOnMap = selectedGpxFile != null;
saveExistingGpx(gpxFile, showOnMap, false, true, FinalSaveAction.SHOW_TOAST);
saveExistingGpx(gpxFile, showOnMap, false, true, FinalSaveAction.SHOW_IS_SAVED_FRAGMENT);
}
}

View file

@ -14,6 +14,7 @@ import net.osmand.GPXUtilities.Route;
import net.osmand.GPXUtilities.Track;
import net.osmand.GPXUtilities.TrkSegment;
import net.osmand.GPXUtilities.WptPt;
import net.osmand.GPXUtilities.Metadata;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.Version;
@ -91,6 +92,10 @@ class SaveGpxRouteAsyncTask extends AsyncTask<Void, Void, Exception> {
backupFile = FileUtils.backupFile(app, outFile);
String trackName = Algorithms.getFileNameWithoutExtension(outFile);
GPXFile gpx = generateGpxFile(measurementLayer, editingCtx, trackName, gpxFile);
if (gpxFile.metadata != null) {
gpx.metadata = new Metadata();
gpx.metadata.getExtensionsToWrite().putAll(gpxFile.metadata.getExtensionsToRead());
}
if (!gpx.showCurrentTrack) {
res = GPXUtilities.writeGpxFile(outFile, gpx);
}

View file

@ -0,0 +1,88 @@
package net.osmand.plus.myplaces;
import android.os.AsyncTask;
import net.osmand.GPXUtilities;
import net.osmand.GPXUtilities.GPXFile;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayItemType;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.activities.SavingTrackHelper;
import net.osmand.plus.mapmarkers.MapMarkersGroup;
import net.osmand.plus.mapmarkers.MapMarkersHelper;
import java.io.File;
import java.lang.ref.WeakReference;
import java.util.Set;
class DeletePointsTask extends AsyncTask<Void, Void, Void> {
private OsmandApplication app;
private GPXFile gpx;
private Set<GpxDisplayItem> selectedItems;
private WeakReference<OnPointsDeleteListener> listenerRef;
DeletePointsTask(OsmandApplication app, GPXFile gpxFile, Set<GpxDisplayItem> selectedItems, OnPointsDeleteListener listener) {
this.app = app;
this.gpx = gpxFile;
this.selectedItems = selectedItems;
this.listenerRef = new WeakReference<>(listener);
}
@Override
protected void onPreExecute() {
OnPointsDeleteListener listener = listenerRef.get();
if (listener != null) {
listener.onPointsDeletionStarted();
}
}
@Override
protected Void doInBackground(Void... params) {
SavingTrackHelper savingTrackHelper = app.getSavingTrackHelper();
if (gpx != null) {
for (GpxDisplayItem item : selectedItems) {
if (gpx.showCurrentTrack) {
savingTrackHelper.deletePointData(item.locationStart);
} else {
if (item.group.getType() == GpxDisplayItemType.TRACK_POINTS) {
gpx.deleteWptPt(item.locationStart);
} else if (item.group.getType() == GpxDisplayItemType.TRACK_ROUTE_POINTS) {
gpx.deleteRtePt(item.locationStart);
}
}
}
if (!gpx.showCurrentTrack) {
GPXUtilities.writeGpxFile(new File(gpx.path), gpx);
boolean selected = app.getSelectedGpxHelper().getSelectedFileByPath(gpx.path) != null;
if (selected) {
app.getSelectedGpxHelper().setGpxFileToDisplay(gpx);
}
}
syncGpx(gpx);
}
return null;
}
private void syncGpx(GPXFile gpxFile) {
MapMarkersHelper helper = app.getMapMarkersHelper();
MapMarkersGroup group = helper.getMarkersGroup(gpxFile);
if (group != null) {
helper.runSynchronization(group);
}
}
@Override
protected void onPostExecute(Void aVoid) {
OnPointsDeleteListener listener = listenerRef.get();
if (listener != null) {
listener.onPointsDeleted();
}
}
public interface OnPointsDeleteListener {
void onPointsDeletionStarted();
void onPointsDeleted();
}
}

View file

@ -0,0 +1,695 @@
package net.osmand.plus.myplaces;
import android.annotation.SuppressLint;
import android.graphics.Matrix;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.SwitchCompat;
import androidx.viewpager.widget.PagerAdapter;
import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
import com.github.mikephil.charting.listener.ChartTouchListener.ChartGesture;
import com.github.mikephil.charting.listener.OnChartGestureListener;
import com.github.mikephil.charting.listener.OnChartValueSelectedListener;
import net.osmand.GPXUtilities;
import net.osmand.GPXUtilities.GPXFile;
import net.osmand.GPXUtilities.GPXTrackAnalysis;
import net.osmand.GPXUtilities.Track;
import net.osmand.GPXUtilities.TrkSegment;
import net.osmand.GPXUtilities.WptPt;
import net.osmand.plus.GPXDatabase.GpxDataItem;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem;
import net.osmand.plus.OsmAndFormatter;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.UiUtilities;
import net.osmand.plus.helpers.AndroidUiHelper;
import net.osmand.plus.helpers.GpxUiHelper;
import net.osmand.plus.helpers.GpxUiHelper.GPXDataSetAxisType;
import net.osmand.plus.helpers.GpxUiHelper.LineGraphType;
import net.osmand.plus.helpers.GpxUiHelper.OrderedLineDataSet;
import net.osmand.plus.track.TrackDisplayHelper;
import net.osmand.plus.views.controls.PagerSlidingTabStrip;
import net.osmand.plus.views.controls.PagerSlidingTabStrip.CustomTabProvider;
import net.osmand.plus.views.controls.WrapContentHeightViewPager.ViewAtPositionInterface;
import net.osmand.util.Algorithms;
import net.osmand.util.MapUtils;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static net.osmand.plus.helpers.GpxUiHelper.LineGraphType.ALTITUDE;
import static net.osmand.plus.helpers.GpxUiHelper.LineGraphType.SLOPE;
import static net.osmand.plus.helpers.GpxUiHelper.LineGraphType.SPEED;
public class GPXItemPagerAdapter extends PagerAdapter implements CustomTabProvider, ViewAtPositionInterface {
private OsmandApplication app;
private UiUtilities iconsCache;
private TrackDisplayHelper displayHelper;
private Map<GPXTabItemType, List<ILineDataSet>> dataSetsMap = new HashMap<>();
private WptPt selectedWpt;
private TrkSegment segment;
private GpxDisplayItem gpxItem;
private GPXTabItemType[] tabTypes;
private PagerSlidingTabStrip tabs;
private SparseArray<View> views = new SparseArray<>();
private SegmentActionsListener actionsListener;
private boolean chartClicked;
public GPXItemPagerAdapter(@NonNull PagerSlidingTabStrip tabs,
@NonNull GpxDisplayItem gpxItem,
@NonNull TrackDisplayHelper displayHelper,
@NonNull SegmentActionsListener actionsListener) {
super();
this.tabs = tabs;
this.gpxItem = gpxItem;
this.displayHelper = displayHelper;
this.actionsListener = actionsListener;
app = (OsmandApplication) tabs.getContext().getApplicationContext();
iconsCache = app.getUIUtilities();
fetchTabTypes();
}
private void fetchTabTypes() {
List<GPXTabItemType> tabTypeList = new ArrayList<>();
tabTypeList.add(GPXTabItemType.GPX_TAB_ITEM_GENERAL);
if (gpxItem != null && gpxItem.analysis != null) {
if (gpxItem.analysis.hasElevationData) {
tabTypeList.add(GPXTabItemType.GPX_TAB_ITEM_ALTITUDE);
}
if (gpxItem.analysis.isSpeedSpecified()) {
tabTypeList.add(GPXTabItemType.GPX_TAB_ITEM_SPEED);
}
}
tabTypes = tabTypeList.toArray(new GPXTabItemType[0]);
}
private List<ILineDataSet> getDataSets(LineChart chart, GPXTabItemType tabType,
LineGraphType firstType, LineGraphType secondType) {
List<ILineDataSet> dataSets = dataSetsMap.get(tabType);
if (dataSets == null && chart != null) {
GPXTrackAnalysis analysis = gpxItem.analysis;
GpxDataItem gpxDataItem = displayHelper.getGpxDataItem();
boolean calcWithoutGaps = gpxItem.isGeneralTrack() && gpxDataItem != null && !gpxDataItem.isJoinSegments();
dataSets = GpxUiHelper.getDataSets(chart, app, analysis, firstType, secondType, calcWithoutGaps);
dataSetsMap.put(tabType, dataSets);
}
return dataSets;
}
private TrkSegment getTrackSegment(LineChart chart) {
if (segment == null) {
LineData lineData = chart.getLineData();
List<ILineDataSet> ds = lineData != null ? lineData.getDataSets() : null;
if (ds != null && ds.size() > 0) {
for (GPXUtilities.Track t : gpxItem.group.getGpx().tracks) {
for (TrkSegment s : t.segments) {
if (s.points.size() > 0 && s.points.get(0).equals(gpxItem.analysis.locationStart)) {
segment = s;
break;
}
}
if (segment != null) {
break;
}
}
}
}
return segment;
}
private WptPt getPoint(LineChart chart, float pos) {
WptPt wpt = null;
LineData lineData = chart.getLineData();
List<ILineDataSet> ds = lineData != null ? lineData.getDataSets() : null;
if (ds != null && ds.size() > 0) {
TrkSegment segment = getTrackSegment(chart);
OrderedLineDataSet dataSet = (OrderedLineDataSet) ds.get(0);
if (gpxItem.chartAxisType == GPXDataSetAxisType.TIME) {
float time = pos * 1000;
for (WptPt p : segment.points) {
if (p.time - gpxItem.analysis.startTime >= time) {
wpt = p;
break;
}
}
} else {
float distance = pos * dataSet.getDivX();
double totalDistance = 0;
for (int i = 0; i < segment.points.size(); i++) {
WptPt currentPoint = segment.points.get(i);
if (i != 0) {
WptPt previousPoint = segment.points.get(i - 1);
totalDistance += MapUtils.getDistance(previousPoint.lat, previousPoint.lon, currentPoint.lat, currentPoint.lon);
}
if (currentPoint.distance >= distance || Math.abs(totalDistance - distance) < 0.1) {
wpt = currentPoint;
break;
}
}
}
}
return wpt;
}
@Override
public int getCount() {
return tabTypes.length;
}
@Override
public CharSequence getPageTitle(int position) {
return tabTypes[position].toHumanString(app);
}
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
GPXTabItemType tabType = tabTypes[position];
View view = getViewForTab(container, tabType);
GPXFile gpxFile = displayHelper.getGpx();
if (gpxFile != null && gpxItem != null) {
GPXTrackAnalysis analysis = gpxItem.analysis;
LineChart chart = view.findViewById(R.id.chart);
setupChart(view, chart);
switch (tabType) {
case GPX_TAB_ITEM_GENERAL:
setupGeneralTab(view, chart, analysis, gpxFile, position);
break;
case GPX_TAB_ITEM_ALTITUDE:
setupAltitudeTab(view, chart, analysis, gpxFile, position);
break;
case GPX_TAB_ITEM_SPEED:
setupSpeedTab(view, chart, analysis, gpxFile, position);
break;
}
}
container.addView(view, 0);
views.put(position, view);
return view;
}
private View getViewForTab(@NonNull ViewGroup container, @NonNull GPXTabItemType tabType) {
LayoutInflater inflater = LayoutInflater.from(container.getContext());
switch (tabType) {
case GPX_TAB_ITEM_ALTITUDE:
return inflater.inflate(R.layout.gpx_item_altitude, container, false);
case GPX_TAB_ITEM_SPEED:
return inflater.inflate(R.layout.gpx_item_speed, container, false);
case GPX_TAB_ITEM_GENERAL:
default:
return inflater.inflate(R.layout.gpx_item_general, container, false);
}
}
private void setupSpeedTab(View view, LineChart chart, GPXTrackAnalysis analysis, GPXFile gpxFile, int position) {
if (analysis != null && analysis.isSpeedSpecified()) {
if (analysis.hasSpeedData) {
GpxUiHelper.setupGPXChart(app, chart, 4);
chart.setData(new LineData(getDataSets(chart, GPXTabItemType.GPX_TAB_ITEM_SPEED, SPEED, null)));
updateChart(chart);
chart.setVisibility(View.VISIBLE);
} else {
chart.setVisibility(View.GONE);
}
((ImageView) view.findViewById(R.id.average_icon))
.setImageDrawable(iconsCache.getThemedIcon(R.drawable.ic_action_speed));
((ImageView) view.findViewById(R.id.max_icon))
.setImageDrawable(iconsCache.getThemedIcon(R.drawable.ic_action_max_speed));
((ImageView) view.findViewById(R.id.time_moving_icon))
.setImageDrawable(iconsCache.getThemedIcon(R.drawable.ic_action_time_span));
((ImageView) view.findViewById(R.id.distance_icon))
.setImageDrawable(iconsCache.getThemedIcon(R.drawable.ic_action_polygom_dark));
String avg = OsmAndFormatter.getFormattedSpeed(analysis.avgSpeed, app);
String max = OsmAndFormatter.getFormattedSpeed(analysis.maxSpeed, app);
((TextView) view.findViewById(R.id.average_text)).setText(avg);
((TextView) view.findViewById(R.id.max_text)).setText(max);
view.findViewById(R.id.gpx_join_gaps_container).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (displayHelper.setJoinSegments(!displayHelper.isJoinSegments())) {
for (int i = 0; i < getCount(); i++) {
View view = getViewAtPosition(i);
updateJoinGapsInfo(view, i);
}
}
}
});
} else {
chart.setVisibility(View.GONE);
view.findViewById(R.id.average_max).setVisibility(View.GONE);
view.findViewById(R.id.list_divider).setVisibility(View.GONE);
view.findViewById(R.id.time_distance).setVisibility(View.GONE);
}
updateJoinGapsInfo(view, position);
view.findViewById(R.id.analyze_on_map).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
openAnalyzeOnMap(GPXTabItemType.GPX_TAB_ITEM_SPEED);
}
});
if (gpxFile.showCurrentTrack) {
view.findViewById(R.id.split_interval).setVisibility(View.GONE);
} else {
view.findViewById(R.id.split_interval).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
openSplitIntervalScreen();
}
});
}
ImageView overflowMenu = view.findViewById(R.id.overflow_menu);
if (!gpxItem.group.getTrack().generalTrack) {
setupOptionsPopupMenu(overflowMenu, false);
} else {
overflowMenu.setVisibility(View.GONE);
}
}
private void setupOptionsPopupMenu(ImageView overflowMenu, final boolean confirmDeletion) {
overflowMenu.setImageDrawable(iconsCache.getThemedIcon(R.drawable.ic_overflow_menu_white));
overflowMenu.setVisibility(View.VISIBLE);
overflowMenu.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
actionsListener.showOptionsPopupMenu(view, getTrkSegment(), confirmDeletion);
}
});
}
private void setupAltitudeTab(View view, LineChart chart, GPXTrackAnalysis analysis, GPXFile gpxFile, int position) {
if (analysis != null) {
if (analysis.hasElevationData) {
GpxUiHelper.setupGPXChart(app, chart, 4);
chart.setData(new LineData(getDataSets(chart, GPXTabItemType.GPX_TAB_ITEM_ALTITUDE, ALTITUDE, SLOPE)));
updateChart(chart);
chart.setVisibility(View.VISIBLE);
} else {
chart.setVisibility(View.GONE);
}
((ImageView) view.findViewById(R.id.average_icon))
.setImageDrawable(iconsCache.getThemedIcon(R.drawable.ic_action_altitude_average));
((ImageView) view.findViewById(R.id.range_icon))
.setImageDrawable(iconsCache.getThemedIcon(R.drawable.ic_action_altitude_average));
((ImageView) view.findViewById(R.id.ascent_icon))
.setImageDrawable(iconsCache.getThemedIcon(R.drawable.ic_action_altitude_ascent));
((ImageView) view.findViewById(R.id.descent_icon))
.setImageDrawable(iconsCache.getThemedIcon(R.drawable.ic_action_altitude_descent));
String min = OsmAndFormatter.getFormattedAlt(analysis.minElevation, app);
String max = OsmAndFormatter.getFormattedAlt(analysis.maxElevation, app);
String asc = OsmAndFormatter.getFormattedAlt(analysis.diffElevationUp, app);
String desc = OsmAndFormatter.getFormattedAlt(analysis.diffElevationDown, app);
((TextView) view.findViewById(R.id.average_text))
.setText(OsmAndFormatter.getFormattedAlt(analysis.avgElevation, app));
((TextView) view.findViewById(R.id.range_text)).setText(String.format("%s - %s", min, max));
((TextView) view.findViewById(R.id.ascent_text)).setText(asc);
((TextView) view.findViewById(R.id.descent_text)).setText(desc);
view.findViewById(R.id.gpx_join_gaps_container).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (displayHelper.setJoinSegments(!displayHelper.isJoinSegments())) {
for (int i = 0; i < getCount(); i++) {
View view = getViewAtPosition(i);
updateJoinGapsInfo(view, i);
}
}
}
});
} else {
chart.setVisibility(View.GONE);
view.findViewById(R.id.average_range).setVisibility(View.GONE);
view.findViewById(R.id.list_divider).setVisibility(View.GONE);
view.findViewById(R.id.ascent_descent).setVisibility(View.GONE);
}
updateJoinGapsInfo(view, position);
view.findViewById(R.id.analyze_on_map).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
openAnalyzeOnMap(GPXTabItemType.GPX_TAB_ITEM_ALTITUDE);
}
});
if (gpxFile.showCurrentTrack) {
view.findViewById(R.id.split_interval).setVisibility(View.GONE);
} else {
view.findViewById(R.id.split_interval).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
openSplitIntervalScreen();
}
});
}
ImageView overflowMenu = view.findViewById(R.id.overflow_menu);
if (!gpxItem.group.getTrack().generalTrack) {
setupOptionsPopupMenu(overflowMenu, false);
} else {
overflowMenu.setVisibility(View.GONE);
}
}
private void setupGeneralTab(View view, LineChart chart, GPXTrackAnalysis analysis, GPXFile gpxFile, int position) {
if (analysis != null) {
if (analysis.hasElevationData || analysis.hasSpeedData) {
GpxUiHelper.setupGPXChart(app, chart, 4);
chart.setData(new LineData(getDataSets(chart, GPXTabItemType.GPX_TAB_ITEM_GENERAL, ALTITUDE, SPEED)));
updateChart(chart);
chart.setVisibility(View.VISIBLE);
} else {
chart.setVisibility(View.GONE);
}
((ImageView) view.findViewById(R.id.distance_icon))
.setImageDrawable(iconsCache.getThemedIcon(R.drawable.ic_action_polygom_dark));
((ImageView) view.findViewById(R.id.duration_icon))
.setImageDrawable(iconsCache.getThemedIcon(R.drawable.ic_action_time_span));
((ImageView) view.findViewById(R.id.start_time_icon))
.setImageDrawable(iconsCache.getThemedIcon(R.drawable.ic_action_time_start));
((ImageView) view.findViewById(R.id.end_time_icon))
.setImageDrawable(iconsCache.getThemedIcon(R.drawable.ic_action_time_end));
view.findViewById(R.id.gpx_join_gaps_container).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (displayHelper.setJoinSegments(!displayHelper.isJoinSegments())) {
actionsListener.updateContent();
for (int i = 0; i < getCount(); i++) {
View view = getViewAtPosition(i);
updateJoinGapsInfo(view, i);
}
}
}
});
if (analysis.timeSpan > 0) {
DateFormat tf = SimpleDateFormat.getTimeInstance(DateFormat.SHORT);
DateFormat df = SimpleDateFormat.getDateInstance(DateFormat.MEDIUM);
Date start = new Date(analysis.startTime);
((TextView) view.findViewById(R.id.start_time_text)).setText(tf.format(start));
((TextView) view.findViewById(R.id.start_date_text)).setText(df.format(start));
Date end = new Date(analysis.endTime);
((TextView) view.findViewById(R.id.end_time_text)).setText(tf.format(end));
((TextView) view.findViewById(R.id.end_date_text)).setText(df.format(end));
} else {
view.findViewById(R.id.list_divider).setVisibility(View.GONE);
view.findViewById(R.id.start_end_time).setVisibility(View.GONE);
}
} else {
chart.setVisibility(View.GONE);
view.findViewById(R.id.distance_time_span).setVisibility(View.GONE);
view.findViewById(R.id.list_divider).setVisibility(View.GONE);
view.findViewById(R.id.start_end_time).setVisibility(View.GONE);
}
updateJoinGapsInfo(view, position);
view.findViewById(R.id.analyze_on_map).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
openAnalyzeOnMap(GPXTabItemType.GPX_TAB_ITEM_GENERAL);
}
});
if (gpxFile.showCurrentTrack) {
view.findViewById(R.id.split_interval).setVisibility(View.GONE);
} else {
view.findViewById(R.id.split_interval).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
openSplitIntervalScreen();
}
});
}
ImageView overflowMenu = view.findViewById(R.id.overflow_menu);
if (!gpxItem.group.getTrack().generalTrack) {
setupOptionsPopupMenu(overflowMenu, true);
} else {
overflowMenu.setVisibility(View.GONE);
}
}
private void setupChart(final View view, final LineChart chart) {
chart.setHighlightPerDragEnabled(chartClicked);
chart.setOnClickListener(new View.OnClickListener() {
@SuppressLint("ClickableViewAccessibility")
@Override
public void onClick(View view) {
if (!chartClicked) {
chartClicked = true;
if (selectedWpt != null) {
actionsListener.onPointSelected(selectedWpt.lat, selectedWpt.lon);
}
}
}
});
chart.setOnTouchListener(new View.OnTouchListener() {
private float listViewYPos;
@Override
public boolean onTouch(View v, MotionEvent event) {
if (chartClicked) {
actionsListener.onChartTouch();
if (!chart.isHighlightPerDragEnabled()) {
chart.setHighlightPerDragEnabled(true);
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
listViewYPos = event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
actionsListener.scrollBy(Math.round(listViewYPos - event.getRawY()));
listViewYPos = event.getRawY();
break;
}
}
return false;
}
});
chart.setOnChartValueSelectedListener(new OnChartValueSelectedListener() {
@Override
public void onValueSelected(Entry e, Highlight h) {
WptPt wpt = getPoint(chart, h.getX());
selectedWpt = wpt;
if (chartClicked && wpt != null) {
actionsListener.onPointSelected(wpt.lat, wpt.lon);
}
}
@Override
public void onNothingSelected() {
}
});
chart.setOnChartGestureListener(new OnChartGestureListener() {
float highlightDrawX = -1;
@Override
public void onChartGestureStart(MotionEvent me, ChartGesture lastPerformedGesture) {
if (chart.getHighlighted() != null && chart.getHighlighted().length > 0) {
highlightDrawX = chart.getHighlighted()[0].getDrawX();
} else {
highlightDrawX = -1;
}
}
@Override
public void onChartGestureEnd(MotionEvent me, ChartGesture lastPerformedGesture) {
gpxItem.chartMatrix = new Matrix(chart.getViewPortHandler().getMatrixTouch());
Highlight[] highlights = chart.getHighlighted();
if (highlights != null && highlights.length > 0) {
gpxItem.chartHighlightPos = highlights[0].getX();
} else {
gpxItem.chartHighlightPos = -1;
}
if (chartClicked) {
for (int i = 0; i < getCount(); i++) {
View v = getViewAtPosition(i);
if (v != view) {
updateChart(i);
}
}
}
}
@Override
public void onChartLongPressed(MotionEvent me) {
}
@Override
public void onChartDoubleTapped(MotionEvent me) {
}
@Override
public void onChartSingleTapped(MotionEvent me) {
}
@Override
public void onChartFling(MotionEvent me1, MotionEvent me2, float velocityX, float velocityY) {
}
@Override
public void onChartScale(MotionEvent me, float scaleX, float scaleY) {
}
@Override
public void onChartTranslate(MotionEvent me, float dX, float dY) {
if (chartClicked && highlightDrawX != -1) {
Highlight h = chart.getHighlightByTouchPoint(highlightDrawX, 0f);
if (h != null) {
chart.highlightValue(h);
WptPt wpt = getPoint(chart, h.getX());
if (wpt != null) {
actionsListener.onPointSelected(wpt.lat, wpt.lon);
}
}
}
}
});
}
@Override
public void destroyItem(@NonNull ViewGroup collection, int position, @NonNull Object view) {
views.remove(position);
collection.removeView((View) view);
}
@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
return view == object;
}
@Override
public View getCustomTabView(@NonNull ViewGroup parent, int position) {
View tab = LayoutInflater.from(parent.getContext()).inflate(R.layout.gpx_tab, parent, false);
tab.setTag(tabTypes[position].name());
deselect(tab);
return tab;
}
@Override
public void select(View tab) {
GPXTabItemType tabType = GPXTabItemType.valueOf((String) tab.getTag());
ImageView img = tab.findViewById(R.id.tab_image);
switch (tabs.getTabSelectionType()) {
case ALPHA:
img.setAlpha(tabs.getTabTextSelectedAlpha());
break;
case SOLID_COLOR:
img.setImageDrawable(iconsCache.getPaintedIcon(tabType.getIconId(), tabs.getTextColor()));
break;
}
}
@Override
public void deselect(View tab) {
GPXTabItemType tabType = GPXTabItemType.valueOf((String) tab.getTag());
ImageView img = tab.findViewById(R.id.tab_image);
switch (tabs.getTabSelectionType()) {
case ALPHA:
img.setAlpha(tabs.getTabTextAlpha());
break;
case SOLID_COLOR:
img.setImageDrawable(iconsCache.getPaintedIcon(tabType.getIconId(), tabs.getTabInactiveTextColor()));
break;
}
}
@Override
public View getViewAtPosition(int position) {
return views.get(position);
}
void updateChart(int position) {
View view = getViewAtPosition(position);
if (view != null) {
updateChart((LineChart) view.findViewById(R.id.chart));
}
}
void updateJoinGapsInfo(View view, int position) {
if (view != null) {
GPXTrackAnalysis analysis = gpxItem.analysis;
GPXTabItemType tabType = tabTypes[position];
boolean visible = gpxItem.isGeneralTrack() && analysis != null && tabType.equals(GPXTabItemType.GPX_TAB_ITEM_GENERAL);
AndroidUiHelper.updateVisibility(view.findViewById(R.id.gpx_join_gaps_container), visible);
boolean joinSegments = displayHelper.isJoinSegments();
((SwitchCompat) view.findViewById(R.id.gpx_join_gaps_switch)).setChecked(joinSegments);
if (analysis != null) {
if (tabType.equals(GPXTabItemType.GPX_TAB_ITEM_GENERAL)) {
float totalDistance = !joinSegments && gpxItem.isGeneralTrack() ? analysis.totalDistanceWithoutGaps : analysis.totalDistance;
float timeSpan = !joinSegments && gpxItem.isGeneralTrack() ? analysis.timeSpanWithoutGaps : analysis.timeSpan;
((TextView) view.findViewById(R.id.distance_text)).setText(OsmAndFormatter.getFormattedDistance(totalDistance, app));
((TextView) view.findViewById(R.id.duration_text)).setText(Algorithms.formatDuration((int) (timeSpan / 1000), app.accessibilityEnabled()));
} else if (tabType.equals(GPXTabItemType.GPX_TAB_ITEM_SPEED)) {
long timeMoving = !joinSegments && gpxItem.isGeneralTrack() ? analysis.timeMovingWithoutGaps : analysis.timeMoving;
float totalDistanceMoving = !joinSegments && gpxItem.isGeneralTrack() ? analysis.totalDistanceMovingWithoutGaps : analysis.totalDistanceMoving;
((TextView) view.findViewById(R.id.time_moving_text)).setText(Algorithms.formatDuration((int) (timeMoving / 1000), app.accessibilityEnabled()));
((TextView) view.findViewById(R.id.distance_text)).setText(OsmAndFormatter.getFormattedDistance(totalDistanceMoving, app));
}
}
}
}
void updateChart(LineChart chart) {
if (chart != null && !chart.isEmpty()) {
if (gpxItem.chartMatrix != null) {
chart.getViewPortHandler().refresh(new Matrix(gpxItem.chartMatrix), chart, true);
}
if (gpxItem.chartHighlightPos != -1) {
chart.highlightValue(gpxItem.chartHighlightPos, 0);
} else {
chart.highlightValue(null);
}
}
}
private TrkSegment getTrkSegment() {
for (Track track : gpxItem.group.getGpx().tracks) {
if (!track.generalTrack && !gpxItem.isGeneralTrack() || track.generalTrack && gpxItem.isGeneralTrack()) {
for (TrkSegment segment : track.segments) {
if (segment.points.size() > 0 && segment.points.get(0).equals(gpxItem.analysis.locationStart)) {
return segment;
}
}
}
}
return null;
}
void openAnalyzeOnMap(GPXTabItemType tabType) {
List<ILineDataSet> ds = getDataSets(null, tabType, null, null);
actionsListener.openAnalyzeOnMap(gpxItem, ds, tabType);
}
private void openSplitIntervalScreen() {
actionsListener.openSplitInterval(gpxItem, getTrkSegment());
}
}

View file

@ -0,0 +1,32 @@
package net.osmand.plus.myplaces;
import android.content.Context;
import androidx.annotation.DrawableRes;
import androidx.annotation.StringRes;
import net.osmand.plus.R;
public enum GPXTabItemType {
GPX_TAB_ITEM_GENERAL(R.string.shared_string_overview, R.drawable.ic_action_polygom_dark),
GPX_TAB_ITEM_ALTITUDE(R.string.altitude, R.drawable.ic_action_altitude_average),
GPX_TAB_ITEM_SPEED(R.string.map_widget_speed, R.drawable.ic_action_speed);
private final int iconId;
private final int titleId;
GPXTabItemType(@StringRes int titleId, @DrawableRes int iconId) {
this.iconId = iconId;
this.titleId = titleId;
}
@DrawableRes
public int getIconId() {
return iconId;
}
public String toHumanString(Context ctx) {
return ctx.getString(titleId);
}
}

View file

@ -0,0 +1,27 @@
package net.osmand.plus.myplaces;
import android.view.View;
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
import net.osmand.GPXUtilities.TrkSegment;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem;
import java.util.List;
public interface SegmentActionsListener {
void updateContent();
void onChartTouch();
void scrollBy(int px);
void onPointSelected(double lat, double lon);
void openSplitInterval(GpxDisplayItem gpxItem, TrkSegment trkSegment);
void showOptionsPopupMenu(View view, TrkSegment trkSegment, boolean confirmDeletion);
void openAnalyzeOnMap(GpxDisplayItem gpxItem, List<ILineDataSet> dataSets, GPXTabItemType tabType);
}

View file

@ -0,0 +1,91 @@
package net.osmand.plus.myplaces;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import net.osmand.AndroidUtils;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.UiUtilities;
import net.osmand.plus.track.TrackDisplayHelper;
import net.osmand.plus.views.controls.PagerSlidingTabStrip;
import net.osmand.plus.views.controls.WrapContentHeightViewPager;
import java.util.List;
public class SegmentGPXAdapter extends ArrayAdapter<GpxDisplayItem> {
private OsmandApplication app;
private TrackDisplayHelper displayHelper;
private SegmentActionsListener listener;
private boolean nightMode;
public SegmentGPXAdapter(@NonNull Context context, @NonNull List<GpxDisplayItem> items,
@NonNull TrackDisplayHelper displayHelper,
@NonNull SegmentActionsListener listener,
boolean nightMode) {
super(context, R.layout.gpx_list_item_tab_content, items);
this.app = (OsmandApplication) context.getApplicationContext();
this.displayHelper = displayHelper;
this.listener = listener;
this.nightMode = nightMode;
}
@Override
public boolean isEmpty() {
return false;
}
@NonNull
@Override
public View getView(int position, View convertView, @NonNull ViewGroup parent) {
View row = convertView;
boolean create = false;
if (row == null) {
create = true;
row = createGpxTabsView(displayHelper, parent, listener, nightMode);
}
GpxDisplayItem item = getItem(position);
if (item != null) {
WrapContentHeightViewPager pager = row.findViewById(R.id.pager);
PagerSlidingTabStrip tabLayout = row.findViewById(R.id.sliding_tabs);
pager.setAdapter(new GPXItemPagerAdapter(tabLayout, item, displayHelper, listener));
if (create) {
tabLayout.setViewPager(pager);
} else {
tabLayout.notifyDataSetChanged(true);
}
}
return row;
}
public static View createGpxTabsView(TrackDisplayHelper displayHelper, ViewGroup root,
SegmentActionsListener listener, boolean nightMode) {
Context context = root.getContext();
View row = UiUtilities.getInflater(context, nightMode).inflate(R.layout.gpx_list_item_tab_content, root, false);
PagerSlidingTabStrip tabLayout = row.findViewById(R.id.sliding_tabs);
tabLayout.setTabBackground(R.color.color_transparent);
tabLayout.setIndicatorColorResource(nightMode ? R.color.active_color_primary_dark : R.color.active_color_primary_light);
tabLayout.setIndicatorBgColorResource(nightMode ? R.color.divider_color_dark : R.color.divider_color_light);
tabLayout.setIndicatorHeight(AndroidUtils.dpToPx(context, 1f));
if (!nightMode) {
tabLayout.setTextColor(tabLayout.getIndicatorColor());
tabLayout.setTabInactiveTextColor(ContextCompat.getColor(row.getContext(), R.color.text_color_secondary_light));
}
tabLayout.setTextSize(AndroidUtils.spToPx(context, 12f));
tabLayout.setShouldExpand(true);
WrapContentHeightViewPager pager = row.findViewById(R.id.pager);
pager.setSwipeable(false);
pager.setOffscreenPageLimit(2);
return row;
}
}

View file

@ -1,7 +1,6 @@
package net.osmand.plus.myplaces;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.res.ColorStateList;
import android.graphics.Paint;
import android.graphics.Rect;
@ -24,14 +23,18 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.ListPopupWindow;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import net.osmand.AndroidUtils;
import net.osmand.GPXUtilities;
import net.osmand.GPXUtilities.GPXFile;
import net.osmand.GPXUtilities.GPXTrackAnalysis;
import net.osmand.GPXUtilities.TrkSegment;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayGroup;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayItemType;
@ -40,8 +43,8 @@ import net.osmand.plus.OsmAndFormatter;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.UiUtilities;
import net.osmand.plus.activities.TrackActivity;
import net.osmand.plus.helpers.FontCache;
import net.osmand.plus.track.TrackDisplayHelper;
import net.osmand.util.Algorithms;
import java.text.DateFormat;
@ -55,7 +58,9 @@ import gnu.trove.list.array.TIntArrayList;
public class SplitSegmentDialogFragment extends DialogFragment {
public final static String TAG = "SPLIT_SEGMENT_DIALOG_FRAGMENT";
private OsmandApplication app;
private TrackDisplayHelper displayHelper;
private SplitSegmentsAdapter adapter;
private View headerView;
@ -79,8 +84,8 @@ public class SplitSegmentDialogFragment extends DialogFragment {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TrackActivity trackActivity = requireTrackActivity();
app = trackActivity.getMyApplication();
FragmentActivity activity = requireActivity();
app = (OsmandApplication) activity.getApplication();
ic = app.getUIUtilities();
boolean isLightTheme = app.getSettings().isLightContent();
int themeId = isLightTheme ? R.style.OsmandLightTheme : R.style.OsmandDarkTheme;
@ -90,9 +95,8 @@ public class SplitSegmentDialogFragment extends DialogFragment {
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
TrackActivity trackActivity = requireTrackActivity();
listView.setBackgroundColor(getResources().getColor(
trackActivity.getMyApplication().getSettings().isLightContent() ? R.color.activity_background_color_light
app.getSettings().isLightContent() ? R.color.activity_background_color_light
: R.color.activity_background_color_dark));
}
@ -104,7 +108,7 @@ public class SplitSegmentDialogFragment extends DialogFragment {
minMaxSpeedPaint.setStyle(Paint.Style.FILL);
minMaxSpeedTextBounds = new Rect();
TrackActivity trackActivity = requireTrackActivity();
AppCompatActivity trackActivity = (AppCompatActivity) getActivity();
final View view = trackActivity.getLayoutInflater().inflate(R.layout.split_segments_layout, container, false);
Toolbar toolbar = (Toolbar) view.findViewById(R.id.split_interval_toolbar);
@ -251,7 +255,7 @@ public class SplitSegmentDialogFragment extends DialogFragment {
}
public void updateContent() {
if (getTrackActivity() != null) {
if (getActivity() != null) {
adapter.clear();
adapter.setNotifyOnChange(false);
adapter.add(gpxItem);
@ -298,18 +302,7 @@ public class SplitSegmentDialogFragment extends DialogFragment {
@Nullable
private GPXFile getGpx() {
TrackActivity trackActivity = getTrackActivity();
return trackActivity != null ? trackActivity.getGpx() : null;
}
@Nullable
public TrackActivity getTrackActivity() {
return (TrackActivity) getActivity();
}
@NonNull
public TrackActivity requireTrackActivity() {
return (TrackActivity) requireActivity();
return displayHelper.getGpx();
}
private void prepareSplitIntervalAdapterData() {
@ -347,7 +340,7 @@ public class SplitSegmentDialogFragment extends DialogFragment {
@NonNull
private List<GpxDisplayGroup> getDisplayGroups() {
return filterGroups(true);
return displayHelper.getDisplayGroups(filterTypes);
}
private void addOptionSplit(int value, boolean distance, List<GpxDisplayGroup> model) {
@ -375,32 +368,13 @@ public class SplitSegmentDialogFragment extends DialogFragment {
}
}
@NonNull
private List<GpxDisplayGroup> filterGroups(boolean useDisplayGroups) {
List<GpxDisplayGroup> groups = new ArrayList<>();
if (getTrackActivity() != null) {
List<GpxDisplayGroup> result = getTrackActivity().getGpxFile(useDisplayGroups);
for (GpxDisplayGroup group : result) {
boolean add = hasFilterType(group.getType());
if (add) {
groups.add(group);
}
}
}
return groups;
}
@NonNull
private List<GpxDisplayItem> getSplitSegments() {
TrackActivity trackActivity = getTrackActivity();
List<GpxDisplayItem> splitSegments = new ArrayList<>();
if (trackActivity != null) {
List<GpxDisplayGroup> result = trackActivity.getGpxFile(true);
if (result != null && result.size() > 0 && trkSegment.points.size() > 0) {
for (GpxDisplayGroup group : result) {
splitSegments.addAll(collectDisplayItemsFromGroup(group));
}
List<GpxDisplayGroup> result = displayHelper.getGpxFile(true);
if (result != null && result.size() > 0 && trkSegment.points.size() > 0) {
for (GpxDisplayGroup group : result) {
splitSegments.addAll(collectDisplayItemsFromGroup(group));
}
}
return splitSegments;
@ -434,24 +408,6 @@ public class SplitSegmentDialogFragment extends DialogFragment {
return false;
}
@Override
public void dismiss() {
TrackActivity trackActivity = getTrackActivity();
if (trackActivity != null) {
trackActivity.updateSplitView();
}
super.dismiss();
}
@Override
public void onCancel(DialogInterface dialog) {
TrackActivity trackActivity = getTrackActivity();
if (trackActivity != null) {
trackActivity.updateSplitView();
}
super.onCancel(dialog);
}
private class SplitSegmentsAdapter extends ArrayAdapter<GpxDisplayItem> {
SplitSegmentsAdapter(List<GpxDisplayItem> items) {
@ -464,7 +420,7 @@ public class SplitSegmentDialogFragment extends DialogFragment {
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
GpxDisplayItem currentGpxDisplayItem = getItem(position);
TrackActivity trackActivity = requireTrackActivity();
FragmentActivity trackActivity = requireActivity();
if (convertView == null) {
convertView = trackActivity.getLayoutInflater().inflate(R.layout.gpx_split_segment_fragment, parent, false);
}
@ -750,14 +706,15 @@ public class SplitSegmentDialogFragment extends DialogFragment {
}
}
public static boolean showInstance(@NonNull TrackActivity trackActivity, @NonNull GpxDisplayItem gpxItem, GPXUtilities.TrkSegment trkSegment) {
public static boolean showInstance(@NonNull FragmentManager fragmentManager, @NonNull TrackDisplayHelper displayHelper,
@NonNull GpxDisplayItem gpxItem, @NonNull TrkSegment trkSegment) {
try {
SplitSegmentDialogFragment fragment = new SplitSegmentDialogFragment();
fragment.setGpxItem(gpxItem);
fragment.setTrkSegment(trkSegment);
fragment.setRetainInstance(true);
fragment.setJoinSegments(trackActivity.isJoinSegments());
fragment.show(trackActivity.getSupportFragmentManager(), TAG);
fragment.displayHelper = displayHelper;
fragment.show(fragmentManager, TAG);
return true;
} catch (RuntimeException e) {
return false;

View file

@ -6,7 +6,6 @@ import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.Html;
import android.text.TextUtils;
@ -14,9 +13,7 @@ import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.ListView;
@ -39,16 +36,12 @@ import com.squareup.picasso.RequestCreator;
import net.osmand.AndroidUtils;
import net.osmand.GPXUtilities;
import net.osmand.GPXUtilities.GPXFile;
import net.osmand.GPXUtilities.Metadata;
import net.osmand.GPXUtilities.WptPt;
import net.osmand.PicassoUtils;
import net.osmand.data.PointDescription;
import net.osmand.data.QuadRect;
import net.osmand.plus.GPXDatabase.GpxDataItem;
import net.osmand.plus.GpxSelectionHelper;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayGroup;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayItemType;
import net.osmand.plus.GpxSelectionHelper.SelectedGpxFile;
import net.osmand.plus.OsmAndFormatter;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity;
@ -57,9 +50,7 @@ import net.osmand.plus.dialogs.GpxAppearanceAdapter;
import net.osmand.plus.myplaces.TrackBitmapDrawer.TrackBitmapDrawerListener;
import net.osmand.plus.settings.backend.CommonPreference;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.track.GpxSplitType;
import net.osmand.plus.track.SplitTrackAsyncTask;
import net.osmand.plus.track.SplitTrackAsyncTask.SplitTrackListener;
import net.osmand.plus.track.TrackDisplayHelper;
import net.osmand.plus.widgets.tools.CropCircleTransformation;
import net.osmand.plus.wikipedia.WikiArticleHelper;
import net.osmand.plus.wikivoyage.WikivoyageUtils;
@ -67,12 +58,8 @@ import net.osmand.plus.wikivoyage.article.WikivoyageArticleDialogFragment;
import net.osmand.plus.wikivoyage.data.TravelArticle;
import net.osmand.render.RenderingRulesStorage;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import gnu.trove.list.array.TIntArrayList;
import static net.osmand.plus.dialogs.ConfigureMapMenu.CURRENT_TRACK_COLOR_ATTR;
public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener {
@ -81,11 +68,8 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener {
private Fragment fragment;
private ListView listView;
private GpxDisplayItemType[] filterTypes;
private TrackDisplayHelper displayHelper;
private List<String> options = new ArrayList<>();
private List<Double> distanceSplit = new ArrayList<>();
private TIntArrayList timeSplit = new TIntArrayList();
private int selectedSplitInterval;
private boolean updateEnable;
private View headerView;
private SwitchCompat vis;
@ -108,17 +92,18 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener {
private View lineTextLayout;
private View overlayView;
ListPopupWindow splitListPopupWindow;
ListPopupWindow colorListPopupWindow;
TrackActivityFragmentAdapter(@NonNull OsmandApplication app,
@NonNull Fragment fragment,
@NonNull ListView listView,
@NonNull TrackDisplayHelper displayHelper,
@NonNull GpxDisplayItemType... filterTypes) {
this.app = app;
this.fragment = fragment;
this.listView = listView;
this.filterTypes = filterTypes;
this.displayHelper = displayHelper;
}
public void onActivityCreated(Bundle savedInstanceState) {
@ -206,14 +191,12 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener {
}
private GPXFile getGpx() {
TrackActivity activity = getTrackActivity();
return activity != null ? activity.getGpx() : null;
return displayHelper.getGpx();
}
@Nullable
private GpxDataItem getGpxDataItem() {
TrackActivity activity = getTrackActivity();
return activity != null ? activity.getGpxDataItem() : null;
return displayHelper.getGpxDataItem();
}
private void showTrackBitmapProgress() {
@ -265,17 +248,15 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener {
imageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
showTemporaryObjectOnMap(getRect());
showTemporaryObjectOnMap(displayHelper.getRect());
}
});
final View splitColorView = headerView.findViewById(R.id.split_color_view);
final View appearanceView = headerView.findViewById(R.id.appearance_view);
final View divider = headerView.findViewById(R.id.divider);
final View splitIntervalView = headerView.findViewById(R.id.split_interval_view);
vis = (SwitchCompat) headerView.findViewById(R.id.showOnMapToggle);
final View bottomDivider = headerView.findViewById(R.id.bottom_divider);
GPXFile gpxFile = getGpx();
boolean gpxFileSelected = isGpxFileSelected(gpxFile);
boolean gpxFileSelected = isGpxFileSelected(app, gpxFile);
boolean hasPath = gpxFile != null && (gpxFile.tracks.size() > 0 || gpxFile.routes.size() > 0);
TrackActivity activity = getTrackActivity();
@ -297,13 +278,7 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener {
@Override
public void onClick(View v) {
vis.toggle();
if (!vis.isChecked()) {
selectedSplitInterval = 0;
}
setTrackVisibilityOnMap(vis.isChecked());
if (!showMapOnly) {
updateSplitIntervalView(splitIntervalView);
}
TrackActivity trackActivity = getTrackActivity();
if (trackActivity != null) {
trackActivity.updateHeader(fragment);
@ -312,45 +287,12 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener {
}
});
splitColorView.setVisibility(View.GONE);
if (showMapOnly) {
splitIntervalView.setVisibility(View.GONE);
appearanceView.setVisibility(View.GONE);
divider.setVisibility(View.GONE);
bottomDivider.setVisibility(View.VISIBLE);
} else {
bottomDivider.setVisibility(View.GONE);
if (hasPath) {
if (!gpxFile.showCurrentTrack && listItemsCount > 0) {
prepareSplitIntervalAdapterData();
setupSplitIntervalView(splitIntervalView);
updateSplitIntervalView(splitIntervalView);
splitIntervalView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
TrackActivity activity = getTrackActivity();
if (activity != null) {
ListAdapter adapter = new ArrayAdapter<>(activity, R.layout.popup_list_text_item, options);
OnItemClickListener itemClickListener = new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
selectedSplitInterval = position;
setTrackVisibilityOnMap(vis.isChecked());
splitListPopupWindow.dismiss();
updateSplitIntervalView(splitIntervalView);
}
};
splitListPopupWindow = createPopupWindow(activity, splitIntervalView, adapter, itemClickListener);
splitListPopupWindow.show();
}
}
});
splitIntervalView.setVisibility(View.VISIBLE);
} else {
splitIntervalView.setVisibility(View.GONE);
}
appearanceView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@ -363,6 +305,7 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener {
appearanceView.setVisibility(View.GONE);
divider.setVisibility(View.GONE);
}
bottomDivider.setVisibility(View.GONE);
}
updateTrackColor();
}
@ -373,7 +316,7 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener {
GPXFile gpx = getGpx();
WptPt pointToShow = gpx != null ? gpx.findPointToShow() : null;
if (activity != null && pointToShow != null) {
boolean gpxFileSelected = isGpxFileSelected(gpx);
boolean gpxFileSelected = isGpxFileSelected(app, gpx);
if (!gpxFileSelected) {
Intent intent = activity.getIntent();
if (intent != null) {
@ -422,7 +365,7 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener {
return null;
}
TravelArticle article = getTravelArticle(gpx.metadata);
TravelArticle article = getTravelArticle(gpx);
if (article != null) {
return createTravelArticleCard(context, article);
}
@ -448,7 +391,7 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener {
}
@Nullable
private String getMetadataDescription(@NonNull GPXUtilities.Metadata metadata) {
private String getMetadataDescription(@NonNull Metadata metadata) {
String descHtml = metadata.desc;
if (TextUtils.isEmpty(descHtml)) {
Map<String, String> extensions = metadata.getExtensionsToRead();
@ -466,7 +409,7 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener {
}
@Nullable
private String getMetadataImageLink(@NonNull GPXUtilities.Metadata metadata) {
private String getMetadataImageLink(@NonNull Metadata metadata) {
String link = metadata.link;
if (!TextUtils.isEmpty(link)) {
String lowerCaseLink = link.toLowerCase();
@ -482,11 +425,12 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener {
}
@Nullable
private TravelArticle getTravelArticle(@NonNull GPXUtilities.Metadata metadata) {
private TravelArticle getTravelArticle(@NonNull GPXFile gpx) {
Metadata metadata = gpx.metadata;
String title = metadata.getArticleTitle();
String lang = metadata.getArticleLang();
if (!TextUtils.isEmpty(title) && !TextUtils.isEmpty(lang)) {
return app.getTravelHelper().getArticleByTitle(title, lang);
return app.getTravelHelper().getArticleByTitle(title, gpx.getRect(), lang);
}
return null;
}
@ -506,8 +450,8 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener {
public void onClick(View v) {
TrackActivity activity = getTrackActivity();
if (activity != null) {
WikivoyageArticleDialogFragment.showInstance(app,
activity.getSupportFragmentManager(), article.getRouteId(), article.getLang());
WikivoyageArticleDialogFragment.showInstance(app, activity.getSupportFragmentManager(),
article.generateIdentifier(), article.getLang());
}
}
};
@ -585,7 +529,7 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener {
}
}
public boolean isGpxFileSelected(GPXFile gpxFile) {
public static boolean isGpxFileSelected(OsmandApplication app, GPXFile gpxFile) {
return gpxFile != null &&
((gpxFile.showCurrentTrack && app.getSelectedGpxHelper().getSelectedCurrentRecordingTrack() != null) ||
(gpxFile.path != null && app.getSelectedGpxHelper().getSelectedFileByPath(gpxFile.path) != null));
@ -594,26 +538,10 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener {
private void setTrackVisibilityOnMap(boolean visible) {
GPXFile gpxFile = getGpx();
if (gpxFile != null) {
GpxSelectionHelper gpxHelper = app.getSelectedGpxHelper();
SelectedGpxFile sf = gpxHelper.selectGpxFile(gpxFile, visible, false);
if (gpxFile.hasTrkPt()) {
List<GpxDisplayGroup> groups = getDisplayGroups();
if (groups.size() > 0) {
updateSplit(groups, visible ? sf : null);
if (getGpxDataItem() != null) {
updateSplitInDatabase();
}
}
}
app.getSelectedGpxHelper().selectGpxFile(gpxFile, visible, false);
}
}
@Nullable
private QuadRect getRect() {
TrackActivity activity = getTrackActivity();
return activity != null ? activity.getRect() : null;
}
public boolean isShowOnMap() {
return vis != null && vis.isChecked();
}
@ -629,88 +557,6 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener {
menuFab.setVisibility(visible ? View.VISIBLE : View.GONE);
}
@NonNull
public List<GpxDisplayGroup> getOriginalGroups() {
return filterGroups(false);
}
@NonNull
public List<GpxDisplayGroup> getDisplayGroups() {
return filterGroups(true);
}
private boolean hasFilterType(GpxDisplayItemType filterType) {
for (GpxDisplayItemType type : filterTypes) {
if (type == filterType) {
return true;
}
}
return false;
}
@NonNull
private List<GpxDisplayGroup> filterGroups(boolean useDisplayGroups) {
List<GpxDisplayGroup> groups = new ArrayList<>();
TrackActivity activity = getTrackActivity();
if (activity != null) {
List<GpxDisplayGroup> result = activity.getGpxFile(useDisplayGroups);
for (GpxDisplayGroup group : result) {
boolean add = hasFilterType(group.getType());
if (add) {
groups.add(group);
}
}
}
return groups;
}
private void setupSplitIntervalView(View view) {
final TextView title = (TextView) view.findViewById(R.id.split_interval_title);
final TextView text = (TextView) view.findViewById(R.id.split_interval_text);
final ImageView img = (ImageView) view.findViewById(R.id.split_interval_arrow);
int colorId;
final List<GpxDisplayGroup> groups = getDisplayGroups();
if (groups.size() > 0) {
colorId = app.getSettings().isLightContent() ?
R.color.text_color_primary_light : R.color.text_color_primary_dark;
} else {
colorId = app.getSettings().isLightContent() ?
R.color.text_color_secondary_light : R.color.text_color_secondary_dark;
}
int color = app.getResources().getColor(colorId);
title.setTextColor(color);
text.setTextColor(color);
img.setImageDrawable(app.getUIUtilities().getIcon(R.drawable.ic_action_arrow_drop_down, colorId));
}
private void updateSplitIntervalView(View view) {
final TextView text = (TextView) view.findViewById(R.id.split_interval_text);
selectedSplitInterval = getSelectedSplitInterval();
if (selectedSplitInterval == 0) {
text.setText(app.getString(R.string.shared_string_none));
} else {
text.setText(options.get(selectedSplitInterval));
}
}
private int getSelectedSplitInterval() {
if (getGpxDataItem() == null) {
return 0;
}
int splitType = getGpxDataItem().getSplitType();
double splitInterval = getGpxDataItem().getSplitInterval();
int position = 0;
if (splitType == GpxSplitType.DISTANCE.getType()) {
position = distanceSplit.indexOf(splitInterval);
} else if (splitType == GpxSplitType.TIME.getType()) {
position = timeSplit.indexOf((int) splitInterval);
}
return Math.max(position, 0);
}
private void updateTrackColor() {
int color = getGpxDataItem() != null ? getGpxDataItem().getColor() : 0;
GPXFile gpxFile = getGpx();
@ -732,154 +578,6 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener {
}
}
public List<GpxSelectionHelper.GpxDisplayItem> flatten(List<GpxDisplayGroup> groups) {
ArrayList<GpxSelectionHelper.GpxDisplayItem> list = new ArrayList<>();
for (GpxDisplayGroup g : groups) {
list.addAll(g.getModifiableList());
}
return list;
}
private void prepareSplitIntervalAdapterData() {
final List<GpxDisplayGroup> groups = getDisplayGroups();
options.add(app.getString(R.string.shared_string_none));
distanceSplit.add(-1d);
timeSplit.add(-1);
addOptionSplit(30, true, groups); // 50 feet, 20 yards, 20
// m
addOptionSplit(60, true, groups); // 100 feet, 50 yards,
// 50 m
addOptionSplit(150, true, groups); // 200 feet, 100 yards,
// 100 m
addOptionSplit(300, true, groups); // 500 feet, 200 yards,
// 200 m
addOptionSplit(600, true, groups); // 1000 feet, 500 yards,
// 500 m
addOptionSplit(1500, true, groups); // 2000 feet, 1000 yards, 1 km
addOptionSplit(3000, true, groups); // 1 mi, 2 km
addOptionSplit(6000, true, groups); // 2 mi, 5 km
addOptionSplit(15000, true, groups); // 5 mi, 10 km
addOptionSplit(15, false, groups);
addOptionSplit(30, false, groups);
addOptionSplit(60, false, groups);
addOptionSplit(120, false, groups);
addOptionSplit(150, false, groups);
addOptionSplit(300, false, groups);
addOptionSplit(600, false, groups);
addOptionSplit(900, false, groups);
addOptionSplit(1800, false, groups);
addOptionSplit(3600, false, groups);
}
private void updateSplit(@NonNull List<GpxDisplayGroup> groups, @Nullable final SelectedGpxFile selectedGpx) {
GPXFile gpxFile = getGpx();
TrackActivity activity = getTrackActivity();
GpxSplitType gpxSplitType = getGpxSplitType();
if (activity != null && gpxSplitType != null && gpxFile != null) {
int timeSplit = 0;
double distanceSplit = 0;
if (gpxSplitType != GpxSplitType.NO_SPLIT && !gpxFile.showCurrentTrack) {
timeSplit = this.timeSplit.get(selectedSplitInterval);
distanceSplit = this.distanceSplit.get(selectedSplitInterval);
}
SplitTrackListener splitTrackListener = new SplitTrackListener() {
@Override
public void trackSplittingStarted() {
TrackActivity activity = getTrackActivity();
if (activity != null) {
activity.setSupportProgressBarIndeterminateVisibility(true);
}
}
@Override
public void trackSplittingFinished() {
TrackActivity activity = getTrackActivity();
if (activity != null) {
if (AndroidUtils.isActivityNotDestroyed(activity)) {
activity.setSupportProgressBarIndeterminateVisibility(false);
}
if (selectedGpx != null) {
List<GpxDisplayGroup> groups = getDisplayGroups();
selectedGpx.setDisplayGroups(groups, app);
}
}
}
};
new SplitTrackAsyncTask(app, gpxSplitType, groups, splitTrackListener, activity.isJoinSegments(),
timeSplit, distanceSplit).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
private GpxSplitType getGpxSplitType() {
if (selectedSplitInterval == 0) {
return GpxSplitType.NO_SPLIT;
} else if (distanceSplit.get(selectedSplitInterval) > 0) {
return GpxSplitType.DISTANCE;
} else if (timeSplit.get(selectedSplitInterval) > 0) {
return GpxSplitType.TIME;
}
return null;
}
private void addOptionSplit(int value, boolean distance, @NonNull List<GpxDisplayGroup> model) {
if (model.size() > 0) {
if (distance) {
double dvalue = OsmAndFormatter.calculateRoundedDist(value, app);
options.add(OsmAndFormatter.getFormattedDistanceInterval(app, value));
distanceSplit.add(dvalue);
timeSplit.add(-1);
if (Math.abs(model.get(0).getSplitDistance() - dvalue) < 1) {
selectedSplitInterval = distanceSplit.size() - 1;
}
} else {
options.add(OsmAndFormatter.getFormattedTimeInterval(app, value));
distanceSplit.add(-1d);
timeSplit.add(value);
if (model.get(0).getSplitTime() == value) {
selectedSplitInterval = distanceSplit.size() - 1;
}
}
}
}
private void updateSplitInDatabase() {
double splitInterval = 0;
GpxSplitType splitType = null;
if (selectedSplitInterval == 0) {
splitType = GpxSplitType.NO_SPLIT;
splitInterval = 0;
} else if (distanceSplit.get(selectedSplitInterval) > 0) {
splitType = GpxSplitType.DISTANCE;
splitInterval = distanceSplit.get(selectedSplitInterval);
} else if (timeSplit.get(selectedSplitInterval) > 0) {
splitType = GpxSplitType.TIME;
splitInterval = timeSplit.get(selectedSplitInterval);
}
GpxDataItem item = getGpxDataItem();
if (item != null && splitType != null) {
app.getGpxDbHelper().updateSplit(item, splitType, splitInterval);
}
}
public void updateSplitView() {
GPXFile gpxFile = getGpx();
if (gpxFile != null) {
SelectedGpxFile sf = app.getSelectedGpxHelper().selectGpxFile(gpxFile,
((SwitchCompat) headerView.findViewById(R.id.showOnMapToggle)).isChecked(), false);
final List<GpxDisplayGroup> groups = getDisplayGroups();
if (groups.size() > 0) {
updateSplit(groups, ((SwitchCompat) headerView.findViewById(R.id.showOnMapToggle)).isChecked() ? sf : null);
if (getGpxDataItem() != null) {
updateSplitInDatabase();
}
}
updateSplitIntervalView(headerView.findViewById(R.id.split_interval_view));
}
}
public void hideTransparentOverlay() {
overlayView.setVisibility(View.GONE);
}

View file

@ -41,7 +41,6 @@ import com.google.android.material.snackbar.Snackbar;
import net.osmand.AndroidUtils;
import net.osmand.Collator;
import net.osmand.GPXUtilities;
import net.osmand.GPXUtilities.GPXFile;
import net.osmand.GPXUtilities.WptPt;
import net.osmand.OsmAndCollator;
@ -59,7 +58,6 @@ import net.osmand.plus.UiUtilities;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.activities.OsmandActionBarActivity;
import net.osmand.plus.activities.OsmandBaseExpandableListAdapter;
import net.osmand.plus.activities.SavingTrackHelper;
import net.osmand.plus.activities.TrackActivity;
import net.osmand.plus.base.OsmandExpandableListFragment;
import net.osmand.plus.base.PointImageDrawable;
@ -67,14 +65,16 @@ import net.osmand.plus.helpers.GpxUiHelper;
import net.osmand.plus.mapmarkers.CoordinateInputDialogFragment;
import net.osmand.plus.mapmarkers.MapMarkersGroup;
import net.osmand.plus.mapmarkers.MapMarkersHelper;
import net.osmand.plus.myplaces.DeletePointsTask.OnPointsDeleteListener;
import net.osmand.plus.myplaces.TrackBitmapDrawer.TrackBitmapDrawerListener;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.track.SaveGpxAsyncTask.SaveGpxListener;
import net.osmand.plus.track.TrackDisplayHelper;
import net.osmand.plus.track.TrackMenuFragment;
import net.osmand.plus.widgets.TextViewEx;
import net.osmand.util.Algorithms;
import java.io.File;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@ -88,7 +88,7 @@ import java.util.Map.Entry;
import java.util.Set;
public class TrackPointFragment extends OsmandExpandableListFragment implements TrackBitmapDrawerListener {
public class TrackPointFragment extends OsmandExpandableListFragment implements TrackBitmapDrawerListener, OnPointsDeleteListener {
public static final int SEARCH_ID = -1;
public static final int DELETE_ID = 2;
@ -105,6 +105,8 @@ public class TrackPointFragment extends OsmandExpandableListFragment implements
private OsmandApplication app;
private TrackActivityFragmentAdapter fragmentAdapter;
final private PointGPXAdapter adapter = new PointGPXAdapter();
private GpxDisplayItemType[] filterTypes = new GpxDisplayItemType[] {GpxDisplayItemType.TRACK_POINTS, GpxDisplayItemType.TRACK_ROUTE_POINTS};
private TrackDisplayHelper displayHelper;
private boolean selectionMode;
private LinkedHashMap<GpxDisplayItemType, Set<GpxDisplayItem>> selectedItems = new LinkedHashMap<>();
@ -120,6 +122,13 @@ public class TrackPointFragment extends OsmandExpandableListFragment implements
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.app = getMyApplication();
FragmentActivity activity = getActivity();
if (activity instanceof TrackActivity) {
displayHelper = ((TrackActivity) activity).getDisplayHelper();
} else if (getTargetFragment() instanceof TrackMenuFragment) {
displayHelper = ((TrackMenuFragment) getTargetFragment()).getDisplayHelper();
}
}
@Override
@ -135,8 +144,7 @@ public class TrackPointFragment extends OsmandExpandableListFragment implements
mainView = inflater.inflate(R.layout.track_points_tree, container, false);
ExpandableListView listView = (ExpandableListView) mainView.findViewById(android.R.id.list);
fragmentAdapter = new TrackActivityFragmentAdapter(app, this, listView,
GpxDisplayItemType.TRACK_POINTS, GpxDisplayItemType.TRACK_ROUTE_POINTS);
fragmentAdapter = new TrackActivityFragmentAdapter(app, this, listView, displayHelper, filterTypes);
fragmentAdapter.setShowMapOnly(true);
fragmentAdapter.setTrackBitmapSelectionSupported(false);
fragmentAdapter.setShowDescriptionCard(true);
@ -165,13 +173,8 @@ public class TrackPointFragment extends OsmandExpandableListFragment implements
if (optionsMenu != null) {
optionsMenu.close();
}
if (fragmentAdapter != null) {
if (fragmentAdapter.splitListPopupWindow != null) {
fragmentAdapter.splitListPopupWindow.dismiss();
}
if (fragmentAdapter.colorListPopupWindow != null) {
fragmentAdapter.colorListPopupWindow.dismiss();
}
if (fragmentAdapter != null && fragmentAdapter.colorListPopupWindow != null) {
fragmentAdapter.colorListPopupWindow.dismiss();
}
}
@ -192,7 +195,6 @@ public class TrackPointFragment extends OsmandExpandableListFragment implements
}
}
private int getSelectedItemsCount() {
int count = 0;
for (Set<GpxDisplayItem> set : selectedItems.values()) {
@ -213,21 +215,14 @@ public class TrackPointFragment extends OsmandExpandableListFragment implements
return result;
}
@Nullable
public TrackActivity getTrackActivity() {
return (TrackActivity) getActivity();
}
@Nullable
private GPXFile getGpx() {
TrackActivity activity = getTrackActivity();
return activity != null ? activity.getGpx() : null;
return displayHelper.getGpx();
}
@Nullable
private GpxDataItem getGpxDataItem() {
TrackActivity activity = getTrackActivity();
return activity != null ? activity.getGpxDataItem() : null;
return displayHelper.getGpxDataItem();
}
private void expandAllGroups() {
@ -250,7 +245,7 @@ public class TrackPointFragment extends OsmandExpandableListFragment implements
@Nullable
private List<GpxDisplayGroup> getOriginalGroups() {
return fragmentAdapter != null ? fragmentAdapter.getOriginalGroups() : null;
return displayHelper.getOriginalGroups(filterTypes);
}
public void setContent() {
@ -348,7 +343,7 @@ public class TrackPointFragment extends OsmandExpandableListFragment implements
}
};
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
menu.clear();
@ -495,13 +490,13 @@ public class TrackPointFragment extends OsmandExpandableListFragment implements
}
private void deleteItems() {
new DeletePointsTask(this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
new DeletePointsTask(app, getGpx(), getSelectedItems(), this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void addOrRemoveMapMarkersSyncGroup() {
final MapMarkersHelper markersHelper = app.getMapMarkersHelper();
TrackActivity activity = getTrackActivity();
FragmentActivity activity = getActivity();
if (activity == null) {
return;
}
@ -536,7 +531,7 @@ public class TrackPointFragment extends OsmandExpandableListFragment implements
.setAction(R.string.shared_string_undo, new View.OnClickListener() {
@Override
public void onClick(View v) {
TrackActivity trackActivity = getTrackActivity();
FragmentActivity trackActivity = getActivity();
if (trackActivity != null) {
if (markersRemoved) {
if (gpxFile != null) {
@ -606,7 +601,7 @@ public class TrackPointFragment extends OsmandExpandableListFragment implements
}
private void selectFavoritesImpl() {
TrackActivity activity = getTrackActivity();
FragmentActivity activity = getActivity();
if (activity != null && getSelectedItemsCount() > 0) {
AlertDialog.Builder b = new AlertDialog.Builder(activity);
final EditText editText = new EditText(activity);
@ -684,14 +679,11 @@ public class TrackPointFragment extends OsmandExpandableListFragment implements
} else {
GPXFile gpx = item.group.getGpx();
if (gpx != null) {
TrackActivity trackActivity = getTrackActivity();
if (trackActivity != null && fragmentAdapter != null) {
boolean gpxFileSelected = fragmentAdapter.isGpxFileSelected(gpx);
if (!gpxFileSelected) {
Intent intent = trackActivity.getIntent();
if (intent != null) {
intent.putExtra(TrackActivity.SHOW_TEMPORARILY, true);
}
FragmentActivity activity = getActivity();
if (activity != null && !TrackActivityFragmentAdapter.isGpxFileSelected(app, gpx)) {
Intent intent = activity.getIntent();
if (intent != null) {
intent.putExtra(TrackActivity.SHOW_TEMPORARILY, true);
}
}
app.getSelectedGpxHelper().setGpxFileToDisplay(gpx);
@ -741,6 +733,23 @@ public class TrackPointFragment extends OsmandExpandableListFragment implements
}
}
@Override
public void onPointsDeletionStarted() {
showProgressBar();
}
@Override
public void onPointsDeleted() {
selectedItems.clear();
selectedGroups.clear();
hideProgressBar();
List<GpxDisplayGroup> groups = getOriginalGroups();
if (groups != null) {
adapter.synchronizeGroups(groups);
}
}
class PointGPXAdapter extends OsmandBaseExpandableListAdapter implements Filterable {
Map<GpxDisplayGroup, List<GpxDisplayItem>> itemGroups = new LinkedHashMap<>();
@ -1199,9 +1208,6 @@ public class TrackPointFragment extends OsmandExpandableListFragment implements
public class PointsFilter extends Filter {
PointsFilter() {
}
@Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
@ -1243,77 +1249,4 @@ public class TrackPointFragment extends OsmandExpandableListFragment implements
expandAllGroups();
}
}
private static class DeletePointsTask extends AsyncTask<Void, Void, Void> {
private OsmandApplication app;
private WeakReference<TrackPointFragment> fragmentRef;
private GPXFile gpx;
private Set<GpxDisplayItem> selectedItems;
DeletePointsTask(TrackPointFragment fragment) {
this.app = fragment.getMyApplication();
this.fragmentRef = new WeakReference<>(fragment);
this.gpx = fragment.getGpx();
this.selectedItems = fragment.getSelectedItems();
}
@Override
protected void onPreExecute() {
TrackPointFragment fragment = fragmentRef.get();
if (fragment != null) {
fragment.showProgressBar();
}
}
@Override
protected Void doInBackground(Void... params) {
SavingTrackHelper savingTrackHelper = app.getSavingTrackHelper();
if (gpx != null) {
for (GpxDisplayItem item : selectedItems) {
if (gpx.showCurrentTrack) {
savingTrackHelper.deletePointData(item.locationStart);
} else {
if (item.group.getType() == GpxDisplayItemType.TRACK_POINTS) {
gpx.deleteWptPt(item.locationStart);
} else if (item.group.getType() == GpxDisplayItemType.TRACK_ROUTE_POINTS) {
gpx.deleteRtePt(item.locationStart);
}
}
}
if (!gpx.showCurrentTrack) {
GPXUtilities.writeGpxFile(new File(gpx.path), gpx);
boolean selected = app.getSelectedGpxHelper().getSelectedFileByPath(gpx.path) != null;
if (selected) {
app.getSelectedGpxHelper().setGpxFileToDisplay(gpx);
}
}
syncGpx(gpx);
}
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
TrackPointFragment fragment = fragmentRef.get();
if (fragment != null) {
fragment.selectedItems.clear();
fragment.selectedGroups.clear();
fragment.hideProgressBar();
List<GpxDisplayGroup> groups = fragment.getOriginalGroups();
if (groups != null) {
fragment.adapter.synchronizeGroups(groups);
}
}
}
private void syncGpx(GPXFile gpxFile) {
MapMarkersHelper helper = app.getMapMarkersHelper();
MapMarkersGroup group = helper.getMarkersGroup(gpxFile);
if (group != null) {
helper.runSynchronization(group);
}
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -43,7 +43,7 @@ public class CurrentStreetName {
String rf = n.directionInfo.getRef();
String dn = n.directionInfo.getDestinationName();
isSet = !(Algorithms.isEmpty(nm) && Algorithms.isEmpty(rf) && Algorithms.isEmpty(dn));
streetName.text = RoutingHelperUtils.formatStreetName(nm, null, dn, "»");
streetName.text = RoutingHelperUtils.formatStreetName(nm, rf, dn, "»");
streetName.turnType = n.directionInfo.getTurnType();
streetName.shieldObject = n.directionInfo.getRouteDataObject();
if (streetName.turnType == null) {
@ -62,7 +62,8 @@ public class CurrentStreetName {
if (rs != null) {
streetName.text = getRouteSegmentStreetName(routingHelper, rs, false);
if (Algorithms.isEmpty(streetName.text)) {
isSet = !Algorithms.isEmpty(getRouteSegmentStreetName(routingHelper, rs, true));
streetName.text = getRouteSegmentStreetName(routingHelper, rs, true);
isSet = !Algorithms.isEmpty(streetName.text);
} else {
isSet = true;
}

View file

@ -0,0 +1,57 @@
package net.osmand.plus.track;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayItemType;
import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.myplaces.GPXItemPagerAdapter;
import net.osmand.plus.myplaces.SegmentActionsListener;
import net.osmand.plus.myplaces.SegmentGPXAdapter;
import net.osmand.plus.routepreparationmenu.cards.BaseCard;
import net.osmand.plus.views.controls.PagerSlidingTabStrip;
import net.osmand.plus.views.controls.WrapContentHeightViewPager;
import java.util.List;
public class SegmentsCard extends BaseCard {
private TrackDisplayHelper displayHelper;
private GpxDisplayItemType[] filterTypes = new GpxDisplayItemType[] {GpxDisplayItemType.TRACK_SEGMENT};
private SegmentGPXAdapter adapter;
private SegmentActionsListener listener;
public SegmentsCard(@NonNull MapActivity mapActivity, @NonNull TrackDisplayHelper displayHelper,
@NonNull SegmentActionsListener listener) {
super(mapActivity);
this.displayHelper = displayHelper;
this.listener = listener;
}
@Override
public int getCardLayoutId() {
return R.layout.track_segments_container;
}
@Override
protected void updateContent() {
ViewGroup container = (ViewGroup) view;
container.removeAllViews();
List<GpxDisplayItem> items = TrackDisplayHelper.flatten(displayHelper.getOriginalGroups(filterTypes));
for (GpxDisplayItem displayItem : items) {
View segmentView = SegmentGPXAdapter.createGpxTabsView(displayHelper, container, listener, nightMode);
WrapContentHeightViewPager pager = segmentView.findViewById(R.id.pager);
PagerSlidingTabStrip tabLayout = segmentView.findViewById(R.id.sliding_tabs);
pager.setAdapter(new GPXItemPagerAdapter(tabLayout, displayItem, displayHelper, listener));
tabLayout.setViewPager(pager);
container.addView(segmentView);
}
}
}

View file

@ -0,0 +1,179 @@
package net.osmand.plus.track;
import android.app.Activity;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import net.osmand.GPXUtilities.GPXFile;
import net.osmand.GPXUtilities.WptPt;
import net.osmand.data.LatLon;
import net.osmand.data.PointDescription;
import net.osmand.data.QuadRect;
import net.osmand.plus.GPXDatabase.GpxDataItem;
import net.osmand.plus.GpxSelectionHelper;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayGroup;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayItemType;
import net.osmand.plus.GpxSelectionHelper.SelectedGpxFile;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.measurementtool.GpxData;
import net.osmand.plus.settings.backend.OsmandSettings;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class TrackDisplayHelper {
private final OsmandApplication app;
private File file;
private GPXFile gpxFile;
private GpxDataItem gpxDataItem;
private long modifiedTime = -1;
private List<GpxDisplayGroup> displayGroups;
private List<GpxDisplayGroup> originalGroups = new ArrayList<>();
public TrackDisplayHelper(OsmandApplication app) {
this.app = app;
}
@Nullable
public GPXFile getGpx() {
return gpxFile;
}
@Nullable
public GpxDataItem getGpxDataItem() {
return gpxDataItem;
}
public File getFile() {
return file;
}
public void setFile(@Nullable File file) {
this.file = file;
}
public void setGpx(@NonNull GPXFile result) {
this.gpxFile = result;
if (file == null) {
this.gpxFile = app.getSavingTrackHelper().getCurrentGpx();
}
}
public void setGpxDataItem(GpxDataItem gpxDataItem) {
this.gpxDataItem = gpxDataItem;
}
public QuadRect getRect() {
if (getGpx() != null) {
return getGpx().getRect();
} else {
return new QuadRect(0, 0, 0, 0);
}
}
public boolean setJoinSegments(boolean joinSegments) {
if (gpxDataItem != null) {
boolean updated = app.getGpxDbHelper().updateJoinSegments(gpxDataItem, joinSegments);
SelectedGpxFile selectedGpxFile = app.getSelectedGpxHelper().getSelectedFileByPath(gpxFile.path);
if (updated && selectedGpxFile != null) {
selectedGpxFile.setJoinSegments(joinSegments);
}
return updated;
}
return false;
}
public boolean isJoinSegments() {
return gpxDataItem != null && gpxDataItem.isJoinSegments();
}
public List<GpxDisplayGroup> getGpxFile(boolean useDisplayGroups) {
if (gpxFile == null) {
return new ArrayList<>();
}
if (gpxFile.modifiedTime != modifiedTime) {
modifiedTime = gpxFile.modifiedTime;
GpxSelectionHelper selectedGpxHelper = app.getSelectedGpxHelper();
displayGroups = selectedGpxHelper.collectDisplayGroups(gpxFile);
originalGroups.clear();
for (GpxSelectionHelper.GpxDisplayGroup g : displayGroups) {
originalGroups.add(g.cloneInstance());
}
if (file != null) {
SelectedGpxFile sf = selectedGpxHelper.getSelectedFileByPath(gpxFile.path);
if (sf != null && file != null && sf.getDisplayGroups(app) != null) {
displayGroups = sf.getDisplayGroups(app);
}
}
}
if (useDisplayGroups) {
return displayGroups;
} else {
return originalGroups;
}
}
@NonNull
public List<GpxDisplayGroup> getOriginalGroups(GpxDisplayItemType[] filterTypes) {
return filterGroups(false, filterTypes);
}
@NonNull
public List<GpxDisplayGroup> getDisplayGroups(GpxDisplayItemType[] filterTypes) {
return filterGroups(true, filterTypes);
}
private boolean hasFilterType(GpxDisplayItemType filterType, GpxDisplayItemType[] filterTypes) {
for (GpxDisplayItemType type : filterTypes) {
if (type == filterType) {
return true;
}
}
return false;
}
@NonNull
private List<GpxDisplayGroup> filterGroups(boolean useDisplayGroups, GpxDisplayItemType[] filterTypes) {
List<GpxDisplayGroup> groups = new ArrayList<>();
for (GpxDisplayGroup group : getGpxFile(useDisplayGroups)) {
if (hasFilterType(group.getType(), filterTypes)) {
groups.add(group);
}
}
return groups;
}
public static List<GpxDisplayItem> flatten(List<GpxDisplayGroup> groups) {
ArrayList<GpxDisplayItem> list = new ArrayList<>();
for (GpxDisplayGroup g : groups) {
list.addAll(g.getModifiableList());
}
return list;
}
public void addNewGpxData(Activity activity) {
GPXFile gpxFile = getGpx();
GpxData gpxData = new GpxData(gpxFile);
WptPt pointToShow = gpxFile != null ? gpxFile.findPointToShow() : null;
if (pointToShow != null) {
LatLon location = new LatLon(pointToShow.getLatitude(), pointToShow.getLongitude());
final OsmandSettings settings = app.getSettings();
settings.setMapLocationToShow(location.getLatitude(), location.getLongitude(),
settings.getLastKnownMapZoom(),
new PointDescription(PointDescription.POINT_TYPE_WPT, activity.getString(R.string.add_line)),
false,
gpxData
);
MapActivity.launchMapActivityMoveToTop(activity);
}
}
}

View file

@ -0,0 +1,596 @@
package net.osmand.plus.track;
import android.content.DialogInterface;
import android.content.res.ColorStateList;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextView;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import net.osmand.AndroidUtils;
import net.osmand.GPXUtilities.GPXFile;
import net.osmand.GPXUtilities.Track;
import net.osmand.GPXUtilities.TrkSegment;
import net.osmand.GPXUtilities.WptPt;
import net.osmand.PlatformUtil;
import net.osmand.data.QuadRect;
import net.osmand.data.RotatedTileBox;
import net.osmand.plus.GPXDatabase.GpxDataItem;
import net.osmand.plus.GpxDbHelper;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayGroup;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayItemType;
import net.osmand.plus.GpxSelectionHelper.SelectedGpxFile;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.base.ContextMenuFragment;
import net.osmand.plus.base.ContextMenuScrollFragment;
import net.osmand.plus.helpers.AndroidUiHelper;
import net.osmand.plus.helpers.GpxUiHelper;
import net.osmand.plus.helpers.GpxUiHelper.GPXDataSetType;
import net.osmand.plus.helpers.GpxUiHelper.OrderedLineDataSet;
import net.osmand.plus.mapcontextmenu.other.TrackDetailsMenu;
import net.osmand.plus.measurementtool.GpxData;
import net.osmand.plus.measurementtool.MeasurementEditingContext;
import net.osmand.plus.measurementtool.MeasurementToolFragment;
import net.osmand.plus.myplaces.GPXTabItemType;
import net.osmand.plus.myplaces.SegmentActionsListener;
import net.osmand.plus.myplaces.SplitSegmentDialogFragment;
import net.osmand.plus.myplaces.TrackActivityFragmentAdapter;
import net.osmand.plus.routepreparationmenu.cards.BaseCard;
import net.osmand.plus.routepreparationmenu.cards.BaseCard.CardListener;
import net.osmand.plus.track.SaveGpxAsyncTask.SaveGpxListener;
import net.osmand.plus.widgets.IconPopupMenu;
import net.osmand.util.Algorithms;
import org.apache.commons.logging.Log;
import java.io.File;
import java.util.List;
import static net.osmand.plus.activities.TrackActivity.CURRENT_RECORDING;
import static net.osmand.plus.activities.TrackActivity.TRACK_FILE_NAME;
public class TrackMenuFragment extends ContextMenuScrollFragment implements CardListener, SegmentActionsListener {
public static final String TAG = TrackMenuFragment.class.getName();
private static final Log log = PlatformUtil.getLog(TrackMenuFragment.class);
private OsmandApplication app;
private TrackDisplayHelper displayHelper;
private GpxDataItem gpxDataItem;
private SelectedGpxFile selectedGpxFile;
private View routeMenuTopShadowAll;
private BottomNavigationView bottomNav;
private TrackMenuType menuType = TrackMenuType.TRACK;
private SegmentsCard segmentsCard;
private int menuTitleHeight;
public enum TrackMenuType {
TRACK(R.id.action_track, R.string.shared_string_gpx_tracks),
POINTS(R.id.action_points, R.string.shared_string_gpx_points);
TrackMenuType(@DrawableRes int iconId, @StringRes int titleId) {
this.iconId = iconId;
this.titleId = titleId;
}
public final int iconId;
public final int titleId;
}
@Override
public int getMainLayoutId() {
return R.layout.track_menu;
}
@Override
public int getHeaderViewHeight() {
return menuTitleHeight;
}
@Override
public boolean isHeaderViewDetached() {
return false;
}
@Override
public int getToolbarHeight() {
return 0;
}
public float getMiddleStateKoef() {
return 0.5f;
}
@Override
public int getSupportedMenuStatesPortrait() {
return MenuState.HEADER_ONLY | MenuState.HALF_SCREEN | MenuState.FULL_SCREEN;
}
public TrackDisplayHelper getDisplayHelper() {
return displayHelper;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
app = requireMyApplication();
GpxDbHelper gpxDbHelper = app.getGpxDbHelper();
displayHelper = new TrackDisplayHelper(app);
String gpxFilePath = "";
boolean currentRecording = false;
Bundle arguments = getArguments();
if (savedInstanceState != null) {
gpxFilePath = savedInstanceState.getString(TRACK_FILE_NAME);
currentRecording = savedInstanceState.getBoolean(CURRENT_RECORDING, false);
} else if (arguments != null) {
gpxFilePath = arguments.getString(TRACK_FILE_NAME);
currentRecording = arguments.getBoolean(CURRENT_RECORDING, false);
}
if (currentRecording) {
selectedGpxFile = app.getSavingTrackHelper().getCurrentTrack();
} else {
File file = new File(gpxFilePath);
displayHelper.setFile(file);
gpxDataItem = gpxDbHelper.getItem(file);
selectedGpxFile = app.getSelectedGpxHelper().getSelectedFileByPath(gpxFilePath);
}
displayHelper.setGpxDataItem(gpxDataItem);
displayHelper.setGpx(selectedGpxFile.getGpxFile());
}
public GPXFile getGpx() {
return displayHelper.getGpx();
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
if (view != null) {
bottomNav = view.findViewById(R.id.bottom_navigation);
routeMenuTopShadowAll = view.findViewById(R.id.route_menu_top_shadow_all);
TextView title = view.findViewById(R.id.title);
String fileName = Algorithms.getFileWithoutDirs(getGpx().path);
title.setText(GpxUiHelper.getGpxTitle(fileName));
if (isPortrait()) {
updateCardsLayout();
}
setupCards();
setupButtons(view);
enterTrackAppearanceMode();
runLayoutListener();
}
return view;
}
private void setupCards() {
MapActivity mapActivity = getMapActivity();
if (mapActivity != null) {
ViewGroup cardsContainer = getCardsContainer();
cardsContainer.removeAllViews();
if (menuType == TrackMenuType.TRACK) {
if (segmentsCard != null) {
cardsContainer.addView(segmentsCard.getView());
} else {
segmentsCard = new SegmentsCard(mapActivity, displayHelper, this);
cardsContainer.addView(segmentsCard.build(mapActivity));
}
}
}
}
@Override
protected void calculateLayout(View view, boolean initLayout) {
menuTitleHeight = routeMenuTopShadowAll.getHeight()
+ bottomNav.getHeight();
super.calculateLayout(view, initLayout);
}
@Override
protected void setViewY(int y, boolean animated, boolean adjustMapPos) {
super.setViewY(y, animated, adjustMapPos);
updateStatusBarColor();
}
@Override
protected void updateMainViewLayout(int posY) {
super.updateMainViewLayout(posY);
updateStatusBarColor();
}
@Override
public boolean shouldShowMapControls(int menuState) {
return menuState == MenuState.HEADER_ONLY || menuState == MenuState.HALF_SCREEN;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
adjustMapPosition(getHeight());
}
@Override
public void onDestroyView() {
super.onDestroyView();
exitTrackAppearanceMode();
}
private void enterTrackAppearanceMode() {
MapActivity mapActivity = getMapActivity();
if (mapActivity != null) {
boolean portrait = AndroidUiHelper.isOrientationPortrait(mapActivity);
AndroidUiHelper.setVisibility(mapActivity, portrait ? View.INVISIBLE : View.GONE,
R.id.map_left_widgets_panel,
R.id.map_right_widgets_panel,
R.id.map_center_info);
}
}
private void exitTrackAppearanceMode() {
MapActivity mapActivity = getMapActivity();
if (mapActivity != null) {
AndroidUiHelper.setVisibility(mapActivity, View.VISIBLE,
R.id.map_left_widgets_panel,
R.id.map_right_widgets_panel,
R.id.map_center_info,
R.id.map_search_button);
}
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
outState.putString(TRACK_FILE_NAME, getGpx().path);
outState.putBoolean(CURRENT_RECORDING, selectedGpxFile.isShowCurrentTrack());
super.onSaveInstanceState(outState);
}
@Override
public int getStatusBarColorId() {
View view = getView();
if (view != null) {
boolean nightMode = isNightMode();
if (getViewY() <= getFullScreenTopPosY() || !isPortrait()) {
if (Build.VERSION.SDK_INT >= 23 && !nightMode) {
view.setSystemUiVisibility(view.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
return nightMode ? R.color.divider_color_dark : R.color.divider_color_light;
} else {
if (Build.VERSION.SDK_INT >= 23 && !nightMode) {
view.setSystemUiVisibility(view.getSystemUiVisibility() & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
}
}
return -1;
}
private void updateStatusBarColor() {
MapActivity mapActivity = getMapActivity();
if (mapActivity != null) {
mapActivity.updateStatusBarColor();
}
}
@Override
protected String getThemeInfoProviderTag() {
return TAG;
}
@Override
public void onCardLayoutNeeded(@NonNull BaseCard card) {
}
@Override
public void onCardPressed(@NonNull BaseCard card) {
}
@Override
public void onCardButtonPressed(@NonNull BaseCard card, int buttonIndex) {
}
@Override
protected int applyPosY(int currentY, boolean needCloseMenu, boolean needMapAdjust, int previousMenuState, int newMenuState, int dZoom, boolean animated) {
int y = super.applyPosY(currentY, needCloseMenu, needMapAdjust, previousMenuState, newMenuState, dZoom, animated);
if (needMapAdjust) {
adjustMapPosition(y);
}
return y;
}
@Override
protected void onHeaderClick() {
adjustMapPosition(getViewY());
}
private void adjustMapPosition(int y) {
MapActivity mapActivity = getMapActivity();
if (mapActivity != null && mapActivity.getMapView() != null) {
GPXFile gpxFile = getGpx();
QuadRect r = gpxFile.getRect();
RotatedTileBox tb = mapActivity.getMapView().getCurrentRotatedTileBox().copy();
int tileBoxWidthPx = 0;
int tileBoxHeightPx = 0;
if (!isPortrait()) {
tileBoxWidthPx = tb.getPixWidth() - getWidth();
} else {
int fHeight = getViewHeight() - y - AndroidUtils.getStatusBarHeight(mapActivity);
tileBoxHeightPx = tb.getPixHeight() - fHeight;
}
if (r.left != 0 && r.right != 0) {
mapActivity.getMapView().fitRectToMap(r.left, r.right, r.top, r.bottom, tileBoxWidthPx, tileBoxHeightPx, 0);
}
}
}
private void updateCardsLayout() {
View mainView = getMainView();
if (mainView != null) {
View topShadow = getTopShadow();
FrameLayout bottomContainer = getBottomContainer();
if (getCurrentMenuState() == MenuState.HEADER_ONLY) {
topShadow.setVisibility(View.INVISIBLE);
bottomContainer.setBackgroundDrawable(null);
} else {
topShadow.setVisibility(View.VISIBLE);
AndroidUtils.setBackground(mainView.getContext(), bottomContainer, isNightMode(), R.color.list_background_color_light, R.color.list_background_color_dark);
}
}
}
private void setupButtons(View view) {
ColorStateList navColorStateList = AndroidUtils.createBottomNavColorStateList(getContext(), isNightMode());
BottomNavigationView bottomNav = view.findViewById(R.id.bottom_navigation);
bottomNav.setItemIconTintList(navColorStateList);
bottomNav.setItemTextColor(navColorStateList);
bottomNav.setSelectedItemId(R.id.action_track);
bottomNav.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
for (TrackMenuType type : TrackMenuType.values()) {
if (type.iconId == item.getItemId()) {
menuType = type;
setupCards();
break;
}
}
return true;
}
});
}
@Override
public void updateContent() {
if (segmentsCard != null) {
segmentsCard.updateContent();
}
setupCards();
}
@Override
public void onChartTouch() {
getBottomScrollView().requestDisallowInterceptTouchEvent(true);
}
@Override
public void scrollBy(int px) {
}
@Override
public void onPointSelected(double lat, double lon) {
}
@Override
public void openSplitInterval(GpxDisplayItem gpxItem, TrkSegment trkSegment) {
FragmentManager fragmentManager = getFragmentManager();
if (fragmentManager != null) {
SplitSegmentDialogFragment.showInstance(fragmentManager, displayHelper, gpxItem, trkSegment);
}
}
@Override
public void openAnalyzeOnMap(GpxDisplayItem gpxItem, List<ILineDataSet> dataSets, GPXTabItemType tabType) {
WptPt wpt = null;
gpxItem.chartTypes = null;
if (dataSets != null && dataSets.size() > 0) {
gpxItem.chartTypes = new GPXDataSetType[dataSets.size()];
for (int i = 0; i < dataSets.size(); i++) {
OrderedLineDataSet orderedDataSet = (OrderedLineDataSet) dataSets.get(i);
gpxItem.chartTypes[i] = orderedDataSet.getDataSetType();
}
if (gpxItem.chartHighlightPos != -1) {
TrkSegment segment = null;
for (Track t : gpxItem.group.getGpx().tracks) {
for (TrkSegment s : t.segments) {
if (s.points.size() > 0 && s.points.get(0).equals(gpxItem.analysis.locationStart)) {
segment = s;
break;
}
}
if (segment != null) {
break;
}
}
if (segment != null) {
OrderedLineDataSet dataSet = (OrderedLineDataSet) dataSets.get(0);
float distance = gpxItem.chartHighlightPos * dataSet.getDivX();
for (WptPt p : segment.points) {
if (p.distance >= distance) {
wpt = p;
break;
}
}
}
}
}
if (wpt != null) {
gpxItem.locationOnMap = wpt;
} else {
gpxItem.locationOnMap = gpxItem.locationStart;
}
TrackDetailsMenu trackDetailsMenu = getMapActivity().getTrackDetailsMenu();
trackDetailsMenu.setGpxItem(gpxItem);
trackDetailsMenu.show();
close();
}
@Override
public void showOptionsPopupMenu(View view, final TrkSegment segment, final boolean confirmDeletion) {
FragmentActivity activity = getActivity();
if (activity != null) {
IconPopupMenu optionsPopupMenu = new IconPopupMenu(activity, view.findViewById(R.id.overflow_menu));
Menu menu = optionsPopupMenu.getMenu();
optionsPopupMenu.getMenuInflater().inflate(R.menu.track_segment_menu, menu);
menu.findItem(R.id.action_edit).setIcon(app.getUIUtilities().getThemedIcon(R.drawable.ic_action_edit_dark));
menu.findItem(R.id.action_delete).setIcon(app.getUIUtilities().getThemedIcon(R.drawable.ic_action_remove_dark));
optionsPopupMenu.setOnMenuItemClickListener(new IconPopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
int i = item.getItemId();
if (i == R.id.action_edit) {
editSegment(segment);
return true;
} else if (i == R.id.action_delete) {
FragmentActivity activity = getActivity();
if (!confirmDeletion) {
deleteAndSaveSegment(segment);
} else if (activity != null) {
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setMessage(R.string.recording_delete_confirm);
builder.setPositiveButton(R.string.shared_string_yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
deleteAndSaveSegment(segment);
}
});
builder.setNegativeButton(R.string.shared_string_cancel, null);
builder.show();
}
return true;
}
return false;
}
});
optionsPopupMenu.show();
}
}
private void editSegment(TrkSegment segment) {
GPXFile gpxFile = getGpx();
openPlanRoute(new GpxData(gpxFile));
close();
}
public void openPlanRoute(GpxData gpxData) {
QuadRect qr = gpxData.getRect();
getMapActivity().getMapView().fitRectToMap(qr.left, qr.right, qr.top, qr.bottom, (int) qr.width(), (int) qr.height(), 0);
MeasurementEditingContext editingContext = new MeasurementEditingContext();
editingContext.setGpxData(gpxData);
MeasurementToolFragment.showInstance(getFragmentManager(), editingContext);
}
private void deleteAndSaveSegment(TrkSegment segment) {
if (deleteSegment(segment)) {
GPXFile gpx = displayHelper.getGpx();
if (gpx != null) {
boolean showOnMap = TrackActivityFragmentAdapter.isGpxFileSelected(app, gpx);
SelectedGpxFile selectedGpxFile = app.getSelectedGpxHelper().selectGpxFile(gpx, showOnMap, false);
saveGpx(showOnMap ? selectedGpxFile : null, gpx);
}
}
}
private boolean deleteSegment(TrkSegment segment) {
if (segment != null) {
GPXFile gpx = displayHelper.getGpx();
if (gpx != null) {
return gpx.removeTrkSegment(segment);
}
}
return false;
}
private void saveGpx(final SelectedGpxFile selectedGpxFile, GPXFile gpxFile) {
new SaveGpxAsyncTask(new File(gpxFile.path), gpxFile, new SaveGpxListener() {
@Override
public void gpxSavingStarted() {
}
@Override
public void gpxSavingFinished(Exception errorMessage) {
if (selectedGpxFile != null) {
List<GpxDisplayGroup> groups = displayHelper.getDisplayGroups(new GpxDisplayItemType[] {GpxDisplayItemType.TRACK_SEGMENT});
if (groups != null) {
selectedGpxFile.setDisplayGroups(groups, app);
selectedGpxFile.processPoints(app);
}
}
updateContent();
}
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void close() {
try {
MapActivity mapActivity = getMapActivity();
if (mapActivity != null) {
FragmentManager fragmentManager = mapActivity.getSupportFragmentManager();
fragmentManager.beginTransaction().remove(this).commitAllowingStateLoss();
}
} catch (Exception e) {
log.error(e);
}
}
public static boolean showInstance(@NonNull MapActivity mapActivity, String path, boolean showCurrentTrack) {
try {
Bundle args = new Bundle();
args.putString(TRACK_FILE_NAME, path);
args.putBoolean(CURRENT_RECORDING, showCurrentTrack);
args.putInt(ContextMenuFragment.MENU_STATE_KEY, MenuState.HALF_SCREEN);
TrackMenuFragment fragment = new TrackMenuFragment();
fragment.setArguments(args);
mapActivity.getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragmentContainer, fragment, fragment.getFragmentTag())
.addToBackStack(fragment.getFragmentTag())
.commitAllowingStateLoss();
return true;
} catch (RuntimeException e) {
return false;
}
}
}

View file

@ -14,6 +14,7 @@ import androidx.fragment.app.FragmentManager;
import net.osmand.AndroidUtils;
import net.osmand.GPXUtilities;
import net.osmand.GPXUtilities.WptPt;
import net.osmand.data.LatLon;
import net.osmand.data.PointDescription;
import net.osmand.plus.OsmandApplication;
@ -22,6 +23,7 @@ import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.wikipedia.WikiArticleHelper;
import net.osmand.plus.wikivoyage.article.WikivoyageArticleDialogFragment;
import net.osmand.plus.wikivoyage.data.TravelArticle;
import net.osmand.plus.wikivoyage.data.TravelArticle.TravelArticleIdentifier;
import net.osmand.plus.wikivoyage.explore.WikivoyageExploreActivity;
import java.io.File;
@ -67,8 +69,8 @@ public class WikivoyageWebViewClient extends WebViewClient {
if (url.contains(WIKIVOYAGE_DOMAIN) && isWebPage) {
String lang = WikiArticleHelper.getLang(url);
String articleName = WikiArticleHelper.getArticleNameFromUrl(url, lang);
String articleId = app.getTravelHelper().getArticleId(articleName, lang);
if (!articleId.isEmpty()) {
TravelArticleIdentifier articleId = app.getTravelHelper().getArticleId(articleName, lang);
if (articleId != null) {
WikivoyageArticleDialogFragment.showInstance(app, fragmentManager, articleId, lang);
} else {
WikiArticleHelper.warnAboutExternalLoad(url, activity, nightMode);
@ -83,8 +85,8 @@ public class WikivoyageWebViewClient extends WebViewClient {
WikiArticleHelper.warnAboutExternalLoad(url, activity, nightMode);
} else if (url.startsWith(PREFIX_GEO)) {
if (article != null) {
List<GPXUtilities.WptPt> points = article.getGpxFile().getPoints();
GPXUtilities.WptPt gpxPoint = null;
List<WptPt> points = article.getGpxFile().getPoints();
WptPt gpxPoint = null;
String coordinates = url.replace(PREFIX_GEO, "");
double lat;
double lon;
@ -96,7 +98,7 @@ public class WikivoyageWebViewClient extends WebViewClient {
Log.w(TAG, e.getMessage(), e);
return true;
}
for (GPXUtilities.WptPt point : points) {
for (WptPt point : points) {
if (point.getLatitude() == lat && point.getLongitude() == lon) {
gpxPoint = point;
break;

View file

@ -30,17 +30,18 @@ import net.osmand.AndroidUtils;
import net.osmand.IndexConstants;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.OsmandPlugin;
import net.osmand.plus.UiUtilities;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.R;
import net.osmand.plus.UiUtilities;
import net.osmand.plus.activities.TrackActivity;
import net.osmand.plus.development.OsmandDevelopmentPlugin;
import net.osmand.plus.helpers.FileNameTranslationHelper;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.wikipedia.WikiArticleBaseDialogFragment;
import net.osmand.plus.wikipedia.WikiArticleHelper;
import net.osmand.plus.wikivoyage.WikivoyageShowPicturesDialogFragment;
import net.osmand.plus.wikivoyage.WikivoyageWebViewClient;
import net.osmand.plus.wikivoyage.data.TravelArticle;
import net.osmand.plus.wikivoyage.data.TravelArticle.TravelArticleIdentifier;
import net.osmand.plus.wikivoyage.data.TravelHelper;
import net.osmand.plus.wikivoyage.data.TravelLocalDataHelper;
import net.osmand.util.Algorithms;
@ -58,15 +59,15 @@ public class WikivoyageArticleDialogFragment extends WikiArticleBaseDialogFragme
public static final String TAG = "WikivoyageArticleDialogFragment";
private static final String ROUTE_ID_KEY = "route_id_key";
private static final String LANGS_KEY = "langs_key";
private static final String SELECTED_LANG_KEY = "selected_lang_key";
private static final String ARTICLE_ID_KEY = "article_id";
private static final String LANGS_KEY = "langs";
private static final String SELECTED_LANG_KEY = "selected_lang";
private static final String EMPTY_URL = "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d4//";
private static final int MENU_ITEM_SHARE = 0;
private String routeId = "";
private TravelArticleIdentifier articleId;
private ArrayList<String> langs;
private String selectedLang;
private TravelArticle article;
@ -192,15 +193,17 @@ public class WikivoyageArticleDialogFragment extends WikiArticleBaseDialogFragme
if (requestCode == WikivoyageArticleContentsFragment.SHOW_CONTENT_ITEM_REQUEST_CODE) {
String link = data.getStringExtra(WikivoyageArticleContentsFragment.CONTENT_ITEM_LINK_KEY);
String title = data.getStringExtra(WikivoyageArticleContentsFragment.CONTENT_ITEM_TITLE_KEY);
moveToAnchor(link, title);
if (title != null) {
moveToAnchor(link, title);
}
} else if (requestCode == WikivoyageShowPicturesDialogFragment.SHOW_PICTURES_CHANGED_REQUEST_CODE) {
updateWebSettings();
populateArticle();
} else if (requestCode == WikivoyageArticleNavigationFragment.OPEN_ARTICLE_REQUEST_CODE) {
String tripId = data.getStringExtra(WikivoyageArticleNavigationFragment.ROUTE_ID_KEY);
TravelArticleIdentifier articleId = data.getParcelableExtra(WikivoyageArticleNavigationFragment.ARTICLE_ID_KEY);
String selectedLang = data.getStringExtra(WikivoyageArticleNavigationFragment.SELECTED_LANG_KEY);
if (!Algorithms.isEmpty(tripId) && !TextUtils.isEmpty(selectedLang)) {
this.routeId = tripId;
if (articleId != null && !TextUtils.isEmpty(selectedLang)) {
this.articleId = articleId;
this.selectedLang = selectedLang;
populateArticle();
}
@ -286,21 +289,21 @@ public class WikivoyageArticleDialogFragment extends WikiArticleBaseDialogFragme
@Override
protected void populateArticle() {
if (Algorithms.isEmpty(routeId) || langs == null) {
if (articleId == null || langs == null) {
Bundle args = getArguments();
if (args != null) {
routeId = args.getString(ROUTE_ID_KEY);
articleId = args.getParcelable(ARTICLE_ID_KEY);
langs = args.getStringArrayList(LANGS_KEY);
}
}
if (Algorithms.isEmpty(routeId) || langs == null || langs.isEmpty()) {
if (articleId == null || langs == null || langs.isEmpty()) {
return;
}
if (selectedLang == null) {
selectedLang = langs.get(0);
}
articleToolbarText.setText("");
article = getMyApplication().getTravelHelper().getArticleById(routeId, selectedLang);
article = getMyApplication().getTravelHelper().getArticleById(articleId, selectedLang);
if (article == null) {
return;
}
@ -366,34 +369,34 @@ public class WikivoyageArticleDialogFragment extends WikiArticleBaseDialogFragme
}
public static boolean showInstanceByTitle(@NonNull OsmandApplication app,
@NonNull FragmentManager fm,
@NonNull String title,
@NonNull String lang) {
String articleId = app.getTravelHelper().getArticleId(title, lang);
@NonNull FragmentManager fm,
@NonNull String title,
@NonNull String lang) {
TravelArticleIdentifier articleId = app.getTravelHelper().getArticleId(title, lang);
return showInstance(app, fm, articleId, lang);
}
public static boolean showInstance(@NonNull OsmandApplication app,
@NonNull FragmentManager fm,
@NonNull String routeId,
@NonNull TravelArticleIdentifier articleId,
@Nullable String selectedLang) {
ArrayList<String> langs = app.getTravelHelper().getArticleLangs(routeId);
return showInstance(fm, routeId, langs, selectedLang);
ArrayList<String> langs = app.getTravelHelper().getArticleLangs(articleId);
return showInstance(fm, articleId, langs, selectedLang);
}
public static boolean showInstance(@NonNull FragmentManager fm,
String routeId,
@NonNull TravelArticleIdentifier articleId,
@NonNull ArrayList<String> langs) {
return showInstance(fm, routeId, langs, null);
return showInstance(fm, articleId, langs, null);
}
public static boolean showInstance(@NonNull FragmentManager fm,
String routeId,
@NonNull ArrayList<String> langs,
@Nullable String selectedLang) {
private static boolean showInstance(@NonNull FragmentManager fm,
@NonNull TravelArticleIdentifier articleId,
@NonNull ArrayList<String> langs,
@Nullable String selectedLang) {
try {
Bundle args = new Bundle();
args.putString(ROUTE_ID_KEY, routeId);
args.putParcelable(ARTICLE_ID_KEY, articleId);
args.putStringArrayList(LANGS_KEY, langs);
if (langs.contains(selectedLang)) {
args.putString(SELECTED_LANG_KEY, selectedLang);
@ -416,7 +419,7 @@ public class WikivoyageArticleDialogFragment extends WikiArticleBaseDialogFragme
return;
}
WikivoyageArticleNavigationFragment.showInstance(fm,
WikivoyageArticleDialogFragment.this, routeId, selectedLang);
WikivoyageArticleDialogFragment.this, articleId, selectedLang);
}
}

View file

@ -26,6 +26,7 @@ import net.osmand.plus.base.MenuBottomSheetDialogFragment;
import net.osmand.plus.base.bottomsheetmenu.SimpleBottomSheetItem;
import net.osmand.plus.base.bottomsheetmenu.simpleitems.TitleItem;
import net.osmand.plus.wikivoyage.data.TravelArticle;
import net.osmand.plus.wikivoyage.data.TravelArticle.TravelArticleIdentifier;
import net.osmand.plus.wikivoyage.data.WikivoyageSearchResult;
import net.osmand.util.Algorithms;
@ -38,14 +39,14 @@ public class WikivoyageArticleNavigationFragment extends MenuBottomSheetDialogFr
public static final String TAG = WikivoyageArticleNavigationFragment.class.getSimpleName();
public static final String ROUTE_ID_KEY = "route_id_key";
public static final String SELECTED_LANG_KEY = "selected_lang_key";
public static final String ARTICLE_ID_KEY = "article_id";
public static final String SELECTED_LANG_KEY = "selected_lang";
public static final int OPEN_ARTICLE_REQUEST_CODE = 2;
private static final long UNDEFINED = -1;
private String routeId = "";
private TravelArticleIdentifier articleId;
private String selectedLang;
private TravelArticle article;
private List<String> parentsList;
@ -61,26 +62,27 @@ public class WikivoyageArticleNavigationFragment extends MenuBottomSheetDialogFr
if (savedInstanceState != null) {
selectedLang = savedInstanceState.getString(SELECTED_LANG_KEY);
routeId = savedInstanceState.getString(ROUTE_ID_KEY);
articleId = savedInstanceState.getParcelable(ARTICLE_ID_KEY);
} else {
Bundle args = getArguments();
if (args != null) {
selectedLang = args.getString(SELECTED_LANG_KEY);
routeId = args.getString(ROUTE_ID_KEY);
articleId = args.getParcelable(ARTICLE_ID_KEY);
}
}
if (Algorithms.isEmpty(routeId) || TextUtils.isEmpty(selectedLang)) {
if (articleId == null || TextUtils.isEmpty(selectedLang)) {
return;
}
article = getMyApplication().getTravelHelper().getArticleById(routeId, selectedLang);
article = getMyApplication().getTravelHelper().getArticleById(articleId, selectedLang);
if (article == null) {
return;
}
parentsList = new ArrayList<>(Arrays.asList(article.getAggregatedPartOf().split(",")));
Map<WikivoyageSearchResult, List<WikivoyageSearchResult>> navigationMap = getMyApplication().getTravelHelper().getNavigationMap(article);
Map<WikivoyageSearchResult, List<WikivoyageSearchResult>> navigationMap
= getMyApplication().getTravelHelper().getNavigationMap(article);
items.add(new TitleItem(getString(R.string.shared_string_navigation)));
@ -102,7 +104,7 @@ public class WikivoyageArticleNavigationFragment extends MenuBottomSheetDialogFr
public boolean onChildClick(ExpandableListView parent, View v,
int groupPosition, int childPosition, long id) {
WikivoyageSearchResult articleItem = listAdapter.getArticleItem(groupPosition, childPosition);
sendResults(articleItem.getRouteId());
sendResults(articleItem.getArticleId());
dismiss();
return true;
}
@ -111,10 +113,10 @@ public class WikivoyageArticleNavigationFragment extends MenuBottomSheetDialogFr
@Override
public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) {
WikivoyageSearchResult articleItem = (WikivoyageSearchResult) listAdapter.getGroup(groupPosition);
if (Algorithms.isEmpty(articleItem.getRouteId())) {
if (Algorithms.isEmpty(articleItem.getArticleRouteId())) {
Toast.makeText(getContext(), R.string.wiki_article_not_found, Toast.LENGTH_LONG).show();
} else {
sendResults(articleItem.getRouteId());
sendResults(articleItem.getArticleId());
dismiss();
}
return true;
@ -134,7 +136,7 @@ public class WikivoyageArticleNavigationFragment extends MenuBottomSheetDialogFr
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(ROUTE_ID_KEY, routeId);
outState.putParcelable(ARTICLE_ID_KEY, articleId);
outState.putString(SELECTED_LANG_KEY, selectedLang);
}
@ -148,17 +150,17 @@ public class WikivoyageArticleNavigationFragment extends MenuBottomSheetDialogFr
return nightMode ? R.color.wikivoyage_bottom_bar_bg_dark : R.color.list_background_color_light;
}
private void sendResults(String routeId) {
WikivoyageArticleDialogFragment.showInstance(getMyApplication(), getFragmentManager(), routeId, selectedLang);
private void sendResults(TravelArticleIdentifier articleId) {
WikivoyageArticleDialogFragment.showInstance(getMyApplication(), getFragmentManager(), articleId, selectedLang);
}
public static boolean showInstance(@NonNull FragmentManager fm,
@Nullable Fragment targetFragment,
String routeId,
@NonNull TravelArticleIdentifier articleId,
@NonNull String selectedLang) {
try {
Bundle args = new Bundle();
args.putString(ROUTE_ID_KEY, routeId);
args.putParcelable(ARTICLE_ID_KEY, articleId);
args.putString(SELECTED_LANG_KEY, selectedLang);
WikivoyageArticleNavigationFragment fragment = new WikivoyageArticleNavigationFragment();
if (targetFragment != null) {
@ -204,7 +206,7 @@ public class WikivoyageArticleNavigationFragment extends MenuBottomSheetDialogFr
@Override
public Object getChild(int groupPosition, int childPosititon) {
return getArticleItem(groupPosition, childPosititon).getArticleTitles().get(0);
return getArticleItem(groupPosition, childPosititon).getArticleTitle();
}
@Override
@ -236,8 +238,8 @@ public class WikivoyageArticleNavigationFragment extends MenuBottomSheetDialogFr
public View getChildView(int groupPosition, final int childPosition,
boolean isLastChild, View convertView, ViewGroup parent) {
WikivoyageSearchResult articleItem = getArticleItem(groupPosition, childPosition);
String childTitle = articleItem.getArticleTitles().get(0);
boolean selected = Algorithms.stringsEqual(routeId, articleItem.getRouteId()) || parentsList.contains(childTitle);
String childTitle = articleItem.getArticleTitle();
boolean selected = articleItem.getArticleId().equals(articleId) || parentsList.contains(childTitle);
if (convertView == null) {
convertView = LayoutInflater.from(context)
@ -263,7 +265,7 @@ public class WikivoyageArticleNavigationFragment extends MenuBottomSheetDialogFr
@Override
public View getGroupView(final int groupPosition, final boolean isExpanded,
View convertView, ViewGroup parent) {
String groupTitle = ((WikivoyageSearchResult) getGroup(groupPosition)).getArticleTitles().get(0);
String groupTitle = ((WikivoyageSearchResult) getGroup(groupPosition)).getArticleTitle();
boolean selected = parentsList.contains(groupTitle) || article.getTitle().equals(groupTitle);
if (convertView == null) {
convertView = LayoutInflater.from(context)

View file

@ -1,5 +1,7 @@
package net.osmand.plus.wikivoyage.data;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import androidx.annotation.NonNull;
@ -7,13 +9,21 @@ import androidx.annotation.Nullable;
import androidx.annotation.Size;
import net.osmand.GPXUtilities.GPXFile;
import net.osmand.Location;
import net.osmand.aidl.search.SearchResult;
import net.osmand.data.LatLon;
import net.osmand.util.Algorithms;
import net.osmand.util.MapUtils;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.Objects;
public class TravelArticle {
@ -21,20 +31,30 @@ public class TravelArticle {
private static final String THUMB_PREFIX = "320px-";
private static final String REGULAR_PREFIX = "1280px-";//1280, 1024, 800
File file;
String title;
String content;
String isPartOf;
double lat;
double lon;
double lat = Double.NaN;
double lon = Double.NaN;
String imageTitle;
GPXFile gpxFile;
String routeId;
String routeSource;
long originalId;
String lang;
String contentsJson;
String aggregatedPartOf;
String fullContent;
@NonNull
public TravelArticleIdentifier generateIdentifier() {
return new TravelArticleIdentifier(this);
}
public File getFile() {
return file;
}
public String getTitle() {
return title;
@ -68,6 +88,10 @@ public class TravelArticle {
return routeId;
}
public String getRouteSource() {
return routeSource;
}
public long getOriginalId() {
return originalId;
}
@ -127,4 +151,92 @@ public class TravelArticle {
String md5 = new String(Hex.encodeHex(DigestUtils.md5(s)));
return new String[]{md5.substring(0, 1), md5.substring(0, 2)};
}
public static class TravelArticleIdentifier implements Parcelable {
@Nullable File file;
double lat;
double lon;
@Nullable String title;
@Nullable String routeId;
@Nullable String routeSource;
public static final Creator<TravelArticleIdentifier> CREATOR = new Creator<TravelArticleIdentifier>() {
@Override
public TravelArticleIdentifier createFromParcel(Parcel in) {
return new TravelArticleIdentifier(in);
}
@Override
public TravelArticleIdentifier[] newArray(int size) {
return new TravelArticleIdentifier[size];
}
};
private TravelArticleIdentifier(@NonNull Parcel in) {
readFromParcel(in);
}
private TravelArticleIdentifier(@NonNull TravelArticle article) {
file = article.file;
lat = article.lat;
lon = article.lon;
title = article.title;
routeId = article.routeId;
routeSource = article.routeSource;
}
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeDouble(lat);
out.writeDouble(lon);
out.writeString(title);
out.writeString(routeId);
out.writeString(routeSource);
out.writeString(file != null ? file.getAbsolutePath() : null);
}
private void readFromParcel(Parcel in) {
lat = in.readDouble();
lon = in.readDouble();
title = in.readString();
routeId = in.readString();
routeSource = in.readString();
String filePath = in.readString();
if (!Algorithms.isEmpty(filePath)) {
file = new File(filePath);
}
}
@Override
public int describeContents() {
return 0;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
TravelArticleIdentifier that = (TravelArticleIdentifier) o;
return areLatLonEqual(that.lat, that.lon, lat, lon) &&
Algorithms.objectEquals(file, that.file) &&
Algorithms.stringsEqual(title, that.title) &&
Algorithms.stringsEqual(routeId, that.routeId) &&
Algorithms.stringsEqual(routeSource, that.routeSource);
}
@Override
public int hashCode() {
return Algorithms.hash(file, lat, lon, title, routeId, routeSource);
}
private static boolean areLatLonEqual(double lat1, double lon1, double lat2, double lon2) {
boolean latEqual = (Double.isNaN(lat1) && Double.isNaN(lat2)) || Math.abs(lat1 - lat2) < 0.00001;
boolean lonEqual = (Double.isNaN(lon1) && Double.isNaN(lon2)) || Math.abs(lon1 - lon2) < 0.00001;
return latEqual && lonEqual;
}
}
}

View file

@ -16,10 +16,12 @@ import net.osmand.OsmAndCollator;
import net.osmand.PlatformUtil;
import net.osmand.data.Amenity;
import net.osmand.data.LatLon;
import net.osmand.data.QuadRect;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.api.SQLiteAPI.SQLiteConnection;
import net.osmand.plus.api.SQLiteAPI.SQLiteCursor;
import net.osmand.plus.wikivoyage.data.TravelArticle.TravelArticleIdentifier;
import net.osmand.util.Algorithms;
import net.osmand.util.MapUtils;
@ -251,12 +253,15 @@ public class TravelDbHelper implements TravelHelper {
if (cursor != null) {
if (cursor.moveToFirst()) {
do {
WikivoyageSearchResult rs = new WikivoyageSearchResult();
rs.routeId = cursor.getLong(0) + "";
rs.articleTitles.add(cursor.getString(1));
rs.langs.add(cursor.getString(2));
rs.isPartOf.add(cursor.getString(3));
rs.imageTitle = cursor.getString(4);
String routeId = cursor.getLong(0) + "";
String articleTitle = cursor.getString(1);
String lang = cursor.getString(2);
String isPartOf = cursor.getString(3);
String imageTitle = cursor.getString(4);
List<String> langs = new ArrayList<>();
langs.add(lang);
WikivoyageSearchResult rs = new WikivoyageSearchResult(routeId, articleTitle,
isPartOf, imageTitle, langs);
res.add(rs);
} while (cursor.moveToNext());
}
@ -392,12 +397,12 @@ public class TravelDbHelper implements TravelHelper {
Collections.sort(list, new Comparator<WikivoyageSearchResult>() {
@Override
public int compare(WikivoyageSearchResult o1, WikivoyageSearchResult o2) {
boolean c1 = CollatorStringMatcher.cmatches(collator, searchQuery, o1.articleTitles.get(0),
boolean c1 = CollatorStringMatcher.cmatches(collator, searchQuery, o1.getArticleTitle(),
StringMatcherMode.CHECK_ONLY_STARTS_WITH);
boolean c2 = CollatorStringMatcher.cmatches(collator, searchQuery, o2.articleTitles.get(0),
boolean c2 = CollatorStringMatcher.cmatches(collator, searchQuery, o2.getArticleTitle(),
StringMatcherMode.CHECK_ONLY_STARTS_WITH);
if (c1 == c2) {
return collator.compare(o1.articleTitles.get(0), o2.articleTitles.get(0));
return collator.compare(o1.getArticleTitle(), o2.getArticleTitle());
} else if (c1) {
return -1;
} else if (c2) {
@ -427,29 +432,28 @@ public class TravelDbHelper implements TravelHelper {
});
}
}
private Collection<WikivoyageSearchResult> groupSearchResultsByRouteId(List<WikivoyageSearchResult> res) {
String baseLng = application.getLanguage();
Map<String, WikivoyageSearchResult> wikivoyage = new HashMap<>();
for (WikivoyageSearchResult rs : res) {
WikivoyageSearchResult prev = wikivoyage.get(rs.routeId);
WikivoyageSearchResult prev = wikivoyage.get(rs.getArticleRouteId());
if (prev != null) {
int insInd = prev.langs.size();
boolean matchLang = false;
if (rs.langs.get(0).equals(baseLng)) {
insInd = 0;
matchLang = true;
} else if (rs.langs.get(0).equals("en")) {
if (!prev.langs.get(0).equals(baseLng)) {
insInd = 0;
} else {
insInd = 1;
matchLang = true;
}
}
prev.articleTitles.add(insInd, rs.articleTitles.get(0));
prev.langs.add(insInd, rs.langs.get(0));
prev.isPartOf.add(insInd, rs.isPartOf.get(0));
if (matchLang) {
prev.articleId.title = rs.getArticleTitle();
prev.isPartOf = rs.getIsPartOf();
}
prev.langs.add(matchLang ? 0 : 1, rs.langs.get(0));
} else {
wikivoyage.put(rs.routeId, rs);
wikivoyage.put(rs.getArticleRouteId(), rs);
}
}
return wikivoyage.values();
@ -457,12 +461,11 @@ public class TravelDbHelper implements TravelHelper {
@NonNull
@Override
public LinkedHashMap<WikivoyageSearchResult, List<WikivoyageSearchResult>> getNavigationMap(
@NonNull final TravelArticle article) {
public Map<WikivoyageSearchResult, List<WikivoyageSearchResult>> getNavigationMap(@NonNull final TravelArticle article) {
String lang = article.getLang();
String title = article.getTitle();
if (TextUtils.isEmpty(lang) || TextUtils.isEmpty(title)) {
return new LinkedHashMap<>();
return Collections.emptyMap();
}
String[] parts = null;
if (!TextUtils.isEmpty(article.getAggregatedPartOf())) {
@ -504,20 +507,20 @@ public class TravelDbHelper implements TravelHelper {
SQLiteCursor cursor = conn.rawQuery(query.toString(), params.toArray(new String[0]));
if (cursor != null && cursor.moveToFirst()) {
do {
WikivoyageSearchResult rs = new WikivoyageSearchResult();
rs.routeId = cursor.getLong(0) + "";
rs.articleTitles.add(cursor.getString(1));
rs.langs.add(cursor.getString(2));
rs.isPartOf.add(cursor.getString(3));
List<WikivoyageSearchResult> l = navMap.get(rs.isPartOf.get(0));
String routeId = cursor.getLong(0) + "";
String articleTitle = cursor.getString(1);
String articleLang = cursor.getString(2);
String isPartOf = cursor.getString(3);
WikivoyageSearchResult rs = new WikivoyageSearchResult(routeId, articleTitle,
isPartOf, null, Collections.singletonList(articleLang));
List<WikivoyageSearchResult> l = navMap.get(rs.isPartOf);
if (l == null) {
l = new ArrayList<>();
navMap.put(rs.isPartOf.get(0), l);
navMap.put(rs.isPartOf, l);
}
l.add(rs);
String key = rs.getArticleTitles().get(0);
if (headers != null && headers.contains(key)) {
headerObjs.put(key, rs);
if (headers != null && headers.contains(articleTitle)) {
headerObjs.put(articleTitle, rs);
}
} while (cursor.moveToNext());
}
@ -533,12 +536,10 @@ public class TravelDbHelper implements TravelHelper {
Collections.sort(results, new Comparator<WikivoyageSearchResult>() {
@Override
public int compare(WikivoyageSearchResult o1, WikivoyageSearchResult o2) {
return collator.compare(o1.articleTitles.get(0), o2.articleTitles.get(0));
return collator.compare(o1.getArticleTitle(), o2.getArticleTitle());
}
});
WikivoyageSearchResult emptyResult = new WikivoyageSearchResult();
emptyResult.articleTitles.add(header);
emptyResult.routeId = "";
WikivoyageSearchResult emptyResult = new WikivoyageSearchResult("", header, null, null, null);
searchResult = searchResult != null ? searchResult : emptyResult;
res.put(searchResult, results);
}
@ -548,9 +549,10 @@ public class TravelDbHelper implements TravelHelper {
@Override
@Nullable
public TravelArticle getArticleById(@NonNull String routeId, @NonNull String lang) {
public TravelArticle getArticleById(@NonNull TravelArticleIdentifier articleId, @NonNull String lang) {
TravelArticle res = null;
SQLiteConnection conn = openConnection();
String routeId = articleId.routeId;
if (conn != null && !Algorithms.isEmpty(routeId)) {
SQLiteCursor cursor = conn.rawQuery(ARTICLES_TABLE_SELECT + " WHERE " + ARTICLES_COL_TRIP_ID + " = ? AND "
+ ARTICLES_COL_LANG + " = ?", new String[] { routeId, lang });
@ -564,9 +566,21 @@ public class TravelDbHelper implements TravelHelper {
return res;
}
@Override
@Nullable
public TravelArticle getArticleByTitle(@NonNull String title, @NonNull String lang) {
@Override
public TravelArticle getArticleByTitle(@NonNull final String title, @NonNull final String lang) {
return getArticleByTitle(title, new QuadRect(), lang);
}
@Nullable
@Override
public TravelArticle getArticleByTitle(@NonNull final String title, @NonNull LatLon latLon, @NonNull final String lang) {
return getArticleByTitle(title, new QuadRect(), lang);
}
@Nullable
@Override
public TravelArticle getArticleByTitle(@NonNull final String title, @NonNull QuadRect rect, @NonNull final String lang) {
TravelArticle res = null;
SQLiteConnection conn = openConnection();
if (conn != null) {
@ -582,33 +596,32 @@ public class TravelDbHelper implements TravelHelper {
return res;
}
@NonNull
@Nullable
@Override
public String getArticleId(@NonNull String title, @NonNull String lang) {
String res = "";
public TravelArticleIdentifier getArticleId(@NonNull String title, @NonNull String lang) {
TravelArticle article = null;
SQLiteConnection conn = openConnection();
if (conn != null) {
SQLiteCursor cursor = conn.rawQuery("SELECT " + ARTICLES_COL_TRIP_ID + " FROM "
+ ARTICLES_TABLE_NAME + " WHERE " + ARTICLES_COL_TITLE + " = ? AND "
SQLiteCursor cursor = conn.rawQuery(ARTICLES_TABLE_SELECT + " WHERE " + ARTICLES_COL_TITLE + " = ? AND "
+ ARTICLES_COL_LANG + " = ?", new String[]{title, lang});
if (cursor != null) {
if (cursor.moveToFirst()) {
res = cursor.getLong(0) + "";
article = readArticle(cursor);
}
cursor.close();
}
}
return res;
return article != null ? article.generateIdentifier() : null;
}
@NonNull
@Override
public ArrayList<String> getArticleLangs(@NonNull String routeId) {
public ArrayList<String> getArticleLangs(@NonNull TravelArticleIdentifier articleId) {
ArrayList<String> res = new ArrayList<>();
SQLiteConnection conn = openConnection();
if (conn != null) {
SQLiteCursor cursor = conn.rawQuery("SELECT " + ARTICLES_COL_LANG + " FROM " + ARTICLES_TABLE_NAME
+ " WHERE " + ARTICLES_COL_TRIP_ID + " = ?", new String[]{routeId});
+ " WHERE " + ARTICLES_COL_TRIP_ID + " = ?", new String[]{articleId.routeId});
if (cursor != null) {
if (cursor.moveToFirst()) {
String baseLang = application.getLanguage();

View file

@ -3,6 +3,10 @@ package net.osmand.plus.wikivoyage.data;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import net.osmand.data.LatLon;
import net.osmand.data.QuadRect;
import net.osmand.plus.wikivoyage.data.TravelArticle.TravelArticleIdentifier;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
@ -28,16 +32,22 @@ public interface TravelHelper {
Map<WikivoyageSearchResult, List<WikivoyageSearchResult>> getNavigationMap(@NonNull final TravelArticle article);
@Nullable
TravelArticle getArticleById(@NonNull String routeId, @NonNull String lang);
TravelArticle getArticleById(@NonNull TravelArticleIdentifier articleId, @NonNull String lang);
@Nullable
TravelArticle getArticleByTitle(@NonNull String title, @NonNull String lang);
public TravelArticle getArticleByTitle(@NonNull final String title, @NonNull final String lang);
@Nullable
public TravelArticle getArticleByTitle(@NonNull final String title, @NonNull LatLon latLon, @NonNull final String lang);
@Nullable
TravelArticle getArticleByTitle(@NonNull String title, @NonNull QuadRect rect, @NonNull String lang);
@Nullable
TravelArticleIdentifier getArticleId(@NonNull String title, @NonNull String lang);
@NonNull
String getArticleId(@NonNull String title, @NonNull String lang);
@NonNull
ArrayList<String> getArticleLangs(@NonNull String routeId);
ArrayList<String> getArticleLangs(@NonNull TravelArticleIdentifier articleId);
@NonNull
String getGPXName(@NonNull final TravelArticle article);

View file

@ -1,10 +1,11 @@
package net.osmand.plus.wikivoyage.data;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import net.osmand.Collator;
import net.osmand.CollatorStringMatcher;
import net.osmand.GPXUtilities;
import net.osmand.GPXUtilities.GPXFile;
import net.osmand.IndexConstants;
@ -13,10 +14,13 @@ import net.osmand.PlatformUtil;
import net.osmand.ResultMatcher;
import net.osmand.binary.BinaryMapIndexReader;
import net.osmand.binary.BinaryMapIndexReader.SearchPoiTypeFilter;
import net.osmand.binary.BinaryMapIndexReader.SearchRequest;
import net.osmand.data.Amenity;
import net.osmand.data.LatLon;
import net.osmand.data.QuadRect;
import net.osmand.osm.PoiCategory;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.wikivoyage.data.TravelArticle.TravelArticleIdentifier;
import net.osmand.util.Algorithms;
import net.osmand.util.MapUtils;
@ -25,35 +29,40 @@ import org.apache.commons.logging.Log;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import static net.osmand.CollatorStringMatcher.StringMatcherMode.CHECK_EQUALS_FROM_SPACE;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class TravelObfHelper implements TravelHelper {
private static final Log LOG = PlatformUtil.getLog(TravelObfHelper.class);
private static final String WORLD_WIKIVOYAGE_FILE_NAME = "World_wikivoyage.travel.obf";
private static final String ROUTE_ARTICLE = "route_article";
private static final int SEARCH_RADIUS = 100000;
public static final String ROUTE_ARTICLE = "route_article";
public static final int POPULAR_ARTICLES_SEARCH_RADIUS = 100000;
public static final int ARTICLE_SEARCH_RADIUS = 50000;
public static final int MAX_POPULAR_ARTICLES_COUNT = 100;
private final OsmandApplication app;
private final Collator collator;
private List<TravelArticle> popularArticles = new ArrayList<>();
private final Map<String, TravelArticle> cachedArticles;
private Map<TravelArticleIdentifier, Map<String, TravelArticle>> cachedArticles = new ConcurrentHashMap<>();
private final TravelLocalDataHelper localDataHelper;
public TravelObfHelper(OsmandApplication app) {
this.app = app;
collator = OsmAndCollator.primaryCollator();
localDataHelper = new TravelLocalDataHelper(app);
cachedArticles = new HashMap<>();
}
@Override
@ -73,22 +82,23 @@ public class TravelObfHelper implements TravelHelper {
@NonNull
public List<TravelArticle> loadPopularArticles() {
String language = app.getLanguage();
String lang = app.getLanguage();
List<TravelArticle> popularArticles = new ArrayList<>();
for (BinaryMapIndexReader travelBookReader : getTravelBookReaders()) {
for (BinaryMapIndexReader reader : getReaders()) {
try {
final LatLon location = app.getMapViewTrackingUtilities().getMapLocation();
BinaryMapIndexReader.SearchRequest<Amenity> req = BinaryMapIndexReader.buildSearchPoiRequest(
location, SEARCH_RADIUS, -1, getSearchRouteArticleFilter(), null);
List<Amenity> amenities = travelBookReader.searchPoi(req);
SearchRequest<Amenity> req = BinaryMapIndexReader.buildSearchPoiRequest(
location, POPULAR_ARTICLES_SEARCH_RADIUS, -1, getSearchRouteArticleFilter(), null);
List<Amenity> amenities = reader.searchPoi(req);
if (amenities.size() > 0) {
for (Amenity a : amenities) {
if (!Algorithms.isEmpty(a.getName(language))) {
TravelArticle article = readArticle(a, language);
popularArticles.add(article);
cachedArticles.put(article.routeId, article);
if (popularArticles.size() >= 100) {
break;
for (Amenity amenity : amenities) {
if (!Algorithms.isEmpty(amenity.getName(lang))) {
TravelArticle article = cacheTravelArticles(reader.getFile(), amenity, lang);
if (article != null) {
popularArticles.add(article);
if (popularArticles.size() >= MAX_POPULAR_ARTICLES_COUNT) {
break;
}
}
}
}
@ -104,13 +114,25 @@ public class TravelObfHelper implements TravelHelper {
});
}
} catch (Exception e) {
LOG.error(e.getMessage());
LOG.error(e.getMessage(), e);
}
}
this.popularArticles = popularArticles;
return popularArticles;
}
@Nullable
private TravelArticle cacheTravelArticles(File file, Amenity amenity, String lang) {
TravelArticle article = null;
Map<String, TravelArticle> articles = readArticles(file, amenity);
if (!Algorithms.isEmpty(articles)) {
TravelArticleIdentifier newArticleId = articles.values().iterator().next().generateIdentifier();
cachedArticles.put(newArticleId, articles);
article = getCachedArticle(newArticleId, lang);
}
return article;
}
SearchPoiTypeFilter getSearchRouteArticleFilter() {
return new SearchPoiTypeFilter() {
@Override
@ -125,23 +147,29 @@ public class TravelObfHelper implements TravelHelper {
};
}
private TravelArticle readArticle(@NonNull Amenity amenity, @Nullable String lang) {
TravelArticle res = new TravelArticle();
String title = Algorithms.isEmpty(amenity.getName(lang)) ? amenity.getName() : amenity.getName(lang);
if (Algorithms.isEmpty(title)) {
Map<String, String> namesMap = amenity.getNamesMap(true);
if (!namesMap.isEmpty()) {
lang = namesMap.keySet().iterator().next();
title = amenity.getName(lang);
}
@NonNull
private Map<String, TravelArticle> readArticles(@NonNull File file, @NonNull Amenity amenity) {
Map<String, TravelArticle> articles = new HashMap<>();
Set<String> langs = getLanguages(amenity);
for (String lang : langs) {
articles.put(lang, readArticle(file, amenity, lang));
}
res.title = title;
return articles;
}
@NonNull
private TravelArticle readArticle(@NonNull File file, @NonNull Amenity amenity, @Nullable String lang) {
TravelArticle res = new TravelArticle();
res.file = file;
String title = amenity.getName(lang);
res.title = Algorithms.isEmpty(title) ? amenity.getName() : title;
res.content = amenity.getDescription(lang);
res.isPartOf = emptyIfNull(amenity.getTagContent(Amenity.IS_PART, lang));
res.lat = amenity.getLocation().getLatitude();
res.lon = amenity.getLocation().getLongitude();
res.imageTitle = emptyIfNull(amenity.getTagContent(Amenity.IMAGE_TITLE, lang));
res.routeId = getRouteId(amenity);
res.imageTitle = emptyIfNull(amenity.getTagContent(Amenity.IMAGE_TITLE, null));
res.routeId = emptyIfNull(amenity.getTagContent(Amenity.ROUTE_ID, null));
res.routeSource = emptyIfNull(amenity.getTagContent(Amenity.ROUTE_SOURCE, null));
res.originalId = 0;
res.lang = lang;
res.contentsJson = emptyIfNull(amenity.getTagContent(Amenity.CONTENT_JSON, lang));
@ -153,85 +181,72 @@ public class TravelObfHelper implements TravelHelper {
return text == null ? "" : text;
}
private String getRouteId(Amenity amenity) {
return amenity.getTagContent(Amenity.ROUTE_ID, null);
}
@Override
public boolean isAnyTravelBookPresent() {
return !Algorithms.isEmpty(getTravelBookReaders());
return !Algorithms.isEmpty(getReaders());
}
@NonNull
@Override
public List<WikivoyageSearchResult> search(@NonNull String searchQuery) {
List<WikivoyageSearchResult> res = new ArrayList<>();
List<Amenity> searchObjects = null;
for (BinaryMapIndexReader reader : app.getResourceManager().getTravelRepositories()) {
Map<File, List<Amenity>> amenityMap = new HashMap<>();
for (BinaryMapIndexReader reader : getReaders()) {
try {
BinaryMapIndexReader.SearchRequest<Amenity> searchRequest = BinaryMapIndexReader.
buildSearchPoiRequest(0, 0, searchQuery,
SearchRequest<Amenity> searchRequest = BinaryMapIndexReader.buildSearchPoiRequest(0, 0, searchQuery,
0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, getSearchRouteArticleFilter(), null, null);
searchObjects = reader.searchPoiByName(searchRequest);
List<Amenity> amenities = reader.searchPoiByName(searchRequest);
if (!Algorithms.isEmpty(amenities)) {
amenityMap.put(reader.getFile(), amenities);
}
} catch (IOException e) {
LOG.error(e);
LOG.error(e.getMessage(), e);
}
}
if (!Algorithms.isEmpty(searchObjects)) {
String baseLng = app.getLanguage();
for (Amenity obj : searchObjects) {
WikivoyageSearchResult r = new WikivoyageSearchResult();
TravelArticle article = readArticle(obj, baseLng);
r.articleTitles = new ArrayList<>(Collections.singletonList(article.title));
r.imageTitle = article.imageTitle;
r.routeId = article.routeId;
r.isPartOf = new ArrayList<>(Collections.singletonList(article.isPartOf));
r.langs = new ArrayList<>(Collections.singletonList(baseLng));
res.add(r);
cachedArticles.put(article.routeId, article);
if (!Algorithms.isEmpty(amenityMap)) {
String appLang = app.getLanguage();
for (Entry<File, List<Amenity>> entry : amenityMap.entrySet()) {
File file = entry.getKey();
for (Amenity amenity : entry.getValue()) {
Set<String> nameLangs = getLanguages(amenity);
if (nameLangs.contains(appLang)) {
TravelArticle article = readArticle(file, amenity, appLang);
WikivoyageSearchResult r = new WikivoyageSearchResult(article, new ArrayList<>(nameLangs));
res.add(r);
}
}
}
res = new ArrayList<>(groupSearchResultsByRouteId(res));
sortSearchResults(res);
}
return res;
}
private void sortSearchResults(@NonNull List<WikivoyageSearchResult> list) {
Collections.sort(list, new Comparator<WikivoyageSearchResult>() {
@Override
public int compare(WikivoyageSearchResult res1, WikivoyageSearchResult res2) {
return collator.compare(res1.articleTitles.get(0), res2.articleTitles.get(0));
}
});
}
@NonNull
private Collection<WikivoyageSearchResult> groupSearchResultsByRouteId(@NonNull List<WikivoyageSearchResult> res) {
String baseLng = app.getLanguage();
Map<String, WikivoyageSearchResult> wikivoyage = new HashMap<>();
for (WikivoyageSearchResult rs : res) {
WikivoyageSearchResult prev = wikivoyage.get(rs.routeId);
if (prev != null) {
int insInd = prev.langs.size();
if (rs.langs.get(0).equals(baseLng)) {
insInd = 0;
} else if (rs.langs.get(0).equals("en")) {
if (!prev.langs.get(0).equals(baseLng)) {
insInd = 0;
} else {
insInd = 1;
}
private Set<String> getLanguages(@NonNull Amenity amenity) {
Set<String> langs = new HashSet<>();
String descrStart = Amenity.DESCRIPTION + ":";
String partStart = Amenity.IS_PART + ":";
for (String infoTag : amenity.getAdditionalInfoKeys()) {
if (infoTag.startsWith(descrStart)) {
if (infoTag.length() > descrStart.length()) {
langs.add(infoTag.substring(descrStart.length()));
}
} else if (infoTag.startsWith(partStart)) {
if (infoTag.length() > partStart.length()) {
langs.add(infoTag.substring(partStart.length()));
}
prev.articleTitles.add(insInd, rs.articleTitles.get(0));
prev.langs.add(insInd, rs.langs.get(0));
prev.isPartOf.add(insInd, rs.isPartOf.get(0));
} else {
wikivoyage.put(rs.routeId, rs);
}
}
return wikivoyage.values();
return langs;
}
private void sortSearchResults(@NonNull List<WikivoyageSearchResult> list) {
Collections.sort(list, new Comparator<WikivoyageSearchResult>() {
@Override
public int compare(WikivoyageSearchResult res1, WikivoyageSearchResult res2) {
return collator.compare(res1.articleId.title, res2.articleId.title);
}
});
}
@NonNull
@ -243,74 +258,153 @@ public class TravelObfHelper implements TravelHelper {
@NonNull
@Override
public Map<WikivoyageSearchResult, List<WikivoyageSearchResult>> getNavigationMap(@NonNull final TravelArticle article) {
return Collections.emptyMap();
}
@Override
public TravelArticle getArticleById(@NonNull String routeId, @NonNull String lang) {
TravelArticle article = cachedArticles.get(routeId);
if (article != null) {
return article;
final String lang = article.getLang();
final String title = article.getTitle();
if (TextUtils.isEmpty(lang) || TextUtils.isEmpty(title)) {
return Collections.emptyMap();
}
article = getArticleByIdFromTravelBooks(routeId, lang);
if (article != null) {
return getArticleByIdFromTravelBooks(routeId, lang);
final String[] parts;
if (!TextUtils.isEmpty(article.getAggregatedPartOf())) {
String[] originalParts = article.getAggregatedPartOf().split(",");
if (originalParts.length > 1) {
parts = new String[originalParts.length];
for (int i = 0; i < originalParts.length; i++) {
parts[i] = originalParts[originalParts.length - i - 1];
}
} else {
parts = originalParts;
}
} else {
parts = null;
}
return localDataHelper.getSavedArticle(routeId, lang);
}
private TravelArticle getArticleByIdFromTravelBooks(final String routeId, final String lang) {
TravelArticle article = null;
final List<Amenity> amenities = new ArrayList<>();
for (BinaryMapIndexReader travelBookReader : getTravelBookReaders()) {
Map<String, List<WikivoyageSearchResult>> navMap = new HashMap<>();
Set<String> headers = new LinkedHashSet<String>();
Map<String, WikivoyageSearchResult> headerObjs = new HashMap<>();
Map<File, List<Amenity>> amenityMap = new HashMap<>();
for (BinaryMapIndexReader reader : getReaders()) {
try {
BinaryMapIndexReader.SearchRequest<Amenity> req = BinaryMapIndexReader.buildSearchPoiRequest(
0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, -1, getSearchRouteArticleFilter(),
new ResultMatcher<Amenity>() {
boolean done = false;
SearchRequest<Amenity> req = BinaryMapIndexReader.buildSearchPoiRequest(0,
Integer.MAX_VALUE, 0, Integer.MAX_VALUE, -1, getSearchRouteArticleFilter(), new ResultMatcher<Amenity>() {
@Override
public boolean publish(Amenity amenity) {
if (getRouteId(amenity).equals(routeId)) {
amenities.add(amenity);
done = true;
String isPartOf = amenity.getTagContent(Amenity.IS_PART, lang);
if (Algorithms.stringsEqual(title, isPartOf)) {
return true;
} else if (parts != null && parts.length > 0) {
String title = amenity.getName(lang);
title = Algorithms.isEmpty(title) ? amenity.getName() : title;
for (int i = 0; i < parts.length; i++) {
String part = parts[i];
if (i == 0 && Algorithms.stringsEqual(part, title) || Algorithms.stringsEqual(part, isPartOf)) {
return true;
}
}
}
return false;
}
@Override
public boolean isCancelled() {
return done;
return false;
}
});
travelBookReader.searchPoi(req);
} catch (IOException e) {
LOG.error(e.getMessage());
}
if (!amenities.isEmpty()) {
article = readArticle(amenities.get(0), lang);
cachedArticles.put(article.routeId, article);
List<Amenity> amenities = reader.searchPoi(req);
if (!Algorithms.isEmpty(amenities)) {
amenityMap.put(reader.getFile(), amenities);
}
} catch (Exception e) {
LOG.error(e.getMessage(), e);
}
}
return article;
if (parts != null && parts.length > 0) {
headers.addAll(Arrays.asList(parts));
headers.add(title);
}
if (!Algorithms.isEmpty(amenityMap)) {
for (Entry<File, List<Amenity>> entry : amenityMap.entrySet()) {
File file = entry.getKey();
for (Amenity amenity : entry.getValue()) {
Set<String> nameLangs = getLanguages(amenity);
if (nameLangs.contains(lang)) {
TravelArticle a = readArticle(file, amenity, lang);
WikivoyageSearchResult rs = new WikivoyageSearchResult(a, new ArrayList<>(nameLangs));
List<WikivoyageSearchResult> l = navMap.get(rs.isPartOf);
if (l == null) {
l = new ArrayList<>();
navMap.put(rs.isPartOf, l);
}
l.add(rs);
if (headers != null && headers.contains(a.getTitle())) {
headerObjs.put(a.getTitle(), rs);
}
}
}
}
}
LinkedHashMap<WikivoyageSearchResult, List<WikivoyageSearchResult>> res = new LinkedHashMap<>();
for (String header : headers) {
WikivoyageSearchResult searchResult = headerObjs.get(header);
List<WikivoyageSearchResult> results = navMap.get(header);
if (results != null) {
Collections.sort(results, new Comparator<WikivoyageSearchResult>() {
@Override
public int compare(WikivoyageSearchResult o1, WikivoyageSearchResult o2) {
return collator.compare(o1.getArticleTitle(), o2.getArticleTitle());
}
});
WikivoyageSearchResult emptyResult = new WikivoyageSearchResult("", header, null, null, null);
searchResult = searchResult != null ? searchResult : emptyResult;
res.put(searchResult, results);
}
}
return res;
}
@Override
public TravelArticle getArticleById(@NonNull TravelArticleIdentifier articleId, @NonNull String lang) {
TravelArticle article = getCachedArticle(articleId, lang);
return article == null ? findArticleById(articleId, lang) : article;
}
@Nullable
@Override
public TravelArticle getArticleByTitle(@NonNull final String title, @NonNull final String lang) {
private TravelArticle getCachedArticle(@NonNull TravelArticleIdentifier articleId, @NonNull String lang) {
TravelArticle article = null;
Map<String, TravelArticle> articles = cachedArticles.get(articleId);
if (articles != null) {
if (Algorithms.isEmpty(lang)) {
Collection<TravelArticle> ac = articles.values();
if (!ac.isEmpty()) {
article = ac.iterator().next();
}
} else {
article = articles.get(lang);
if (article == null) {
article = articles.get("");
}
}
}
return article == null ? findArticleById(articleId, lang) : article;
}
private TravelArticle findArticleById(@NonNull final TravelArticleIdentifier articleId, final String lang) {
TravelArticle article = null;
final List<Amenity> amenities = new ArrayList<>();
for (BinaryMapIndexReader travelBookReader : getTravelBookReaders()) {
for (BinaryMapIndexReader reader : getReaders()) {
try {
BinaryMapIndexReader.SearchRequest<Amenity> req = BinaryMapIndexReader.buildSearchPoiRequest(
0, 0, title, 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, getSearchRouteArticleFilter(),
new ResultMatcher<Amenity>() {
if (articleId.file != null && !articleId.file.equals(reader.getFile())) {
continue;
}
SearchRequest<Amenity> req = BinaryMapIndexReader.buildSearchPoiRequest(0, 0,
Algorithms.emptyIfNull(articleId.title), 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE,
getSearchRouteArticleFilter(), new ResultMatcher<Amenity>() {
boolean done = false;
@Override
public boolean publish(Amenity amenity) {
if (CollatorStringMatcher.cmatches(collator, title, amenity.getName(lang), CHECK_EQUALS_FROM_SPACE)) {
if (Algorithms.stringsEqual(articleId.routeId, Algorithms.emptyIfNull(amenity.getTagContent(Amenity.ROUTE_ID, null)))
&& Algorithms.stringsEqual(articleId.routeSource, Algorithms.emptyIfNull(amenity.getTagContent(Amenity.ROUTE_SOURCE, null)))) {
amenities.add(amenity);
done = true;
}
@ -323,19 +417,74 @@ public class TravelObfHelper implements TravelHelper {
}
}, null);
travelBookReader.searchPoiByName(req);
if (!Double.isNaN(articleId.lat)) {
req.setBBoxRadius(articleId.lat, articleId.lon, ARTICLE_SEARCH_RADIUS);
if (!Algorithms.isEmpty(articleId.title)) {
reader.searchPoiByName(req);
} else {
reader.searchPoi(req);
}
} else {
reader.searchPoi(req);
}
} catch (IOException e) {
LOG.error(e.getMessage());
}
if (!amenities.isEmpty()) {
article = readArticle(amenities.get(0), lang);
cachedArticles.put(article.routeId, article);
article = cacheTravelArticles(reader.getFile(), amenities.get(0), lang);
}
}
return article;
}
private List<BinaryMapIndexReader> getTravelBookReaders() {
@Nullable
@Override
public TravelArticle getArticleByTitle(@NonNull final String title, @NonNull final String lang) {
return getArticleByTitle(title, new QuadRect(), lang);
}
@Nullable
@Override
public TravelArticle getArticleByTitle(@NonNull final String title, @NonNull LatLon latLon, @NonNull final String lang) {
QuadRect rect = latLon != null ? MapUtils.calculateLatLonBbox(latLon.getLatitude(), latLon.getLongitude(), ARTICLE_SEARCH_RADIUS) : new QuadRect();
return getArticleByTitle(title, rect, lang);
}
@Nullable
@Override
public TravelArticle getArticleByTitle(@NonNull final String title, @NonNull QuadRect rect, @NonNull final String lang) {
TravelArticle article = null;
List<Amenity> amenities = null;
int x = 0;
int y = 0;
int left = 0;
int right = Integer.MAX_VALUE;
int top = 0;
int bottom = Integer.MAX_VALUE;
if (rect.height() > 0 && rect.width() > 0) {
x = (int) rect.centerX();
y = (int) rect.centerY();
left = (int) rect.left;
right = (int) rect.right;
top = (int) rect.top;
bottom = (int) rect.bottom;
}
for (BinaryMapIndexReader reader : getReaders()) {
try {
SearchRequest<Amenity> req = BinaryMapIndexReader.buildSearchPoiRequest(
x, y, title, left, right, top, bottom, getSearchRouteArticleFilter(), null, null);
amenities = reader.searchPoiByName(req);
} catch (IOException e) {
LOG.error(e.getMessage());
}
if (!Algorithms.isEmpty(amenities)) {
article = cacheTravelArticles(reader.getFile(), amenities.get(0), lang);
}
}
return article;
}
private List<BinaryMapIndexReader> getReaders() {
if (!app.isApplicationInitializing()) {
return app.getResourceManager().getTravelRepositories();
} else {
@ -343,14 +492,16 @@ public class TravelObfHelper implements TravelHelper {
}
}
@NonNull
@Nullable
@Override
public String getArticleId(@NonNull String title, @NonNull String lang) {
public TravelArticleIdentifier getArticleId(@NonNull String title, @NonNull String lang) {
TravelArticle a = null;
for (TravelArticle article : cachedArticles.values()) {
if (article.getTitle().equals(title)) {
a = article;
break;
for (Map<String, TravelArticle> articles : cachedArticles.values()) {
for (TravelArticle article : articles.values()) {
if (article.getTitle().equals(title)) {
a = article;
break;
}
}
}
if (a == null) {
@ -359,17 +510,18 @@ public class TravelObfHelper implements TravelHelper {
a = article;
}
}
return a != null && a.getRouteId() != null ? a.getRouteId() : "";
return a != null ? a.generateIdentifier() : null;
}
@NonNull
@Override
public ArrayList<String> getArticleLangs(@NonNull String routeId) {
public ArrayList<String> getArticleLangs(@NonNull TravelArticleIdentifier articleId) {
ArrayList<String> res = new ArrayList<>();
res.add("en");
for (TravelArticle article : popularArticles) {
if (article.getRouteId().equals(routeId)) {
res.add(article.getLang());
TravelArticle article = getArticleById(articleId, "");
if (article != null) {
Map<String, TravelArticle> articles = cachedArticles.get(articleId);
if (articles != null) {
res.addAll(articles.keySet());
}
}
return res;

View file

@ -1,5 +1,9 @@
package net.osmand.plus.wikivoyage.data;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import net.osmand.plus.wikivoyage.data.TravelArticle.TravelArticleIdentifier;
import net.osmand.util.Algorithms;
import java.util.ArrayList;
@ -9,25 +13,52 @@ public class WikivoyageSearchResult {
private static final int SHOW_LANGS = 3;
String routeId;
List<String> articleTitles = new ArrayList<>();
List<String> langs = new ArrayList<>();
List<String> isPartOf = new ArrayList<>();
String imageTitle;
TravelArticleIdentifier articleId;
public String getRouteId() {
return routeId;
String imageTitle;
String isPartOf;
List<String> langs = new ArrayList<>();
public WikivoyageSearchResult(@NonNull TravelArticle article, @Nullable List<String> langs) {
articleId = article.generateIdentifier();
imageTitle = article.imageTitle;
isPartOf = article.isPartOf;
if (langs != null) {
this.langs = langs;
}
}
public List<String> getArticleTitles() {
return articleTitles;
public WikivoyageSearchResult(String routeId, String articleTitle, String isPartOf, String imageTitle, @Nullable List<String> langs) {
TravelArticle article = new TravelArticle();
article.routeId = routeId;
article.title = articleTitle;
this.articleId = article.generateIdentifier();
this.imageTitle = imageTitle;
this.isPartOf = isPartOf;
if (langs != null) {
this.langs = langs;
}
}
public TravelArticleIdentifier getArticleId() {
return articleId;
}
public String getArticleTitle() {
return articleId.title;
}
public String getArticleRouteId() {
return articleId.routeId;
}
public List<String> getLangs() {
return langs;
}
public List<String> getIsPartOf() {
public String getIsPartOf() {
return isPartOf;
}

View file

@ -50,7 +50,7 @@ public class SavedArticlesTabFragment extends BaseOsmAndFragment implements Trav
public void openArticle(TravelArticle article) {
FragmentManager fm = getFragmentManager();
if (fm != null) {
WikivoyageArticleDialogFragment.showInstance(app, fm, article.getRouteId(), article.getLang());
WikivoyageArticleDialogFragment.showInstance(app, fm, article.generateIdentifier(), article.getLang());
}
}
});

View file

@ -37,6 +37,7 @@ import net.osmand.plus.download.DownloadIndexesThread.DownloadEvents;
import net.osmand.plus.wikipedia.WikiArticleHelper;
import net.osmand.plus.wikivoyage.article.WikivoyageArticleDialogFragment;
import net.osmand.plus.wikivoyage.data.TravelArticle;
import net.osmand.plus.wikivoyage.data.TravelArticle.TravelArticleIdentifier;
import net.osmand.plus.wikivoyage.data.TravelHelper;
import net.osmand.plus.wikivoyage.data.TravelLocalDataHelper;
import net.osmand.plus.wikivoyage.search.WikivoyageSearchDialogFragment;
@ -50,8 +51,8 @@ public class WikivoyageExploreActivity extends TabActivity implements DownloadEv
TravelLocalDataHelper.Listener {
private static final String TAB_SELECTED = "tab_selected";
private static final String ROUTE_ID_KEY = "route_id_key";
private static final String SELECTED_LANG_KEY = "selected_lang_key";
private static final String ARTICLE_ID_KEY = "article_id";
private static final String SELECTED_LANG_KEY = "selected_lang";
private static final int EXPLORE_POSITION = 0;
private static final int SAVED_ARTICLES_POSITION = 1;
@ -182,9 +183,9 @@ public class WikivoyageExploreActivity extends TabActivity implements DownloadEv
BottomNavigationView bottomNav = (BottomNavigationView) findViewById(R.id.bottom_navigation);
bottomNav.setSelectedItemId(R.id.action_saved_articles);
}
String articleId = intent.getStringExtra(ROUTE_ID_KEY);
TravelArticleIdentifier articleId = intent.getParcelableExtra(ARTICLE_ID_KEY);
String selectedLang = intent.getStringExtra(SELECTED_LANG_KEY);
if (!Algorithms.isEmpty(articleId)) {
if (articleId != null) {
WikivoyageArticleDialogFragment.showInstance(app, getSupportFragmentManager(), articleId, selectedLang);
}
}
@ -201,8 +202,8 @@ public class WikivoyageExploreActivity extends TabActivity implements DownloadEv
String title = WikiArticleHelper.decodeTitleFromTravelUrl(data.getQueryParameter("title"));
String selectedLang = data.getQueryParameter("lang");
if (!Algorithms.isEmpty(title) && !Algorithms.isEmpty(selectedLang)) {
String articleId = app.getTravelHelper().getArticleId(title, selectedLang);
if (!articleId.isEmpty()) {
TravelArticleIdentifier articleId = app.getTravelHelper().getArticleId(title, selectedLang);
if (articleId != null) {
WikivoyageArticleDialogFragment.showInstance(app, getSupportFragmentManager(), articleId, selectedLang);
}
}
@ -279,7 +280,7 @@ public class WikivoyageExploreActivity extends TabActivity implements DownloadEv
private void applyIntentParameters(Intent intent, TravelArticle article) {
intent.putExtra(TAB_SELECTED, viewPager.getCurrentItem());
intent.putExtra(ROUTE_ID_KEY, article.getRouteId());
intent.putExtra(ARTICLE_ID_KEY, article.generateIdentifier());
intent.putExtra(SELECTED_LANG_KEY, article.getLang());
}

View file

@ -76,7 +76,8 @@ public class ArticleTravelCard extends BaseTravelCard {
@Override
public void onClick(View v) {
if (fragmentManager != null) {
WikivoyageArticleDialogFragment.showInstance(app, fragmentManager, article.getRouteId(), article.getLang());
WikivoyageArticleDialogFragment.showInstance(app, fragmentManager,
article.generateIdentifier(), article.getLang());
}
}
};

View file

@ -7,6 +7,7 @@ import androidx.annotation.NonNull;
import net.osmand.GPXUtilities.GPXFile;
import net.osmand.GPXUtilities.Metadata;
import net.osmand.GPXUtilities.WptPt;
import net.osmand.data.LatLon;
import net.osmand.data.PointDescription;
import net.osmand.plus.GpxSelectionHelper.SelectedGpxFile;
import net.osmand.plus.R;
@ -14,12 +15,13 @@ import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.mapcontextmenu.controllers.WptPtMenuController;
import net.osmand.plus.wikivoyage.article.WikivoyageArticleDialogFragment;
import net.osmand.plus.wikivoyage.data.TravelArticle;
import net.osmand.plus.wikivoyage.data.TravelArticle.TravelArticleIdentifier;
public class WikivoyageWptPtMenuController extends WptPtMenuController {
private WikivoyageWptPtMenuController(@NonNull MapActivity mapActivity, @NonNull PointDescription pointDescription, @NonNull WptPt wpt, @NonNull TravelArticle article) {
super(new WikivoyageWptPtMenuBuilder(mapActivity, wpt), mapActivity, pointDescription, wpt);
final String tripId = article.getRouteId();
final TravelArticleIdentifier articleId = article.generateIdentifier();
final String lang = article.getLang();
leftTitleButtonController = new TitleButtonController() {
@Override
@ -27,7 +29,7 @@ public class WikivoyageWptPtMenuController extends WptPtMenuController {
MapActivity mapActivity = getMapActivity();
if (mapActivity != null) {
WikivoyageArticleDialogFragment.showInstance(mapActivity.getMyApplication(),
mapActivity.getSupportFragmentManager(), tripId, lang);
mapActivity.getSupportFragmentManager(), articleId, lang);
}
}
};
@ -42,7 +44,7 @@ public class WikivoyageWptPtMenuController extends WptPtMenuController {
String title = metadata != null ? metadata.getArticleTitle() : null;
String lang = metadata != null ? metadata.getArticleLang() : null;
if (!TextUtils.isEmpty(title) && !TextUtils.isEmpty(lang)) {
return mapActivity.getMyApplication().getTravelHelper().getArticleByTitle(title, lang);
return mapActivity.getMyApplication().getTravelHelper().getArticleByTitle(title, new LatLon(wpt.lat, wpt.lon), lang);
}
return null;
}

View file

@ -83,8 +83,8 @@ public class SearchRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView
rc.transform(new CropCircleTransformation())
.placeholder(placeholder)
.into(holder.icon);
holder.title.setText(searchRes.getArticleTitles().get(0));
holder.leftDescr.setText(searchRes.getIsPartOf().get(0));
holder.title.setText(searchRes.getArticleTitle());
holder.leftDescr.setText(searchRes.getIsPartOf());
holder.rightDescr.setText(searchRes.getFirstLangsString());
} else {
WikivoyageSearchHistoryItem historyItem = (WikivoyageSearchHistoryItem) item;

View file

@ -112,8 +112,7 @@ public class WikivoyageSearchDialogFragment extends WikiBaseDialogFragment {
Object item = adapter.getItem(pos);
if (item instanceof WikivoyageSearchResult) {
WikivoyageSearchResult res = (WikivoyageSearchResult) item;
WikivoyageArticleDialogFragment
.showInstance(fm, res.getRouteId(), new ArrayList<>(res.getLangs()));
WikivoyageArticleDialogFragment.showInstance(fm, res.getArticleId(), new ArrayList<>(res.getLangs()));
} else if (item instanceof WikivoyageSearchHistoryItem) {
WikivoyageSearchHistoryItem historyItem = (WikivoyageSearchHistoryItem) item;
WikivoyageArticleDialogFragment