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

This commit is contained in:
max-klaus 2021-01-28 19:04:25 +03:00
commit 28596321c6
61 changed files with 2248 additions and 1078 deletions

157
GPX.md
View file

@ -1,157 +0,0 @@
The OsmAnd's GPX file format conforms to the GPX 1.1 specification with additional data written as extensions. There are several sections of such data:
## Track appearance
The following parameters are used to customize the appearance of a track on the map. They are used inside the "gpx" tag and apply to all tracks contained in the gpx.
#### Parameters
* **show_arrows** [*true, false*] - show / hide arrows along the path line.
* **width** [*thin, medium, bold, 1-24*] - width of the track line on the map. The thin, medium, and bold are style depended values (should be defined as currentTrackWidth attribute).
* **color** [*#AARRGGBB, #RRGGBB*] - color of a track line on the map. Hex value.
* **split_type** [*no_split, distance, time*] - split type for a track.
* **split_interval** [*double*] - split interval for a track. Distance (meters), time (seconds).
#### Example:
```xml
<gpx version="1.1" creator="OsmAndRouterV2" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
...
<extensions>
<show_arrows>true</show_arrows>
<color>#4e4eff</color>
<split_type>distance</split_type>
<split_interval>2000.0</split_interval>
<width>bold</width>
</extensions>
</gpx>
```
## Details of a track point (trkpt)
Written to a gpx file while recording a track.
* **speed** (meters per second)
* **heading** (0-359 degrees)
#### Example:
```xml
<trkpt lat="52.397799" lon="4.575998">
<ele>203</ele>
<time>2019-05-08T10:36:43Z</time>
<hdop>3</hdop>
<extensions>
<heading>273</heading>
<speed>5.02</speed>
</extensions>
</trkpt>
```
## Calculated route(s)
This data contains all details of a route built with **OsmAnd** (route segments, turns, road names, road types, restrictions, etc.). The route can be completely restored as if just built, even in the absence of the respective offline maps.
A gpx file may contain several routes. Each of them is contained in a specific segment under **trkseg** / **extensions**. A gpx file is saved in this form when exporting a constructed route or when saving a track that consists of several separate segments via the **Plan a route** functionality.
**Plan a route** also adds one (or several, in accordance with the number of contained separate segments / tracks) **rte** blocks to the gpx file, containing route key points (**rtept**).
#### Gpx structure:
```xml
<trk>
<trkseg>
<!-- List of segment points. The order of the points corresponds to the order and length of the route segments (<route><segment length="x" ... />). -->
<!-- The value of the "length" attribute corresponds to the number of points in this segment of the route. -->
<trkpt ... ></trkpt>
<extensions>
<!-- List of route segments -->
<route>
<segment ... />
</route>
<!-- Properties of segments included in the route. -->
<!-- This data is taken from offline maps during the initial construction of a route. -->
<types>
<type ... />
</types>
</extensions>
</trkseg>
</trk>
<!-- List of intermediate route points. If there are multiple routes, the order of the rte list matches the order of the route segments. -->
<rte>
<rtept ... />
<!-- For routes built with the "Plan route", the parameters of key points are saved. -->
<extensions>
<!-- Route profile type for next segment (car, bicycle, pedestrian, etc.). -->
<profile>...</profile>
<!-- The index of the point in the gpx segment that corresponds to the first point of the calculated route for this segment. -->
<trkpt_idx>...</trkpt_idx>
</extensions>
</rtept>
</rte>
```
#### Example:
```xml
<gpx version="1.1" creator="OsmAndRouterV2" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
<metadata>
<name>Fri 06 Nov 2020</name>
</metadata>
<trk>
<name>Fri 06 Nov 2020</name>
<trkseg>
<trkpt lat="52.3639849" lon="4.8900533">
<ele>0.801</ele>
</trkpt>
<trkpt lat="52.3636917" lon="4.8922849">
<ele>0.998</ele>
</trkpt>
<trkpt lat="52.3636885" lon="4.892309">
<ele>1</ele>
</trkpt>
<trkpt lat="52.3636426" lon="4.8922902">
<ele>0.963</ele>
</trkpt>
<trkpt lat="52.363564" lon="4.8922607">
<ele>0.899</ele>
</trkpt>
....
<extensions>
<route>
<segment id="7372058" length="3" segmentTime="178.44" speed="1.11" turnType="C" types="0,1,2,3,4,5,6" names="57" />
<segment id="334164679" length="5" segmentTime="86.11" speed="1.11" turnType="TR" turnAngle="91.88" types="7,8,0,9,10,11,12,13,6" pointTypes=";;14,15;16,17,18;" names="58" />
<segment id="334603581" length="6" segmentTime="75.5" speed="1.11" types="19,20,21,7,8,0,22,9,10,11,12,13,23,6" pointTypes=";14;16,24;16,24;14;" names="58" />
<segment id="446707354" length="3" segmentTime="8.32" speed="1.11" turnType="TSLL" turnAngle="-25.44" types="19,25,21,7,8,22,9,1,11,12,13,6" names="58" />
...
</route>
<types>
<type t="lit" v="yes" />
<type t="oneway" v="yes" />
<type t="highway" v="unclassified" />
<type t="surface" v="paving_stones" />
<type t="maxspeed" v="30" />
...
</types>
</extensions>
</trkseg>
</trk>
<rte>
<rtept lat="52.3639945" lon="4.8900532">
<extensions>
<profile>pedestrian</profile>
<trkpt_idx>0</trkpt_idx>
</extensions>
</rtept>
<rtept lat="52.3612797" lon="4.8911677">
<extensions>
<profile>pedestrian</profile>
<trkpt_idx>24</trkpt_idx>
</extensions>
</rtept>
<rtept lat="52.356996" lon="4.8912071">
<extensions>
<profile>pedestrian</profile>
<trkpt_idx>89</trkpt_idx>
</extensions>
</rtept>
<rtept lat="52.3542374" lon="4.8947024">
<extensions>
<profile>pedestrian</profile>
<trkpt_idx>121</trkpt_idx>
</extensions>
</rtept>
</rte>
</gpx>
```

View file

@ -274,6 +274,10 @@ public class Amenity extends MapObject {
return null;
}
public String getTagContent(String tag) {
return getTagContent(tag, null);
}
public String getTagContent(String tag, String lang) {
if (lang != null) {
String translateName = getAdditionalInfo(tag + ":" + lang);

View file

@ -40,7 +40,7 @@
<string name="items_modified">items modified</string>
<string name="osmand_unlimited">OsmAnd Unlimited</string>
<string name="markers">Markers</string>
<string name="opr_base_url">https://test.openplacereviews.org/</string>
<string name="opr_base_url">https://openplacereviews.org/</string>
<string name="dev_opr_base_url">https://test.openplacereviews.org/</string>
<string name="osm_oauth_developer_key">v8G8r9NLJZGMV4he5lwbQlz620FNVARKjI9Bm5UJ</string>
<string name="osm_oauth_developer_secret">jDvM95Ne1Bq2BDTmIfB6b3ZMxvdK87WGfp6DC07J</string>

View file

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:osmand="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/bottom_sheet_selected_item_title_height"
android:orientation="vertical"
android:paddingStart="@dimen/content_padding"
android:paddingLeft="@dimen/content_padding"
android:paddingTop="@dimen/measurement_tool_menu_title_padding_top"
android:paddingEnd="@dimen/content_padding"
android:paddingRight="@dimen/content_padding"
android:paddingBottom="@dimen/content_padding_small">
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="center_vertical"
android:maxLines="1"
android:textColor="?android:textColorPrimary"
android:textSize="@dimen/default_list_text_size"
osmand:typeface="@string/font_roboto_medium"
tools:text="Some title" />
<ProgressBar
android:id="@+id/progress_bar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/route_info_buttons_padding_left_right"
android:layout_marginBottom="@dimen/route_info_buttons_padding_left_right"
android:minHeight="0dp"
android:visibility="visible" />
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:lineSpacingMultiplier="@dimen/bottom_sheet_text_spacing_multiplier"
android:textColor="?android:textColorPrimary"
android:textSize="@dimen/default_list_text_size"
osmand:typeface="@string/font_roboto_regular"
tools:text="Some description" />
</LinearLayout>

View file

@ -1,80 +1,83 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:baselineAligned="false"
android:gravity="center_vertical"
android:minHeight="@dimen/bottom_sheet_selected_item_title_height"
android:paddingStart="@dimen/content_padding"
android:paddingEnd="@dimen/content_padding"
android:paddingRight="@dimen/content_padding"
android:paddingLeft="@dimen/content_padding">
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:baselineAligned="false"
android:gravity="center_vertical"
android:minHeight="@dimen/bottom_sheet_selected_item_title_height">
<LinearLayout
android:id="@+id/basic_item_body"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="?attr/selectableItemBackground">
<LinearLayout
android:id="@+id/basic_item_body"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="?attr/selectableItemBackground"
android:paddingStart="@dimen/content_padding"
android:paddingLeft="@dimen/content_padding"
android:paddingEnd="@dimen/content_padding"
android:paddingRight="@dimen/content_padding">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:orientation="vertical">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:paddingTop="@dimen/content_padding_small"
android:paddingBottom="@dimen/content_padding"
android:textAppearance="@style/TextAppearance.ListItemTitle"
tools:text="Some title" />
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:paddingTop="@dimen/content_padding_small"
android:paddingBottom="@dimen/content_padding"
android:textAppearance="@style/TextAppearance.ListItemTitle"
tools:text="Some title" />
</LinearLayout>
</LinearLayout>
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/switch_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="@dimen/bottom_sheet_content_margin"
android:layout_marginLeft="@dimen/bottom_sheet_content_margin"
android:layout_marginEnd="@dimen/bottom_sheet_content_margin"
android:layout_marginRight="@dimen/bottom_sheet_content_margin"
tools:checked="true" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/switch_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="@dimen/bottom_sheet_content_margin"
android:layout_marginLeft="@dimen/bottom_sheet_content_margin"
android:clickable="false"
android:focusable="false"
android:focusableInTouchMode="false"
tools:checked="true" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/additional_button"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal">
<LinearLayout
android:id="@+id/additional_button"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal">
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/content_padding_half"
android:layout_marginBottom="@dimen/content_padding_half"
android:background="?attr/divider_color_basic" />
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/content_padding_half"
android:layout_marginBottom="@dimen/content_padding_half"
android:background="?attr/divider_color_basic" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/icon_after_divider"
style="@style/Widget.AppCompat.Toolbar.Button.Navigation"
android:layout_width="@dimen/favorites_icon_right_margin"
android:layout_height="@dimen/favorites_icon_right_margin"
android:layout_gravity="center"
android:layout_marginStart="@dimen/content_padding"
android:layout_marginLeft="@dimen/content_padding"
app:srcCompat="@drawable/ic_action_track_line_bold_color" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/icon_after_divider"
style="@style/Widget.AppCompat.Toolbar.Button.Navigation"
android:layout_width="@dimen/favorites_icon_right_margin"
android:layout_height="@dimen/favorites_icon_right_margin"
android:layout_gravity="center"
android:layout_marginStart="@dimen/content_padding"
android:layout_marginLeft="@dimen/content_padding"
android:layout_marginEnd="@dimen/content_padding"
android:layout_marginRight="@dimen/content_padding"
app:srcCompat="@drawable/ic_action_track_line_bold_color" />
</LinearLayout>
</LinearLayout>
</LinearLayout>

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/map_controls_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
tools:visibility="invisible">
<include
layout="@layout/map_ruler"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start|bottom"
android:layout_marginStart="@dimen/fab_margin_right"
android:layout_marginLeft="@dimen/fab_margin_right"
android:layout_marginEnd="@dimen/fab_margin_right"
android:layout_marginRight="@dimen/fab_margin_right" />
<include
layout="@layout/map_hud_controls"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_marginStart="@dimen/fab_margin_right"
android:layout_marginLeft="@dimen/fab_margin_right"
android:layout_marginEnd="@dimen/fab_margin_right"
android:layout_marginRight="@dimen/fab_margin_right" />
</FrameLayout>

View file

@ -42,25 +42,31 @@
android:textSize="@dimen/dialog_header_text_size"
osmand:typeface="@string/font_roboto_medium" />
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/btn_save"
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="@dimen/content_padding_half"
android:layout_marginEnd="@dimen/content_padding_half"
android:layout_gravity="center_vertical"
android:background="@drawable/btn_border_active"
android:paddingStart="@dimen/content_padding"
android:paddingLeft="@dimen/content_padding"
android:paddingTop="@dimen/content_padding_half"
android:paddingRight="@dimen/content_padding"
android:paddingEnd="@dimen/content_padding"
android:paddingBottom="@dimen/content_padding_half"
android:text="@string/shared_string_save"
android:textColor="?attr/active_color_basic"
android:textSize="@dimen/default_desc_text_size"
osmand:typeface="@string/font_roboto_medium" />
android:background="@drawable/btn_border_active">
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/btn_save"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:paddingStart="@dimen/content_padding"
android:paddingLeft="@dimen/content_padding"
android:paddingTop="@dimen/content_padding_half"
android:paddingRight="@dimen/content_padding"
android:paddingEnd="@dimen/content_padding"
android:paddingBottom="@dimen/content_padding_half"
android:text="@string/shared_string_save"
android:textColor="?attr/active_color_basic"
android:textSize="@dimen/default_desc_text_size"
osmand:typeface="@string/font_roboto_medium" />
</FrameLayout>
</LinearLayout>
@ -69,17 +75,26 @@
android:layout_height="1dp"
android:background="?attr/divider_color" />
<net.osmand.plus.widgets.EditTextEx
android:id="@+id/description"
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/content_padding"
android:layout_marginLeft="@dimen/content_padding"
android:layout_marginRight="@dimen/content_padding"
android:background="?attr/card_and_list_background_basic"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/default_list_text_size"
osmand:typeface="@string/font_roboto_regular"
tools:text="Amsterdam is the Netherlands' capital and financial, cultural and creative centre with more than 850,000 inhabitants. Amsterdam is known for the canals that criss-cross the city, its impressive architecture and more than 1,500 bridges. The city has a heritage dating back to the Dutch Golden Age in the 17th century as well as a diverse art scene and a bustling nightlife." />
android:layout_height="match_parent">
<net.osmand.plus.widgets.EditTextEx
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/content_padding"
android:layout_marginLeft="@dimen/content_padding"
android:layout_marginTop="@dimen/content_padding_half"
android:layout_marginRight="@dimen/content_padding"
android:layout_marginEnd="@dimen/content_padding"
android:layout_marginBottom="@dimen/content_padding_half"
android:background="?attr/card_and_list_background_basic"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/default_list_text_size"
osmand:typeface="@string/font_roboto_regular"
tools:text="Amsterdam is the Netherlands' capital and financial, cultural and creative centre with more than 850,000 inhabitants. Amsterdam is known for the canals that criss-cross the city, its impressive architecture and more than 1,500 bridges. The city has a heritage dating back to the Dutch Golden Age in the 17th century as well as a diverse art scene and a bustling nightlife." />
</ScrollView>
</LinearLayout>

View file

@ -56,32 +56,40 @@
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/divider_color"
android:background="?attr/ctx_menu_info_divider"
android:visibility="gone"
tools:visibility="visible"/>
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/btn_edit"
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/content_padding"
android:layout_marginStart="@dimen/content_padding"
android:layout_marginTop="@dimen/context_menu_padding_margin_small"
android:layout_marginBottom="@dimen/context_menu_padding_margin_small"
android:gravity="center_vertical"
android:padding="@dimen/context_menu_padding_margin_small"
android:paddingStart="@dimen/context_menu_padding_margin_small"
android:paddingEnd="@dimen/context_menu_padding_margin_small"
android:drawablePadding="@dimen/list_content_padding_large"
osmand:drawableLeftCompat="@drawable/ic_action_edit_dark"
osmand:drawableStartCompat="@drawable/ic_action_edit_dark"
osmand:drawableTint="?attr/active_color_basic"
android:visibility="gone"
android:text="@string/shared_string_edit"
android:textColor="?attr/active_color_basic"
android:textSize="@dimen/default_list_text_size"
osmand:typeface="@string/font_roboto_medium"
tools:visibility="visible" />
android:background="@drawable/rounded_background_3dp">
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/btn_edit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="@dimen/context_menu_padding_margin_small"
android:paddingStart="@dimen/context_menu_padding_margin_small"
android:paddingEnd="@dimen/context_menu_padding_margin_small"
android:drawablePadding="@dimen/list_content_padding_large"
osmand:drawableLeftCompat="@drawable/ic_action_edit_dark"
osmand:drawableStartCompat="@drawable/ic_action_edit_dark"
osmand:drawableTint="?attr/active_color_basic"
android:visibility="gone"
android:text="@string/shared_string_edit"
android:textColor="?attr/active_color_basic"
android:textSize="@dimen/default_list_text_size"
osmand:typeface="@string/font_roboto_medium"
tools:visibility="visible" />
</FrameLayout>
<FrameLayout
android:id="@+id/bottom_empty_space"

View file

@ -99,34 +99,7 @@
</LinearLayout>
<FrameLayout
android:id="@+id/map_controls_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
tools:visibility="invisible">
<include
layout="@layout/map_ruler"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start|bottom"
android:layout_marginStart="@dimen/fab_margin_right"
android:layout_marginLeft="@dimen/fab_margin_right"
android:layout_marginEnd="@dimen/fab_margin_right"
android:layout_marginRight="@dimen/fab_margin_right" />
<include
layout="@layout/map_hud_controls"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_marginStart="@dimen/fab_margin_right"
android:layout_marginLeft="@dimen/fab_margin_right"
android:layout_marginEnd="@dimen/fab_margin_right"
android:layout_marginRight="@dimen/fab_margin_right" />
</FrameLayout>
<include layout="@layout/context_menu_controls" />
<LinearLayout
android:id="@+id/control_buttons"

View file

@ -40,69 +40,119 @@
tools:visibility="visible"
tools:src="@drawable/img_help_announcement_time_day"/>
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/description"
<LinearLayout
android:id="@id/description_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/content_padding"
android:layout_marginLeft="@dimen/content_padding"
android:layout_marginTop="@dimen/context_menu_padding_margin_small"
android:layout_marginRight="@dimen/content_padding"
android:layout_marginEnd="@dimen/content_padding"
android:textColor="?android:textColorPrimary"
android:textSize="@dimen/default_list_text_size"
android:lineSpacingMultiplier="1.1"
osmand:typeface="@string/font_roboto_regular"
tools:text="Amsterdam is the Netherlands' capital and financial, cultural and creative centre with more than 850,000 inhabitants. Amsterdam is known for the canals that criss-cross the city, its impressive architecture and more than 1,500 bridges. The city has a heritage dating back to the Dutch Golden Age in the 17th century as well as a diverse art scene and a bustling nightlife."/>
android:orientation="vertical">
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/content_padding"
android:layout_marginLeft="@dimen/content_padding"
android:layout_marginTop="@dimen/context_menu_padding_margin_small"
android:layout_marginRight="@dimen/content_padding"
android:layout_marginEnd="@dimen/content_padding"
android:textColor="?android:textColorPrimary"
android:textSize="@dimen/default_list_text_size"
android:lineSpacingMultiplier="1.1"
osmand:typeface="@string/font_roboto_regular"
tools:text="Amsterdam is the Netherlands' capital and financial, cultural and creative centre with more than 850,000 inhabitants. Amsterdam is known for the canals that criss-cross the city, its impressive architecture and more than 1,500 bridges. The city has a heritage dating back to the Dutch Golden Age in the 17th century as well as a diverse art scene and a bustling nightlife."/>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/context_menu_padding_margin_small"
android:orientation="horizontal">
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/context_menu_padding_margin_small"
android:layout_marginLeft="@dimen/context_menu_padding_margin_small"
android:background="@drawable/rounded_background_3dp">
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/btn_read_full"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:gravity="center_vertical"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="@dimen/bottom_sheet_content_padding_small"
android:paddingStart="@dimen/bottom_sheet_content_padding_small"
android:paddingEnd="@dimen/bottom_sheet_content_padding_small"
android:drawablePadding="@dimen/bottom_sheet_content_padding_small"
osmand:drawableStartCompat="@drawable/ic_action_read_article"
osmand:drawableLeftCompat="@drawable/ic_action_read_article"
osmand:drawableTint="?attr/wikivoyage_active_color"
android:text="@string/context_menu_read_full"
android:textColor="?attr/wikivoyage_active_color"
android:textSize="@dimen/default_desc_text_size"
osmand:typeface="@string/font_roboto_medium" />
</FrameLayout>
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="@dimen/context_menu_padding_margin_small"
android:layout_marginEnd="@dimen/context_menu_padding_margin_small"
android:layout_gravity="end"
android:background="@drawable/rounded_background_3dp">
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/btn_edit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="@dimen/bottom_sheet_content_padding_small"
android:paddingStart="@dimen/bottom_sheet_content_padding_small"
android:paddingEnd="@dimen/bottom_sheet_content_padding_small"
android:drawablePadding="@dimen/bottom_sheet_content_padding_small"
osmand:drawableRightCompat="@drawable/ic_action_edit_dark"
osmand:drawableEndCompat="@drawable/ic_action_edit_dark"
osmand:drawableTint="?attr/wikivoyage_active_color"
android:text="@string/shared_string_edit"
android:textColor="?attr/wikivoyage_active_color"
android:textSize="@dimen/default_desc_text_size"
osmand:typeface="@string/font_roboto_medium" />
</FrameLayout>
</FrameLayout>
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:id="@+id/btn_add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/context_menu_padding_margin_small"
android:layout_marginBottom="@dimen/context_menu_padding_margin_small"
android:orientation="horizontal">
android:layout_marginStart="@dimen/card_padding"
android:layout_marginLeft="@dimen/card_padding"
android:layout_marginTop="@dimen/content_padding"
android:background="@drawable/rounded_background_3dp"
android:visibility="gone"
tools:visibility="visible">
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/btn_read_full"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/context_menu_padding_margin_small"
android:layout_marginLeft="@dimen/context_menu_padding_margin_small"
android:layout_gravity="start"
android:gravity="center_vertical"
android:paddingStart="@dimen/bottom_sheet_content_padding_small"
android:paddingLeft="@dimen/bottom_sheet_content_padding_small"
android:paddingTop="@dimen/bottom_sheet_content_padding_small"
android:paddingRight="@dimen/bottom_sheet_content_padding_small"
android:paddingEnd="@dimen/bottom_sheet_content_padding_small"
android:paddingBottom="@dimen/bottom_sheet_content_padding_small"
android:drawablePadding="@dimen/bottom_sheet_content_padding_small"
osmand:drawableStartCompat="@drawable/ic_action_read_article"
osmand:drawableLeftCompat="@drawable/ic_action_read_article"
osmand:drawableTint="?attr/wikivoyage_active_color"
android:text="@string/context_menu_read_full"
android:textColor="?attr/wikivoyage_active_color"
android:textSize="@dimen/default_desc_text_size"
osmand:typeface="@string/font_roboto_medium" />
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/btn_edit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="@dimen/context_menu_padding_margin_small"
android:layout_marginEnd="@dimen/context_menu_padding_margin_small"
android:layout_gravity="end"
android:gravity="center_vertical"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="@dimen/bottom_sheet_content_padding_small"
android:paddingStart="@dimen/bottom_sheet_content_padding_small"
android:paddingEnd="@dimen/bottom_sheet_content_padding_small"
android:drawablePadding="@dimen/bottom_sheet_content_padding_small"
osmand:drawableRightCompat="@drawable/ic_action_edit_dark"
osmand:drawableEndCompat="@drawable/ic_action_edit_dark"
android:drawablePadding="@dimen/dialog_button_height"
osmand:drawableStartCompat="@drawable/ic_action_add"
osmand:drawableLeftCompat="@drawable/ic_action_add"
osmand:drawableTint="?attr/wikivoyage_active_color"
android:text="@string/shared_string_edit"
android:text="@string/add_description"
android:textColor="?attr/wikivoyage_active_color"
android:textSize="@dimen/default_desc_text_size"
android:textSize="@dimen/default_list_text_size"
osmand:typeface="@string/font_roboto_medium" />
</FrameLayout>
@ -110,6 +160,7 @@
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/context_menu_padding_margin_small"
android:background="?attr/activity_background_basic">
<include

View file

@ -9,7 +9,7 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_overview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="@dimen/list_header_height"
android:layout_marginBottom="@dimen/content_padding_small_half"
android:clipToPadding="false"
android:orientation="horizontal"
@ -48,55 +48,60 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/content_padding"
android:layout_marginLeft="@dimen/content_padding"
android:layout_height="@dimen/setting_list_item_small_height"
android:layout_marginStart="@dimen/card_padding"
android:layout_marginLeft="@dimen/card_padding"
android:layout_marginTop="@dimen/content_padding_half"
android:layout_marginEnd="@dimen/content_padding"
android:layout_marginRight="@dimen/content_padding"
android:layout_marginEnd="@dimen/card_padding"
android:layout_marginRight="@dimen/card_padding"
android:layout_marginBottom="@dimen/content_padding"
android:baselineAligned="false"
android:minHeight="@dimen/context_menu_controller_height"
android:orientation="horizontal"
android:weightSum="4">
<!-- todo stretch buttons correctly -->
<include
android:id="@+id/show_button"
layout="@layout/item_gpx_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/list_item_button_padding"
android:layout_marginLeft="@dimen/list_item_button_padding"
android:layout_marginEnd="@dimen/list_item_button_padding"
android:layout_marginRight="@dimen/list_item_button_padding"
android:layout_weight="1" />
<Space
android:layout_width="@dimen/content_padding_half"
android:layout_height="match_parent" />
<include
android:id="@+id/appearance_button"
layout="@layout/item_gpx_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/list_item_button_padding"
android:layout_marginLeft="@dimen/list_item_button_padding"
android:layout_marginEnd="@dimen/list_item_button_padding"
android:layout_marginRight="@dimen/list_item_button_padding"
android:layout_weight="1" />
<Space
android:layout_width="@dimen/content_padding_half"
android:layout_height="match_parent" />
<include
android:id="@+id/edit_button"
layout="@layout/item_gpx_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/list_item_button_padding"
android:layout_marginLeft="@dimen/list_item_button_padding"
android:layout_marginEnd="@dimen/list_item_button_padding"
android:layout_marginRight="@dimen/list_item_button_padding"
android:layout_weight="1" />
<Space
android:layout_width="@dimen/content_padding_half"
android:layout_height="match_parent" />
<include
android:id="@+id/directions_button"
layout="@layout/item_gpx_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/list_item_button_padding"
android:layout_marginLeft="@dimen/list_item_button_padding"
android:layout_marginEnd="@dimen/list_item_button_padding"
android:layout_marginRight="@dimen/list_item_button_padding"
android:layout_weight="1" />
</LinearLayout>

View file

@ -1,13 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
android:minHeight="@dimen/context_menu_controller_height"
android:minWidth="@dimen/context_menu_top_right_button_min_width"
android:layout_width="@dimen/fab_recycler_view_padding_bottom"
android:layout_height="@dimen/setting_list_item_small_height">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/filled"
android:layout_width="@dimen/fab_recycler_view_padding_bottom"
android:layout_height="@dimen/setting_list_item_small_height"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:alpha="0.1"
tools:srcCompat="@drawable/bg_topbar_shield_exit_ref" />

View file

@ -3,42 +3,62 @@
xmlns:osmand="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
android:layout_height="@dimen/list_header_height"
android:layout_marginStart="@dimen/content_padding"
android:layout_marginLeft="@dimen/content_padding"
android:gravity="center_vertical"
android:orientation="horizontal">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginEnd="@dimen/content_padding"
android:layout_marginRight="@dimen/content_padding"
android:orientation="vertical">
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/value"
android:layout_width="match_parent"
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/default_desc_text_size"
osmand:typeface="@string/font_roboto_medium"
tools:text="700 km" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/image"
android:layout_width="@dimen/context_menu_transport_icon_size"
android:layout_height="@dimen/context_menu_transport_icon_size"
android:layout_gravity="center_vertical"
android:layout_marginStart="@dimen/context_menu_first_line_top_margin"
android:layout_marginLeft="@dimen/context_menu_first_line_top_margin"
tools:src="@drawable/ic_action_track_16" />
</LinearLayout>
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@null"
android:textColor="?android:attr/textColorPrimary"
android:textColor="?android:attr/textColorSecondary"
android:textSize="@dimen/default_desc_text_size"
osmand:typeface="@string/font_roboto_medium"
tools:text="700 km" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/image"
android:layout_width="@dimen/context_menu_transport_icon_size"
android:layout_height="@dimen/context_menu_transport_icon_size"
android:layout_gravity="center_vertical"
android:layout_marginStart="@dimen/context_menu_first_line_top_margin"
android:layout_marginLeft="@dimen/context_menu_first_line_top_margin"
tools:src="@drawable/ic_action_track_16" />
tools:text="@string/distance" />
</LinearLayout>
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
android:textColor="?android:attr/textColorSecondary"
android:textSize="@dimen/default_desc_text_size"
tools:text="@string/distance" />
<View
android:id="@+id/divider"
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/map_small_button_margin"
android:layout_marginBottom="@dimen/map_small_button_margin"
tools:background="@color/divider_color_light" />
</LinearLayout>

View file

@ -90,34 +90,7 @@
</LinearLayout>
<FrameLayout
android:id="@+id/map_controls_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
tools:visibility="invisible">
<include
layout="@layout/map_ruler"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start|bottom"
android:layout_marginStart="@dimen/fab_margin_right"
android:layout_marginLeft="@dimen/fab_margin_right"
android:layout_marginEnd="@dimen/fab_margin_right"
android:layout_marginRight="@dimen/fab_margin_right" />
<include
layout="@layout/map_hud_controls"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_marginStart="@dimen/fab_margin_right"
android:layout_marginLeft="@dimen/fab_margin_right"
android:layout_marginEnd="@dimen/fab_margin_right"
android:layout_marginRight="@dimen/fab_margin_right" />
</FrameLayout>
<include layout="@layout/context_menu_controls" />
<LinearLayout
android:id="@+id/control_buttons"

View file

@ -91,6 +91,13 @@
</LinearLayout>
<LinearLayout
android:id="@+id/header_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/list_background_color"
android:orientation="vertical" />
<FrameLayout
android:id="@+id/bottom_container"
android:layout_width="match_parent"
@ -191,6 +198,8 @@
</com.google.android.material.appbar.AppBarLayout>
<include layout="@layout/context_menu_controls" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_navigation"
android:layout_width="match_parent"

View file

@ -1,114 +1,115 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:osmand="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:osmand="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:letterSpacing="@dimen/text_button_letter_spacing"
android:paddingStart="@dimen/content_padding"
android:paddingLeft="@dimen/content_padding"
android:paddingTop="@dimen/content_padding_small"
android:paddingEnd="@dimen/content_padding"
android:paddingRight="@dimen/content_padding"
android:paddingBottom="@dimen/content_padding_small"
android:text="@string/monitoring_settings"
android:textSize="@dimen/default_list_text_size"
osmand:typeface="@string/font_roboto_medium" />
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:letterSpacing="@dimen/text_button_letter_spacing"
android:paddingStart="@dimen/content_padding"
android:paddingLeft="@dimen/content_padding"
android:paddingTop="@dimen/content_padding_small"
android:paddingEnd="@dimen/content_padding"
android:paddingRight="@dimen/content_padding"
android:paddingBottom="@dimen/content_padding_small"
android:text="@string/monitoring_settings"
android:textSize="@dimen/default_list_text_size"
osmand:typeface="@string/font_roboto_medium" />
<include
android:id="@+id/show_track_on_map"
layout="@layout/bottom_sheet_with_switch_divider_and_additional_button" />
<include
android:id="@+id/show_track_on_map"
layout="@layout/bottom_sheet_with_switch_divider_and_additional_button" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/dashboard_divider" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/dashboard_divider" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:orientation="horizontal"
android:paddingStart="@dimen/content_padding"
android:paddingLeft="@dimen/content_padding"
android:paddingTop="@dimen/bottom_sheet_content_margin"
android:paddingEnd="@dimen/content_padding"
android:paddingRight="@dimen/content_padding">
<LinearLayout
android:id="@+id/interval_view_container"
android:background="?attr/selectableItemBackground"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:orientation="horizontal"
android:paddingStart="@dimen/content_padding"
android:paddingLeft="@dimen/content_padding"
android:paddingTop="@dimen/bottom_sheet_content_margin"
android:paddingBottom="@dimen/bottom_sheet_content_margin"
android:paddingEnd="@dimen/content_padding"
android:paddingRight="@dimen/content_padding">
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/interval_value"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ellipsize="end"
android:maxLines="1"
android:textSize="@dimen/default_list_text_size"
tools:text="Interval value" />
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/interval_value"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ellipsize="end"
android:maxLines="1"
android:textSize="@dimen/default_list_text_size"
tools:text="Interval value" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/up_down_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@null"
app:srcCompat="@drawable/ic_action_arrow_down" />
</LinearLayout>
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/up_down_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@null"
app:srcCompat="@drawable/ic_action_arrow_down" />
</LinearLayout>
<LinearLayout
android:id="@+id/always_ask_and_range_slider_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="@dimen/content_padding"
android:paddingLeft="@dimen/content_padding"
android:paddingEnd="@dimen/content_padding"
android:paddingRight="@dimen/content_padding">
<LinearLayout
android:id="@+id/always_ask_and_range_slider_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="@dimen/content_padding"
android:paddingLeft="@dimen/content_padding"
android:paddingEnd="@dimen/content_padding"
android:paddingRight="@dimen/content_padding">
<com.google.android.material.slider.RangeSlider
android:id="@+id/interval_slider"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/save_track_interval_globally"
android:stepSize="1"
app:haloRadius="@dimen/content_padding_small"
app:labelBehavior="gone"
app:thumbColor="@color/profile_icon_color_blue_light"
app:thumbRadius="@dimen/content_padding_small_half"
app:trackColorActive="@color/profile_icon_color_blue_light"
app:trackColorInactive="#4D007EB3"
app:tickColorInactive="#007EB3"
app:trackHeight="@dimen/slider_track_height"
tools:visibility="visible" />
<com.google.android.material.slider.RangeSlider
android:id="@+id/interval_slider"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/save_track_interval_globally"
android:stepSize="1"
app:haloRadius="@dimen/content_padding_small"
app:labelBehavior="gone"
app:thumbColor="@color/profile_icon_color_blue_light"
app:thumbRadius="@dimen/content_padding_small_half"
app:trackColorActive="@color/profile_icon_color_blue_light"
app:trackColorInactive="#4D007EB3"
app:tickColorInactive="#007EB3"
app:trackHeight="@dimen/slider_track_height"
tools:visibility="visible" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/confirm_every_run"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:letterSpacing="@dimen/text_button_letter_spacing"
android:minHeight="@dimen/context_menu_buttons_bottom_height"
android:paddingLeft="@dimen/content_padding_small"
android:paddingRight="@dimen/content_padding_small"
android:text="@string/confirm_every_run"
android:textColor="?attr/color_dialog_buttons"
android:textSize="@dimen/default_list_text_size"
osmand:typeface="@string/font_roboto_regular"
tools:visibility="visible" />
</LinearLayout>
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/confirm_every_run"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:letterSpacing="@dimen/text_button_letter_spacing"
android:minHeight="@dimen/context_menu_buttons_bottom_height"
android:paddingLeft="@dimen/content_padding_small"
android:paddingRight="@dimen/content_padding_small"
android:text="@string/confirm_every_run"
android:textColor="?attr/color_dialog_buttons"
android:textSize="@dimen/default_list_text_size"
osmand:typeface="@string/font_roboto_regular"
tools:visibility="visible" />
</LinearLayout>
<View
android:id="@+id/second_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="@dimen/content_padding"
android:layout_marginBottom="@dimen/bottom_sheet_content_margin_small"
android:background="?attr/dashboard_divider" />
<View
android:id="@+id/second_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/dashboard_divider" />
</LinearLayout>

View file

@ -0,0 +1,282 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:osmand="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<FrameLayout
android:id="@+id/background_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/wikivoyage_card_bg_color">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:orientation="vertical"
android:paddingTop="@dimen/content_padding">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/content_padding"
android:layout_marginRight="@dimen/content_padding"
android:layout_marginStart="@dimen/content_padding"
android:layout_marginEnd="@dimen/content_padding">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="@dimen/default_title_line_height"
android:layout_marginBottom="@dimen/measurement_tool_menu_title_padding_bottom"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?attr/active_color_basic"
android:textSize="@dimen/default_list_text_size"
osmand:typeface="@string/font_roboto_medium"
tools:text="London" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/distance"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.ContextMenuSubtitle"
android:textColor="@null"
tools:text="5.3 km" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/distance_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginEnd="@dimen/content_padding_half"
android:layout_marginRight="@dimen/content_padding_half"
android:layout_marginStart="@dimen/content_padding_half"
android:layout_marginLeft="@dimen/content_padding_half"
osmand:srcCompat="@drawable/ic_action_distance_16"
android:contentDescription="@string/distance"/>
<View
android:layout_width="1dp"
android:layout_height="16dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="@dimen/content_padding_half"
android:layout_marginRight="@dimen/content_padding_half"
android:background="?attr/wikivoyage_card_divider_color" />
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/diff_ele_down"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.ContextMenuSubtitle"
android:textColor="@null"
tools:text="145 m" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/down_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginEnd="@dimen/content_padding_half"
android:layout_marginRight="@dimen/content_padding_half"
android:layout_marginStart="@dimen/content_padding_half"
android:layout_marginLeft="@dimen/content_padding_half"
osmand:srcCompat="@drawable/ic_action_arrow_down_16"
android:tint="@color/icon_color_default_light"
android:contentDescription="@string/distance" />
<View
android:layout_width="1dp"
android:layout_height="16dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="@dimen/content_padding_half"
android:layout_marginRight="@dimen/content_padding_half"
android:background="?attr/wikivoyage_card_divider_color" />
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/diff_ele_up"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.ContextMenuSubtitle"
android:textColor="@null"
tools:text="15 m" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/up_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginEnd="@dimen/content_padding_half"
android:layout_marginRight="@dimen/content_padding_half"
android:layout_marginStart="@dimen/content_padding_half"
android:layout_marginLeft="@dimen/content_padding_half"
osmand:srcCompat="@drawable/ic_action_arrow_up_16"
android:tint="@color/icon_color_default_light"
android:contentDescription="@string/distance" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="@dimen/content_padding_half"
android:paddingBottom="@dimen/content_padding_small">
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/user_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/btn_border_bg_light"
android:gravity="center_vertical"
android:ellipsize="end"
android:maxLines="1"
android:paddingTop="@dimen/subHeaderPadding"
android:paddingBottom="@dimen/subHeaderPadding"
android:paddingLeft="@dimen/bottom_sheet_content_padding_small"
android:paddingRight="@dimen/bottom_sheet_content_padding_small"
android:textAppearance="@style/TextAppearance.ContextMenuSubtitle"
android:textColor="?attr/active_color_basic"
android:textSize="@dimen/default_desc_text_size"
osmand:typeface="@string/font_roboto_medium"
android:drawablePadding="@dimen/content_padding_small_half"
android:drawableStart="@drawable/ic_action_user_account_16"
android:drawableLeft="@drawable/ic_action_user_account_16"
tools:drawableTint="?attr/wikivoyage_active_color"
tools:text="Lorem Ipsum" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
<View
android:layout_width="match_parent"
android:visibility="invisible"
android:layout_height="1dp"
android:background="?attr/wikivoyage_card_divider_color" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/content_padding_small_half"
android:paddingBottom="@dimen/content_padding_small_half">
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/bottom_sheet_content_padding_small"
android:layout_marginStart="@dimen/bottom_sheet_content_padding_small"
android:background="@drawable/rounded_background_3dp">
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/left_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:drawablePadding="@dimen/content_padding_small"
android:ellipsize="end"
android:gravity="center_vertical"
android:letterSpacing="@dimen/text_button_letter_spacing"
android:maxLines="1"
android:paddingBottom="@dimen/content_padding_half"
android:paddingLeft="@dimen/bottom_sheet_content_padding_small"
android:paddingRight="@dimen/bottom_sheet_content_padding_small"
android:paddingTop="@dimen/content_padding_half"
android:textColor="?attr/wikivoyage_active_color"
android:textSize="@dimen/default_desc_text_size"
osmand:typeface="@string/font_roboto_medium"
tools:drawableLeft="@drawable/ic_action_read_article"
tools:drawableTint="?attr/wikivoyage_active_color"
tools:ignore="UnusedAttribute"
tools:text="Read"
android:paddingStart="@dimen/bottom_sheet_content_padding_small"
android:paddingEnd="@dimen/bottom_sheet_content_padding_small"
tools:drawableStart="@drawable/ic_action_read_article" />
</FrameLayout>
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/bottom_sheet_content_padding_small"
android:layout_marginRight="@dimen/bottom_sheet_content_padding_small"
android:background="@drawable/rounded_background_3dp">
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/right_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:drawablePadding="@dimen/content_padding_small"
android:ellipsize="end"
android:gravity="center_vertical"
android:letterSpacing="@dimen/text_button_letter_spacing"
android:maxLines="1"
android:paddingBottom="@dimen/content_padding_half"
android:paddingLeft="@dimen/bottom_sheet_content_padding_small"
android:paddingRight="@dimen/bottom_sheet_content_padding_small"
android:paddingTop="@dimen/content_padding_half"
android:textColor="?attr/wikivoyage_active_color"
android:textSize="@dimen/default_desc_text_size"
osmand:typeface="@string/font_roboto_medium"
tools:drawableRight="@drawable/ic_action_read_later_fill"
tools:drawableTint="?attr/wikivoyage_active_color"
tools:ignore="UnusedAttribute"
tools:text="Delete"
tools:drawableEnd="@drawable/ic_action_read_later_fill"
android:paddingEnd="@dimen/bottom_sheet_content_padding_small"
android:paddingStart="@dimen/bottom_sheet_content_padding_small" />
</FrameLayout>
</LinearLayout>
</LinearLayout>
</FrameLayout>
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/wikivoyage_card_divider_color" />
<include
android:id="@+id/shadow"
layout="@layout/card_bottom_divider"
android:visibility="gone"
tools:visibility="visible" />
<include
android:id="@+id/list_item_divider"
layout="@layout/list_item_divider"
android:visibility="gone"
tools:visibility="visible" />
</LinearLayout>

View file

@ -4002,4 +4002,10 @@
<string name="announcement_time_off_route">Abweichung von der Route</string>
<string name="announcement_time_arrive">Ankunft am Ziel</string>
<string name="announcement_time_approach">Annährung ans Ziel</string>
<string name="context_menu_read_full">Vollständig lesen</string>
<string name="delete_online_routing_engine">Dieses Online-Routingmodul löschen\?</string>
<string name="context_menu_edit_descr">Beschreibung bearbeiten</string>
<string name="delete_waypoints">Wegpunkte löschen</string>
<string name="copy_to_map_favorites">In Favoriten kopieren</string>
<string name="copy_to_map_markers">In Kartenmarkierungen kopieren</string>
</resources>

View file

@ -4000,4 +4000,10 @@
<string name="announcement_time_intervals">Interspacoj distancaj kaj tempaj</string>
<string name="announcement_time_descr">Tempo de anonco de diversaj voĉaj sciigoj dependas de ilia specoj, nuna naviga kaj implicita naviga rapido.</string>
<string name="announcement_time_title">Tempo de anonco</string>
<string name="copy_to_map_favorites">Kopii al ŝatataj</string>
<string name="delete_online_routing_engine">Ĉu forigi tiun ĉi enretan navigilon\?</string>
<string name="context_menu_read_full">Legi pli</string>
<string name="context_menu_edit_descr">Redakti priskribon</string>
<string name="delete_waypoints">Forigi navigadpunktojn</string>
<string name="copy_to_map_markers">Kopii al mapmarkoj</string>
</resources>

View file

@ -543,7 +543,7 @@
<string name="left_side_navigation">Circolazione a sinistra</string>
<string name="left_side_navigation_descr">Per i paesi in cui si guida nella parte sinistra della strada.</string>
<string name="unknown_from_location">Posizione di partenza non ancora determinata.</string>
<string name="confirm_interrupt_download">Annulla il download del file\?</string>
<string name="confirm_interrupt_download">Annulla il download\?</string>
<string name="basemap_was_selected_to_download">La mappa di base necessaria per il funzionamento è in coda per il download.</string>
<string name="map_online_plugin_is_not_installed">Abilita il plugin \'Mappe Online\' per scegliere altre sorgenti di mappe</string>
<string name="map_online_data">Mappe online e a tasselli</string>
@ -929,7 +929,7 @@
<string name="favourites_list_activity">Seleziona Preferito</string>
<string name="local_openstreetmap_act_title">Modifiche OSM</string>
<string name="download_using_mobile_internet">Non connesso al Wi-Fi. Usare la connessione a Internet attuale per il download\?</string>
<string name="cancel_route">Cancellare il percorso\?</string>
<string name="cancel_route">Ignora il percorso\?</string>
<string name="cancel_navigation">Interrompere la navigazione</string>
<string name="clear_destination">Cancella la destinazione</string>
<string name="other_location">Altra</string>
@ -1399,7 +1399,7 @@
<string name="shared_string_ellipsis"></string>
<string name="shared_string_ok">Ok</string>
<string name="shared_string_cancel">Annulla</string>
<string name="shared_string_dismiss">Annulla</string>
<string name="shared_string_dismiss">Ignora</string>
<string name="shared_string_yes"></string>
<string name="shared_string_no">No</string>
<string name="shared_string_on">Acceso</string>
@ -1989,7 +1989,7 @@
<string name="first_usage_greeting">Ottieni indicazioni e scopri nuovi luoghi senza una connessione a Internet</string>
<string name="search_another_country">Scegli un\'altra regione</string>
<string name="skip_map_downloading_desc">Non hai mappe offline installate. Si può scegliere una mappa dalla lista o scaricarle in seguito andando su \'Menù - %1$s\'.</string>
<string name="osm_live_payment_desc">Il costo della sottoscrizione verrà addebitato ogni mese. Puoi scegliere di annullare la sottoscrizione su Google Play in ogni momento.</string>
<string name="osm_live_payment_desc">La sottoscrizione viene addebitata ogni mese. Puoi annullarla su Google Play in ogni momento.</string>
<string name="donation_to_osm">Donazione per la comunità di OpenStreetMap</string>
<string name="donation_to_osm_desc">Parte della tua donazione verrà inviata agli utenti che fanno modifiche su OpenStreetMap. Il costo della sottoscrizione rimane inalterato.</string>
<string name="osm_live_subscription_desc">La sottoscrizione attiva aggiornamenti orari, quotidiani e settimanali e la possibilità di scaricare senza limiti tutte le mappe del mondo.</string>
@ -3134,7 +3134,7 @@
<string name="coordinates_format_info">Il formato selezionato sarà applicato per tutta l\'app.</string>
<string name="pref_selected_by_default_for_profiles">Questa impostazione è selezionata di default per i profili: %s</string>
<string name="change_default_settings">Cambia impostazioni</string>
<string name="discard_changes">Annulla cambiamenti</string>
<string name="discard_changes">Annulla modifiche</string>
<string name="apply_to_current_profile">Applica solo a \"%1$s\"</string>
<string name="apply_to_all_profiles">Applica a tutti i profili</string>
<string name="start_up_message_pref">Messaggio di avvio</string>
@ -3704,7 +3704,7 @@
<string name="use_volume_buttons_as_zoom_descr">Abilita per controllare il livello di zoom della mappa con i pulsanti del volume del dispositivo.</string>
<string name="use_volume_buttons_as_zoom">Pulsanti volume come zoom</string>
<string name="please_provide_point_name_error">Per favore indica un nome per il punto</string>
<string name="quick_action_remove_next_destination_descr">Il punto di destinazione corrente sul percorso verrà eliminato. Se sarà la Destinazione, la navigazione verrà interrotta.</string>
<string name="quick_action_remove_next_destination_descr">Elimina la destinazione successiva nel percorso. Se questa è la destinazione finale, la navigazione si fermerà.</string>
<string name="search_download_wikipedia_maps">Scarica mappe Wikipedia</string>
<string name="plugin_wikipedia_description">Ottieni informazioni sui punti di interesse da Wikipedia. È la tua guida tascabile offline - basta abilitare il plugin Wikipedia e goderti gli articoli sugli oggetti intorno a te.</string>
<string name="app_mode_enduro_motorcycle">Moto da enduro</string>

View file

@ -3993,4 +3993,12 @@
<string name="routing_engine_vehicle_type_cycling_mountain">רכיבת הרים</string>
<string name="routing_engine_vehicle_type_cycling_road">רכיבת כביש</string>
<string name="routing_engine_vehicle_type_cycling_regular">רכיבה רגילה</string>
<string name="routing_engine_vehicle_type_hiking">טיול שטח</string>
<string name="routing_engine_vehicle_type_hgv">משאית כבדה</string>
<string name="delete_online_routing_engine">למחוק את מנוע הניווט המקוון הזה\?</string>
<string name="context_menu_read_full">להציג במלואו</string>
<string name="context_menu_edit_descr">עריכת תיאור</string>
<string name="delete_waypoints">מחיקת נקודות דרך</string>
<string name="copy_to_map_markers">העתקה לסמני המפה</string>
<string name="copy_to_map_favorites">העתקה למועדפים</string>
</resources>

View file

@ -2790,7 +2790,7 @@
<string name="poi_crop_rice">Вирощується культура: рис</string>
<string name="poi_nuclear_explosion_type_cratering_burst">Вид вибуху: неглибокого закладення</string>
<string name="poi_provided_for_adult_yes">Послуги надаються дорослим: так</string>
<string name="poi_xmas_note">Різдво: нотатка</string>
<string name="poi_xmas_note">Різдво: примітка</string>
<string name="poi_cuisine_wings">Крильця</string>
<string name="poi_cuisine_jamaican">Ямайська</string>
<string name="poi_outdoor_seating_pedestrian_zone">Місця на відкритому повітрі: пішохідна зона</string>
@ -3019,7 +3019,7 @@
<string name="poi_drink_wine_yes">Вино: так</string>
<string name="poi_vending_drinks_food">Напої і закуски</string>
<string name="poi_wetland_saltmarsh">Болото, періодично затоплюване морською водою</string>
<string name="poi_note">Нотатка</string>
<string name="poi_note">Примітка</string>
<string name="poi_crop_flowers">Вирощується культура: квіти</string>
<string name="poi_water_heater_no">Водонагрівач: відсутній</string>
<string name="poi_free_flying_site_orientation_e">Орієнтація майданчика для вільного польоту: схід (E)</string>

View file

@ -57,7 +57,7 @@
<string name="switch_to_raster_map_to_see">Для поточної місцевості відсутні векторні автономні мапи. Завантажте їх у \'Налаштуваннях\' (\'Керування файлами мап\') або перемкніться на втулок \'Мережеві мапи\'.</string>
<string name="send_files_to_osm">Відправити GPX файли в OSM?</string>
<string name="gpx_visibility_txt">Видимість</string>
<string name="gpx_tags_txt">Теги</string>
<string name="gpx_tags_txt">Мітки</string>
<string name="validate_gpx_upload_name_pwd">Для вивантаження GPX-файлів вкажіть ваше ім\'я користувача і пароль в OSM.</string>
<string name="default_buttons_support">Підтримка</string>
<string name="support_new_features">Підтримати розробку нових функцій</string>
@ -121,13 +121,13 @@
<string name="offline_edition_descr">Якщо ввімкнено редагування в автономному режимі, тоді зміни буде збережено спочатку локально та завантажено за запитом, інакше зміни буде завантажено негайно.</string>
<string name="update_poi_does_not_change_indexes">Зміни POI всередині застосунку не впливають на завантажені файли мап - вони зберігаються в окремий файл на Вашому пристрої.</string>
<string name="local_openstreetmap_uploading">Вивантаження…</string>
<string name="local_openstreetmap_were_uploaded">{0} POI/нотатки вивантажено</string>
<string name="local_openstreetmap_were_uploaded">{0} POI/примітки вивантажено</string>
<string name="local_openstreetmap_uploadall">Вивантажити усе</string>
<string name="local_openstreetmap_upload">Вивантажити правки в OSM</string>
<string name="local_openstreetmap_delete">Видалити редагування</string>
<string name="local_openstreetmap_descr_title">Асинхронне редагування OSM:</string>
<string name="local_openstreetmap_settings">OSM-POI/Нотатки, збережено на пристрої</string>
<string name="local_openstreetmap_settings_descr">Перегляд і керування OSM-POI/нотатками, збереженими у базі даних на пристрої.</string>
<string name="local_openstreetmap_settings">OSM-POI/примітки, збережено на пристрої</string>
<string name="local_openstreetmap_settings_descr">Перегляд і керування OSM-POI/примітками, збереженими у базі даних пристрою.</string>
<string name="live_monitoring_interval_descr">Вкажіть інтервал надсилання даних.</string>
<string name="live_monitoring_interval">Інтервал надсилання даних</string>
<string name="live_monitoring_url_descr">Вкажіть веб-адресу з наступними параметрами: lat={0}, lon={1}, timestamp={2}, hdop={3}, altitude={4}, speed={5}, bearing={6}.</string>
@ -435,7 +435,7 @@
<string name="layer_yandex_traffic">Яндекс Пробки</string>
<string name="layer_route">Маршрут</string>
<string name="shared_string_favorites">Закладки</string>
<string name="layer_osm_bugs">Нотатки OSM (мережеві)</string>
<string name="layer_osm_bugs">Примітки OSM (мережеві)</string>
<string name="layer_poi">POI-накладення…</string>
<string name="layer_map">Джерело мапи…</string>
<string name="menu_layers">Шари мапи</string>
@ -597,7 +597,7 @@
<string name="update_tile">Оновити мапу</string>
<string name="reload_tile">Оновити частину мапи</string>
<string name="mark_point">Точка</string>
<string name="shared_string_add_to_favorites">Додати в закладки</string>
<string name="shared_string_add_to_favorites">Додати до закладок</string>
<string name="use_english_names_descr">Вибір між місцевими та англійськими назвами.</string>
<string name="use_english_names">Використовувати англійські назви на мапах</string>
<string name="app_settings">Налаштування програми</string>
@ -657,7 +657,7 @@
<string name="shared_string_apply">Застосувати</string>
<string name="shared_string_add">Додати</string>
<string name="shared_string_no">Ні</string>
<string name="add_favorite_dialog_top_text">Введіть ім’я Закладки</string>
<string name="add_favorite_dialog_top_text">Введіть назву Закладки</string>
<string name="add_favorite_dialog_default_favourite_name">Закладка</string>
<string name="add_favorite_dialog_favourite_added_template">Точку Закладки \'\'{0}\'\' додано.</string>
<string name="favourites_context_menu_edit">Редагувати Закладку</string>
@ -683,7 +683,7 @@
<string name="poi_dialog_opening_hours">Відкрити</string>
<string name="poi_dialog_comment">Коментар</string>
<string name="poi_dialog_comment_default">Зміна POI</string>
<string name="poi_dialog_other_tags_message">Наступні теги POI не можуть бути змінені</string>
<string name="poi_dialog_other_tags_message">Всі інші мітки POI не буде змінено</string>
<string name="default_buttons_commit">Зберегти</string>
<string name="shared_string_clear">Очистити</string>
<string name="filter_current_poiButton">Фільтр</string>
@ -776,7 +776,7 @@
<string name="recording_photo_description">Світлина %1$s %2$s</string>
<string name="av_def_action_picture">Зробити світлину</string>
<string name="recording_context_menu_precord">Зробити світлину</string>
<string name="dropbox_plugin_description">Взаємозберігайте треки та звуко/відео-нотатки з Вашим обліковим записом Dropbox.</string>
<string name="dropbox_plugin_description">Синхронізуйте треки та звуко/відеопримітки з вашим обліковим записом Dropbox.</string>
<string name="dropbox_plugin_name">Втулок Dropbox</string>
<string name="intermediate_points_change_order">Змінити порядок</string>
<string name="srtm_paid_version_msg">Будь ласка, зверніть увагу на оплату втулка \"Горизонталі\" для підтримки подальшого розвитку.</string>
@ -795,7 +795,7 @@
<string name="recording_unavailable">недоступно</string>
<string name="shared_string_control_stop">Зупинити</string>
<string name="shared_string_control_start">Почати</string>
<string name="map_widget_av_notes">Звуко/відео-нотатки</string>
<string name="map_widget_av_notes">Звуко/відеопримітки</string>
<string name="osmand_srtm_short_description_80_chars">Втулок OsmAnd для показу горизонталей в автономному режимі</string>
<string name="av_use_external_camera">Використовувати програму Камера</string>
<string name="av_settings_descr">Налаштування аудіо/відео запису.</string>
@ -805,8 +805,8 @@
<string name="recording_default_name">Запис</string>
<string name="av_def_action_choose">За запитом\?</string>
<string name="recording_is_recorded">Аудіо/відео зараз записується. Для зупинки натисніть на AV-віджет.</string>
<string name="recording_context_menu_arecord">Створити аудіо-нотатку</string>
<string name="recording_context_menu_vrecord">Створити відео-нотатку</string>
<string name="recording_context_menu_arecord">Створити аудіопримітку</string>
<string name="recording_context_menu_vrecord">Створити відеопримітку</string>
<string name="recording_context_menu_delete">Вилучити запис</string>
<string name="recording_context_menu_play">Грати</string>
<string name="map_widget_intermediate_distance">Проміжний пункт призначення</string>
@ -820,9 +820,9 @@
\n
\nВсесвітні дані (між 70° на півночі і 70° на півдні) базуються на вимірюваннях SRTM (Shuttle Radar Topography Mission) і ASTER (Advanced Spaceborne Thermal Emission and Reflection Radiometer), даних інструменту відмальовуванню Terra, флагманського супутника системи спостереження Землі від NASA. ASTER — це підсумок спільної роботи NASA, міністерства економіки Японії, міністерства торгівлі і промисловості Японії (METI), агенції космічних систем Японії (J-spacesystems).</string>
<string name="map_widget_distancemeasurement">Вимірювання відстаней</string>
<string name="map_widget_audionotes">Звуко-нотатки</string>
<string name="audionotes_plugin_description">Зробіть звуко/світлино/відео нотатки під час подорожі, використовуючи або кнопку мапи, або контекстне меню розташування.</string>
<string name="audionotes_plugin_name">Звуко/відео-нотатки</string>
<string name="map_widget_audionotes">Аудіопримітки</string>
<string name="audionotes_plugin_description">Робіть звуко/світлино/відео примітки під час подорожі використовуючи кнопку мапи або контекстне меню розташування.</string>
<string name="audionotes_plugin_name">Звуко/відеопримітки</string>
<string name="index_srtm_parts">частин</string>
<string name="index_srtm_ele">Горизонталі</string>
<string name="srtm_plugin_description">Цей втулок показує як шар горизонталей, так і шар рельєфу поверх усталених мап OsmAnd. Ця функціональність стане в пригоді спортсменам, туристам, мандрівникам та будь-кому, хто цікавиться структурою рельєфу місцевості. (Зверніть увагу, що дані про горизонталі є окремими від даних про рельєф; додаткові завантаження доступні після задіювання втулку.)
@ -843,7 +843,7 @@
<string name="safe_mode_description">Запустити програму в безпечному режимі (використовує повільніші Android-функції замість власних).</string>
<string name="safe_mode">Безпечний режим</string>
<string name="native_library_not_running">Програму запущено в безпечному режимі (вимкніть його в \'Налаштуваннях\').</string>
<string name="audionotes_location_not_defined">Виберіть \"Використати місцеперебування...\" для прив\'язки нотатки до поточного розташування.</string>
<string name="audionotes_location_not_defined">Виберіть \"Використати місцеперебування...\" для прив\'язки примітки до поточного розташування.</string>
<string name="background_service_is_enabled_question">Службу OsmAnd у тлі досі запущено. Зупинити її роботу також\?</string>
<string name="close_changeset">Закрити набір змін</string>
<string name="zxing_barcode_scanner_not_found">Програма \'ZXing Barcode Scanner\' не встановлена. Шукати в Google Play?</string>
@ -1370,8 +1370,8 @@
<string name="navigate_point_northing">Північної широти</string>
<string name="shared_string_dismiss">Відхилити</string>
<string name="shared_string_audio">Аудіо</string>
<string name="share_note">Поділитись нотаткою</string>
<string name="notes">A/V нотатки</string>
<string name="share_note">Поділитись приміткою</string>
<string name="notes">A/V примітки</string>
<string name="online_map">Мережева мапа</string>
<string name="roads_only">Тільки дороги</string>
<string name="free">Вільно %1$s</string>
@ -1382,7 +1382,7 @@
<string name="nautical_maps_missing">Завантажте спеціальну безмережеву мапу, щоб відобразити морські подробиці.</string>
<string name="edit_group">Редагувати групу</string>
<string name="parking_place">Місце для стоянки</string>
<string name="remove_the_tag">ВИЛУЧИТИ ТЕҐ</string>
<string name="remove_the_tag">ВИЛУЧИТИ МІТКУ</string>
<string name="gps_status">Стан GPS</string>
<string name="version_settings_descr">Завантажити нічні збірки.</string>
<string name="version_settings">Збірки</string>
@ -1624,19 +1624,19 @@
<string name="osm_save_offline">Зберегти локально</string>
<string name="osm_edit_modified_poi">OSM POI відредаговано</string>
<string name="osm_edit_deleted_poi">OSM POI видалено</string>
<string name="context_menu_item_open_note">Відкрити OSM-нотатку</string>
<string name="osm_edit_reopened_note">Відкрити заново OSM-нотатку</string>
<string name="osm_edit_commented_note">До OSM-нотатки додано коментар</string>
<string name="osm_edit_created_note">Створено OSM-нотатку</string>
<string name="osn_bug_name">OSM-нотатка</string>
<string name="osn_add_dialog_title">Створити нотатку</string>
<string name="context_menu_item_open_note">Відкрити примітку OSM</string>
<string name="osm_edit_reopened_note">Повторно відкрити примітку OSM</string>
<string name="osm_edit_commented_note">До примітки OSM додано коментар</string>
<string name="osm_edit_created_note">Створено примітку OSM</string>
<string name="osn_bug_name">Примітка OSM</string>
<string name="osn_add_dialog_title">Створити примітку</string>
<string name="osn_comment_dialog_title">Додати коментар</string>
<string name="osn_reopen_dialog_title">Перевідкрити нотатку</string>
<string name="osn_close_dialog_title">Закрити нотатку</string>
<string name="osn_add_dialog_success">Нотатку створено</string>
<string name="osn_add_dialog_error">Не вдалося створити нотатку.</string>
<string name="osn_close_dialog_success">Нотатку закрито</string>
<string name="osn_close_dialog_error">Не вдалося закрити нотатку.</string>
<string name="osn_reopen_dialog_title">Перевідкрити примітку</string>
<string name="osn_close_dialog_title">Закрити примітку</string>
<string name="osn_add_dialog_success">Примітку створено</string>
<string name="osn_add_dialog_error">Не вдалося створити примітку.</string>
<string name="osn_close_dialog_success">Примітку закрито</string>
<string name="osn_close_dialog_error">Не вдалося закрити примітку.</string>
<string name="shared_string_commit">Підтвердити</string>
<string name="context_menu_item_delete_waypoint">Вилучити GPX точку?</string>
<string name="context_menu_item_edit_waypoint">Редагувати GPX точку</string>
@ -1947,8 +1947,8 @@
<string name="osm_live_header">Передплата дозволяє щогодини отримувати оновлення для мап по всьому світу.
\nЧастина від передплати переводиться спільноті OSM та виплачується кожному землеписцю за його внесок.
\nЯкщо Вам подобається OsmAnd та OSM, і хочете підтримати і бути підтриманими ними, це ідеальний спосіб зробити це.</string>
<string name="upload_osm_note_description">Надішліть Вашу OSM-нотатку таємно, або скориставшись обліковим записом на OSM.org.</string>
<string name="upload_osm_note">Надіслати нотатку в OSM</string>
<string name="upload_osm_note_description">Надішліть примітку OSM знеособлено чи скориставшись профілем OpenStreetMap.org.</string>
<string name="upload_osm_note">Надіслати примітку в OSM</string>
<string name="file_name_containes_illegal_char">Неприпустимі знаки в назві файлу</string>
<string name="follow_us">Слідкуйте за нами</string>
<string name="access_direction_audio_feedback">Звукові напрямки</string>
@ -2162,19 +2162,19 @@
<string name="quick_action_map_underlay_action">Додати підкладку</string>
<string name="quick_action_map_source">Змінити джерело мапи</string>
<string name="quick_action_btn_tutorial_descr">Довге натискання з перетягуванням кнопки змінює її розташування на екрані.</string>
<string name="quick_action_add_osm_bug">Додати нотатку OSM</string>
<string name="quick_action_add_osm_bug">Додати примітку OSM</string>
<string name="rendering_value_fine_name">Дуже тонкий</string>
<string name="navigate_point_olc">Відкритий код розташування (OLC)</string>
<string name="quick_action_take_audio_note">Нова аудіонотатка</string>
<string name="quick_action_take_video_note">Нова відеонотатка</string>
<string name="quick_action_take_photo_note">Нова фотонотатка</string>
<string name="quick_action_take_audio_note">Нова аудіопримітка</string>
<string name="quick_action_take_video_note">Нова відеопримітка</string>
<string name="quick_action_take_photo_note">Нова фотопримітка</string>
<string name="quick_favorites_name_preset">Найменування</string>
<string name="quick_action_add_marker_descr">Кнопка для додавання позначки мапи посередині екрану.</string>
<string name="quick_action_add_gpx_descr">Натискання на цю кнопку додасть маршрутну точку GPX посередині екрану.</string>
<string name="quick_action_take_audio_note_descr">Натискання цієї кнопки додає аудіонотатку посередині екрану.</string>
<string name="quick_action_take_video_note_descr">Натискання цієї кнопки додає відеонотатку посередині екрану.</string>
<string name="quick_action_take_photo_note_descr">Натискання цієї кнопки додає фотонотатку посередині екрану.</string>
<string name="quick_action_add_osm_bug_descr">Натискання цієї кнопки додає OSM-нотатку посередині екрану.</string>
<string name="quick_action_take_audio_note_descr">Натискання цієї кнопки додає аудіопримітку посередині екрана.</string>
<string name="quick_action_take_video_note_descr">Натискання цієї кнопки додає відеопримітку посередині екрана.</string>
<string name="quick_action_take_photo_note_descr">Натискання цієї кнопки додає фотопримітку посередині екрана.</string>
<string name="quick_action_add_osm_bug_descr">Натискання цієї кнопки додає примітку OSM посередині екрану.</string>
<string name="quick_action_add_poi_descr">Натискання цієї кнопки додає POI посередині екрану.</string>
<string name="quick_action_navigation_voice_descr">Перемикач, щоб вимкнути або увімкнути голосові підказки під час навігації.</string>
<string name="quick_action_add_parking_descr">Кнопка для додавання місця паркування посередині екрана.</string>
@ -2212,10 +2212,10 @@
<string name="nothing_found">Нічого не знайдено</string>
<string name="private_access_routing_req">Місце призначення розташовано в області з приватним доступом. Дозволити доступ до приватних доріг у цій подорожі\?</string>
<string name="nothing_found_descr">Змініть пошуковий запит або ж розширте пошуковий радіус.</string>
<string name="quick_action_showhide_osmbugs_title">Показати/приховати OSM-нотатки</string>
<string name="quick_action_osmbugs_show">Показати OSM-нотатки</string>
<string name="quick_action_osmbugs_hide">Приховати OSM-нотатки</string>
<string name="quick_action_showhide_osmbugs_descr">Натискання на кнопку дії покаже чи приховає OSM-нотатки на мапі.</string>
<string name="quick_action_showhide_osmbugs_title">Показати/приховати примітки OSM</string>
<string name="quick_action_osmbugs_show">Показати примітки OSM</string>
<string name="quick_action_osmbugs_hide">Приховати примітки OSM</string>
<string name="quick_action_showhide_osmbugs_descr">Натискання на кнопку дії покаже чи приховає примітки OSM на мапі.</string>
<string name="sorted_by_distance">Відсортоване за відстанню</string>
<string name="search_favorites">Пошук у закладках</string>
<string name="hillshade_menu_download_descr">Завантажте шар мапи «Пагорби», щоб показати вертикальне затінення.</string>
@ -2526,9 +2526,9 @@
<string name="routing_attr_avoid_ice_roads_fords_description">Уникає льодових доріг і бродів.</string>
<string name="make_round_trip_descr">Додати копію початкової точки як місце призначення.</string>
<string name="make_round_trip">Зробити кругову подорож</string>
<string name="osn_modify_dialog_error">Не вдалося змінити нотатку.</string>
<string name="osn_modify_dialog_title">Змінити нотатку</string>
<string name="context_menu_item_modify_note">Змінити OSM-нотатку</string>
<string name="osn_modify_dialog_error">Не вдалося змінити примітку.</string>
<string name="osn_modify_dialog_title">Змінити примітку</string>
<string name="context_menu_item_modify_note">Змінити примітку OSM</string>
<string name="wrong_input">Неправильний ввід</string>
<string name="wrong_format">Неправильний формат</string>
<string name="shared_string_road">Дорога</string>
@ -2576,15 +2576,15 @@
<string name="looking_for_tracks_with_waypoints">Пошук треків з шляховими точками</string>
<string name="shared_string_more_without_dots">Більше</string>
<string name="empty_state_osm_edits">Створити або змінити OSM-об\'єкти</string>
<string name="empty_state_osm_edits_descr">Створюйте або змінюйте цікаві точки в OSM, відкривайте або коментуйте OSM-нотатки, а також надсилайте записані GPX-файли.</string>
<string name="empty_state_osm_edits_descr">Створюйте або змінюйте OSM POI, відкривайте або коментуйте примітки OSM та надсилайте записані GPX-файли.</string>
<string name="shared_string_deleted">Вилучено</string>
<string name="shared_string_edited">Відредаговано</string>
<string name="shared_string_added">Додано</string>
<string name="marker_activated">Позначку %s задіяно.</string>
<string name="one_tap_active_descr">Натискання на позначку на мапі перемістить її на перше місце в списку задіяних позначок, не відкриваючи контекстне меню.</string>
<string name="one_tap_active">Задіювання одним натисненням</string>
<string name="empty_state_av_notes">Робіть нотатки!</string>
<string name="empty_state_av_notes_desc">Додайте аудіо, відео або фотонотатку в будь-яку точку на мапі, використовуючи віджет або контекстне меню.</string>
<string name="empty_state_av_notes">Робіть примітки!</string>
<string name="empty_state_av_notes_desc">Додайте аудіо, відео або фотопримітку в будь-яку точку на мапі використовуючи віджет або контекстне меню.</string>
<string name="notes_by_date">Примітки за датою</string>
<string name="by_date">За датою</string>
<string name="by_type">За типом</string>
@ -2620,14 +2620,14 @@
<string name="last_intermediate_dest_description">Додає проміжну зупинку</string>
<string name="first_intermediate_dest_description">Додає першу зупинку</string>
<string name="subsequent_dest_description">Пересунути призначення далі і створити його</string>
<string name="show_closed_notes">Показати закриті нотатки</string>
<string name="show_closed_notes">Показати закриті примітки</string>
<string name="switch_osm_notes_visibility_desc">Показати чи приховати примітки OSM на мапі.</string>
<string name="gpx_file_desc">GPX — підходить для експорту в JOSM та інші OSM-редактори.</string>
<string name="osc_file_desc">OSC — підходить для експорту в OSM.</string>
<string name="shared_string_gpx_file">GPX-файл</string>
<string name="osc_file">OSC-файл</string>
<string name="choose_file_type">Виберіть тип файлу</string>
<string name="osm_edits_export_desc">Виберіть вид експорту: OSM-нотатки, POI чи і те і те.</string>
<string name="osm_edits_export_desc">Виберіть вид експорту: примітки OSM, POI чи і те і те.</string>
<string name="all_data">Усі дані</string>
<string name="osm_notes">Примітки OSM</string>
<string name="will_open_tomorrow_at">Відкриється завтра о</string>
@ -2844,7 +2844,7 @@
<string name="routeInfo_steepness_name">Крутість</string>
<string name="run_full_osmand_header">Запустити OsmAnd\?</string>
<string name="shared_string_walk">Пішки</string>
<string name="save_poi_value_exceed_length">Довжина тегу \"%s\" має бути менше 255 символів.</string>
<string name="save_poi_value_exceed_length">Довжина мітки \"%s\" має бути менше 255 символів.</string>
<string name="public_transport_warning_descr_blog">Докладніше про те, як OsmAnd розраховує маршрути, читайте в нашому деннику.</string>
<string name="public_transport_warning_title">Навігація громадським транспортом на даний час проходить тестування, можливі помилки та неточності.</string>
<string name="add_intermediate">Додати проміжну точку</string>
@ -3343,9 +3343,9 @@
<string name="live_monitoring">Мережеве відстеження</string>
<string name="save_track_logging_accuracy">Точність журналювання</string>
<string name="tracks_view_descr">Ви можете знайти всі записи в %1$s або в теці OsmAnd за допомогою файлового провідника.</string>
<string name="multimedia_notes_view_descr">Ваші нотатки OSM розміщено в %1$s.</string>
<string name="video_notes">Відеонотатки</string>
<string name="photo_notes">Фотонотатки</string>
<string name="multimedia_notes_view_descr">Ваші примітки OSM розміщено в %1$s.</string>
<string name="video_notes">Відеопримітки</string>
<string name="photo_notes">Фотопримітки</string>
<string name="route_recalculation">Перерахунок маршруту</string>
<string name="accessibility_announce">Оголошення</string>
<string name="login_and_pass">Ім\'я користувача і пароль</string>
@ -3617,7 +3617,7 @@
<string name="quick_action_show_hide_transport">Показати чи приховати громадський транспорт</string>
<string name="quick_action_transport_descr">Кнопка показу або приховування громадського транспорту на мапі.</string>
<string name="create_edit_poi">Створити чи змінити POI</string>
<string name="add_edit_favorite">Додати чи змінити вибране</string>
<string name="add_edit_favorite">Додати чи змінити закладку</string>
<string name="reset_deafult_order">Відновити усталене впорядкування</string>
<string name="back_to_editing">Повернутися до редагування</string>
<string name="additional_actions_descr">Ви можете отримати доступ до цих дій, торкнувшись кнопки “%1$s”.</string>
@ -3999,4 +3999,10 @@
<string name="routing_engine_vehicle_type_mtb">Гірський велосипед</string>
<string name="message_server_error">Помилка сервера: %1$s</string>
<string name="message_name_is_already_exists">Назва вже існує</string>
<string name="delete_online_routing_engine">Видалити цей рушій мережної маршрутизації\?</string>
<string name="context_menu_read_full">Читати повністю</string>
<string name="context_menu_edit_descr">Змінити опис</string>
<string name="delete_waypoints">Видалити маршрутні точки</string>
<string name="copy_to_map_markers">Копіювати до позначок мапи</string>
<string name="copy_to_map_favorites">Копіювати до закладок</string>
</resources>

View file

@ -12,6 +12,11 @@
-->
<string name="toast_select_edits_for_upload">Select edits for upload</string>
<string name="uploaded_count">Uploaded %1$d of %2$d</string>
<string name="uploading_count">Uploading %1$d of %2$d</string>
<string name="upload_photo_completed">Upload completed</string>
<string name="upload_photo">Uploading</string>
<string name="copy_to_map_favorites">Copy to favorites</string>
<string name="copy_to_map_markers">Copy to map markers</string>
<string name="delete_waypoints">Delete waypoints</string>
@ -2439,7 +2444,7 @@
<string name="average">Average</string>
<string name="of">%1$d of %2$d</string>
<string name="ascent_descent">Ascent/Descent</string>
<string name="moving_time">Moving time</string>
<string name="moving_time">Time in motion</string>
<string name="max_min">Max/Min</string>
<string name="min_max">Min/Max</string>
<string name="index_tours">Tours</string>

View file

@ -636,7 +636,7 @@ public class UiUtilities {
int activeDisableColor = getColorWithAlpha(activeColor, 0.25f);
ColorStateList activeCsl = new ColorStateList(states, new int[] {activeColor, activeDisableColor});
int inactiveColor = ContextCompat.getColor(ctx, nightMode ? R.color.icon_color_default_dark : R.color.icon_color_secondary_light);
ColorStateList inactiveCsl = new ColorStateList(states, new int[] {inactiveColor, inactiveColor});
ColorStateList inactiveCsl = new ColorStateList(states, new int[] {activeDisableColor, inactiveColor});
slider.setTrackActiveTintList(activeCsl);
slider.setTrackInactiveTintList(inactiveCsl);
slider.setHaloTintList(activeCsl);

View file

@ -70,7 +70,6 @@ import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem;
import net.osmand.plus.GpxSelectionHelper.SelectedGpxFile;
import net.osmand.plus.OnDismissDialogFragmentListener;
import net.osmand.plus.OsmAndConstants;
import net.osmand.plus.OsmAndLocationProvider;
import net.osmand.plus.OsmAndLocationProvider.OsmAndLocationListener;
import net.osmand.plus.OsmAndLocationSimulation;
import net.osmand.plus.OsmandApplication;
@ -1201,7 +1200,7 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven
selectedGpxFile = app.getSelectedGpxHelper().getSelectedFileByPath(gpxFile.path);
}
TrackAppearanceFragment.showInstance(this, selectedGpxFile);
TrackAppearanceFragment.showInstance(this, selectedGpxFile, null);
} else if (toShow instanceof QuadRect) {
QuadRect qr = (QuadRect) toShow;
mapView.fitRectToMap(qr.left, qr.right, qr.top, qr.bottom, (int) qr.width(), (int) qr.height(), 0);
@ -1568,6 +1567,17 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven
}
}
public boolean shouldHideTopControls() {
boolean hideTopControls = !mapContextMenu.shouldShowTopControls();
TrackMenuFragment fragment = getTrackMenuFragment();
if (fragment != null) {
hideTopControls = hideTopControls || !fragment.shouldShowTopControls();
}
return hideTopControls;
}
public OsmandMapTileView getMapView() {
return mapView;
}

View file

@ -103,7 +103,7 @@ public abstract class ContextMenuFragment extends BaseOsmAndFragment
public interface ContextMenuFragmentListener {
void onContextMenuYPosChanged(@NonNull ContextMenuFragment fragment, int y, boolean needMapAdjust, boolean animated);
void onContextMenuStateChanged(@NonNull ContextMenuFragment fragment, int menuState);
void onContextMenuStateChanged(@NonNull ContextMenuFragment fragment, int menuState, int previousMenuState);
void onContextMenuDismiss(@NonNull ContextMenuFragment fragment);
}
@ -815,7 +815,7 @@ public abstract class ContextMenuFragment extends BaseOsmAndFragment
ContextMenuFragmentListener listener = this.listener;
if (listener != null) {
listener.onContextMenuStateChanged(this, newMenuState);
listener.onContextMenuStateChanged(this, newMenuState, currentMenuState);
}
}
@ -946,6 +946,11 @@ public abstract class ContextMenuFragment extends BaseOsmAndFragment
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
protected void runLayoutListener() {
runLayoutListener(null);
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
protected void runLayoutListener(final Runnable runnable) {
if (view != null) {
ViewTreeObserver vto = view.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@ -971,7 +976,11 @@ public abstract class ContextMenuFragment extends BaseOsmAndFragment
ContextMenuFragmentListener listener = ContextMenuFragment.this.listener;
if (listener != null) {
listener.onContextMenuStateChanged(ContextMenuFragment.this, getCurrentMenuState());
int menuState = getCurrentMenuState();
listener.onContextMenuStateChanged(ContextMenuFragment.this, menuState, menuState);
}
if (runnable != null) {
runnable.run();
}
}
}

View file

@ -82,7 +82,7 @@ public abstract class ContextMenuScrollFragment extends ContextMenuFragment impl
}
@Override
public void onContextMenuStateChanged(@NonNull ContextMenuFragment fragment, int menuState) {
public void onContextMenuStateChanged(@NonNull ContextMenuFragment fragment, int menuState, int previousMenuState) {
updateMapControlsVisibility(menuState);
}

View file

@ -268,6 +268,18 @@ public abstract class MenuBottomSheetDialogFragment extends BottomSheetDialogFra
dismissButtonStringRes = stringRes;
}
protected int getDismissButtonHeight(){
return getResources().getDimensionPixelSize(R.dimen.bottom_sheet_cancel_button_height_small);
}
protected int getRightButtonHeight(){
return getResources().getDimensionPixelSize(R.dimen.bottom_sheet_cancel_button_height_small);
}
protected int getThirdButtonHeight(){
return getResources().getDimensionPixelSize(R.dimen.bottom_sheet_cancel_button_height_small);
}
protected DialogButtonType getDismissButtonType() {
return DialogButtonType.SECONDARY;
}
@ -360,6 +372,7 @@ public abstract class MenuBottomSheetDialogFragment extends BottomSheetDialogFra
private void setupDismissButton() {
dismissButton = buttonsContainer.findViewById(R.id.dismiss_button);
dismissButton.getLayoutParams().height = getDismissButtonHeight();
int buttonTextId = getDismissButtonTextId();
if (buttonTextId != DEFAULT_VALUE) {
UiUtilities.setupDialogButton(nightMode, dismissButton, getDismissButtonType(), buttonTextId);
@ -376,6 +389,7 @@ public abstract class MenuBottomSheetDialogFragment extends BottomSheetDialogFra
private void setupRightButton() {
rightButton = buttonsContainer.findViewById(R.id.right_bottom_button);
rightButton.getLayoutParams().height = getRightButtonHeight();
int buttonTextId = getRightBottomButtonTextId();
if (buttonTextId != DEFAULT_VALUE) {
UiUtilities.setupDialogButton(nightMode, rightButton, getRightBottomButtonType(), buttonTextId);
@ -398,6 +412,7 @@ public abstract class MenuBottomSheetDialogFragment extends BottomSheetDialogFra
protected void setupThirdButton() {
thirdButton = buttonsContainer.findViewById(R.id.third_button);
thirdButton.getLayoutParams().height = getThirdButtonHeight();
int buttonTextId = getThirdBottomButtonTextId();
if (buttonTextId != DEFAULT_VALUE) {
UiUtilities.setupDialogButton(nightMode, thirdButton, getThirdBottomButtonType(), buttonTextId);

View file

@ -99,7 +99,9 @@ public class RenameFileBottomSheet extends MenuBottomSheetDialogFragment {
}
private void updateFileName(String name) {
if (!Algorithms.isEmpty(name) && ILLEGAL_FILE_NAME_CHARACTERS.matcher(name).find()) {
if (Algorithms.isBlank(name)) {
nameTextBox.setError(getString(R.string.empty_filename));
} else if (ILLEGAL_FILE_NAME_CHARACTERS.matcher(name).find()) {
nameTextBox.setError(getString(R.string.file_name_containes_illegal_char));
} else {
selectedFileName = name;

View file

@ -0,0 +1,118 @@
package net.osmand.plus.dialogs;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnDismissListener;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import net.osmand.plus.R;
import net.osmand.plus.UiUtilities;
import net.osmand.plus.base.MenuBottomSheetDialogFragment;
import net.osmand.plus.base.bottomsheetmenu.BaseBottomSheetItem;
import net.osmand.plus.base.bottomsheetmenu.BottomSheetItemWithDescription;
import net.osmand.plus.base.bottomsheetmenu.simpleitems.DividerSpaceItem;
import net.osmand.plus.mapcontextmenu.UploadPhotosAsyncTask.UploadPhotosListener;
public class UploadPhotoProgressBottomSheet extends MenuBottomSheetDialogFragment implements UploadPhotosListener {
public static final String TAG = UploadPhotoProgressBottomSheet.class.getSimpleName();
private ProgressBar progressBar;
private TextView uploadedPhotosTitle;
private TextView uploadedPhotosCounter;
private OnDismissListener onDismissListener;
private int progress;
private int maxProgress;
@Override
public void createMenuItems(Bundle savedInstanceState) {
Context context = requireContext();
LayoutInflater inflater = UiUtilities.getInflater(context, nightMode);
View view = inflater.inflate(R.layout.bottom_sheet_with_progress_bar, null);
uploadedPhotosTitle = view.findViewById(R.id.title);
uploadedPhotosCounter = view.findViewById(R.id.description);
progressBar = view.findViewById(R.id.progress_bar);
progressBar.setMax(maxProgress);
String titleProgress = getString(progress == maxProgress? R.string.upload_photo_completed: R.string.upload_photo);
String descriptionProgress;
if (progress == maxProgress) {
descriptionProgress = getString(R.string.uploaded_count, progress, maxProgress);
} else {
descriptionProgress = getString(R.string.uploading_count, progress, maxProgress);
}
BaseBottomSheetItem descriptionItem = new BottomSheetItemWithDescription.Builder()
.setDescription(descriptionProgress)
.setTitle(titleProgress)
.setCustomView(view)
.create();
items.add(descriptionItem);
updateProgress(progress);
int padding = getResources().getDimensionPixelSize(R.dimen.content_padding_small);
items.add(new DividerSpaceItem(context, padding));
}
public void setMaxProgress(int maxProgress) {
this.maxProgress = maxProgress;
}
public void setOnDismissListener(OnDismissListener onDismissListener) {
this.onDismissListener = onDismissListener;
}
private void updateProgress(int progress) {
progressBar.setProgress(progress);
uploadedPhotosCounter.setText((getString(R.string.uploading_count, progress, maxProgress)));
uploadedPhotosTitle.setText(progress == maxProgress ? R.string.upload_photo_completed : R.string.upload_photo);
}
@Override
public void uploadPhotosProgressUpdate(int progress) {
this.progress = progress;
updateProgress(progress);
}
@Override
public void uploadPhotosFinished() {
updateProgress(maxProgress);
if (progress == maxProgress) {
uploadedPhotosCounter.setText((getString(R.string.uploaded_count, progress, maxProgress)));
setDismissButtonTextId(R.string.shared_string_close);
UiUtilities.setupDialogButton(nightMode, dismissButton, getDismissButtonType(), getDismissButtonTextId());
}
}
@Override
public void onDismiss(@NonNull DialogInterface dialog) {
super.onDismiss(dialog);
FragmentActivity activity = getActivity();
if (onDismissListener != null && activity != null && !activity.isChangingConfigurations()) {
onDismissListener.onDismiss(dialog);
}
}
public static UploadPhotosListener showInstance(@NonNull FragmentManager fragmentManager, int maxProgress, OnDismissListener listener) {
UploadPhotoProgressBottomSheet fragment = new UploadPhotoProgressBottomSheet();
fragment.setRetainInstance(true);
fragment.setMaxProgress(maxProgress);
fragment.setOnDismissListener(listener);
fragmentManager.beginTransaction()
.add(fragment, UploadPhotoProgressBottomSheet.TAG)
.commitAllowingStateLoss();
return fragment;
}
}

View file

@ -22,18 +22,17 @@ import net.osmand.data.FavouritePoint;
import net.osmand.data.LatLon;
import net.osmand.data.PointDescription;
import net.osmand.data.TransportStop;
import net.osmand.plus.settings.backend.ApplicationMode;
import net.osmand.plus.ContextMenuAdapter;
import net.osmand.plus.GpxSelectionHelper.SelectedGpxFile;
import net.osmand.plus.mapmarkers.MapMarker;
import net.osmand.plus.mapmarkers.MapMarkersHelper.MapMarkerChangedListener;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.OsmandPlugin;
import net.osmand.plus.R;
import net.osmand.plus.TargetPointsHelper.TargetPoint;
import net.osmand.plus.TargetPointsHelper.TargetPointChangedListener;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.helpers.AndroidUiHelper;
import net.osmand.plus.helpers.GpxUiHelper;
import net.osmand.plus.mapcontextmenu.AdditionalActionsBottomSheetDialogFragment.ContextMenuItemClickListener;
import net.osmand.plus.mapcontextmenu.MenuController.ContextMenuToolbarController;
import net.osmand.plus.mapcontextmenu.MenuController.MenuState;
import net.osmand.plus.mapcontextmenu.MenuController.MenuType;
@ -47,12 +46,14 @@ import net.osmand.plus.mapcontextmenu.editors.RtePtEditor;
import net.osmand.plus.mapcontextmenu.editors.WptPtEditor;
import net.osmand.plus.mapcontextmenu.other.MapMultiSelectionMenu;
import net.osmand.plus.mapcontextmenu.other.ShareMenu;
import net.osmand.plus.mapcontextmenu.AdditionalActionsBottomSheetDialogFragment.ContextMenuItemClickListener;
import net.osmand.plus.mapmarkers.MapMarker;
import net.osmand.plus.mapmarkers.MapMarkersHelper.MapMarkerChangedListener;
import net.osmand.plus.monitoring.OsmandMonitoringPlugin;
import net.osmand.plus.routing.RoutingHelper;
import net.osmand.plus.settings.backend.ApplicationMode;
import net.osmand.plus.transport.TransportStopRoute;
import net.osmand.plus.views.layers.ContextMenuLayer;
import net.osmand.plus.views.OsmandMapLayer;
import net.osmand.plus.views.layers.ContextMenuLayer;
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopToolbarController;
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopToolbarControllerType;
import net.osmand.util.Algorithms;
@ -570,18 +571,21 @@ public class MapContextMenu extends MenuTitleController implements StateChangedL
public void updateControlsVisibility(boolean menuVisible) {
MapActivity mapActivity = getMapActivity();
if (mapActivity != null) {
int topControlsVisibility = shouldShowTopControls(menuVisible) ? View.VISIBLE : View.GONE;
mapActivity.findViewById(R.id.map_center_info).setVisibility(topControlsVisibility);
mapActivity.findViewById(R.id.map_left_widgets_panel).setVisibility(topControlsVisibility);
mapActivity.findViewById(R.id.map_right_widgets_panel).setVisibility(topControlsVisibility);
int bottomControlsVisibility = shouldShowBottomControls(menuVisible) ? View.VISIBLE : View.GONE;
mapActivity.findViewById(R.id.bottom_controls_container).setVisibility(bottomControlsVisibility);
mapActivity.refreshMap();
boolean topControlsVisible = shouldShowTopControls(menuVisible);
boolean bottomControlsVisible = shouldShowBottomControls(menuVisible);
updateControlsVisibility(mapActivity, topControlsVisible, bottomControlsVisible);
}
}
public static void updateControlsVisibility(@NonNull MapActivity mapActivity, boolean topControlsVisible, boolean bottomControlsVisible) {
AndroidUiHelper.updateVisibility(mapActivity.findViewById(R.id.map_center_info), topControlsVisible);
AndroidUiHelper.updateVisibility(mapActivity.findViewById(R.id.map_left_widgets_panel), topControlsVisible);
AndroidUiHelper.updateVisibility(mapActivity.findViewById(R.id.map_right_widgets_panel), topControlsVisible);
AndroidUiHelper.updateVisibility(mapActivity.findViewById(R.id.bottom_controls_container), bottomControlsVisible);
mapActivity.refreshMap();
}
public boolean shouldShowTopControls() {
return shouldShowTopControls(isVisible());
}

View file

@ -1,22 +1,19 @@
package net.osmand.plus.mapcontextmenu;
import android.app.Activity;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import android.os.Build;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
@ -39,14 +36,12 @@ import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;
import net.osmand.AndroidUtils;
import net.osmand.PlatformUtil;
import net.osmand.data.Amenity;
import net.osmand.data.LatLon;
import net.osmand.data.PointDescription;
import net.osmand.data.QuadRect;
import net.osmand.osm.PoiCategory;
import net.osmand.osm.PoiType;
import net.osmand.osm.io.NetworkUtils;
import net.osmand.plus.OsmAndFormatter;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.OsmandPlugin;
@ -54,6 +49,7 @@ import net.osmand.plus.R;
import net.osmand.plus.UiUtilities;
import net.osmand.plus.Version;
import net.osmand.plus.activities.ActivityResultListener;
import net.osmand.plus.activities.ActivityResultListener.OnActivityResultListener;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.helpers.FontCache;
import net.osmand.plus.mapcontextmenu.builders.cards.AbstractCard;
@ -79,13 +75,6 @@ import net.osmand.plus.widgets.tools.ClickableSpanTouchListener;
import net.osmand.util.Algorithms;
import net.osmand.util.MapUtils;
import org.apache.commons.logging.Log;
import org.openplacereviews.opendb.util.exception.FailedVerificationException;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@ -99,8 +88,6 @@ import static net.osmand.plus.mapcontextmenu.builders.cards.ImageCard.GetImageCa
public class MenuBuilder {
private static final int PICK_IMAGE = 1231;
private static final int MAX_IMAGE_LENGTH = 2048;
private static final Log LOG = PlatformUtil.getLog(MenuBuilder.class);
public static final float SHADOW_HEIGHT_TOP_DP = 17f;
public static final int TITLE_LIMIT = 60;
protected static final String[] arrowChars = new String[] {"=>", " - "};
@ -133,7 +120,6 @@ public class MenuBuilder {
private String preferredMapLang;
private String preferredMapAppLang;
private boolean transliterateNames;
private View view;
private View photoButton;
private final OpenDBAPI openDBAPI = new OpenDBAPI();
@ -270,7 +256,6 @@ public class MenuBuilder {
}
public void build(View view) {
this.view = view;
firstRow = true;
hidden = false;
buildTopInternal(view);
@ -425,7 +410,7 @@ public class MenuBuilder {
if (false) {
AddPhotosBottomSheetDialogFragment.showInstance(mapActivity.getSupportFragmentManager());
} else {
registerResultListener(view);
registerResultListener();
final String baseUrl = OPRConstants.getBaseUrl(app);
final String name = app.getSettings().OPR_USERNAME.get();
final String privateKey = app.getSettings().OPR_ACCESS_TOKEN.get();
@ -443,6 +428,9 @@ public class MenuBuilder {
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
if (Build.VERSION.SDK_INT > 18) {
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
}
mapActivity.startActivityForResult(Intent.createChooser(intent,
mapActivity.getString(R.string.select_picture)), PICK_IMAGE);
}
@ -472,132 +460,33 @@ public class MenuBuilder {
false, null, false);
}
private void registerResultListener(final View view) {
mapActivity.registerActivityResultListener(new ActivityResultListener(PICK_IMAGE, new ActivityResultListener.
OnActivityResultListener() {
private void registerResultListener() {
mapActivity.registerActivityResultListener(new ActivityResultListener(PICK_IMAGE, new OnActivityResultListener() {
@Override
public void onResult(int resultCode, Intent resultData) {
if (resultData != null) {
handleSelectedImage(view, resultData.getData());
List<Uri> imagesUri = new ArrayList<>();
Uri data = resultData.getData();
if (data != null) {
imagesUri.add(data);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
ClipData clipData = resultData.getClipData();
if (clipData != null) {
for (int i = 0; i < clipData.getItemCount(); i++) {
Uri uri = resultData.getClipData().getItemAt(i).getUri();
if (uri != null) {
imagesUri.add(uri);
}
}
}
}
execute(new UploadPhotosAsyncTask(mapActivity, imagesUri, getLatLon(), placeId, getAdditionalCardParams(), imageCardListener));
}
}
}));
}
private void handleSelectedImage(final View view, final Uri uri) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
InputStream inputStream = null;
try {
inputStream = app.getContentResolver().openInputStream(uri);
if (inputStream != null) {
uploadImageToPlace(inputStream);
}
} catch (Exception e) {
LOG.error(e);
String str = app.getString(R.string.cannot_upload_image);
showToastMessage(str);
} finally {
Algorithms.closeStream(inputStream);
}
}
});
t.start();
}
private void uploadImageToPlace(InputStream image) {
InputStream serverData = new ByteArrayInputStream(compressImageToJpeg(image));
final String baseUrl = OPRConstants.getBaseUrl(app);
// all these should be constant
String url = baseUrl + "api/ipfs/image";
String response = NetworkUtils.sendPostDataRequest(url, "file", "compressed.jpeg", serverData);
if (response != null) {
int res = 0;
try {
StringBuilder error = new StringBuilder();
String privateKey = app.getSettings().OPR_ACCESS_TOKEN.get();
String username = app.getSettings().OPR_USERNAME.get();
res = openDBAPI.uploadImage(
placeId,
baseUrl,
privateKey,
username,
response, error);
if (res != 200) {
showToastMessage(error.toString());
} else {
//ok, continue
}
} catch (FailedVerificationException e) {
LOG.error(e);
checkTokenAndShowScreen();
}
if (res != 200) {
//image was uploaded but not added to blockchain
checkTokenAndShowScreen();
} else {
String str = app.getString(R.string.successfully_uploaded_pattern, 1, 1);
showToastMessage(str);
//refresh the image
execute(new GetImageCardsTask(mapActivity, getLatLon(), getAdditionalCardParams(), imageCardListener));
}
} else {
checkTokenAndShowScreen();
}
}
private void showToastMessage(final String str) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
Toast.makeText(mapActivity.getBaseContext(), str, Toast.LENGTH_LONG).show();
}
});
}
//This method runs on non main thread
private void checkTokenAndShowScreen() {
final String baseUrl = OPRConstants.getBaseUrl(app);
final String name = app.getSettings().OPR_USERNAME.get();
final String privateKey = app.getSettings().OPR_ACCESS_TOKEN.get();
if (openDBAPI.checkPrivateKeyValid(baseUrl, name, privateKey)) {
String str = app.getString(R.string.cannot_upload_image);
showToastMessage(str);
} else {
app.runInUIThread(new Runnable() {
@Override
public void run() {
OprStartFragment.showInstance(mapActivity.getSupportFragmentManager());
}
});
}
}
private byte[] compressImageToJpeg(InputStream image) {
BufferedInputStream bufferedInputStream = new BufferedInputStream(image);
Bitmap bmp = BitmapFactory.decodeStream(bufferedInputStream);
ByteArrayOutputStream os = new ByteArrayOutputStream();
int h = bmp.getHeight();
int w = bmp.getWidth();
boolean scale = false;
while (w > MAX_IMAGE_LENGTH || h > MAX_IMAGE_LENGTH) {
w = w / 2;
h = h / 2;
scale = true;
}
if (scale) {
Matrix matrix = new Matrix();
matrix.postScale(w, h);
Bitmap resizedBitmap = Bitmap.createBitmap(
bmp, 0, 0, w, h, matrix, false);
bmp.recycle();
bmp = resizedBitmap;
}
bmp.compress(Bitmap.CompressFormat.JPEG, 90, os);
return os.toByteArray();
}
private void startLoadingImages() {
if (onlinePhotoCardsRow == null) {
return;

View file

@ -0,0 +1,220 @@
package net.osmand.plus.mapcontextmenu;
import android.content.DialogInterface;
import android.content.DialogInterface.OnDismissListener;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.net.Uri;
import android.os.AsyncTask;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import net.osmand.AndroidUtils;
import net.osmand.PlatformUtil;
import net.osmand.data.LatLon;
import net.osmand.osm.io.NetworkUtils;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.dialogs.UploadPhotoProgressBottomSheet;
import net.osmand.plus.mapcontextmenu.builders.cards.ImageCard.GetImageCardsTask;
import net.osmand.plus.mapcontextmenu.builders.cards.ImageCard.GetImageCardsTask.GetImageCardsListener;
import net.osmand.plus.openplacereviews.OPRConstants;
import net.osmand.plus.openplacereviews.OprStartFragment;
import net.osmand.plus.osmedit.opr.OpenDBAPI;
import net.osmand.util.Algorithms;
import org.apache.commons.logging.Log;
import org.openplacereviews.opendb.util.exception.FailedVerificationException;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.Map;
public class UploadPhotosAsyncTask extends AsyncTask<Void, Integer, Void> {
private static final Log LOG = PlatformUtil.getLog(UploadPhotosAsyncTask.class);
private static final int MAX_IMAGE_LENGTH = 2048;
private final OsmandApplication app;
private final WeakReference<MapActivity> activityRef;
private UploadPhotosListener listener;
private final OpenDBAPI openDBAPI = new OpenDBAPI();
private final LatLon latLon;
private final List<Uri> data;
private final String[] placeId;
private final Map<String, String> params;
private final GetImageCardsListener imageCardListener;
public UploadPhotosAsyncTask(MapActivity activity, List<Uri> data, LatLon latLon, String[] placeId,
Map<String, String> params, GetImageCardsListener imageCardListener) {
app = (OsmandApplication) activity.getApplicationContext();
activityRef = new WeakReference<>(activity);
this.data = data;
this.latLon = latLon;
this.params = params;
this.placeId = placeId;
this.imageCardListener = imageCardListener;
}
@Override
protected void onPreExecute() {
FragmentActivity activity = activityRef.get();
if (AndroidUtils.isActivityNotDestroyed(activity)) {
FragmentManager manager = activity.getSupportFragmentManager();
listener = UploadPhotoProgressBottomSheet.showInstance(manager, data.size(), new OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
cancel(false);
}
});
}
}
@Override
protected void onProgressUpdate(Integer... values) {
if (listener != null) {
listener.uploadPhotosProgressUpdate(values[0]);
}
}
protected Void doInBackground(Void... uris) {
for (int i = 0; i < data.size(); i++) {
if (isCancelled()) {
break;
}
Uri uri = data.get(i);
handleSelectedImage(uri);
publishProgress(i + 1);
}
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
if (listener != null) {
listener.uploadPhotosFinished();
}
}
private void handleSelectedImage(final Uri uri) {
InputStream inputStream = null;
try {
inputStream = app.getContentResolver().openInputStream(uri);
if (inputStream != null) {
uploadImageToPlace(inputStream);
}
} catch (Exception e) {
LOG.error(e);
app.showToastMessage(R.string.cannot_upload_image);
} finally {
Algorithms.closeStream(inputStream);
}
}
private void uploadImageToPlace(InputStream image) {
InputStream serverData = new ByteArrayInputStream(compressImageToJpeg(image));
final String baseUrl = OPRConstants.getBaseUrl(app);
// all these should be constant
String url = baseUrl + "api/ipfs/image";
String response = NetworkUtils.sendPostDataRequest(url, "file", "compressed.jpeg", serverData);
if (response != null) {
int res = 0;
try {
StringBuilder error = new StringBuilder();
String privateKey = app.getSettings().OPR_ACCESS_TOKEN.get();
String username = app.getSettings().OPR_USERNAME.get();
res = openDBAPI.uploadImage(
placeId,
baseUrl,
privateKey,
username,
response, error);
if (res != 200) {
app.showToastMessage(error.toString());
} else {
//ok, continue
}
} catch (FailedVerificationException e) {
LOG.error(e);
checkTokenAndShowScreen();
}
if (res != 200) {
//image was uploaded but not added to blockchain
checkTokenAndShowScreen();
} else {
String str = app.getString(R.string.successfully_uploaded_pattern, 1, 1);
app.showToastMessage(str);
//refresh the image
MapActivity activity = activityRef.get();
if (activity != null) {
MenuBuilder.execute(new GetImageCardsTask(activity, latLon, params, imageCardListener));
}
}
} else {
checkTokenAndShowScreen();
}
}
//This method runs on non main thread
private void checkTokenAndShowScreen() {
String baseUrl = OPRConstants.getBaseUrl(app);
String name = app.getSettings().OPR_USERNAME.get();
String privateKey = app.getSettings().OPR_ACCESS_TOKEN.get();
if (openDBAPI.checkPrivateKeyValid(baseUrl, name, privateKey)) {
app.showToastMessage(R.string.cannot_upload_image);
} else {
app.runInUIThread(new Runnable() {
@Override
public void run() {
MapActivity activity = activityRef.get();
if (activity != null) {
OprStartFragment.showInstance(activity.getSupportFragmentManager());
}
}
});
}
}
private byte[] compressImageToJpeg(InputStream image) {
BufferedInputStream bufferedInputStream = new BufferedInputStream(image);
Bitmap bmp = BitmapFactory.decodeStream(bufferedInputStream);
ByteArrayOutputStream os = new ByteArrayOutputStream();
int h = bmp.getHeight();
int w = bmp.getWidth();
boolean scale = false;
while (w > MAX_IMAGE_LENGTH || h > MAX_IMAGE_LENGTH) {
w = w / 2;
h = h / 2;
scale = true;
}
if (scale) {
Matrix matrix = new Matrix();
matrix.postScale(w, h);
Bitmap resizedBitmap = Bitmap.createBitmap(
bmp, 0, 0, w, h, matrix, false);
bmp.recycle();
bmp = resizedBitmap;
}
bmp.compress(Bitmap.CompressFormat.JPEG, 90, os);
return os.toByteArray();
}
public interface UploadPhotosListener {
void uploadPhotosProgressUpdate(int progress);
void uploadPhotosFinished();
}
}

View file

@ -42,9 +42,8 @@ public class SelectedGpxMenuController extends MenuController {
leftTitleButtonController = new TitleButtonController() {
@Override
public void buttonPressed() {
SelectedGpxFile selectedGpxFile = selectedGpxPoint.getSelectedGpxFile();
mapActivity.getContextMenu().hide(false);
TrackMenuFragment.showInstance(mapActivity, selectedGpxFile.getGpxFile().path, selectedGpxFile.isShowCurrentTrack());
mapContextMenu.hide(false);
TrackMenuFragment.showInstance(mapActivity, selectedGpxPoint.getSelectedGpxFile());
}
};
leftTitleButtonController.caption = mapActivity.getString(R.string.shared_string_open_track);

View file

@ -9,6 +9,7 @@ import android.os.Bundle;
import android.text.SpannableString;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ImageView;
@ -18,10 +19,12 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.SwitchCompat;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentManager;
import com.google.android.material.slider.RangeSlider;
import net.osmand.AndroidUtils;
import net.osmand.plus.GpxSelectionHelper.SelectedGpxFile;
import net.osmand.plus.NavigationService;
import net.osmand.plus.OsmandApplication;
@ -49,9 +52,9 @@ public class TripRecordingBottomSheet extends MenuBottomSheetDialogFragment {
private ImageView upDownBtn;
private SwitchCompat confirmEveryRun;
private RangeSlider intervalSlider;
private TextView intervalValueView;
private LinearLayout container;
private View divider;
private boolean infoExpanded;
@Override
@ -87,13 +90,15 @@ public class TripRecordingBottomSheet extends MenuBottomSheetDialogFragment {
if (mapActivity != null) {
hide();
SelectedGpxFile selectedGpxFile = app.getSavingTrackHelper().getCurrentTrack();
TrackAppearanceFragment.showInstance(mapActivity, selectedGpxFile);
TrackAppearanceFragment.showInstance(mapActivity, selectedGpxFile, TripRecordingBottomSheet.this);
}
}
});
divider = itemView.findViewById(R.id.second_divider);
LinearLayout expandHideIntervalContainer = itemView.findViewById(R.id.interval_view_container);
upDownBtn = itemView.findViewById(R.id.up_down_button);
upDownBtn.setOnClickListener(new View.OnClickListener() {
expandHideIntervalContainer.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@ -108,8 +113,11 @@ public class TripRecordingBottomSheet extends MenuBottomSheetDialogFragment {
updateIntervalLegend();
container = itemView.findViewById(R.id.always_ask_and_range_slider_container);
intervalSlider = itemView.findViewById(R.id.interval_slider);
RangeSlider intervalSlider = itemView.findViewById(R.id.interval_slider);
intervalSlider.setValueTo(secondsLength + minutesLength - 1);
int currentModeColorRes = app.getSettings().getApplicationMode().getIconColorInfo().getColor(nightMode);
int currentModeColor = ContextCompat.getColor(app, currentModeColorRes);
UiUtilities.setupSlider(intervalSlider, nightMode, currentModeColor, true);
container.setVisibility(View.GONE);
intervalSlider.addOnChangeListener(new RangeSlider.OnChangeListener() {
@ -126,6 +134,7 @@ public class TripRecordingBottomSheet extends MenuBottomSheetDialogFragment {
updateIntervalLegend();
}
});
for (int i = 0; i < secondsLength + minutesLength; i++) {
if (i < secondsLength) {
if (settings.SAVE_GLOBAL_TRACK_INTERVAL.get() <= SECONDS[i] * 1000) {
@ -151,13 +160,15 @@ public class TripRecordingBottomSheet extends MenuBottomSheetDialogFragment {
}
});
SwitchCompat showTrackOnMapButton = showTrackOnMapView.findViewById(R.id.switch_button);
final SwitchCompat showTrackOnMapButton = showTrackOnMapView.findViewById(R.id.switch_button);
showTrackOnMapButton.setChecked(app.getSelectedGpxHelper().getSelectedCurrentRecordingTrack() != null);
showTrackOnMapButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
View basicItem = itemView.findViewById(R.id.basic_item_body);
basicItem.setOnClickListener(new View.OnClickListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
app.getSelectedGpxHelper().selectGpxFile(app.getSavingTrackHelper().getCurrentGpx(), isChecked, false);
public void onClick(View v) {
boolean checked = !showTrackOnMapButton.isChecked();
showTrackOnMapButton.setChecked(checked);
app.getSelectedGpxHelper().selectGpxFile(app.getSavingTrackHelper().getCurrentGpx(), checked, false);
}
});
UiUtilities.setupCompoundButton(showTrackOnMapButton, nightMode, PROFILE_DEPENDENT);
@ -217,6 +228,14 @@ public class TripRecordingBottomSheet extends MenuBottomSheetDialogFragment {
private void toggleInfoView() {
infoExpanded = !infoExpanded;
ViewGroup.MarginLayoutParams marginParams = (ViewGroup.MarginLayoutParams) divider.getLayoutParams();
final int dp8 = AndroidUtils.dpToPx(app, 8f);
final int dp16 = AndroidUtils.dpToPx(app, 16f);
if (infoExpanded) {
AndroidUtils.setMargins(marginParams, 0, dp16, 0, dp8);
} else {
AndroidUtils.setMargins(marginParams, 0, 0, 0, dp8);
}
AndroidUiHelper.updateVisibility(container, infoExpanded);
updateUpDownBtn();
}
@ -226,6 +245,16 @@ public class TripRecordingBottomSheet extends MenuBottomSheetDialogFragment {
return true;
}
@Override
protected int getRightButtonHeight(){
return getResources().getDimensionPixelSize(R.dimen.bottom_sheet_cancel_button_height);
}
@Override
protected int getDismissButtonHeight(){
return getResources().getDimensionPixelSize(R.dimen.bottom_sheet_cancel_button_height);
}
@Override
protected int getRightBottomButtonTextId() {
return R.string.start_recording;

View file

@ -30,6 +30,7 @@ import net.osmand.GPXUtilities.GPXFile;
import net.osmand.GPXUtilities.WptPt;
import net.osmand.data.FavouritePoint;
import net.osmand.plus.FavouritesDbHelper;
import net.osmand.plus.GpxSelectionHelper;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayGroup;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayItemType;
@ -70,19 +71,32 @@ public class EditTrackGroupDialogFragment extends MenuBottomSheetDialogFragment
public static final String TAG = EditTrackGroupDialogFragment.class.getSimpleName();
private OsmandApplication app;
private GpxSelectionHelper selectedGpxHelper;
private MapMarkersHelper mapMarkersHelper;
private GpxDisplayGroup group;
@Override
public void createMenuItems(Bundle savedInstanceState) {
app = requiredMyApplication();
if (group == null) {
return;
}
app = requiredMyApplication();
selectedGpxHelper = app.getSelectedGpxHelper();
mapMarkersHelper = app.getMapMarkersHelper();
items.add(new TitleItem(getCategoryName(app, group.getName())));
GPXFile gpxFile = group.getGpx();
boolean currentTrack = group.getGpx().showCurrentTrack;
SelectedGpxFile selectedGpxFile;
if (currentTrack) {
selectedGpxFile = selectedGpxHelper.getSelectedCurrentRecordingTrack();
} else {
selectedGpxFile = selectedGpxHelper.getSelectedFileByPath(gpxFile.path);
}
boolean trackPoints = group.getType() == GpxDisplayItemType.TRACK_POINTS;
SelectedGpxFile selectedGpxFile = app.getSelectedGpxHelper().getSelectedFileByPath(group.getGpx().path);
if (trackPoints && selectedGpxFile != null) {
items.add(createShowOnMapItem(selectedGpxFile));
}
@ -92,7 +106,9 @@ public class EditTrackGroupDialogFragment extends MenuBottomSheetDialogFragment
}
items.add(new OptionsDividerItem(app));
// items.add(createCopyToMarkersItem());
if (!currentTrack) {
items.add(createCopyToMarkersItem(gpxFile));
}
items.add(createCopyToFavoritesItem());
items.add(new OptionsDividerItem(app));
@ -175,27 +191,51 @@ public class EditTrackGroupDialogFragment extends MenuBottomSheetDialogFragment
.create();
}
private BaseBottomSheetItem createCopyToMarkersItem() {
private BaseBottomSheetItem createCopyToMarkersItem(final GPXFile gpxFile) {
final MapMarkersGroup markersGroup = getOrCreateMarkersGroup(gpxFile);
final Set<String> categories = markersGroup.getWptCategories();
final boolean synced = categories != null && categories.contains(group.getName());
return new SimpleBottomSheetItem.Builder()
.setIcon(getContentIcon(R.drawable.ic_action_copy))
.setTitle(getString(R.string.copy_to_map_markers))
.setLayoutId(R.layout.bottom_sheet_item_simple)
.setIcon(getContentIcon(synced ? R.drawable.ic_action_delete_dark : R.drawable.ic_action_copy))
.setTitle(getString(synced ? R.string.remove_from_map_markers : R.string.copy_to_map_markers))
.setLayoutId(R.layout.bottom_sheet_item_simple_pad_32dp)
.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// MapMarkersHelper markersHelper = app.getMapMarkersHelper();
// MapMarkersGroup markersGroup = markersHelper.getMarkersGroup(group);
// if (markersGroup != null) {
// markersHelper.removeMarkersGroup(markersGroup);
// } else {
// markersHelper.addOrEnableGroup(group);
// }
updateGroupWptCategory(gpxFile, markersGroup, categories, synced);
dismiss();
}
})
.create();
}
private void updateGroupWptCategory(GPXFile gpxFile, MapMarkersGroup markersGroup, Set<String> categories, boolean synced) {
SelectedGpxFile selectedGpxFile = selectedGpxHelper.getSelectedFileByPath(gpxFile.path);
if (selectedGpxFile == null) {
selectedGpxHelper.selectGpxFile(gpxFile, true, false, false, false, false);
}
Set<String> selectedCategories = new HashSet<>();
if (categories != null) {
selectedCategories.addAll(categories);
}
if (synced) {
selectedCategories.remove(group.getName());
} else {
selectedCategories.add(group.getName());
}
mapMarkersHelper.updateGroupWptCategories(markersGroup, selectedCategories);
mapMarkersHelper.runSynchronization(markersGroup);
}
private MapMarkersGroup getOrCreateMarkersGroup(GPXFile gpxFile) {
MapMarkersGroup markersGroup = mapMarkersHelper.getMarkersGroup(gpxFile);
if (markersGroup == null) {
markersGroup = mapMarkersHelper.addOrEnableGroup(gpxFile);
}
return markersGroup;
}
private BaseBottomSheetItem createCopyToFavoritesItem() {
return new SimpleBottomSheetItem.Builder()
.setIcon(getContentIcon(R.drawable.ic_action_copy))

View file

@ -2,6 +2,7 @@ package net.osmand.plus.onlinerouting.ui;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.Gravity;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
@ -79,6 +80,7 @@ public class OnlineRoutingCard extends BaseCard {
int activeColor = ContextCompat.getColor(app, appMode.getIconColorInfo().getColor(nightMode));
textFieldBoxes.setPrimaryColor(activeColor);
textFieldBoxes.setGravityFloatingLabel(Gravity.START);
editText.addTextChangedListener(new TextWatcher() {
@Override

View file

@ -295,8 +295,12 @@ public class OsmEditsFragment extends OsmAndListFragment implements ProgressDial
item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
uploadItems(osmEditsSelected.toArray(new OsmPoint[0]));
mode.finish();
if (Algorithms.isEmpty(osmEditsSelected)) {
app.showToastMessage(R.string.toast_select_edits_for_upload);
} else {
uploadItems(osmEditsSelected.toArray(new OsmPoint[0]));
mode.finish();
}
return true;
}
});

View file

@ -815,7 +815,7 @@ public class ChooseRouteFragment extends BaseOsmAndFragment implements ContextMe
}
@Override
public void onContextMenuStateChanged(@NonNull ContextMenuFragment fragment, int menuState) {
public void onContextMenuStateChanged(@NonNull ContextMenuFragment fragment, int menuState, int previousMenuState) {
LockableViewPager viewPager = this.viewPager;
RouteDetailsFragment current = getCurrentFragment();
if (viewPager != null && fragment == current) {

View file

@ -1,14 +1,14 @@
package net.osmand.plus.track;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatImageView;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import com.squareup.picasso.Callback;
import com.squareup.picasso.Picasso;
import com.squareup.picasso.RequestCreator;
import net.osmand.GPXUtilities;
import net.osmand.GPXUtilities.GPXFile;
import net.osmand.PicassoUtils;
import net.osmand.plus.R;
@ -19,6 +19,9 @@ import net.osmand.plus.widgets.TextViewEx;
import net.osmand.plus.wikipedia.WikiArticleHelper;
import net.osmand.util.Algorithms;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatImageView;
import static net.osmand.plus.myplaces.TrackActivityFragmentAdapter.getMetadataImageLink;
public class DescriptionCard extends BaseCard {
@ -37,11 +40,8 @@ public class DescriptionCard extends BaseCard {
@Override
protected void updateContent() {
if (gpxFile.metadata == null || gpxFile.metadata.getDescription() == null) {
AndroidUiHelper.updateVisibility(view, false);
return;
} else {
AndroidUiHelper.updateVisibility(view, true);
if (gpxFile.metadata == null) {
gpxFile.metadata = new GPXUtilities.Metadata();
}
final String title = gpxFile.metadata.getArticleTitle();
@ -50,6 +50,34 @@ public class DescriptionCard extends BaseCard {
setupImage(imageUrl);
if (Algorithms.isBlank(descriptionHtml)) {
showAddBtn();
} else {
showDescription(title, imageUrl, descriptionHtml);
}
}
private void showAddBtn() {
LinearLayout descriptionContainer = view.findViewById(R.id.description_container);
FrameLayout addBtn = view.findViewById(R.id.btn_add);
addBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
GpxEditDescriptionDialogFragment.showInstance(getMapActivity(), "", null);
}
});
AndroidUiHelper.updateVisibility(descriptionContainer, false);
AndroidUiHelper.updateVisibility(addBtn, true);
}
private void showDescription(final String title, final String imageUrl, final String descriptionHtml) {
LinearLayout descriptionContainer = view.findViewById(R.id.description_container);
FrameLayout addBtn = view.findViewById(R.id.btn_add);
AndroidUiHelper.updateVisibility(descriptionContainer, true);
AndroidUiHelper.updateVisibility(addBtn, false);
TextViewEx tvDescription = view.findViewById(R.id.description);
tvDescription.setText(getFirstParagraph(descriptionHtml));
@ -60,6 +88,7 @@ public class DescriptionCard extends BaseCard {
GpxReadDescriptionDialogFragment.showInstance(mapActivity, title, imageUrl, descriptionHtml);
}
});
TextViewEx editBtn = view.findViewById(R.id.btn_edit);
editBtn.setOnClickListener(new View.OnClickListener() {
@Override

View file

@ -1,5 +1,7 @@
package net.osmand.plus.track;
import android.content.Context;
import android.content.DialogInterface;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.Editable;
@ -7,12 +9,6 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import net.osmand.GPXUtilities.GPXFile;
import net.osmand.PlatformUtil;
import net.osmand.plus.OsmandApplication;
@ -22,12 +18,18 @@ import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.base.BaseOsmAndDialogFragment;
import net.osmand.plus.track.SaveGpxAsyncTask.SaveGpxListener;
import net.osmand.plus.widgets.EditTextEx;
import net.osmand.util.Algorithms;
import org.apache.commons.logging.Log;
import java.io.File;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
public class GpxEditDescriptionDialogFragment extends BaseOsmAndDialogFragment {
public static final String TAG = GpxEditDescriptionDialogFragment.class.getSimpleName();
@ -37,6 +39,8 @@ public class GpxEditDescriptionDialogFragment extends BaseOsmAndDialogFragment {
private EditTextEx editableHtml;
private String htmlCode;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
@ -46,11 +50,16 @@ public class GpxEditDescriptionDialogFragment extends BaseOsmAndDialogFragment {
View view = themedInflater.inflate(R.layout.dialog_edit_gpx_description, container, false);
editableHtml = view.findViewById(R.id.description);
editableHtml.requestFocus();
view.findViewById(R.id.btn_close).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
if (shouldClose()) {
dismiss();
} else {
showDismissDialog();
}
}
});
@ -58,7 +67,7 @@ public class GpxEditDescriptionDialogFragment extends BaseOsmAndDialogFragment {
@Override
public void onClick(View v) {
Editable editable = editableHtml.getText();
if (!Algorithms.isEmpty(editable) && !saveGpx(editable.toString())) {
if (editable != null && !saveGpx(editable.toString())) {
dismiss();
}
}
@ -66,15 +75,38 @@ public class GpxEditDescriptionDialogFragment extends BaseOsmAndDialogFragment {
Bundle args = getArguments();
if (args != null) {
String html = args.getString(CONTENT_KEY);
if (html != null) {
editableHtml.setText(html);
htmlCode = args.getString(CONTENT_KEY);
if (htmlCode != null) {
editableHtml.setText(htmlCode);
}
}
return view;
}
private boolean shouldClose() {
Editable editable = editableHtml.getText();
if (htmlCode == null || editable == null || editable.toString() == null) {
return true;
}
return htmlCode.equals(editable.toString());
}
private void showDismissDialog() {
Context themedContext = UiUtilities.getThemedContext(getMapActivity(), isNightMode(false));
AlertDialog.Builder dismissDialog = new AlertDialog.Builder(themedContext);
dismissDialog.setTitle(getString(R.string.shared_string_dismiss));
dismissDialog.setMessage(getString(R.string.exit_without_saving));
dismissDialog.setNegativeButton(R.string.shared_string_cancel, null);
dismissDialog.setPositiveButton(R.string.shared_string_exit, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dismiss();
}
});
dismissDialog.show();
}
private boolean saveGpx(final String html) {
MapActivity mapActivity = getMapActivity();
if (mapActivity == null || mapActivity.getTrackMenuFragment() == null) {

View file

@ -3,6 +3,7 @@ package net.osmand.plus.track;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.util.Base64;
import android.view.LayoutInflater;
@ -15,14 +16,6 @@ import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import com.squareup.picasso.Callback;
import com.squareup.picasso.Picasso;
import com.squareup.picasso.RequestCreator;
@ -39,6 +32,14 @@ import net.osmand.plus.widgets.WebViewEx;
import net.osmand.plus.wikivoyage.WikivoyageUtils;
import net.osmand.util.Algorithms;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
public class GpxReadDescriptionDialogFragment extends BaseOsmAndDialogFragment {
public static final String TAG = GpxReadDescriptionDialogFragment.class.getSimpleName();
@ -181,10 +182,16 @@ public class GpxReadDescriptionDialogFragment extends BaseOsmAndDialogFragment {
private void setupWebView(final View view) {
webView = view.findViewById(R.id.content);
webView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
if (Build.VERSION.SDK_INT >= 19) {
webView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
else {
webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
webView.setScrollbarFadingEnabled(true);
webView.setVerticalScrollBarEnabled(false);
webView.setBackgroundColor(Color.TRANSPARENT);
webView.setLayerType(WebView.LAYER_TYPE_HARDWARE, null);
webView.getSettings().setTextZoom((int) (getResources().getConfiguration().fontScale * 100f));
webView.getSettings().setDomStorageEnabled(true);
webView.getSettings().setLoadWithOverviewMode(true);

View file

@ -74,7 +74,9 @@ public class OptionsCard extends BaseCard {
items.add(createDirectionsItem());
}
items.add(createDividerItem());
items.add(createJoinGapsItem());
if (gpxFile.getGeneralTrack() != null) {
items.add(createJoinGapsItem());
}
items.add(createAnalyzeOnMapItem());
items.add(createAnalyzeByIntervalsItem());

View file

@ -1,10 +1,6 @@
package net.osmand.plus.track;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.MotionEvent;
@ -17,11 +13,10 @@ import androidx.annotation.ColorRes;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.ItemDecoration;
import net.osmand.AndroidUtils;
import net.osmand.GPXUtilities.GPXFile;
import net.osmand.GPXUtilities.GPXTrackAnalysis;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayGroup;
@ -29,6 +24,7 @@ import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayItemType;
import net.osmand.plus.OsmAndFormatter;
import net.osmand.plus.R;
import net.osmand.plus.UiUtilities;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.helpers.AndroidUiHelper;
import net.osmand.plus.helpers.GpxUiHelper.GPXDataSetType;
@ -38,7 +34,6 @@ import net.osmand.plus.widgets.TextViewEx;
import net.osmand.util.Algorithms;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static net.osmand.plus.myplaces.TrackActivityFragmentAdapter.isGpxFileSelected;
@ -46,6 +41,7 @@ import static net.osmand.plus.track.OptionsCard.APPEARANCE_BUTTON_INDEX;
import static net.osmand.plus.track.OptionsCard.DIRECTIONS_BUTTON_INDEX;
import static net.osmand.plus.track.OptionsCard.EDIT_BUTTON_INDEX;
import static net.osmand.plus.track.OptionsCard.SHOW_ON_MAP_BUTTON_INDEX;
import static net.osmand.plus.track.OverviewCard.StatBlock.ItemType.*;
public class OverviewCard extends BaseCard {
@ -57,15 +53,22 @@ public class OverviewCard extends BaseCard {
private final TrackDisplayHelper displayHelper;
private final GPXFile gpxFile;
private final GpxDisplayItemType[] filterTypes = new GpxDisplayItemType[] {GpxDisplayItemType.TRACK_SEGMENT};
private final GpxDisplayItemType[] filterTypes = {GpxDisplayItemType.TRACK_SEGMENT};
private final SegmentActionsListener listener;
private boolean gpxFileSelected;
private GpxDisplayItem gpxItem;
public OverviewCard(@NonNull MapActivity mapActivity, @NonNull TrackDisplayHelper displayHelper,
@NonNull SegmentActionsListener listener) {
super(mapActivity);
this.displayHelper = displayHelper;
this.gpxFile = displayHelper.getGpx();
this.listener = listener;
gpxFile = displayHelper.getGpx();
gpxFileSelected = isGpxFileSelected(app, gpxFile);
List<GpxDisplayGroup> groups = displayHelper.getOriginalGroups(filterTypes);
if (!Algorithms.isEmpty(groups)) {
gpxItem = TrackDisplayHelper.flatten(displayHelper.getOriginalGroups(filterTypes)).get(0);
}
}
@Override
@ -75,7 +78,7 @@ public class OverviewCard extends BaseCard {
@Override
protected void updateContent() {
int iconColorDef = R.color.icon_color_active_light;
int iconColorDef = nightMode ? R.color.icon_color_active_dark : R.color.icon_color_active_light;
int iconColorPres = R.color.active_buttons_and_links_text_dark;
boolean fileAvailable = gpxFile.path != null && !gpxFile.showCurrentTrack;
@ -95,9 +98,7 @@ public class OverviewCard extends BaseCard {
}
void initStatBlocks() {
List<GpxDisplayGroup> groups = displayHelper.getOriginalGroups(filterTypes);
if (!Algorithms.isEmpty(groups)) {
GpxDisplayItem gpxItem = TrackDisplayHelper.flatten(groups).get(0);
if (gpxItem != null) {
GPXTrackAnalysis analysis = gpxItem.analysis;
boolean joinSegments = displayHelper.isJoinSegments();
float totalDistance = !joinSegments && gpxItem.isGeneralTrack() ? analysis.totalDistanceWithoutGaps : analysis.totalDistance;
@ -106,95 +107,85 @@ public class OverviewCard extends BaseCard {
String desc = OsmAndFormatter.getFormattedAlt(analysis.diffElevationDown, app);
String avg = OsmAndFormatter.getFormattedSpeed(analysis.avgSpeed, app);
String max = OsmAndFormatter.getFormattedSpeed(analysis.maxSpeed, app);
List<StatBlock> items = new ArrayList<>();
StatBlock sDistance = new StatBlock(app.getString(R.string.distance), OsmAndFormatter.getFormattedDistance(totalDistance, app),
R.drawable.ic_action_track_16, R.color.icon_color_default_light, GPXDataSetType.ALTITUDE, GPXDataSetType.SPEED);
StatBlock sAscent = new StatBlock(app.getString(R.string.altitude_ascent), asc,
R.drawable.ic_action_arrow_up_16, R.color.gpx_chart_red, GPXDataSetType.SLOPE, null);
StatBlock sDescent = new StatBlock(app.getString(R.string.altitude_descent), desc,
R.drawable.ic_action_arrow_down_16, R.color.gpx_pale_green, GPXDataSetType.ALTITUDE, GPXDataSetType.SLOPE);
StatBlock sAvSpeed = new StatBlock(app.getString(R.string.average_speed), avg,
R.drawable.ic_action_speed_16, R.color.icon_color_default_light, GPXDataSetType.SPEED, null);
StatBlock sMaxSpeed = new StatBlock(app.getString(R.string.max_speed), max,
R.drawable.ic_action_max_speed_16, R.color.icon_color_default_light, GPXDataSetType.SPEED, null);
StatBlock sTimeSpan = new StatBlock(app.getString(R.string.shared_string_time_span),
StatBlock.prepareData(analysis, items, app.getString(R.string.distance), OsmAndFormatter.getFormattedDistance(totalDistance, app),
R.drawable.ic_action_track_16, R.color.icon_color_default_light, GPXDataSetType.ALTITUDE, GPXDataSetType.SPEED, ITEM_DISTANCE);
StatBlock.prepareData(analysis, items, app.getString(R.string.altitude_ascent), asc,
R.drawable.ic_action_arrow_up_16, R.color.gpx_chart_red, GPXDataSetType.SLOPE, null, ITEM_ALTITUDE);
StatBlock.prepareData(analysis, items, app.getString(R.string.altitude_descent), desc,
R.drawable.ic_action_arrow_down_16, R.color.gpx_pale_green, GPXDataSetType.ALTITUDE, GPXDataSetType.SLOPE, ITEM_ALTITUDE);
StatBlock.prepareData(analysis, items, app.getString(R.string.average_speed), avg,
R.drawable.ic_action_speed_16, R.color.icon_color_default_light, GPXDataSetType.SPEED, null, ITEM_SPEED);
StatBlock.prepareData(analysis, items, app.getString(R.string.max_speed), max,
R.drawable.ic_action_max_speed_16, R.color.icon_color_default_light, GPXDataSetType.SPEED, null, ITEM_SPEED);
StatBlock.prepareData(analysis, items, app.getString(R.string.shared_string_time_span),
Algorithms.formatDuration((int) (timeSpan / 1000), app.accessibilityEnabled()),
R.drawable.ic_action_time_span_16, R.color.icon_color_default_light, GPXDataSetType.SPEED, null);
R.drawable.ic_action_time_span_16, R.color.icon_color_default_light, GPXDataSetType.SPEED, null, ITEM_TIME);
LinearLayoutManager llManager = new LinearLayoutManager(app);
llManager.setOrientation(LinearLayoutManager.HORIZONTAL);
rvOverview.setLayoutManager(llManager);
rvOverview.setItemAnimator(new DefaultItemAnimator());
List<StatBlock> items = Arrays.asList(sDistance, sAscent, sDescent, sAvSpeed, sMaxSpeed, sTimeSpan);
final StatBlockAdapter siAdapter = new StatBlockAdapter(items);
rvOverview.setAdapter(siAdapter);
rvOverview.addItemDecoration(new HorizontalDividerDecoration(app));
if (Algorithms.isEmpty(items)) {
AndroidUiHelper.updateVisibility(rvOverview, false);
} else {
final StatBlockAdapter sbAdapter = new StatBlockAdapter(items);
rvOverview.setLayoutManager(new LinearLayoutManager(app, LinearLayoutManager.HORIZONTAL, false));
rvOverview.setAdapter(sbAdapter);
}
} else {
AndroidUiHelper.updateVisibility(rvOverview, false);
}
}
private void initShowButton(final int iconColorDef, final int iconColorPres) {
final AppCompatImageView image = showButton.findViewById(R.id.image);
final AppCompatImageView filled = showButton.findViewById(R.id.filled);
final int iconShowResId = R.drawable.ic_action_view;
final int iconHideResId = R.drawable.ic_action_hide;
final boolean[] gpxFileSelected = {isGpxFileSelected(app, gpxFile)};
filled.setImageResource(R.drawable.bg_topbar_shield_exit_ref);
filled.setAlpha(gpxFileSelected[0] ? 1f : 0.1f);
setImageDrawable(image, gpxFileSelected[0] ? iconShowResId : iconHideResId,
gpxFileSelected[0] ? iconColorPres : iconColorDef);
showButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
gpxFileSelected[0] = !gpxFileSelected[0];
filled.setAlpha(gpxFileSelected[0] ? 1f : 0.1f);
setImageDrawable(image, gpxFileSelected[0] ? iconShowResId : iconHideResId,
gpxFileSelected[0] ? iconColorPres : iconColorDef);
CardListener listener = getListener();
if (listener != null) {
listener.onCardButtonPressed(OverviewCard.this, SHOW_ON_MAP_BUTTON_INDEX);
}
}
});
@DrawableRes
private int getActiveShowHideIcon() {
gpxFileSelected = isGpxFileSelected(app, gpxFile);
return gpxFileSelected ? R.drawable.ic_action_view : R.drawable.ic_action_hide;
}
private void initAppearanceButton(int iconColorDef, int iconColorPres) {
private void initShowButton(final int iconColorDef, final int iconColorPres) {
initButton(showButton, SHOW_ON_MAP_BUTTON_INDEX, getActiveShowHideIcon(), iconColorDef, iconColorPres);
}
private void initAppearanceButton(@ColorRes int iconColorDef, @ColorRes int iconColorPres) {
initButton(appearanceButton, APPEARANCE_BUTTON_INDEX, R.drawable.ic_action_appearance, iconColorDef, iconColorPres);
}
private void initEditButton(int iconColorDef, int iconColorPres) {
private void initEditButton(@ColorRes int iconColorDef, @ColorRes int iconColorPres) {
initButton(editButton, EDIT_BUTTON_INDEX, R.drawable.ic_action_edit_dark, iconColorDef, iconColorPres);
}
private void initDirectionsButton(int iconColorDef, int iconColorPres) {
private void initDirectionsButton(@ColorRes int iconColorDef, @ColorRes int iconColorPres) {
initButton(directionsButton, DIRECTIONS_BUTTON_INDEX, R.drawable.ic_action_gdirections_dark, iconColorDef, iconColorPres);
}
private void initButton(View item, final int buttonIndex, int iconResId, int iconColorDef, int iconColorPres) {
final AppCompatImageView image = item.findViewById(R.id.image);
private void initButton(View item, final int buttonIndex, @DrawableRes Integer iconResId,
@ColorRes final int iconColorDef, @ColorRes int iconColorPres) {
final AppCompatImageView icon = item.findViewById(R.id.image);
final AppCompatImageView filled = item.findViewById(R.id.filled);
filled.setImageResource(R.drawable.bg_topbar_shield_exit_ref);
filled.setImageResource(nightMode ? R.drawable.bg_plugin_logo_enabled_dark : R.drawable.bg_topbar_shield_exit_ref);
filled.setAlpha(0.1f);
setImageDrawable(image, iconResId, iconColorDef);
setOnTouchItem(item, image, filled, iconResId, iconColorDef, iconColorPres);
setImageDrawable(icon, iconResId, iconColorDef);
setOnTouchItem(item, icon, filled, iconResId, iconColorDef, iconColorPres);
item.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
CardListener listener = getListener();
if (listener != null) {
listener.onCardButtonPressed(OverviewCard.this, buttonIndex);
if (buttonIndex == SHOW_ON_MAP_BUTTON_INDEX) {
setImageDrawable(icon, getActiveShowHideIcon(), iconColorDef);
}
}
}
});
}
private void setImageDrawable(ImageView iv, @DrawableRes int resId, @ColorRes int color) {
Drawable icon = app.getUIUtilities().getIcon(resId, color);
private void setImageDrawable(ImageView iv, @DrawableRes Integer resId, @ColorRes int color) {
Drawable icon = resId != null ? app.getUIUtilities().getIcon(resId, color)
: UiUtilities.tintDrawable(iv.getDrawable(), getResolvedColor(color));
iv.setImageDrawable(icon);
}
private void setOnTouchItem(View item, final ImageView image, final ImageView filled, @DrawableRes final int resId, @ColorRes final int colorDef, @ColorRes final int colorPres) {
private void setOnTouchItem(View item, final ImageView image, final ImageView filled, @DrawableRes final Integer resId, @ColorRes final int colorDef, @ColorRes final int colorPres) {
item.setOnTouchListener(new View.OnTouchListener() {
@SuppressLint("ClickableViewAccessibility")
@Override
@ -241,26 +232,24 @@ public class OverviewCard extends BaseCard {
@Override
public void onBindViewHolder(StatBlockViewHolder holder, int position) {
final StatBlock item = statBlocks.get(position);
holder.valueText.setText(item.value);
holder.titleText.setText(item.title);
holder.valueText.setTextColor(app.getResources().getColor(R.color.active_color_primary_light));
holder.valueText.setTextColor(getActiveColor());
holder.titleText.setTextColor(app.getResources().getColor(R.color.text_color_secondary_light));
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
GpxDisplayItem gpxItem = TrackDisplayHelper.flatten(displayHelper.getOriginalGroups(filterTypes)).get(0);
if (gpxItem != null && gpxItem.analysis != null) {
ArrayList<GPXDataSetType> list = new ArrayList<>();
if (item.firstType != null) {
list.add(item.firstType);
}
if (item.secondType != null) {
list.add(item.secondType);
}
if (list.size() > 0) {
gpxItem.chartTypes = list.toArray(new GPXDataSetType[0]);
if (gpxItem.analysis.hasElevationData || gpxItem.analysis.isSpeedSpecified() || gpxItem.analysis.hasSpeedData) {
if (item.firstType != null) {
list.add(item.firstType);
}
if (item.secondType != null) {
list.add(item.secondType);
}
}
gpxItem.chartTypes = list.size() > 0 ? list.toArray(new GPXDataSetType[0]) : null;
gpxItem.locationOnMap = gpxItem.locationStart;
listener.openAnalyzeOnMap(gpxItem);
@ -268,6 +257,8 @@ public class OverviewCard extends BaseCard {
}
});
setImageDrawable(holder.imageView, item.imageResId, item.imageColorId);
AndroidUtils.setBackgroundColor(view.getContext(), holder.divider, nightMode, R.color.divider_color_light, R.color.divider_color_dark);
AndroidUiHelper.updateVisibility(holder.divider, position != statBlocks.size() - 1);
}
}
@ -276,55 +267,18 @@ public class OverviewCard extends BaseCard {
private final TextViewEx valueText;
private final TextView titleText;
private final AppCompatImageView imageView;
private final View divider;
StatBlockViewHolder(View view) {
super(view);
valueText = view.findViewById(R.id.value);
titleText = view.findViewById(R.id.title);
imageView = view.findViewById(R.id.image);
divider = view.findViewById(R.id.divider);
}
}
private static class HorizontalDividerDecoration extends ItemDecoration {
private final Drawable divider;
private final int marginV;
private final int marginH;
public HorizontalDividerDecoration(Context context) {
int[] ATTRS = new int[] {android.R.attr.listDivider};
final TypedArray ta = context.obtainStyledAttributes(ATTRS);
divider = ta.getDrawable(0);
// DrawableCompat.setTint(divider, context.getResources().getColor(R.color.divider_color_light)); //todo change drawable color
ta.recycle();
marginV = context.getResources().getDimensionPixelSize(R.dimen.map_small_button_margin);
marginH = context.getResources().getDimensionPixelSize(R.dimen.content_padding);
}
@Override
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
drawHorizontal(c, parent);
}
public void drawHorizontal(Canvas c, RecyclerView parent) {
for (int i = 0; i < parent.getChildCount(); i++) {
final View child = parent.getChildAt(i);
final int left = child.getRight() - divider.getIntrinsicWidth() + marginH;
final int right = left + divider.getIntrinsicHeight();
final int top = child.getTop() + marginV;
final int bottom = child.getBottom() - marginV;
divider.setBounds(left, top, right, bottom);
divider.draw(c);
}
}
@Override
public void getItemOffsets(Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
outRect.set(marginH - divider.getIntrinsicWidth(), marginV, marginH + divider.getIntrinsicWidth(), marginV);
}
}
private static class StatBlock {
protected static class StatBlock {
private final String title;
private final String value;
@ -332,15 +286,56 @@ public class OverviewCard extends BaseCard {
private final int imageColorId;
private final GPXDataSetType firstType;
private final GPXDataSetType secondType;
private final ItemType itemType;
public StatBlock(String title, String value, @DrawableRes int imageResId, @ColorRes int imageColorId,
GPXDataSetType firstType, GPXDataSetType secondType) {
GPXDataSetType firstType, GPXDataSetType secondType, ItemType itemType) {
this.title = title;
this.value = value;
this.imageResId = imageResId;
this.imageColorId = imageColorId;
this.firstType = firstType;
this.secondType = secondType;
this.itemType = itemType;
}
public static void prepareData(GPXTrackAnalysis analysis, List<StatBlock> listItems, String title,
String value, @DrawableRes int imageResId, @ColorRes int imageColorId,
GPXDataSetType firstType, GPXDataSetType secondType, ItemType itemType) {
StatBlock statBlock = new StatBlock(title, value, imageResId, imageColorId, firstType, secondType, itemType);
switch (statBlock.itemType) {
case ITEM_DISTANCE: {
if (analysis.totalDistance != 0f) {
listItems.add(statBlock);
}
break;
}
case ITEM_ALTITUDE: {
if (analysis.hasElevationData) {
listItems.add(statBlock);
}
break;
}
case ITEM_SPEED: {
if (analysis.isSpeedSpecified()) {
listItems.add(statBlock);
}
break;
}
case ITEM_TIME: {
if (analysis.hasSpeedData) {
listItems.add(statBlock);
}
break;
}
}
}
public enum ItemType {
ITEM_DISTANCE,
ITEM_ALTITUDE,
ITEM_SPEED,
ITEM_TIME;
}
}
}

View file

@ -19,6 +19,7 @@ import androidx.activity.OnBackPressedCallback;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
@ -37,6 +38,7 @@ import net.osmand.plus.R;
import net.osmand.plus.UiUtilities;
import net.osmand.plus.UiUtilities.DialogButtonType;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.base.ContextMenuFragment;
import net.osmand.plus.base.ContextMenuScrollFragment;
import net.osmand.plus.dialogs.GpxAppearanceAdapter;
import net.osmand.plus.dialogs.GpxAppearanceAdapter.AppearanceListItem;
@ -165,16 +167,7 @@ public class TrackAppearanceFragment extends ContextMenuScrollFragment implement
}
requireMyActivity().getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
public void handleOnBackPressed() {
MapActivity mapActivity = getMapActivity();
if (mapActivity != null) {
TripRecordingBottomSheet fragment = mapActivity.getTripRecordingBottomSheet();
if (fragment != null) {
fragment.show();
} else {
mapActivity.launchPrevActivityIntent();
}
dismissImmediate();
}
dismiss();
}
});
}
@ -385,6 +378,14 @@ public class TrackAppearanceFragment extends ContextMenuScrollFragment implement
return y;
}
@Override
public void onContextMenuDismiss(@NonNull ContextMenuFragment fragment) {
Fragment target = getTargetFragment();
if (target instanceof TripRecordingBottomSheet) {
((TripRecordingBottomSheet) target).show();
}
}
private void updateAppearanceIcon() {
Drawable icon = getTrackIcon(app, trackDrawInfo.getWidth(), trackDrawInfo.isShowArrows(), trackDrawInfo.getColor());
trackIcon.setImageDrawable(icon);
@ -725,11 +726,12 @@ public class TrackAppearanceFragment extends ContextMenuScrollFragment implement
return totalScreenHeight - frameTotalHeight;
}
public static boolean showInstance(@NonNull MapActivity mapActivity, @NonNull SelectedGpxFile selectedGpxFile) {
public static boolean showInstance(@NonNull MapActivity mapActivity, @NonNull SelectedGpxFile selectedGpxFile, Fragment target) {
try {
TrackAppearanceFragment fragment = new TrackAppearanceFragment();
fragment.setSelectedGpxFile(selectedGpxFile);
fragment.setRetainInstance(true);
fragment.setSelectedGpxFile(selectedGpxFile);
fragment.setTargetFragment(target, 0);
mapActivity.getSupportFragmentManager()
.beginTransaction()

View file

@ -60,7 +60,6 @@ import net.osmand.plus.UiUtilities;
import net.osmand.plus.UiUtilities.UpdateLocationViewCache;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.activities.MapActivityActions;
import net.osmand.plus.activities.TrackActivity;
import net.osmand.plus.base.ContextMenuFragment;
import net.osmand.plus.base.ContextMenuScrollFragment;
import net.osmand.plus.helpers.AndroidUiHelper;
@ -139,6 +138,7 @@ public class TrackMenuFragment extends ContextMenuScrollFragment implements Card
private ImageView searchButton;
private EditText searchEditText;
private TextView toolbarTextView;
private ViewGroup headerContainer;
private View routeMenuTopShadowAll;
private BottomNavigationView bottomNav;
@ -152,6 +152,7 @@ public class TrackMenuFragment extends ContextMenuScrollFragment implements Card
private int menuTitleHeight;
private int toolbarHeightPx;
private boolean mapPositionAdjusted;
public enum TrackMenuType {
OVERVIEW(R.id.action_overview, R.string.shared_string_overview),
@ -185,13 +186,17 @@ public class TrackMenuFragment extends ContextMenuScrollFragment implements Card
@Override
public int getToolbarHeight() {
return toolbarHeightPx;
return isPortrait() ? toolbarHeightPx : 0;
}
public float getMiddleStateKoef() {
return 0.5f;
}
public int getMinY() {
return getFullScreenTopPosY();
}
@Override
public int getSupportedMenuStatesPortrait() {
return MenuState.HEADER_ONLY | MenuState.HALF_SCREEN | MenuState.FULL_SCREEN;
@ -226,11 +231,22 @@ public class TrackMenuFragment extends ContextMenuScrollFragment implements Card
FragmentActivity activity = requireMyActivity();
activity.getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
public void handleOnBackPressed() {
MapActivity mapActivity = getMapActivity();
if (mapActivity != null) {
mapActivity.launchPrevActivityIntent();
if (getCurrentMenuState() != MenuState.HEADER_ONLY && isPortrait()) {
openMenuHeaderOnly();
} else {
dismiss();
MapActivity mapActivity = getMapActivity();
if (mapActivity != null) {
MapContextMenu contextMenu = mapActivity.getContextMenu();
if (contextMenu.isActive() && contextMenu.getPointDescription() != null
&& contextMenu.getPointDescription().isGpxPoint()) {
contextMenu.show();
} else {
mapActivity.launchPrevActivityIntent();
}
}
}
dismiss();
}
});
}
@ -249,6 +265,7 @@ public class TrackMenuFragment extends ContextMenuScrollFragment implements Card
if (view != null) {
bottomNav = view.findViewById(R.id.bottom_navigation);
routeMenuTopShadowAll = view.findViewById(R.id.route_menu_top_shadow_all);
headerContainer = view.findViewById(R.id.header_container);
headerTitle = view.findViewById(R.id.title);
headerIcon = view.findViewById(R.id.icon_view);
toolbarContainer = view.findViewById(R.id.context_menu_toolbar_container);
@ -269,8 +286,8 @@ public class TrackMenuFragment extends ContextMenuScrollFragment implements Card
setupToolbar();
updateHeader();
setupButtons(view);
enterTrackAppearanceMode();
runLayoutListener();
updateCardsLayout();
calculateLayoutAndUpdateMenuState();
}
return view;
}
@ -281,7 +298,6 @@ public class TrackMenuFragment extends ContextMenuScrollFragment implements Card
}
private void updateHeader() {
ViewGroup headerContainer = (ViewGroup) routeMenuTopShadowAll;
if (menuType == TrackMenuType.OVERVIEW) {
setHeaderTitle(gpxTitle, true);
if (overviewCard != null && overviewCard.getView() != null) {
@ -428,6 +444,17 @@ public class TrackMenuFragment extends ContextMenuScrollFragment implements Card
}
}
private void updateCardsLayout() {
FrameLayout bottomContainer = getBottomContainer();
if (menuType == TrackMenuType.OPTIONS) {
AndroidUtils.setBackground(app, bottomContainer, isNightMode(),
R.color.list_background_color_light, R.color.list_background_color_dark);
} else {
AndroidUtils.setBackground(app, bottomContainer, isNightMode(),
R.color.activity_background_color_light, R.color.activity_background_color_dark);
}
}
@Override
protected void calculateLayout(View view, boolean initLayout) {
menuTitleHeight = routeMenuTopShadowAll.getHeight()
@ -455,15 +482,21 @@ public class TrackMenuFragment extends ContextMenuScrollFragment implements Card
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
adjustMapPosition(getHeight());
public void onContextMenuStateChanged(@NonNull ContextMenuFragment fragment, int currentMenuState, int previousMenuState) {
super.onContextMenuStateChanged(fragment, currentMenuState, previousMenuState);
boolean changed = currentMenuState != previousMenuState;
if (changed) {
updateControlsVisibility(true);
}
if (currentMenuState != MenuState.FULL_SCREEN && (changed || !mapPositionAdjusted)) {
adjustMapPosition(getMenuStatePosY(currentMenuState));
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
exitTrackAppearanceMode();
updateStatusBarColor();
}
@ -474,6 +507,7 @@ public class TrackMenuFragment extends ContextMenuScrollFragment implements Card
if (mapActivity != null && trackChartPoints != null) {
mapActivity.getMapLayers().getGpxLayer().setTrackChartPoints(trackChartPoints);
}
updateControlsVisibility(true);
startLocationUpdate();
}
@ -484,6 +518,7 @@ public class TrackMenuFragment extends ContextMenuScrollFragment implements Card
if (mapActivity != null) {
mapActivity.getMapLayers().getGpxLayer().setTrackChartPoints(null);
}
updateControlsVisibility(false);
stopLocationUpdate();
}
@ -569,26 +604,25 @@ public class TrackMenuFragment extends ContextMenuScrollFragment implements Card
updateContent();
}
private void enterTrackAppearanceMode() {
public void updateControlsVisibility(boolean menuVisible) {
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);
boolean topControlsVisible = shouldShowTopControls(menuVisible);
boolean bottomControlsVisible = shouldShowBottomControls(menuVisible);
MapContextMenu.updateControlsVisibility(mapActivity, topControlsVisible, bottomControlsVisible);
}
}
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);
}
public boolean shouldShowTopControls() {
return shouldShowTopControls(isVisible());
}
public boolean shouldShowTopControls(boolean menuVisible) {
return !menuVisible || !isPortrait() || getCurrentMenuState() == MenuState.HEADER_ONLY;
}
public boolean shouldShowBottomControls(boolean menuVisible) {
return !menuVisible || !isPortrait();
}
@Override
@ -652,7 +686,7 @@ public class TrackMenuFragment extends ContextMenuScrollFragment implements Card
app.getSelectedGpxHelper().selectGpxFile(gpxFile, gpxFileSelected, false);
mapActivity.refreshMap();
} else if (buttonIndex == APPEARANCE_BUTTON_INDEX) {
TrackAppearanceFragment.showInstance(mapActivity, selectedGpxFile);
TrackAppearanceFragment.showInstance(mapActivity, selectedGpxFile, this);
} else if (buttonIndex == DIRECTIONS_BUTTON_INDEX) {
MapActivityActions mapActions = mapActivity.getMapActions();
if (app.getRoutingHelper().isFollowingMode()) {
@ -760,15 +794,6 @@ public class TrackMenuFragment extends ContextMenuScrollFragment implements Card
}
}
@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;
}
public void updateToolbar(int y, boolean animated) {
final MapActivity mapActivity = getMapActivity();
if (mapActivity != null) {
@ -799,7 +824,7 @@ public class TrackMenuFragment extends ContextMenuScrollFragment implements Card
@Override
protected void onHeaderClick() {
adjustMapPosition(getViewY());
updateMenuState();
}
private void adjustMapPosition(int y) {
@ -821,6 +846,7 @@ public class TrackMenuFragment extends ContextMenuScrollFragment implements Card
if (r.left != 0 && r.right != 0) {
mapActivity.getMapView().fitRectToMap(r.left, r.right, r.top, r.bottom, tileBoxWidthPx, tileBoxHeightPx, 0);
}
mapPositionAdjusted = true;
}
}
@ -838,6 +864,8 @@ public class TrackMenuFragment extends ContextMenuScrollFragment implements Card
menuType = type;
setupCards();
updateHeader();
updateCardsLayout();
calculateLayoutAndUpdateMenuState();
break;
}
}
@ -846,6 +874,25 @@ public class TrackMenuFragment extends ContextMenuScrollFragment implements Card
});
}
private void calculateLayoutAndUpdateMenuState() {
runLayoutListener(new Runnable() {
@Override
public void run() {
updateMenuState();
}
});
}
private void updateMenuState() {
if (menuType == TrackMenuType.OPTIONS) {
openMenuFullScreen();
} else if (menuType == TrackMenuType.OVERVIEW) {
openMenuHeaderOnly();
} else {
openMenuHalfScreen();
}
}
@Override
public void updateContent() {
if (segmentsCard != null) {
@ -1044,14 +1091,17 @@ public class TrackMenuFragment extends ContextMenuScrollFragment implements Card
}
public static void openTrack(@NonNull Context context, @Nullable File file, Bundle prevIntentParams) {
Bundle bundle = new Bundle();
bundle.putBoolean(OPEN_TRACK_MENU, true);
if (file == null) {
bundle.putBoolean(TrackActivity.CURRENT_RECORDING, true);
boolean currentRecording = file == null;
String path = file != null ? file.getAbsolutePath() : null;
if (context instanceof MapActivity) {
TrackMenuFragment.showInstance((MapActivity) context, path, currentRecording);
} else {
bundle.putString(TrackActivity.TRACK_FILE_NAME, file.getAbsolutePath());
Bundle bundle = new Bundle();
bundle.putString(TRACK_FILE_NAME, path);
bundle.putBoolean(OPEN_TRACK_MENU, true);
bundle.putBoolean(CURRENT_RECORDING, currentRecording);
MapActivity.launchMapActivityMoveToTop(context, prevIntentParams, null, bundle);
}
MapActivity.launchMapActivityMoveToTop(context, prevIntentParams, null, bundle);
}
public static void showInstance(@NonNull final MapActivity mapActivity, @Nullable String path, boolean showCurrentTrack) {

View file

@ -859,30 +859,30 @@ public class MapControlsLayer extends OsmandMapLayer {
}
boolean routeFollowingMode = !routePlanningMode && rh.isFollowingMode();
boolean trackDialogOpened = mapActivity.getTrackDetailsMenu().isVisible();
boolean contextMenuOpened = !mapActivity.getContextMenu().shouldShowTopControls();
boolean shouldHideTopControls = mapActivity.shouldHideTopControls();
boolean showRouteCalculationControls = routePlanningMode ||
((app.accessibilityEnabled() || (System.currentTimeMillis() - touchEvent < TIMEOUT_TO_SHOW_BUTTONS)) && routeFollowingMode);
boolean routeDialogOpened = mapRouteInfoMenu.isVisible() || (showRouteCalculationControls && mapRouteInfoMenu.needShowMenu());
updateMyLocationVisibility(backToLocationControl, rh, routeDialogOpened || contextMenuOpened);
updateMyLocationVisibility(backToLocationControl, rh, routeDialogOpened || shouldHideTopControls);
//routePlanningBtn.setIconResId(routeFollowingMode ? R.drawable.ic_action_info_dark : R.drawable.ic_action_gdirections_dark);
updateRoutePlaningButton(rh, routePlanningMode);
boolean showBottomMenuButtons = (showRouteCalculationControls || !routeFollowingMode)
&& !isInMovingMarkerMode() && !isInGpxDetailsMode() && !isInMeasurementToolMode()
&& !isInPlanRouteMode() && !contextMenuOpened && !isInChoosingRoutesMode()
&& !isInPlanRouteMode() && !shouldHideTopControls && !isInChoosingRoutesMode()
&& !isInWaypointsChoosingMode() && !isInFollowTrackMode() && !isInTrackAppearanceMode();
routePlanningBtn.updateVisibility(showBottomMenuButtons);
menuControl.updateVisibility(showBottomMenuButtons);
boolean showZoomButtons = !routeDialogOpened && !contextMenuOpened && !isInTrackAppearanceMode()
boolean showZoomButtons = !routeDialogOpened && !shouldHideTopControls && !isInTrackAppearanceMode()
&& (!isInGpxApproximationMode() || !isPotrait())
&& !isInFollowTrackMode() && (!isInChoosingRoutesMode() || !isInWaypointsChoosingMode() || !portrait);
mapZoomIn.updateVisibility(showZoomButtons);
mapZoomOut.updateVisibility(showZoomButtons);
boolean forceHideCompass = routeDialogOpened || trackDialogOpened || isInMeasurementToolMode()
|| isInPlanRouteMode() || contextMenuOpened || isInChoosingRoutesMode()
|| isInPlanRouteMode() || shouldHideTopControls || isInChoosingRoutesMode()
|| isInTrackAppearanceMode() || isInWaypointsChoosingMode() || isInFollowTrackMode();
compassHud.forceHideCompass = forceHideCompass;
compassHud.updateVisibility(!forceHideCompass && shouldShowCompass());
@ -892,7 +892,7 @@ public class MapControlsLayer extends OsmandMapLayer {
if (layersHud.setIconResId(appMode.getIconRes())) {
layersHud.update(app, isNight);
}
boolean showTopButtons = !routeDialogOpened && !trackDialogOpened && !contextMenuOpened
boolean showTopButtons = !routeDialogOpened && !trackDialogOpened && !shouldHideTopControls
&& !isInMeasurementToolMode() && !isInPlanRouteMode() && !isInChoosingRoutesMode()
&& !isInTrackAppearanceMode() && !isInWaypointsChoosingMode() && !isInFollowTrackMode();
layersHud.updateVisibility(showTopButtons);

View file

@ -425,6 +425,7 @@ public class MapQuickActionLayer extends OsmandMapLayer implements QuickActionRe
measurementToolLayer.isInMeasurementMode() ||
mapMarkersLayer.isInPlanRouteMode() ||
gpxLayer.isInTrackAppearanceMode() ||
mapControlsLayer.isInTrackMenuMode() ||
mapRouteInfoMenu.isVisible() ||
MapRouteInfoMenu.chooseRoutesVisible ||
MapRouteInfoMenu.waypointsVisible ||

View file

@ -974,7 +974,7 @@ public class MapInfoWidgetsFactory {
}
}
}
if (map.isTopToolbarActive() || !map.getContextMenu().shouldShowTopControls() || MapRouteInfoMenu.chooseRoutesVisible || MapRouteInfoMenu.waypointsVisible) {
if (map.isTopToolbarActive() || map.shouldHideTopControls() || MapRouteInfoMenu.chooseRoutesVisible || MapRouteInfoMenu.waypointsVisible) {
updateVisibility(false);
} else if (showClosestWaypointFirstInAddress && updateWaypoint()) {
updateVisibility(true);
@ -1232,7 +1232,7 @@ public class MapInfoWidgetsFactory {
@SuppressLint("SetTextI18n")
public boolean updateInfo() {
boolean visible = settings.SHOW_COORDINATES_WIDGET.get() && map.getContextMenu().shouldShowTopControls()
boolean visible = settings.SHOW_COORDINATES_WIDGET.get() && !map.shouldHideTopControls()
&& map.getMapRouteInfoMenu().shouldShowTopControls() && !map.isTopToolbarActive()
&& !map.getMapLayers().getGpxLayer().isInTrackAppearanceMode()
&& !MapRouteInfoMenu.chooseRoutesVisible && !MapRouteInfoMenu.waypointsVisible

View file

@ -191,7 +191,7 @@ public class MapMarkersWidgetsFactory {
|| map.getMapRouteInfoMenu().isVisible()
|| addressTopBar.getVisibility() == View.VISIBLE
|| map.isTopToolbarActive()
|| !map.getContextMenu().shouldShowTopControls()
|| map.shouldHideTopControls()
|| map.getMapLayers().getGpxLayer().isInTrackAppearanceMode()
|| map.getMapLayers().getMapMarkersLayer().isInPlanRouteMode()) {
updateVisibility(false);

View file

@ -26,7 +26,30 @@ public class OsmandTextFieldBoxes extends TextFieldBoxes {
floatingLabel.setVisibility(View.GONE);
labelSpace.setVisibility(View.GONE);
labelSpaceBelow.setVisibility(View.GONE);
int paddingH = getResources().getDimensionPixelSize(R.dimen.route_info_card_details_margin);
inputLayout.setPadding(0, paddingH, 0, paddingH);
int paddingV = getResources().getDimensionPixelSize(R.dimen.route_info_card_details_margin);
inputLayout.setPadding(0, paddingV, 0, paddingV);
}
public void setGravityFloatingLabel(int gravity) {
floatingLabel.setGravity(gravity);
}
@Override
public void setLabelText(String labelText) {
super.setLabelText(labelText);
floatingLabel.post(new Runnable() {
@Override
public void run() {
if (floatingLabel.getLineCount() > 1) {
inputLayout.setPadding(
inputLayout.getPaddingLeft(),
getResources().getDimensionPixelOffset(useDenseSpacing ? R.dimen.dense_editTextLayout_padding_top : R.dimen.editTextLayout_padding_top) +
getResources().getDimensionPixelSize(useDenseSpacing ? R.dimen.context_menu_first_line_top_margin : R.dimen.content_padding_small),
inputLayout.getPaddingRight(),
inputLayout.getPaddingBottom()
);
}
}
});
}
}

View file

@ -0,0 +1,14 @@
package net.osmand.plus.wikivoyage.data;
public class TravelGpx extends TravelArticle {
public static final String DISTANCE = "distance";
public static final String DIFF_ELE_UP = "diff_ele_up";
public static final String DIFF_ELE_DOWN = "diff_ele_down";
public static final String USER = "user";
public String user;
public float totalDistance = 0;
public double diffElevationUp = 0;
public double diffElevationDown = 0;
}

View file

@ -150,7 +150,8 @@ public class TravelLocalDataHelper {
@Nullable
private TravelArticle getArticle(String title, String lang) {
for (TravelArticle article : savedArticles) {
if (article.title != null && article.title.equals(title) && article.lang != null && article.lang.equals(lang)) {
if (Algorithms.stringsEqual(article.title, title)
&& Algorithms.stringsEqual(article.lang, lang)) {
return article;
}
}
@ -503,12 +504,12 @@ public class TravelLocalDataHelper {
SQLiteConnection conn = openConnection(false);
if (conn != null) {
try {
conn.execSQL("DELETE FROM " + BOOKMARKS_TABLE_NAME +
" WHERE " + BOOKMARKS_COL_ARTICLE_TITLE + " = ?" +
" AND " + BOOKMARKS_COL_ROUTE_ID + " = ?" +
" AND " + BOOKMARKS_COL_LANG + " = ?" +
" AND " + BOOKMARKS_COL_TRAVEL_BOOK + " = ?",
new Object[]{article.title, article.routeId, article.lang, travelBook});
String query = "DELETE FROM " + BOOKMARKS_TABLE_NAME +
" WHERE " + BOOKMARKS_COL_ARTICLE_TITLE + " = ?" +
" AND " + BOOKMARKS_COL_ROUTE_ID + " = ?" +
" AND " + BOOKMARKS_COL_LANG + ((article.lang != null) ? " = '" + article.lang + "'" : " IS NULL") +
" AND " + BOOKMARKS_COL_TRAVEL_BOOK + " = ?";
conn.execSQL(query, new Object[]{article.title, article.routeId, travelBook});
} finally {
conn.close();
}
@ -563,7 +564,12 @@ public class TravelLocalDataHelper {
@NonNull
private TravelArticle readSavedArticle(SQLiteCursor cursor) {
TravelArticle res = new TravelArticle();
TravelArticle res;
if (cursor.getString(cursor.getColumnIndex(BOOKMARKS_COL_LANG)) == null) {
res = new TravelGpx();
} else {
res = new TravelArticle();
}
res.title = cursor.getString(cursor.getColumnIndex(BOOKMARKS_COL_ARTICLE_TITLE));
res.lang = cursor.getString(cursor.getColumnIndex(BOOKMARKS_COL_LANG));
res.aggregatedPartOf = cursor.getString(cursor.getColumnIndex(BOOKMARKS_COL_IS_PART_OF));

View file

@ -2,6 +2,7 @@ package net.osmand.plus.wikivoyage.data;
import android.os.AsyncTask;
import android.text.TextUtils;
import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@ -13,6 +14,7 @@ import net.osmand.IndexConstants;
import net.osmand.OsmAndCollator;
import net.osmand.PlatformUtil;
import net.osmand.ResultMatcher;
import net.osmand.binary.BinaryMapDataObject;
import net.osmand.binary.BinaryMapIndexReader;
import net.osmand.binary.BinaryMapIndexReader.SearchPoiTypeFilter;
import net.osmand.binary.BinaryMapIndexReader.SearchRequest;
@ -46,8 +48,18 @@ import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import gnu.trove.iterator.TIntObjectIterator;
import static net.osmand.GPXUtilities.Track;
import static net.osmand.GPXUtilities.TrkSegment;
import static net.osmand.GPXUtilities.WptPt;
import static net.osmand.GPXUtilities.writeGpxFile;
import static net.osmand.plus.helpers.GpxUiHelper.getGpxTitle;
import static net.osmand.plus.wikivoyage.data.TravelGpx.DIFF_ELE_DOWN;
import static net.osmand.plus.wikivoyage.data.TravelGpx.DIFF_ELE_UP;
import static net.osmand.plus.wikivoyage.data.TravelGpx.DISTANCE;
import static net.osmand.plus.wikivoyage.data.TravelGpx.USER;
import static net.osmand.util.Algorithms.capitalizeFirstLetter;
public class TravelObfHelper implements TravelHelper {
@ -55,9 +67,13 @@ public class TravelObfHelper implements TravelHelper {
private static final String WORLD_WIKIVOYAGE_FILE_NAME = "World_wikivoyage.travel.obf";
public static final String ROUTE_ARTICLE = "route_article";
public static final String ROUTE_ARTICLE_POINT = "route_article_point";
public static final String ROUTE_TRACK = "route_track";
public static final int POPULAR_ARTICLES_SEARCH_RADIUS = 100000;
public static final int ARTICLE_SEARCH_RADIUS = 50000;
public static final int GPX_TRACKS_SEARCH_RADIUS = 10000;
public static final int MAX_POPULAR_ARTICLES_COUNT = 30;
public static final String REF_TAG = "ref";
public static final String NAME_TAG = "name";
private final OsmandApplication app;
private final Collator collator;
@ -91,47 +107,69 @@ public class TravelObfHelper implements TravelHelper {
public synchronized List<TravelArticle> loadPopularArticles() {
String lang = app.getLanguage();
List<TravelArticle> popularArticles = new ArrayList<>();
for (BinaryMapIndexReader reader : getReaders()) {
final List<Pair<File, Amenity>> amenities = new ArrayList<>();
final LatLon location = app.getMapViewTrackingUtilities().getMapLocation();
for (final BinaryMapIndexReader reader : getReaders()) {
try {
final LatLon location = app.getMapViewTrackingUtilities().getMapLocation();
SearchRequest<Amenity> req = BinaryMapIndexReader.buildSearchPoiRequest(
location, POPULAR_ARTICLES_SEARCH_RADIUS, -1, getSearchFilter(false), null);
List<Amenity> amenities = reader.searchPoi(req);
if (amenities.size() > 0) {
Collections.sort(amenities, new Comparator<Amenity>() {
@Override
public int compare(Amenity a1, Amenity a2) {
int d1 = (int) (MapUtils.getDistance(a1.getLocation().getLatitude(), a1.getLocation().getLongitude(),
location.getLatitude(), location.getLongitude()));
int d2 = (int) (MapUtils.getDistance(a2.getLocation().getLatitude(), a2.getLocation().getLongitude(),
location.getLatitude(), location.getLongitude()));
return d1 < d2 ? -1 : (d1 == d2 ? 0 : 1);
}
});
for (Amenity amenity : amenities) {
if (!Algorithms.isEmpty(amenity.getName(lang))) {
TravelArticle article = cacheTravelArticles(reader.getFile(), amenity, lang, false, null);
if (article != null) {
popularArticles.add(article);
if (popularArticles.size() >= MAX_POPULAR_ARTICLES_COUNT) {
break;
}
}
searchAmenity(amenities, location, reader, POPULAR_ARTICLES_SEARCH_RADIUS, -1, ROUTE_ARTICLE);
searchAmenity(amenities, location, reader, GPX_TRACKS_SEARCH_RADIUS, 15, ROUTE_TRACK);
} catch (Exception e) {
LOG.error(e.getMessage(), e);
}
}
if (amenities.size() > 0) {
Collections.sort(amenities, new Comparator<Pair<File, Amenity>>() {
@Override
public int compare(Pair article1, Pair article2) {
int d1 = (int) (MapUtils.getDistance(((Amenity) article1.second).getLocation(), location));
int d2 = (int) (MapUtils.getDistance(((Amenity) article2.second).getLocation(), location));
return d1 < d2 ? -1 : (d1 == d2 ? 0 : 1);
}
});
for (Pair<File, Amenity> amenity : amenities) {
if (!Algorithms.isEmpty(amenity.second.getName(lang))) {
TravelArticle article = cacheTravelArticles(amenity.first, amenity.second, lang, false, null);
if (article != null) {
popularArticles.add(article);
if (popularArticles.size() >= MAX_POPULAR_ARTICLES_COUNT) {
break;
}
}
}
} catch (Exception e) {
LOG.error(e.getMessage(), e);
}
}
this.popularArticles = popularArticles;
return popularArticles;
}
private void searchAmenity(final List<Pair<File, Amenity>> amenitiesList, LatLon location,
final BinaryMapIndexReader reader, int searchRadius, int zoom,
String searchFilter) throws IOException {
reader.searchPoi(BinaryMapIndexReader.buildSearchPoiRequest(
location, searchRadius, zoom, getSearchFilter(searchFilter), new ResultMatcher<Amenity>() {
@Override
public boolean publish(Amenity object) {
amenitiesList.add(new Pair<>(reader.getFile(), object));
return false;
}
@Override
public boolean isCancelled() {
return false;
}
}));
}
@Nullable
private TravelArticle cacheTravelArticles(File file, Amenity amenity, String lang, boolean readPoints, @Nullable GpxReadCallback callback) {
TravelArticle article = null;
Map<String, TravelArticle> articles = readArticles(file, amenity);
Map<String, TravelArticle> articles;
if (ROUTE_TRACK.equals(amenity.getSubType())) {
articles = readRoutePoint(file, amenity);
} else {
articles = readArticles(file, amenity);
}
if (!Algorithms.isEmpty(articles)) {
TravelArticleIdentifier newArticleId = articles.values().iterator().next().generateIdentifier();
cachedArticles.put(newArticleId, articles);
@ -140,12 +178,41 @@ public class TravelObfHelper implements TravelHelper {
return article;
}
private Map<String, TravelArticle> readRoutePoint(File file, Amenity amenity) {
Map<String, TravelArticle> articles = new HashMap<>();
TravelGpx res = new TravelGpx();
res.file = file;
String title = amenity.getName("en");
res.title = createTitle(Algorithms.isEmpty(title) ? amenity.getName() : title);
res.lat = amenity.getLocation().getLatitude();
res.lon = amenity.getLocation().getLongitude();
res.routeId = Algorithms.emptyIfNull(amenity.getTagContent(Amenity.ROUTE_ID));
try {
res.totalDistance = Float.parseFloat(Algorithms.emptyIfNull(amenity.getTagContent(DISTANCE)));
} catch (NumberFormatException e) {
LOG.debug(e.getMessage(), e);
}
try {
res.diffElevationUp = Double.parseDouble(Algorithms.emptyIfNull(amenity.getTagContent(DIFF_ELE_UP)));
} catch (NumberFormatException e) {
LOG.debug(e.getMessage(), e);
}
try {
res.diffElevationDown = Double.parseDouble(Algorithms.emptyIfNull(amenity.getTagContent(DIFF_ELE_DOWN)));
} catch (NumberFormatException e) {
LOG.debug(e.getMessage(), e);
}
res.user = Algorithms.emptyIfNull(amenity.getTagContent(USER));
articles.put("en", res);
return articles;
}
@NonNull
private SearchPoiTypeFilter getSearchFilter(final boolean articlePoints) {
private SearchPoiTypeFilter getSearchFilter(final String filterSubcategory) {
return new SearchPoiTypeFilter() {
@Override
public boolean accept(PoiCategory type, String subcategory) {
return subcategory.equals(articlePoints ? ROUTE_ARTICLE_POINT : ROUTE_ARTICLE);
return subcategory.equals(filterSubcategory);
}
@Override
@ -176,9 +243,9 @@ public class TravelObfHelper implements TravelHelper {
res.isParentOf = Algorithms.emptyIfNull(amenity.getTagContent(Amenity.IS_PARENT_OF, lang));
res.lat = amenity.getLocation().getLatitude();
res.lon = amenity.getLocation().getLongitude();
res.imageTitle = Algorithms.emptyIfNull(amenity.getTagContent(Amenity.IMAGE_TITLE, null));
res.routeId = Algorithms.emptyIfNull(amenity.getTagContent(Amenity.ROUTE_ID, null));
res.routeSource = Algorithms.emptyIfNull(amenity.getTagContent(Amenity.ROUTE_SOURCE, null));
res.imageTitle = Algorithms.emptyIfNull(amenity.getTagContent(Amenity.IMAGE_TITLE));
res.routeId = Algorithms.emptyIfNull(amenity.getTagContent(Amenity.ROUTE_ID));
res.routeSource = Algorithms.emptyIfNull(amenity.getTagContent(Amenity.ROUTE_SOURCE));
res.originalId = 0;
res.lang = lang;
res.contentsJson = Algorithms.emptyIfNull(amenity.getTagContent(Amenity.CONTENT_JSON, lang));
@ -186,6 +253,82 @@ public class TravelObfHelper implements TravelHelper {
return res;
}
@Nullable
private GPXFile buildTravelGpxFile(@NonNull final TravelGpx article) {
String routeId = article.getRouteId();
final String ref = routeId.substring(routeId.length() - 3);
final List<BinaryMapDataObject> segmentList = new ArrayList<>();
for (BinaryMapIndexReader reader : getReaders()) {
try {
if (article.file != null && !article.file.equals(reader.getFile())) {
continue;
}
BinaryMapIndexReader.SearchRequest<BinaryMapDataObject> sr = BinaryMapIndexReader.buildSearchRequest(
0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, 15, null,
new ResultMatcher<BinaryMapDataObject>() {
@Override
public boolean publish(BinaryMapDataObject object) {
if (object.getPointsLength() > 1) {
if (getTagValue(object, REF_TAG).equals(ref)
&& createTitle(getTagValue(object, NAME_TAG)).equals(article.title)) {
segmentList.add(object);
}
}
return false;
}
@Override
public boolean isCancelled() {
return false;
}
});
reader.searchMapIndex(sr);
if (!Algorithms.isEmpty(segmentList)) {
break;
}
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
GPXFile gpxFile = null;
if (!segmentList.isEmpty()) {
Track track = new Track();
for (BinaryMapDataObject segment : segmentList) {
TrkSegment trkSegment = new TrkSegment();
for (int i = 0; i < segment.getPointsLength(); i++) {
WptPt point = new WptPt();
point.lat = MapUtils.get31LatitudeY(segment.getPoint31YTile(i));
point.lon = MapUtils.get31LongitudeX(segment.getPoint31XTile(i));
trkSegment.points.add(point);
}
track.segments.add(trkSegment);
}
gpxFile = new GPXFile(article.getTitle(), article.getLang(), "");
gpxFile.tracks = new ArrayList<>();
gpxFile.tracks.add(track);
}
article.gpxFile = gpxFile;
return gpxFile;
}
private String getTagValue(BinaryMapDataObject object, String tag) {
BinaryMapIndexReader.MapIndex mi = object.getMapIndex();
TIntObjectIterator<String> it = object.getObjectNames().iterator();
while (it.hasNext()) {
it.advance();
BinaryMapIndexReader.TagValuePair tp = mi.decodeType(it.key());
if (tp.tag.equals(tag)) {
return it.value();
}
}
return "";
}
private String createTitle(String name) {
return capitalizeFirstLetter(getGpxTitle(name));
}
@NonNull
private synchronized List<Amenity> getPointList(@NonNull final TravelArticle article) {
final List<Amenity> pointList = new ArrayList<>();
@ -197,14 +340,14 @@ public class TravelObfHelper implements TravelHelper {
}
SearchRequest<Amenity> req = BinaryMapIndexReader.buildSearchPoiRequest(0, 0,
Algorithms.emptyIfNull(article.title), 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE,
getSearchFilter(true), new ResultMatcher<Amenity>() {
getSearchFilter(ROUTE_ARTICLE_POINT), new ResultMatcher<Amenity>() {
@Override
public boolean publish(Amenity amenity) {
String amenityLang = amenity.getTagSuffix(Amenity.LANG_YES + ":");
if (Algorithms.stringsEqual(lang, amenityLang)
&& Algorithms.stringsEqual(article.routeId,
Algorithms.emptyIfNull(amenity.getTagContent(Amenity.ROUTE_ID, null)))) {
Algorithms.emptyIfNull(amenity.getTagContent(Amenity.ROUTE_ID)))) {
pointList.add(amenity);
}
return false;
@ -246,7 +389,7 @@ public class TravelObfHelper implements TravelHelper {
}
String category = amenity.getTagSuffix("category_");
if (category != null) {
wptPt.category = Algorithms.capitalizeFirstLetter(category);
wptPt.category = capitalizeFirstLetter(category);
}
return wptPt;
}
@ -266,7 +409,7 @@ public class TravelObfHelper implements TravelHelper {
for (BinaryMapIndexReader reader : getReaders()) {
try {
SearchRequest<Amenity> searchRequest = BinaryMapIndexReader.buildSearchPoiRequest(0, 0, searchQuery,
0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, getSearchFilter(false), new ResultMatcher<Amenity>() {
0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, getSearchFilter(ROUTE_ARTICLE), new ResultMatcher<Amenity>() {
@Override
public boolean publish(Amenity object) {
List<String> otherNames = object.getAllNames(false);
@ -441,7 +584,7 @@ public class TravelObfHelper implements TravelHelper {
for (BinaryMapIndexReader reader : getReaders()) {
try {
SearchRequest<Amenity> req = BinaryMapIndexReader.buildSearchPoiRequest(
0, 0, title, 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, getSearchFilter(false),
0, 0, title, 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, getSearchFilter(ROUTE_ARTICLE),
new ResultMatcher<Amenity>() {
boolean done = false;
@ -530,12 +673,13 @@ public class TravelObfHelper implements TravelHelper {
}
SearchRequest<Amenity> req = BinaryMapIndexReader.buildSearchPoiRequest(0, 0,
Algorithms.emptyIfNull(articleId.title), 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE,
getSearchFilter(false), new ResultMatcher<Amenity>() {
getSearchFilter(ROUTE_ARTICLE), new ResultMatcher<Amenity>() {
boolean done = false;
@Override
public boolean publish(Amenity amenity) {
if (Algorithms.stringsEqual(articleId.routeId, Algorithms.emptyIfNull(amenity.getTagContent(Amenity.ROUTE_ID, null))) || isDbArticle) {
if (Algorithms.stringsEqual(articleId.routeId,
Algorithms.emptyIfNull(amenity.getTagContent(Amenity.ROUTE_ID))) || isDbArticle) {
amenities.add(amenity);
done = true;
}
@ -606,7 +750,7 @@ public class TravelObfHelper implements TravelHelper {
for (BinaryMapIndexReader reader : getReaders()) {
try {
SearchRequest<Amenity> req = BinaryMapIndexReader.buildSearchPoiRequest(
x, y, title, left, right, top, bottom, getSearchFilter(false),
x, y, title, left, right, top, bottom, getSearchFilter(ROUTE_ARTICLE),
new ResultMatcher<Amenity>() {
boolean done = false;
@ -693,7 +837,12 @@ public class TravelObfHelper implements TravelHelper {
@NonNull
@Override
public File createGpxFile(@NonNull TravelArticle article) {
final GPXFile gpx = article.getGpxFile();
final GPXFile gpx;
if (article instanceof TravelGpx) {
gpx = buildTravelGpxFile((TravelGpx) article);
} else {
gpx = article.getGpxFile();
}
File file = app.getAppPath(IndexConstants.GPX_TRAVEL_DIR + getGPXName(article));
writeGpxFile(file, gpx);
return file;

View file

@ -20,6 +20,8 @@ import net.osmand.plus.wikivoyage.explore.travelcards.StartEditingTravelCard;
import net.osmand.plus.wikivoyage.explore.travelcards.StartEditingTravelCard.StartEditingTravelVH;
import net.osmand.plus.wikivoyage.explore.travelcards.TravelDownloadUpdateCard;
import net.osmand.plus.wikivoyage.explore.travelcards.TravelDownloadUpdateCard.DownloadUpdateVH;
import net.osmand.plus.wikivoyage.explore.travelcards.TravelGpxCard;
import net.osmand.plus.wikivoyage.explore.travelcards.TravelGpxCard.TravelGpxVH;
import net.osmand.plus.wikivoyage.explore.travelcards.TravelNeededMapsCard;
import net.osmand.plus.wikivoyage.explore.travelcards.TravelNeededMapsCard.NeededMapsVH;
@ -48,6 +50,9 @@ public class ExploreRvAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
case ArticleTravelCard.TYPE:
return new ArticleTravelVH(inflate(parent, R.layout.wikivoyage_article_card));
case TravelGpxCard.TYPE:
return new TravelGpxVH(inflate(parent, R.layout.wikivoyage_travel_gpx_card));
case TravelDownloadUpdateCard.TYPE:
return new DownloadUpdateVH(inflate(parent, R.layout.travel_download_update_card));
@ -74,6 +79,10 @@ public class ExploreRvAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
HeaderTravelCard headerTravelCard = (HeaderTravelCard) item;
headerTravelCard.setArticleItemCount(getArticleItemCount());
headerTravelCard.bindViewHolder(viewHolder);
} else if (viewHolder instanceof ArticleTravelVH && item instanceof TravelGpxCard) {
TravelGpxCard travelGpxCard = (TravelGpxCard) item;
travelGpxCard.setLastItem(position == getLastArticleItemIndex());
travelGpxCard.bindViewHolder(viewHolder);
} else if (viewHolder instanceof ArticleTravelVH && item instanceof ArticleTravelCard) {
ArticleTravelCard articleTravelCard = (ArticleTravelCard) item;
articleTravelCard.setLastItem(position == getLastArticleItemIndex());
@ -96,7 +105,7 @@ public class ExploreRvAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
public int getArticleItemCount() {
int count = 0;
for (BaseTravelCard o : items) {
if (o instanceof ArticleTravelCard) {
if (o instanceof ArticleTravelCard || o instanceof TravelGpxCard) {
count++;
}
}
@ -106,7 +115,7 @@ public class ExploreRvAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
private int getLastArticleItemIndex() {
for (int i = items.size() - 1; i > 0; i--) {
BaseTravelCard o = items.get(i);
if (o instanceof ArticleTravelCard) {
if (o instanceof ArticleTravelCard || o instanceof TravelGpxCard) {
return i;
}
}

View file

@ -29,6 +29,7 @@ import net.osmand.plus.download.DownloadResources;
import net.osmand.plus.download.DownloadValidationManager;
import net.osmand.plus.download.IndexItem;
import net.osmand.plus.wikivoyage.data.TravelArticle;
import net.osmand.plus.wikivoyage.data.TravelGpx;
import net.osmand.plus.wikivoyage.data.TravelHelper;
import net.osmand.plus.wikivoyage.data.TravelLocalDataHelper;
import net.osmand.plus.wikivoyage.explore.travelcards.ArticleTravelCard;
@ -37,6 +38,7 @@ import net.osmand.plus.wikivoyage.explore.travelcards.HeaderTravelCard;
import net.osmand.plus.wikivoyage.explore.travelcards.OpenBetaTravelCard;
import net.osmand.plus.wikivoyage.explore.travelcards.StartEditingTravelCard;
import net.osmand.plus.wikivoyage.explore.travelcards.TravelDownloadUpdateCard;
import net.osmand.plus.wikivoyage.explore.travelcards.TravelGpxCard;
import net.osmand.plus.wikivoyage.explore.travelcards.TravelNeededMapsCard;
import java.io.IOException;
@ -178,17 +180,21 @@ public class ExploreTabFragment extends BaseOsmAndFragment implements DownloadEv
List<TravelArticle> popularArticles = app.getTravelHelper().getPopularArticles();
for (TravelArticle article : popularArticles) {
items.add(new ArticleTravelCard(app, nightMode, article, activity.getSupportFragmentManager()));
if (article instanceof TravelGpx) {
items.add(new TravelGpxCard(app, nightMode, (TravelGpx) article, getActivity()));
} else {
items.add(new ArticleTravelCard(app, nightMode, article, activity.getSupportFragmentManager()));
}
}
items.add(new StartEditingTravelCard(activity, nightMode));
adapter.setItems(items);
final DownloadIndexesThread downloadThread = app.getDownloadThread();
if (!downloadThread.getIndexes().isDownloadedFromInternet) {
waitForIndexes = true;
downloadThread.runReloadIndexFilesSilent();
} else {
checkDownloadIndexes();
}
}
items.add(new StartEditingTravelCard(activity, nightMode));
adapter.setItems(items);
final DownloadIndexesThread downloadThread = app.getDownloadThread();
if (!downloadThread.getIndexes().isDownloadedFromInternet) {
waitForIndexes = true;
downloadThread.runReloadIndexFilesSilent();
} else {
checkDownloadIndexes();
}
}
}

View file

@ -7,6 +7,8 @@ import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.DrawableRes;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
@ -16,16 +18,20 @@ import com.squareup.picasso.Callback;
import com.squareup.picasso.Picasso;
import com.squareup.picasso.RequestCreator;
import net.osmand.AndroidUtils;
import net.osmand.PicassoUtils;
import net.osmand.plus.OsmAndFormatter;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.R;
import net.osmand.plus.UiUtilities;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.widgets.tools.CropCircleTransformation;
import net.osmand.plus.wikipedia.WikiArticleHelper;
import net.osmand.plus.wikivoyage.WikivoyageUtils;
import net.osmand.plus.wikivoyage.data.TravelArticle;
import net.osmand.plus.wikivoyage.data.TravelGpx;
import net.osmand.plus.wikivoyage.data.TravelLocalDataHelper;
import net.osmand.plus.wikivoyage.explore.travelcards.TravelGpxCard;
import java.util.ArrayList;
import java.util.List;
@ -33,7 +39,8 @@ import java.util.List;
public class SavedArticlesRvAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int HEADER_TYPE = 0;
private static final int ITEM_TYPE = 1;
private static final int ARTICLE_TYPE = 1;
private static final int GPX_TYPE = 2;
private final OsmandApplication app;
private final OsmandSettings settings;
@ -45,6 +52,7 @@ public class SavedArticlesRvAdapter extends RecyclerView.Adapter<RecyclerView.Vi
private final Drawable readIcon;
private final Drawable deleteIcon;
private PicassoUtils picasso;
boolean nightMode;
public void setListener(Listener listener) {
this.listener = listener;
@ -54,21 +62,34 @@ public class SavedArticlesRvAdapter extends RecyclerView.Adapter<RecyclerView.Vi
this.app = app;
this.settings = app.getSettings();
picasso = PicassoUtils.getPicasso(app);
nightMode = !app.getSettings().isLightContent();
readIcon = getActiveIcon(R.drawable.ic_action_read_article);
deleteIcon = getActiveIcon(R.drawable.ic_action_read_later_fill);
}
int colorId = settings.isLightContent()
? R.color.wikivoyage_active_light : R.color.wikivoyage_active_dark;
UiUtilities ic = app.getUIUtilities();
readIcon = ic.getIcon(R.drawable.ic_action_read_article, colorId);
deleteIcon = ic.getIcon(R.drawable.ic_action_read_later_fill, colorId);
private Drawable getActiveIcon(@DrawableRes int iconId) {
int colorId = nightMode ? R.color.wikivoyage_active_dark : R.color.wikivoyage_active_light;
return app.getUIUtilities().getIcon(iconId, colorId);
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
boolean header = viewType == HEADER_TYPE;
int layoutId = header ? R.layout.wikivoyage_list_header : R.layout.wikivoyage_article_card;
View itemView = LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);
return header ? new HeaderVH(itemView) : new ItemVH(itemView);
switch (viewType) {
case HEADER_TYPE:
return new HeaderVH(inflate(parent, R.layout.wikivoyage_list_header));
case ARTICLE_TYPE:
return new ItemVH(inflate(parent, R.layout.wikivoyage_article_card));
case GPX_TYPE:
return new TravelGpxCard.TravelGpxVH(inflate(parent, R.layout.wikivoyage_travel_gpx_card));
default:
throw new RuntimeException("Unsupported view type: " + viewType);
}
}
@NonNull
private View inflate(@NonNull ViewGroup parent, @LayoutRes int layoutId) {
return LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);
}
@Override
@ -77,7 +98,7 @@ public class SavedArticlesRvAdapter extends RecyclerView.Adapter<RecyclerView.Vi
final HeaderVH holder = (HeaderVH) viewHolder;
holder.title.setText((String) getItem(position));
holder.description.setText(String.valueOf(items.size() - 1));
} else {
} else if (viewHolder instanceof ItemVH) {
final ItemVH holder = (ItemVH) viewHolder;
TravelArticle article = (TravelArticle) getItem(position);
final String url = TravelArticle.getImageUrl(article.getImageTitle(), false);
@ -111,6 +132,52 @@ public class SavedArticlesRvAdapter extends RecyclerView.Adapter<RecyclerView.Vi
holder.rightButton.setCompoundDrawablesWithIntrinsicBounds(null, null, deleteIcon, null);
holder.divider.setVisibility(lastItem ? View.GONE : View.VISIBLE);
holder.shadow.setVisibility(lastItem ? View.VISIBLE : View.GONE);
} else if (viewHolder instanceof TravelGpxCard.TravelGpxVH) {
final TravelGpx article = (TravelGpx) getItem(position);
final TravelGpxCard.TravelGpxVH holder = (TravelGpxCard.TravelGpxVH) viewHolder;
holder.title.setText(article.getTitle());
Drawable icon = getActiveIcon(R.drawable.ic_action_user_account_16);
holder.user.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
holder.user.setText(WikiArticleHelper.getPartialContent(article.user));
AndroidUtils.setBackground(app, holder.user, nightMode, R.drawable.btn_border_bg_light, R.drawable.btn_border_bg_dark);
holder.distance.setText(OsmAndFormatter.getFormattedDistance(article.totalDistance, app));
holder.diffElevationUp.setText(OsmAndFormatter.getFormattedAlt(article.diffElevationUp, app));
holder.diffElevationDown.setText(OsmAndFormatter.getFormattedAlt(article.diffElevationDown, app));
holder.leftButton.setText(app.getString(R.string.shared_string_view));
View.OnClickListener readClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
if (listener != null) {
listener.openArticle(article);
}
}
};
holder.leftButton.setOnClickListener(readClickListener);
holder.itemView.setOnClickListener(readClickListener);
holder.leftButton.setCompoundDrawablesWithIntrinsicBounds(readIcon, null, null, null);
updateSaveButton(holder, article);
}
}
private void updateSaveButton(final TravelGpxCard.TravelGpxVH holder, final TravelGpx article) {
if (article != null) {
final TravelLocalDataHelper helper = app.getTravelHelper().getBookmarksHelper();
final boolean saved = helper.isArticleSaved(article);
Drawable icon = getActiveIcon(saved ? R.drawable.ic_action_read_later_fill : R.drawable.ic_action_read_later);
holder.rightButton.setText(saved ? R.string.shared_string_remove : R.string.shared_string_save);
holder.rightButton.setCompoundDrawablesWithIntrinsicBounds(null, null, icon, null);
holder.rightButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (saved) {
helper.removeArticleFromSaved(article);
} else {
app.getTravelHelper().createGpxFile(article);
helper.addArticleToSaved(article);
}
updateSaveButton(holder, article);
}
});
}
}
@ -118,8 +185,10 @@ public class SavedArticlesRvAdapter extends RecyclerView.Adapter<RecyclerView.Vi
public int getItemViewType(int position) {
if (getItem(position) instanceof String) {
return HEADER_TYPE;
} else if (getItem(position) instanceof TravelGpx) {
return GPX_TYPE;
}
return ITEM_TYPE;
return ARTICLE_TYPE;
}
@Override

View file

@ -8,6 +8,7 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.LinearLayoutManager;
@ -17,12 +18,15 @@ import net.osmand.PlatformUtil;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.base.BaseOsmAndFragment;
import net.osmand.plus.track.TrackMenuFragment;
import net.osmand.plus.wikivoyage.article.WikivoyageArticleDialogFragment;
import net.osmand.plus.wikivoyage.data.TravelArticle;
import net.osmand.plus.wikivoyage.data.TravelGpx;
import net.osmand.plus.wikivoyage.data.TravelLocalDataHelper;
import org.apache.commons.logging.Log;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -48,9 +52,17 @@ public class SavedArticlesTabFragment extends BaseOsmAndFragment implements Trav
adapter.setListener(new SavedArticlesRvAdapter.Listener() {
@Override
public void openArticle(TravelArticle article) {
FragmentManager fm = getFragmentManager();
if (fm != null) {
WikivoyageArticleDialogFragment.showInstance(app, fm, article.generateIdentifier(), article.getLang());
if (article instanceof TravelGpx) {
FragmentActivity activity = getActivity();
if (activity != null) {
File file = app.getTravelHelper().createGpxFile(article);
TrackMenuFragment.openTrack(getActivity(), file, null);
}
} else {
FragmentManager fm = getFragmentManager();
if (fm != null) {
WikivoyageArticleDialogFragment.showInstance(app, fm, article.generateIdentifier(), article.getLang());
}
}
}
});

View file

@ -0,0 +1,125 @@
package net.osmand.plus.wikivoyage.explore.travelcards;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentActivity;
import androidx.recyclerview.widget.RecyclerView;
import net.osmand.AndroidUtils;
import net.osmand.plus.OsmAndFormatter;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.track.TrackMenuFragment;
import net.osmand.plus.wikivoyage.data.TravelGpx;
import net.osmand.plus.wikivoyage.data.TravelLocalDataHelper;
import java.io.File;
public class TravelGpxCard extends BaseTravelCard {
public static final int TYPE = 3;
private final TravelGpx article;
private final Drawable readIcon;
private final FragmentActivity activity;
private boolean isLastItem;
public TravelGpxCard(@NonNull OsmandApplication app, boolean nightMode, @NonNull TravelGpx article,
@NonNull FragmentActivity activity) {
super(app, nightMode);
this.article = article;
readIcon = getActiveIcon(R.drawable.ic_action_read_article);
this.activity = activity;
}
@Override
public void bindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder) {
if (viewHolder instanceof TravelGpxVH) {
final TravelGpxVH holder = (TravelGpxVH) viewHolder;
holder.title.setText(article.getTitle());
Drawable icon = getActiveIcon(R.drawable.ic_action_user_account_16);
holder.user.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
holder.user.setText(article.user);
AndroidUtils.setBackground(app, holder.user, nightMode, R.drawable.btn_border_bg_light, R.drawable.btn_border_bg_dark);
holder.distance.setText(OsmAndFormatter.getFormattedDistance(article.totalDistance, app));
holder.diffElevationUp.setText(OsmAndFormatter.getFormattedAlt(article.diffElevationUp, app));
holder.diffElevationDown.setText(OsmAndFormatter.getFormattedAlt(article.diffElevationDown, app));
holder.leftButton.setText(app.getString(R.string.shared_string_view));
View.OnClickListener readClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
if (activity != null) {
File file = app.getTravelHelper().createGpxFile(article);
TrackMenuFragment.openTrack(activity, file, null);
}
}
};
holder.leftButton.setOnClickListener(readClickListener);
holder.itemView.setOnClickListener(readClickListener);
holder.leftButton.setCompoundDrawablesWithIntrinsicBounds(readIcon, null, null, null);
updateSaveButton(holder);
holder.divider.setVisibility(isLastItem ? View.GONE : View.VISIBLE);
holder.shadow.setVisibility(isLastItem ? View.VISIBLE : View.GONE);
}
}
private void updateSaveButton(final TravelGpxVH holder) {
if (article != null) {
final TravelLocalDataHelper helper = app.getTravelHelper().getBookmarksHelper();
final boolean saved = helper.isArticleSaved(article);
Drawable icon = getActiveIcon(saved ? R.drawable.ic_action_read_later_fill : R.drawable.ic_action_read_later);
holder.rightButton.setText(saved ? R.string.shared_string_remove : R.string.shared_string_save);
holder.rightButton.setCompoundDrawablesWithIntrinsicBounds(null, null, icon, null);
holder.rightButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (saved) {
helper.removeArticleFromSaved(article);
} else {
app.getTravelHelper().createGpxFile(article);
helper.addArticleToSaved(article);
}
updateSaveButton(holder);
}
});
}
}
public static class TravelGpxVH extends RecyclerView.ViewHolder {
public final TextView title;
public final TextView user;
public final TextView distance;
public final TextView diffElevationUp;
public final TextView diffElevationDown;
public final TextView leftButton;
public final TextView rightButton;
public final View divider;
public final View shadow;
public TravelGpxVH(final View itemView) {
super(itemView);
title = itemView.findViewById(R.id.title);
user = itemView.findViewById(R.id.user_name);
distance = itemView.findViewById(R.id.distance);
diffElevationUp = itemView.findViewById(R.id.diff_ele_up);
diffElevationDown = itemView.findViewById(R.id.diff_ele_down);
leftButton = itemView.findViewById(R.id.left_button);
rightButton = itemView.findViewById(R.id.right_button);
divider = itemView.findViewById(R.id.divider);
shadow = itemView.findViewById(R.id.shadow);
}
}
public void setLastItem(boolean lastItem) {
isLastItem = lastItem;
}
@Override
public int getCardType() {
return TYPE;
}
}

View file

@ -21,4 +21,4 @@
# UPDATE 3: Turn on D8 to recover builds with new gradle 6.5 and pluigin 4.1.1
#android.enableD8=false
android.enableJetifier=true
android.useAndroidX=true
android.useAndroidX=true