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

This commit is contained in:
Victor Shcherb 2016-01-27 22:10:45 +01:00
commit a0c1c05cdd
57 changed files with 2096 additions and 1287 deletions

View file

@ -29,6 +29,7 @@ public class RenderingRuleStorageProperties {
public static final String SHIELD = "shield"; public static final String SHIELD = "shield";
public static final String SHADOW_RADIUS = "shadowRadius"; public static final String SHADOW_RADIUS = "shadowRadius";
public static final String SHADOW_COLOR = "shadowColor"; public static final String SHADOW_COLOR = "shadowColor";
public static final String ONEWAY_ARROWS_COLOR = "onewayArrowsColor";
public static final String SHADER = "shader"; public static final String SHADER = "shader";
public static final String CAP_5 = "cap_5"; public static final String CAP_5 = "cap_5";
public static final String CAP_4 = "cap_4"; public static final String CAP_4 = "cap_4";
@ -103,6 +104,7 @@ public class RenderingRuleStorageProperties {
public RenderingRuleProperty R_SHADOW_RADIUS; public RenderingRuleProperty R_SHADOW_RADIUS;
public RenderingRuleProperty R_SHADOW_COLOR; public RenderingRuleProperty R_SHADOW_COLOR;
public RenderingRuleProperty R_SHADER; public RenderingRuleProperty R_SHADER;
public RenderingRuleProperty R_ONEWAY_ARROWS_COLOR;
public RenderingRuleProperty R_CAP_5; public RenderingRuleProperty R_CAP_5;
public RenderingRuleProperty R_CAP_4; public RenderingRuleProperty R_CAP_4;
public RenderingRuleProperty R_CAP_3; public RenderingRuleProperty R_CAP_3;
@ -288,6 +290,8 @@ public class RenderingRuleStorageProperties {
R_SHADOW_COLOR = registerRuleInternal(RenderingRuleProperty.createOutputColorProperty(SHADOW_COLOR)); R_SHADOW_COLOR = registerRuleInternal(RenderingRuleProperty.createOutputColorProperty(SHADOW_COLOR));
R_SHADOW_RADIUS = registerRuleInternal(RenderingRuleProperty.createOutputFloatProperty(SHADOW_RADIUS)); R_SHADOW_RADIUS = registerRuleInternal(RenderingRuleProperty.createOutputFloatProperty(SHADOW_RADIUS));
R_ONEWAY_ARROWS_COLOR = registerRuleInternal(RenderingRuleProperty.createOutputColorProperty(ONEWAY_ARROWS_COLOR));
} }
public RenderingRuleProperty get(String name) { public RenderingRuleProperty get(String name) {

View file

@ -30,6 +30,19 @@
<string name="osm_live_subscription">OSM Live subscription</string> <string name="osm_live_subscription">OSM Live subscription</string>
<string name="osm_live_subscribe_btn">Subscribe</string> <string name="osm_live_subscribe_btn">Subscribe</string>
<string name="osm_live_default_price">€1,49</string> <string name="osm_live_default_price">€1,49</string>
<string name="osm_live_email_desc">We need it to provide you information about contributions</string>
<string name="osm_live_user_public_name">Public Name</string>
<string name="osm_live_hide_user_name">Don\'t show my name in reports</string>
<string name="osm_live_support_region">Support region</string>
<string name="osm_live_month_cost">Month cost</string>
<string name="osm_live_month_cost_desc">Monthly payment</string>
<string name="osm_live_active">Active</string>
<string name="osm_live_not_active">Inactive</string>
<string name="osm_live_enter_email">Please enter valid E-mail address</string>
<string name="osm_live_enter_user_name">Please enter Public Name</string>
<string name="osm_live_thanks">Thank you for subscribing to live updates!</string>
<string name="osm_live_region_desc">Part your donation will be sent to OSM users who submit changes to the map in that region</string>
<string name="osm_live_subscription_settings">Subscription settings</string>
<string name="osm_live_header">This subscription enables hourly updates for all maps around the world. <string name="osm_live_header">This subscription enables hourly updates for all maps around the world.
Major part of the income goes back to OSM community and is paid out per each OSM contribution. Major part of the income goes back to OSM community and is paid out per each OSM contribution.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<nine-patch android:src="@drawable/bg_card_shadow" />
</item>
<item>
<shape>
<solid
android:color="@color/osmand_orange" />
<corners
android:radius="2dp" />
</shape>
</item>
</layer-list>

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"><shape android:shape="rectangle">
<solid android:color="@color/map_widget_blue_pressed" />
<corners android:radius="@dimen/map_button_rect_rad" />
</shape></item>
<item><shape android:shape="rectangle">
<solid android:color="@color/map_widget_blue" />
<corners android:radius="@dimen/map_button_rect_rad" />
</shape></item>
</selector>

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"><shape android:shape="rectangle">
<solid android:color="@color/map_widget_dark_pressed" />
<corners android:radius="@dimen/map_button_rect_rad" />
</shape></item>
<item><shape android:shape="rectangle">
<solid android:color="@color/fab_color_pressed" />
<corners android:radius="@dimen/map_button_rect_rad" />
</shape></item>
</selector>

View file

@ -62,7 +62,7 @@
layout="@layout/reports_for_spinner_item" layout="@layout/reports_for_spinner_item"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical" android:layout_gravity="fill_vertical"
android:layout_marginLeft="16dp" android:layout_marginLeft="16dp"
android:layout_marginRight="56dp"/> android:layout_marginRight="56dp"/>

View file

@ -4,16 +4,16 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="8dp" android:paddingLeft="8dp"
android:paddingRight="8dp" android:paddingRight="8dp"
android:paddingTop="8dp" android:paddingTop="8dp">
android:orientation="vertical">
<LinearLayout <LinearLayout
android:id="@+id/subscription_banner" android:id="@+id/subscription_banner"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@color/osmand_orange" android:background="@drawable/bg_card_orange"
android:orientation="vertical" android:orientation="vertical"
android:paddingBottom="8dp" android:paddingBottom="8dp"
android:paddingLeft="24dp" android:paddingLeft="24dp"
@ -24,6 +24,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal"> android:orientation="horizontal">
<ImageView <ImageView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -32,14 +33,15 @@
<net.osmand.plus.widgets.TextViewEx <net.osmand.plus.widgets.TextViewEx
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/osm_live_subscription"
android:layout_gravity="center" android:layout_gravity="center"
android:layout_marginLeft="10dp" android:layout_marginLeft="10dp"
android:text="@string/osm_live_subscription"
android:textColor="@color/color_white" android:textColor="@color/color_white"
android:textSize="@dimen/default_list_text_size" android:textSize="@dimen/default_list_text_size"
android:textStyle="bold" android:textStyle="bold"
app:typeface="@string/font_roboto_regular"/> app:typeface="@string/font_roboto_regular"/>
</LinearLayout> </LinearLayout>
<net.osmand.plus.widgets.TextViewEx <net.osmand.plus.widgets.TextViewEx
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -53,24 +55,25 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="bottom|right" android:layout_gravity="bottom|right"
android:gravity="right" android:layout_marginTop="16dp"
android:layout_marginTop="16dp"> android:gravity="right">
<Button <Button
android:id="@+id/read_more_button" android:id="@+id/read_more_button"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textColor="@color/color_white" android:background="@drawable/btn_round_shade"
android:background="@drawable/btn_round_transparent" android:text="@string/shared_string_read_more"
android:text="@string/shared_string_read_more"/> android:textColor="@color/color_white"/>
<Button <Button
android:id="@+id/subscription_button" android:id="@+id/subscription_button"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="32dp" android:layout_marginLeft="32dp"
android:textColor="@color/color_white" android:background="@drawable/btn_round_shade"
android:background="@drawable/btn_round_transparent" android:text="@string/osm_live_subscribe_btn"
android:text="@string/osm_live_subscribe_btn"/> android:textColor="@color/color_white"/>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
@ -85,31 +88,54 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:gravity="center_vertical"
android:minHeight="44dp"> android:minHeight="64dp"
android:orientation="horizontal">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<net.osmand.plus.widgets.TextViewEx <net.osmand.plus.widgets.TextViewEx
android:layout_width="120dp" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="16dp" android:layout_marginLeft="16dp"
android:layout_marginRight="16dp" android:layout_marginRight="16dp"
android:text="@string/osm_live_subscription" android:text="@string/shared_string_status"
android:textColor="?android:attr/textColorPrimary" android:textColor="?android:attr/textColorSecondary"
android:textSize="@dimen/default_desc_text_size" android:textSize="@dimen/default_desc_text_size"
app:typeface="@string/font_roboto_regular"/> app:typeface="@string/font_roboto_regular"/>
<net.osmand.plus.widgets.TextViewEx <net.osmand.plus.widgets.TextViewEx
android:layout_width="match_parent" android:id="@+id/statusTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="4dp" android:layout_marginBottom="4dp"
android:layout_marginLeft="16dp" android:layout_marginLeft="16dp"
android:layout_marginRight="16dp" android:layout_marginRight="16dp"
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
android:text="Active" android:text="@string/osm_live_active"
android:textColor="?android:attr/textColorPrimary" android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/default_desc_text_size" android:textSize="@dimen/default_list_text_size_large"
app:textAllCapsCompat="true"
app:typeface="@string/font_roboto_medium"/> app:typeface="@string/font_roboto_medium"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="right|center_vertical"
android:layout_marginRight="16dp">
<ImageView
android:id="@+id/statusIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_action_done"/>
</LinearLayout>
</LinearLayout> </LinearLayout>
<View <View
@ -121,7 +147,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:gravity="center"
android:minHeight="44dp"> android:minHeight="48dp">
<net.osmand.plus.widgets.TextViewEx <net.osmand.plus.widgets.TextViewEx
@ -129,21 +155,23 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="16dp" android:layout_marginLeft="16dp"
android:layout_marginRight="16dp" android:layout_marginRight="16dp"
android:text="Support country" android:text="@string/osm_live_support_region"
android:textColor="?android:attr/textColorPrimary" android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/default_desc_text_size" android:textSize="@dimen/default_list_text_size"
app:typeface="@string/font_roboto_regular"/> app:typeface="@string/font_roboto_regular"/>
<net.osmand.plus.widgets.TextViewEx <net.osmand.plus.widgets.TextViewEx
android:id="@+id/regionTextView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="4dp" android:layout_marginBottom="4dp"
android:layout_marginLeft="16dp" android:layout_marginLeft="16dp"
android:layout_marginRight="16dp" android:layout_marginRight="16dp"
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
android:gravity="right"
android:text="World" android:text="World"
android:textColor="?android:attr/textColorPrimary" android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/default_desc_text_size" android:textSize="@dimen/default_list_text_size"
app:typeface="@string/font_roboto_medium"/> app:typeface="@string/font_roboto_medium"/>
</LinearLayout> </LinearLayout>
@ -156,105 +184,20 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:gravity="right|center_vertical"
android:minHeight="44dp"> android:minHeight="48dp">
<Button
<net.osmand.plus.widgets.TextViewEx android:id="@+id/subscribeButton"
android:layout_width="120dp" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="16dp" android:layout_marginLeft="16dp"
android:layout_marginRight="16dp" android:layout_marginRight="6dp"
android:text="Email" android:background="?attr/selectableItemBackground"
android:textColor="?android:attr/textColorPrimary" android:paddingLeft="10dp"
android:textSize="@dimen/default_desc_text_size" android:paddingRight="10dp"
app:typeface="@string/font_roboto_regular"/> android:text="@string/osm_live_subscribe_btn"
android:textColor="?attr/color_dialog_buttons"/>
<net.osmand.plus.widgets.TextViewEx
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="4dp"
android:text="my@email.com"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/default_desc_text_size"
app:typeface="@string/font_roboto_medium"/>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/dashboard_divider"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:minHeight="44dp">
<net.osmand.plus.widgets.TextViewEx
android:layout_width="120dp"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:text="Visible Name"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/default_desc_text_size"
app:typeface="@string/font_roboto_regular"/>
<net.osmand.plus.widgets.TextViewEx
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="4dp"
android:text="None"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/default_desc_text_size"
app:typeface="@string/font_roboto_medium"/>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/dashboard_divider"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:minHeight="44dp">
<net.osmand.plus.widgets.TextViewEx
android:layout_width="120dp"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:text="Last pay"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/default_desc_text_size"
app:typeface="@string/font_roboto_regular"/>
<net.osmand.plus.widgets.TextViewEx
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="4dp"
android:text="25 Nov 2015"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/default_desc_text_size"
app:typeface="@string/font_roboto_medium"/>
</LinearLayout> </LinearLayout>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceListItemSmall"
android:textColor="?android:textColorPrimary"
android:gravity="center_vertical"
android:paddingStart="@dimen/list_content_padding"
android:paddingEnd="@dimen/list_content_padding"
android:minHeight="?android:attr/listPreferredItemHeightSmall" />

View file

@ -5,7 +5,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:gravity="center" android:gravity="center"
android:background="@android:drawable/list_selector_background"> android:background="?attr/expandable_list_item_background">
<TextView <TextView
android:text="@string/reports_for" android:text="@string/reports_for"

View file

@ -16,15 +16,16 @@
<ImageButton <ImageButton
android:id="@+id/closeButton" android:id="@+id/closeButton"
style="@style/Widget.AppCompat.Button.Borderless" style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="48dp" android:layout_width="52dp"
android:layout_height="48dp" android:layout_height="52dp"
android:src="@drawable/ic_action_remove_dark"/> android:src="@drawable/ic_action_remove_dark"/>
<net.osmand.plus.widgets.TextViewEx <net.osmand.plus.widgets.TextViewEx
android:id="@+id/titleTextView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="16dp" android:layout_marginLeft="16dp"
android:text="Subscription form" android:text="@string/osm_live_subscription"
android:textColor="@color/color_white" android:textColor="@color/color_white"
android:textSize="@dimen/default_list_text_size_large" android:textSize="@dimen/default_list_text_size_large"
android:textStyle="bold" android:textStyle="bold"
@ -34,6 +35,14 @@
<include layout="@layout/shadow_bottom"/> <include layout="@layout/shadow_bottom"/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -55,7 +64,8 @@
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:layout_marginLeft="16dp" android:layout_marginLeft="16dp"
android:layout_marginRight="16dp" android:layout_marginRight="16dp"
android:hint="E-mail address" android:paddingLeft="2dp"
android:hint="@string/shared_string_email_address"
android:inputType="textEmailAddress"/> android:inputType="textEmailAddress"/>
</LinearLayout> </LinearLayout>
@ -64,13 +74,13 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:paddingLeft="2dp"
android:layout_marginLeft="72dp" android:layout_marginLeft="72dp"
android:layout_marginRight="16dp" android:layout_marginRight="16dp"
android:text="We need it to provide you information about contributions" android:text="@string/osm_live_email_desc"
android:textColor="?android:attr/textColorSecondary"/> android:textColor="?android:attr/textColorSecondary"/>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -96,27 +106,31 @@
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:layout_marginLeft="16dp" android:layout_marginLeft="16dp"
android:layout_marginRight="16dp" android:layout_marginRight="16dp"
android:hint="Public Name" android:paddingLeft="2dp"
android:hint="@string/osm_live_user_public_name"
android:inputType="text"/> android:inputType="text"/>
</LinearLayout> </LinearLayout>
<CheckBox <CheckBox
android:id="@+id/hideUserNameCheckbox"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="12dp" android:layout_marginBottom="12dp"
android:layout_marginLeft="72dp" android:layout_marginLeft="72dp"
android:layout_marginRight="16dp" android:layout_marginRight="16dp"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:text="Don't show my name in reports" android:paddingLeft="2dp"
android:text="@string/osm_live_hide_user_name"
android:textColor="?android:attr/textColorPrimary"/> android:textColor="?android:attr/textColorPrimary"/>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minHeight="56dp"
android:layout_marginTop="4dp"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:layout_marginTop="4dp"
android:minHeight="56dp"
android:orientation="horizontal"> android:orientation="horizontal">
<ImageView <ImageView
@ -129,19 +143,36 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="8dp"
android:orientation="vertical"> android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="2dp"
android:textColor="?android:attr/textColorSecondary"
android:text="@string/osm_live_support_region"/>
<net.osmand.plus.widgets.AutoCompleteTextViewEx <net.osmand.plus.widgets.AutoCompleteTextViewEx
android:id="@+id/selectCountryEdit" android:id="@+id/selectCountryEdit"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="16dp" android:paddingLeft="2dp"
android:layout_marginRight="16dp" android:paddingRight="0dp"
android:layout_marginTop="4dp"
android:hint="Support region"
android:drawableRight="@drawable/ic_action_arrow_drop_down" android:drawableRight="@drawable/ic_action_arrow_drop_down"
android:editable="false"/> android:editable="false"
android:text="Ukraine"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="2dp"
android:layout_marginTop="8dp"
android:textColor="?android:attr/textColorSecondary"
android:text="@string/osm_live_region_desc"/>
</LinearLayout> </LinearLayout>
@ -150,11 +181,35 @@
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/editModeBottomView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:visibility="gone">
<Button
android:id="@+id/saveChangesButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="72dp"
android:layout_marginRight="16dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:textColor="@color/color_white"
android:background="@drawable/btn_round_blue"
android:text="@string/shared_string_save_changes"/>
</LinearLayout>
<LinearLayout
android:id="@+id/purchaseCard"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="8dp" android:layout_margin="8dp"
android:background="?attr/bg_card" android:background="?attr/bg_card"
android:orientation="vertical"> android:orientation="vertical"
android:visibility="visible">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -166,22 +221,21 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:text="Month cost" android:text="@string/osm_live_month_cost"
android:textColor="?android:attr/textColorPrimary" android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/default_list_text_size_large" android:textSize="@dimen/default_list_text_size_large"
android:textStyle="bold" android:textStyle="bold"/>
app:typeface="@string/font_roboto_regular"/>
<net.osmand.plus.widgets.TextViewEx <net.osmand.plus.widgets.TextViewEx
android:id="@+id/priceTextView" android:id="@+id/priceTextView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:gravity="right" android:gravity="right"
android:text="@string/osm_live_default_price" android:text="@string/osm_live_default_price"
android:textColor="?attr/color_dialog_buttons" android:textColor="?attr/color_dialog_buttons"
android:textSize="@dimen/default_list_text_size_large" android:textSize="@dimen/default_list_text_size_large"
android:textStyle="bold" android:textStyle="bold"/>
app:typeface="@string/font_roboto_regular"/>
</LinearLayout> </LinearLayout>
@ -190,10 +244,9 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="16dp" android:layout_marginLeft="16dp"
android:layout_marginRight="16dp" android:layout_marginRight="16dp"
android:text="Monthly payment" android:text="@string/osm_live_month_cost_desc"
android:textColor="?android:attr/textColorSecondary" android:textColor="?android:attr/textColorSecondary"
android:textSize="@dimen/default_desc_text_size" android:textSize="@dimen/default_desc_text_size"/>
app:typeface="@string/font_roboto_regular"/>
<View <View
android:layout_width="match_parent" android:layout_width="match_parent"
@ -215,10 +268,13 @@
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
android:paddingLeft="10dp" android:paddingLeft="10dp"
android:paddingRight="10dp" android:paddingRight="10dp"
android:text="Subscribe" android:text="@string/osm_live_subscribe_btn"
android:textColor="?attr/color_dialog_buttons"/> android:textColor="?attr/color_dialog_buttons"/>
</LinearLayout> </LinearLayout>
</LinearLayout>
</ScrollView>
</LinearLayout> </LinearLayout>

View file

@ -2057,4 +2057,6 @@
<string name="clear_updates_proposition_message">Можете да премахнете свалените актуализации и да възстановите оригиналната карта</string> <string name="clear_updates_proposition_message">Можете да премахнете свалените актуализации и да възстановите оригиналната карта</string>
<string name="add_time_span">Добавете времеви интервал</string> <string name="add_time_span">Добавете времеви интервал</string>
<string name="shared_string_remove">Премахване</string> <string name="shared_string_remove">Премахване</string>
<string name="rendering_attr_hideUnderground_name">Скрий подземните обекти</string>
<string name="shared_string_read_more">Прочети още</string>
</resources> </resources>

View file

@ -2092,4 +2092,7 @@
<string name="poi_protected_area">Àrea protegida</string> <string name="poi_protected_area">Àrea protegida</string>
<string name="poi_fair_trade_no">Comerç just: no</string> <string name="poi_fair_trade_no">Comerç just: no</string>
<string name="poi_fair_trade_yes">Comerç just: sí</string> <string name="poi_fair_trade_yes">Comerç just: sí</string>
<string name="poi_nuclear_explosion_purpose_industrial_application_earth_moving">Motiu de l\'explosió: aplicació industrial, moviment de terres</string>
<string name="poi_nuclear_explosion_time_utc">Hora de la explosió (UTC)</string>
<string name="poi_nuclear_explosion_hole">Cràter de l\'explosió</string>
</resources> </resources>

View file

@ -1917,9 +1917,12 @@ Per retornar a l\'estil habitual dels mapes d\'OsmAnd, només cal desactivar aqu
<string name="select_month_and_country">Seleccioneu el mes i el país</string> <string name="select_month_and_country">Seleccioneu el mes i el país</string>
<string name="number_of_contributors">Nombre de col·laboradors</string> <string name="number_of_contributors">Nombre de col·laboradors</string>
<string name="number_of_edits">Nombre d\'edicions</string> <string name="number_of_edits">Nombre d\'edicions</string>
<string name="reports_for">Informe per:</string> <string name="reports_for">Informe per</string>
<string name="shared_string_select">Selecciona</string> <string name="shared_string_select">Selecciona</string>
<string name="shared_string_remove">Esborra</string> <string name="shared_string_remove">Esborra</string>
<string name="clear_updates_proposition_message">Podeu esborrar les actualitzacions baixades i tornar a l\'edició original del mapa</string> <string name="clear_updates_proposition_message">Podeu esborrar les actualitzacions baixades i tornar a l\'edició original del mapa</string>
<string name="road_blocked">Via tallada</string> <string name="road_blocked">Via tallada</string>
<string name="rendering_attr_hideUnderground_name">Amaga elements soterrats</string>
<string name="data_is_not_available">No es disposa de dades</string>
<string name="shared_string_read_more">Llegeix més</string>
</resources> </resources>

View file

@ -1925,4 +1925,5 @@
<string name="number_of_edits">Počet úprav</string> <string name="number_of_edits">Počet úprav</string>
<string name="reports_for">Report za</string> <string name="reports_for">Report za</string>
<string name="rendering_attr_hideUnderground_name">Skrýt objekty v pozadí</string> <string name="rendering_attr_hideUnderground_name">Skrýt objekty v pozadí</string>
<string name="shared_string_email_address">Email</string>
</resources> </resources>

View file

@ -2678,19 +2678,19 @@
<string name="poi_nuclear_explosion_shot_name_en">Shot navnet (en)</string> <string name="poi_nuclear_explosion_shot_name_en">Shot navnet (en)</string>
<string name="poi_nuclear_explosion_series">Eksplosionsserie</string> <string name="poi_nuclear_explosion_series">Eksplosionsserie</string>
<string name="poi_nuclear_explosion_purpose_nuclear_weapons_related">Eksplosionsformål: atomvåben relateret</string> <string name="poi_nuclear_explosion_purpose_nuclear_weapons_related">Eksplosionsformål: atomvåben relateret</string>
<string name="poi_nuclear_explosion_purpose_weapons_effects">Eksplosionsformål: våben effekt</string> <string name="poi_nuclear_explosion_purpose_weapons_effects">Eksplosionsformål: våbeneffekt</string>
<string name="poi_nuclear_explosion_purpose_safety_experiment">Eksplosionsformål: sikkerhedseksperiment</string> <string name="poi_nuclear_explosion_purpose_safety_experiment">Eksplosionsformål: sikkerhedseksperiment</string>
<string name="poi_nuclear_explosion_purpose_research_for_peaceful_applications">Eksplosionsformål: forskning for fredelige anvendelser</string> <string name="poi_nuclear_explosion_purpose_research_for_peaceful_applications">Eksplosionsformål: forskning for fredelige anvendelser</string>
<string name="poi_nuclear_explosion_purpose_fundamental_science">Eksplosionsformål: grundlæggende videnskab</string> <string name="poi_nuclear_explosion_purpose_fundamental_science">Eksplosionsformål: grundlæggende videnskab</string>
<string name="poi_nuclear_explosion_purpose_industrial_application_cavity_excavation">Eksplosionsformål: industriel anvendelse, hulrum udgravning</string> <string name="poi_nuclear_explosion_purpose_industrial_application_cavity_excavation">Eksplosionsformål: industriel anvendelse, udgravning af hulrum</string>
<string name="poi_nuclear_explosion_purpose_industrial_application_seismic_sounding">Eksplosionsformål: industriel anvendelse, seismiske pejlinger</string> <string name="poi_nuclear_explosion_purpose_industrial_application_seismic_sounding">Eksplosionsformål: industriel anvendelse, seismiske pejlinger</string>
<string name="poi_nuclear_explosion_purpose_industrial_application_oil_stimulation">Eksplosionsformål: industriel anvendelse, olie stimulation</string> <string name="poi_nuclear_explosion_purpose_industrial_application_oil_stimulation">Eksplosionsformål: industriel anvendelse, oliestimulering</string>
<string name="poi_nuclear_explosion_purpose_industrial_application">Eksplosionsformål: industriel anvendelse</string> <string name="poi_nuclear_explosion_purpose_industrial_application">Eksplosionsformål: industriel anvendelse</string>
<string name="poi_nuclear_explosion_purpose_industrial_application_earth_moving">Eksplosionsformål: industriel anvendelse, jordflytning</string> <string name="poi_nuclear_explosion_purpose_industrial_application_earth_moving">Eksplosionsformål: industriel anvendelse, jordflytning</string>
<string name="poi_nuclear_explosion_time_utc">Eksplosionstidpunkt (UTC)</string> <string name="poi_nuclear_explosion_time_utc">Eksplosionstidpunkt (UTC)</string>
<string name="poi_nuclear_explosion_yield">Eksplosion udbytte</string> <string name="poi_nuclear_explosion_yield">Eksplosion udbytte</string>
<string name="poi_nuclear_explosion_hole">Eksplosion hul</string> <string name="poi_nuclear_explosion_hole">Eksplosionshul</string>
<string name="poi_nuclear_explosion_height_of_burst">Højden af eksplosionen</string> <string name="poi_nuclear_explosion_height_of_burst">Eksplosionenshøjde</string>
<string name="poi_nuclear_explosion_ground_zero_altitude">Jordoverfladenshøjde</string> <string name="poi_nuclear_explosion_ground_zero_altitude">Jordoverfladenshøjde</string>
<string name="poi_nuclear_explosion_body_wave_magnitude">bølge størrelsesorden</string> <string name="poi_nuclear_explosion_body_wave_magnitude">bølge størrelsesorden</string>
<string name="poi_nuclear_explosion_salvo_first_detonation">Eksplosionssalve: første detonation af en salvetest</string> <string name="poi_nuclear_explosion_salvo_first_detonation">Eksplosionssalve: første detonation af en salvetest</string>
@ -2699,4 +2699,8 @@
<string name="poi_nuclear_explosion_warhead">Eksplosion: sprænghoved</string> <string name="poi_nuclear_explosion_warhead">Eksplosion: sprænghoved</string>
<string name="poi_nuclear_explosion_device">Eksplosion: enhed</string> <string name="poi_nuclear_explosion_device">Eksplosion: enhed</string>
<string name="poi_addr_housename">Huset navn</string>
<string name="poi_religion">Religiøse varer</string>
</resources> </resources>

View file

@ -1535,7 +1535,7 @@
<string name="rendering_attr_showAccess_name">Vis adgangsbegrænsninger</string> <string name="rendering_attr_showAccess_name">Vis adgangsbegrænsninger</string>
<string name="rendering_attr_showSurfaceGrade_name">Vis vejkvaliteten</string> <string name="rendering_attr_showSurfaceGrade_name">Vis vejkvaliteten</string>
<string name="rendering_attr_showSurfaces_name">Vis vejbelægning</string> <string name="rendering_attr_showSurfaces_name">Vis vejbelægning</string>
<string name="rendering_attr_showCycleRoutes_name">Vis cykelruter</string> <string name="rendering_attr_showCycleRoutes_name">Cykelruter</string>
<string name="local_map_names">Lokale navne</string> <string name="local_map_names">Lokale navne</string>
<string name="lang_sw">Swahili</string> <string name="lang_sw">Swahili</string>
<string name="lang_he">Hebræisk</string> <string name="lang_he">Hebræisk</string>
@ -2237,4 +2237,34 @@
<string name="shared_string_remove">Fjern</string> <string name="shared_string_remove">Fjern</string>
<string name="data_is_not_available">Data er ikke tilgængelig</string> <string name="data_is_not_available">Data er ikke tilgængelig</string>
<string name="rendering_attr_hideUnderground_name">Skjul underjordiske objekter</string> <string name="rendering_attr_hideUnderground_name">Skjul underjordiske objekter</string>
<string name="shared_string_read_more">Læs mere</string>
<string name="osmand_plus_extended_description_4000_chars_v2">" OsmAnd Maps &amp; Navigation - lav ruter og find steder på kortet uden en internetforbindelse. Hent et kort for et land, før du går på en rejse til at finde steder og ruter i et ukendt område.
\n\nGrundlæggende indstillinger:
\n• Detaljerede kort over 200 lande
\n• Offline navigator med stemmevejledning\n• Adressesøgning i offline mode
\n• Info om steder på kortet: steder af interesse, caféer, parkeringspladser, butikker
\n• Tlføj steder til foretrukne
\n• Kørsel-, cykling- og fodgængernavigation
\n\nYderligere indstillinger:
\n• Mulighed for at vise og optage GPX spor\n• Wikipedia beskrivelse af IP\n• Tilføj foto-, audio-, og videonoter til kort
\n• Dag- og natkort tilstand for mere praktisk kørsel
\n• Oplysninger om offentlig transport og stoppesteder\n• Cykel- og gangstier
\n• Vandreruter for turisme over hele verden
\n• Online kort fra adskillige kilder
\n• Info om vej- og fortovskvalitet og gadebelysning
\n• Tilføj, rediger og sletning af IP (for OpenStreetMap.org brugere)
\n• OsMo - live overvågning af andre enheder
\n\nFå pålidelig navigation i dit land - enten det er Frankrig, Tyskland, Mexico, Storbritannien, Spanien, Holland, USA, Rusland, Brasilien eller et andet land.
\n\nUdvidelser:
\n• kontur kort og terræn skygge https://goo.gl/7mojP8 højdekurver data og terræn visualisering tilføjet til grundlæggende OsmAnd kortet.
\n• Skikort info om skipister, langrendsløjper, tovbaner og skilifterne-https://goo.gl/pX6DxJ.
\n• Søkort https://goo.gl/0hEdxm særlig kortstil til visning af nautiske navigation tegn for arteriel og offshore marine navigation.
\n• Parkeringposition https://goo.gl/6JxQXF hjælper dig med at markere placeringen af din parkerede bil og se hvor meget tid tilbage, hvis parkering er tidsbegrænset.
\n\nFølg med på!
\nTwitter: https://twitter.com/osmandapp
\nFacebook: https://www.facebook.com/osmandapp\nHjemmeside: http://osmand.net
\n\nEr der brug for hjælp med OsmAnd, kontakt da supportteamet: support@osmand.net. "</string>
<string name="shared_string_email_address">Email adresse</string>
<string name="shared_string_status">Status</string>
<string name="shared_string_save_changes">Gem ændringer</string>
</resources> </resources>

View file

@ -2010,4 +2010,5 @@
<string name="clear_updates_proposition_message">Kartenaktualisierungen können gelöscht werden um zur orginal Karte zurückzukehren</string> <string name="clear_updates_proposition_message">Kartenaktualisierungen können gelöscht werden um zur orginal Karte zurückzukehren</string>
<string name="data_is_not_available">Daten nicht verfügbar</string> <string name="data_is_not_available">Daten nicht verfügbar</string>
<string name="rendering_attr_hideUnderground_name">Unterirdische Objekt verbergen</string> <string name="rendering_attr_hideUnderground_name">Unterirdische Objekt verbergen</string>
<string name="shared_string_read_more">Mehr lesen</string>
</resources> </resources>

View file

@ -2671,4 +2671,7 @@
<string name="poi_nuclear_explosion_yield">Rendimiento de la explosión</string> <string name="poi_nuclear_explosion_yield">Rendimiento de la explosión</string>
<string name="poi_nuclear_explosion_salvo_first_detonation">Explosión de salva: Primera detonación de prueba</string> <string name="poi_nuclear_explosion_salvo_first_detonation">Explosión de salva: Primera detonación de prueba</string>
<string name="poi_nuclear_explosion_salvo_second_or_later_detonation">Explosión de salva: Segunda o posterior detonación de prueba</string> <string name="poi_nuclear_explosion_salvo_second_or_later_detonation">Explosión de salva: Segunda o posterior detonación de prueba</string>
</resources> <string name="poi_addr_housename">Nombre de casa</string>
<string name="poi_religion">Artículos religiosos</string>
</resources>

View file

@ -2057,4 +2057,23 @@
<string name="shared_string_remove">Quitar</string> <string name="shared_string_remove">Quitar</string>
<string name="data_is_not_available">Datos no disponibles</string> <string name="data_is_not_available">Datos no disponibles</string>
<string name="rendering_attr_hideUnderground_name">Ocultar objetos subterráneos</string> <string name="rendering_attr_hideUnderground_name">Ocultar objetos subterráneos</string>
<string name="shared_string_read_more">Leer más</string>
<string name="osmand_plus_extended_description_4000_chars_v2">" OsmAnd Maps &amp; Navigation (mapas y navegación de OsmAnd), crea rutas y encuentra ubicaciones en el mapa sin conectar con Internet. Descarga el mapa de un país antes de viajar para encontrar lugares y rutas en áreas desconocidas.\n\nOpciones básicas:\n• Mapas detallados en 200 países\n• Navegación sin conexión con avisos de voz\n• Búsqueda de direcciones en modo sin conexión\n• Información sobre ubicaciones en el mapa: lugares de interés, cafés, estacionamientos, tiendas\n• Añada sus ubicaciones a los favoritos\n• Navegación en automóvil, bicicleta y caminando\n\nOpciones adicionales:\n• Capacidad para ver y grabar trazas GPX\n• Descripción de Wikipedia en PDI
\n• Añada notas de fotos, audios y videos en el mapa
\n• Modo de mapa Día y Noche para una conducción más cómoda\n• Información sobre rutas y paradas de transporte público
\n• Rutas de ciclovías y senderos
\n• Rutas de senderos para el turismo en todo el mundo\n• Mapas en línea de numerosas fuentes
\n• Información sobre la calidad del pavimento y el alumbrado público
\n• Añada, edite y quite PDI (para usuarios de OpenStreetMap.org)
\n• OsMo - monitorice en vivo otros dispositivos.
\n• Obtén un navegador confiable en su país - ya sea Francia, Alemania, México, Reino Unido, España, los Países Bajos, EUA, Rusia, Brasil o cualquier otro estado.
\n\nComplementos:
\n• Mapas de curvas de nivel y sombreado del terreno: https://goo.gl/7mojP8 Datos de curvas de nivel y visualización del terreno se agrega al mapa básico de OsmAnd.
\n• Mapa invernal: https://goo.gl/pX6DxJ Información sobre pistas de esquí, pistas de esquí de fondo, teleféricos y telesillas.
\n• Mapa náutico: https://goo.gl/0hEdxm Estilo especial del mapa para la visualización de señales de navegación náutica y costera.
\n• Estacionamiento: https://goo.gl/6JxQXF Ayuda a marcar la ubicación de su vehículo estacionado y ver cuánto tiempo falta si el estacionamiento es limitado.
\n\n¡Manténgase al tanto!\nTwitter: https://twitter.com/osmandapp
\nFacebook: https://www.facebook.com/osmandapp
\nSitio web: http://osmand.net
\nSi necesita ayuda con la aplicación OsmAnd, contacte con nuestro equipo de soporte: support@osmand.net. "</string>
</resources> </resources>

View file

@ -2665,4 +2665,7 @@
<string name="poi_nuclear_explosion_warhead">Explosión: ojiva</string> <string name="poi_nuclear_explosion_warhead">Explosión: ojiva</string>
<string name="poi_nuclear_explosion_device">Explosión: dispositivo</string> <string name="poi_nuclear_explosion_device">Explosión: dispositivo</string>
<string name="poi_addr_housename">Nombre de la casa</string>
<string name="poi_religion">Artículos religiosos</string>
</resources> </resources>

View file

@ -2056,4 +2056,7 @@
<string name="reports_for">Informe para</string> <string name="reports_for">Informe para</string>
<string name="rendering_attr_hideUnderground_name">Ocultar objetos subterráneos</string> <string name="rendering_attr_hideUnderground_name">Ocultar objetos subterráneos</string>
<string name="data_is_not_available">No hay datos disponibles</string> <string name="data_is_not_available">No hay datos disponibles</string>
<string name="shared_string_read_more">Leer más</string>
<string name="osmand_plus_extended_description_4000_chars_v2">" OsmAnd Mapas y Navegación - prepara rutas y encuentra ubicaciones en el mapa sin una conexión a Internet. Descarga un mapa de un país antes de que viajes para encontrar sitios y rutas en un área que no te sea familiar. Opciones básicas: • Mapas detallados de 200 países • Navegador fuera de línea con avisos de voz • Búsqueda de direcciones en modo sin conexión • Información sobre ubicaciones en el mapa: sitios de interés, cafeterías, aparcamientos, tiendas • Añadir ubicaciones a tus favoritos • Opciones adicionales de navegación en modos Conducción, Ciclismo y Pedestre : • Capacidad de ver y grabar trazas GPX • Descripciones de Wikipedia de los PDI • Añadir notas de foto, audio y vídeo al mapa • Modos de mapa Día y Noche para una conducción más conveniente • Información sobre rutas y paradas de transporte público • Vías ciclistas y caminos • Vías de senderismo para hacer turismo en todo el mundo • Mapas en línea de numerosas fuentes • Información sobre calidad del pavimento e iluminación de las y las calles • Añadir, editar y eliminar PDI (para usuarios deOpenstreetmap.org) • OsMo - monitorización de directo de otros dispositivos Consigue un explorador fiable en tu país - ya sea Francia, Alemania, México, Reino Unido, España, Holanda, EE.UU., Rusia, Brasil o cualquiera otro estado. Extras: • Mapas de contorno y sombreado del terreno https://goo.gl/7mojP8 Datos de curvas de nivel y visualización de terreno añadidos al mapa básico OsmAnd . • Mapas de esquí https://goo.gl/pX6DxJ La información sobre pistas de esquí, pistas de esquí de fondo, telecabinas y ascensores de esquí. • Mapa náutico https://goo.gl/0hEdxm Estilo de mapa especial para ver señales de navegación náutica para arteriales y navegación costera. • Posición de aparcamiento https://goo.gl/6jxQXF Te ayuda a marcar la ubicación de tu vehículo aparcado y ver cuánto tiempo queda si el aparcamiento es por tiempo limitado. ¡Mantente informado! Twitter: https://twitter.com/osmandapp Facebook: https://www.facebook.com/osmandapp Sitio: http://osmand.net Si necesitas ayuda con la aplicación OsmAnd, por favor contacta con nuestro equipo de soporte: support@osmand.net. "</string>
<string name="shared_string_email_address">Dirección de correo electrónico</string>
</resources> </resources>

View file

@ -2122,4 +2122,9 @@
<string name="road_blocked">Route barrée</string> <string name="road_blocked">Route barrée</string>
<string name="add_time_span">Ajouter un laps de temps</string> <string name="add_time_span">Ajouter un laps de temps</string>
<string name="data_is_not_available">Donnée indisponible</string> <string name="data_is_not_available">Donnée indisponible</string>
<string name="rendering_attr_hideUnderground_name">Cacher les objets souterrains</string>
<string name="shared_string_read_more">Lire la suite</string>
<string name="shared_string_email_address">Adresse email</string>
<string name="shared_string_status">Statut</string>
<string name="shared_string_save_changes">Enregistrer les modifications</string>
</resources> </resources>

View file

@ -2143,10 +2143,13 @@
<string name="number_of_edits">Numero di modifiche</string> <string name="number_of_edits">Numero di modifiche</string>
<string name="number_of_contributors">Numero di contributori</string> <string name="number_of_contributors">Numero di contributori</string>
<string name="shared_string_select">Scegli</string> <string name="shared_string_select">Scegli</string>
<string name="reports_for">Rapporto per:</string> <string name="reports_for">Rapporto per</string>
<string name="shared_string_remove">Rimuovi</string> <string name="shared_string_remove">Rimuovi</string>
<string name="clear_updates_proposition_message">Puoi eliminare gli aggiornamenti scaricati e avere la mappa originale</string> <string name="clear_updates_proposition_message">Puoi eliminare gli aggiornamenti scaricati e avere la mappa originale</string>
<string name="road_blocked">Strada bloccata</string> <string name="road_blocked">Strada bloccata</string>
<string name="add_time_span">Aggiungi durata</string> <string name="add_time_span">Aggiungi durata</string>
<string name="data_is_not_available">Il dato non è disponibile</string> <string name="data_is_not_available">Il dato non è disponibile</string>
<string name="rendering_attr_hideUnderground_name">Nascondi gli oggetti sotterranei</string>
<string name="shared_string_read_more">Approfondisci</string>
<string name="shared_string_email_address">Indirizzo email</string>
</resources> </resources>

View file

@ -2175,4 +2175,5 @@
<string name="reports_for">Verslag voor:</string> <string name="reports_for">Verslag voor:</string>
<string name="data_is_not_available">Gegevens niet beschikbaar</string> <string name="data_is_not_available">Gegevens niet beschikbaar</string>
<string name="rendering_attr_hideUnderground_name">Verberg ondergrondse objecten</string> <string name="rendering_attr_hideUnderground_name">Verberg ondergrondse objecten</string>
<string name="shared_string_read_more">Lees meer...</string>
</resources> </resources>

View file

@ -303,7 +303,7 @@
<string name="error_occurred_loading_gpx">Wystąpił błąd podczas wczytywania GPX</string> <string name="error_occurred_loading_gpx">Wystąpił błąd podczas wczytywania GPX</string>
<string name="send_report">Wyślij zgłoszenie</string> <string name="send_report">Wyślij zgłoszenie</string>
<string name="none_region_found">Brak danych offline na karcie SD. Pobierz je z Internetu.</string> <string name="none_region_found">Brak danych offline na karcie SD. Proszę pobrać je z sieci.</string>
<string name="poi_namefinder_query_empty">Wprowadź zapytanie by znaleźć POI</string> <string name="poi_namefinder_query_empty">Wprowadź zapytanie by znaleźć POI</string>
<string name="any_poi">Jakiekolwiek</string> <string name="any_poi">Jakiekolwiek</string>
<string name="layer_transport_route">Trasa transportu publicznego</string> <string name="layer_transport_route">Trasa transportu publicznego</string>
@ -2069,7 +2069,7 @@
<string name="updates_size">Aktualizacje: %s</string> <string name="updates_size">Aktualizacje: %s</string>
<string name="last_map_change">Ostatnia zmiana mapy: %s</string> <string name="last_map_change">Ostatnia zmiana mapy: %s</string>
<string name="rec_split_clip_length">Długość nagrania</string> <string name="rec_split_clip_length">Długość nagrania</string>
<string name="shared_string_not_selected">Nie wybrane</string> <string name="shared_string_not_selected">Nie wybrany</string>
<string name="item_removed">Pozycja usunięta</string> <string name="item_removed">Pozycja usunięta</string>
<string name="n_items_removed">Pozycje usunięta</string> <string name="n_items_removed">Pozycje usunięta</string>
<string name="shared_string_undo_all">COFNIJ WSZYSTKO</string> <string name="shared_string_undo_all">COFNIJ WSZYSTKO</string>
@ -2090,4 +2090,6 @@
<string name="number_of_edits">Liczba edycji</string> <string name="number_of_edits">Liczba edycji</string>
<string name="reports_for">Raport dla</string> <string name="reports_for">Raport dla</string>
<string name="number_of_contributors">Liczba współtwórców</string> <string name="number_of_contributors">Liczba współtwórców</string>
<string name="shared_string_read_more">Przeczytaj więcej</string>
<string name="rendering_attr_hideUnderground_name">Ukrycie podziemnych obiektów</string>
</resources> </resources>

View file

@ -2626,4 +2626,48 @@
<string name="poi_protection_object_habitat">Objeto de proteção: habitat</string> <string name="poi_protection_object_habitat">Objeto de proteção: habitat</string>
<string name="poi_protection_object_water">Objeto de proteção: água</string> <string name="poi_protection_object_water">Objeto de proteção: água</string>
<string name="poi_nuclear_explosion_country">Explosão: país</string>
<string name="poi_nuclear_explosion_site">Explosão: local</string>
<string name="poi_nuclear_explosion_type_underground_shaft">Tipo de explosão: subterrânea, escavação</string>
<string name="poi_nuclear_explosion_type_underground_tunnel">Tipo de explosão: subterrânea, túnel</string>
<string name="poi_nuclear_explosion_type_atmospheric">Tipo de explosão: atmosférica</string>
<string name="poi_nuclear_explosion_type_atmospheric_airdrop">Tipo de explosão: atmosférica, lançamento aéreo</string>
<string name="poi_nuclear_explosion_type_atmospheric_surface_tower">Tipo de explosão: atmosférica, superfície, torre</string>
<string name="poi_nuclear_explosion_type_atmospheric_balloon">Tipo de explosão: atmosférica, balão</string>
<string name="poi_nuclear_explosion_type_atmospheric_atmospheric_surface">Tipo de explosão: atmosférica, superfície</string>
<string name="poi_nuclear_explosion_type_atmospheric_water_surface_barge">Tipo de explosão: atmosférica, superfície d\'água, barca</string>
<string name="poi_nuclear_explosion_type_underground">Tipo de explosão: subterrânea</string>
<string name="poi_nuclear_explosion_type_cratering_burst">Tipo de explosão: erupção de cratera (subsolo raso)</string>
<string name="poi_nuclear_explosion_type_atmospheric_rocket_or_missile">Tipo de explosão: atmosférica, foguete ou míssil</string>
<string name="poi_nuclear_explosion_type_space">Tipo de explosão: espacial (mais de 80 km de altitude)</string>
<string name="poi_nuclear_explosion_type_underwater">Tipo de explosão: subaquática</string>
<string name="poi_nuclear_explosion_date_utc">Data da explosão (UTC)</string>
<string name="poi_nuclear_explosion_shot_name_en">Nome do disparo (en)</string>
<string name="poi_nuclear_explosion_series">Série de explosões</string>
<string name="poi_nuclear_explosion_purpose_nuclear_weapons_related">Propósito da explosão: relacionado com armas nucleares</string>
<string name="poi_nuclear_explosion_purpose_weapons_effects">Propósito da explosão: efeito das armas</string>
<string name="poi_nuclear_explosion_purpose_safety_experiment">Propósito da explosão: experimento de segurança</string>
<string name="poi_nuclear_explosion_purpose_research_for_peaceful_applications">Propósito da explosão: investigação para aplicações pacíficas</string>
<string name="poi_nuclear_explosion_purpose_fundamental_science">Propósito da explosão: ciência básica</string>
<string name="poi_nuclear_explosion_purpose_industrial_application_cavity_excavation">Propósito da explosão: aplicação industrial, escavação de cavidade</string>
<string name="poi_nuclear_explosion_purpose_industrial_application_seismic_sounding">Propósito da explosão: aplicação industrial, sondagem sísmica</string>
<string name="poi_nuclear_explosion_purpose_industrial_application_oil_stimulation">Propósito da explosão: aplicação industrial, estimulação de petróleo</string>
<string name="poi_nuclear_explosion_purpose_industrial_application">Propósito da explosão: aplicação industrial</string>
<string name="poi_nuclear_explosion_purpose_industrial_application_earth_moving">Propósito da explosão: aplicação industrial, movimentação de terra</string>
<string name="poi_nuclear_explosion_time_utc">Hora da explosão (UTC)</string>
<string name="poi_nuclear_explosion_yield">Produto da explosão</string>
<string name="poi_nuclear_explosion_hole">Buraco da explosão</string>
<string name="poi_nuclear_explosion_height_of_burst">Altura da erupção</string>
<string name="poi_nuclear_explosion_ground_zero_altitude">Altitude do marco zero</string>
<string name="poi_nuclear_explosion_body_wave_magnitude">Magnitude da conda do corpo</string>
<string name="poi_nuclear_explosion_salvo_first_detonation">Salva de explosão: primeira detonação de um teste de salva</string>
<string name="poi_nuclear_explosion_salvo_second_or_later_detonation">Salva de explosão: segunda ou posterior detonação de um teste de salva</string>
<string name="poi_nuclear_explosion_crater_diameter">Explosão: diâmetro de cratera</string>
<string name="poi_nuclear_explosion_warhead">Explosão: ogiva</string>
<string name="poi_nuclear_explosion_device">Explosão: dispositivo</string>
<string name="poi_addr_housename">Complemento</string>
<string name="poi_religion">Artigos religiosos</string>
</resources> </resources>

File diff suppressed because one or more lines are too long

View file

@ -2640,4 +2640,7 @@
<string name="poi_nuclear_explosion_warhead">Боеголовка</string> <string name="poi_nuclear_explosion_warhead">Боеголовка</string>
<string name="poi_nuclear_explosion_device">Устройство</string> <string name="poi_nuclear_explosion_device">Устройство</string>
<string name="poi_addr_housename">Название дома</string>
<string name="poi_religion">Религиозный магазин</string>
</resources> </resources>

View file

@ -714,7 +714,7 @@
<string name="left_side_navigation">Ghia a manca</string> <string name="left_side_navigation">Ghia a manca</string>
<string name="left_side_navigation_descr">Abìlita in sos istados in ue si ghiat mantenende sa manca</string> <string name="left_side_navigation_descr">Abìlita in sos istados in ue si ghiat mantenende sa manca</string>
<string name="unknown_from_location">Sa positzione de tzucada no est istada galu determinata</string> <string name="unknown_from_location">Sa positzione de tzucada no est istada galu determinata</string>
<string name="unknown_location">Positzione calu non connota</string> <string name="unknown_location">Positzione galu disconnota</string>
<string name="modify_transparency">Muda sa trasparèntzia (0 - trasparente, 255 - annapau)</string> <string name="modify_transparency">Muda sa trasparèntzia (0 - trasparente, 255 - annapau)</string>
<string name="confirm_interrupt_download">Boles firmare sisgarrigamentu de sos documentos?</string> <string name="confirm_interrupt_download">Boles firmare sisgarrigamentu de sos documentos?</string>
<string name="local_indexes_cat_tile">Mapas in lìnia e in cache a tasseddos</string> <string name="local_indexes_cat_tile">Mapas in lìnia e in cache a tasseddos</string>
@ -2094,4 +2094,5 @@
<string name="road_blocked">Àndala blocada</string> <string name="road_blocked">Àndala blocada</string>
<string name="shared_string_select">Ischerta</string> <string name="shared_string_select">Ischerta</string>
<string name="rendering_attr_hideUnderground_name">Cua ogetos suta de terra</string> <string name="rendering_attr_hideUnderground_name">Cua ogetos suta de terra</string>
<string name="shared_string_read_more">Leghe àteru</string>
</resources> </resources>

View file

@ -2076,4 +2076,7 @@
<string name="road_blocked">Blockerad väg</string> <string name="road_blocked">Blockerad väg</string>
<string name="data_is_not_available">Inga data tillgängliga</string> <string name="data_is_not_available">Inga data tillgängliga</string>
<string name="rendering_attr_hideUnderground_name">Dölj underjordiska objekt</string> <string name="rendering_attr_hideUnderground_name">Dölj underjordiska objekt</string>
<string name="shared_string_read_more">Läs mer</string>
<string name="rec_split_storage_size">Lagringsutrymme</string>
<string name="shared_string_email_address">E-postadress</string>
</resources> </resources>

File diff suppressed because one or more lines are too long

View file

@ -2641,4 +2641,7 @@
<string name="poi_nuclear_explosion_warhead">Explosion: warhead</string> <string name="poi_nuclear_explosion_warhead">Explosion: warhead</string>
<string name="poi_nuclear_explosion_device">Explosion: device</string> <string name="poi_nuclear_explosion_device">Explosion: device</string>
<string name="poi_addr_housename">House name</string>
<string name="poi_religion">Religious goods</string>
</resources> </resources>

View file

@ -9,6 +9,9 @@
3. All your modified/created strings are in the top of the file (to make easier find what\'s translated). 3. All your modified/created strings are in the top of the file (to make easier find what\'s translated).
PLEASE: Have a look at http://code.google.com/p/osmand/wiki/UIConsistency, it may really improve your and our work :-) Thx - Hardy PLEASE: Have a look at http://code.google.com/p/osmand/wiki/UIConsistency, it may really improve your and our work :-) Thx - Hardy
--> -->
<string name="shared_string_status">Status</string>
<string name="shared_string_save_changes">Save changes</string>
<string name="shared_string_email_address">E-mail address</string>
<string name="rendering_attr_hideUnderground_name">Hide underground objects</string> <string name="rendering_attr_hideUnderground_name">Hide underground objects</string>
<string name="data_is_not_available">Data is not available</string> <string name="data_is_not_available">Data is not available</string>
<string name="shared_string_remove">Remove</string> <string name="shared_string_remove">Remove</string>
@ -1287,6 +1290,53 @@ Approximate map coverage and quality:
List of countries supported (basically world wide!): List of countries supported (basically world wide!):
Afghanistan, Albania, Algeria, Andorra, Angola, Anguilla, Antigua and Barbuda, Argentina, Armenia, Aruba, Australia, Austria, Azerbaijan, Bahamas, Bahrain, Bangladesh, Barbados, Belarus, Belgium, Belize, Benin, Bermuda, Bhutan, Bolivia, Bonaire, Bosnia and Herzegovina, Botswana, Brazil, British Virgin Islands, Brunei, Bulgaria, Burkina Faso, Burundi, Cambodia, Cameroon, Canada, Cape Verde, Central African Republic, Chad, Chile, China, Colombia, Comoros, Congo, Costa Rica, Ivory Coast, Croatia, Cuba, Curaçao, Cyprus, Czech Republic, Denmark, Djibouti, Dominica, Dominican Republic, Ecuador, Egypt, El Salvador, Equatorial Guinea, Eritrea, Estonia, Ethiopia, Fiji, Finland, France, French Guiana, French Polynesia, Gabon, Gambia, Georgia, Germany, Ghana, Gibraltar, Greece, Greenland, Grenada, Guadeloupe, Guam, Guatemala, Guernsey, Guinea, Guinea-Bissau, Guyana, Haiti, Vatican, Honduras, Hong Kong, Hungary, Iceland, India, Indonesia, Iran, Iraq, Ireland, Isle of Man, Israel, Italy, Jamaica, Japan, Jersey, Jordan, Kazakhstan, Kenya, Kiribati, North Korea and South Korea, Kuwait, Kyrgyzstan, Laos, Latvia, Lebanon, Lesotho, Liberia, Libya, Liechtenstein, Lithuania, Luxembourg, Macao, Macedonia, Madagascar, Malawi, Malaysia, Maldives, Mali, Malta, Martinique, Mauritania, Mauritius, Mayotte, Mexico, Micronesia, Moldova, Monaco, Mongolia, Montenegro, Montserrat, Morocco, Mozambique, Myanmar, Namibia, Nauru, Nepal, Netherlands, Netherlands Antilles, New Caledonia, New Zealand, Nicaragua, Niger, Nigeria, Norway, Oman, Pakistan, Palau, Palestinian Territory, Panama, Papua New Guinea, Paraguay, Peru, Philippines, Poland, Portugal, Puerto Rico, Qatar, Romania, Russia, Rwanda, Saint Barthelemy, Saint Helena, Saint Kitts and Nevis, Saint Lucia, Saint Martin, Saint Pierre and Miquelon, Saint Vincent and the Grenadines, Samoa, San Marino, Saudi Arabia, Senegal, Serbia, Seychelles, Sierra Leone, Singapore, Slovakia, Slovenia, Somalia, South Africa, South Georgia, South Sudan, Spain, Sri Lanka, Sudan, Suriname, Swaziland, Sweden, Switzerland, Syria, Taiwan, Tajikistan, Tanzania, Thailand, Timor-Leste, Togo, Tokelau, Tonga, Trinidad and Tobago, Tunisia, Turkey, Turkmenistan, Tuvalu, Uganda, Ukraine, United Arab Emirates, United Kingdom (UK), United States of America (USA), Uruguay, Uzbekistan, Vanuatu, Venezuela, Vietnam, Wallis and Futuna, Western Sahara, Yemen, Zambia, Zimbabwe. Afghanistan, Albania, Algeria, Andorra, Angola, Anguilla, Antigua and Barbuda, Argentina, Armenia, Aruba, Australia, Austria, Azerbaijan, Bahamas, Bahrain, Bangladesh, Barbados, Belarus, Belgium, Belize, Benin, Bermuda, Bhutan, Bolivia, Bonaire, Bosnia and Herzegovina, Botswana, Brazil, British Virgin Islands, Brunei, Bulgaria, Burkina Faso, Burundi, Cambodia, Cameroon, Canada, Cape Verde, Central African Republic, Chad, Chile, China, Colombia, Comoros, Congo, Costa Rica, Ivory Coast, Croatia, Cuba, Curaçao, Cyprus, Czech Republic, Denmark, Djibouti, Dominica, Dominican Republic, Ecuador, Egypt, El Salvador, Equatorial Guinea, Eritrea, Estonia, Ethiopia, Fiji, Finland, France, French Guiana, French Polynesia, Gabon, Gambia, Georgia, Germany, Ghana, Gibraltar, Greece, Greenland, Grenada, Guadeloupe, Guam, Guatemala, Guernsey, Guinea, Guinea-Bissau, Guyana, Haiti, Vatican, Honduras, Hong Kong, Hungary, Iceland, India, Indonesia, Iran, Iraq, Ireland, Isle of Man, Israel, Italy, Jamaica, Japan, Jersey, Jordan, Kazakhstan, Kenya, Kiribati, North Korea and South Korea, Kuwait, Kyrgyzstan, Laos, Latvia, Lebanon, Lesotho, Liberia, Libya, Liechtenstein, Lithuania, Luxembourg, Macao, Macedonia, Madagascar, Malawi, Malaysia, Maldives, Mali, Malta, Martinique, Mauritania, Mauritius, Mayotte, Mexico, Micronesia, Moldova, Monaco, Mongolia, Montenegro, Montserrat, Morocco, Mozambique, Myanmar, Namibia, Nauru, Nepal, Netherlands, Netherlands Antilles, New Caledonia, New Zealand, Nicaragua, Niger, Nigeria, Norway, Oman, Pakistan, Palau, Palestinian Territory, Panama, Papua New Guinea, Paraguay, Peru, Philippines, Poland, Portugal, Puerto Rico, Qatar, Romania, Russia, Rwanda, Saint Barthelemy, Saint Helena, Saint Kitts and Nevis, Saint Lucia, Saint Martin, Saint Pierre and Miquelon, Saint Vincent and the Grenadines, Samoa, San Marino, Saudi Arabia, Senegal, Serbia, Seychelles, Sierra Leone, Singapore, Slovakia, Slovenia, Somalia, South Africa, South Georgia, South Sudan, Spain, Sri Lanka, Sudan, Suriname, Swaziland, Sweden, Switzerland, Syria, Taiwan, Tajikistan, Tanzania, Thailand, Timor-Leste, Togo, Tokelau, Tonga, Trinidad and Tobago, Tunisia, Turkey, Turkmenistan, Tuvalu, Uganda, Ukraine, United Arab Emirates, United Kingdom (UK), United States of America (USA), Uruguay, Uzbekistan, Vanuatu, Venezuela, Vietnam, Wallis and Futuna, Western Sahara, Yemen, Zambia, Zimbabwe.
</string> </string>
<string name="osmand_plus_extended_description_4000_chars_v2">
OsmAnd Maps &amp; Navigation - make routes and find locations on the map without an internet connection. Download a map of a country before you go on a trip to find places and routes in an unfamiliar area.
Basic options:
• Detailed maps of 200 countries
• Offline navigator with voice prompts
• Address search in offline mode
• Info about locations on the map: places of interest, cafes, parking lots, shops
• Adding locations to favorites
• Driving, cycling and pedestrian navigation
Additional options:
• Ability to view and record GPX tracks
• Wikipedia description of POI
• Adding photo, audio, and video notes to the map
• Day and Night map modes for more convenient driving
• Information about public transport routes and stops
• Bicycle paths and footpaths
• Walking trails for tourism all over the world
• Online maps from numerous sources
• Info about road pavement quality and street lighting
• Adding, editing and deleting POI (for OpenStreetMap.org users)
• OsMo - live monitoring of other devices
Get a reliable navigator in your country - be it France, Germany, Mexico, United Kingdom, Spain, the Netherlands, USA, Russia, Brazil or any other state.
Plugins:
• Contour maps and terrain shading https://goo.gl/7mojP8
Contour lines data and terrain visualization added to the basic OsmAnd map.
• Ski maps https://goo.gl/pX6DxJ
The info about ski pistes, cross-country skiing tracks, cable railways and ski lifts.
• Nautical map https://goo.gl/0hEdxm
Special map style for viewing nautical navigation signs for arterial and nearshore marine navigation.
• Parking position https://goo.gl/6JxQXF
Helps you mark the location of your parked vehicle and see how much time left if the parking is time-limited.
Stay tuned!
Twitter: https://twitter.com/osmandapp
Facebook: https://www.facebook.com/osmandapp
Site: http://osmand.net
If you need help with OsmAnd application, please contact our support team: support@osmand.net.
</string>
<string name="filterpoi_activity">Create POI filter</string> <string name="filterpoi_activity">Create POI filter</string>
<string name="recalculate_route_to_your_location">Transport mode:</string> <string name="recalculate_route_to_your_location">Transport mode:</string>
<string name="select_navigation_mode">Select transport mode</string> <string name="select_navigation_mode">Select transport mode</string>

View file

@ -0,0 +1,147 @@
package net.osmand;
import android.os.AsyncTask;
import net.osmand.osm.io.NetworkUtils;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.Version;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URLEncoder;
import java.text.MessageFormat;
import java.util.Map;
public class AndroidNetworkUtils {
public interface OnRequestResultListener {
void onResult(String result);
}
public static void sendRequestAsync(final OsmandApplication ctx,
final String url,
final Map<String, String> parameters,
final String userOperation,
final OnRequestResultListener listener) {
new AsyncTask<Void, Void, String>() {
@Override
protected String doInBackground(Void... params) {
try {
return sendRequest(ctx, url, parameters, userOperation);
} catch (Exception e) {
return null;
}
}
@Override
protected void onPostExecute(String response) {
if (listener != null) {
listener.onResult(response);
}
}
}.execute((Void) null);
}
public static String sendRequest(OsmandApplication ctx, String url, Map<String, String> parameters, String userOperation) {
HttpURLConnection connection = null;
try {
connection = NetworkUtils.getHttpURLConnection(url);
connection.setRequestProperty("Accept-Charset", "UTF-8");
connection.setRequestProperty("User-Agent", Version.getFullVersion(ctx));
connection.setConnectTimeout(15000);
if (parameters != null && parameters.size() > 0) {
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : parameters.entrySet()) {
if (sb.length() > 0) {
sb.append("&");
}
sb.append(entry.getKey()).append("=").append(URLEncoder.encode(entry.getValue(), "UTF-8"));
}
String params = sb.toString();
connection.setDoInput(true);
connection.setDoOutput(true);
connection.setUseCaches(false);
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
connection.setRequestProperty("Content-Length", String.valueOf(params.getBytes("UTF-8").length));
connection.setFixedLengthStreamingMode(params.getBytes("UTF-8").length);
OutputStream output = new BufferedOutputStream(connection.getOutputStream());
output.write(params.getBytes("UTF-8"));
output.flush();
output.close();
} else {
connection.setRequestMethod("GET");
connection.connect();
}
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
String msg = userOperation
+ " " + ctx.getString(R.string.failed_op) + " : " + connection.getResponseMessage();
showToast(ctx, msg);
} else {
StringBuilder responseBody = new StringBuilder();
responseBody.setLength(0);
InputStream i = connection.getInputStream();
if (i != null) {
BufferedReader in = new BufferedReader(new InputStreamReader(i, "UTF-8"), 256);
String s;
boolean f = true;
while ((s = in.readLine()) != null) {
if (!f) {
responseBody.append("\n");
} else {
f = false;
}
responseBody.append(s);
}
try {
in.close();
i.close();
} catch (Exception e) {
// ignore exception
}
}
return responseBody.toString();
}
} catch (NullPointerException e) {
// that's tricky case why NPE is thrown to fix that problem httpClient could be used
String msg = ctx.getString(R.string.auth_failed);
showToast(ctx, msg);
} catch (MalformedURLException e) {
showToast(ctx, MessageFormat.format(ctx.getResources().getString(R.string.shared_string_action_template)
+ ": " + ctx.getResources().getString(R.string.shared_string_unexpected_error), userOperation));
} catch (IOException e) {
showToast(ctx, MessageFormat.format(ctx.getResources().getString(R.string.shared_string_action_template)
+ ": " + ctx.getResources().getString(R.string.shared_string_io_error), userOperation));
} finally {
if (connection != null) {
connection.disconnect();
}
}
return null;
}
private static void showToast(OsmandApplication ctx, String message) {
ctx.showToastMessage(message);
}
}

View file

@ -815,6 +815,10 @@ public class OsmandSettings {
public final OsmandPreference<String> BILLING_USER_NAME = new StringPreference("billing_user_name", "").makeGlobal(); public final OsmandPreference<String> BILLING_USER_NAME = new StringPreference("billing_user_name", "").makeGlobal();
public final OsmandPreference<String> BILLING_USER_EMAIL = new StringPreference("billing_user_email", "").makeGlobal(); public final OsmandPreference<String> BILLING_USER_EMAIL = new StringPreference("billing_user_email", "").makeGlobal();
public final OsmandPreference<String> BILLING_USER_COUNTRY = new StringPreference("billing_user_country", "").makeGlobal(); public final OsmandPreference<String> BILLING_USER_COUNTRY = new StringPreference("billing_user_country", "").makeGlobal();
public final OsmandPreference<String> BILLING_USER_COUNTRY_DOWNLOAD_NAME = new StringPreference("billing_user_country_download_name", "").makeGlobal();
public final OsmandPreference<Boolean> BILLING_HIDE_USER_NAME = new BooleanPreference("billing_hide_user_name", false).makeGlobal();
public final OsmandPreference<Boolean> BILLING_PURCHASE_TOKEN_SENT = new BooleanPreference("billing_purchase_token_sent", false).makeGlobal();
public final OsmandPreference<Boolean> LIVE_UPDATES_PURCHASED = new BooleanPreference("billing_live_updates_purchased", false).makeGlobal();
// this value string is synchronized with settings_pref.xml preference name // this value string is synchronized with settings_pref.xml preference name
public final OsmandPreference<String> USER_OSM_BUG_NAME = public final OsmandPreference<String> USER_OSM_BUG_NAME =

View file

@ -5,7 +5,8 @@ import android.content.Intent;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.util.Log; import android.util.Log;
import net.osmand.osm.io.NetworkUtils; import net.osmand.AndroidNetworkUtils;
import net.osmand.AndroidNetworkUtils.OnRequestResultListener;
import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R; import net.osmand.plus.R;
import net.osmand.plus.Version; import net.osmand.plus.Version;
@ -13,33 +14,28 @@ import net.osmand.plus.inapp.util.IabHelper;
import net.osmand.plus.inapp.util.IabResult; import net.osmand.plus.inapp.util.IabResult;
import net.osmand.plus.inapp.util.Inventory; import net.osmand.plus.inapp.util.Inventory;
import net.osmand.plus.inapp.util.Purchase; import net.osmand.plus.inapp.util.Purchase;
import net.osmand.plus.inapp.util.SkuDetails;
import net.osmand.util.Algorithms; import net.osmand.util.Algorithms;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import java.io.BufferedReader; import java.util.ArrayList;
import java.io.IOException; import java.util.HashMap;
import java.io.InputStream; import java.util.List;
import java.io.InputStreamReader; import java.util.Map;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.text.MessageFormat;
public class InAppHelper { public class InAppHelper {
// Debug tag, for logging // Debug tag, for logging
static final String TAG = "InAppHelper"; static final String TAG = "InAppHelper";
boolean mDebugLog = false;
private static boolean mSubscribedToLiveUpdates = false; private static boolean mSubscribedToLiveUpdates = false;
private static String mLiveUpdatesPrice;
public static final String SKU_LIVE_UPDATES = "osm_live_subscription_1"; private static final String SKU_LIVE_UPDATES_FULL = "osm_live_subscription_2";
private static final String SKU_LIVE_UPDATES_FREE = "osm_free_live_subscription_2";
// Static test private static String SKU_LIVE_UPDATES;
//public static final String SKU_LIVE_UPDATES = "android.test.purchased";
//public static final String SKU_LIVE_UPDATES = "android.test.canceled";
//public static final String SKU_LIVE_UPDATES = "android.test.refunded";
//public static final String SKU_LIVE_UPDATES = "android.test.item_unavailable";
// (arbitrary) request code for the purchase flow // (arbitrary) request code for the purchase flow
private static final int RC_REQUEST = 10001; private static final int RC_REQUEST = 10001;
@ -53,19 +49,38 @@ public class InAppHelper {
public interface InAppCallbacks { public interface InAppCallbacks {
void onError(String error); void onError(String error);
void onGetItems(); void onGetItems();
void onItemPurchased(String sku); void onItemPurchased(String sku);
void showHideProgress(boolean show); void showProgress();
void dismissProgress();
} }
public static boolean isSubscribedToLiveUpdates() { public static boolean isSubscribedToLiveUpdates() {
return mSubscribedToLiveUpdates; return mSubscribedToLiveUpdates;
} }
public static String getLiveUpdatesPrice() {
return mLiveUpdatesPrice;
}
public static String getSkuLiveUpdates() {
return SKU_LIVE_UPDATES;
}
public InAppHelper(OsmandApplication ctx, InAppCallbacks callbacks) { public InAppHelper(OsmandApplication ctx, InAppCallbacks callbacks) {
this.ctx = ctx; this.ctx = ctx;
this.callbacks = callbacks; this.callbacks = callbacks;
if (SKU_LIVE_UPDATES == null) {
if (Version.isFreeVersion(ctx)) {
SKU_LIVE_UPDATES = SKU_LIVE_UPDATES_FREE;
} else {
SKU_LIVE_UPDATES = SKU_LIVE_UPDATES_FULL;
}
}
} }
public void start(final boolean stopAfterResult) { public void start(final boolean stopAfterResult) {
@ -89,18 +104,18 @@ public class InAppHelper {
"YTjh1H/ZgqIHy5ZluahINuDE76qdLYMXrDMQIDAQAB"; "YTjh1H/ZgqIHy5ZluahINuDE76qdLYMXrDMQIDAQAB";
// Create the helper, passing it our context and the public key to verify signatures with // Create the helper, passing it our context and the public key to verify signatures with
Log.d(TAG, "Creating InAppHelper."); logDebug("Creating InAppHelper.");
mHelper = new IabHelper(ctx, base64EncodedPublicKey); mHelper = new IabHelper(ctx, base64EncodedPublicKey);
// enable debug logging (for a production application, you should set this to false). // enable debug logging (for a production application, you should set this to false).
mHelper.enableDebugLogging(true); mHelper.enableDebugLogging(false);
// Start setup. This is asynchronous and the specified listener // Start setup. This is asynchronous and the specified listener
// will be called once setup completes. // will be called once setup completes.
Log.d(TAG, "Starting setup."); logDebug("Starting setup.");
mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() { mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
public void onIabSetupFinished(IabResult result) { public void onIabSetupFinished(IabResult result) {
Log.d(TAG, "Setup finished."); logDebug("Setup finished.");
if (!result.isSuccess()) { if (!result.isSuccess()) {
// Oh noes, there was a problem. // Oh noes, there was a problem.
@ -118,8 +133,10 @@ public class InAppHelper {
if (mHelper == null) return; if (mHelper == null) return;
// IAB is fully set up. Now, let's get an inventory of stuff we own. // IAB is fully set up. Now, let's get an inventory of stuff we own.
Log.d(TAG, "Setup successful. Querying inventory."); logDebug("Setup successful. Querying inventory.");
mHelper.queryInventoryAsync(mGotInventoryListener); List<String> skus = new ArrayList<>();
skus.add(SKU_LIVE_UPDATES);
mHelper.queryInventoryAsync(true, skus, mGotInventoryListener);
} }
}); });
} }
@ -127,7 +144,7 @@ public class InAppHelper {
// Listener that's called when we finish querying the items and subscriptions we own // Listener that's called when we finish querying the items and subscriptions we own
private IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() { private IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result, Inventory inventory) { public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
Log.d(TAG, "Query inventory finished."); logDebug("Query inventory finished.");
// Have we been disposed of in the meantime? If so, quit. // Have we been disposed of in the meantime? If so, quit.
if (mHelper == null) return; if (mHelper == null) return;
@ -144,7 +161,7 @@ public class InAppHelper {
return; return;
} }
Log.d(TAG, "Query inventory was successful."); logDebug("Query inventory was successful.");
/* /*
* Check for items we own. Notice that for each purchase, we check * Check for items we own. Notice that for each purchase, we check
@ -154,25 +171,36 @@ public class InAppHelper {
// Do we have the live updates? // Do we have the live updates?
Purchase liveUpdatesPurchase = inventory.getPurchase(SKU_LIVE_UPDATES); Purchase liveUpdatesPurchase = inventory.getPurchase(SKU_LIVE_UPDATES);
mSubscribedToLiveUpdates = (liveUpdatesPurchase != null && mSubscribedToLiveUpdates = (liveUpdatesPurchase != null);
verifyDeveloperPayload(liveUpdatesPurchase)); if (mSubscribedToLiveUpdates) {
Log.d(TAG, "User " + (mSubscribedToLiveUpdates ? "HAS" : "DOES NOT HAVE") ctx.getSettings().LIVE_UPDATES_PURCHASED.set(true);
}
logDebug("User " + (mSubscribedToLiveUpdates ? "HAS" : "DOES NOT HAVE")
+ " live updates purchased."); + " live updates purchased.");
if (inventory.hasDetails(SKU_LIVE_UPDATES)) {
SkuDetails liveUpdatesDetails = inventory.getSkuDetails(SKU_LIVE_UPDATES);
mLiveUpdatesPrice = liveUpdatesDetails.getPrice();
}
if (liveUpdatesPurchase != null && !ctx.getSettings().BILLING_PURCHASE_TOKEN_SENT.get()) {
sendToken(liveUpdatesPurchase.getToken());
}
if (callbacks != null) { if (callbacks != null) {
callbacks.showHideProgress(false); callbacks.dismissProgress();
callbacks.onGetItems(); callbacks.onGetItems();
} }
if (stopAfterResult) { if (stopAfterResult) {
stop(); stop();
} }
Log.d(TAG, "Initial inapp query finished"); logDebug("Initial inapp query finished");
} }
}; };
public void purchaseLiveUpdates(final Activity activity, final String email, final String userName, public void purchaseLiveUpdates(final Activity activity, final String email, final String userName,
final String country) { final String countryDownloadName, final boolean hideUserName) {
if (!mHelper.subscriptionsSupported()) { if (!mHelper.subscriptionsSupported()) {
complain("Subscriptions not supported on your device yet. Sorry!"); complain("Subscriptions not supported on your device yet. Sorry!");
if (callbacks != null) { if (callbacks != null) {
@ -185,7 +213,7 @@ public class InAppHelper {
} }
if (callbacks != null) { if (callbacks != null) {
callbacks.showHideProgress(true); callbacks.showProgress();
} }
new AsyncTask<Void, Void, String>() { new AsyncTask<Void, Void, String>() {
@ -195,23 +223,32 @@ public class InAppHelper {
@Override @Override
protected String doInBackground(Void... params) { protected String doInBackground(Void... params) {
userId = ctx.getSettings().BILLING_USER_ID.get(); userId = ctx.getSettings().BILLING_USER_ID.get();
try {
Map<String, String> parameters = new HashMap<>();
parameters.put("visibleName", hideUserName ? "" : userName);
parameters.put("preferredCountry", countryDownloadName);
parameters.put("email", email);
if (Algorithms.isEmpty(userId)) { if (Algorithms.isEmpty(userId)) {
return sendRequest("http://download.osmand.net/subscription/register?email=" + email parameters.put("status", "new");
+ "&visibleName=" + userName + "&preferredCountry=" + country }
+ (Algorithms.isEmpty(userId) ? "&status=new" : ""),
"POST", "Requesting userId..."); return AndroidNetworkUtils.sendRequest(ctx,
} else { "http://download.osmand.net/subscription/register.php",
parameters, "Requesting userId...");
} catch (Exception e) {
logError("sendRequest Error", e);
return null; return null;
} }
} }
@Override @Override
protected void onPostExecute(String response) { protected void onPostExecute(String response) {
if (Algorithms.isEmpty(userId)) { logDebug("Response=" + response);
Log.d(TAG, "Response=" + response);
if (response == null) { if (response == null) {
complain("Cannot retrieve userId from server.");
if (callbacks != null) { if (callbacks != null) {
callbacks.showHideProgress(false); callbacks.dismissProgress();
callbacks.onError("Cannot retrieve userId from server."); callbacks.onError("Cannot retrieve userId from server.");
} }
if (stopAfterResult) { if (stopAfterResult) {
@ -224,28 +261,34 @@ public class InAppHelper {
JSONObject obj = new JSONObject(response); JSONObject obj = new JSONObject(response);
userId = obj.getString("userid"); userId = obj.getString("userid");
ctx.getSettings().BILLING_USER_ID.set(userId); ctx.getSettings().BILLING_USER_ID.set(userId);
Log.d(TAG, "UserId=" + userId); logDebug("UserId=" + userId);
} catch (JSONException e) { } catch (JSONException e) {
String message = "JSON parsing error: "
+ (e.getMessage() == null ? "unknown" : e.getMessage());
complain(message);
if (callbacks != null) { if (callbacks != null) {
callbacks.showHideProgress(false); callbacks.dismissProgress();
callbacks.onError("JSON parsing error: " + e.getMessage()); callbacks.onError(message);
} }
if (stopAfterResult) { if (stopAfterResult) {
stop(); stop();
} }
} }
} }
}
if (callbacks != null) {
callbacks.dismissProgress();
}
if (!Algorithms.isEmpty(userId)) { if (!Algorithms.isEmpty(userId)) {
Log.d(TAG, "Launching purchase flow for live updates subscription for userId=" + userId); logDebug("Launching purchase flow for live updates subscription for userId=" + userId);
String payload = userId; String payload = userId;
if (mHelper != null) {
mHelper.launchPurchaseFlow(activity, mHelper.launchPurchaseFlow(activity,
SKU_LIVE_UPDATES, IabHelper.ITEM_TYPE_SUBS, SKU_LIVE_UPDATES, IabHelper.ITEM_TYPE_SUBS,
RC_REQUEST, mPurchaseFinishedListener, payload); RC_REQUEST, mPurchaseFinishedListener, payload);
}
} else { } else {
if (callbacks != null) { if (callbacks != null) {
callbacks.showHideProgress(false);
callbacks.onError("Empty userId"); callbacks.onError("Empty userId");
} }
if (stopAfterResult) { if (stopAfterResult) {
@ -257,9 +300,10 @@ public class InAppHelper {
} }
public boolean onActivityResultHandled(int requestCode, int resultCode, Intent data) { public boolean onActivityResultHandled(int requestCode, int resultCode, Intent data) {
Log.d(TAG, "onActivityResult(" + requestCode + "," + resultCode + "," + data); logDebug("onActivityResult(" + requestCode + "," + resultCode + "," + data);
if (mHelper == null) return false; if (mHelper == null) return false;
try {
// Pass on the activity result to the helper for handling // Pass on the activity result to the helper for handling
if (!mHelper.handleActivityResult(requestCode, resultCode, data)) { if (!mHelper.handleActivityResult(requestCode, resultCode, data)) {
// not handled, so handle it ourselves (here's where you'd // not handled, so handle it ourselves (here's where you'd
@ -267,47 +311,20 @@ public class InAppHelper {
// billing... // billing...
//super.onActivityResult(requestCode, resultCode, data); //super.onActivityResult(requestCode, resultCode, data);
return false; return false;
} } else {
else { logDebug("onActivityResult handled by IABUtil.");
Log.d(TAG, "onActivityResult handled by IABUtil.");
return true; return true;
} }
} catch (Exception e) {
logError("onActivityResultHandled", e);
return false;
} }
/** Verifies the developer payload of a purchase. */
private boolean verifyDeveloperPayload(Purchase p) {
String payload = p.getDeveloperPayload();
/*
* TODO: verify that the developer payload of the purchase is correct. It will be
* the same one that you sent when initiating the purchase.
*
* WARNING: Locally generating a random string when starting a purchase and
* verifying it here might seem like a good approach, but this will fail in the
* case where the user purchases an item on one device and then uses your app on
* a different device, because on the other device you will not have access to the
* random string you originally generated.
*
* So a good developer payload has these characteristics:
*
* 1. If two different users purchase an item, the payload is different between them,
* so that one user's purchase can't be replayed to another user.
*
* 2. The payload must be such that you can verify it even when the app wasn't the
* one who initiated the purchase flow (so that items purchased by the user on
* one device work on other devices owned by the user).
*
* Using your own server to store and verify developer payloads across app
* installations is recommended.
*/
return true;
} }
// Callback for when a purchase is finished // Callback for when a purchase is finished
private IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() { private IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result, Purchase purchase) { public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
Log.d(TAG, "Purchase finished: " + result + ", purchase: " + purchase); logDebug("Purchase finished: " + result + ", purchase: " + purchase);
// if we were disposed of in the meantime, quit. // if we were disposed of in the meantime, quit.
if (mHelper == null) return; if (mHelper == null) return;
@ -315,7 +332,7 @@ public class InAppHelper {
if (result.isFailure()) { if (result.isFailure()) {
complain("Error purchasing: " + result); complain("Error purchasing: " + result);
if (callbacks != null) { if (callbacks != null) {
callbacks.showHideProgress(false); callbacks.dismissProgress();
callbacks.onError("Error purchasing: " + result); callbacks.onError("Error purchasing: " + result);
} }
if (stopAfterResult) { if (stopAfterResult) {
@ -323,27 +340,20 @@ public class InAppHelper {
} }
return; return;
} }
if (!verifyDeveloperPayload(purchase)) {
complain("Error purchasing. Authenticity verification failed.");
if (callbacks != null) {
callbacks.showHideProgress(false);
callbacks.onError("Error purchasing. Authenticity verification failed.");
}
if (stopAfterResult) {
stop();
}
return;
}
Log.d(TAG, "Purchase successful."); logDebug("Purchase successful.");
if (purchase.getSku().equals(SKU_LIVE_UPDATES)) { if (purchase.getSku().equals(SKU_LIVE_UPDATES)) {
// bought the infinite gas subscription // bought live updates
Log.d(TAG, "Live updates subscription purchased."); sendToken(purchase.getToken());
showToast("Thank you for subscribing to live updates!");
logDebug("Live updates subscription purchased.");
showToast(ctx.getString(R.string.osm_live_thanks));
mSubscribedToLiveUpdates = true; mSubscribedToLiveUpdates = true;
ctx.getSettings().LIVE_UPDATES_PURCHASED.set(true);
if (callbacks != null) { if (callbacks != null) {
callbacks.showHideProgress(false); callbacks.dismissProgress();
callbacks.onItemPurchased(SKU_LIVE_UPDATES); callbacks.onItemPurchased(SKU_LIVE_UPDATES);
} }
if (stopAfterResult) { if (stopAfterResult) {
@ -355,79 +365,67 @@ public class InAppHelper {
// Do not forget call stop() when helper is not needed anymore // Do not forget call stop() when helper is not needed anymore
public void stop() { public void stop() {
Log.d(TAG, "Destroying helper."); logDebug("Destroying helper.");
if (mHelper != null) { if (mHelper != null) {
mHelper.dispose(); mHelper.dispose();
mHelper = null; mHelper = null;
} }
} }
void complain(String message) { private void sendToken(String token) {
Log.e(TAG, "**** InAppHelper Error: " + message); String userId = ctx.getSettings().BILLING_USER_ID.get();
String email = ctx.getSettings().BILLING_USER_EMAIL.get();
try {
Map<String, String> parameters = new HashMap<>();
parameters.put("userid", userId);
parameters.put("sku", SKU_LIVE_UPDATES);
parameters.put("purchaseToken", token);
parameters.put("email", email);
AndroidNetworkUtils.sendRequestAsync(ctx,
"http://download.osmand.net/subscription/purchased.php",
parameters, "Sending purchase info...", new OnRequestResultListener() {
@Override
public void onResult(String result) {
if (result != null) {
try {
JSONObject obj = new JSONObject(result);
if (!obj.has("error")) {
ctx.getSettings().BILLING_PURCHASE_TOKEN_SENT.set(true);
} else {
complain("SendToken Error: " + obj.getString("error"));
}
} catch (JSONException e) {
logError("sendToken", e);
complain("SendToken Error: " + (e.getMessage() != null ? e.getMessage() : "JSONException"));
}
}
}
});
} catch (Exception e) {
logError("sendToken Error", e);
}
}
private void complain(String message) {
logError("**** InAppHelper Error: " + message);
showToast("Error: " + message); showToast("Error: " + message);
} }
void showToast(final String message) { private void showToast(final String message) {
ctx.showToastMessage(message); ctx.showToastMessage(message);
} }
private String sendRequest(String url, String requestMethod, String userOperation) { void logDebug(String msg) {
Log.d(TAG, "Sending request " + url); //$NON-NLS-1$ if (mDebugLog) Log.d(TAG, msg);
try {
HttpURLConnection connection = NetworkUtils.getHttpURLConnection(url);
connection.setConnectTimeout(15000);
connection.setRequestMethod(requestMethod);
connection.setRequestProperty("User-Agent", Version.getFullVersion(ctx)); //$NON-NLS-1$
StringBuilder responseBody = new StringBuilder();
connection.connect();
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
String msg = userOperation
+ " " + ctx.getString(R.string.failed_op) + " : " + connection.getResponseMessage(); //$NON-NLS-1$//$NON-NLS-2$
Log.e(TAG, msg);
showToast(msg);
} else {
Log.d(TAG, "Response : " + connection.getResponseMessage()); //$NON-NLS-1$
// populate return fields.
responseBody.setLength(0);
InputStream i = connection.getInputStream();
if (i != null) {
BufferedReader in = new BufferedReader(new InputStreamReader(i, "UTF-8"), 256); //$NON-NLS-1$
String s;
boolean f = true;
while ((s = in.readLine()) != null) {
if (!f) {
responseBody.append("\n"); //$NON-NLS-1$
} else {
f = false;
}
responseBody.append(s);
}
try {
in.close();
i.close();
} catch (Exception e) {
Log.d(TAG, e.getMessage());
}
}
return responseBody.toString();
}
connection.disconnect();
} catch (NullPointerException e) {
// that's tricky case why NPE is thrown to fix that problem httpClient could be used
String msg = ctx.getString(R.string.auth_failed);
Log.e(TAG, msg, e);
showToast(msg);
} catch (MalformedURLException e) {
Log.e(TAG, userOperation + " " + ctx.getString(R.string.failed_op), e); //$NON-NLS-1$
showToast(MessageFormat.format(ctx.getResources().getString(R.string.shared_string_action_template)
+ ": " + ctx.getResources().getString(R.string.shared_string_unexpected_error), userOperation));
} catch (IOException e) {
Log.e(TAG, userOperation + " " + ctx.getString(R.string.failed_op), e); //$NON-NLS-1$
showToast(MessageFormat.format(ctx.getResources().getString(R.string.shared_string_action_template)
+ ": " + ctx.getResources().getString(R.string.shared_string_io_error), userOperation));
} }
return null; void logError(String msg) {
Log.e(TAG, "Error: " + msg);
} }
void logError(String msg, Throwable e) {
Log.e(TAG, "Error: " + msg, e);
}
} }

View file

@ -0,0 +1,234 @@
package net.osmand.plus.liveupdates;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.annotation.DrawableRes;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.TextView;
import net.osmand.map.WorldRegion;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.base.BaseOsmAndDialogFragment;
import net.osmand.util.Algorithms;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class CountrySelectionFragment extends BaseOsmAndDialogFragment {
private List<CountryItem> countryItems = new ArrayList<>();
private OnFragmentInteractionListener mListener;
public List<CountryItem> getCountryItems() {
return countryItems;
}
public CountryItem getCountryItem(String downloadName) {
if (!Algorithms.isEmpty(downloadName)) {
for (CountryItem item : countryItems) {
if (downloadName.equals(item.downloadName)) {
return item;
}
}
}
return null;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (countryItems.size() == 0) {
initCountries(getMyApplication());
}
View view = inflater.inflate(R.layout.fragment_search_list, container, false);
ListView listView = (ListView) view.findViewById(android.R.id.list);
final ArrayAdapter<CountryItem> adapter = new ListAdapter(getListItemIcon());
if (countryItems.size() > 0) {
adapter.addAll(countryItems);
}
listView.setAdapter(adapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
mListener.onSearchResult(adapter.getItem(position));
dismiss();
}
});
final EditText searchEditText = (EditText) view.findViewById(R.id.searchEditText);
searchEditText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
adapter.getFilter().filter(s);
}
});
ImageButton clearButton = (ImageButton) view.findViewById(R.id.clearButton);
setThemedDrawable(clearButton, R.drawable.ic_action_remove_dark);
clearButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
}
});
return view;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnFragmentInteractionListener) {
mListener = (OnFragmentInteractionListener) context;
} else if (getParentFragment() instanceof OnFragmentInteractionListener) {
mListener = (OnFragmentInteractionListener) getParentFragment();
} else {
throw new RuntimeException(context.toString()
+ " must implement OnFragmentInteractionListener");
}
}
@DrawableRes
protected int getListItemIcon() {
return R.drawable.ic_map;
}
@Override
public void onDetach() {
super.onDetach();
mListener = null;
}
public interface OnFragmentInteractionListener {
void onSearchResult(CountryItem name);
}
public void initCountries(OsmandApplication app) {
final WorldRegion root = app.getRegions().getWorldRegion();
ArrayList<WorldRegion> groups = new ArrayList<>();
groups.add(root);
processGroup(root, groups);
Collections.sort(groups, new Comparator<WorldRegion>() {
@Override
public int compare(WorldRegion lhs, WorldRegion rhs) {
if (lhs == root) {
return -1;
}
if (rhs == root) {
return 1;
}
return getHumanReadableName(lhs).compareTo(getHumanReadableName(rhs));
}
});
for (WorldRegion group : groups) {
String name = getHumanReadableName(group);
if (group == root) {
countryItems.add(new CountryItem(name, ""));
} else {
countryItems.add(new CountryItem(name, group.getRegionDownloadName()));
}
}
}
private static void processGroup(WorldRegion group,
List<WorldRegion> nameList) {
if (group.isRegionMapDownload()) {
nameList.add(group);
}
if (group.getSubregions() != null) {
for (WorldRegion g : group.getSubregions()) {
processGroup(g, nameList);
}
}
}
private static String getHumanReadableName(WorldRegion group) {
String name;
if (group.getLevel() > 2 || (group.getLevel() == 2
&& group.getSuperregion().getRegionId().equals(WorldRegion.RUSSIA_REGION_ID))) {
WorldRegion parent = group.getSuperregion();
WorldRegion parentsParent = group.getSuperregion().getSuperregion();
if (group.getLevel() == 3) {
if (parentsParent.getRegionId().equals(WorldRegion.RUSSIA_REGION_ID)) {
name = parentsParent.getLocaleName() + " " + group.getLocaleName();
} else if (!parent.getRegionId().equals(WorldRegion.UNITED_KINGDOM_REGION_ID)) {
name = parent.getLocaleName() + " " + group.getLocaleName();
} else {
name = group.getLocaleName();
}
} else {
name = parent.getLocaleName() + " " + group.getLocaleName();
}
} else {
name = group.getLocaleName();
}
if (name == null) {
name = "";
}
return name;
}
public static class CountryItem implements Serializable {
private String localName;
private String downloadName;
public CountryItem(String localName, String downloadName) {
this.localName = localName;
this.downloadName = downloadName;
}
public String getLocalName() {
return localName;
}
public String getDownloadName() {
return downloadName;
}
@Override
public String toString() {
return localName;
}
}
private class ListAdapter extends ArrayAdapter<CountryItem> {
private final Drawable drawableLeft;
public ListAdapter(@DrawableRes int drawableLeftId) {
super(getMyActivity(), android.R.layout.simple_list_item_1);
this.drawableLeft = drawableLeftId == -1 ? null : getContentIcon(drawableLeftId);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
CountryItem item = getItem(position);
TextView view = (TextView) super.getView(position, convertView, parent);
view.setText(item.localName);
view.setCompoundDrawablesWithIntrinsicBounds(drawableLeft, null, null, null);
view.setCompoundDrawablePadding(getResources().getDimensionPixelSize(R.dimen.list_content_padding));
return view;
}
}
}

View file

@ -3,15 +3,23 @@ package net.osmand.plus.liveupdates;
import android.app.AlarmManager; import android.app.AlarmManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.DrawableRes; import android.support.annotation.DrawableRes;
import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.SwitchCompat; import android.support.v7.widget.SwitchCompat;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.AbsListView; import android.widget.AbsListView;
@ -22,6 +30,7 @@ import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import net.osmand.map.WorldRegion;
import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandApplication;
import net.osmand.plus.OsmandSettings; import net.osmand.plus.OsmandSettings;
import net.osmand.plus.R; import net.osmand.plus.R;
@ -56,6 +65,7 @@ import static net.osmand.plus.liveupdates.LiveUpdatesHelper.setAlarmForPendingIn
public class LiveUpdatesFragment extends BaseOsmAndFragment { public class LiveUpdatesFragment extends BaseOsmAndFragment {
public static final String TITLE = "Live Updates"; public static final String TITLE = "Live Updates";
private static final int SUBSCRIPTION_SETTINGS = 5;
public static final Comparator<LocalIndexInfo> LOCAL_INDEX_INFO_COMPARATOR = new Comparator<LocalIndexInfo>() { public static final Comparator<LocalIndexInfo> LOCAL_INDEX_INFO_COMPARATOR = new Comparator<LocalIndexInfo>() {
@Override @Override
public int compare(LocalIndexInfo lhs, LocalIndexInfo rhs) { public int compare(LocalIndexInfo lhs, LocalIndexInfo rhs) {
@ -70,6 +80,7 @@ public class LiveUpdatesFragment extends BaseOsmAndFragment {
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
} }
@Override @Override
@ -77,7 +88,6 @@ public class LiveUpdatesFragment extends BaseOsmAndFragment {
Bundle savedInstanceState) { Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_live_updates, container, false); View view = inflater.inflate(R.layout.fragment_live_updates, container, false);
listView = (ExpandableListView) view.findViewById(android.R.id.list); listView = (ExpandableListView) view.findViewById(android.R.id.list);
// View header = inflater.inflate(R.layout.live_updates_header, listView, false);
View bottomShadowView = inflater.inflate(R.layout.shadow_bottom, listView, false); View bottomShadowView = inflater.inflate(R.layout.shadow_bottom, listView, false);
listView.addFooterView(bottomShadowView); listView.addFooterView(bottomShadowView);
@ -94,9 +104,64 @@ public class LiveUpdatesFragment extends BaseOsmAndFragment {
} }
}); });
//test
//getSettings().LIVE_UPDATES_PURCHASED.set(true);
subscriptionHeader = inflater.inflate(R.layout.live_updates_header, listView, false); subscriptionHeader = inflater.inflate(R.layout.live_updates_header, listView, false);
updateSubscriptionHeader();
listView.addHeaderView(subscriptionHeader);
loadLocalIndexesTask = new LoadLocalIndexTask(adapter, this).execute();
return view;
}
public void updateSubscriptionHeader() {
View subscriptionBanner = subscriptionHeader.findViewById(R.id.subscription_banner); View subscriptionBanner = subscriptionHeader.findViewById(R.id.subscription_banner);
View subscriptionInfo = subscriptionHeader.findViewById(R.id.subscription_info); View subscriptionInfo = subscriptionHeader.findViewById(R.id.subscription_info);
if (getSettings().LIVE_UPDATES_PURCHASED.get()) {
ImageView statusIcon = (ImageView) subscriptionHeader.findViewById(R.id.statusIcon);
TextView statusTextView = (TextView) subscriptionHeader.findViewById(R.id.statusTextView);
TextView regionNameTextView = (TextView) subscriptionHeader.findViewById(R.id.regionTextView);
if (InAppHelper.isSubscribedToLiveUpdates()) {
statusTextView.setText(getString(R.string.osm_live_active));
statusIcon.setImageDrawable(getMyApplication().getIconsCache().getContentIcon(R.drawable.ic_action_done));
} else {
statusTextView.setText(getString(R.string.osm_live_not_active));
statusIcon.setImageDrawable(getMyApplication().getIconsCache().getContentIcon(R.drawable.ic_action_remove_dark));
}
OsmandSettings settings = getMyApplication().getSettings();
String countryName = settings.BILLING_USER_COUNTRY.get();
if (Algorithms.isEmpty(countryName)) {
WorldRegion world = getMyApplication().getRegions().getWorldRegion();
countryName = world.getLocaleName();
}
regionNameTextView.setText(countryName);
Button subscribeButton = (Button) subscriptionHeader.findViewById(R.id.subscribeButton);
subscribeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SubscriptionFragment subscriptionFragment = new SubscriptionFragment();
subscriptionFragment.show(getChildFragmentManager(), SubscriptionFragment.TAG);
}
});
subscriptionBanner.setVisibility(View.GONE);
subscriptionInfo.setVisibility(View.VISIBLE);
} else {
Button readMoreBtn = (Button) subscriptionHeader.findViewById(R.id.read_more_button);
readMoreBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Uri uri = Uri.parse("http://osmand.net/osm_live.php");
Intent goToOsmLive = new Intent(Intent.ACTION_VIEW, uri);
startActivity(goToOsmLive);
}
});
Button subscriptionButton = (Button) subscriptionHeader.findViewById(R.id.subscription_button); Button subscriptionButton = (Button) subscriptionHeader.findViewById(R.id.subscription_button);
subscriptionButton.setOnClickListener(new View.OnClickListener() { subscriptionButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
@ -105,26 +170,7 @@ public class LiveUpdatesFragment extends BaseOsmAndFragment {
subscriptionFragment.show(getChildFragmentManager(), SubscriptionFragment.TAG); subscriptionFragment.show(getChildFragmentManager(), SubscriptionFragment.TAG);
} }
}); });
if (InAppHelper.isSubscribedToLiveUpdates()) {
subscriptionBanner.setVisibility(View.GONE);
subscriptionInfo.setVisibility(View.VISIBLE);
} else {
subscriptionBanner.setVisibility(View.VISIBLE);
subscriptionInfo.setVisibility(View.GONE);
}
listView.addHeaderView(subscriptionHeader);
loadLocalIndexesTask = new LoadLocalIndexTask(adapter, this).execute();
return view;
}
public void updateSubscriptionBanner() {
View subscriptionBanner = subscriptionHeader.findViewById(R.id.subscription_banner);
View subscriptionInfo = subscriptionHeader.findViewById(R.id.subscription_info);
if (InAppHelper.isSubscribedToLiveUpdates()) {
subscriptionBanner.setVisibility(View.GONE);
subscriptionInfo.setVisibility(View.VISIBLE);
} else {
subscriptionBanner.setVisibility(View.VISIBLE); subscriptionBanner.setVisibility(View.VISIBLE);
subscriptionInfo.setVisibility(View.GONE); subscriptionInfo.setVisibility(View.GONE);
} }
@ -142,6 +188,34 @@ public class LiveUpdatesFragment extends BaseOsmAndFragment {
} }
} }
@SuppressWarnings("deprecation")
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
if (getSettings().LIVE_UPDATES_PURCHASED.get()) {
ActionBar actionBar = getMyActivity().getSupportActionBar();
if (actionBar != null) {
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
}
SubMenu split = menu.addSubMenu(R.string.shared_string_more_actions);
split.setIcon(R.drawable.ic_overflow_menu_white);
MenuItemCompat.setShowAsAction(split.getItem(), MenuItemCompat.SHOW_AS_ACTION_ALWAYS);
MenuItem item = split.add(0, SUBSCRIPTION_SETTINGS, 0, R.string.osm_live_subscription_settings);
MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_ALWAYS);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == SUBSCRIPTION_SETTINGS) {
SubscriptionFragment subscriptionFragment = new SubscriptionFragment();
subscriptionFragment.setEditMode(true);
subscriptionFragment.show(getChildFragmentManager(), SubscriptionFragment.TAG);
return true;
}
return super.onOptionsItemSelected(item);
}
protected class LocalIndexesAdapter extends OsmandBaseExpandableListAdapter { protected class LocalIndexesAdapter extends OsmandBaseExpandableListAdapter {
public static final int SHOULD_UPDATE_GROUP_POSITION = 0; public static final int SHOULD_UPDATE_GROUP_POSITION = 0;
public static final int SHOULD_NOT_UPDATE_GROUP_POSITION = 1; public static final int SHOULD_NOT_UPDATE_GROUP_POSITION = 1;
@ -359,6 +433,7 @@ public class LiveUpdatesFragment extends BaseOsmAndFragment {
divider = view.findViewById(R.id.divider); divider = view.findViewById(R.id.divider);
} }
@SuppressWarnings("deprecation")
public void bindLocalIndexInfo(final LocalIndexInfo item, boolean isLastChild) { public void bindLocalIndexInfo(final LocalIndexInfo item, boolean isLastChild) {
OsmandApplication context = fragment.getMyActivity().getMyApplication(); OsmandApplication context = fragment.getMyActivity().getMyApplication();
final OsmandSettings.CommonPreference<Boolean> shouldUpdatePreference = final OsmandSettings.CommonPreference<Boolean> shouldUpdatePreference =
@ -425,7 +500,7 @@ public class LiveUpdatesFragment extends BaseOsmAndFragment {
extends AsyncTask<Void, LocalIndexInfo, List<LocalIndexInfo>> extends AsyncTask<Void, LocalIndexInfo, List<LocalIndexInfo>>
implements AbstractLoadLocalIndexTask { implements AbstractLoadLocalIndexTask {
private List<LocalIndexInfo> result; //private List<LocalIndexInfo> result;
private LocalIndexesAdapter adapter; private LocalIndexesAdapter adapter;
private LiveUpdatesFragment fragment; private LiveUpdatesFragment fragment;
private LocalIndexHelper helper; private LocalIndexHelper helper;
@ -461,7 +536,7 @@ public class LiveUpdatesFragment extends BaseOsmAndFragment {
@Override @Override
protected void onPostExecute(List<LocalIndexInfo> result) { protected void onPostExecute(List<LocalIndexInfo> result) {
this.result = result; //this.result = result;
adapter.sort(); adapter.sort();
} }
} }

View file

@ -80,7 +80,7 @@ public class PerformLiveUpdateAsyncTask
} else { } else {
settings.LIVE_UPDATES_RETRIES.resetToDefault(); settings.LIVE_UPDATES_RETRIES.resetToDefault();
List<IncrementalChangesManager.IncrementalUpdate> ll = result.getItemsForUpdate(); List<IncrementalChangesManager.IncrementalUpdate> ll = result.getItemsForUpdate();
if (!ll.isEmpty()) { if (ll != null && !ll.isEmpty()) {
ArrayList<IndexItem> itemsToDownload = new ArrayList<>(ll.size()); ArrayList<IndexItem> itemsToDownload = new ArrayList<>(ll.size());
for (IncrementalChangesManager.IncrementalUpdate iu : ll) { for (IncrementalChangesManager.IncrementalUpdate iu : ll) {
IndexItem indexItem = new IndexItem(iu.fileName, "Incremental update", IndexItem indexItem = new IndexItem(iu.fileName, "Incremental update",

View file

@ -22,23 +22,19 @@ import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException; import com.google.gson.JsonSyntaxException;
import net.osmand.PlatformUtil; import net.osmand.PlatformUtil;
import net.osmand.map.WorldRegion;
import net.osmand.osm.io.NetworkUtils; import net.osmand.osm.io.NetworkUtils;
import net.osmand.plus.R; import net.osmand.plus.R;
import net.osmand.plus.base.BaseOsmAndFragment; import net.osmand.plus.base.BaseOsmAndFragment;
import net.osmand.plus.liveupdates.CountrySelectionFragment.CountryItem;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale; import java.util.Locale;
public class ReportsFragment extends BaseOsmAndFragment implements SearchSelectionFragment.OnFragmentInteractionListener { public class ReportsFragment extends BaseOsmAndFragment implements CountrySelectionFragment.OnFragmentInteractionListener {
public static final String TITLE = "Report"; public static final String TITLE = "Report";
public static final String TOTAL_CHANGES_BY_MONTH_URL_PATTERN = "http://download.osmand.net/" + public static final String TOTAL_CHANGES_BY_MONTH_URL_PATTERN = "http://download.osmand.net/" +
"reports/query_report.php?report=total_changes_by_month&month=%s&region=%s"; "reports/query_report.php?report=total_changes_by_month&month=%s&region=%s";
@ -50,11 +46,9 @@ public class ReportsFragment extends BaseOsmAndFragment implements SearchSelecti
private Spinner montReportsSpinner; private Spinner montReportsSpinner;
private MonthsForReportsAdapter monthsForReportsAdapter; private MonthsForReportsAdapter monthsForReportsAdapter;
CountrySearchSelectionFragment searchSelectionFragment = new CountrySearchSelectionFragment(); private CountrySelectionFragment countrySelectionFragment = new CountrySelectionFragment();
private TextView countryNameTextView; private TextView countryNameTextView;
private CountryItem selectedCountryItem;
HashMap<String, String> queryRegionNames = new HashMap<>();
ArrayList<String> regionNames = new ArrayList<>();
private ImageView numberOfContributorsIcon; private ImageView numberOfContributorsIcon;
private ImageView numberOfEditsIcon; private ImageView numberOfEditsIcon;
@ -69,7 +63,6 @@ public class ReportsFragment extends BaseOsmAndFragment implements SearchSelecti
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
initCountries();
View view = inflater.inflate(R.layout.fragment_reports, container, false); View view = inflater.inflate(R.layout.fragment_reports, container, false);
montReportsSpinner = (Spinner) view.findViewById(R.id.montReportsSpinner); montReportsSpinner = (Spinner) view.findViewById(R.id.montReportsSpinner);
monthsForReportsAdapter = new MonthsForReportsAdapter(getActivity()); monthsForReportsAdapter = new MonthsForReportsAdapter(getActivity());
@ -79,15 +72,15 @@ public class ReportsFragment extends BaseOsmAndFragment implements SearchSelecti
regionReportsButton.setOnClickListener(new View.OnClickListener() { regionReportsButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
SearchSelectionFragment countrySearchSelectionFragment = countrySelectionFragment.show(getChildFragmentManager(), "CountriesSearchSelectionFragment");
searchSelectionFragment;
countrySearchSelectionFragment
.show(getChildFragmentManager(), "CountriesSearchSelectionFragment");
} }
}); });
countrySelectionFragment.initCountries(getMyApplication());
selectedCountryItem = countrySelectionFragment.getCountryItems().get(0);
countryNameTextView = (TextView) regionReportsButton.findViewById(android.R.id.text1); countryNameTextView = (TextView) regionReportsButton.findViewById(android.R.id.text1);
countryNameTextView.setText(regionNames.get(0)); countryNameTextView.setText(selectedCountryItem.getLocalName());
setThemedDrawable(view, R.id.calendarImageView, R.drawable.ic_action_data); setThemedDrawable(view, R.id.calendarImageView, R.drawable.ic_action_data);
setThemedDrawable(view, R.id.regionIconImageView, R.drawable.ic_world_globe_dark); setThemedDrawable(view, R.id.regionIconImageView, R.drawable.ic_world_globe_dark);
@ -127,7 +120,7 @@ public class ReportsFragment extends BaseOsmAndFragment implements SearchSelecti
public void requestAndUpdateUi() { public void requestAndUpdateUi() {
int monthItemPosition = montReportsSpinner.getSelectedItemPosition(); int monthItemPosition = montReportsSpinner.getSelectedItemPosition();
String monthUrlString = monthsForReportsAdapter.getQueryString(monthItemPosition); String monthUrlString = monthsForReportsAdapter.getQueryString(monthItemPosition);
String countryUrlString = queryRegionNames.get(countryNameTextView.getText().toString()); String countryUrlString = selectedCountryItem.getDownloadName();
tryUpdateData(monthUrlString, countryUrlString); tryUpdateData(monthUrlString, countryUrlString);
} }
@ -171,79 +164,12 @@ public class ReportsFragment extends BaseOsmAndFragment implements SearchSelecti
} }
@Override @Override
public void onSearchResult(String name) { public void onSearchResult(CountryItem item) {
countryNameTextView.setText(name); selectedCountryItem = item;
countryNameTextView.setText(item.getLocalName());
requestAndUpdateUi(); requestAndUpdateUi();
} }
private void initCountries() {
final WorldRegion root = getMyApplication().getRegions().getWorldRegion();
ArrayList<WorldRegion> groups = new ArrayList<>();
groups.add(root);
processGroup(root, groups, getActivity());
Collections.sort(groups, new Comparator<WorldRegion>() {
@Override
public int compare(WorldRegion lhs, WorldRegion rhs) {
if (lhs == root) {
return -1;
}
if (rhs == root) {
return 1;
}
return getHumanReadableName(lhs).compareTo(getHumanReadableName(rhs));
}
});
for (WorldRegion group : groups) {
String name = getHumanReadableName(group);
regionNames.add(name);
queryRegionNames.put(name, group == root ? "" : group.getRegionDownloadName());
}
}
private static String getHumanReadableName(WorldRegion group) {
String name;
if (group.getLevel() > 2 || (group.getLevel() == 2
&& group.getSuperregion().getRegionId().equals(WorldRegion.RUSSIA_REGION_ID))) {
WorldRegion parent = group.getSuperregion();
WorldRegion parentsParent = group.getSuperregion().getSuperregion();
if (group.getLevel() == 3) {
if (parentsParent.getRegionId().equals(WorldRegion.RUSSIA_REGION_ID)) {
name = parentsParent.getLocaleName() + " " + group.getLocaleName();
} else if (!parent.getRegionId().equals(WorldRegion.UNITED_KINGDOM_REGION_ID)) {
name = parent.getLocaleName() + " " + group.getLocaleName();
} else {
name = group.getLocaleName();
}
} else {
name = parent.getLocaleName() + " " + group.getLocaleName();
}
} else {
name = group.getLocaleName();
}
if (name == null) {
name = "";
}
return name;
}
public String getQueryString(int position) {
return queryRegionNames.get(position);
}
private static void processGroup(WorldRegion group,
List<WorldRegion> nameList,
Context context) {
if (group.isRegionMapDownload()) {
nameList.add(group);
}
if (group.getSubregions() != null) {
for (WorldRegion g : group.getSubregions()) {
processGroup(g, nameList, context);
}
}
}
private static class MonthsForReportsAdapter extends ArrayAdapter<String> { private static class MonthsForReportsAdapter extends ArrayAdapter<String> {
private static final SimpleDateFormat queryFormat = new SimpleDateFormat("yyyy-MM", Locale.US); private static final SimpleDateFormat queryFormat = new SimpleDateFormat("yyyy-MM", Locale.US);
@SuppressLint("SimpleDateFormat") @SuppressLint("SimpleDateFormat")
@ -325,18 +251,6 @@ public class ReportsFragment extends BaseOsmAndFragment implements SearchSelecti
} }
} }
public static class CountrySearchSelectionFragment extends SearchSelectionFragment {
@Override
protected ArrayList<String> getList() {
return ((ReportsFragment) getParentFragment()).regionNames;
}
@Override
protected int getListItemIcon() {
return R.drawable.ic_map;
}
}
private void enableProgress() { private void enableProgress() {
numberOfContributorsIcon.setImageDrawable(getPaintedContentIcon(R.drawable.ic_group, inactiveColor)); numberOfContributorsIcon.setImageDrawable(getPaintedContentIcon(R.drawable.ic_group, inactiveColor));
numberOfEditsIcon.setImageDrawable(getPaintedContentIcon(R.drawable.ic_map, inactiveColor)); numberOfEditsIcon.setImageDrawable(getPaintedContentIcon(R.drawable.ic_map, inactiveColor));

View file

@ -111,7 +111,7 @@ public abstract class SearchSelectionFragment extends BaseOsmAndDialogFragment {
private final Drawable drawableLeft; private final Drawable drawableLeft;
public ListAdapter(Context context, @DrawableRes int drawableLeftId) { public ListAdapter(Context context, @DrawableRes int drawableLeftId) {
super(getMyActivity(), android.R.layout.simple_list_item_1); super(getMyActivity(), R.layout.osmand_simple_list_item_1);
this.drawableLeft = drawableLeftId == -1 ? null : getContentIcon(drawableLeftId); this.drawableLeft = drawableLeftId == -1 ? null : getContentIcon(drawableLeftId);
} }

View file

@ -2,7 +2,6 @@ package net.osmand.plus.liveupdates;
import android.app.Activity; import android.app.Activity;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -10,61 +9,122 @@ import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView;
import net.osmand.AndroidNetworkUtils;
import net.osmand.AndroidUtils; import net.osmand.AndroidUtils;
import net.osmand.map.WorldRegion; import net.osmand.plus.OsmandApplication;
import net.osmand.plus.OsmandSettings; import net.osmand.plus.OsmandSettings;
import net.osmand.plus.R; import net.osmand.plus.R;
import net.osmand.plus.base.BaseOsmAndDialogFragment; import net.osmand.plus.base.BaseOsmAndDialogFragment;
import net.osmand.plus.inapp.InAppHelper; import net.osmand.plus.inapp.InAppHelper;
import net.osmand.plus.inapp.InAppHelper.InAppCallbacks; import net.osmand.plus.inapp.InAppHelper.InAppCallbacks;
import net.osmand.plus.liveupdates.SearchSelectionFragment.OnFragmentInteractionListener; import net.osmand.plus.liveupdates.CountrySelectionFragment.CountryItem;
import net.osmand.plus.liveupdates.CountrySelectionFragment.OnFragmentInteractionListener;
import net.osmand.util.Algorithms; import net.osmand.util.Algorithms;
import java.util.ArrayList; import org.json.JSONException;
import java.util.Collections; import org.json.JSONObject;
import java.util.Comparator;
import java.util.List;
public class SubscriptionFragment extends BaseOsmAndDialogFragment implements InAppCallbacks, OnFragmentInteractionListener{ import java.util.HashMap;
import java.util.Map;
public class SubscriptionFragment extends BaseOsmAndDialogFragment implements InAppCallbacks, OnFragmentInteractionListener {
public static final String TAG = "SubscriptionFragment"; public static final String TAG = "SubscriptionFragment";
private static final String EDIT_MODE_ID = "edit_mode_id";
private static final String USER_NAME_ID = "user_name_id";
private static final String EMAIL_ID = "email_id";
private static final String COUNTRY_ITEM_ID = "country_id";
private static final String HIDE_USER_NAME_ID = "hide_user_name_id";
private InAppHelper inAppHelper; private InAppHelper inAppHelper;
private OsmandSettings settings; private OsmandSettings settings;
private ProgressDialog dlg; private ProgressDialog dlg;
private boolean editMode;
private String userName; private String prevEmail;
private String email; private CountryItem selectedCountryItem;
private String country;
ArrayList<String> regionNames = new ArrayList<>(); private CountrySelectionFragment countrySelectionFragment = new CountrySelectionFragment();
private CountrySearchSelectionFragment searchSelectionFragment
= new CountrySearchSelectionFragment(); public void setEditMode(boolean editMode) {
this.editMode = editMode;
}
@Override
public void onSaveInstanceState(Bundle outState) {
outState.putBoolean(EDIT_MODE_ID, editMode);
View view = getView();
if (view != null) {
EditText userNameEdit = (EditText) view.findViewById(R.id.userNameEdit);
outState.putString(USER_NAME_ID, userNameEdit.getText().toString());
EditText emailEdit = (EditText) view.findViewById(R.id.emailEdit);
outState.putString(EMAIL_ID, emailEdit.getText().toString());
CheckBox hideUserNameCheckbox = (CheckBox) view.findViewById(R.id.hideUserNameCheckbox);
outState.putBoolean(HIDE_USER_NAME_ID, hideUserNameCheckbox.isChecked());
if (selectedCountryItem != null) {
outState.putSerializable(COUNTRY_ITEM_ID, selectedCountryItem);
}
}
super.onSaveInstanceState(outState);
}
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
editMode = savedInstanceState.getBoolean(EDIT_MODE_ID);
}
settings = getMyApplication().getSettings(); settings = getMyApplication().getSettings();
prevEmail = settings.BILLING_USER_EMAIL.get();
if (!editMode) {
inAppHelper = new InAppHelper(getMyApplication(), this); inAppHelper = new InAppHelper(getMyApplication(), this);
Activity activity = getActivity(); Activity activity = getActivity();
if (activity instanceof OsmLiveActivity) { if (activity instanceof OsmLiveActivity) {
((OsmLiveActivity) activity).setInAppHelper(inAppHelper); ((OsmLiveActivity) activity).setInAppHelper(inAppHelper);
} }
inAppHelper.start(false); inAppHelper.start(false);
} }
}
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
String userName = settings.BILLING_USER_NAME.get();
String email = settings.BILLING_USER_EMAIL.get();
String countryDownloadName = settings.BILLING_USER_COUNTRY_DOWNLOAD_NAME.get();
boolean hideUserName = settings.BILLING_HIDE_USER_NAME.get();
if (savedInstanceState != null) {
userName = savedInstanceState.getString(USER_NAME_ID);
email = savedInstanceState.getString(EMAIL_ID);
hideUserName = savedInstanceState.getBoolean(HIDE_USER_NAME_ID);
Object obj = savedInstanceState.getSerializable(COUNTRY_ITEM_ID);
if (obj instanceof CountryItem) {
selectedCountryItem = (CountryItem) obj;
countryDownloadName = selectedCountryItem.getDownloadName();
} else {
countryDownloadName = "";
}
}
View view = inflater.inflate(R.layout.subscription_fragment, container, false); View view = inflater.inflate(R.layout.subscription_fragment, container, false);
ImageButton closeButton = (ImageButton) view.findViewById(R.id.closeButton); ImageButton closeButton = (ImageButton) view.findViewById(R.id.closeButton);
if (editMode) {
closeButton.setImageDrawable(getMyApplication().getIconsCache().getIcon(R.drawable.ic_action_mode_back));
} else {
closeButton.setImageDrawable(getMyApplication().getIconsCache().getIcon(R.drawable.ic_action_remove_dark));
}
closeButton.setOnClickListener(new View.OnClickListener() { closeButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -72,67 +132,141 @@ public class SubscriptionFragment extends BaseOsmAndDialogFragment implements In
} }
}); });
initCountries(); TextView title = (TextView) view.findViewById(R.id.titleTextView);
if (editMode) {
title.setText(getString(R.string.osm_live_subscription_settings));
} else {
title.setText(getString(R.string.osm_live_subscription));
}
userName = settings.BILLING_USER_NAME.get();
final EditText userNameEdit = (EditText) view.findViewById(R.id.userNameEdit); final EditText userNameEdit = (EditText) view.findViewById(R.id.userNameEdit);
if (!Algorithms.isEmpty(userName)) { if (!Algorithms.isEmpty(userName)) {
userNameEdit.setText(userName); userNameEdit.setText(userName);
} }
email = settings.BILLING_USER_EMAIL.get();
final EditText emailEdit = (EditText) view.findViewById(R.id.emailEdit); final EditText emailEdit = (EditText) view.findViewById(R.id.emailEdit);
if (!Algorithms.isEmpty(email)) { if (!Algorithms.isEmpty(email)) {
emailEdit.setText(email); emailEdit.setText(email);
} }
country = settings.BILLING_USER_COUNTRY.get(); countrySelectionFragment.initCountries(getMyApplication());
if (Algorithms.isEmpty(countryDownloadName)) {
selectedCountryItem = countrySelectionFragment.getCountryItems().get(0);
} else {
selectedCountryItem = countrySelectionFragment.getCountryItem(countryDownloadName);
}
final EditText selectCountryEdit = (EditText) view.findViewById(R.id.selectCountryEdit); final EditText selectCountryEdit = (EditText) view.findViewById(R.id.selectCountryEdit);
if (!Algorithms.isEmpty(country)) { if (selectedCountryItem != null) {
selectCountryEdit.setText(country); selectCountryEdit.setText(selectedCountryItem.getLocalName());
} }
selectCountryEdit.setOnTouchListener(new View.OnTouchListener() { selectCountryEdit.setOnTouchListener(new View.OnTouchListener() {
@Override @Override
public boolean onTouch(View v, MotionEvent event) { public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) { if (event.getAction() == MotionEvent.ACTION_UP) {
SearchSelectionFragment countrySearchSelectionFragment = CountrySelectionFragment countryCountrySelectionFragment =
searchSelectionFragment; countrySelectionFragment;
countrySearchSelectionFragment countryCountrySelectionFragment
.show(getChildFragmentManager(), "CountriesSearchSelectionFragment"); .show(getChildFragmentManager(), "CountriesSearchSelectionFragment");
} }
return false; return false;
} }
}); });
Button subscribeButton = (Button) view.findViewById(R.id.subscribeButton); final CheckBox hideUserNameCheckbox = (CheckBox) view.findViewById(R.id.hideUserNameCheckbox);
hideUserNameCheckbox.setChecked(hideUserName);
View editModeBottomView = view.findViewById(R.id.editModeBottomView);
View purchaseCard = view.findViewById(R.id.purchaseCard);
if (editMode) {
editModeBottomView.setVisibility(View.VISIBLE);
purchaseCard.setVisibility(View.GONE);
Button saveChangesButton = (Button) view.findViewById(R.id.saveChangesButton);
saveChangesButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (applySettings(userNameEdit.getText().toString().trim(),
emailEdit.getText().toString().trim(), hideUserNameCheckbox.isChecked())) {
final Map<String, String> parameters = new HashMap<>();
parameters.put("visibleName", settings.BILLING_HIDE_USER_NAME.get() ? "" : settings.BILLING_USER_NAME.get());
parameters.put("preferredCountry", settings.BILLING_USER_COUNTRY_DOWNLOAD_NAME.get());
parameters.put("email", settings.BILLING_USER_EMAIL.get());
parameters.put("cemail", prevEmail);
parameters.put("userid", settings.BILLING_USER_ID.get());
showProgress();
AndroidNetworkUtils.sendRequestAsync(getMyApplication(),
"http://download.osmand.net/subscription/update.php",
parameters, "Sending data...", new AndroidNetworkUtils.OnRequestResultListener() {
@Override
public void onResult(String result) {
dismissProgress();
OsmandApplication app = getMyApplication();
if (result != null) {
try {
JSONObject obj = new JSONObject(result);
if (!obj.has("error")) {
String userId = obj.getString("userid");
app.getSettings().BILLING_USER_ID.set(userId);
String email = obj.getString("email");
app.getSettings().BILLING_USER_EMAIL.set(email);
String visibleName = obj.getString("visibleName");
if (!Algorithms.isEmpty(visibleName)) {
app.getSettings().BILLING_USER_NAME.set(visibleName);
app.getSettings().BILLING_HIDE_USER_NAME.set(false);
} else {
app.getSettings().BILLING_HIDE_USER_NAME.set(true);
}
String preferredCountry = obj.getString("preferredCountry");
app.getSettings().BILLING_USER_COUNTRY_DOWNLOAD_NAME.set(preferredCountry);
Fragment parent = getParentFragment();
if (parent != null && parent instanceof LiveUpdatesFragment) {
((LiveUpdatesFragment) parent).updateSubscriptionHeader();
}
dismiss();
} else {
app.showToastMessage("Error: " + obj.getString("error"));
}
} catch (JSONException e) {
app.showToastMessage(getString(R.string.shared_string_io_error));
}
} else {
app.showToastMessage(getString(R.string.shared_string_io_error));
}
}
});
}
}
});
} else {
editModeBottomView.setVisibility(View.GONE);
purchaseCard.setVisibility(View.VISIBLE);
updatePrice(view);
final Button subscribeButton = (Button) view.findViewById(R.id.subscribeButton);
subscribeButton.setOnClickListener(new View.OnClickListener() { subscribeButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
if (inAppHelper != null) { if (inAppHelper != null) {
userName = userNameEdit.getText().toString().trim(); if (applySettings(userNameEdit.getText().toString().trim(),
email = emailEdit.getText().toString().trim(); emailEdit.getText().toString().trim(), hideUserNameCheckbox.isChecked())) {
country = selectCountryEdit.getText().toString().trim();
if (Algorithms.isEmpty(userName)) { inAppHelper.purchaseLiveUpdates(getActivity(),
getMyApplication().showToastMessage("Please enter visible name"); settings.BILLING_USER_EMAIL.get(),
return; settings.BILLING_USER_NAME.get(),
settings.BILLING_USER_COUNTRY_DOWNLOAD_NAME.get(),
settings.BILLING_HIDE_USER_NAME.get());
} }
if (Algorithms.isEmpty(email) || !AndroidUtils.isValidEmail(email)) {
getMyApplication().showToastMessage("Please enter valid E-mail address");
return;
}
settings.BILLING_USER_NAME.set(userName);
settings.BILLING_USER_EMAIL.set(email);
settings.BILLING_USER_COUNTRY.set(country);
final WorldRegion world = getMyApplication().getRegions().getWorldRegion();
String countryParam = country.equals(world.getLocaleName()) ? "" : country;
inAppHelper.purchaseLiveUpdates(getActivity(), email, userName, countryParam);
} }
} }
}); });
}
setThemedDrawable((ImageView) view.findViewById(R.id.userNameIcon), R.drawable.ic_person); setThemedDrawable((ImageView) view.findViewById(R.id.userNameIcon), R.drawable.ic_person);
setThemedDrawable((ImageView) view.findViewById(R.id.emailIcon), R.drawable.ic_action_message); setThemedDrawable((ImageView) view.findViewById(R.id.emailIcon), R.drawable.ic_action_message);
@ -140,17 +274,15 @@ public class SubscriptionFragment extends BaseOsmAndDialogFragment implements In
selectCountryEdit.setCompoundDrawablesWithIntrinsicBounds( selectCountryEdit.setCompoundDrawablesWithIntrinsicBounds(
null, null, getContentIcon(R.drawable.ic_action_arrow_drop_down), null); null, null, getContentIcon(R.drawable.ic_action_arrow_drop_down), null);
dlg = new ProgressDialog(getActivity());
dlg.setTitle("");
dlg.setMessage(getString(R.string.wait_current_task_finished));
return view; return view;
} }
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
if (inAppHelper != null) {
inAppHelper.stop(); inAppHelper.stop();
}
if (dlg != null && dlg.isShowing()) { if (dlg != null && dlg.isShowing()) {
dlg.hide(); dlg.hide();
} }
@ -160,22 +292,44 @@ public class SubscriptionFragment extends BaseOsmAndDialogFragment implements In
} }
} }
private boolean applySettings(String userName, String email, boolean hideUserName) {
String countryName = selectedCountryItem != null ? selectedCountryItem.getLocalName() : "";
String countryDownloadName = selectedCountryItem != null ? selectedCountryItem.getDownloadName() : "";
if (Algorithms.isEmpty(email) || !AndroidUtils.isValidEmail(email)) {
getMyApplication().showToastMessage(getString(R.string.osm_live_enter_email));
return false;
}
if (Algorithms.isEmpty(userName) && !hideUserName) {
getMyApplication().showToastMessage(getString(R.string.osm_live_enter_user_name));
return false;
}
settings.BILLING_USER_NAME.set(userName);
settings.BILLING_USER_EMAIL.set(email);
settings.BILLING_USER_COUNTRY.set(countryName);
settings.BILLING_USER_COUNTRY_DOWNLOAD_NAME.set(countryDownloadName);
settings.BILLING_HIDE_USER_NAME.set(hideUserName);
return true;
}
@Override @Override
public void onError(String error) { public void onError(String error) {
} }
@Override @Override
public void onGetItems() { public void onGetItems() {
updatePrice(getView());
} }
@Override @Override
public void onItemPurchased(String sku) { public void onItemPurchased(String sku) {
if (InAppHelper.SKU_LIVE_UPDATES.equals(sku)) { if (InAppHelper.getSkuLiveUpdates().equals(sku)) {
Fragment parentFragment = getParentFragment(); Fragment parentFragment = getParentFragment();
if (parentFragment instanceof LiveUpdatesFragment) { if (parentFragment instanceof LiveUpdatesFragment) {
((LiveUpdatesFragment) parentFragment).updateSubscriptionBanner(); ((LiveUpdatesFragment) parentFragment).updateSubscriptionHeader();
} }
} }
@ -183,97 +337,46 @@ public class SubscriptionFragment extends BaseOsmAndDialogFragment implements In
} }
@Override @Override
public void showHideProgress(boolean show) { public void showProgress() {
if (show) { if (dlg != null) {
dlg.dismiss();
}
dlg = new ProgressDialog(getActivity());
dlg.setTitle("");
dlg.setMessage(getString(R.string.wait_current_task_finished));
dlg.setCancelable(false);
dlg.show(); dlg.show();
} else { }
dlg.hide();
@Override
public void dismissProgress() {
if (dlg != null) {
dlg.dismiss();
dlg = null;
} }
} }
@Override @Override
public void onSearchResult(String name) { public void onSearchResult(CountryItem item) {
selectedCountryItem = item;
View view = getView(); View view = getView();
if (view != null) { if (view != null) {
EditText selectCountryEdit = (EditText) view.findViewById(R.id.selectCountryEdit); EditText selectCountryEdit = (EditText) view.findViewById(R.id.selectCountryEdit);
if (selectCountryEdit != null) { if (selectCountryEdit != null) {
selectCountryEdit.setText(name); selectCountryEdit.setText(item.getLocalName());
} }
} }
} }
private void initCountries() { private void updatePrice(View view) {
final WorldRegion root = getMyApplication().getRegions().getWorldRegion(); if (view == null) {
ArrayList<WorldRegion> groups = new ArrayList<>(); view = getView();
groups.add(root);
processGroup(root, groups, getActivity());
Collections.sort(groups, new Comparator<WorldRegion>() {
@Override
public int compare(WorldRegion lhs, WorldRegion rhs) {
if (lhs == root) {
return -1;
} }
if (rhs == root) { if (view != null) {
return 1; TextView priceTextView = (TextView) view.findViewById(R.id.priceTextView);
if (InAppHelper.getLiveUpdatesPrice() != null) {
priceTextView.setText(InAppHelper.getLiveUpdatesPrice());
} }
return getHumanReadableName(lhs).compareTo(getHumanReadableName(rhs));
}
});
for (WorldRegion group : groups) {
String name = getHumanReadableName(group);
regionNames.add(name);
}
}
private static void processGroup(WorldRegion group,
List<WorldRegion> nameList,
Context context) {
if (group.isRegionMapDownload()) {
nameList.add(group);
}
if (group.getSubregions() != null) {
for (WorldRegion g : group.getSubregions()) {
processGroup(g, nameList, context);
}
}
}
private static String getHumanReadableName(WorldRegion group) {
String name;
if (group.getLevel() > 2 || (group.getLevel() == 2
&& group.getSuperregion().getRegionId().equals(WorldRegion.RUSSIA_REGION_ID))) {
WorldRegion parent = group.getSuperregion();
WorldRegion parentsParent = group.getSuperregion().getSuperregion();
if (group.getLevel() == 3) {
if (parentsParent.getRegionId().equals(WorldRegion.RUSSIA_REGION_ID)) {
name = parentsParent.getLocaleName() + " " + group.getLocaleName();
} else if (!parent.getRegionId().equals(WorldRegion.UNITED_KINGDOM_REGION_ID)) {
name = parent.getLocaleName() + " " + group.getLocaleName();
} else {
name = group.getLocaleName();
}
} else {
name = parent.getLocaleName() + " " + group.getLocaleName();
}
} else {
name = group.getLocaleName();
}
if (name == null) {
name = "";
}
return name;
}
public static class CountrySearchSelectionFragment extends SearchSelectionFragment {
@Override
protected ArrayList<String> getList() {
return ((SubscriptionFragment) getParentFragment()).regionNames;
}
@Override
protected int getListItemIcon() {
return R.drawable.ic_map;
} }
} }
} }

View file

@ -76,6 +76,8 @@ public class MapContextMenuFragment extends Fragment implements DownloadEvents {
private int menuBottomViewHeight; private int menuBottomViewHeight;
private int menuFullHeight; private int menuFullHeight;
private int menuFullHeightMax; private int menuFullHeightMax;
private int menuTopViewHeightExcludingTitle;
private int menuTitleTopBottomPadding;
private int screenHeight; private int screenHeight;
private int viewHeight; private int viewHeight;
@ -717,8 +719,19 @@ public class MapContextMenuFragment extends Fragment implements DownloadEvents {
menuFullHeight = view.findViewById(R.id.context_menu_main).getHeight(); menuFullHeight = view.findViewById(R.id.context_menu_main).getHeight();
int dy = 0; int dy = 0;
if (!menu.isLandscapeLayout() && menuTopViewHeight != 0) { if (!menu.isLandscapeLayout()) {
TextView line1 = (TextView) view.findViewById(R.id.context_menu_line1);
if (menuTopViewHeight != 0) {
int titleHeight = line1.getLineCount() * line1.getLineHeight() + menuTitleTopBottomPadding;
if (titleHeight < line1.getMeasuredHeight()) {
titleHeight = line1.getMeasuredHeight();
}
newMenuTopViewHeight = menuTopViewHeightExcludingTitle + titleHeight;
dy = Math.max(0, newMenuTopViewHeight - menuTopViewHeight - (newMenuTopShadowAllHeight - menuTopShadowAllHeight)); dy = Math.max(0, newMenuTopViewHeight - menuTopViewHeight - (newMenuTopShadowAllHeight - menuTopShadowAllHeight));
} else {
menuTopViewHeightExcludingTitle = newMenuTopViewHeight - line1.getMeasuredHeight();
menuTitleTopBottomPadding = line1.getMeasuredHeight() - line1.getLineCount() * line1.getLineHeight();
}
} }
menuTopViewHeight = newMenuTopViewHeight; menuTopViewHeight = newMenuTopViewHeight;
menuTopShadowAllHeight = newMenuTopShadowAllHeight; menuTopShadowAllHeight = newMenuTopShadowAllHeight;

View file

@ -1,13 +1,6 @@
package net.osmand.plus.osmo; package net.osmand.plus.osmo;
import java.util.ArrayList; import com.google.gson.Gson;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.osmand.Location; import net.osmand.Location;
import net.osmand.plus.GPXUtilities.WptPt; import net.osmand.plus.GPXUtilities.WptPt;
@ -24,6 +17,15 @@ import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class OsMoGroups implements OsMoReactor, OsmoTrackerListener { public class OsMoGroups implements OsMoReactor, OsmoTrackerListener {
private static final String GROUP_NAME = "name"; private static final String GROUP_NAME = "name";
@ -48,6 +50,8 @@ public class OsMoGroups implements OsMoReactor, OsmoTrackerListener {
private OsMoPlugin plugin; private OsMoPlugin plugin;
private OsmandApplication app; private OsmandApplication app;
private Gson gson = new Gson();
public interface OsMoGroupsUIListener { public interface OsMoGroupsUIListener {
public void groupsListChange(String operation, OsMoGroup group); public void groupsListChange(String operation, OsMoGroup group);
@ -69,7 +73,7 @@ public class OsMoGroups implements OsMoReactor, OsmoTrackerListener {
} }
public void addUiListeners(OsMoGroupsUIListener uiListener) { public void addUiListeners(OsMoGroupsUIListener uiListener) {
if (!uiListeners.contains(uiListener)){ if (!uiListeners.contains(uiListener)) {
uiListeners.add(uiListener); uiListeners.add(uiListener);
} }
} }
@ -115,9 +119,9 @@ public class OsMoGroups implements OsMoReactor, OsmoTrackerListener {
public String disconnectGroup(OsMoGroup model) { public String disconnectGroup(OsMoGroup model) {
model.enabled = false; model.enabled = false;
String operation = "GROUP_DISCONNECT:"+model.groupId; String operation = "GROUP_DISCONNECT:" + model.groupId;
service.pushCommand(operation); service.pushCommand(operation);
for(OsMoDevice d : model.getGroupUsers(null)) { for (OsMoDevice d : model.getGroupUsers(null)) {
tracker.stopTrackingId(d); tracker.stopTrackingId(d);
} }
storage.save(); storage.save();
@ -144,7 +148,7 @@ public class OsMoGroups implements OsMoReactor, OsmoTrackerListener {
for (OsMoGroup g : getGroups()) { for (OsMoGroup g : getGroups()) {
OsMoDevice d = g.updateLastLocation(trackerId, location); OsMoDevice d = g.updateLastLocation(trackerId, location);
if (d != null && uiListeners != null) { if (d != null && uiListeners != null) {
for(OsMoGroupsUIListener listener : uiListeners) { for (OsMoGroupsUIListener listener : uiListeners) {
listener.deviceLocationChanged(d); listener.deviceLocationChanged(d);
} }
} }
@ -157,47 +161,47 @@ public class OsMoGroups implements OsMoReactor, OsmoTrackerListener {
boolean processed = false; boolean processed = false;
String operation = command + ":" + gid; String operation = command + ":" + gid;
OsMoGroup group = null; OsMoGroup group = null;
if(command.equalsIgnoreCase("GP")) { if (command.equalsIgnoreCase("GP")) {
group = storage.getGroup(gid); group = storage.getGroup(gid);
if(group != null && gid.length() > 0) { if (group != null && gid.length() > 0) {
List<OsMoDevice> delta = mergeGroup(group, obj, false); List<OsMoDevice> delta = mergeGroup(group, obj, false);
String mygid = service.getMyGroupTrackerId(); String mygid = service.getMyGroupTrackerId();
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
for(OsMoDevice d : delta) { for (OsMoDevice d : delta) {
if(d.getDeletedTimestamp() != 0 && d.isEnabled()) { if (d.getDeletedTimestamp() != 0 && d.isEnabled()) {
if(group.name != null && !d.getTrackerId().equals(mygid)) { if (group.name != null && !d.getTrackerId().equals(mygid)) {
b.append(app.getString(R.string.osmo_user_left, d.getVisibleName(), group.getVisibleName(app))).append("\n"); b.append(app.getString(R.string.osmo_user_left, d.getVisibleName(), group.getVisibleName(app))).append("\n");
} }
disconnectImpl(d); disconnectImpl(d);
} else if(!d.isActive()) { } else if (!d.isActive()) {
if(group.name != null && !d.getTrackerId().equals(mygid)) { if (group.name != null && !d.getTrackerId().equals(mygid)) {
b.append(app.getString(R.string.osmo_user_joined, d.getVisibleName(), group.getVisibleName(app))).append("\n"); b.append(app.getString(R.string.osmo_user_joined, d.getVisibleName(), group.getVisibleName(app))).append("\n");
} }
connectDeviceImpl(d); connectDeviceImpl(d);
} }
} }
if(b.length() > 0 && app.getSettings().OSMO_SHOW_GROUP_NOTIFICATIONS.get()){ if (b.length() > 0 && app.getSettings().OSMO_SHOW_GROUP_NOTIFICATIONS.get()) {
app.showToastMessage(b.toString().trim()); app.showToastMessage(b.toString().trim());
} }
storage.save(); storage.save();
} }
processed = true; processed = true;
} else if(command.equalsIgnoreCase("GROUP_DISCONNECT")) { } else if (command.equalsIgnoreCase("GROUP_DISCONNECT")) {
group = storage.getGroup(gid); group = storage.getGroup(gid);
if(group != null) { if (group != null) {
disconnectAllGroupUsers(group); disconnectAllGroupUsers(group);
disableGroupTracks(group, group.groupTracks); disableGroupTracks(group, group.groupTracks);
disableGroupTracks(group, Collections.singleton(group.name + " points.gpx")); disableGroupTracks(group, Collections.singleton(group.name + " points.gpx"));
} }
processed = true; processed = true;
} else if(command.equalsIgnoreCase("GROUP_CONNECT")) { } else if (command.equalsIgnoreCase("GROUP_CONNECT")) {
group = storage.getGroup(gid); group = storage.getGroup(gid);
if(group != null) { if (group != null) {
mergeGroup(group, obj, true); mergeGroup(group, obj, true);
group.active = true; group.active = true;
// connect to enabled devices in group // connect to enabled devices in group
for(OsMoDevice d : group.getGroupUsers(null)) { for (OsMoDevice d : group.getGroupUsers(null)) {
if(d.isEnabled()) { if (d.isEnabled()) {
d.active = false; d.active = false;
connectDeviceImpl(d); connectDeviceImpl(d);
} }
@ -205,8 +209,8 @@ public class OsMoGroups implements OsMoReactor, OsmoTrackerListener {
storage.save(); storage.save();
} }
processed = true; processed = true;
} else if(command.equalsIgnoreCase("GROUP_CREATE") || command.equalsIgnoreCase("GROUP_JOIN") ) { } else if (command.equalsIgnoreCase("GROUP_CREATE") || command.equalsIgnoreCase("GROUP_JOIN")) {
if(command.equalsIgnoreCase("GROUP_CREATE")) { if (command.equalsIgnoreCase("GROUP_CREATE")) {
operation = command; operation = command;
try { try {
gid = obj.getString(GROUP_ID); gid = obj.getString(GROUP_ID);
@ -217,7 +221,7 @@ public class OsMoGroups implements OsMoReactor, OsmoTrackerListener {
} }
} }
group = storage.getGroup(gid); group = storage.getGroup(gid);
if(group == null) { if (group == null) {
group = new OsMoGroup(); group = new OsMoGroup();
group.groupId = gid; group.groupId = gid;
storage.addGroup(group); storage.addGroup(group);
@ -225,9 +229,9 @@ public class OsMoGroups implements OsMoReactor, OsmoTrackerListener {
connectGroupImpl(group); connectGroupImpl(group);
storage.save(); storage.save();
processed = true; processed = true;
} else if(command.equals("GROUP_LEAVE")) { } else if (command.equals("GROUP_LEAVE")) {
group = storage.getGroup(gid); group = storage.getGroup(gid);
if(group != null) { if (group != null) {
storage.deleteGroup(group); storage.deleteGroup(group);
} }
storage.save(); storage.save();
@ -248,8 +252,8 @@ public class OsMoGroups implements OsMoReactor, OsmoTrackerListener {
storage.save(); storage.save();
} }
processed = true; processed = true;
for(OsMoGroup g : storage.getGroups()) { for (OsMoGroup g : storage.getGroups()) {
if(!g.isMainGroup() && g.isEnabled()) { if (!g.isMainGroup() && g.isEnabled()) {
connectGroupImpl(g); connectGroupImpl(g);
} }
} }
@ -259,8 +263,8 @@ public class OsMoGroups implements OsMoReactor, OsmoTrackerListener {
return processed; return processed;
} }
} }
if(processed && uiListeners != null) { if (processed && uiListeners != null) {
for(OsMoGroupsUIListener listener : uiListeners){ for (OsMoGroupsUIListener listener : uiListeners) {
listener.groupsListChange(operation, group); listener.groupsListChange(operation, group);
} }
@ -269,11 +273,11 @@ public class OsMoGroups implements OsMoReactor, OsmoTrackerListener {
} }
private void disableGroupTracks(OsMoGroup group, Collection<String> tracks) { private void disableGroupTracks(OsMoGroup group, Collection<String> tracks) {
if(!tracks.isEmpty()) { if (!tracks.isEmpty()) {
GpxSelectionHelper helper = app.getSelectedGpxHelper(); GpxSelectionHelper helper = app.getSelectedGpxHelper();
for(String t : tracks) { for (String t : tracks) {
SelectedGpxFile sg = helper.getSelectedFileByName("osmo/"+t); SelectedGpxFile sg = helper.getSelectedFileByName("osmo/" + t);
if(sg != null && sg.getGpxFile() != null) { if (sg != null && sg.getGpxFile() != null) {
helper.selectGpxFile(sg.getGpxFile(), false, false); helper.selectGpxFile(sg.getGpxFile(), false, false);
} }
} }
@ -295,16 +299,16 @@ public class OsMoGroups implements OsMoReactor, OsmoTrackerListener {
private void parseGroup(JSONObject obj, OsMoGroup gr) { private void parseGroup(JSONObject obj, OsMoGroup gr) {
try { try {
if(obj.has(GROUP_NAME)) { if (obj.has(GROUP_NAME)) {
gr.name = obj.getString(GROUP_NAME); gr.name = obj.getString(GROUP_NAME);
} }
if(obj.has(DESCRIPTION)) { if (obj.has(DESCRIPTION)) {
gr.description = obj.getString(DESCRIPTION); gr.description = obj.getString(DESCRIPTION);
} }
if(obj.has(POLICY)) { if (obj.has(POLICY)) {
gr.policy = obj.getString(POLICY); gr.policy = obj.getString(POLICY);
} }
if(obj.has(EXPIRE_TIME)) { if (obj.has(EXPIRE_TIME)) {
gr.expireTime = obj.getLong(EXPIRE_TIME); gr.expireTime = obj.getLong(EXPIRE_TIME);
} }
} catch (JSONException e) { } catch (JSONException e) {
@ -316,7 +320,7 @@ public class OsMoGroups implements OsMoReactor, OsmoTrackerListener {
private void disconnectAllGroupUsers(OsMoGroup gr) { private void disconnectAllGroupUsers(OsMoGroup gr) {
gr.active = false; gr.active = false;
for(OsMoDevice d : gr.getGroupUsers(null)) { for (OsMoDevice d : gr.getGroupUsers(null)) {
disconnectImpl(d); disconnectImpl(d);
} }
} }
@ -325,7 +329,7 @@ public class OsMoGroups implements OsMoReactor, OsmoTrackerListener {
private List<OsMoDevice> mergeGroup(OsMoGroup gr, JSONObject obj, boolean deleteUsers) { private List<OsMoDevice> mergeGroup(OsMoGroup gr, JSONObject obj, boolean deleteUsers) {
List<OsMoDevice> delta = new ArrayList<OsMoDevice>(); List<OsMoDevice> delta = new ArrayList<OsMoDevice>();
try { try {
if(obj.has("group")) { if (obj.has("group")) {
parseGroup(obj.getJSONObject("group"), gr); parseGroup(obj.getJSONObject("group"), gr);
} }
Map<String, OsMoDevice> toDelete = new HashMap<String, OsMoDevice>(gr.users); Map<String, OsMoDevice> toDelete = new HashMap<String, OsMoDevice>(gr.users);
@ -404,12 +408,12 @@ public class OsMoGroups implements OsMoReactor, OsmoTrackerListener {
} }
points.add(pt); points.add(pt);
} }
if(points.size() > 0) { if (points.size() > 0) {
plugin.getSaveGpxTask(gr.name + " points", modify, false).execute(points.toArray(new WptPt[points.size()])); plugin.getSaveGpxTask(gr.name + " points", modify, false).execute(points.toArray(new WptPt[points.size()]));
} }
} }
if(deleteUsers) { if (deleteUsers) {
for(OsMoDevice s : toDelete.values()) { for (OsMoDevice s : toDelete.values()) {
s.deleted = System.currentTimeMillis(); s.deleted = System.currentTimeMillis();
delta.add(s); delta.add(s);
} }
@ -423,17 +427,10 @@ public class OsMoGroups implements OsMoReactor, OsmoTrackerListener {
public String createGroup(String groupName, boolean onlyByInvite, String description, String policy) { public String createGroup(String groupName, boolean onlyByInvite, String description, String policy) {
JSONObject obj = new JSONObject(); Protocol.CreateGroupData obj = new Protocol.CreateGroupData(groupName,
try { onlyByInvite, description, policy);
obj.put("name", groupName); service.pushCommand("GROUP_CREATE|" + gson.toJson(obj));
obj.put("onlyByInvite", onlyByInvite);
obj.put("description", description);
obj.put("policy", policy);
service.pushCommand("GROUP_CREATE|" + obj.toString());
return "GROUP_CREATE"; return "GROUP_CREATE";
} catch (JSONException e) {
throw new RuntimeException(e);
}
} }
@ -443,24 +440,24 @@ public class OsMoGroups implements OsMoReactor, OsmoTrackerListener {
} }
public String joinGroup(String groupId, String userName, String nick) { public String joinGroup(String groupId, String userName, String nick) {
final String op = "GROUP_JOIN:"+groupId+"|"+nick; final String op = "GROUP_JOIN:" + groupId + "|" + nick;
OsMoGroup g = storage.getGroup(groupId); OsMoGroup g = storage.getGroup(groupId);
if(g == null){ if (g == null) {
g = new OsMoGroup(); g = new OsMoGroup();
g.groupId = groupId; g.groupId = groupId;
storage.addGroup(g); storage.addGroup(g);
} }
g.userName = userName; g.userName = userName;
service.pushCommand(op); service.pushCommand(op);
return "GROUP_JOIN:"+groupId; return "GROUP_JOIN:" + groupId;
} }
public String leaveGroup(OsMoGroup group) { public String leaveGroup(OsMoGroup group) {
final String op = "GROUP_LEAVE:"+group.groupId; final String op = "GROUP_LEAVE:" + group.groupId;
storage.deleteGroup(group); storage.deleteGroup(group);
storage.save(); storage.save();
service.pushCommand(op); service.pushCommand(op);
if(group.isEnabled()) { if (group.isEnabled()) {
group.enabled = false; group.enabled = false;
disconnectAllGroupUsers(group); disconnectAllGroupUsers(group);
} }
@ -483,5 +480,4 @@ public class OsMoGroups implements OsMoReactor, OsmoTrackerListener {
storage.clearGroups(); storage.clearGroups();
storage.save(); storage.save();
} }
} }

View file

@ -617,9 +617,9 @@ public class OsMoGroupsActivity extends OsmandExpandableListActivity implements
if (!checkOperationIsNotRunning()) { if (!checkOperationIsNotRunning()) {
return; return;
} }
String operation = osMoPlugin.getGroups().leaveGroup((OsMoGroup) selectedObject); String operation = osMoPlugin.getGroups().leaveGroup(selectedObject);
startLongRunningOperation(operation); startLongRunningOperation(operation);
adapter.update((OsMoGroup) selectedObject); adapter.update(selectedObject);
adapter.notifyDataSetChanged(); adapter.notifyDataSetChanged();
} }
@ -1037,7 +1037,7 @@ public class OsMoGroupsActivity extends OsmandExpandableListActivity implements
} }
public void hideProgressBar() { public void hideProgressBar() {
OsMoGroupsActivity.this.operation = null; this.operation = null;
setSupportProgressBarIndeterminateVisibility(false); setSupportProgressBarIndeterminateVisibility(false);
} }
@ -1210,7 +1210,7 @@ public class OsMoGroupsActivity extends OsmandExpandableListActivity implements
} else { } else {
label.setTypeface(Typeface.DEFAULT, Typeface.ITALIC); label.setTypeface(Typeface.DEFAULT, Typeface.ITALIC);
} }
View v = (View) row.findViewById(R.id.settings); View v = row.findViewById(R.id.settings);
if(model.isMainGroup()) { if(model.isMainGroup()) {
v.setVisibility(View.GONE); v.setVisibility(View.GONE);
} else { } else {
@ -1383,7 +1383,7 @@ public class OsMoGroupsActivity extends OsmandExpandableListActivity implements
Message msg = Message.obtain(uiHandler, new Runnable() { Message msg = Message.obtain(uiHandler, new Runnable() {
@Override @Override
public void run() { public void run() {
adapter.notifyDataSetInvalidated(); adapter.notifyDataSetChanged();
updateStatus(); updateStatus();
} }
}); });

View file

@ -0,0 +1,17 @@
package net.osmand.plus.osmo;
public class Protocol {
public static class CreateGroupData {
public final String name;
public final boolean onlyByInvite;
public final String description;
public final String policy;
public CreateGroupData(String name, boolean onlyByInvite, String description, String policy) {
this.name = name;
this.onlyByInvite = onlyByInvite;
this.description = description;
this.policy = policy;
}
}
}

View file

@ -52,7 +52,7 @@ public class DoubleTapScaleDetector {
secondDown = null; secondDown = null;
if (isDoubleTapping) { if (isDoubleTapping) {
isDoubleTapping = false; isDoubleTapping = false;
listener.onZoomEnded(scale, 0); listener.onZoomEnded(scale);
return true; return true;
} else { } else {
firstUp = MotionEvent.obtain(event); firstUp = MotionEvent.obtain(event);
@ -77,7 +77,7 @@ public class DoubleTapScaleDetector {
float delta = convertPxToDp((int) (firstDown.getY() - event.getY())); float delta = convertPxToDp((int) (firstDown.getY() - event.getY()));
float scaleDelta = delta / (displayHeightPx / SCALE_PER_SCREEN); float scaleDelta = delta / (displayHeightPx / SCALE_PER_SCREEN);
scale = 1 - scaleDelta; scale = 1 - scaleDelta;
listener.onZoomingOrRotating(scale, 0); listener.onZooming(scale);
return true; return true;
} }
} }
@ -130,9 +130,9 @@ public class DoubleTapScaleDetector {
public interface DoubleTapZoomListener { public interface DoubleTapZoomListener {
public void onZoomStarted(PointF centerPoint); public void onZoomStarted(PointF centerPoint);
public void onZoomingOrRotating(double relativeToStart, float angle); public void onZooming(double relativeToStart);
public void onZoomEnded(double relativeToStart, float angleRelative); public void onZoomEnded(double relativeToStart);
public void onGestureInit(float x1, float y1, float x2, float y2); public void onGestureInit(float x1, float y1, float x2, float y2);
} }

View file

@ -1,16 +1,16 @@
package net.osmand.plus.views; package net.osmand.plus.views;
import java.lang.reflect.Method;
import net.osmand.PlatformUtil;
import net.osmand.util.MapUtils;
import org.apache.commons.logging.Log;
import android.content.Context; import android.content.Context;
import android.graphics.PointF; import android.graphics.PointF;
import android.view.MotionEvent; import android.view.MotionEvent;
import net.osmand.PlatformUtil;
import net.osmand.util.MapUtils;
import org.apache.commons.logging.Log;
import java.lang.reflect.Method;
public class MultiTouchSupport { public class MultiTouchSupport {
@ -29,7 +29,7 @@ public class MultiTouchSupport {
public void onZoomingOrRotating(double relativeToStart, float angle); public void onZoomingOrRotating(double relativeToStart, float angle);
public void onZoomEnded(double relativeToStart, float angleRelative); public void onZoomOrRotationEnded(double relativeToStart, float angleRelative);
public void onGestureInit(float x1, float y1, float x2, float y2); public void onGestureInit(float x1, float y1, float x2, float y2);
@ -87,7 +87,7 @@ public class MultiTouchSupport {
Integer pointCount = (Integer) getPointerCount.invoke(event); Integer pointCount = (Integer) getPointerCount.invoke(event);
if(pointCount < 2){ if(pointCount < 2){
if(inZoomMode){ if(inZoomMode){
listener.onZoomEnded(zoomRelative, angleRelative); listener.onZoomOrRotationEnded(zoomRelative, angleRelative);
} }
return false; return false;
} }
@ -112,7 +112,7 @@ public class MultiTouchSupport {
return true; return true;
} else if(actionCode == ACTION_POINTER_UP){ } else if(actionCode == ACTION_POINTER_UP){
if(inZoomMode){ if(inZoomMode){
listener.onZoomEnded(zoomRelative, angleRelative); listener.onZoomOrRotationEnded(zoomRelative, angleRelative);
inZoomMode = false; inZoomMode = false;
} }
return true; return true;

View file

@ -73,6 +73,9 @@ public class OsmandMapTileView implements IMapDownloaderCallback {
private OsmandApplication application; private OsmandApplication application;
protected OsmandSettings settings = null; protected OsmandSettings settings = null;
private int maxZoom;
private int minZoom;
private class FPSMeasurement { private class FPSMeasurement {
int fpsMeasureCount = 0; int fpsMeasureCount = 0;
int fpsMeasureMs = 0; int fpsMeasureMs = 0;
@ -167,8 +170,10 @@ public class OsmandMapTileView implements IMapDownloaderCallback {
@Override @Override
public void onTwoFingerTap() { public void onTwoFingerTap() {
afterTwoFingerTap = true; afterTwoFingerTap = true;
if (isZoomingAllowed(getZoom(), -1)) {
getAnimatedDraggingThread().startZooming(getZoom() - 1, currentViewport.getZoomFloatPart(), true); getAnimatedDraggingThread().startZooming(getZoom() - 1, currentViewport.getZoomFloatPart(), true);
} }
}
}; };
private int displayHeightPx; private int displayHeightPx;
@ -301,7 +306,9 @@ public class OsmandMapTileView implements IMapDownloaderCallback {
// ///////////////////////// NON UI PART (could be extracted in common) ///////////////////////////// // ///////////////////////// NON UI PART (could be extracted in common) /////////////////////////////
public void setIntZoom(int zoom) { public void setIntZoom(int zoom) {
if (mainLayer != null && zoom <= mainLayer.getMaximumShownMapZoom() && zoom >= mainLayer.getMinimumShownMapZoom()) { zoom = zoom > maxZoom ? maxZoom : zoom;
zoom = zoom < minZoom ? minZoom : zoom;
if (mainLayer != null) {
animatedDraggingThread.stopAnimating(); animatedDraggingThread.stopAnimating();
currentViewport.setZoomAndAnimation(zoom, 0, 0); currentViewport.setZoomAndAnimation(zoom, 0, 0);
currentViewport.setRotate(zoom > LOWEST_ZOOM_TO_ROTATE ? rotate : 0); currentViewport.setRotate(zoom > LOWEST_ZOOM_TO_ROTATE ? rotate : 0);
@ -310,7 +317,7 @@ public class OsmandMapTileView implements IMapDownloaderCallback {
} }
public void setComplexZoom(int zoom, double mapDensity) { public void setComplexZoom(int zoom, double mapDensity) {
if (mainLayer != null && zoom <= mainLayer.getMaximumShownMapZoom() && zoom >= mainLayer.getMinimumShownMapZoom()) { if (mainLayer != null && zoom <= maxZoom && zoom >= minZoom) {
animatedDraggingThread.stopAnimating(); animatedDraggingThread.stopAnimating();
currentViewport.setZoomAndAnimation(zoom, 0); currentViewport.setZoomAndAnimation(zoom, 0);
currentViewport.setMapDensity(mapDensity); currentViewport.setMapDensity(mapDensity);
@ -396,11 +403,13 @@ public class OsmandMapTileView implements IMapDownloaderCallback {
public void setMainLayer(BaseMapLayer mainLayer) { public void setMainLayer(BaseMapLayer mainLayer) {
this.mainLayer = mainLayer; this.mainLayer = mainLayer;
int zoom = currentViewport.getZoom(); int zoom = currentViewport.getZoom();
if (mainLayer.getMaximumShownMapZoom() < zoom) { maxZoom = mainLayer.getMaximumShownMapZoom() - 1;
zoom = mainLayer.getMaximumShownMapZoom(); minZoom = mainLayer.getMinimumShownMapZoom() + 1;
if (maxZoom < zoom) {
zoom = maxZoom;
} }
if (mainLayer.getMinimumShownMapZoom() > zoom) { if (minZoom > zoom) {
zoom = mainLayer.getMinimumShownMapZoom(); zoom = minZoom;
} }
currentViewport.setZoomAndAnimation(zoom, 0, 0); currentViewport.setZoomAndAnimation(zoom, 0, 0);
refreshMap(); refreshMap();
@ -736,7 +745,7 @@ public class OsmandMapTileView implements IMapDownloaderCallback {
// for internal usage // for internal usage
protected void zoomToAnimate(int zoom, double zoomToAnimate, boolean notify) { protected void zoomToAnimate(int zoom, double zoomToAnimate, boolean notify) {
if (mainLayer != null && mainLayer.getMaximumShownMapZoom() >= zoom && mainLayer.getMinimumShownMapZoom() <= zoom) { if (mainLayer != null && maxZoom >= zoom && minZoom <= zoom) {
currentViewport.setZoomAndAnimation(zoom, zoomToAnimate); currentViewport.setZoomAndAnimation(zoom, zoomToAnimate);
currentViewport.setRotate(zoom > LOWEST_ZOOM_TO_ROTATE ? rotate : 0); currentViewport.setRotate(zoom > LOWEST_ZOOM_TO_ROTATE ? rotate : 0);
refreshMap(); refreshMap();
@ -859,7 +868,7 @@ public class OsmandMapTileView implements IMapDownloaderCallback {
private static final float ANGLE_THRESHOLD = 15; private static final float ANGLE_THRESHOLD = 15;
@Override @Override
public void onZoomEnded(double relativeToStart, float angleRelative) { public void onZoomOrRotationEnded(double relativeToStart, float angleRelative) {
// 1.5 works better even on dm.density=1 devices // 1.5 works better even on dm.density=1 devices
float dz = (float) (Math.log(relativeToStart) / Math.log(2)) * 1.5f; float dz = (float) (Math.log(relativeToStart) / Math.log(2)) * 1.5f;
setIntZoom(Math.round(dz) + initialViewport.getZoom()); setIntZoom(Math.round(dz) + initialViewport.getZoom());
@ -880,6 +889,24 @@ public class OsmandMapTileView implements IMapDownloaderCallback {
} }
} }
@Override
public void onZoomEnded(double relativeToStart) {
// 1.5 works better even on dm.density=1 devices
float dz = (float) ((relativeToStart - 1) * DoubleTapScaleDetector.SCALE_PER_SCREEN);
setIntZoom(Math.round(dz) + initialViewport.getZoom());
final int newZoom = getZoom();
if (application.accessibilityEnabled()) {
if (newZoom != initialViewport.getZoom()) {
showMessage(application.getString(R.string.zoomIs) + " " + newZoom); //$NON-NLS-1$
} else {
final LatLon p1 = initialViewport.getLatLonFromPixel(x1, y1);
final LatLon p2 = initialViewport.getLatLonFromPixel(x2, y2);
showMessage(OsmAndFormatter.getFormattedDistance((float) MapUtils.getDistance(p1.getLatitude(), p1.getLongitude(), p2.getLatitude(), p2.getLongitude()), application));
}
}
}
@Override @Override
public void onGestureInit(float x1, float y1, float x2, float y2) { public void onGestureInit(float x1, float y1, float x2, float y2) {
this.x1 = x1; this.x1 = x1;
@ -912,7 +939,12 @@ public class OsmandMapTileView implements IMapDownloaderCallback {
if (dz != 0 || relAngle != 0) { if (dz != 0 || relAngle != 0) {
changeZoomPosition((float) dz, relAngle); changeZoomPosition((float) dz, relAngle);
} }
}
@Override
public void onZooming(double relativeToStart) {
double dz = (relativeToStart - 1) * DoubleTapScaleDetector.SCALE_PER_SCREEN;
changeZoomPosition((float) dz, 0);
} }
private void changeZoomPosition(float dz, float angle) { private void changeZoomPosition(float dz, float angle) {
@ -929,26 +961,39 @@ public class OsmandMapTileView implements IMapDownloaderCallback {
final LatLon r = calc.getLatLonFromPixel(cp.x + dx, cp.y + dy); final LatLon r = calc.getLatLonFromPixel(cp.x + dx, cp.y + dy);
setLatLon(r.getLatitude(), r.getLongitude()); setLatLon(r.getLatitude(), r.getLongitude());
int baseZoom = initialViewport.getZoom(); int baseZoom = initialViewport.getZoom();
LOG.debug("baseZoom=" + baseZoom); while (initialViewport.getZoomFloatPart() + dz > 1 && isZoomingAllowed(baseZoom, dz)) {
while (initialViewport.getZoomFloatPart() + dz > 1) {
dz--; dz--;
if (baseZoom < mainLayer.getMaximumShownMapZoom()) {
baseZoom++; baseZoom++;
} }
} while (initialViewport.getZoomFloatPart() + dz < 0 && isZoomingAllowed(baseZoom, dz)) {
while (initialViewport.getZoomFloatPart() + dz < 0) {
dz++; dz++;
if (baseZoom > mainLayer.getMinimumShownMapZoom()) {
baseZoom--; baseZoom--;
} }
if (!isZoomingAllowed(baseZoom, dz)) {
dz = 0;
} }
LOG.debug("baseZoom=" + baseZoom);
zoomToAnimate(baseZoom, dz, true); zoomToAnimate(baseZoom, dz, true);
rotateToAnimate(calcRotate); rotateToAnimate(calcRotate);
} }
} }
private boolean isZoomingAllowed(int baseZoom, float dz) {
if (baseZoom > maxZoom) {
return false;
}
if (baseZoom == maxZoom - 2 && dz > 1) {
return false;
}
if (baseZoom < minZoom) {
return false;
}
if (baseZoom == minZoom + 2 && dz < -1) {
return false;
}
return true;
}
private class MapTileViewOnGestureListener extends SimpleOnGestureListener { private class MapTileViewOnGestureListener extends SimpleOnGestureListener {
@Override @Override
public boolean onDown(MotionEvent e) { public boolean onDown(MotionEvent e) {
@ -1019,10 +1064,12 @@ public class OsmandMapTileView implements IMapDownloaderCallback {
@Override @Override
public boolean onDoubleTap(MotionEvent e) { public boolean onDoubleTap(MotionEvent e) {
if (isZoomingAllowed(getZoom(), 1)) {
final RotatedTileBox tb = getCurrentRotatedTileBox(); final RotatedTileBox tb = getCurrentRotatedTileBox();
final double lat = tb.getLatFromPixel(e.getX(), e.getY()); final double lat = tb.getLatFromPixel(e.getX(), e.getY());
final double lon = tb.getLonFromPixel(e.getX(), e.getY()); final double lon = tb.getLonFromPixel(e.getX(), e.getY());
getAnimatedDraggingThread().startMoving(lat, lon, getZoom() + 1, true); getAnimatedDraggingThread().startMoving(lat, lon, getZoom() + 1, true);
}
return true; return true;
} }
} }