diff options
Diffstat (limited to 'main/src')
189 files changed, 6746 insertions, 7096 deletions
diff --git a/main/src/android/support/v4/app/FragmentListActivity.java b/main/src/android/support/v4/app/FragmentListActivity.java deleted file mode 100644 index e3ed42c..0000000 --- a/main/src/android/support/v4/app/FragmentListActivity.java +++ /dev/null @@ -1,316 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v4.app; - -import android.os.Bundle; -import android.os.Handler; -import android.view.View; -import android.widget.AdapterView; -import android.widget.ListAdapter; -import android.widget.ListView; - -/** - * An activity that displays a list of items by binding to a data source such as - * an array or Cursor, and exposes event handlers when the user selects an item. - * <p> - * FragmentListActivity hosts a {@link android.widget.ListView ListView} object that can - * be bound to different data sources, typically either an array or a Cursor - * holding query results. Binding, screen layout, and row layout are discussed - * in the following sections. - * <p> - * <strong>Screen Layout</strong> - * </p> - * <p> - * FragmentListActivity has a default layout that consists of a single, full-screen list - * in the center of the screen. However, if you desire, you can customize the - * screen layout by setting your own view layout with setContentView() in - * onCreate(). To do this, your own view MUST contain a ListView object with the - * id "@android:id/list" (or {@link android.R.id#list} if it's in code) - * <p> - * Optionally, your custom view can contain another view object of any type to - * display when the list view is empty. This "empty list" notifier must have an - * id "android:empty". Note that when an empty view is present, the list view - * will be hidden when there is no data to display. - * <p> - * The following code demonstrates an (ugly) custom screen layout. It has a list - * with a green background, and an alternate red "no data" message. - * </p> - * - * <pre> - * <?xml version="1.0" encoding="utf-8"?> - * <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - * android:orientation="vertical" - * android:layout_width="fill_parent" - * android:layout_height="fill_parent" - * android:paddingLeft="8dp" - * android:paddingRight="8dp"> - * - * <ListView android:id="@id/android:list" - * android:layout_width="fill_parent" - * android:layout_height="fill_parent" - * android:background="#00FF00" - * android:layout_weight="1" - * android:drawSelectorOnTop="false"/> - * - * <TextView android:id="@id/android:empty" - * android:layout_width="fill_parent" - * android:layout_height="fill_parent" - * android:background="#FF0000" - * android:text="No data"/> - * </LinearLayout> - * </pre> - * - * <p> - * <strong>Row Layout</strong> - * </p> - * <p> - * You can specify the layout of individual rows in the list. You do this by - * specifying a layout resource in the ListAdapter object hosted by the activity - * (the ListAdapter binds the ListView to the data; more on this later). - * <p> - * A ListAdapter constructor takes a parameter that specifies a layout resource - * for each row. It also has two additional parameters that let you specify - * which data field to associate with which object in the row layout resource. - * These two parameters are typically parallel arrays. - * </p> - * <p> - * Android provides some standard row layout resources. These are in the - * {@link android.R.layout} class, and have names such as simple_list_item_1, - * simple_list_item_2, and two_line_list_item. The following layout XML is the - * source for the resource two_line_list_item, which displays two data - * fields,one above the other, for each list row. - * </p> - * - * <pre> - * <?xml version="1.0" encoding="utf-8"?> - * <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - * android:layout_width="fill_parent" - * android:layout_height="wrap_content" - * android:orientation="vertical"> - * - * <TextView android:id="@+id/text1" - * android:textSize="16sp" - * android:textStyle="bold" - * android:layout_width="fill_parent" - * android:layout_height="wrap_content"/> - * - * <TextView android:id="@+id/text2" - * android:textSize="16sp" - * android:layout_width="fill_parent" - * android:layout_height="wrap_content"/> - * </LinearLayout> - * </pre> - * - * <p> - * You must identify the data bound to each TextView object in this layout. The - * syntax for this is discussed in the next section. - * </p> - * <p> - * <strong>Binding to Data</strong> - * </p> - * <p> - * You bind the FragmentListActivity's ListView object to data using a class that - * implements the {@link android.widget.ListAdapter ListAdapter} interface. - * Android provides two standard list adapters: - * {@link android.widget.SimpleAdapter SimpleAdapter} for static data (Maps), - * and {@link android.widget.SimpleCursorAdapter SimpleCursorAdapter} for Cursor - * query results. - * </p> - * <p> - * The following code from a custom FragmentListActivity demonstrates querying the - * Contacts provider for all contacts, then binding the Name and Company fields - * to a two line row layout in the activity's ListView. - * </p> - * - * <pre> - * public class MyListAdapter extends FragmentListActivity { - * - * @Override - * protected void onCreate(Bundle savedInstanceState){ - * super.onCreate(savedInstanceState); - * - * // We'll define a custom screen layout here (the one shown above), but - * // typically, you could just use the standard FragmentListActivity layout. - * setContentView(R.layout.custom_list_activity_view); - * - * // Query for all people contacts using the {@link android.provider.Contacts.People} convenience class. - * // Put a managed wrapper around the retrieved cursor so we don't have to worry about - * // requerying or closing it as the activity changes state. - * mCursor = this.getContentResolver().query(People.CONTENT_URI, null, null, null, null); - * startManagingCursor(mCursor); - * - * // Now create a new list adapter bound to the cursor. - * // SimpleListAdapter is designed for binding to a Cursor. - * ListAdapter adapter = new SimpleCursorAdapter( - * this, // Context. - * android.R.layout.two_line_list_item, // Specify the row template to use (here, two columns bound to the two retrieved cursor - * rows). - * mCursor, // Pass in the cursor to bind to. - * new String[] {People.NAME, People.COMPANY}, // Array of cursor columns to bind to. - * new int[] {android.R.id.text1, android.R.id.text2}); // Parallel array of which template objects to bind to those columns. - * - * // Bind to our new adapter. - * setListAdapter(adapter); - * } - * } - * </pre> - * - * @see #setListAdapter - * @see android.widget.ListView - */ -public class FragmentListActivity extends FragmentActivity { - /** - * This field should be made private, so it is hidden from the SDK. - * {@hide} - */ - protected ListAdapter mAdapter; - /** - * This field should be made private, so it is hidden from the SDK. - * {@hide} - */ - protected ListView mList; - - private Handler mHandler = new Handler(); - private boolean mFinishedStart = false; - - private Runnable mRequestFocus = new Runnable() { - @Override - public void run() { - mList.focusableViewAvailable(mList); - } - }; - - /** - * This method will be called when an item in the list is selected. - * Subclasses should override. Subclasses can call - * getListView().getItemAtPosition(position) if they need to access the - * data associated with the selected item. - * - * @param l The ListView where the click happened - * @param v The view that was clicked within the ListView - * @param position The position of the view in the list - * @param id The row id of the item that was clicked - */ - protected void onListItemClick(ListView l, View v, int position, long id) { - } - - /** - * Ensures the list view has been created before Activity restores all - * of the view states. - * - *@see Activity#onRestoreInstanceState(Bundle) - */ - @Override - protected void onRestoreInstanceState(Bundle state) { - ensureList(); - super.onRestoreInstanceState(state); - } - - /** - * Updates the screen state (current list and other views) when the - * content changes. - * - * @see Activity#onContentChanged() - */ - @Override - public void onContentChanged() { - super.onContentChanged(); - View emptyView = findViewById(android.R.id.empty); - mList = (ListView)findViewById(android.R.id.list); - if (mList == null) { - throw new RuntimeException( - "Your content must have a ListView whose id attribute is " + - "'android.R.id.list'"); - } - if (emptyView != null) { - mList.setEmptyView(emptyView); - } - mList.setOnItemClickListener(mOnClickListener); - if (mFinishedStart) { - setListAdapter(mAdapter); - } - mHandler.post(mRequestFocus); - mFinishedStart = true; - } - - /** - * Provide the cursor for the list view. - */ - public void setListAdapter(ListAdapter adapter) { - synchronized (this) { - ensureList(); - mAdapter = adapter; - mList.setAdapter(adapter); - } - } - - /** - * Set the currently selected list item to the specified - * position with the adapter's data - * - * @param position - */ - public void setSelection(int position) { - mList.setSelection(position); - } - - /** - * Get the position of the currently selected list item. - */ - public int getSelectedItemPosition() { - return mList.getSelectedItemPosition(); - } - - /** - * Get the cursor row ID of the currently selected list item. - */ - public long getSelectedItemId() { - return mList.getSelectedItemId(); - } - - /** - * Get the activity's list view widget. - */ - public ListView getListView() { - ensureList(); - return mList; - } - - /** - * Get the ListAdapter associated with this activity's ListView. - */ - public ListAdapter getListAdapter() { - return mAdapter; - } - - private void ensureList() { - if (mList != null) { - return; - } - setContentView(android.R.layout.list_content); - - } - - private AdapterView.OnItemClickListener mOnClickListener = new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView<?> parent, View v, int position, long id) - { - onListItemClick((ListView)parent, v, position, id); - } - }; -} - diff --git a/main/src/cgeo/geocaching/AboutActivity.java b/main/src/cgeo/geocaching/AboutActivity.java index c154ffb..f947655 100644 --- a/main/src/cgeo/geocaching/AboutActivity.java +++ b/main/src/cgeo/geocaching/AboutActivity.java @@ -1,75 +1,73 @@ package cgeo.geocaching; import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.ui.AnchorAwareLinkMovementMethod; import cgeo.geocaching.utils.Version; +import com.googlecode.androidannotations.annotations.AfterViews; +import com.googlecode.androidannotations.annotations.Click; +import com.googlecode.androidannotations.annotations.EActivity; +import com.googlecode.androidannotations.annotations.ViewById; + import android.content.Intent; import android.net.Uri; import android.os.Bundle; -import android.text.method.LinkMovementMethod; -import android.view.View; import android.widget.TextView; +@EActivity public class AboutActivity extends AbstractActivity { + @ViewById(R.id.about_version_string) protected TextView version; + @ViewById(R.id.contributors) protected TextView contributors; + @ViewById(R.id.changelog) protected TextView changeLog; @Override public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setTheme(); - setContentView(R.layout.about_activity); - setTitle(res.getString(R.string.about)); + // TODO remove this after the theme has been fixed + super.onCreate(savedInstanceState, R.layout.about_activity); + } - ((TextView) findViewById(R.id.about_version_string)).setText(Version.getVersionName(this)); - ((TextView) findViewById(R.id.contributors)).setMovementMethod(LinkMovementMethod.getInstance()); - ((TextView) findViewById(R.id.changelog)).setMovementMethod(LinkMovementMethod.getInstance()); + @AfterViews + void initializeViews() { + version.setText(Version.getVersionName(this)); + contributors.setMovementMethod(AnchorAwareLinkMovementMethod.getInstance()); + changeLog.setMovementMethod(AnchorAwareLinkMovementMethod.getInstance()); } - /** - * @param view - * unused here but needed since this method is referenced from XML layout - */ - public void donate(View view) { + @Click(R.id.donate) + public void donate() { startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FMLNN8GXZKJEE"))); } - /** - * @param view - * unused here but needed since this method is referenced from XML layout - */ - public void support(View view) { + @Click(R.id.support) + public void support() { startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("mailto:support@cgeo.org"))); } - /** - * @param view - * unused here but needed since this method is referenced from XML layout - */ - public void website(View view) { + + @Click(R.id.website) + void webSite() { startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.cgeo.org/"))); } - /** - * @param view - * unused here but needed since this method is referenced from XML layout - */ - public void facebook(View view) { + @Click(R.id.facebook) + public void facebook() { startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.facebook.com/pages/cgeo/297269860090"))); } - /** - * @param view - * unused here but needed since this method is referenced from XML layout - */ - public void twitter(View view) { + @Click(R.id.twitter) + public void twitter() { startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://twitter.com/android_gc"))); } - /** - * @param view - * unused here but needed since this method is referenced from XML layout - */ - public void nutshellmanual(View view) { - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.cgeo.org/"))); + @Click(R.id.nutshellmanual) + public void nutshellmanual() { + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://manual.cgeo.org/"))); + } + + @Click(R.id.market) + public void market() { + final Intent marketIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + getPackageName())); + marketIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); + startActivity(marketIntent); } } diff --git a/main/src/cgeo/geocaching/AbstractLoggingActivity.java b/main/src/cgeo/geocaching/AbstractLoggingActivity.java index 37c3643..78da757 100644 --- a/main/src/cgeo/geocaching/AbstractLoggingActivity.java +++ b/main/src/cgeo/geocaching/AbstractLoggingActivity.java @@ -17,27 +17,17 @@ import android.view.SubMenu; import android.widget.EditText; public abstract class AbstractLoggingActivity extends AbstractActivity { - private static final int MENU_SIGNATURE = 1; - private static final int MENU_SMILEY = 2; - - protected AbstractLoggingActivity(String helpTopic) { - super(helpTopic); - } @Override public boolean onCreateOptionsMenu(final Menu menu) { - // signature menu - menu.add(0, MENU_SIGNATURE, 0, res.getString(R.string.init_signature)).setIcon(R.drawable.ic_menu_edit); + getMenuInflater().inflate(R.menu.abstract_logging_activity, menu); - // templates menu - final SubMenu menuLog = menu.addSubMenu(0, 0, 0, res.getString(R.string.log_add)).setIcon(R.drawable.ic_menu_add); + final SubMenu menuLog = menu.findItem(R.id.menu_templates).getSubMenu(); for (LogTemplate template : LogTemplateProvider.getTemplates()) { menuLog.add(0, template.getItemId(), 0, template.getResourceId()); } - menuLog.add(0, MENU_SIGNATURE, 0, res.getString(R.string.init_signature)); - // smilies - final SubMenu menuSmilies = menu.addSubMenu(0, MENU_SMILEY, 0, res.getString(R.string.log_smilies)).setIcon(R.drawable.ic_menu_emoticons); + final SubMenu menuSmilies = menu.findItem(R.id.menu_smilies).getSubMenu(); for (Smiley smiley : GCSmiliesProvider.getSmilies()) { menuSmilies.add(0, smiley.getItemId(), 0, smiley.text); } @@ -48,7 +38,7 @@ public abstract class AbstractLoggingActivity extends AbstractActivity { @Override public boolean onPrepareOptionsMenu(Menu menu) { final boolean signatureAvailable = StringUtils.isNotBlank(Settings.getSignature()); - menu.findItem(MENU_SIGNATURE).setVisible(signatureAvailable); + menu.findItem(R.id.menu_signature).setVisible(signatureAvailable); boolean smileyVisible = false; final Geocache cache = getLogContext().getCache(); @@ -60,7 +50,7 @@ public abstract class AbstractLoggingActivity extends AbstractActivity { smileyVisible = true; } - menu.findItem(MENU_SMILEY).setVisible(smileyVisible); + menu.findItem(R.id.menu_smilies).setVisible(smileyVisible); return true; } @@ -69,7 +59,7 @@ public abstract class AbstractLoggingActivity extends AbstractActivity { public boolean onOptionsItemSelected(MenuItem item) { final int id = item.getItemId(); - if (id == MENU_SIGNATURE) { + if (id == R.id.menu_signature) { insertIntoLog(LogTemplateProvider.applyTemplates(Settings.getSignature(), getLogContext()), true); return true; } diff --git a/main/src/cgeo/geocaching/AbstractPopupActivity.java b/main/src/cgeo/geocaching/AbstractPopupActivity.java index f903d00..64e5539 100644 --- a/main/src/cgeo/geocaching/AbstractPopupActivity.java +++ b/main/src/cgeo/geocaching/AbstractPopupActivity.java @@ -34,11 +34,6 @@ import android.widget.TextView; public abstract class AbstractPopupActivity extends AbstractActivity { - private static final int MENU_CACHES_AROUND = 5; - private static final int MENU_NAVIGATION = 3; - private static final int MENU_DEFAULT_NAVIGATION = 2; - private static final int MENU_SHOW_IN_BROWSER = 7; - protected Geocache cache = null; protected String geocode = null; protected CacheDetailsCreator details; @@ -52,7 +47,7 @@ public abstract class AbstractPopupActivity extends AbstractActivity { public void handleMessage(Message msg) { try { details.addRating(cache); - } catch (Exception e) { + } catch (final Exception e) { // nothing } } @@ -67,14 +62,24 @@ public abstract class AbstractPopupActivity extends AbstractActivity { cacheDistance.setText(Units.getDistanceFromKilometers(geo.getCoords().distanceTo(cache.getCoords()))); cacheDistance.bringToFront(); } - } catch (Exception e) { + onUpdateGeoData(geo); + } catch (final Exception e) { Log.w("Failed to UpdateLocation location."); } } }; - protected AbstractPopupActivity(String helpTopic, int layout) { - super(helpTopic); + /** + * Callback to run when new location information is available. + * This may be overridden by deriving classes. The default implementation does nothing. + * + * @param geo + * the new data + */ + public void onUpdateGeoData(final IGeoData geo) { + } + + protected AbstractPopupActivity(int layout) { this.layout = layout; } @@ -102,12 +107,6 @@ public abstract class AbstractPopupActivity extends AbstractActivity { }).start(); } - @Override - public void goManual(View view) { - super.goManual(view); - finish(); - } - protected void init() { cache = cgData.loadCache(geocode, LoadFlags.LOAD_CACHE_OR_DB); @@ -134,7 +133,6 @@ public abstract class AbstractPopupActivity extends AbstractActivity { this.setTheme(ActivityMixin.getDialogTheme()); // set layout setContentView(layout); - setTitle(res.getString(R.string.detail)); // get parameters final Bundle extras = getIntent().getExtras(); @@ -162,12 +160,7 @@ public abstract class AbstractPopupActivity extends AbstractActivity { @Override public boolean onCreateOptionsMenu(Menu menu) { - menu.add(0, MENU_DEFAULT_NAVIGATION, 0, NavigationAppFactory.getDefaultNavigationApplication().getName()).setIcon(R.drawable.ic_menu_compass); // default navigation tool - menu.add(0, MENU_NAVIGATION, 0, res.getString(R.string.cache_menu_navigate)).setIcon(R.drawable.ic_menu_mapmode); - LoggingUI.addMenuItems(menu, cache); - menu.add(0, MENU_CACHES_AROUND, 0, res.getString(R.string.cache_menu_around)).setIcon(R.drawable.ic_menu_rotate); // caches around - menu.add(0, MENU_SHOW_IN_BROWSER, 0, res.getString(R.string.cache_menu_browser)).setIcon(R.drawable.ic_menu_info_details); // browser - + getMenuInflater().inflate(R.menu.abstract_popup_activity, menu); return true; } @@ -176,16 +169,16 @@ public abstract class AbstractPopupActivity extends AbstractActivity { final int menuItem = item.getItemId(); switch (menuItem) { - case MENU_DEFAULT_NAVIGATION: + case R.id.menu_default_navigation: navigateTo(); return true; - case MENU_NAVIGATION: + case R.id.menu_navigate: showNavigationMenu(); return true; - case MENU_CACHES_AROUND: + case R.id.menu_caches_around: cachesAround(); return true; - case MENU_SHOW_IN_BROWSER: + case R.id.menu_show_in_browser: showInBrowser(); return true; default: @@ -209,12 +202,13 @@ public abstract class AbstractPopupActivity extends AbstractActivity { try { final boolean visible = getCoordinates() != null; - menu.findItem(MENU_DEFAULT_NAVIGATION).setVisible(visible); - menu.findItem(MENU_NAVIGATION).setVisible(visible); - menu.findItem(MENU_CACHES_AROUND).setVisible(visible); + menu.findItem(R.id.menu_default_navigation).setVisible(visible); + menu.findItem(R.id.menu_navigate).setVisible(visible); + menu.findItem(R.id.menu_caches_around).setVisible(visible); - LoggingUI.onPrepareOptionsMenu(menu); - } catch (Exception e) { + menu.findItem(R.id.menu_default_navigation).setTitle(NavigationAppFactory.getDefaultNavigationApplication().getName()); + LoggingUI.onPrepareOptionsMenu(menu, cache); + } catch (final Exception e) { // nothing } @@ -269,8 +263,8 @@ public abstract class AbstractPopupActivity extends AbstractActivity { aquireGCVote(); } - // favourite count - details.add(R.string.cache_favourite, cache.getFavoritePoints() + "×"); + // favorite count + details.add(R.string.cache_favorite, cache.getFavoritePoints() + "×"); // more details final Button buttonMore = (Button) findViewById(R.id.more_details); diff --git a/main/src/cgeo/geocaching/AddressListActivity.java b/main/src/cgeo/geocaching/AddressListActivity.java index b1de065..c984d28 100644 --- a/main/src/cgeo/geocaching/AddressListActivity.java +++ b/main/src/cgeo/geocaching/AddressListActivity.java @@ -20,11 +20,7 @@ public class AddressListActivity extends AbstractListActivity { @Override public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setTheme(); - setContentView(R.layout.addresses); - setTitle(res.getString(R.string.search_address_result)); + super.onCreate(savedInstanceState, R.layout.addresslist_activity); // get parameters final String keyword = getIntent().getStringExtra(Intents.EXTRA_KEYWORD); diff --git a/main/src/cgeo/geocaching/CacheDetailActivity.java b/main/src/cgeo/geocaching/CacheDetailActivity.java index 1662bb6..d140be1 100644 --- a/main/src/cgeo/geocaching/CacheDetailActivity.java +++ b/main/src/cgeo/geocaching/CacheDetailActivity.java @@ -1,5 +1,8 @@ package cgeo.geocaching; +import butterknife.InjectView; +import butterknife.Views; + import cgeo.calendar.ICalendar; import cgeo.geocaching.activity.AbstractViewPagerActivity; import cgeo.geocaching.activity.Progress; @@ -21,23 +24,26 @@ import cgeo.geocaching.network.Parameters; import cgeo.geocaching.ui.AbstractCachingPageViewCreator; import cgeo.geocaching.ui.AnchorAwareLinkMovementMethod; import cgeo.geocaching.ui.CacheDetailsCreator; +import cgeo.geocaching.ui.CoordinatesFormatSwitcher; import cgeo.geocaching.ui.DecryptTextClickListener; +import cgeo.geocaching.ui.EditNoteDialog; +import cgeo.geocaching.ui.EditNoteDialog.EditNoteDialogListener; import cgeo.geocaching.ui.Formatter; import cgeo.geocaching.ui.ImagesList; -import cgeo.geocaching.ui.ImagesList.ImageType; import cgeo.geocaching.ui.LoggingUI; +import cgeo.geocaching.ui.OwnerActionsClickListener; +import cgeo.geocaching.ui.UserActionsClickListener; import cgeo.geocaching.ui.WeakReferenceHandler; -import cgeo.geocaching.ui.dialog.EditorDialog; -import cgeo.geocaching.utils.BaseUtils; import cgeo.geocaching.utils.CancellableHandler; import cgeo.geocaching.utils.ClipboardUtils; import cgeo.geocaching.utils.CryptUtils; import cgeo.geocaching.utils.GeoDirHandler; import cgeo.geocaching.utils.HtmlUtils; -import cgeo.geocaching.utils.ImageHelper; +import cgeo.geocaching.utils.ImageUtils; import cgeo.geocaching.utils.Log; import cgeo.geocaching.utils.MatcherWrapper; import cgeo.geocaching.utils.RunnableWithArgument; +import cgeo.geocaching.utils.TextUtils; import cgeo.geocaching.utils.TranslationUtils; import cgeo.geocaching.utils.UnknownTagsHandler; @@ -65,12 +71,12 @@ import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.os.Message; +import android.support.v4.app.FragmentManager; import android.text.Editable; import android.text.Html; import android.text.Spannable; import android.text.Spanned; import android.text.format.DateUtils; -import android.text.method.LinkMovementMethod; import android.text.style.ForegroundColorSpan; import android.text.style.StrikethroughSpan; import android.text.style.StyleSpan; @@ -112,7 +118,8 @@ import java.util.regex.Pattern; * * e.g. details, description, logs, waypoints, inventory... */ -public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailActivity.Page> { +public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailActivity.Page> + implements EditNoteDialogListener { private static final int MENU_FIELD_COPY = 1; private static final int MENU_FIELD_TRANSLATE = 2; @@ -132,13 +139,21 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc private static final int CONTEXT_MENU_WAYPOINT_DEFAULT_NAVIGATION = 1240; private static final int CONTEXT_MENU_WAYPOINT_RESET_ORIGINAL_CACHE_COORDINATES = 1241; - private static final Pattern DARK_COLOR_PATTERN = Pattern.compile(Pattern.quote("color=\"#") + "(0[0-9]){3}" + "\""); + private static final Pattern[] DARK_COLOR_PATTERNS = { + Pattern.compile("((?<!bg)color)=\"#" + "(0[0-9]){3}" + "\"", Pattern.CASE_INSENSITIVE), + Pattern.compile("((?<!bg)color)=\"" + "black" + "\"", Pattern.CASE_INSENSITIVE), + Pattern.compile("((?<!bg)color)=\"#" + "000080" + "\"", Pattern.CASE_INSENSITIVE) }; + private static final Pattern[] LIGHT_COLOR_PATTERNS = { + Pattern.compile("((?<!bg)color)=\"#" + "([F][6-9A-F]){3}" + "\"", Pattern.CASE_INSENSITIVE), + Pattern.compile("((?<!bg)color)=\"" + "white" + "\"", Pattern.CASE_INSENSITIVE) }; public static final String STATE_PAGE_INDEX = "cgeo.geocaching.pageIndex"; private Geocache cache; private final Progress progress = new Progress(); private SearchResult search; + private EditNoteDialogListener editNoteDialogListener; + private final GeoDirHandler locationUpdater = new GeoDirHandler() { @Override public void updateGeoData(final IGeoData geo) { @@ -162,7 +177,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc cacheDistanceView.setText(dist.toString()); cacheDistanceView.bringToFront(); - } catch (Exception e) { + } catch (final Exception e) { Log.w("Failed to update location."); } } @@ -179,7 +194,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc // some views that must be available from everywhere // TODO: Reference can block GC? private TextView cacheDistanceView; - private Handler cacheChangeNotificationHandler = new Handler() { + private final Handler cacheChangeNotificationHandler = new Handler() { @Override public void handleMessage(Message msg) { notifyDataSetChanged(); @@ -187,18 +202,11 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc }; protected ImagesList imagesList; - public CacheDetailActivity() { - // identifier for manual - super("c:geolocation-cache-details"); - } - @Override public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + super.onCreate(savedInstanceState, R.layout.cachedetail_activity); - // initialize the main view and set a default title - setTheme(); - setContentView(R.layout.cacheview); + // set title in code, as the activity needs a hard coded title due to the intent filters setTitle(res.getString(R.string.cache)); String geocode = null; @@ -226,9 +234,9 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc // try to get data from URI if (geocode == null && guid == null && uri != null) { - String uriHost = uri.getHost().toLowerCase(Locale.US); - String uriPath = uri.getPath().toLowerCase(Locale.US); - String uriQuery = uri.getQuery(); + final String uriHost = uri.getHost().toLowerCase(Locale.US); + final String uriPath = uri.getPath().toLowerCase(Locale.US); + final String uriQuery = uri.getQuery(); if (uriQuery != null) { Log.i("Opening URI: " + uriHost + uriPath + "?" + uriQuery); @@ -259,6 +267,23 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc finish(); return; } + } else if (uriHost.contains("opencaching.de")) { + if (uriPath != null && uriPath.startsWith("/oc")) { + geocode = uriPath.substring(1).toUpperCase(Locale.US); + } else { + geocode = uri.getQueryParameter("wp"); + if (StringUtils.isNotBlank(geocode)) { + geocode = geocode.toUpperCase(Locale.US); + } else { + showToast(res.getString(R.string.err_detail_open)); + finish(); + return; + } + } + } else { + showToast(res.getString(R.string.err_detail_open)); + finish(); + return; } } @@ -279,11 +304,11 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc title = geocode; } progress.show(this, title, res.getString(R.string.cache_dialog_loading_details), true, loadCacheHandler.cancelMessage()); - } catch (Exception e) { + } catch (final Exception e) { // nothing, we lost the window } - ImageView defaultNavigationImageView = (ImageView) findViewById(R.id.defaultNavigation); + final ImageView defaultNavigationImageView = (ImageView) findViewById(R.id.defaultNavigation); defaultNavigationImageView.setOnLongClickListener(new OnLongClickListener() { @Override public boolean onLongClick(View v) { @@ -352,7 +377,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc switch (viewId) { case R.id.value: // coordinates, gc-code, name clickedItemText = ((TextView) view).getText(); - String itemTitle = (String) ((TextView) ((View) view.getParent()).findViewById(R.id.name)).getText(); + final String itemTitle = (String) ((TextView) ((View) view.getParent()).findViewById(R.id.name)).getText(); buildOptionsContextmenu(menu, viewId, itemTitle, true); break; case R.id.shortdesc: @@ -361,7 +386,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc break; case R.id.longdesc: // combine short and long description - String shortDesc = cache.getShortDescription(); + final String shortDesc = cache.getShortDescription(); if (StringUtils.isBlank(shortDesc)) { clickedItemText = ((TextView) view).getText(); } else { @@ -410,7 +435,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc break; } } - } catch (Exception e) { + } catch (final Exception e) { } } break; @@ -541,7 +566,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc NavigationAppFactory.addMenuItems(subMenu, cache); menu.add(0, MENU_CALENDAR, 0, res.getString(R.string.cache_menu_event)).setIcon(R.drawable.ic_menu_agenda); // add event to calendar - LoggingUI.addMenuItems(menu, cache); + LoggingUI.addMenuItems(this, menu, cache); menu.add(0, MENU_CACHES_AROUND, 0, res.getString(R.string.cache_menu_around)).setIcon(R.drawable.ic_menu_rotate); // caches around menu.add(0, MENU_BROWSER, 0, res.getString(R.string.cache_menu_browser)).setIcon(R.drawable.ic_menu_globe); // browser menu.add(0, MENU_SHARE, 0, res.getString(R.string.cache_menu_share)).setIcon(R.drawable.ic_menu_share); // share cache @@ -557,6 +582,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc menu.findItem(MENU_CACHES_AROUND).setVisible(null != cache.getCoords() && cache.supportsCachesAround()); menu.findItem(MENU_BROWSER).setVisible(cache.canOpenInBrowser()); } + LoggingUI.onPrepareOptionsMenu(menu, cache); return super.onPrepareOptionsMenu(menu); } @@ -764,7 +790,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc .setPositiveButton(getString(android.R.string.yes), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { - Intent intent = new Intent(Intent.ACTION_VIEW); + final Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse(ICalendar.CALENDAR_ADDON_URI)); startActivity(intent); } @@ -797,7 +823,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc /** * Wrapper for the referenced method in the xml-layout. */ - public void startDefaultNavigation(@SuppressWarnings("unused") View view) { + public void goDefaultNavigation(@SuppressWarnings("unused") View view) { startDefaultNavigation(); } @@ -808,100 +834,20 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc NavigationAppFactory.showNavigationMenu(this, cache, null, null, true, true); } - /** - * Listener for clicks on username - */ - private class UserActionsClickListener implements View.OnClickListener { - - @Override - public void onClick(View view) { - if (view == null) { - return; - } - if (!cache.supportsUserActions()) { - return; - } - - clickedItemText = ((TextView) view).getText().toString(); - showUserActionsDialog(clickedItemText); - } - } - - /** - * Listener for clicks on owner name - */ - private class OwnerActionsClickListener implements View.OnClickListener { - - @Override - public void onClick(View view) { - if (view == null) { - return; - } - if (!cache.supportsUserActions()) { - return; - } - - // Use real owner name vice the one owner chose to display - if (StringUtils.isNotBlank(cache.getOwnerUserId())) { - clickedItemText = cache.getOwnerUserId(); - } else { - clickedItemText = ((TextView) view).getText().toString(); - } - showUserActionsDialog(clickedItemText); - } - } - - /** - * Opens a dialog to do actions on an username - */ - private void showUserActionsDialog(final CharSequence name) { - final CharSequence[] items = { res.getString(R.string.user_menu_view_hidden), - res.getString(R.string.user_menu_view_found), - res.getString(R.string.user_menu_open_browser), - res.getString(R.string.user_menu_send_message) - }; - - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(res.getString(R.string.user_menu_title) + " " + name); - builder.setItems(items, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int item) { - switch (item) { - case 0: - cgeocaches.startActivityOwner(CacheDetailActivity.this, name.toString()); - return; - case 1: - cgeocaches.startActivityUserName(CacheDetailActivity.this, name.toString()); - return; - case 2: - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.geocaching.com/profile/?u=" + Network.encode(name.toString())))); - return; - case 3: - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.geocaching.com/email/?u=" + Network.encode(name.toString())))); - return; - default: - break; - } - } - }); - AlertDialog alert = builder.create(); - alert.show(); - } - private void loadCacheImages() { if (imagesList != null) { return; } - PageViewCreator creator = getViewCreator(Page.IMAGES); + final PageViewCreator creator = getViewCreator(Page.IMAGES); if (creator == null) { return; } - View imageView = creator.getView(); + final View imageView = creator.getView(); if (imageView == null) { return; } imagesList = new ImagesList(this, cache.getGeocode()); - imagesList.loadImages(imageView, cache.getImages(), ImageType.AllImages, false); + imagesList.loadImages(imageView, cache.getImages(), false); } public static void startActivity(final Context context, final String geocode) { @@ -1038,12 +984,12 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc noAttributeIconsFound = true; - for (String attributeName : cache.getAttributes()) { + for (final String attributeName : cache.getAttributes()) { // check if another attribute icon fits in this row attributeRow.measure(0, 0); - int rowWidth = attributeRow.getMeasuredWidth(); - FrameLayout fl = (FrameLayout) getLayoutInflater().inflate(R.layout.attribute_image, null); - ImageView iv = (ImageView) fl.getChildAt(0); + final int rowWidth = attributeRow.getMeasuredWidth(); + final FrameLayout fl = (FrameLayout) getLayoutInflater().inflate(R.layout.attribute_image, null); + final ImageView iv = (ImageView) fl.getChildAt(0); if ((parentWidth - rowWidth) < iv.getLayoutParams().width) { // make a new row attributeRow = newAttributeIconsRow(); @@ -1059,14 +1005,14 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc // strike through? if (strikethru) { // generate strikethru image with same properties as attribute image - ImageView strikethruImage = new ImageView(CacheDetailActivity.this); + final ImageView strikethruImage = new ImageView(CacheDetailActivity.this); strikethruImage.setLayoutParams(iv.getLayoutParams()); d = res.getDrawable(R.drawable.attribute__strikethru); strikethruImage.setImageDrawable(d); fl.addView(strikethruImage); } } else { - Drawable d = res.getDrawable(R.drawable.attribute_unknown); + final Drawable d = res.getDrawable(R.drawable.attribute_unknown); iv.setImageDrawable(d); } @@ -1077,7 +1023,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } private LinearLayout newAttributeIconsRow() { - LinearLayout rowLayout = new LinearLayout(CacheDetailActivity.this); + final LinearLayout rowLayout = new LinearLayout(CacheDetailActivity.this); rowLayout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); rowLayout.setOrientation(LinearLayout.HORIZONTAL); @@ -1131,7 +1077,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc return null; } - view = (ScrollView) getLayoutInflater().inflate(R.layout.cacheview_details, null); + view = (ScrollView) getLayoutInflater().inflate(R.layout.cachedetail_details_page, null); // Start loading preview map if (Settings.isStoreOfflineMaps()) { @@ -1142,7 +1088,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc final CacheDetailsCreator details = new CacheDetailsCreator(CacheDetailActivity.this, detailsList); // cache name (full name) - Spannable span = (new Spannable.Factory()).newSpannable(Html.fromHtml(cache.getName()).toString()); + final Spannable span = (new Spannable.Factory()).newSpannable(Html.fromHtml(cache.getName()).toString()); if (cache.isDisabled() || cache.isArchived()) { // strike span.setSpan(new StrikethroughSpan(), 0, span.toString().length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } @@ -1165,7 +1111,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc // favorite count if (cache.getFavoritePoints() > 0) { - details.add(R.string.cache_favourite, cache.getFavoritePoints() + "×"); + details.add(R.string.cache_favorite, cache.getFavoritePoints() + "×"); } // own rating @@ -1175,18 +1121,18 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc // cache author if (StringUtils.isNotBlank(cache.getOwnerDisplayName()) || StringUtils.isNotBlank(cache.getOwnerUserId())) { - TextView ownerView = details.add(R.string.cache_owner, ""); + final TextView ownerView = details.add(R.string.cache_owner, ""); if (StringUtils.isNotBlank(cache.getOwnerDisplayName())) { ownerView.setText(cache.getOwnerDisplayName(), TextView.BufferType.SPANNABLE); } else { // OwnerReal guaranteed to be not blank based on above ownerView.setText(cache.getOwnerUserId(), TextView.BufferType.SPANNABLE); } - ownerView.setOnClickListener(new OwnerActionsClickListener()); + ownerView.setOnClickListener(new OwnerActionsClickListener(cache)); } // cache hidden if (cache.getHiddenDate() != null) { - long time = cache.getHiddenDate().getTime(); + final long time = cache.getHiddenDate().getTime(); if (time > 0) { String dateString = Formatter.formatFullDate(time); if (cache.isEventCache()) { @@ -1203,24 +1149,8 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc // cache coordinates if (cache.getCoords() != null) { - TextView valueView = details.add(R.string.cache_coordinates, cache.getCoords().toString()); - valueView.setOnClickListener(new View.OnClickListener() { - private int position = 0; - private GeopointFormatter.Format[] availableFormats = new GeopointFormatter.Format[] { - GeopointFormatter.Format.LAT_LON_DECMINUTE, - GeopointFormatter.Format.LAT_LON_DECSECOND, - GeopointFormatter.Format.LAT_LON_DECDEGREE - }; - - // rotate coordinate formats on click - @Override - public void onClick(View view) { - position = (position + 1) % availableFormats.length; - - final TextView valueView = (TextView) view.findViewById(R.id.value); - valueView.setText(cache.getCoords().format(availableFormats[position])); - } - }); + final TextView valueView = details.add(R.string.cache_coordinates, cache.getCoords().toString()); + valueView.setOnClickListener(new CoordinatesFormatSwitcher(cache.getCoords())); registerForContextMenu(valueView); } @@ -1233,34 +1163,34 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc updateOfflineBox(view, cache, res, new RefreshCacheClickListener(), new DropCacheClickListener(), new StoreCacheClickListener()); // watchlist - Button buttonWatchlistAdd = (Button) view.findViewById(R.id.add_to_watchlist); - Button buttonWatchlistRemove = (Button) view.findViewById(R.id.remove_from_watchlist); + final Button buttonWatchlistAdd = (Button) view.findViewById(R.id.add_to_watchlist); + final Button buttonWatchlistRemove = (Button) view.findViewById(R.id.remove_from_watchlist); buttonWatchlistAdd.setOnClickListener(new AddToWatchlistClickListener()); buttonWatchlistRemove.setOnClickListener(new RemoveFromWatchlistClickListener()); updateWatchlistBox(); // favorite points - Button buttonFavPointAdd = (Button) view.findViewById(R.id.add_to_favpoint); - Button buttonFavPointRemove = (Button) view.findViewById(R.id.remove_from_favpoint); + final Button buttonFavPointAdd = (Button) view.findViewById(R.id.add_to_favpoint); + final Button buttonFavPointRemove = (Button) view.findViewById(R.id.remove_from_favpoint); buttonFavPointAdd.setOnClickListener(new FavoriteAddClickListener()); buttonFavPointRemove.setOnClickListener(new FavoriteRemoveClickListener()); updateFavPointBox(); // list - Button buttonChangeList = (Button) view.findViewById(R.id.change_list); + final Button buttonChangeList = (Button) view.findViewById(R.id.change_list); buttonChangeList.setOnClickListener(new ChangeListClickListener()); updateListBox(); // data license - IConnector connector = ConnectorFactory.getConnector(cache); + final IConnector connector = ConnectorFactory.getConnector(cache); if (connector != null) { - String license = connector.getLicenseText(cache); + final String license = connector.getLicenseText(cache); if (StringUtils.isNotBlank(license)) { view.findViewById(R.id.license_box).setVisibility(View.VISIBLE); - TextView licenseView = ((TextView) view.findViewById(R.id.license)); + final TextView licenseView = ((TextView) view.findViewById(R.id.license)); licenseView.setText(Html.fromHtml(license), BufferType.SPANNABLE); licenseView.setClickable(true); - licenseView.setMovementMethod(LinkMovementMethod.getInstance()); + licenseView.setMovementMethod(AnchorAwareLinkMovementMethod.getInstance()); } else { view.findViewById(R.id.license_box).setVisibility(View.GONE); } @@ -1356,6 +1286,11 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc return; } + if (!Network.isNetworkConnected(getApplicationContext())) { + showToast(getString(R.string.err_server)); + return; + } + final RefreshCacheHandler refreshCacheHandler = new RefreshCacheHandler(); progress.show(CacheDetailActivity.this, res.getString(R.string.cache_dialog_refresh_title), res.getString(R.string.cache_dialog_refresh_message), true, refreshCacheHandler.cancelMessage()); @@ -1482,7 +1417,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc @Override public void run() { - handler.sendEmptyMessage(GCConnector.addToWatchlist(cache) ? 1 : -1); + handler.sendEmptyMessage(ConnectorFactory.getConnector(cache).addToWatchlist(cache) ? 1 : -1); } } @@ -1496,11 +1431,11 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc @Override public void run() { - handler.sendEmptyMessage(GCConnector.removeFromWatchlist(cache) ? 1 : -1); + handler.sendEmptyMessage(ConnectorFactory.getConnector(cache).removeFromWatchlist(cache) ? 1 : -1); } } - /** Thread to add this cache to the favourite list of the user */ + /** Thread to add this cache to the favorite list of the user */ private class FavoriteAddThread extends Thread { private final Handler handler; @@ -1514,7 +1449,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } } - /** Thread to remove this cache to the favourite list of the user */ + /** Thread to remove this cache to the favorite list of the user */ private class FavoriteRemoveThread extends Thread { private final Handler handler; @@ -1541,25 +1476,25 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } /** - * Listener for "add to favourites" button + * Listener for "add to favorites" button */ private class FavoriteAddClickListener extends AbstractWatchlistClickListener { @Override public void onClick(View arg0) { - doExecute(R.string.cache_dialog_favourite_add_title, - R.string.cache_dialog_favourite_add_message, + doExecute(R.string.cache_dialog_favorite_add_title, + R.string.cache_dialog_favorite_add_message, new FavoriteAddThread(new FavoriteUpdateHandler())); } } /** - * Listener for "remove from favourites" button + * Listener for "remove from favorites" button */ private class FavoriteRemoveClickListener extends AbstractWatchlistClickListener { @Override public void onClick(View arg0) { - doExecute(R.string.cache_dialog_favourite_remove_title, - R.string.cache_dialog_favourite_remove_message, + doExecute(R.string.cache_dialog_favorite_remove_title, + R.string.cache_dialog_favorite_remove_message, new FavoriteRemoveThread(new FavoriteUpdateHandler())); } } @@ -1600,15 +1535,15 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc * shows/hides buttons, sets text in watchlist box */ private void updateWatchlistBox() { - LinearLayout layout = (LinearLayout) view.findViewById(R.id.watchlist_box); - boolean supportsWatchList = cache.supportsWatchList(); + final LinearLayout layout = (LinearLayout) view.findViewById(R.id.watchlist_box); + final boolean supportsWatchList = cache.supportsWatchList(); layout.setVisibility(supportsWatchList ? View.VISIBLE : View.GONE); if (!supportsWatchList) { return; } - Button buttonAdd = (Button) view.findViewById(R.id.add_to_watchlist); - Button buttonRemove = (Button) view.findViewById(R.id.remove_from_watchlist); - TextView text = (TextView) view.findViewById(R.id.watchlist_text); + final Button buttonAdd = (Button) view.findViewById(R.id.add_to_watchlist); + final Button buttonRemove = (Button) view.findViewById(R.id.remove_from_watchlist); + final TextView text = (TextView) view.findViewById(R.id.watchlist_text); if (cache.isOnWatchlist() || cache.isOwner()) { buttonAdd.setVisibility(View.GONE); @@ -1634,15 +1569,15 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc * shows/hides buttons, sets text in watchlist box */ private void updateFavPointBox() { - LinearLayout layout = (LinearLayout) view.findViewById(R.id.favpoint_box); - boolean supportsFavoritePoints = cache.supportsFavoritePoints(); + final LinearLayout layout = (LinearLayout) view.findViewById(R.id.favpoint_box); + final boolean supportsFavoritePoints = cache.supportsFavoritePoints(); layout.setVisibility(supportsFavoritePoints ? View.VISIBLE : View.GONE); if (!supportsFavoritePoints || cache.isOwner() || !Settings.isPremiumMember()) { return; } - Button buttonAdd = (Button) view.findViewById(R.id.add_to_favpoint); - Button buttonRemove = (Button) view.findViewById(R.id.remove_from_favpoint); - TextView text = (TextView) view.findViewById(R.id.favpoint_text); + final Button buttonAdd = (Button) view.findViewById(R.id.add_to_favpoint); + final Button buttonRemove = (Button) view.findViewById(R.id.remove_from_favpoint); + final TextView text = (TextView) view.findViewById(R.id.favpoint_text); if (cache.isFavorite()) { buttonAdd.setVisibility(View.GONE); @@ -1667,15 +1602,15 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc * shows/hides/updates list box */ private void updateListBox() { - View box = view.findViewById(R.id.list_box); + final View box = view.findViewById(R.id.list_box); if (cache.isOffline()) { // show box box.setVisibility(View.VISIBLE); // update text - TextView text = (TextView) view.findViewById(R.id.list_text); - StoredList list = cgData.getList(cache.getListId()); + final TextView text = (TextView) view.findViewById(R.id.list_text); + final StoredList list = cgData.getList(cache.getListId()); if (list != null) { text.setText(res.getString(R.string.cache_list_text) + " " + list.title); } else { @@ -1719,8 +1654,8 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } } - return ImageHelper.scaleBitmapToFitDisplay(image); - } catch (Exception e) { + return ImageUtils.scaleBitmapToFitDisplay(image); + } catch (final Exception e) { Log.w("CacheDetailActivity.PreviewMapTask", e); return null; } @@ -1744,7 +1679,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc ((ImageView) view.findViewById(R.id.map_preview)).setImageDrawable(image); view.findViewById(R.id.map_preview_box).setVisibility(View.VISIBLE); - } catch (Exception e) { + } catch (final Exception e) { Log.e("CacheDetailActivity.PreviewMapTask", e); } } @@ -1752,7 +1687,9 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } - private class DescriptionViewCreator extends AbstractCachingPageViewCreator<ScrollView> { + protected class DescriptionViewCreator extends AbstractCachingPageViewCreator<ScrollView> { + + @InjectView(R.id.personalnote) protected TextView personalNoteView; @Override public ScrollView getDispatchedView() { @@ -1761,12 +1698,12 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc return null; } - view = (ScrollView) getLayoutInflater().inflate(R.layout.cacheview_description, null); + view = (ScrollView) getLayoutInflater().inflate(R.layout.cachedetail_description_page, null); + Views.inject(this, view); // cache short description if (StringUtils.isNotBlank(cache.getShortDescription())) { - new LoadDescriptionTask().execute(cache.getShortDescription(), view.findViewById(R.id.shortdesc), null); - registerForContextMenu(view.findViewById(R.id.shortdesc)); + new LoadDescriptionTask(cache.getShortDescription(), view.findViewById(R.id.shortdesc), null, null).execute(); } // long description @@ -1774,7 +1711,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc if (Settings.isAutoLoadDescription()) { loadLongDescription(); } else { - Button showDesc = (Button) view.findViewById(R.id.show_description); + final Button showDesc = (Button) view.findViewById(R.id.show_description); showDesc.setVisibility(View.VISIBLE); showDesc.setOnClickListener(new View.OnClickListener() { @Override @@ -1786,31 +1723,20 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } // cache personal note - final TextView personalNoteView = (TextView) view.findViewById(R.id.personalnote); - setPersonalNote(personalNoteView); - personalNoteView.setMovementMethod(LinkMovementMethod.getInstance()); + setPersonalNote(); + personalNoteView.setMovementMethod(AnchorAwareLinkMovementMethod.getInstance()); registerForContextMenu(personalNoteView); final Button personalNoteEdit = (Button) view.findViewById(R.id.edit_personalnote); - if (cache.isOffline()) { - personalNoteEdit.setVisibility(View.VISIBLE); - personalNoteEdit.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - EditorDialog editor = new EditorDialog(CacheDetailActivity.this, personalNoteView.getText()); - editor.setOnEditorUpdate(new EditorDialog.EditorUpdate() { - @Override - public void update(CharSequence editorText) { - cache.setPersonalNote(editorText.toString()); - setPersonalNote(personalNoteView); - cgData.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); - } - }); - editor.show(); + personalNoteEdit.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (cache.isOffline()) { + editPersonalNote(); + } else { + warnPersonalNoteNeedsStoring(); } - }); - } else { - personalNoteEdit.setVisibility(View.INVISIBLE); - } + } + }); // cache hint and spoiler images final View hintBoxView = view.findViewById(R.id.hint_box); @@ -1822,7 +1748,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc final TextView hintView = ((TextView) view.findViewById(R.id.hint)); if (StringUtils.isNotBlank(cache.getHint())) { - if (BaseUtils.containsHtml(cache.getHint())) { + if (TextUtils.containsHtml(cache.getHint())) { hintView.setText(Html.fromHtml(cache.getHint(), new HtmlImage(cache.getGeocode(), false, cache.getListId(), false), null), TextView.BufferType.SPANNABLE); hintView.setText(CryptUtils.rot13((Spannable) hintView.getText())); } @@ -1863,29 +1789,77 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc return view; } - private void setPersonalNote(final TextView personalNoteView) { + private void editPersonalNote() { + if (cache.isOffline()) { + editNoteDialogListener = new EditNoteDialogListener() { + @Override + public void onFinishEditNoteDialog(final String note) { + cache.setPersonalNote(note); + cache.parseWaypointsFromNote(); + setPersonalNote(); + cgData.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); + CacheDetailActivity.this.notifyDataSetChanged(); + } + }; + final FragmentManager fm = getSupportFragmentManager(); + final EditNoteDialog dialog = EditNoteDialog.newInstance(cache.getPersonalNote()); + dialog.show(fm, "fragment_edit_note"); + } + } + + private void setPersonalNote() { final String personalNote = cache.getPersonalNote(); personalNoteView.setText(personalNote, TextView.BufferType.SPANNABLE); if (StringUtils.isNotBlank(personalNote)) { personalNoteView.setVisibility(View.VISIBLE); - } - else { + } else { personalNoteView.setVisibility(View.GONE); } } private void loadLongDescription() { - Button showDesc = (Button) view.findViewById(R.id.show_description); + final Button showDesc = (Button) view.findViewById(R.id.show_description); showDesc.setVisibility(View.GONE); showDesc.setOnClickListener(null); view.findViewById(R.id.loading).setVisibility(View.VISIBLE); - new LoadDescriptionTask().execute(cache.getDescription(), view.findViewById(R.id.longdesc), view.findViewById(R.id.loading)); - registerForContextMenu(view.findViewById(R.id.longdesc)); + new LoadDescriptionTask(cache.getDescription(), view.findViewById(R.id.longdesc), view.findViewById(R.id.loading), view.findViewById(R.id.shortdesc)).execute(); + } + + private void warnPersonalNoteNeedsStoring() { + final AlertDialog.Builder builder = new AlertDialog.Builder(CacheDetailActivity.this); + builder.setTitle(R.string.cache_personal_note_unstored); + builder.setMessage(R.string.cache_personal_note_store); + builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + // do nothing + } + }); + + builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + cache.store(null); + editPersonalNote(); + } + + }); + final AlertDialog dialog = builder.create(); + dialog.setOwnerActivity(CacheDetailActivity.this); + dialog.show(); } } + @Override + public void onFinishEditNoteDialog(final String note) { + editNoteDialogListener.onFinishEditNoteDialog(note); + } + private static class HtmlImageCounter implements Html.ImageGetter { private int imageCount = 0; @@ -1912,28 +1886,33 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc * </ol> */ private class LoadDescriptionTask extends AsyncTask<Object, Void, Void> { - private View loadingIndicatorView; - private TextView descriptionView; - private String descriptionString; + private final View loadingIndicatorView; + private final TextView descriptionView; + private final String descriptionString; private Spanned description; + private final View shortDescView; + public LoadDescriptionTask(final String description, final View descriptionView, final View loadingIndicatorView, final View shortDescView) { + this.descriptionString = description; + this.descriptionView = (TextView) descriptionView; + this.loadingIndicatorView = loadingIndicatorView; + this.shortDescView = shortDescView; + } @Override protected Void doInBackground(Object... params) { try { - descriptionString = ((String) params[0]); - descriptionView = (TextView) params[1]; - loadingIndicatorView = (View) params[2]; - // Fast preview: parse only HTML without loading any images - HtmlImageCounter imageCounter = new HtmlImageCounter(); + final HtmlImageCounter imageCounter = new HtmlImageCounter(); final UnknownTagsHandler unknownTagsHandler = new UnknownTagsHandler(); description = Html.fromHtml(descriptionString, imageCounter, unknownTagsHandler); publishProgress(); + + boolean needsRefresh = false; if (imageCounter.getImageCount() > 0) { // Complete view: parse again with loading images - if necessary ! If there are any images causing problems the user can see at least the preview description = Html.fromHtml(descriptionString, new HtmlImage(cache.getGeocode(), true, cache.getListId(), false), unknownTagsHandler); - publishProgress(); + needsRefresh = true; } // If description has an HTML construct which may be problematic to render, add a note at the end of the long description. @@ -1945,56 +1924,94 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc final Spanned tableNote = Html.fromHtml(res.getString(R.string.cache_description_table_note, "<a href=\"" + cache.getUrl() + "\">" + connector.getName() + "</a>")); ((Editable) description).append("\n\n").append(tableNote); ((Editable) description).setSpan(new StyleSpan(Typeface.ITALIC), startPos, description.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + needsRefresh = true; + } + + if (needsRefresh) { publishProgress(); } - } catch (Exception e) { + } catch (final Exception e) { Log.e("LoadDescriptionTask: ", e); } return null; } - /* - * (non-Javadoc) - * - * @see android.os.AsyncTask#onProgressUpdate(Progress[]) - */ @Override protected void onProgressUpdate(Void... values) { - if (description != null) { - if (StringUtils.isNotBlank(descriptionString)) { + if (description == null) { + showToast(res.getString(R.string.err_load_descr_failed)); + return; + } + if (StringUtils.isNotBlank(descriptionString)) { + try { descriptionView.setText(description, TextView.BufferType.SPANNABLE); - descriptionView.setMovementMethod(AnchorAwareLinkMovementMethod.getInstance()); - fixBlackTextColor(descriptionView, descriptionString); - } - + } catch (final Exception e) { + // On 4.1, there is sometimes a crash on measuring the layout: https://code.google.com/p/android/issues/detail?id=35412 + Log.e("Android bug setting text: ", e); + // remove the formatting by converting to a simple string + descriptionView.setText(description.toString()); + } + descriptionView.setMovementMethod(AnchorAwareLinkMovementMethod.getInstance()); + fixTextColor(descriptionView, descriptionString); descriptionView.setVisibility(View.VISIBLE); - } else { - showToast(res.getString(R.string.err_load_descr_failed)); + registerForContextMenu(descriptionView); + + hideDuplicatedShortDescription(); } + } + /** + * Hide the short description, if it is contained somewhere at the start of the long description. + */ + private void hideDuplicatedShortDescription() { + if (shortDescView != null) { + final String shortDescription = cache.getShortDescription(); + if (StringUtils.isNotBlank(shortDescription)) { + final int index = descriptionString.indexOf(shortDescription); + if (index >= 0 && index < 200) { + shortDescView.setVisibility(View.GONE); + } + } + } + } + + @Override + protected void onPostExecute(Void result) { if (null != loadingIndicatorView) { loadingIndicatorView.setVisibility(View.GONE); } } /** - * handle caches with black font color + * Handle caches with black font color in dark skin and white font color in light skin + * by changing background color of the view * * @param view + * containing the text * @param text + * to be checked */ - private void fixBlackTextColor(final TextView view, final String text) { + private void fixTextColor(final TextView view, final String text) { + int backcolor; if (Settings.isLightSkin()) { - return; - } - int backcolor = color.black; - if (-1 != StringUtils.indexOfAny(text, new String[] { "color=\"black", "color=\"#000080\"" })) { - backcolor = color.darker_gray; - } - else { - MatcherWrapper matcher = new MatcherWrapper(DARK_COLOR_PATTERN, text); - if (matcher.find()) { - backcolor = color.darker_gray; + backcolor = color.white; + + for (final Pattern pattern : LIGHT_COLOR_PATTERNS) { + final MatcherWrapper matcher = new MatcherWrapper(pattern, text); + if (matcher.find()) { + view.setBackgroundResource(color.darker_gray); + return; + } + } + } else { + backcolor = color.black; + + for (final Pattern pattern : DARK_COLOR_PATTERNS) { + final MatcherWrapper matcher = new MatcherWrapper(pattern, text); + if (matcher.find()) { + view.setBackgroundResource(color.darker_gray); + return; + } } } view.setBackgroundResource(backcolor); @@ -2015,13 +2032,13 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc return null; } - view = (ListView) getLayoutInflater().inflate(R.layout.cacheview_logs, null); + view = (ListView) getLayoutInflater().inflate(R.layout.cachedetail_logs_page, null); // log count final Map<LogType, Integer> logCounts = cache.getLogCounts(); if (logCounts != null) { final List<Entry<LogType, Integer>> sortedLogCounts = new ArrayList<Entry<LogType, Integer>>(logCounts.size()); - for (Entry<LogType, Integer> entry : logCounts.entrySet()) { + for (final Entry<LogType, Integer> entry : logCounts.entrySet()) { // it may happen that the label is unknown -> then avoid any output for this type if (entry.getKey() != LogType.PUBLISH_LISTING && entry.getKey().getL10n() != null) { sortedLogCounts.add(entry); @@ -2038,8 +2055,8 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } }); - ArrayList<String> labels = new ArrayList<String>(sortedLogCounts.size()); - for (Entry<LogType, Integer> pair : sortedLogCounts) { + final ArrayList<String> labels = new ArrayList<String>(sortedLogCounts.size()); + for (final Entry<LogType, Integer> pair : sortedLogCounts) { labels.add(pair.getValue() + "× " + pair.getKey().getL10n()); } @@ -2050,27 +2067,26 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } final List<LogEntry> logs = allLogs ? cache.getLogs() : cache.getFriendsLogs(); - view.setAdapter(new ArrayAdapter<LogEntry>(CacheDetailActivity.this, R.layout.cacheview_logs_item, logs) { - final UserActionsClickListener userActionsClickListener = new UserActionsClickListener(); + view.setAdapter(new ArrayAdapter<LogEntry>(CacheDetailActivity.this, R.layout.logs_item, logs) { + final UserActionsClickListener userActionsClickListener = new UserActionsClickListener(cache); final DecryptTextClickListener decryptTextClickListener = new DecryptTextClickListener(); @Override public View getView(final int position, final View convertView, final ViewGroup parent) { View rowView = convertView; if (null == rowView) { - rowView = getLayoutInflater().inflate(R.layout.cacheview_logs_item, null); + rowView = getLayoutInflater().inflate(R.layout.logs_item, null); } LogViewHolder holder = (LogViewHolder) rowView.getTag(); if (null == holder) { holder = new LogViewHolder(rowView); - rowView.setTag(holder); } holder.setPosition(position); final LogEntry log = getItem(position); if (log.date > 0) { - holder.date.setText(Formatter.formatShortDate(log.date)); + holder.date.setText(Formatter.formatShortDateVerbally(log.date)); holder.date.setVisibility(View.VISIBLE); } else { holder.date.setVisibility(View.GONE); @@ -2080,29 +2096,29 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc holder.author.setText(StringEscapeUtils.unescapeHtml4(log.author)); // finds count - holder.count.setVisibility(View.VISIBLE); + holder.countOrLocation.setVisibility(View.VISIBLE); if (log.found == -1) { - holder.count.setVisibility(View.GONE); + holder.countOrLocation.setVisibility(View.GONE); } else { - holder.count.setText(res.getQuantityString(R.plurals.cache_counts, log.found, log.found)); + holder.countOrLocation.setText(res.getQuantityString(R.plurals.cache_counts, log.found, log.found)); } // logtext, avoid parsing HTML if not necessary String logText = log.log; - if (BaseUtils.containsHtml(logText)) { + if (TextUtils.containsHtml(logText)) { logText = log.getDisplayText(); // Fast preview: parse only HTML without loading any images - HtmlImageCounter imageCounter = new HtmlImageCounter(); + final HtmlImageCounter imageCounter = new HtmlImageCounter(); final UnknownTagsHandler unknownTagsHandler = new UnknownTagsHandler(); - holder.text.setText(Html.fromHtml(logText, imageCounter, unknownTagsHandler)); + holder.text.setText(Html.fromHtml(logText, imageCounter, unknownTagsHandler), TextView.BufferType.SPANNABLE); if (imageCounter.getImageCount() > 0) { // Complete view: parse again with loading images - if necessary ! If there are any images causing problems the user can see at least the preview - LogImageLoader loader = new LogImageLoader(holder); - loader.execute(new String[] { logText }); + final LogImageLoader loader = new LogImageLoader(holder); + loader.execute(logText); } } else { - holder.text.setText(logText); + holder.text.setText(logText, TextView.BufferType.SPANNABLE); } // images @@ -2120,19 +2136,19 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } // colored marker - int marker = log.type.markerId; + final int marker = log.type.markerId; if (marker != 0) { - holder.statusMarker.setVisibility(View.VISIBLE); - holder.statusMarker.setImageResource(marker); + holder.marker.setVisibility(View.VISIBLE); + holder.marker.setImageResource(marker); } else { - holder.statusMarker.setVisibility(View.GONE); + holder.marker.setVisibility(View.GONE); } if (null == convertView) { // if convertView != null then this listeners are already set holder.author.setOnClickListener(userActionsClickListener); - holder.text.setMovementMethod(LinkMovementMethod.getInstance()); + holder.text.setMovementMethod(AnchorAwareLinkMovementMethod.getInstance()); holder.text.setOnClickListener(decryptTextClickListener); registerForContextMenu(holder.text); } @@ -2170,35 +2186,6 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } - private class LogViewHolder { - final TextView date; - final TextView type; - final TextView author; - final TextView count; - final TextView text; - final TextView images; - final ImageView statusMarker; - private int position; - - public LogViewHolder(final View base) { - date = (TextView) base.findViewById(R.id.added); - type = (TextView) base.findViewById(R.id.type); - author = (TextView) base.findViewById(R.id.author); - count = (TextView) base.findViewById(R.id.count); - text = (TextView) base.findViewById(R.id.log); - images = (TextView) base.findViewById(R.id.log_images); - statusMarker = (ImageView) base.findViewById(R.id.log_mark); - } - - public int getPosition() { - return position; - } - - public void setPosition(final int position) { - this.position = position; - } - - } } private class WaypointsViewCreator extends AbstractCachingPageViewCreator<ScrollView> { @@ -2210,7 +2197,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc return null; } - view = (ScrollView) getLayoutInflater().inflate(R.layout.cacheview_waypoints, null); + view = (ScrollView) getLayoutInflater().inflate(R.layout.cachedetail_waypoints_page, null); final LinearLayout waypoints = (LinearLayout) view.findViewById(R.id.waypoints); @@ -2224,6 +2211,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc // coordinates if (null != wpt.getCoords()) { final TextView coordinatesView = (TextView) waypointView.findViewById(R.id.coordinates); + coordinatesView.setOnClickListener(new CoordinatesFormatSwitcher(wpt.getCoords())); coordinatesView.setText(wpt.getCoords().toString()); coordinatesView.setVisibility(View.VISIBLE); } @@ -2249,7 +2237,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc // visited if (wpt.isVisited()) { - TypedValue a = new TypedValue(); + final TypedValue a = new TypedValue(); getTheme().resolveAttribute(R.attr.text_color_grey, a, true); if (a.type >= TypedValue.TYPE_FIRST_COLOR_INT && a.type <= TypedValue.TYPE_LAST_COLOR_INT) { // really should be just a color! @@ -2261,7 +2249,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc if (StringUtils.isNotBlank(wpt.getNote())) { final TextView noteView = (TextView) waypointView.findViewById(R.id.note); noteView.setVisibility(View.VISIBLE); - if (BaseUtils.containsHtml(wpt.getNote())) { + if (TextUtils.containsHtml(wpt.getNote())) { noteView.setText(Html.fromHtml(wpt.getNote()), TextView.BufferType.SPANNABLE); } else { @@ -2292,6 +2280,15 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } }); + waypointView.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + EditWaypointActivity.startActivityEditWaypoint(CacheDetailActivity.this, wpt.getId()); + refreshOnResume = true; + return true; + } + }); + waypoints.addView(waypointView); } @@ -2319,7 +2316,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc return null; } - view = (ListView) getLayoutInflater().inflate(R.layout.cacheview_inventory, null); + view = (ListView) getLayoutInflater().inflate(R.layout.cachedetail_inventory_page, null); // TODO: fix layout, then switch back to Android-resource and delete copied one // this copy is modified to respect the text color @@ -2327,9 +2324,9 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc view.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) { - Object selection = arg0.getItemAtPosition(arg2); + final Object selection = arg0.getItemAtPosition(arg2); if (selection instanceof Trackable) { - Trackable trackable = (Trackable) selection; + final Trackable trackable = (Trackable) selection; TrackableActivity.startActivity(CacheDetailActivity.this, trackable.getGuid(), trackable.getGeocode(), trackable.getName()); } } @@ -2347,7 +2344,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc return null; // something is really wrong } - view = getLayoutInflater().inflate(R.layout.caches_images, null); + view = getLayoutInflater().inflate(R.layout.cachedetail_images_page, null); if (imagesList == null && isCurrentPage(Page.IMAGES)) { loadCacheImages(); } @@ -2374,10 +2371,10 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc */ private AlertDialog createResetCacheCoordinatesDialog(final Geocache cache, final Waypoint wpt) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); + final AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.waypoint_reset_cache_coords); - String[] items = new String[] { res.getString(R.string.waypoint_localy_reset_cache_coords), res.getString(R.string.waypoint_reset_local_and_remote_cache_coords) }; + final String[] items = new String[] { res.getString(R.string.waypoint_localy_reset_cache_coords), res.getString(R.string.waypoint_reset_local_and_remote_cache_coords) }; builder.setSingleChoiceItems(items, 0, new DialogInterface.OnClickListener() { @Override @@ -2429,7 +2426,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc private final boolean local; private final boolean remote; private final Waypoint wpt; - private ProgressDialog progress; + private final ProgressDialog progress; public static final int LOCAL = 0; public static final int ON_WEBSITE = 1; @@ -2459,7 +2456,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc handler.sendEmptyMessage(LOCAL); } - IConnector con = ConnectorFactory.getConnector(cache); + final IConnector con = ConnectorFactory.getConnector(cache); if (remote && con.supportsOwnCoordinates()) { runOnUiThread(new Runnable() { @Override @@ -2560,7 +2557,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc final Button offlineStore = (Button) view.findViewById(R.id.offline_store); if (cache.isOffline()) { - long diff = (System.currentTimeMillis() / (60 * 1000)) - (cache.getDetailedUpdate() / (60 * 1000)); // minutes + final long diff = (System.currentTimeMillis() / (60 * 1000)) - (cache.getDetailedUpdate() / (60 * 1000)); // minutes String ago; if (diff < 15) { diff --git a/main/src/cgeo/geocaching/CachePopup.java b/main/src/cgeo/geocaching/CachePopup.java index e6d0148..39a896a 100644 --- a/main/src/cgeo/geocaching/CachePopup.java +++ b/main/src/cgeo/geocaching/CachePopup.java @@ -3,6 +3,7 @@ package cgeo.geocaching; import cgeo.geocaching.activity.Progress; import cgeo.geocaching.apps.cache.navi.NavigationAppFactory; import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.network.Network; import cgeo.geocaching.ui.CacheDetailsCreator; import cgeo.geocaching.utils.CancellableHandler; import cgeo.geocaching.utils.Log; @@ -64,7 +65,7 @@ public class CachePopup extends AbstractPopupActivity { } public CachePopup() { - super("c:geo-cache-info", R.layout.popup); + super(R.layout.popup); } @Override @@ -93,7 +94,7 @@ public class CachePopup extends AbstractPopupActivity { CacheDetailActivity.updateOfflineBox(findViewById(android.R.id.content), cache, res, new RefreshCacheClickListener(), new DropCacheClickListener(), new StoreCacheClickListener()); } catch (Exception e) { - Log.e("cgeopopup.init", e); + Log.e("CachePopup.init", e); } // cache is loaded. remove progress-popup if any there @@ -160,6 +161,11 @@ public class CachePopup extends AbstractPopupActivity { return; } + if (!Network.isNetworkConnected(getApplicationContext())) { + showToast(getString(R.string.err_server)); + return; + } + final RefreshCacheHandler refreshCacheHandler = new RefreshCacheHandler(); progress.show(CachePopup.this, res.getString(R.string.cache_dialog_refresh_title), res.getString(R.string.cache_dialog_refresh_message), true, refreshCacheHandler.cancelMessage()); new RefreshCacheThread(refreshCacheHandler).start(); diff --git a/main/src/cgeo/geocaching/cgeonavigate.java b/main/src/cgeo/geocaching/CompassActivity.java index c029ee7..9da428d 100644 --- a/main/src/cgeo/geocaching/cgeonavigate.java +++ b/main/src/cgeo/geocaching/CompassActivity.java @@ -1,9 +1,13 @@ package cgeo.geocaching; +import butterknife.InjectView; +import butterknife.Views; + import cgeo.geocaching.activity.AbstractActivity; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.Units; import cgeo.geocaching.maps.CGeoMap; +import cgeo.geocaching.speech.SpeechService; import cgeo.geocaching.ui.CompassView; import cgeo.geocaching.utils.GeoDirHandler; import cgeo.geocaching.utils.Log; @@ -15,7 +19,7 @@ import android.content.Intent; import android.hardware.Sensor; import android.hardware.SensorManager; import android.os.Bundle; -import android.os.PowerManager; +import android.speech.tts.TextToSpeech.Engine; import android.view.Menu; import android.view.MenuItem; import android.view.SubMenu; @@ -26,40 +30,34 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -public class cgeonavigate extends AbstractActivity { +public class CompassActivity extends AbstractActivity { + + @InjectView(R.id.nav_type) protected TextView navType; + @InjectView(R.id.nav_accuracy) protected TextView navAccuracy; + @InjectView(R.id.nav_satellites) protected TextView navSatellites; + @InjectView(R.id.nav_location) protected TextView navLocation; + @InjectView(R.id.distance) protected TextView distanceView; + @InjectView(R.id.heading) protected TextView headingView; + @InjectView(R.id.rose) protected CompassView compassView; + @InjectView(R.id.destination) protected TextView destinationTextView; + @InjectView(R.id.cacheinfo) protected TextView cacheInfoView; private static final String EXTRAS_COORDS = "coords"; private static final String EXTRAS_NAME = "name"; private static final String EXTRAS_GEOCODE = "geocode"; private static final String EXTRAS_CACHE_INFO = "cacheinfo"; private static final List<IWaypoint> coordinates = new ArrayList<IWaypoint>(); - private static final int MENU_MAP = 0; - private static final int MENU_SWITCH_COMPASS_GPS = 1; - private PowerManager pm = null; + private static final int COORDINATES_OFFSET = 10; + private static final int REQUEST_TTS_DATA_CHECK = 1; private Geopoint dstCoords = null; private float cacheHeading = 0; private String title = null; private String info = null; - private TextView navType = null; - private TextView navAccuracy = null; - private TextView navSatellites = null; - private TextView navLocation = null; - private TextView distanceView = null; - private TextView headingView = null; - private CompassView compassView = null; private boolean hasMagneticFieldSensor; - public cgeonavigate() { - super("c:geo-compass", true); - } - @Override public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setTheme(); - setContentView(R.layout.navigate); - setTitle(res.getString(R.string.compass_title)); + super.onCreate(savedInstanceState, R.layout.compass_activity); final SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); hasMagneticFieldSensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) != null; @@ -72,7 +70,7 @@ public class cgeonavigate extends AbstractActivity { if (extras != null) { title = extras.getString(EXTRAS_GEOCODE); final String name = extras.getString(EXTRAS_NAME); - dstCoords = (Geopoint) extras.getParcelable(EXTRAS_COORDS); + dstCoords = extras.getParcelable(EXTRAS_COORDS); info = extras.getString(EXTRAS_CACHE_INFO); if (StringUtils.isNotBlank(name)) { @@ -95,8 +93,7 @@ public class cgeonavigate extends AbstractActivity { setDestCoords(); setCacheInfo(); - // get textviews once - compassView = (CompassView) findViewById(R.id.rose); + Views.inject(this); } @Override @@ -105,11 +102,6 @@ public class cgeonavigate extends AbstractActivity { // sensor & geolocation manager geoDirHandler.startGeoAndDir(); - - // keep backlight on - if (pm == null) { - pm = (PowerManager) getSystemService(Context.POWER_SERVICE); - } } @Override @@ -121,24 +113,22 @@ public class cgeonavigate extends AbstractActivity { @Override public void onDestroy() { compassView.destroyDrawingCache(); + SpeechService.stopService(this); super.onDestroy(); } @Override public boolean onCreateOptionsMenu(final Menu menu) { - menu.add(0, MENU_SWITCH_COMPASS_GPS, 0, res.getString(Settings.isUseCompass() ? R.string.use_gps : R.string.use_compass)).setIcon(R.drawable.ic_menu_compass); - menu.findItem(MENU_SWITCH_COMPASS_GPS).setVisible(hasMagneticFieldSensor); - menu.add(0, MENU_MAP, 0, res.getString(R.string.caches_on_map)).setIcon(R.drawable.ic_menu_mapmode); - menu.add(0, 2, 0, res.getString(R.string.destination_set)).setIcon(R.drawable.ic_menu_edit); + getMenuInflater().inflate(R.menu.compass_activity_options, menu); + menu.findItem(R.id.menu_switch_compass_gps).setVisible(hasMagneticFieldSensor); + final SubMenu subMenu = menu.findItem(R.id.menu_select_destination).getSubMenu(); if (coordinates.size() > 1) { - final SubMenu subMenu = menu.addSubMenu(0, 3, 0, res.getString(R.string.destination_select)).setIcon(R.drawable.ic_menu_myplaces); - int cnt = 4; - for (final IWaypoint coordinate : coordinates) { - subMenu.add(0, cnt, 0, coordinate.getName() + " (" + coordinate.getCoordType() + ")"); - cnt++; + for (int i = 0; i < coordinates.size(); i++) { + final IWaypoint coordinate = coordinates.get(i); + subMenu.add(0, COORDINATES_OFFSET + i, 0, coordinate.getName() + " (" + coordinate.getCoordType() + ")"); } } else { - menu.add(0, 3, 0, res.getString(R.string.destination_select)).setIcon(R.drawable.ic_menu_myplaces).setEnabled(false); + menu.findItem(R.id.menu_select_destination).setVisible(false); } return true; } @@ -146,46 +136,72 @@ public class cgeonavigate extends AbstractActivity { @Override public boolean onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); - menu.findItem(MENU_SWITCH_COMPASS_GPS).setTitle(res.getString(Settings.isUseCompass() ? R.string.use_gps : R.string.use_compass)); + menu.findItem(R.id.menu_switch_compass_gps).setTitle(res.getString(Settings.isUseCompass() ? R.string.use_gps : R.string.use_compass)); + menu.findItem(R.id.menu_tts_start).setVisible(!SpeechService.isRunning()); + menu.findItem(R.id.menu_tts_stop).setVisible(SpeechService.isRunning()); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); + switch (id) { + case R.id.menu_map: + CGeoMap.startActivityCoords(this, dstCoords, null, null); + return true; + case R.id.menu_switch_compass_gps: + boolean oldSetting = Settings.isUseCompass(); + Settings.setUseCompass(!oldSetting); + invalidateOptionsMenuCompatible(); + if (oldSetting) { + geoDirHandler.stopDir(); + } else { + geoDirHandler.startDir(); + } + return true; + case R.id.menu_edit_destination: + Intent pointIntent = new Intent(this, NavigateAnyPointActivity.class); + startActivity(pointIntent); + + finish(); + return true; + case R.id.menu_tts_start: + initTextToSpeech(); + return true; + case R.id.menu_tts_stop: + SpeechService.stopService(this); + return true; + default: + int coordinatesIndex = id - COORDINATES_OFFSET; + if (coordinatesIndex >= 0 && coordinatesIndex < coordinates.size()) { + final IWaypoint coordinate = coordinates.get(coordinatesIndex); + title = coordinate.getName(); + dstCoords = coordinate.getCoords(); + setTitle(); + setDestCoords(); + setCacheInfo(); + updateDistanceInfo(app.currentGeo()); + + Log.d("destination set: " + title + " (" + dstCoords + ")"); + return true; + } + } + return false; + } - if (id == MENU_MAP) { - CGeoMap.startActivityCoords(this, dstCoords, null, null); - } else if (id == MENU_SWITCH_COMPASS_GPS) { - boolean oldSetting = Settings.isUseCompass(); - Settings.setUseCompass(!oldSetting); - invalidateOptionsMenuCompatible(); - if (oldSetting) { - geoDirHandler.stopDir(); - } else { - geoDirHandler.startDir(); - } - } else if (id == 2) { - Intent pointIntent = new Intent(this, NavigateAnyPointActivity.class); - startActivity(pointIntent); + private void initTextToSpeech() { + Intent intent = new Intent(Engine.ACTION_CHECK_TTS_DATA); + startActivityForResult(intent, REQUEST_TTS_DATA_CHECK); + } - finish(); - return true; - } else if (id > 3 && coordinates.get(id - 4) != null) { - final IWaypoint coordinate = coordinates.get(id - 4); - - title = coordinate.getName(); - dstCoords = coordinate.getCoords(); - setTitle(); - setDestCoords(); - setCacheInfo(); - updateDistanceInfo(app.currentGeo()); - - Log.d("destination set: " + title + " (" + dstCoords + ")"); - return true; + @Override + protected void onActivityResult(int request, int result, Intent data) { + if (request == REQUEST_TTS_DATA_CHECK && result == Engine.CHECK_VOICE_DATA_PASS) { + SpeechService.startService(this, dstCoords); + } else { + Log.i("TTS failed to start. Request: " + request + " result: " + result); + startActivity(new Intent(Engine.ACTION_INSTALL_TTS_DATA)); } - - return false; } private void setTitle() { @@ -201,11 +217,10 @@ public class cgeonavigate extends AbstractActivity { return; } - ((TextView) findViewById(R.id.destination)).setText(dstCoords.toString()); + destinationTextView.setText(dstCoords.toString()); } private void setCacheInfo() { - final TextView cacheInfoView = (TextView) findViewById(R.id.cacheinfo); if (info == null) { cacheInfoView.setVisibility(View.GONE); return; @@ -219,13 +234,6 @@ public class cgeonavigate extends AbstractActivity { return; } - if (distanceView == null) { - distanceView = (TextView) findViewById(R.id.distance); - } - if (headingView == null) { - headingView = (TextView) findViewById(R.id.heading); - } - cacheHeading = geo.getCoords().bearingTo(dstCoords); distanceView.setText(Units.getDistanceFromKilometers(geo.getCoords().distanceTo(dstCoords))); headingView.setText(Math.round(cacheHeading) + "°"); @@ -235,18 +243,10 @@ public class cgeonavigate extends AbstractActivity { @Override public void updateGeoData(final IGeoData geo) { try { - if (navType == null || navLocation == null || navAccuracy == null) { - navType = (TextView) findViewById(R.id.nav_type); - navAccuracy = (TextView) findViewById(R.id.nav_accuracy); - navSatellites = (TextView) findViewById(R.id.nav_satellites); - navLocation = (TextView) findViewById(R.id.nav_location); - } - if (geo.getCoords() != null) { if (geo.getSatellitesVisible() >= 0) { navSatellites.setText(res.getString(R.string.loc_sat) + ": " + geo.getSatellitesFixed() + "/" + geo.getSatellitesVisible()); - } - else { + } else { navSatellites.setText(""); } navType.setText(res.getString(geo.getLocationProvider().resourceId)); @@ -282,7 +282,7 @@ public class cgeonavigate extends AbstractActivity { @Override public void updateDirection(final float direction) { if (app.currentGeo().getSpeed() <= 5) { // use compass when speed is lower than 18 km/h - updateNorthHeading(DirectionProvider.getDirectionNow(cgeonavigate.this, direction)); + updateNorthHeading(DirectionProvider.getDirectionNow(CompassActivity.this, direction)); } } }; @@ -296,11 +296,15 @@ public class cgeonavigate extends AbstractActivity { public static void startActivity(final Context context, final String geocode, final String displayedName, final Geopoint coords, final Collection<IWaypoint> coordinatesWithType, final String info) { coordinates.clear(); - if (coordinatesWithType != null) { // avoid possible NPE - coordinates.addAll(coordinatesWithType); + if (coordinatesWithType != null) { + for (IWaypoint coordinate : coordinatesWithType) { + if (coordinate != null) { + coordinates.add(coordinate); + } + } } - final Intent navigateIntent = new Intent(context, cgeonavigate.class); + final Intent navigateIntent = new Intent(context, CompassActivity.class); navigateIntent.putExtra(EXTRAS_COORDS, coords); navigateIntent.putExtra(EXTRAS_GEOCODE, geocode); if (null != displayedName) { @@ -311,7 +315,7 @@ public class cgeonavigate extends AbstractActivity { } public static void startActivity(final Context context, final String geocode, final String displayedName, final Geopoint coords, final Collection<IWaypoint> coordinatesWithType) { - cgeonavigate.startActivity(context, geocode, displayedName, coords, coordinatesWithType, null); + CompassActivity.startActivity(context, geocode, displayedName, coords, coordinatesWithType, null); } } diff --git a/main/src/cgeo/geocaching/DirectionProvider.java b/main/src/cgeo/geocaching/DirectionProvider.java index c1f83ac..37b184a 100644 --- a/main/src/cgeo/geocaching/DirectionProvider.java +++ b/main/src/cgeo/geocaching/DirectionProvider.java @@ -14,7 +14,7 @@ public class DirectionProvider extends MemorySubject<Float> implements SensorEve private final SensorManager sensorManager; - // Previous values signaled to observers to avoid resending the same value when the + // Previous values signaled to observers to avoid re-sending the same value when the // device doesn't change orientation. The orientation is usually given with a 1 degree // precision by Android, so it is not uncommon to obtain exactly the same value several // times. @@ -27,7 +27,8 @@ public class DirectionProvider extends MemorySubject<Float> implements SensorEve @Override protected void onFirstObserver() { - sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION), SensorManager.SENSOR_DELAY_NORMAL); + final Sensor defaultSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION); + sensorManager.registerListener(this, defaultSensor, SensorManager.SENSOR_DELAY_NORMAL); } @Override @@ -43,7 +44,7 @@ public class DirectionProvider extends MemorySubject<Float> implements SensorEve * this event leads to the log being flooded with multiple entries _per second_, * which I experienced when running cgeo in a building (with GPS and network being * unreliable). - * + * * See for example https://code.google.com/p/android/issues/detail?id=14792 */ diff --git a/main/src/cgeo/geocaching/EditWaypointActivity.java b/main/src/cgeo/geocaching/EditWaypointActivity.java index 7f011fc..dce49a3 100644 --- a/main/src/cgeo/geocaching/EditWaypointActivity.java +++ b/main/src/cgeo/geocaching/EditWaypointActivity.java @@ -1,7 +1,6 @@ package cgeo.geocaching; import cgeo.geocaching.activity.AbstractActivity; -import cgeo.geocaching.activity.ActivityMixin; import cgeo.geocaching.connector.ConnectorFactory; import cgeo.geocaching.connector.IConnector; import cgeo.geocaching.enumerations.CacheType; @@ -12,15 +11,19 @@ import cgeo.geocaching.geopoint.DistanceParser; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.GeopointFormatter; import cgeo.geocaching.ui.dialog.CoordinatesInputDialog; -import cgeo.geocaching.utils.BaseUtils; +import cgeo.geocaching.utils.TextUtils; import cgeo.geocaching.utils.GeoDirHandler; import cgeo.geocaching.utils.Log; +import com.googlecode.androidannotations.annotations.EActivity; +import com.googlecode.androidannotations.annotations.Extra; +import com.googlecode.androidannotations.annotations.InstanceState; +import com.googlecode.androidannotations.annotations.ViewById; + import org.apache.commons.lang3.StringUtils; import android.app.ProgressDialog; import android.content.Context; -import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; @@ -28,76 +31,88 @@ import android.os.Message; import android.text.Html; import android.view.View; import android.widget.AdapterView; -import android.widget.AdapterView.OnItemSelectedListener; import android.widget.ArrayAdapter; import android.widget.AutoCompleteTextView; import android.widget.Button; import android.widget.CheckBox; -import android.widget.CompoundButton; -import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.EditText; import android.widget.RadioButton; +import android.widget.RadioGroup; import android.widget.Spinner; import java.util.ArrayList; +import java.util.Arrays; import java.util.EnumSet; import java.util.List; +@EActivity public class EditWaypointActivity extends AbstractActivity { + @ViewById(R.id.buttonLatitude) protected Button buttonLat; + @ViewById(R.id.buttonLongitude) protected Button buttonLon; + @ViewById(R.id.add_waypoint) protected Button addWaypoint; + @ViewById(R.id.note) protected EditText note; + @ViewById(R.id.wpt_visited_checkbox) protected CheckBox visitedCheckBox; + @ViewById(R.id.name) protected AutoCompleteTextView waypointName; + @ViewById(R.id.type) protected Spinner waypointTypeSelector; + @ViewById(R.id.distance) protected EditText distanceView; + @ViewById(R.id.modify_cache_coordinates_group) protected RadioGroup coordinatesGroup; + @ViewById(R.id.modify_cache_coordinates_local_and_remote) protected RadioButton modifyBoth; + @ViewById(R.id.distanceUnit) protected Spinner distanceUnitSelector; + @ViewById(R.id.bearing) protected EditText bearing; + @ViewById(R.id.modify_cache_coordinates_local) protected RadioButton modifyLocal; + + @Extra(Intents.EXTRA_GEOCODE) protected String geocode = null; + @Extra(Intents.EXTRA_WAYPOINT_ID) protected int id = -1; + /** + * number of waypoints that the corresponding cache has until now + */ + @Extra(Intents.EXTRA_COUNT) protected int wpCount = 0; - private String geocode = null; - private int id = -1; + @InstanceState protected int waypointTypeSelectorPosition = -1; private ProgressDialog waitDialog = null; private Waypoint waypoint = null; - private Geopoint gpTemp = null; - private WaypointType type = WaypointType.OWN; private String prefix = "OWN"; private String lookup = "---"; private boolean own = true; - private boolean visited = false; ArrayList<WaypointType> wpTypes = null; - String distanceUnit = ""; + ArrayList<String> distanceUnits = null; + private boolean initViews = true; - /** - * number of waypoints that the corresponding cache has until now - */ - private int wpCount = 0; private Handler loadWaypointHandler = new Handler() { @Override public void handleMessage(Message msg) { try { if (waypoint == null) { + Log.d("No waypoint loaded to edit. id= " + id); id = -1; } else { geocode = waypoint.getGeocode(); - type = waypoint.getWaypointType(); prefix = waypoint.getPrefix(); lookup = waypoint.getLookup(); own = waypoint.isUserDefined(); - visited = waypoint.isVisited(); - if (waypoint.getCoords() != null) { - ((Button) findViewById(R.id.buttonLatitude)).setText(waypoint.getCoords().format(GeopointFormatter.Format.LAT_DECMINUTE)); - ((Button) findViewById(R.id.buttonLongitude)).setText(waypoint.getCoords().format(GeopointFormatter.Format.LON_DECMINUTE)); - } - ((EditText) findViewById(R.id.name)).setText(Html.fromHtml(StringUtils.trimToEmpty(waypoint.getName())).toString()); - if (BaseUtils.containsHtml(waypoint.getNote())) { - ((EditText) findViewById(R.id.note)).setText(Html.fromHtml(StringUtils.trimToEmpty(waypoint.getNote())).toString()); - } - else { - ((EditText) findViewById(R.id.note)).setText(StringUtils.trimToEmpty(waypoint.getNote())); + if (initViews) { + visitedCheckBox.setChecked(waypoint.isVisited()); + if (waypoint.getCoords() != null) { + buttonLat.setText(waypoint.getCoords().format(GeopointFormatter.Format.LAT_DECMINUTE)); + buttonLon.setText(waypoint.getCoords().format(GeopointFormatter.Format.LON_DECMINUTE)); + } + waypointName.setText(Html.fromHtml(StringUtils.trimToEmpty(waypoint.getName())).toString()); + if (TextUtils.containsHtml(waypoint.getNote())) { + note.setText(Html.fromHtml(StringUtils.trimToEmpty(waypoint.getNote())).toString()); + } + else { + note.setText(StringUtils.trimToEmpty(waypoint.getNote())); + } } - Geocache cache = cgData.loadCache(geocode, LoadFlags.LOAD_CACHE_ONLY); + final Geocache cache = cgData.loadCache(geocode, LoadFlags.LOAD_CACHE_ONLY); setCoordsModificationVisibility(ConnectorFactory.getConnector(geocode), cache); } if (own) { initializeWaypointTypeSelector(); } - ((CheckBox) findViewById(R.id.wpt_visited_checkbox)).setChecked(visited); - - initializeDistanceUnitSelector(); } catch (Exception e) { Log.e("EditWaypointActivity.loadWaypointHandler", e); } finally { @@ -111,19 +126,7 @@ public class EditWaypointActivity extends AbstractActivity { @Override public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setTheme(); - setContentView(R.layout.edit_waypoint_activity); - setTitle("waypoint"); - - // get parameters - Bundle extras = getIntent().getExtras(); - if (extras != null) { - geocode = extras.getString(Intents.EXTRA_GEOCODE); - wpCount = extras.getInt(Intents.EXTRA_COUNT, 0); - id = extras.getInt(Intents.EXTRA_WAYPOINT_ID); - } + super.onCreate(savedInstanceState, R.layout.editwaypoint_activity); if (StringUtils.isBlank(geocode) && id <= 0) { showToast(res.getString(R.string.err_waypoint_cache_unknown)); @@ -138,59 +141,49 @@ public class EditWaypointActivity extends AbstractActivity { setTitle(res.getString(R.string.waypoint_edit_title)); } - Button buttonLat = (Button) findViewById(R.id.buttonLatitude); buttonLat.setOnClickListener(new CoordDialogListener()); - Button buttonLon = (Button) findViewById(R.id.buttonLongitude); buttonLon.setOnClickListener(new CoordDialogListener()); - Button addWaypoint = (Button) findViewById(R.id.add_waypoint); - addWaypoint.setOnClickListener(new CoordsListener()); + addWaypoint.setOnClickListener(new SaveWaypointListener()); List<String> wayPointNames = new ArrayList<String>(); for (WaypointType wpt : WaypointType.ALL_TYPES_EXCEPT_OWN_AND_ORIGINAL) { wayPointNames.add(wpt.getL10n()); } - AutoCompleteTextView textView = (AutoCompleteTextView) findViewById(R.id.name); ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_dropdown_item_1line, wayPointNames); - textView.setAdapter(adapter); + waypointName.setAdapter(adapter); - if (id > 0) { - Spinner waypointTypeSelector = (Spinner) findViewById(R.id.type); - waypointTypeSelector.setVisibility(View.GONE); + if (savedInstanceState != null) { + initViews = false; + } + if (id > 0) { // existing waypoint waitDialog = ProgressDialog.show(this, null, res.getString(R.string.waypoint_loading), true); waitDialog.setCancelable(true); (new LoadWaypointThread()).start(); - } else { + + } else { // new waypoint initializeWaypointTypeSelector(); - } - if (geocode != null) { - Geocache cache = cgData.loadCache(geocode, LoadFlags.LOAD_CACHE_OR_DB); - IConnector con = ConnectorFactory.getConnector(geocode); - setCoordsModificationVisibility(con, cache); - } - CheckBox visitedCheckBox = ((CheckBox) findViewById(R.id.wpt_visited_checkbox)); - visitedCheckBox.setOnCheckedChangeListener(new OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - visited = isChecked; + if (geocode != null) { + final Geocache cache = cgData.loadCache(geocode, LoadFlags.LOAD_CACHE_OR_DB); + setCoordsModificationVisibility(ConnectorFactory.getConnector(geocode), cache); } - }); + } initializeDistanceUnitSelector(); - disableSuggestions((EditText) findViewById(R.id.distance)); + disableSuggestions(distanceView); } private void setCoordsModificationVisibility(IConnector con, Geocache cache) { if (cache != null && (cache.getType() == CacheType.MYSTERY || cache.getType() == CacheType.MULTI)) { - findViewById(R.id.modify_cache_coordinates_group).setVisibility(View.VISIBLE); - findViewById(R.id.modify_cache_coordinates_local_and_remote).setVisibility(con.supportsOwnCoordinates() ? View.VISIBLE : View.GONE); + coordinatesGroup.setVisibility(View.VISIBLE); + modifyBoth.setVisibility(con.supportsOwnCoordinates() ? View.VISIBLE : View.GONE); } else { - findViewById(R.id.modify_cache_coordinates_group).setVisibility(View.GONE); - findViewById(R.id.modify_cache_coordinates_local_and_remote).setVisibility(View.GONE); + coordinatesGroup.setVisibility(View.GONE); + modifyBoth.setVisibility(View.GONE); } } @@ -199,25 +192,6 @@ public class EditWaypointActivity extends AbstractActivity { super.onResume(); geoDirHandler.startGeo(); - - if (id > 0) { - if (waitDialog == null) { - waitDialog = ProgressDialog.show(this, null, res.getString(R.string.waypoint_loading), true); - waitDialog.setCancelable(true); - - (new LoadWaypointThread()).start(); - } - } - } - - @Override - public void onDestroy() { - super.onDestroy(); - } - - @Override - public void onStop() { - super.onStop(); } @Override @@ -227,40 +201,40 @@ public class EditWaypointActivity extends AbstractActivity { } private void initializeWaypointTypeSelector() { - - Spinner waypointTypeSelector = (Spinner) findViewById(R.id.type); - wpTypes = new ArrayList<WaypointType>(WaypointType.ALL_TYPES_EXCEPT_OWN_AND_ORIGINAL); ArrayAdapter<WaypointType> wpAdapter = new ArrayAdapter<WaypointType>(this, android.R.layout.simple_spinner_item, wpTypes.toArray(new WaypointType[wpTypes.size()])); wpAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); waypointTypeSelector.setAdapter(wpAdapter); - int typeIndex = wpTypes.indexOf(type); - if (typeIndex < 0) { - typeIndex = wpTypes.indexOf(WaypointType.WAYPOINT); - } + waypointTypeSelector.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView<?> parent, View v, int pos, long id) { + waypointTypeSelectorPosition = pos; + } - waypointTypeSelector.setSelection(typeIndex); - waypointTypeSelector.setOnItemSelectedListener(new ChangeWaypointType(this)); + @Override + public void onNothingSelected(AdapterView<?> parent) { + } + }); + + if (initViews) { + int typeIndex = -1; + if (waypoint != null) { + typeIndex = wpTypes.indexOf(waypoint.getWaypointType()); + } + waypointTypeSelector.setSelection(typeIndex >= 0 ? typeIndex : wpTypes.indexOf(WaypointType.WAYPOINT)); + } else { + waypointTypeSelector.setSelection(waypointTypeSelectorPosition); + } waypointTypeSelector.setVisibility(View.VISIBLE); } private void initializeDistanceUnitSelector() { - - Spinner distanceUnitSelector = (Spinner) findViewById(R.id.distanceUnit); - - if (StringUtils.isBlank(distanceUnit)) { - if (Settings.isUseMetricUnits()) { - distanceUnitSelector.setSelection(0); // m - distanceUnit = res.getStringArray(R.array.distance_units)[0]; - } else { - distanceUnitSelector.setSelection(2); // ft - distanceUnit = res.getStringArray(R.array.distance_units)[2]; - } + distanceUnits = new ArrayList<String>(Arrays.asList(res.getStringArray(R.array.distance_units))); + if (initViews) { + distanceUnitSelector.setSelection(Settings.isUseMetricUnits() ? 0 : 2); //0:m, 2:ft } - - distanceUnitSelector.setOnItemSelectedListener(new ChangeDistanceUnit(this)); } final private GeoDirHandler geoDirHandler = new GeoDirHandler() { @@ -271,10 +245,8 @@ public class EditWaypointActivity extends AbstractActivity { } try { - Button bLat = (Button) findViewById(R.id.buttonLatitude); - Button bLon = (Button) findViewById(R.id.buttonLongitude); - bLat.setHint(geo.getCoords().format(GeopointFormatter.Format.LAT_DECMINUTE_RAW)); - bLon.setHint(geo.getCoords().format(GeopointFormatter.Format.LON_DECMINUTE_RAW)); + buttonLat.setHint(geo.getCoords().format(GeopointFormatter.Format.LAT_DECMINUTE_RAW)); + buttonLon.setHint(geo.getCoords().format(GeopointFormatter.Format.LON_DECMINUTE_RAW)); } catch (final Exception e) { Log.e("failed to update location", e); } @@ -290,7 +262,7 @@ public class EditWaypointActivity extends AbstractActivity { loadWaypointHandler.sendMessage(Message.obtain()); } catch (Exception e) { - Log.e("cgeowaypoint.loadWaypoint.run", e); + Log.e("EditWaypointActivity.loadWaypoint.run", e); } } } @@ -300,10 +272,10 @@ public class EditWaypointActivity extends AbstractActivity { @Override public void onClick(View arg0) { Geopoint gp = null; - if (waypoint != null && waypoint.getCoords() != null) { - gp = waypoint.getCoords(); - } else if (gpTemp != null) { - gp = gpTemp; + try { + gp = new Geopoint(buttonLat.getText().toString(), buttonLon.getText().toString()); + } catch (Geopoint.ParseException e) { + // button text is blank when creating new waypoint } Geocache cache = cgData.loadCache(geocode, LoadFlags.LOAD_WAYPOINTS); CoordinatesInputDialog coordsDialog = new CoordinatesInputDialog(EditWaypointActivity.this, cache, gp, app.currentGeo()); @@ -311,62 +283,14 @@ public class EditWaypointActivity extends AbstractActivity { coordsDialog.setOnCoordinateUpdate(new CoordinatesInputDialog.CoordinateUpdate() { @Override public void update(final Geopoint gp) { - ((Button) findViewById(R.id.buttonLatitude)).setText(gp.format(GeopointFormatter.Format.LAT_DECMINUTE)); - ((Button) findViewById(R.id.buttonLongitude)).setText(gp.format(GeopointFormatter.Format.LON_DECMINUTE)); - if (waypoint != null) { - waypoint.setCoords(gp); - } else { - gpTemp = gp; - } + buttonLat.setText(gp.format(GeopointFormatter.Format.LAT_DECMINUTE)); + buttonLon.setText(gp.format(GeopointFormatter.Format.LON_DECMINUTE)); } }); coordsDialog.show(); } } - private static class ChangeWaypointType implements OnItemSelectedListener { - - private ChangeWaypointType(EditWaypointActivity wpView) { - this.wpView = wpView; - } - - private EditWaypointActivity wpView; - - @Override - public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, - long arg3) { - if (null != wpView.wpTypes) { - wpView.type = wpView.wpTypes.get(arg2); - } - } - - @Override - public void onNothingSelected(AdapterView<?> arg0) { - if (null != wpView.wpTypes) { - arg0.setSelection(wpView.wpTypes.indexOf(wpView.type)); - } - } - } - - private static class ChangeDistanceUnit implements OnItemSelectedListener { - - private ChangeDistanceUnit(EditWaypointActivity unitView) { - this.unitView = unitView; - } - - private EditWaypointActivity unitView; - - @Override - public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, - long arg3) { - unitView.distanceUnit = (String) arg0.getItemAtPosition(arg2); - } - - @Override - public void onNothingSelected(AdapterView<?> arg0) { - } - } - public static final int SUCCESS = 0; public static final int UPLOAD_START = 1; public static final int UPLOAD_ERROR = 2; @@ -374,15 +298,15 @@ public class EditWaypointActivity extends AbstractActivity { public static final int UPLOAD_SUCCESS = 4; public static final int SAVE_ERROR = 5; - private class CoordsListener implements View.OnClickListener { + private class SaveWaypointListener implements View.OnClickListener { @Override public void onClick(View arg0) { - final String bearingText = ((EditText) findViewById(R.id.bearing)).getText().toString(); + final String bearingText = bearing.getText().toString(); // combine distance from EditText and distanceUnit saved from Spinner - final String distanceText = ((EditText) findViewById(R.id.distance)).getText().toString() + distanceUnit; - final String latText = ((Button) findViewById(R.id.buttonLatitude)).getText().toString(); - final String lonText = ((Button) findViewById(R.id.buttonLongitude)).getText().toString(); + final String distanceText = distanceView.getText().toString() + distanceUnits.get(distanceUnitSelector.getSelectedItemPosition()); + final String latText = buttonLat.getText().toString(); + final String lonText = buttonLon.getText().toString(); if (StringUtils.isBlank(bearingText) && StringUtils.isBlank(distanceText) && StringUtils.isBlank(latText) && StringUtils.isBlank(lonText)) { @@ -430,10 +354,12 @@ public class EditWaypointActivity extends AbstractActivity { } // if no name is given, just give the waypoint its number as name - final String givenName = ((EditText) findViewById(R.id.name)).getText().toString().trim(); + final String givenName = waypointName.getText().toString().trim(); final String name = StringUtils.isNotEmpty(givenName) ? givenName : res.getString(R.string.waypoint) + " " + (wpCount + 1); - final String note = ((EditText) findViewById(R.id.note)).getText().toString().trim(); + final String noteText = note.getText().toString().trim(); final Geopoint coordsToSave = coords; + final WaypointType type = wpTypes.get(waypointTypeSelector.getSelectedItemPosition()); + final boolean visited = visitedCheckBox.isChecked(); final ProgressDialog progress = ProgressDialog.show(EditWaypointActivity.this, getString(R.string.cache), getString(R.string.waypoint_being_saved), true); final Handler finishHandler = new Handler() { @@ -483,7 +409,7 @@ public class EditWaypointActivity extends AbstractActivity { waypoint.setPrefix(prefix); waypoint.setLookup(lookup); waypoint.setCoords(coordsToSave); - waypoint.setNote(note); + waypoint.setNote(noteText); waypoint.setVisited(visited); waypoint.setId(id); @@ -501,8 +427,6 @@ public class EditWaypointActivity extends AbstractActivity { StaticMapsProvider.storeWaypointStaticMap(cache, waypoint, false); } } - final RadioButton modifyLocal = (RadioButton) findViewById(R.id.modify_cache_coordinates_local); - final RadioButton modifyBoth = (RadioButton) findViewById(R.id.modify_cache_coordinates_local_and_remote); if (modifyLocal.isChecked() || modifyBoth.isChecked()) { if (!cache.hasUserModifiedCoords()) { final Waypoint origWaypoint = new Waypoint(cgeoapplication.getInstance().getString(R.string.cache_coordinates_original), WaypointType.ORIGINAL, false); @@ -541,19 +465,11 @@ public class EditWaypointActivity extends AbstractActivity { return con.supportsOwnCoordinates() && con.uploadModifiedCoordinates(cache, waypointUploaded); } - @Override - public void goManual(final View view) { - ActivityMixin.goManual(this, id >= 0 ? "c:geo-waypoint-edit" : "c:geo-waypoint-new"); - } - public static void startActivityEditWaypoint(final Context context, final int waypointId) { - context.startActivity(new Intent(context, EditWaypointActivity.class) - .putExtra(Intents.EXTRA_WAYPOINT_ID, waypointId)); + EditWaypointActivity_.intent(context).id(waypointId).start(); } public static void startActivityAddWaypoint(final Context context, final Geocache cache) { - context.startActivity(new Intent(context, EditWaypointActivity.class) - .putExtra(Intents.EXTRA_GEOCODE, cache.getGeocode()) - .putExtra(Intents.EXTRA_COUNT, cache.getWaypoints().size())); + EditWaypointActivity_.intent(context).geocode(cache.getGeocode()).wpCount(cache.getWaypoints().size()).start(); } } diff --git a/main/src/cgeo/geocaching/Geocache.java b/main/src/cgeo/geocaching/Geocache.java index 836cccb..1972c7a 100644 --- a/main/src/cgeo/geocaching/Geocache.java +++ b/main/src/cgeo/geocaching/Geocache.java @@ -5,13 +5,13 @@ import cgeo.geocaching.activity.ActivityMixin; import cgeo.geocaching.activity.IAbstractActivity; import cgeo.geocaching.connector.ConnectorFactory; import cgeo.geocaching.connector.IConnector; +import cgeo.geocaching.connector.ILoggingManager; import cgeo.geocaching.connector.capability.ISearchByCenter; import cgeo.geocaching.connector.capability.ISearchByGeocode; import cgeo.geocaching.connector.gc.GCConnector; import cgeo.geocaching.connector.gc.GCConstants; import cgeo.geocaching.connector.gc.Tile; import cgeo.geocaching.enumerations.CacheAttribute; -import cgeo.geocaching.enumerations.CacheRealm; import cgeo.geocaching.enumerations.CacheSize; import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.enumerations.LoadFlags; @@ -47,8 +47,8 @@ import java.util.Collections; import java.util.Date; import java.util.EnumSet; import java.util.HashMap; -import java.util.LinkedList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.regex.Pattern; @@ -175,7 +175,9 @@ public class Geocache implements ICache, IWaypoint { } /** - * Gather missing information from another cache object. + * Gather missing information for new Geocache object from the stored Geocache object. + * This is called in the new Geocache parsed from website to set information not yet + * parsed. * * @param other * the other version, or null if non-existent @@ -187,6 +189,8 @@ public class Geocache implements ICache, IWaypoint { } updated = System.currentTimeMillis(); + // if parsed cache is not yet detailed and stored is, the information of + // the parsed cache will be overwritten if (!detailed && (other.detailed || zoomlevel < other.zoomlevel)) { detailed = other.detailed; detailedUpdate = other.detailedUpdate; @@ -194,15 +198,19 @@ public class Geocache implements ICache, IWaypoint { cacheType = other.cacheType; zoomlevel = other.zoomlevel; // boolean values must be enumerated here. Other types are assigned outside this if-statement + // TODO: check whether a search or a live map systematically returns those, in which case + // we want to keep the most recent one instead of getting information from the previously + // stored data. This is the case for "archived" for example which has been taken out of this + // list. premiumMembersOnly = other.premiumMembersOnly; reliableLatLon = other.reliableLatLon; - archived = other.archived; found = other.found; disabled = other.disabled; favorite = other.favorite; onWatchlist = other.onWatchlist; logOffline = other.logOffline; finalDefined = other.finalDefined; + archived = other.archived; } /* @@ -447,13 +455,13 @@ public class Geocache implements ICache, IWaypoint { } public void logVisit(final IAbstractActivity fromActivity) { - if (StringUtils.isBlank(cacheId)) { + if (!getConnector().canLog(this)) { fromActivity.showToast(((Activity) fromActivity).getResources().getString(R.string.err_cannot_log_visit)); return; } - Intent logVisitIntent = new Intent((Activity) fromActivity, VisitCacheActivity.class); - logVisitIntent.putExtra(VisitCacheActivity.EXTRAS_ID, cacheId); - logVisitIntent.putExtra(VisitCacheActivity.EXTRAS_GEOCODE, geocode); + Intent logVisitIntent = new Intent((Activity) fromActivity, LogCacheActivity.class); + logVisitIntent.putExtra(LogCacheActivity.EXTRAS_ID, cacheId); + logVisitIntent.putExtra(LogCacheActivity.EXTRAS_GEOCODE, geocode); ((Activity) fromActivity).startActivity(logVisitIntent); } @@ -482,35 +490,43 @@ public class Geocache implements ICache, IWaypoint { } } + public void clearOfflineLog() { + cgData.clearLogOffline(geocode); + notifyChange(); + } + public List<LogType> getPossibleLogTypes() { - final List<LogType> logTypes = new LinkedList<LogType>(); + final List<LogType> logTypes = new ArrayList<LogType>(); if (isEventCache()) { logTypes.add(LogType.WILL_ATTEND); - logTypes.add(LogType.NOTE); logTypes.add(LogType.ATTENDED); - logTypes.add(LogType.NEEDS_ARCHIVE); if (isOwner()) { logTypes.add(LogType.ANNOUNCEMENT); } } else if (CacheType.WEBCAM == cacheType) { logTypes.add(LogType.WEBCAM_PHOTO_TAKEN); - logTypes.add(LogType.DIDNT_FIND_IT); - logTypes.add(LogType.NOTE); - logTypes.add(LogType.NEEDS_ARCHIVE); - logTypes.add(LogType.NEEDS_MAINTENANCE); } else { logTypes.add(LogType.FOUND_IT); + } + if (!isEventCache()) { logTypes.add(LogType.DIDNT_FIND_IT); - logTypes.add(LogType.NOTE); - logTypes.add(LogType.NEEDS_ARCHIVE); + } + logTypes.add(LogType.NOTE); + if (!isEventCache()) { logTypes.add(LogType.NEEDS_MAINTENANCE); } if (isOwner()) { logTypes.add(LogType.OWNER_MAINTENANCE); - logTypes.add(LogType.TEMP_DISABLE_LISTING); - logTypes.add(LogType.ENABLE_LISTING); + if (isDisabled()) { + logTypes.add(LogType.ENABLE_LISTING); + } + else { + logTypes.add(LogType.TEMP_DISABLE_LISTING); + } logTypes.add(LogType.ARCHIVE); - logTypes.remove(LogType.UPDATE_COORDINATES); + } + if (!isArchived() && !isOwner()) { + logTypes.add(LogType.NEEDS_ARCHIVE); } return logTypes; } @@ -551,12 +567,16 @@ public class Geocache implements ICache, IWaypoint { return getConnector().supportsLogging(); } + public boolean supportsLogImages() { + return getConnector().supportsLogImages(); + } + public boolean supportsOwnCoordinates() { return getConnector().supportsOwnCoordinates(); } - public CacheRealm getCacheRealm() { - return getConnector().getCacheRealm(); + public ILoggingManager getLoggingManager(Activity activity) { + return getConnector().getLoggingManager(activity, this); } @Override @@ -710,10 +730,7 @@ public class Geocache implements ICache, IWaypoint { public String getPersonalNote() { // non premium members have no personal notes, premium members have an empty string by default. // map both to null, so other code doesn't need to differentiate - if (StringUtils.isBlank(personalNote)) { - return null; - } - return personalNote; + return StringUtils.defaultIfBlank(personalNote, null); } public boolean supportsUserActions() { @@ -765,8 +782,8 @@ public class Geocache implements ICache, IWaypoint { return favorite; } - public void setFavorite(boolean favourite) { - this.favorite = favourite; + public void setFavorite(boolean favorite) { + this.favorite = favorite; } @Override @@ -829,8 +846,7 @@ public class Geocache implements ICache, IWaypoint { } public boolean isVirtual() { - return CacheType.VIRTUAL == cacheType || CacheType.WEBCAM == cacheType - || CacheType.EARTH == cacheType; + return cacheType.isVirtual(); } public boolean showSize() { @@ -1360,6 +1376,9 @@ public class Geocache implements ICache, IWaypoint { return null; } + /** + * Detect coordinates in the personal note and convert them to user defined waypoints. Works by rule of thumb. + */ public void parseWaypointsFromNote() { try { if (StringUtils.isBlank(getPersonalNote())) { @@ -1378,7 +1397,8 @@ public class Geocache implements ICache, IWaypoint { ((point.getLatitudeE6() % 1000) != 0 || (point.getLongitudeE6() % 1000) != 0) && !hasIdenticalWaypoint(point)) { final String name = cgeoapplication.getInstance().getString(R.string.cache_personal_note) + " " + count; - final Waypoint waypoint = new Waypoint(name, WaypointType.WAYPOINT, false); + final String potentialWaypointType = note.substring(Math.max(0, matcher.start() - 15)); + final Waypoint waypoint = new Waypoint(name, parseWaypointType(potentialWaypointType), false); waypoint.setCoords(point); addOrChangeWaypoint(waypoint, false); count++; @@ -1395,9 +1415,30 @@ public class Geocache implements ICache, IWaypoint { } } + /** + * Detect waypoint types in the personal note text. It works by rule of thumb only. + */ + private static WaypointType parseWaypointType(final String input) { + final String lowerInput = StringUtils.substring(input, 0, 20).toLowerCase(Locale.getDefault()); + for (WaypointType wpType : WaypointType.values()) { + if (lowerInput.contains(wpType.getL10n().toLowerCase(Locale.getDefault()))) { + return wpType; + } + if (lowerInput.contains(wpType.id)) { + return wpType; + } + if (lowerInput.contains(wpType.name().toLowerCase(Locale.US))) { + return wpType; + } + } + return WaypointType.WAYPOINT; + } + private boolean hasIdenticalWaypoint(final Geopoint point) { for (final Waypoint waypoint: waypoints) { - if (waypoint.getCoords().equals(point)) { + // waypoint can have no coords such as a Final set by cache owner + final Geopoint coords = waypoint.getCoords(); + if (coords != null && coords.equals(point)) { return true; } } @@ -1525,9 +1566,12 @@ public class Geocache implements ICache, IWaypoint { Geocache cache; // get cache details, they may not yet be complete if (origCache != null) { + SearchResult search = null; // only reload the cache if it was already stored or doesn't have full details (by checking the description) if (origCache.isOffline() || StringUtils.isBlank(origCache.getDescription())) { - final SearchResult search = searchByGeocode(origCache.getGeocode(), null, listId, false, handler); + search = searchByGeocode(origCache.getGeocode(), null, listId, false, handler); + } + if (search != null) { cache = search.getFirstCacheFromResult(LoadFlags.LOAD_CACHE_OR_DB); } else { cache = origCache; @@ -1601,7 +1645,7 @@ public class Geocache implements ICache, IWaypoint { handler.sendMessage(Message.obtain()); } } catch (Exception e) { - Log.e("cgBase.storeCache"); + Log.e("Geocache.storeCache", e); } } @@ -1724,4 +1768,8 @@ public class Geocache implements ICache, IWaypoint { } return false; } + + public int getMapMarkerId() { + return getConnector().getCacheMapMarkerId(isDisabled() || isArchived()); + } } diff --git a/main/src/cgeo/geocaching/GpxFileListActivity.java b/main/src/cgeo/geocaching/GpxFileListActivity.java index f12a30c..de0be21 100644 --- a/main/src/cgeo/geocaching/GpxFileListActivity.java +++ b/main/src/cgeo/geocaching/GpxFileListActivity.java @@ -31,14 +31,9 @@ public class GpxFileListActivity extends AbstractFileListActivity<GPXListAdapter return Collections.singletonList(new File(Settings.getGpxImportDir()));
}
- @Override
- protected void setTitle() {
- setTitle(res.getString(R.string.gpx_import_title));
- }
-
public static void startSubActivity(Activity fromActivity, int listId) {
final Intent intent = new Intent(fromActivity, GpxFileListActivity.class);
- intent.putExtra(Intents.EXTRA_LIST_ID, listId);
+ intent.putExtra(Intents.EXTRA_LIST_ID, StoredList.getConcreteList(listId));
fromActivity.startActivityForResult(intent, 0);
}
diff --git a/main/src/cgeo/geocaching/ImageSelectActivity.java b/main/src/cgeo/geocaching/ImageSelectActivity.java index 347cd86..39824a9 100644 --- a/main/src/cgeo/geocaching/ImageSelectActivity.java +++ b/main/src/cgeo/geocaching/ImageSelectActivity.java @@ -1,7 +1,11 @@ package cgeo.geocaching; +import butterknife.InjectView; +import butterknife.Views; + import cgeo.geocaching.activity.AbstractActivity; import cgeo.geocaching.compatibility.Compatibility; +import cgeo.geocaching.utils.ImageUtils; import cgeo.geocaching.utils.Log; import org.apache.commons.lang3.StringUtils; @@ -10,14 +14,18 @@ import android.content.Intent; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.drawable.BitmapDrawable; import android.net.Uri; import android.os.Bundle; import android.provider.MediaStore; import android.provider.MediaStore.MediaColumns; import android.view.View; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemSelectedListener; import android.widget.Button; import android.widget.EditText; import android.widget.ImageView; +import android.widget.Spinner; import java.io.File; import java.text.SimpleDateFormat; @@ -25,37 +33,41 @@ import java.util.Date; import java.util.Locale; public class ImageSelectActivity extends AbstractActivity { + + @InjectView(R.id.caption) protected EditText captionView; + @InjectView(R.id.description) protected EditText descriptionView; + @InjectView(R.id.logImageScale) protected Spinner scaleView; + @InjectView(R.id.camera) protected Button cameraButton; + @InjectView(R.id.stored) protected Button storedButton; + @InjectView(R.id.save) protected Button saveButton; + @InjectView(R.id.cancel) protected Button clearButton; + @InjectView(R.id.image_preview) protected ImageView imagePreview; + static final String EXTRAS_CAPTION = "caption"; static final String EXTRAS_DESCRIPTION = "description"; static final String EXTRAS_URI_AS_STRING = "uri"; + static final String EXTRAS_SCALE = "scale"; private static final String SAVED_STATE_IMAGE_CAPTION = "cgeo.geocaching.saved_state_image_caption"; private static final String SAVED_STATE_IMAGE_DESCRIPTION = "cgeo.geocaching.saved_state_image_description"; private static final String SAVED_STATE_IMAGE_URI = "cgeo.geocaching.saved_state_image_uri"; + private static final String SAVED_STATE_IMAGE_SCALE = "cgeo.geocaching.saved_state_image_scale"; private static final int SELECT_NEW_IMAGE = 1; private static final int SELECT_STORED_IMAGE = 2; - private EditText captionView; - private EditText descriptionView; - // Data to be saved while reconfiguring private String imageCaption; private String imageDescription; + private int scaleChoiceIndex; private Uri imageUri; - public ImageSelectActivity() { - super("c:geo-selectimage"); - } - @Override public void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setTheme(); - setContentView(R.layout.visit_image); - setTitle(res.getString(R.string.log_image)); + super.onCreate(savedInstanceState, R.layout.imageselect_activity); + Views.inject(this); + scaleChoiceIndex = Settings.getLogImageScale(); imageCaption = ""; imageDescription = ""; imageUri = Uri.EMPTY; @@ -66,6 +78,7 @@ public class ImageSelectActivity extends AbstractActivity { imageCaption = extras.getString(EXTRAS_CAPTION); imageDescription = extras.getString(EXTRAS_DESCRIPTION); imageUri = Uri.parse(extras.getString(EXTRAS_URI_AS_STRING)); + scaleChoiceIndex = extras.getInt(EXTRAS_SCALE, scaleChoiceIndex); } // Restore previous state @@ -73,9 +86,9 @@ public class ImageSelectActivity extends AbstractActivity { imageCaption = savedInstanceState.getString(SAVED_STATE_IMAGE_CAPTION); imageDescription = savedInstanceState.getString(SAVED_STATE_IMAGE_DESCRIPTION); imageUri = Uri.parse(savedInstanceState.getString(SAVED_STATE_IMAGE_URI)); + scaleChoiceIndex = savedInstanceState.getInt(SAVED_STATE_IMAGE_SCALE); } - final Button cameraButton = (Button) findViewById(R.id.camera); cameraButton.setOnClickListener(new View.OnClickListener() { @Override @@ -84,7 +97,6 @@ public class ImageSelectActivity extends AbstractActivity { } }); - final Button storedButton = (Button) findViewById(R.id.stored); storedButton.setOnClickListener(new View.OnClickListener() { @Override @@ -93,17 +105,27 @@ public class ImageSelectActivity extends AbstractActivity { } }); - captionView = (EditText) findViewById(R.id.caption); if (StringUtils.isNotBlank(imageCaption)) { captionView.setText(imageCaption); } - descriptionView = (EditText) findViewById(R.id.description); if (StringUtils.isNotBlank(imageDescription)) { descriptionView.setText(imageDescription); } - final Button saveButton = (Button) findViewById(R.id.save); + scaleView.setSelection(scaleChoiceIndex); + scaleView.setOnItemSelectedListener(new OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) { + scaleChoiceIndex = scaleView.getSelectedItemPosition(); + Settings.setLogImageScale(scaleChoiceIndex); + } + + @Override + public void onNothingSelected(AdapterView<?> arg0) { + } + }); + saveButton.setOnClickListener(new View.OnClickListener() { @Override @@ -112,7 +134,6 @@ public class ImageSelectActivity extends AbstractActivity { } }); - final Button clearButton = (Button) findViewById(R.id.cancel); clearButton.setOnClickListener(new View.OnClickListener() { @Override @@ -131,15 +152,19 @@ public class ImageSelectActivity extends AbstractActivity { outState.putString(SAVED_STATE_IMAGE_CAPTION, imageCaption); outState.putString(SAVED_STATE_IMAGE_DESCRIPTION, imageDescription); outState.putString(SAVED_STATE_IMAGE_URI, imageUri != null ? imageUri.getPath() : StringUtils.EMPTY); + outState.putInt(SAVED_STATE_IMAGE_SCALE, scaleChoiceIndex); } public void saveImageInfo(boolean saveInfo) { if (saveInfo) { + String filename = writeScaledImage(imageUri.getPath()); + imageUri = Uri.parse(filename); Intent intent = new Intent(); syncEditTexts(); intent.putExtra(EXTRAS_CAPTION, imageCaption); intent.putExtra(EXTRAS_DESCRIPTION, imageDescription); intent.putExtra(EXTRAS_URI_AS_STRING, imageUri.toString()); + intent.putExtra(EXTRAS_SCALE, scaleChoiceIndex); setResult(RESULT_OK, intent); } else { @@ -152,6 +177,7 @@ public class ImageSelectActivity extends AbstractActivity { private void syncEditTexts() { imageCaption = captionView.getText().toString(); imageDescription = descriptionView.getText().toString(); + scaleChoiceIndex = scaleView.getSelectedItemPosition(); } private void selectImageFromCamera() { @@ -231,8 +257,40 @@ public class ImageSelectActivity extends AbstractActivity { loadImagePreview(); } + /** + * Scales and writes the scaled image. + * + * @param filePath + * @return + */ + private String writeScaledImage(final String filePath) { + scaleChoiceIndex = scaleView.getSelectedItemPosition(); + final int maxXY = getResources().getIntArray(R.array.log_image_scale_values)[scaleChoiceIndex]; + if (maxXY == 0) { + return filePath; + } + BitmapFactory.Options sizeOnlyOptions = new BitmapFactory.Options(); + sizeOnlyOptions.inJustDecodeBounds = true; + BitmapFactory.decodeFile(filePath, sizeOnlyOptions); + final int myMaxXY = Math.max(sizeOnlyOptions.outHeight, sizeOnlyOptions.outWidth); + final int sampleSize = myMaxXY / maxXY; + Bitmap image; + if (sampleSize > 1) { + BitmapFactory.Options sampleOptions = new BitmapFactory.Options(); + sampleOptions.inSampleSize = sampleSize; + image = BitmapFactory.decodeFile(filePath, sampleOptions); + } else { + image = BitmapFactory.decodeFile(filePath); + } + final BitmapDrawable scaledImage = ImageUtils.scaleBitmapTo(image, maxXY, maxXY); + image = null; + final String uploadFilename = getOutputImageFile().getPath(); + ImageUtils.storeBitmap(scaledImage.getBitmap(), Bitmap.CompressFormat.JPEG, 75, uploadFilename); + return uploadFilename; + } + private void showFailure() { - showToast(getResources().getString(R.string.err_aquire_image_failed)); + showToast(getResources().getString(R.string.err_acquire_image_failed)); } private void loadImagePreview() { @@ -244,7 +302,6 @@ public class ImageSelectActivity extends AbstractActivity { return; } - final ImageView imagePreview = (ImageView) findViewById(R.id.image_preview); BitmapFactory.Options bitmapOptions = new BitmapFactory.Options(); bitmapOptions.inSampleSize = 8; final Bitmap bitmap = BitmapFactory.decodeFile(imageUri.getPath(), bitmapOptions); diff --git a/main/src/cgeo/geocaching/ImagesActivity.java b/main/src/cgeo/geocaching/ImagesActivity.java index 24f699e..030b3f7 100644 --- a/main/src/cgeo/geocaching/ImagesActivity.java +++ b/main/src/cgeo/geocaching/ImagesActivity.java @@ -19,10 +19,6 @@ import java.util.List; public class ImagesActivity extends AbstractActivity { - private static final String EXTRAS_IMAGES = "images"; - private static final String EXTRAS_TYPE = "type"; - private static final String EXTRAS_GEOCODE = "geocode"; - private boolean offline; private ArrayList<Image> imageNames; private ImagesList imagesList; @@ -37,8 +33,8 @@ public class ImagesActivity extends AbstractActivity { String geocode = null; if (extras != null) { - geocode = extras.getString(EXTRAS_GEOCODE); - imgType = (ImageType) extras.getSerializable(EXTRAS_TYPE); + geocode = extras.getString(Intents.EXTRA_GEOCODE); + imgType = (ImageType) extras.getSerializable(Intents.EXTRA_TYPE); } if (extras == null || geocode == null) { @@ -49,12 +45,12 @@ public class ImagesActivity extends AbstractActivity { // init setTheme(); - setContentView(R.layout.spoilers); + setContentView(R.layout.images_activity); setTitle(res.getString(imgType.getTitle())); imagesList = new ImagesList(this, geocode); - imageNames = extras.getParcelableArrayList(EXTRAS_IMAGES); + imageNames = extras.getParcelableArrayList(Intents.EXTRA_IMAGES); if (CollectionUtils.isEmpty(imageNames)) { showToast(res.getString(R.string.warn_load_images)); finish(); @@ -67,7 +63,7 @@ public class ImagesActivity extends AbstractActivity { @Override public void onStart() { super.onStart(); - imagesList.loadImages(findViewById(R.id.spoiler_list), imageNames, imgType, offline); + imagesList.loadImages(findViewById(R.id.spoiler_list), imageNames, offline); } @Override @@ -85,12 +81,12 @@ public class ImagesActivity extends AbstractActivity { final Intent logImgIntent = new Intent(fromActivity, ImagesActivity.class); // if resuming our app within this activity, finish it and return to the cache activity logImgIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) - .putExtra(EXTRAS_GEOCODE, geocode) - .putExtra(EXTRAS_TYPE, imageType); + .putExtra(Intents.EXTRA_GEOCODE, geocode) + .putExtra(Intents.EXTRA_TYPE, imageType); // avoid forcing the array list as parameter type final ArrayList<Image> arrayList = new ArrayList<Image>(logImages); - logImgIntent.putParcelableArrayListExtra(EXTRAS_IMAGES, arrayList); + logImgIntent.putParcelableArrayListExtra(Intents.EXTRA_IMAGES, arrayList); fromActivity.startActivity(logImgIntent); } diff --git a/main/src/cgeo/geocaching/Intents.java b/main/src/cgeo/geocaching/Intents.java index 7f0a004..a700451 100644 --- a/main/src/cgeo/geocaching/Intents.java +++ b/main/src/cgeo/geocaching/Intents.java @@ -9,10 +9,11 @@ public class Intents { private static final String PREFIX = "cgeo.geocaching.intent.extra."; public static final String EXTRA_ADDRESS = PREFIX + "address"; - public static final String EXTRAS_COORDS = PREFIX + "coords"; + public static final String EXTRA_COORDS = PREFIX + "coords"; public static final String EXTRA_COUNT = PREFIX + "count"; public static final String EXTRA_GEOCODE = PREFIX + "geocode"; public static final String EXTRA_GUID = PREFIX + "guid"; + public static final String EXTRA_IMAGES = PREFIX + "images"; public static final String EXTRA_ID = PREFIX + "id"; public static final String EXTRA_KEYWORD = PREFIX + "keyword"; public static final String EXTRA_KEYWORD_SEARCH = PREFIX + "keyword_search"; @@ -23,6 +24,7 @@ public class Intents { public static final String EXTRA_SEARCH = PREFIX + "search"; public static final String EXTRA_START_DIR = PREFIX + "start_dir"; public static final String EXTRA_TRACKING_CODE = PREFIX + "tracking_code"; + public static final String EXTRA_TYPE = PREFIX + "type"; public static final String EXTRA_USERNAME = PREFIX + "username"; public static final String EXTRA_WAYPOINT_ID = PREFIX + "waypoint_id"; public static final String EXTRA_CACHELIST = PREFIX + "cache_list"; diff --git a/main/src/cgeo/geocaching/VisitCacheActivity.java b/main/src/cgeo/geocaching/LogCacheActivity.java index dce0fbf..62c94ce 100644 --- a/main/src/cgeo/geocaching/VisitCacheActivity.java +++ b/main/src/cgeo/geocaching/LogCacheActivity.java @@ -1,38 +1,34 @@ package cgeo.geocaching; -import cgeo.geocaching.connector.gc.GCParser; -import cgeo.geocaching.connector.gc.Login; +import cgeo.geocaching.connector.ILoggingManager; +import cgeo.geocaching.connector.ImageResult; +import cgeo.geocaching.connector.LogResult; import cgeo.geocaching.enumerations.LoadFlags; import cgeo.geocaching.enumerations.LogType; import cgeo.geocaching.enumerations.LogTypeTrackable; import cgeo.geocaching.enumerations.StatusCode; import cgeo.geocaching.gcvote.GCVote; -import cgeo.geocaching.loaders.UrlLoader; -import cgeo.geocaching.network.Parameters; import cgeo.geocaching.twitter.Twitter; import cgeo.geocaching.ui.Formatter; import cgeo.geocaching.ui.dialog.DateDialog; +import cgeo.geocaching.utils.AsyncTaskWithProgress; +import cgeo.geocaching.utils.DateUtils; import cgeo.geocaching.utils.Log; import cgeo.geocaching.utils.LogTemplateProvider; import cgeo.geocaching.utils.LogTemplateProvider.LogContext; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.tuple.ImmutablePair; +import android.app.Activity; import android.app.AlertDialog; import android.app.AlertDialog.Builder; import android.app.Dialog; -import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.net.Uri; import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.Loader; import android.util.SparseArray; import android.view.LayoutInflater; import android.view.Menu; @@ -51,7 +47,7 @@ import java.util.Date; import java.util.List; import java.util.Locale; -public class VisitCacheActivity extends AbstractLoggingActivity implements DateDialog.DateDialogParent, LoaderManager.LoaderCallbacks<String> { +public class LogCacheActivity extends AbstractLoggingActivity implements DateDialog.DateDialogParent { static final String EXTRAS_GEOCODE = "geocode"; static final String EXTRAS_ID = "id"; @@ -67,12 +63,10 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD private LayoutInflater inflater = null; private Geocache cache = null; - private ProgressDialog waitDialog = null; private String cacheid = null; private String geocode = null; private String text = null; private List<LogType> possibleLogTypes = new ArrayList<LogType>(); - private String[] viewstates = null; private List<TrackableLog> trackables = null; private Button postButton = null; private CheckBox tweetCheck = null; @@ -80,6 +74,8 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD private boolean tbChanged = false; private SparseArray<TrackableLog> actionButtons; + private ILoggingManager loggingManager; + // Data to be saved while reconfiguring private double rating; private LogType typeSelected; @@ -88,31 +84,16 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD private String imageDescription; private Uri imageUri; - @Override - public Loader<String> onCreateLoader(final int id, final Bundle args) { - if (!Settings.isLogin()) { // allow offline logging - showToast(res.getString(R.string.err_login)); - return null; - } - return new UrlLoader(getBaseContext(), "http://www.geocaching.com/seek/log.aspx", new Parameters("ID", cacheid)); - } - @Override - public void onLoaderReset(final Loader<String> loader) { - // Nothing to do - } + public void onLoadFinished() { - @Override - public void onLoadFinished(final Loader<String> loader, final String page) { - if (page == null) { + if (loggingManager.hasLoaderError()) { showErrorLoadingData(); return; } - viewstates = Login.getViewstates(page); - trackables = GCParser.parseTrackableLog(page); - possibleLogTypes = GCParser.parseTypes(page); - possibleLogTypes.remove(LogType.UPDATE_COORDINATES); + trackables = loggingManager.getTrackables(); + possibleLogTypes = loggingManager.getPossibleLogTypes(); if (possibleLogTypes.isEmpty()) { showErrorLoadingData(); @@ -161,7 +142,7 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD inventoryView.removeAllViews(); for (TrackableLog tb : trackables) { - LinearLayout inventoryItem = (LinearLayout) inflater.inflate(R.layout.visit_trackable, null); + LinearLayout inventoryItem = (LinearLayout) inflater.inflate(R.layout.logcache_trackable_item, null); ((TextView) inventoryItem.findViewById(R.id.trackcode)).setText(tb.trackCode); ((TextView) inventoryItem.findViewById(R.id.name)).setText(tb.name); @@ -183,7 +164,7 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD @Override public void onClick(View view) { - final Intent trackablesIntent = new Intent(VisitCacheActivity.this, TrackableActivity.class); + final Intent trackablesIntent = new Intent(LogCacheActivity.this, TrackableActivity.class); trackablesIntent.putExtra(Intents.EXTRA_GEOCODE, tbCode); startActivity(trackablesIntent); } @@ -231,7 +212,7 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD if (!postButton.isEnabled()) { return res.getString(R.string.log_post_not_possible); } - if (typeSelected != LogType.FOUND_IT || !Settings.isGCvoteLogin()) { + if (typeSelected != LogType.FOUND_IT || !Settings.isGCvoteLogin() || !cache.supportsGCVote()) { return res.getString(R.string.log_post); } if (rating == 0) { @@ -240,45 +221,9 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD return res.getString(R.string.log_post_rate) + " " + ratingTextValue(rating) + "*"; } - private final Handler postLogHandler = new Handler() { - - @Override - public void handleMessage(final Message msg) { - if (waitDialog != null) { - waitDialog.dismiss(); - } - - final StatusCode error = (StatusCode) msg.obj; - if (error == StatusCode.NO_ERROR) { - showToast(res.getString(R.string.info_log_posted)); - // No need to save the log when quitting if it has been posted. - text = currentLogText(); - finish(); - } else if (error == StatusCode.LOG_SAVED) { - showToast(res.getString(R.string.info_log_saved)); - - if (waitDialog != null) { - waitDialog.dismiss(); - } - - finish(); - } else { - showToast(error.getErrorString(res)); - } - } - }; - - public VisitCacheActivity() { - super("c:geo-log"); - } - @Override public void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setTheme(); - setContentView(R.layout.visit); - setTitle(res.getString(R.string.log_new_log)); + super.onCreate(savedInstanceState, R.layout.logcache_activity); // Get parameters from intent and basic cache information from database final Bundle extras = getIntent().getExtras(); @@ -333,7 +278,7 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD } } updatePostButtonText(); - setImageButtonText(); + updateImageButton(); enablePostButton(false); final Button typeButton = (Button) findViewById(R.id.type); @@ -385,15 +330,25 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD } }); - getSupportLoaderManager().initLoader(0, null, this); + loggingManager = cache.getLoggingManager(this); + + loggingManager.init(); } private void setDefaultValues() { date = Calendar.getInstance(); rating = 0.0; if (cache.isEventCache()) { - if (cache.hasOwnLog(LogType.WILL_ATTEND)) { - typeSelected = LogType.ATTENDED; + final Date eventDate = cache.getHiddenDate(); + boolean expired = DateUtils.daysSince(eventDate.getTime()) > 0; + + if (cache.hasOwnLog(LogType.WILL_ATTEND) || expired) { + if (cache.hasOwnLog(LogType.ATTENDED)) { + typeSelected = LogType.NOTE; + } + else { + typeSelected = LogType.ATTENDED; + } } else { typeSelected = LogType.WILL_ATTEND; @@ -407,13 +362,13 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD } } text = null; - imageCaption = ""; - imageDescription = ""; + imageCaption = StringUtils.EMPTY; + imageDescription = StringUtils.EMPTY; imageUri = Uri.EMPTY; } private void clearLog() { - cgData.clearLogOffline(geocode); + cache.clearOfflineLog(); setDefaultValues(); @@ -423,7 +378,7 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD final EditText logView = (EditText) findViewById(R.id.log); logView.setText(StringUtils.EMPTY); - setImageButtonText(); + updateImageButton(); showToast(res.getString(R.string.info_log_cleared)); } @@ -463,7 +418,7 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD public boolean onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); - final boolean voteAvailable = Settings.isGCvoteLogin() && typeSelected == LogType.FOUND_IT && StringUtils.isNotBlank(cache.getGuid()); + final boolean voteAvailable = Settings.isGCvoteLogin() && typeSelected == LogType.FOUND_IT && StringUtils.isNotBlank(cache.getGuid()) && cache.supportsGCVote(); menu.findItem(SUBMENU_VOTE).setVisible(voteAvailable); return true; @@ -540,7 +495,7 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD @Override public void onClick(View arg0) { - final Dialog dateDialog = new DateDialog(VisitCacheActivity.this, VisitCacheActivity.this, date); + final Dialog dateDialog = new DateDialog(LogCacheActivity.this, LogCacheActivity.this, date); dateDialog.setCancelable(true); dateDialog.show(); } @@ -549,81 +504,77 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD private class PostListener implements View.OnClickListener { @Override public void onClick(View arg0) { - waitDialog = ProgressDialog.show(VisitCacheActivity.this, null, - res.getString(StringUtils.isBlank(imageUri.getPath()) ? R.string.log_saving : R.string.log_saving_and_uploading), true); - waitDialog.setCancelable(true); - - final Thread thread = new PostLogThread(postLogHandler, currentLogText()); - thread.start(); + final String message = res.getString(StringUtils.isBlank(imageUri.getPath()) ? + R.string.log_saving : + R.string.log_saving_and_uploading); + new Poster(LogCacheActivity.this, message).execute(currentLogText()); } } - private class PostLogThread extends Thread { - - private final Handler handler; - private final String log; + private class Poster extends AsyncTaskWithProgress<String, StatusCode> { - public PostLogThread(Handler handlerIn, String logIn) { - super("Post log"); - handler = handlerIn; - log = logIn; + public Poster(final Activity activity, final String progressMessage) { + super(activity, null, progressMessage, true); } @Override - public void run() { - final StatusCode status = postLogFn(log); - handler.sendMessage(handler.obtainMessage(0, status)); - } - } - - public StatusCode postLogFn(String log) { - - StatusCode result = StatusCode.LOG_POST_ERROR; - - try { - - final ImmutablePair<StatusCode, String> logResult = GCParser.postLog(geocode, cacheid, viewstates, typeSelected, - date.get(Calendar.YEAR), (date.get(Calendar.MONTH) + 1), date.get(Calendar.DATE), - log, trackables); - - result = logResult.left; - - if (logResult.left == StatusCode.NO_ERROR) { - final LogEntry logNow = new LogEntry(date, typeSelected, log); - - cache.getLogs().add(0, logNow); - - if (typeSelected == LogType.FOUND_IT || typeSelected == LogType.ATTENDED) { - cache.setFound(true); + protected StatusCode doInBackgroundInternal(final String[] logTexts) { + final String log = logTexts[0]; + try { + final LogResult logResult = loggingManager.postLog(cache, typeSelected, date, log, trackables); + + if (logResult.getPostLogResult() == StatusCode.NO_ERROR) { + final LogEntry logNow = new LogEntry(date, typeSelected, log); + + cache.getLogs().add(0, logNow); + + if (typeSelected == LogType.FOUND_IT || typeSelected == LogType.ATTENDED) { + cache.setFound(true); + } + + cgData.saveChangedCache(cache); + cache.clearOfflineLog(); + + if (typeSelected == LogType.FOUND_IT) { + if (tweetCheck.isChecked() && tweetBox.getVisibility() == View.VISIBLE) { + Twitter.postTweetCache(geocode); + } + GCVote.setRating(cache, rating); + } + + if (StringUtils.isNotBlank(imageUri.getPath())) { + ImageResult imageResult = loggingManager.postLogImage(logResult.getLogId(), imageCaption, imageDescription, imageUri); + final String uploadedImageUrl = imageResult.getImageUri(); + if (StringUtils.isNotEmpty(uploadedImageUrl)) { + logNow.addLogImage(new Image(uploadedImageUrl, imageCaption, imageDescription)); + cgData.saveChangedCache(cache); + } + return imageResult.getPostResult(); + } } - cgData.saveChangedCache(cache); - } - - if (logResult.left == StatusCode.NO_ERROR) { - cgData.clearLogOffline(geocode); - } - - if (logResult.left == StatusCode.NO_ERROR && typeSelected == LogType.FOUND_IT && Settings.isUseTwitter() - && Settings.isTwitterLoginValid() - && tweetCheck.isChecked() && tweetBox.getVisibility() == View.VISIBLE) { - Twitter.postTweetCache(geocode); + return logResult.getPostLogResult(); + } catch (Exception e) { + Log.e("VisitCacheActivity.Poster.doInBackgroundInternal", e); } - if (logResult.left == StatusCode.NO_ERROR && typeSelected == LogType.FOUND_IT && Settings.isGCvoteLogin()) { - GCVote.setRating(cache, rating); - } + return StatusCode.LOG_POST_ERROR; + } - if (logResult.left == StatusCode.NO_ERROR && StringUtils.isNotBlank(imageUri.getPath())) { - result = GCParser.uploadLogImage(logResult.right, imageCaption, imageDescription, imageUri); + @Override + protected void onPostExecuteInternal(final StatusCode status) { + if (status == StatusCode.NO_ERROR) { + showToast(res.getString(R.string.info_log_posted)); + // No need to save the log when quitting if it has been posted. + text = currentLogText(); + finish(); + } else if (status == StatusCode.LOG_SAVED) { + showToast(res.getString(R.string.info_log_saved)); + finish(); + } else { + showToast(status.getErrorString(res)); } - - return result; - } catch (Exception e) { - Log.e("cgeovisit.postLogFn", e); } - - return StatusCode.LOG_POST_ERROR; } private void saveLog(final boolean force) { @@ -677,16 +628,19 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD } private void selectLogType() { + // use a local copy of the possible types, as that one might be modified in the background by the loader + final ArrayList<LogType> possible = new ArrayList<LogType>(possibleLogTypes); + Builder alert = new AlertDialog.Builder(this); - String[] choices = new String[possibleLogTypes.size()]; + String[] choices = new String[possible.size()]; for (int i = 0; i < choices.length; i++) { - choices[i] = possibleLogTypes.get(i).getL10n(); + choices[i] = possible.get(i).getL10n(); } - alert.setSingleChoiceItems(choices, possibleLogTypes.indexOf(typeSelected), new OnClickListener() { + alert.setSingleChoiceItems(choices, possible.indexOf(typeSelected), new OnClickListener() { @Override public void onClick(DialogInterface dialog, int position) { - setType(possibleLogTypes.get(position)); + setType(possible.get(position)); dialog.dismiss(); } }); @@ -734,14 +688,19 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD // Image capture failed, advise user showToast(getResources().getString(R.string.err_select_logimage_failed)); } - setImageButtonText(); + updateImageButton(); } } - private void setImageButtonText() { + private void updateImageButton() { final Button imageButton = (Button) findViewById(R.id.image_btn); - imageButton.setText(StringUtils.isNotBlank(imageUri.getPath()) ? + if (cache.supportsLogImages()) { + imageButton.setVisibility(View.VISIBLE); + imageButton.setText(StringUtils.isNotBlank(imageUri.getPath()) ? res.getString(R.string.log_image_edit) : res.getString(R.string.log_image_attach)); + } else { + imageButton.setVisibility(View.GONE); + } } } diff --git a/main/src/cgeo/geocaching/LogTrackableActivity.java b/main/src/cgeo/geocaching/LogTrackableActivity.java index b8983ba..1f6feb7 100644 --- a/main/src/cgeo/geocaching/LogTrackableActivity.java +++ b/main/src/cgeo/geocaching/LogTrackableActivity.java @@ -1,5 +1,8 @@ package cgeo.geocaching; +import butterknife.InjectView; +import butterknife.Views; + import cgeo.geocaching.connector.gc.GCParser; import cgeo.geocaching.connector.gc.Login; import cgeo.geocaching.enumerations.LogType; @@ -36,6 +39,14 @@ import java.util.Calendar; import java.util.List; public class LogTrackableActivity extends AbstractLoggingActivity implements DateDialog.DateDialogParent { + + @InjectView(R.id.post) protected Button buttonPost; + @InjectView(R.id.type) protected Button typeButton; + @InjectView(R.id.date) protected Button dateButton; + @InjectView(R.id.tracking) protected EditText trackingEditText; + @InjectView(R.id.tweet) protected CheckBox tweetCheck; + @InjectView(R.id.tweet_box) protected LinearLayout tweetBox; + private List<LogType> possibleLogTypes = new ArrayList<LogType>(); private ProgressDialog waitDialog = null; private String guid = null; @@ -45,8 +56,6 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat private Calendar date = Calendar.getInstance(); private LogType typeSelected = LogType.getById(Settings.getTrackableAction()); private int attempts = 0; - private CheckBox tweetCheck = null; - private LinearLayout tweetBox = null; private Trackable trackable; private Handler showProgressHandler = new Handler() { @@ -57,6 +66,7 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat }; private Handler loadDataHandler = new Handler() { + @Override public void handleMessage(final Message msg) { if (!possibleLogTypes.contains(typeSelected)) { @@ -78,7 +88,6 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat gettingViewstate = false; // we're done, user can post log - final Button buttonPost = (Button) findViewById(R.id.post); buttonPost.setEnabled(true); buttonPost.setOnClickListener(new PostListener()); @@ -104,17 +113,10 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat } }; - public LogTrackableActivity() { - super("c:geo-log-trackable"); - } - @Override public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setTheme(); - setContentView(R.layout.touch); - setTitle(res.getString(R.string.trackable_touch)); + super.onCreate(savedInstanceState, R.layout.logtrackable_activity); + Views.inject(this); // get parameters final Bundle extras = getIntent().getExtras(); @@ -123,7 +125,7 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat guid = extras.getString(Intents.EXTRA_GUID); if (StringUtils.isNotBlank(extras.getString(Intents.EXTRA_TRACKING_CODE))) { - ((EditText) findViewById(R.id.tracking)).setText(extras.getString(Intents.EXTRA_TRACKING_CODE)); + trackingEditText.setText(extras.getString(Intents.EXTRA_TRACKING_CODE)); } } @@ -146,11 +148,6 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat } @Override - public void onResume() { - super.onResume(); - } - - @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); @@ -184,7 +181,6 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat } public void init() { - final Button typeButton = (Button) findViewById(R.id.type); registerForContextMenu(typeButton); typeButton.setText(typeSelected.getL10n()); typeButton.setOnClickListener(new View.OnClickListener() { @@ -194,23 +190,15 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat } }); - final Button dateButton = (Button) findViewById(R.id.date); dateButton.setOnClickListener(new DateListener()); setDate(date); - if (tweetBox == null) { - tweetBox = (LinearLayout) findViewById(R.id.tweet_box); - } - if (tweetCheck == null) { - tweetCheck = (CheckBox) findViewById(R.id.tweet); - } tweetCheck.setChecked(true); if (CollectionUtils.isEmpty(possibleLogTypes)) { possibleLogTypes = Trackable.getPossibleLogTypes(); } - final Button buttonPost = (Button) findViewById(R.id.post); if (Login.isEmpty(viewstates)) { buttonPost.setEnabled(false); buttonPost.setOnTouchListener(null); @@ -221,20 +209,17 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat buttonPost.setEnabled(true); buttonPost.setOnClickListener(new PostListener()); } - disableSuggestions((EditText) findViewById(R.id.tracking)); + disableSuggestions(trackingEditText); } @Override public void setDate(Calendar dateIn) { date = dateIn; - final Button dateButton = (Button) findViewById(R.id.date); dateButton.setText(Formatter.formatShortDateVerbally(date.getTime().getTime())); } public void setType(LogType type) { - final Button typeButton = (Button) findViewById(R.id.type); - typeSelected = type; typeButton.setText(typeSelected.getL10n()); @@ -257,6 +242,8 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat private class PostListener implements View.OnClickListener { + protected EditText logEditText = (EditText) findViewById(R.id.log); + @Override public void onClick(View arg0) { if (!gettingViewstate) { @@ -265,8 +252,8 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat Settings.setTrackableAction(typeSelected.id); - final String tracking = ((EditText) findViewById(R.id.tracking)).getText().toString(); - final String log = ((EditText) findViewById(R.id.log)).getText().toString(); + final String tracking = trackingEditText.getText().toString(); + final String log = logEditText.getText().toString(); new PostLogThread(postLogHandler, tracking, log).start(); } else { showToast(res.getString(R.string.err_log_load_data_still)); diff --git a/main/src/cgeo/geocaching/LogViewHolder.java b/main/src/cgeo/geocaching/LogViewHolder.java new file mode 100644 index 0000000..14148d0 --- /dev/null +++ b/main/src/cgeo/geocaching/LogViewHolder.java @@ -0,0 +1,46 @@ +package cgeo.geocaching; + +import butterknife.InjectView; + +import cgeo.geocaching.ui.AbstractViewHolder; + +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +public class LogViewHolder extends AbstractViewHolder { + @InjectView(R.id.added) protected TextView date ; + @InjectView(R.id.type) protected TextView type; + @InjectView(R.id.author) protected TextView author; + @InjectView(R.id.count_or_location) protected TextView countOrLocation; + @InjectView(R.id.log) protected TextView text; + @InjectView(R.id.log_images) protected TextView images; + @InjectView(R.id.log_mark) protected ImageView marker; + + private int position; + + public LogViewHolder(View rowView) { + super(rowView); + } + + /** + * Read the position of the cursor pointed to by this holder. <br/> + * This must be called by the UI thread. + * + * @return the cursor position + */ + public int getPosition() { + return position; + } + + /** + * Set the position of the cursor pointed to by this holder. <br/> + * This must be called by the UI thread. + * + * @param position + * the cursor position + */ + public void setPosition(final int position) { + this.position = position; + } +}
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/cgeo.java b/main/src/cgeo/geocaching/MainActivity.java index 5680ff3..03a1d0d 100644 --- a/main/src/cgeo/geocaching/cgeo.java +++ b/main/src/cgeo/geocaching/MainActivity.java @@ -1,8 +1,13 @@ package cgeo.geocaching; +import butterknife.InjectView; +import butterknife.Views; + import cgeo.geocaching.activity.AbstractActivity; -import cgeo.geocaching.activity.ActivityMixin; +import cgeo.geocaching.connector.ConnectorFactory; +import cgeo.geocaching.connector.IConnector; import cgeo.geocaching.connector.gc.Login; +import cgeo.geocaching.connector.oc.OCApiLiveConnector; import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.enumerations.StatusCode; import cgeo.geocaching.geopoint.Geopoint; @@ -14,6 +19,9 @@ import cgeo.geocaching.utils.Log; import cgeo.geocaching.utils.RunnableWithArgument; import cgeo.geocaching.utils.Version; +import com.google.zxing.integration.android.IntentIntegrator; +import com.google.zxing.integration.android.IntentResult; + import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; @@ -31,11 +39,10 @@ import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.view.Menu; -import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; -import android.widget.RelativeLayout; +import android.widget.ImageView; import android.widget.TextView; import java.util.ArrayList; @@ -45,14 +52,26 @@ import java.util.Comparator; import java.util.List; import java.util.Locale; -public class cgeo extends AbstractActivity { +public class MainActivity extends AbstractActivity { + @InjectView(R.id.user_info_gc) protected TextView userInfoViewGc; + @InjectView(R.id.user_info_ocde) protected TextView userInfoViewOcDe; + @InjectView(R.id.nav_satellites) protected TextView navSatellites; + @InjectView(R.id.filter_button_title)protected TextView filterTitle; + @InjectView(R.id.map) protected ImageView findOnMap; + @InjectView(R.id.search_offline) protected ImageView findByOffline; + @InjectView(R.id.advanced_button) protected ImageView advanced; + @InjectView(R.id.any_button) protected ImageView any; + @InjectView(R.id.filter_button) protected ImageView filter; + @InjectView(R.id.nearest) protected ImageView nearestView; + @InjectView(R.id.nav_type) protected TextView navType ; + @InjectView(R.id.nav_accuracy) protected TextView navAccuracy ; + @InjectView(R.id.nav_location) protected TextView navLocation ; + @InjectView(R.id.offline_count) protected TextView countBubble ; private static final String SCAN_INTENT = "com.google.zxing.client.android.SCAN"; - private static final int SCAN_REQUEST_CODE = 1; public static final int SEARCH_REQUEST_CODE = 2; private int version = 0; - private TextView filterTitle = null; private boolean cleanupRunning = false; private int countBubbleCnt = 0; private Geopoint addCoords = null; @@ -67,19 +86,47 @@ public class cgeo extends AbstractActivity { @Override public void handleMessage(Message msg) { - TextView userInfoView = (TextView) findViewById(R.id.user_info); + //TODO: Rework to be fully dynamic + if (Settings.isGCConnectorActive()) { + StringBuilder userInfo = new StringBuilder("geocaching.com").append(Formatter.SEPARATOR); + if (Login.isActualLoginStatus()) { + userInfo.append(Login.getActualUserName()); + if (Login.getActualCachesFound() >= 0) { + userInfo.append(" (").append(String.valueOf(Login.getActualCachesFound())).append(')'); + } + userInfo.append(Formatter.SEPARATOR); + } + userInfo.append(Login.getActualStatus()); + + userInfoViewGc.setText(userInfo.toString()); + userInfoViewGc.setVisibility(View.VISIBLE); + } + else { + userInfoViewGc.setVisibility(View.GONE); + } - StringBuilder userInfo = new StringBuilder("geocaching.com").append(Formatter.SEPARATOR); - if (Login.isActualLoginStatus()) { - userInfo.append(Login.getActualUserName()); - if (Login.getActualCachesFound() >= 0) { - userInfo.append(" (").append(String.valueOf(Login.getActualCachesFound())).append(')'); + if (Settings.isOCConnectorActive()) { + StringBuilder userInfo = new StringBuilder("opencaching.de").append(Formatter.SEPARATOR); + IConnector conn = ConnectorFactory.getConnector("OCXXXX"); + if (conn instanceof OCApiLiveConnector) { + OCApiLiveConnector ocapiConn = (OCApiLiveConnector) conn; + if (ocapiConn.supportsPersonalization()) { + userInfo.append(ocapiConn.getUserName()); + int count = ocapiConn.getCachesFound(); + if (count >= 0) { + userInfo.append(" (").append(String.valueOf(count)).append(')'); + } + } else { + userInfo.append("Anonymous"); + } } - userInfo.append(Formatter.SEPARATOR); + userInfoViewOcDe.setText(userInfo.toString()); + userInfoViewOcDe.setVisibility(View.VISIBLE); + } + else { + userInfoViewOcDe.setVisibility(View.GONE); } - userInfo.append(Login.getActualStatus()); - userInfoView.setText(userInfo.toString()); } }; @@ -109,7 +156,6 @@ public class cgeo extends AbstractActivity { addCoords = app.currentGeo().getCoords(); - TextView navLocation = (TextView) findViewById(R.id.nav_location); navLocation.setText(addText.toString()); } } catch (Exception e) { @@ -137,7 +183,6 @@ public class cgeo extends AbstractActivity { satellitesFixed = data.getSatellitesFixed(); satellitesVisible = data.getSatellitesVisible(); - final TextView navSatellites = (TextView) findViewById(R.id.nav_satellites); if (gpsEnabled) { if (satellitesFixed > 0) { navSatellites.setText(res.getString(R.string.loc_sat) + ": " + satellitesFixed + '/' + satellitesVisible); @@ -164,18 +209,17 @@ public class cgeo extends AbstractActivity { showToast(res.getString(reason == StatusCode.MAINTENANCE ? reason.getErrorString() : R.string.err_login_failed_toast)); } } catch (Exception e) { - Log.w("cgeo.firstLoginHander", e); + Log.w("MainActivity.firstLoginHander", e); } } }; - public cgeo() { - super("c:geo-main-screen"); - } - @Override public void onCreate(Bundle savedInstanceState) { + // don't call the super implementation with the layout argument, as that would set the wrong theme super.onCreate(savedInstanceState); + setContentView(R.layout.main_activity); + Views.inject(this); if ((getIntent().getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) { // If we had been open already, start from the last used activity. @@ -183,33 +227,11 @@ public class cgeo extends AbstractActivity { return; } - setContentView(R.layout.main); setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); // type to search version = Version.getVersionCode(this); Log.i("Starting " + getPackageName() + ' ' + version + " a.k.a " + Version.getVersionName(this)); - try { - if (!Settings.isHelpShown()) { - final RelativeLayout helper = (RelativeLayout) findViewById(R.id.helper); - if (helper != null) { - helper.setVisibility(View.VISIBLE); - helper.setClickable(true); - helper.setOnClickListener(new View.OnClickListener() { - - @Override - public void onClick(View view) { - ActivityMixin.goManual(cgeo.this, "c:geo-intro"); - view.setVisibility(View.GONE); - } - }); - Settings.setHelpShown(); - } - } - } catch (Exception e) { - // nothing - } - init(); } @@ -253,8 +275,7 @@ public class cgeo extends AbstractActivity { @Override public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.main_options, menu); + getMenuInflater().inflate(R.menu.main_activity_options, menu); return true; } @@ -302,25 +323,19 @@ public class cgeo extends AbstractActivity { } private void startScannerApplication() { - Intent intent = new Intent(SCAN_INTENT); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); // when resuming our app, cancel this activity - intent.putExtra("SCAN_MODE", "QR_CODE_MODE"); - startActivityForResult(intent, SCAN_REQUEST_CODE); + IntentIntegrator integrator = new IntentIntegrator(this); + integrator.initiateScan(IntentIntegrator.QR_CODE_TYPES); } @Override public void onActivityResult(int requestCode, int resultCode, Intent intent) { - if (requestCode == SCAN_REQUEST_CODE) { - if (resultCode == RESULT_OK) { - String scan = intent.getStringExtra("SCAN_RESULT"); - if (StringUtils.isBlank(scan)) { - return; - } - - SearchActivity.startActivityScan(scan, this); - } else if (resultCode == RESULT_CANCELED) { - // do nothing + IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent); + if (scanResult != null) { + String scan = scanResult.getContents(); + if (StringUtils.isBlank(scan)) { + return; } + SearchActivity.startActivityScan(scan, this); } else if (requestCode == SEARCH_REQUEST_CODE) { // SearchActivity activity returned without making a search if (resultCode == RESULT_CANCELED) { @@ -338,9 +353,6 @@ public class cgeo extends AbstractActivity { } private void setFilterTitle() { - if (filterTitle == null) { - filterTitle = (TextView) findViewById(R.id.filter_button_title); - } filterTitle.setText(Settings.getCacheType().getL10n()); } @@ -358,7 +370,6 @@ public class cgeo extends AbstractActivity { (new FirstLoginThread()).start(); } - final View findOnMap = findViewById(R.id.map); findOnMap.setClickable(true); findOnMap.setOnClickListener(new OnClickListener() { @Override @@ -367,7 +378,6 @@ public class cgeo extends AbstractActivity { } }); - final View findByOffline = findViewById(R.id.search_offline); findByOffline.setClickable(true); findByOffline.setOnClickListener(new OnClickListener() { @Override @@ -379,12 +389,12 @@ public class cgeo extends AbstractActivity { @Override public boolean onLongClick(View v) { - new StoredList.UserInterface(cgeo.this).promptForListSelection(R.string.list_title, new RunnableWithArgument<Integer>() { + new StoredList.UserInterface(MainActivity.this).promptForListSelection(R.string.list_title, new RunnableWithArgument<Integer>() { @Override public void run(Integer selectedListId) { Settings.saveLastList(selectedListId); - cgeocaches.startActivityOffline(cgeo.this); + cgeocaches.startActivityOffline(MainActivity.this); } }); return true; @@ -392,7 +402,6 @@ public class cgeo extends AbstractActivity { }); findByOffline.setLongClickable(true); - final View advanced = findViewById(R.id.advanced_button); advanced.setClickable(true); advanced.setOnClickListener(new OnClickListener() { @Override @@ -401,7 +410,6 @@ public class cgeo extends AbstractActivity { } }); - final View any = findViewById(R.id.any_button); any.setClickable(true); any.setOnClickListener(new OnClickListener() { @Override @@ -410,7 +418,6 @@ public class cgeo extends AbstractActivity { } }); - final View filter = findViewById(R.id.filter_button); filter.setClickable(true); filter.setOnClickListener(new View.OnClickListener() { @Override @@ -501,7 +508,7 @@ public class cgeo extends AbstractActivity { public void onClick(DialogInterface dialog, int id) { dialog.dismiss(); cgData.resetNewlyCreatedDatabase(); - app.restoreDatabase(cgeo.this); + app.restoreDatabase(MainActivity.this); } }) .setNegativeButton(getString(android.R.string.no), new DialogInterface.OnClickListener() { @@ -519,10 +526,6 @@ public class cgeo extends AbstractActivity { @Override public void updateGeoData(final IGeoData geo) { - final View nearestView = findViewById(R.id.nearest); - final TextView navType = (TextView) findViewById(R.id.nav_type); - final TextView navAccuracy = (TextView) findViewById(R.id.nav_accuracy); - final TextView navLocation = (TextView) findViewById(R.id.nav_location); try { if (geo.getCoords() != null) { if (!nearestView.isClickable()) { @@ -583,7 +586,7 @@ public class cgeo extends AbstractActivity { * unused here but needed since this method is referenced from XML layout */ public void cgeoFindOnMap(View v) { - findViewById(R.id.map).setPressed(true); + findOnMap.setPressed(true); CGeoMap.startActivityLiveMap(this); } @@ -596,7 +599,7 @@ public class cgeo extends AbstractActivity { return; } - findViewById(R.id.nearest).setPressed(true); + nearestView.setPressed(true); cgeocaches.startActivityNearest(this, app.currentGeo().getCoords()); } @@ -605,7 +608,7 @@ public class cgeo extends AbstractActivity { * unused here but needed since this method is referenced from XML layout */ public void cgeoFindByOffline(View v) { - findViewById(R.id.search_offline).setPressed(true); + findByOffline.setPressed(true); cgeocaches.startActivityOffline(this); } @@ -614,7 +617,7 @@ public class cgeo extends AbstractActivity { * unused here but needed since this method is referenced from XML layout */ public void cgeoSearch(View v) { - findViewById(R.id.advanced_button).setPressed(true); + advanced.setPressed(true); startActivity(new Intent(this, SearchActivity.class)); } @@ -623,7 +626,7 @@ public class cgeo extends AbstractActivity { * unused here but needed since this method is referenced from XML layout */ public void cgeoPoint(View v) { - findViewById(R.id.any_button).setPressed(true); + any.setPressed(true); startActivity(new Intent(this, NavigateAnyPointActivity.class)); } @@ -632,8 +635,8 @@ public class cgeo extends AbstractActivity { * unused here but needed since this method is referenced from XML layout */ public void cgeoFilter(View v) { - findViewById(R.id.filter_button).setPressed(true); - findViewById(R.id.filter_button).performClick(); + filter.setPressed(true); + filter.performClick(); } /** @@ -646,15 +649,10 @@ public class cgeo extends AbstractActivity { private class CountBubbleUpdateThread extends Thread { private Handler countBubbleHandler = new Handler() { - private TextView countBubble = null; @Override public void handleMessage(Message msg) { try { - if (countBubble == null) { - countBubble = (TextView) findViewById(R.id.offline_count); - } - if (countBubbleCnt == 0) { countBubble.setVisibility(View.GONE); } else { @@ -663,7 +661,7 @@ public class cgeo extends AbstractActivity { countBubble.setVisibility(View.VISIBLE); } } catch (Exception e) { - Log.w("cgeo.countBubbleHander", e); + Log.w("MainActivity.countBubbleHander", e); } } }; @@ -730,22 +728,35 @@ public class cgeo extends AbstractActivity { return; } - // login - final StatusCode status = Login.login(); + //TODO: Rework to be fully dynamic + if (Settings.isGCConnectorActive()) { + // login + final StatusCode status = Login.login(); - if (status == StatusCode.NO_ERROR) { - app.firstRun = false; - Login.detectGcCustomDate(); - updateUserInfoHandler.sendEmptyMessage(-1); - } + if (status == StatusCode.NO_ERROR) { + app.firstRun = false; + Login.detectGcCustomDate(); + updateUserInfoHandler.sendEmptyMessage(-1); + } - if (app.showLoginToast) { - firstLoginHandler.sendMessage(firstLoginHandler.obtainMessage(0, status)); - app.showLoginToast = false; + if (app.showLoginToast) { + firstLoginHandler.sendMessage(firstLoginHandler.obtainMessage(0, status)); + app.showLoginToast = false; - // invoke settings activity to insert login details - if (status == StatusCode.NO_LOGIN_INFO_STORED) { - SettingsActivity.startActivity(cgeo.this); + // invoke settings activity to insert login details + if (status == StatusCode.NO_LOGIN_INFO_STORED) { + SettingsActivity.startActivity(MainActivity.this); + } + } + } + if (Settings.isOCConnectorActive()) { + IConnector conn = ConnectorFactory.getConnector("OCXXXX"); + if (conn instanceof OCApiLiveConnector) { + OCApiLiveConnector ocapiConn = (OCApiLiveConnector) conn; + if (ocapiConn.supportsPersonalization()) { + ocapiConn.retrieveUserInfo(); + } + updateUserInfoHandler.sendEmptyMessage(-1); } } } @@ -765,7 +776,7 @@ public class cgeo extends AbstractActivity { addressObtaining = true; try { - final Geocoder geocoder = new Geocoder(cgeo.this, Locale.getDefault()); + final Geocoder geocoder = new Geocoder(MainActivity.this, Locale.getDefault()); final Geopoint coords = app.currentGeo().getCoords(); addresses = geocoder.getFromLocation(coords.getLatitude(), coords.getLongitude(), 1); } catch (Exception e) { @@ -783,7 +794,7 @@ public class cgeo extends AbstractActivity { * unused here but needed since this method is referenced from XML layout */ public void showAbout(View view) { - startActivity(new Intent(this, AboutActivity.class)); + AboutActivity_.intent(this).start(); } /** diff --git a/main/src/cgeo/geocaching/NavigateAnyPointActivity.java b/main/src/cgeo/geocaching/NavigateAnyPointActivity.java index efea819..6f94944 100644 --- a/main/src/cgeo/geocaching/NavigateAnyPointActivity.java +++ b/main/src/cgeo/geocaching/NavigateAnyPointActivity.java @@ -1,10 +1,14 @@ package cgeo.geocaching; +import butterknife.InjectView; +import butterknife.Views; + import cgeo.geocaching.activity.AbstractActivity; import cgeo.geocaching.apps.cache.navi.NavigationAppFactory; import cgeo.geocaching.geopoint.DistanceParser; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.GeopointFormatter; +import cgeo.geocaching.ui.AbstractViewHolder; import cgeo.geocaching.ui.Formatter; import cgeo.geocaching.ui.dialog.CoordinatesInputDialog; import cgeo.geocaching.utils.GeoDirHandler; @@ -37,10 +41,37 @@ import android.widget.TextView; import java.util.List; public class NavigateAnyPointActivity extends AbstractActivity { - private static final int MENU_DEFAULT_NAVIGATION = 2; - private static final int MENU_NAVIGATE = 0; - private static final int MENU_CACHES_AROUND = 5; - private static final int MENU_CLEAR_HISTORY = 6; + + @InjectView(R.id.buttonLatitude) protected Button latButton; + @InjectView(R.id.buttonLongitude) protected Button lonButton; + @InjectView(R.id.current) protected Button buttonCurrent; + @InjectView(R.id.historyList) protected ListView historyListView; + @InjectView(R.id.distanceUnit) protected Spinner distanceUnitSelector; + @InjectView(R.id.bearing) protected EditText bearingEditText; + @InjectView(R.id.distance) protected EditText distanceEditText; + + private boolean changed = false; + private List<Destination> historyOfSearchedLocations; + private DestinationHistoryAdapter destinationHistoryAdapter; + private TextView historyFooter; + + private static final int CONTEXT_MENU_NAVIGATE = 1; + private static final int CONTEXT_MENU_DELETE_WAYPOINT = 2; + private static final int CONTEXT_MENU_EDIT_WAYPOINT = 3; + + private int contextMenuItemPosition; + + private String distanceUnit = ""; + + protected static class ViewHolder extends AbstractViewHolder { + @InjectView(R.id.simple_way_point_longitude) protected TextView longitude; + @InjectView(R.id.simple_way_point_latitude) protected TextView latitude; + @InjectView(R.id.date) protected TextView date; + + public ViewHolder(View rowView) { + super(rowView); + } + } private static class DestinationHistoryAdapter extends ArrayAdapter<Destination> { private LayoutInflater inflater = null; @@ -52,29 +83,29 @@ public class NavigateAnyPointActivity extends AbstractActivity { @Override public View getView(final int position, final View convertView, final ViewGroup parent) { + View rowView = convertView; - Destination loc = getItem(position); + ViewHolder viewHolder; + if (rowView == null) { + rowView = getInflater().inflate(R.layout.simple_way_point, null); + viewHolder = new ViewHolder(rowView); + } + else { + viewHolder = (ViewHolder) rowView.getTag(); + } - View v = convertView; + fillViewHolder(viewHolder, getItem(position)); - if (v == null) { - v = getInflater().inflate(R.layout.simple_way_point, - null); - } - TextView longitude = (TextView) v - .findViewById(R.id.simple_way_point_longitude); - TextView latitude = (TextView) v - .findViewById(R.id.simple_way_point_latitude); - TextView date = (TextView) v.findViewById(R.id.date); + return rowView; + } + private void fillViewHolder(ViewHolder viewHolder, Destination loc) { String lonString = loc.getCoords().format(GeopointFormatter.Format.LON_DECMINUTE); String latString = loc.getCoords().format(GeopointFormatter.Format.LAT_DECMINUTE); - longitude.setText(lonString); - latitude.setText(latString); - date.setText(Formatter.formatShortDateTime(getContext(), loc.getDate())); - - return v; + viewHolder.longitude.setText(lonString); + viewHolder.latitude.setText(latString); + viewHolder.date.setText(Formatter.formatShortDateTime(getContext(), loc.getDate())); } private LayoutInflater getInflater() { @@ -86,46 +117,22 @@ public class NavigateAnyPointActivity extends AbstractActivity { } } - private Button latButton = null; - private Button lonButton = null; - private boolean changed = false; - private List<Destination> historyOfSearchedLocations; - private DestinationHistoryAdapter destionationHistoryAdapter; - private ListView historyListView; - private TextView historyFooter; - - private static final int CONTEXT_MENU_NAVIGATE = 1; - private static final int CONTEXT_MENU_DELETE_WAYPOINT = 2; - private static final int CONTEXT_MENU_EDIT_WAYPOINT = 3; - - private int contextMenuItemPosition; - - String distanceUnit = ""; - - public NavigateAnyPointActivity() { - super("c:geo-navigate-any"); - } - @Override public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setTheme(); - setContentView(R.layout.point); - setTitle(res.getString(R.string.search_destination)); + super.onCreate(savedInstanceState, R.layout.navigateanypoint_activity); + Views.inject(this); createHistoryView(); - init(); } private void createHistoryView() { - historyListView = (ListView) findViewById(R.id.historyList); - - final View pointControls = getLayoutInflater().inflate( - R.layout.point_controls, null); + final View pointControls = getLayoutInflater().inflate(R.layout.navigateanypoint_header, null); historyListView.addHeaderView(pointControls, null, false); + // inject a second time to also find the dynamically expanded views above + Views.inject(this); + if (getHistoryOfSearchedLocations().isEmpty()) { historyListView.addFooterView(getEmptyHistoryFooter(), null, false); } @@ -146,7 +153,7 @@ public class NavigateAnyPointActivity extends AbstractActivity { historyListView.setOnCreateContextMenuListener(new OnCreateContextMenuListener() { @Override public void onCreateContextMenu(ContextMenu menu, View v, - ContextMenuInfo menuInfo) { + ContextMenuInfo menuInfo) { menu.add(Menu.NONE, CONTEXT_MENU_NAVIGATE, Menu.NONE, res.getString(R.string.cache_menu_navigate)); menu.add(Menu.NONE, CONTEXT_MENU_EDIT_WAYPOINT, Menu.NONE, R.string.waypoint_edit); menu.add(Menu.NONE, CONTEXT_MENU_DELETE_WAYPOINT, Menu.NONE, R.string.waypoint_delete); @@ -190,19 +197,17 @@ public class NavigateAnyPointActivity extends AbstractActivity { private TextView getEmptyHistoryFooter() { if (historyFooter == null) { - historyFooter = (TextView) getLayoutInflater().inflate( - R.layout.caches_footer, null); + historyFooter = (TextView) getLayoutInflater().inflate(R.layout.cacheslist_footer, null); historyFooter.setText(R.string.search_history_empty); } return historyFooter; } private DestinationHistoryAdapter getDestionationHistoryAdapter() { - if (destionationHistoryAdapter == null) { - destionationHistoryAdapter = new DestinationHistoryAdapter(this, - getHistoryOfSearchedLocations()); + if (destinationHistoryAdapter == null) { + destinationHistoryAdapter = new DestinationHistoryAdapter(this, getHistoryOfSearchedLocations()); } - return destionationHistoryAdapter; + return destinationHistoryAdapter; } private List<Destination> getHistoryOfSearchedLocations() { @@ -229,25 +234,12 @@ public class NavigateAnyPointActivity extends AbstractActivity { } @Override - public void onDestroy() { - super.onDestroy(); - } - - @Override - public void onStop() { - super.onStop(); - } - - @Override public void onPause() { geoDirHandler.stopGeo(); super.onPause(); } private void init() { - latButton = (Button) findViewById(R.id.buttonLatitude); - lonButton = (Button) findViewById(R.id.buttonLongitude); - latButton.setOnClickListener(new CoordDialogListener()); lonButton.setOnClickListener(new CoordDialogListener()); @@ -257,19 +249,15 @@ public class NavigateAnyPointActivity extends AbstractActivity { lonButton.setText(coords.format(GeopointFormatter.Format.LON_DECMINUTE)); } - Button buttonCurrent = (Button) findViewById(R.id.current); buttonCurrent.setOnClickListener(new CurrentListener()); getDestionationHistoryAdapter().notifyDataSetChanged(); - disableSuggestions((EditText) findViewById(R.id.distance)); + disableSuggestions(distanceEditText); initializeDistanceUnitSelector(); } private void initializeDistanceUnitSelector() { - - Spinner distanceUnitSelector = (Spinner) findViewById(R.id.distanceUnit); - if (StringUtils.isBlank(distanceUnit)) { if (Settings.isUseMetricUnits()) { distanceUnitSelector.setSelection(0); // m @@ -326,14 +314,8 @@ public class NavigateAnyPointActivity extends AbstractActivity { @Override public boolean onCreateOptionsMenu(Menu menu) { - menu.add(0, MENU_DEFAULT_NAVIGATION, 0, NavigationAppFactory.getDefaultNavigationApplication().getName()).setIcon(R.drawable.ic_menu_compass); // default navigation tool - - menu.add(0, MENU_NAVIGATE, 0, res.getString(R.string.cache_menu_navigate)).setIcon(R.drawable.ic_menu_mapmode); - - menu.add(0, MENU_CACHES_AROUND, 0, res.getString(R.string.cache_menu_around)).setIcon(R.drawable.ic_menu_rotate); // caches around - - menu.add(0, MENU_CLEAR_HISTORY, 0, res.getString(R.string.search_clear_history)).setIcon(R.drawable.ic_menu_delete); // clear history - + getMenuInflater().inflate(R.menu.navigate_any_point_activity_options, menu); + menu.findItem(R.id.menu_default_navigation).setTitle(NavigationAppFactory.getDefaultNavigationApplication().getName()); return true; } @@ -343,11 +325,11 @@ public class NavigateAnyPointActivity extends AbstractActivity { try { boolean visible = getDestination() != null; - menu.findItem(MENU_NAVIGATE).setVisible(visible); - menu.findItem(MENU_DEFAULT_NAVIGATION).setVisible(visible); - menu.findItem(MENU_CACHES_AROUND).setVisible(visible); + menu.findItem(R.id.menu_navigate).setVisible(visible); + menu.findItem(R.id.menu_default_navigation).setVisible(visible); + menu.findItem(R.id.menu_caches_around).setVisible(visible); - menu.findItem(MENU_CLEAR_HISTORY).setEnabled(!getHistoryOfSearchedLocations().isEmpty()); + menu.findItem(R.id.menu_clear_history).setEnabled(!getHistoryOfSearchedLocations().isEmpty()); } catch (Exception e) { // nothing } @@ -366,19 +348,19 @@ public class NavigateAnyPointActivity extends AbstractActivity { } switch (menuItem) { - case MENU_DEFAULT_NAVIGATION: + case R.id.menu_default_navigation: navigateTo(); return true; - case MENU_CACHES_AROUND: + case R.id.menu_caches_around: cachesAround(); return true; - case MENU_CLEAR_HISTORY: + case R.id.menu_clear_history: clearHistory(); return true; - case MENU_NAVIGATE: + case R.id.menu_navigate: NavigationAppFactory.showNavigationMenu(this, null, null, coords); return true; default: @@ -402,7 +384,7 @@ public class NavigateAnyPointActivity extends AbstractActivity { runOnUiThread(new Runnable() { @Override public void run() { - destionationHistoryAdapter.notifyDataSetChanged(); + destinationHistoryAdapter.notifyDataSetChanged(); } }); } @@ -495,10 +477,9 @@ public class NavigateAnyPointActivity extends AbstractActivity { } private Geopoint getDestination() { - - String bearingText = ((EditText) findViewById(R.id.bearing)).getText().toString(); + String bearingText = bearingEditText.getText().toString(); // combine distance from EditText and distanceUnit saved from Spinner - String distanceText = ((EditText) findViewById(R.id.distance)).getText().toString() + distanceUnit; + String distanceText = distanceEditText.getText().toString() + distanceUnit; String latText = latButton.getText().toString(); String lonText = lonButton.getText().toString(); diff --git a/main/src/cgeo/geocaching/SearchActivity.java b/main/src/cgeo/geocaching/SearchActivity.java index 6fdff5a..c2a7b6d 100644 --- a/main/src/cgeo/geocaching/SearchActivity.java +++ b/main/src/cgeo/geocaching/SearchActivity.java @@ -1,14 +1,16 @@ package cgeo.geocaching; +import butterknife.InjectView; +import butterknife.Views; + import cgeo.geocaching.activity.AbstractActivity; import cgeo.geocaching.connector.ConnectorFactory; import cgeo.geocaching.connector.IConnector; import cgeo.geocaching.connector.capability.ISearchByGeocode; -import cgeo.geocaching.connector.gc.GCConstants; +import cgeo.geocaching.connector.trackable.TrackableConnector; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.GeopointFormatter; import cgeo.geocaching.ui.dialog.CoordinatesInputDialog; -import cgeo.geocaching.utils.BaseUtils; import cgeo.geocaching.utils.EditUtils; import cgeo.geocaching.utils.GeoDirHandler; import cgeo.geocaching.utils.Log; @@ -33,20 +35,32 @@ import java.util.Locale; public class SearchActivity extends AbstractActivity { - private static final int MENU_SEARCH_OWN_CACHES = 1; - private EditText latEdit = null; - private EditText lonEdit = null; - - public SearchActivity() { - super("c:geo-search"); - } + @InjectView(R.id.buttonLatitude) protected Button buttonLatitude; + @InjectView(R.id.buttonLongitude) protected Button buttonLongitude; + @InjectView(R.id.search_coordinates) protected Button findByCoords; + @InjectView(R.id.search_address) protected Button findByAddress; + @InjectView(R.id.geocode) protected AutoCompleteTextView geocodeEdit; + @InjectView(R.id.display_geocode) protected Button displayByGeocode; + @InjectView(R.id.search_keyword) protected Button findByKeyword; + @InjectView(R.id.search_username) protected Button findByUserName; + @InjectView(R.id.search_owner) protected Button findByOwner; + @InjectView(R.id.trackable) protected AutoCompleteTextView trackable; + @InjectView(R.id.display_trackable) protected Button displayTrackable; + @InjectView(R.id.latitude) protected EditText latEdit; + @InjectView(R.id.longitude) protected EditText lonEdit; + @InjectView(R.id.keyword) protected EditText keywordEditText; + @InjectView(R.id.address) protected EditText addressEditText; + @InjectView(R.id.username) protected EditText userNameEditText; + @InjectView(R.id.owner) protected EditText ownerNameEditText; + @InjectView(R.id.geocode) protected EditText geocodeEditText; + @InjectView(R.id.trackable) protected EditText trackableEditText; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // search query - Intent intent = getIntent(); + final Intent intent = getIntent(); if (Intent.ACTION_SEARCH.equals(intent.getAction())) { final String query = intent.getStringExtra(SearchManager.QUERY); final boolean keywordSearch = intent.getBooleanExtra(Intents.EXTRA_KEYWORD_SEARCH, true); @@ -63,9 +77,12 @@ public class SearchActivity extends AbstractActivity { } setTheme(); - setContentView(R.layout.search); + setContentView(R.layout.search_activity); + + // set title in code, as the activity needs a hard coded title due to the intent filters setTitle(res.getString(R.string.search)); + Views.inject(this); init(); } @@ -104,7 +121,7 @@ public class SearchActivity extends AbstractActivity { // otherwise see if this is a pure geocode if (StringUtils.isEmpty(geocode)) { - geocode = StringUtils.trim(query); + geocode = StringUtils.upperCase(StringUtils.trim(query)); } final IConnector connector = ConnectorFactory.getConnector(geocode); @@ -116,10 +133,10 @@ public class SearchActivity extends AbstractActivity { } // Check if the query is a TB code - final String trackable = BaseUtils.getMatch(query, GCConstants.PATTERN_TB_CODE, true, 0, "", false); - if (StringUtils.isNotBlank(trackable)) { + final TrackableConnector trackableConnector = ConnectorFactory.getTrackableConnector(geocode); + if (trackableConnector != ConnectorFactory.UNKNOWN_TRACKABLE_CONNECTOR) { final Intent trackablesIntent = new Intent(this, TrackableActivity.class); - trackablesIntent.putExtra(Intents.EXTRA_GEOCODE, trackable.toUpperCase(Locale.US)); + trackablesIntent.putExtra(Intents.EXTRA_GEOCODE, geocode.toUpperCase(Locale.US)); startActivity(trackablesIntent); return true; } @@ -135,10 +152,8 @@ public class SearchActivity extends AbstractActivity { private void init() { Settings.getLogin(); - findViewById(R.id.buttonLatitude).setOnClickListener(new FindByCoordsAction()); - findViewById(R.id.buttonLongitude).setOnClickListener(new FindByCoordsAction()); - - final Button findByCoords = (Button) findViewById(R.id.search_coordinates); + buttonLatitude.setOnClickListener(new FindByCoordsAction()); + buttonLongitude.setOnClickListener(new FindByCoordsAction()); findByCoords.setOnClickListener(new FindByCoordsListener()); EditUtils.setActionListener((EditText) findViewById(R.id.address), new Runnable() { @@ -148,11 +163,8 @@ public class SearchActivity extends AbstractActivity { findByAddressFn(); } }); - - final Button findByAddress = (Button) findViewById(R.id.search_address); findByAddress.setOnClickListener(new FindByAddressListener()); - final AutoCompleteTextView geocodeEdit = (AutoCompleteTextView) findViewById(R.id.geocode); EditUtils.setActionListener(geocodeEdit, new Runnable() { @Override @@ -161,8 +173,6 @@ public class SearchActivity extends AbstractActivity { } }); addHistoryEntries(geocodeEdit, cgData.getRecentGeocodesForSearch()); - - final Button displayByGeocode = (Button) findViewById(R.id.display_geocode); displayByGeocode.setOnClickListener(new FindByGeocodeListener()); EditUtils.setActionListener((EditText) findViewById(R.id.keyword), new Runnable() { @@ -172,8 +182,6 @@ public class SearchActivity extends AbstractActivity { findByKeywordFn(); } }); - - final Button findByKeyword = (Button) findViewById(R.id.search_keyword); findByKeyword.setOnClickListener(new FindByKeywordListener()); EditUtils.setActionListener((EditText) findViewById(R.id.username), new Runnable() { @@ -183,8 +191,6 @@ public class SearchActivity extends AbstractActivity { findByUsernameFn(); } }); - - final Button findByUserName = (Button) findViewById(R.id.search_username); findByUserName.setOnClickListener(new FindByUsernameListener()); EditUtils.setActionListener((EditText) findViewById(R.id.owner), new Runnable() { @@ -194,8 +200,6 @@ public class SearchActivity extends AbstractActivity { findByOwnerFn(); } }); - - final Button findByOwner = (Button) findViewById(R.id.search_owner); findByOwner.setOnClickListener(new OnClickListener() { @Override @@ -204,7 +208,6 @@ public class SearchActivity extends AbstractActivity { } }); - AutoCompleteTextView trackable = (AutoCompleteTextView) findViewById(R.id.trackable); EditUtils.setActionListener(trackable, new Runnable() { @Override @@ -213,10 +216,7 @@ public class SearchActivity extends AbstractActivity { } }); addHistoryEntries(trackable, cgData.getTrackableCodes()); - disableSuggestions(trackable); - - final Button displayTrackable = (Button) findViewById(R.id.display_trackable); displayTrackable.setOnClickListener(new FindTrackableListener()); } @@ -231,13 +231,6 @@ public class SearchActivity extends AbstractActivity { @Override public void updateGeoData(final IGeoData geo) { try { - if (latEdit == null) { - latEdit = (EditText) findViewById(R.id.latitude); - } - if (lonEdit == null) { - lonEdit = (EditText) findViewById(R.id.longitude); - } - if (geo.getCoords() != null) { if (latEdit != null) { latEdit.setHint(geo.getCoords().format(GeopointFormatter.Format.LAT_DECMINUTE_RAW)); @@ -246,7 +239,7 @@ public class SearchActivity extends AbstractActivity { lonEdit.setHint(geo.getCoords().format(GeopointFormatter.Format.LON_DECMINUTE_RAW)); } } - } catch (Exception e) { + } catch (final Exception e) { Log.w("Failed to update location."); } } @@ -256,13 +249,13 @@ public class SearchActivity extends AbstractActivity { @Override public void onClick(View arg0) { - CoordinatesInputDialog coordsDialog = new CoordinatesInputDialog(SearchActivity.this, null, null, app.currentGeo()); + final CoordinatesInputDialog coordsDialog = new CoordinatesInputDialog(SearchActivity.this, null, null, app.currentGeo()); coordsDialog.setCancelable(true); coordsDialog.setOnCoordinateUpdate(new CoordinatesInputDialog.CoordinateUpdate() { @Override public void update(Geopoint gp) { - ((Button) findViewById(R.id.buttonLatitude)).setText(gp.format(GeopointFormatter.Format.LAT_DECMINUTE)); - ((Button) findViewById(R.id.buttonLongitude)).setText(gp.format(GeopointFormatter.Format.LON_DECMINUTE)); + buttonLatitude.setText(gp.format(GeopointFormatter.Format.LAT_DECMINUTE)); + buttonLongitude.setText(gp.format(GeopointFormatter.Format.LON_DECMINUTE)); } }); coordsDialog.show(); @@ -278,21 +271,19 @@ public class SearchActivity extends AbstractActivity { } private void findByCoordsFn() { - final Button latView = (Button) findViewById(R.id.buttonLatitude); - final Button lonView = (Button) findViewById(R.id.buttonLongitude); - final String latText = latView.getText().toString(); - final String lonText = lonView.getText().toString(); + final String latText = buttonLatitude.getText().toString(); + final String lonText = buttonLongitude.getText().toString(); if (StringUtils.isEmpty(latText) || StringUtils.isEmpty(lonText)) { final IGeoData geo = app.currentGeo(); if (geo.getCoords() != null) { - latView.setText(geo.getCoords().format(GeopointFormatter.Format.LAT_DECMINUTE)); - lonView.setText(geo.getCoords().format(GeopointFormatter.Format.LON_DECMINUTE)); + buttonLatitude.setText(geo.getCoords().format(GeopointFormatter.Format.LAT_DECMINUTE)); + buttonLongitude.setText(geo.getCoords().format(GeopointFormatter.Format.LON_DECMINUTE)); } } else { try { cgeocaches.startActivityCoordinates(this, new Geopoint(StringUtils.trim(latText), StringUtils.trim(lonText))); - } catch (Geopoint.ParseException e) { + } catch (final Geopoint.ParseException e) { showToast(res.getString(e.resource)); } } @@ -308,7 +299,7 @@ public class SearchActivity extends AbstractActivity { private void findByKeywordFn() { // find caches by coordinates - String keyText = ((EditText) findViewById(R.id.keyword)).getText().toString(); + final String keyText = keywordEditText.getText().toString(); if (StringUtils.isBlank(keyText)) { helpDialog(res.getString(R.string.warn_search_help_title), res.getString(R.string.warn_search_help_keyword)); @@ -326,7 +317,7 @@ public class SearchActivity extends AbstractActivity { } private void findByAddressFn() { - final String addText = ((EditText) findViewById(R.id.address)).getText().toString(); + final String addText = addressEditText.getText().toString(); if (StringUtils.isBlank(addText)) { helpDialog(res.getString(R.string.warn_search_help_title), res.getString(R.string.warn_search_help_address)); @@ -347,7 +338,7 @@ public class SearchActivity extends AbstractActivity { } public void findByUsernameFn() { - final String usernameText = ((EditText) findViewById(R.id.username)).getText().toString(); + final String usernameText = userNameEditText.getText().toString(); if (StringUtils.isBlank(usernameText)) { helpDialog(res.getString(R.string.warn_search_help_title), res.getString(R.string.warn_search_help_user)); @@ -358,7 +349,7 @@ public class SearchActivity extends AbstractActivity { } private void findByOwnerFn() { - findByOwnerFn(((EditText) findViewById(R.id.owner)).getText().toString()); + findByOwnerFn(ownerNameEditText.getText().toString()); } private void findByOwnerFn(String userName) { @@ -381,7 +372,7 @@ public class SearchActivity extends AbstractActivity { } private void findByGeocodeFn() { - final String geocodeText = ((EditText) findViewById(R.id.geocode)).getText().toString(); + final String geocodeText = geocodeEditText.getText().toString(); if (StringUtils.isBlank(geocodeText) || geocodeText.equalsIgnoreCase("GC")) { helpDialog(res.getString(R.string.warn_search_help_title), res.getString(R.string.warn_search_help_gccode)); @@ -400,7 +391,7 @@ public class SearchActivity extends AbstractActivity { } private void findTrackableFn() { - final String trackableText = ((EditText) findViewById(R.id.trackable)).getText().toString(); + final String trackableText = trackableEditText.getText().toString(); if (StringUtils.isBlank(trackableText) || trackableText.equalsIgnoreCase("TB")) { helpDialog(res.getString(R.string.warn_search_help_title), res.getString(R.string.warn_search_help_tb)); @@ -414,13 +405,13 @@ public class SearchActivity extends AbstractActivity { @Override public boolean onCreateOptionsMenu(Menu menu) { - menu.add(0, MENU_SEARCH_OWN_CACHES, 0, res.getString(R.string.search_own_caches)).setIcon(R.drawable.ic_menu_myplaces); + getMenuInflater().inflate(R.menu.search_activity_options, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == MENU_SEARCH_OWN_CACHES) { + if (item.getItemId() == R.id.menu_search_own_caches) { findByOwnerFn(Settings.getUsername()); return true; } @@ -432,6 +423,6 @@ public class SearchActivity extends AbstractActivity { searchIntent.setAction(Intent.ACTION_SEARCH). putExtra(SearchManager.QUERY, scan). putExtra(Intents.EXTRA_KEYWORD_SEARCH, false); - fromActivity.startActivityForResult(searchIntent, cgeo.SEARCH_REQUEST_CODE); + fromActivity.startActivityForResult(searchIntent, MainActivity.SEARCH_REQUEST_CODE); } } diff --git a/main/src/cgeo/geocaching/SearchResult.java b/main/src/cgeo/geocaching/SearchResult.java index b0540f2..4cef95e 100644 --- a/main/src/cgeo/geocaching/SearchResult.java +++ b/main/src/cgeo/geocaching/SearchResult.java @@ -114,7 +114,7 @@ public class SearchResult implements Parcelable { public SearchResult(final Collection<Geocache> caches) { this(); for (final Geocache cache : caches) { - addCache(cache); + addAndPutInCache(cache); } } @@ -199,7 +199,7 @@ public class SearchResult implements Parcelable { (excludeMine && (cache.isOwner() || cache.isFound())) || (!cacheType.contains(cache)); if (!excludeCache) { - result.addCache(cache); + result.addAndPutInCache(cache); cachesForVote.add(cache); } } @@ -229,7 +229,7 @@ public class SearchResult implements Parcelable { } /** Add the cache geocode to the search and store the cache in the CacheCache */ - public boolean addCache(final Geocache cache) { + public boolean addAndPutInCache(final Geocache cache) { addGeocode(cache.getGeocode()); return cgData.saveCache(cache, EnumSet.of(SaveFlag.SAVE_CACHE)); } diff --git a/main/src/cgeo/geocaching/SelectMapfileActivity.java b/main/src/cgeo/geocaching/SelectMapfileActivity.java index 9557f3e..aa6d46a 100644 --- a/main/src/cgeo/geocaching/SelectMapfileActivity.java +++ b/main/src/cgeo/geocaching/SelectMapfileActivity.java @@ -55,11 +55,6 @@ public class SelectMapfileActivity extends AbstractFileListActivity<FileSelectio } @Override - protected void setTitle() { - setTitle(res.getString(R.string.map_file_select_title)); - } - - @Override public String getCurrentFile() { return mapFile; } diff --git a/main/src/cgeo/geocaching/Settings.java b/main/src/cgeo/geocaching/Settings.java index 0c157e1..7a5db12 100644 --- a/main/src/cgeo/geocaching/Settings.java +++ b/main/src/cgeo/geocaching/Settings.java @@ -40,8 +40,8 @@ import java.util.Locale; */ public final class Settings { - private static final String KEY_TEMP_TOKEN_SECRET = "temp-token-secret"; - private static final String KEY_TEMP_TOKEN_PUBLIC = "temp-token-public"; + private static final String KEY_TEMP_TWITTER_TOKEN_SECRET = "temp-token-secret"; + private static final String KEY_TEMP_TWITTER_TOKEN_PUBLIC = "temp-token-public"; private static final String KEY_HELP_SHOWN = "helper"; private static final String KEY_ANYLONGITUDE = "anylongitude"; private static final String KEY_ANYLATITUDE = "anylatitude"; @@ -110,8 +110,14 @@ public final class Settings { private static final String KEY_PLAIN_LOGS = "plainLogs"; private static final String KEY_NATIVE_UA = "nativeUa"; private static final String KEY_MAP_DIRECTORY = "mapDirectory"; + private static final String KEY_CONNECTOR_GC_ACTIVE = "connectorGCActive"; private static final String KEY_CONNECTOR_OC_ACTIVE = "connectorOCActive"; - private static final String KEY_CONNECTOR_OC_USER = "connectorOCUser"; + private static final String KEY_LOG_IMAGE_SCALE = "logImageScale"; + private static final String KEY_OCDE_TOKEN_SECRET = "ocde_tokensecret"; + private static final String KEY_OCDE_TOKEN_PUBLIC = "ocde_tokenpublic"; + private static final String KEY_TEMP_OCDE_TOKEN_SECRET = "ocde-temp-token-secret"; + private static final String KEY_TEMP_OCDE_TOKEN_PUBLIC = "ocde-temp-token-public"; + private final static int unitsMetric = 1; @@ -149,6 +155,7 @@ public final class Settings { // maps private static MapProvider mapProvider = null; + private static String cacheTwitterMessage = "I found [NAME] ([URL])"; private Settings() { // this class is not to be instantiated; @@ -161,8 +168,8 @@ public final class Settings { final SharedPreferences old = cgeoapplication.getInstance().getSharedPreferences(oldPreferencesName, Context.MODE_PRIVATE); final Editor e = sharedPrefs.edit(); - e.putString(KEY_TEMP_TOKEN_SECRET, old.getString(KEY_TEMP_TOKEN_SECRET, null)); - e.putString(KEY_TEMP_TOKEN_PUBLIC, old.getString(KEY_TEMP_TOKEN_PUBLIC, null)); + e.putString(KEY_TEMP_TWITTER_TOKEN_SECRET, old.getString(KEY_TEMP_TWITTER_TOKEN_SECRET, null)); + e.putString(KEY_TEMP_TWITTER_TOKEN_PUBLIC, old.getString(KEY_TEMP_TWITTER_TOKEN_PUBLIC, null)); e.putBoolean(KEY_HELP_SHOWN, old.getInt(KEY_HELP_SHOWN, 0) != 0); e.putFloat(KEY_ANYLONGITUDE, old.getFloat(KEY_ANYLONGITUDE, 0)); e.putFloat(KEY_ANYLATITUDE, old.getFloat(KEY_ANYLATITUDE, 0)); @@ -185,9 +192,9 @@ public final class Settings { e.putString(KEY_TWITTER_TOKEN_SECRET, old.getString(KEY_TWITTER_TOKEN_SECRET, null)); e.putString(KEY_TWITTER_TOKEN_PUBLIC, old.getString(KEY_TWITTER_TOKEN_PUBLIC, null)); e.putInt(KEY_VERSION, old.getInt(KEY_VERSION, 0)); - e.putBoolean(KEY_LOAD_DESCRIPTION, 0 != old.getInt(KEY_LOAD_DESCRIPTION, 0)); + e.putBoolean(KEY_LOAD_DESCRIPTION, 0 != old.getInt(KEY_LOAD_DESCRIPTION, 1)); e.putBoolean(KEY_RATING_WANTED, old.getBoolean(KEY_RATING_WANTED, true)); - e.putBoolean(KEY_ELEVATION_WANTED, old.getBoolean(KEY_ELEVATION_WANTED, true)); + e.putBoolean(KEY_ELEVATION_WANTED, old.getBoolean(KEY_ELEVATION_WANTED, false)); e.putBoolean(KEY_FRIENDLOGS_WANTED, old.getBoolean(KEY_FRIENDLOGS_WANTED, true)); e.putBoolean(KEY_USE_ENGLISH, old.getBoolean(KEY_USE_ENGLISH, false)); e.putBoolean(KEY_USE_COMPASS, 0 != old.getInt(KEY_USE_COMPASS, 1)); @@ -282,6 +289,20 @@ public final class Settings { }); } + public static boolean isGCConnectorActive() { + return sharedPrefs.getBoolean(KEY_CONNECTOR_GC_ACTIVE, true); + } + + public static boolean setGCConnectorActive(final boolean isActive) { + return editSharedSettings(new PrefRunnable() { + + @Override + public void edit(Editor edit) { + edit.putBoolean(KEY_CONNECTOR_GC_ACTIVE, isActive); + } + }); + } + public static boolean isPremiumMember() { // Basic Member, Premium Member, ??? String memberStatus = Settings.getMemberStatus(); @@ -323,26 +344,44 @@ public final class Settings { }); } - public static String getOCConnectorUserName() { - String ocConnectorUser = sharedPrefs.getString(KEY_CONNECTOR_OC_USER, null); - if (StringUtils.isBlank(ocConnectorUser)) { - return StringUtils.EMPTY; - } - return ocConnectorUser; + public static String getOCDETokenPublic() { + return sharedPrefs.getString(KEY_OCDE_TOKEN_PUBLIC, ""); } - public static boolean setOCConnectorUserName(final String userName) { - return editSharedSettings(new PrefRunnable() { + public static String getOCDETokenSecret() { + return sharedPrefs.getString(KEY_OCDE_TOKEN_SECRET, ""); + } + + public static void setOCDETokens(final String tokenPublic, final String tokenSecret, boolean enableOcDe) { + editSharedSettings(new PrefRunnable() { @Override public void edit(Editor edit) { - if (StringUtils.isBlank(userName)) { - edit.remove(KEY_CONNECTOR_OC_USER); - } else { - edit.putString(KEY_CONNECTOR_OC_USER, userName); + edit.putString(KEY_OCDE_TOKEN_PUBLIC, tokenPublic); + edit.putString(KEY_OCDE_TOKEN_SECRET, tokenSecret); + if (tokenPublic != null) { + edit.remove(KEY_TEMP_OCDE_TOKEN_PUBLIC); + edit.remove(KEY_TEMP_OCDE_TOKEN_SECRET); } } }); + setOCConnectorActive(enableOcDe); + } + + public static void setOCDETempTokens(final String tokenPublic, final String tokenSecret) { + editSharedSettings(new PrefRunnable() { + @Override + public void edit(Editor edit) { + edit.putString(KEY_TEMP_OCDE_TOKEN_PUBLIC, tokenPublic); + edit.putString(KEY_TEMP_OCDE_TOKEN_SECRET, tokenSecret); + } + }); + } + + public static ImmutablePair<String, String> getTempOCDEToken() { + String tokenPublic = sharedPrefs.getString(KEY_TEMP_OCDE_TOKEN_PUBLIC, null); + String tokenSecret = sharedPrefs.getString(KEY_TEMP_OCDE_TOKEN_SECRET, null); + return new ImmutablePair<String, String>(tokenPublic, tokenSecret); } public static boolean isGCvoteLogin() { @@ -725,7 +764,7 @@ public final class Settings { } public static boolean isAutoLoadDescription() { - return sharedPrefs.getBoolean(KEY_LOAD_DESCRIPTION, false); + return sharedPrefs.getBoolean(KEY_LOAD_DESCRIPTION, true); } public static void setAutoLoadDesc(final boolean autoLoad) { @@ -753,7 +792,7 @@ public final class Settings { } public static boolean isElevationWanted() { - return sharedPrefs.getBoolean(KEY_ELEVATION_WANTED, true); + return sharedPrefs.getBoolean(KEY_ELEVATION_WANTED, false); } public static void setElevationWanted(final boolean elevationWanted) { @@ -1120,8 +1159,8 @@ public final class Settings { edit.putString(KEY_TWITTER_TOKEN_PUBLIC, tokenPublic); edit.putString(KEY_TWITTER_TOKEN_SECRET, tokenSecret); if (tokenPublic != null) { - edit.remove(KEY_TEMP_TOKEN_PUBLIC); - edit.remove(KEY_TEMP_TOKEN_SECRET); + edit.remove(KEY_TEMP_TWITTER_TOKEN_PUBLIC); + edit.remove(KEY_TEMP_TWITTER_TOKEN_SECRET); } } }); @@ -1132,15 +1171,15 @@ public final class Settings { editSharedSettings(new PrefRunnable() { @Override public void edit(Editor edit) { - edit.putString(KEY_TEMP_TOKEN_PUBLIC, tokenPublic); - edit.putString(KEY_TEMP_TOKEN_SECRET, tokenSecret); + edit.putString(KEY_TEMP_TWITTER_TOKEN_PUBLIC, tokenPublic); + edit.putString(KEY_TEMP_TWITTER_TOKEN_SECRET, tokenSecret); } }); } public static ImmutablePair<String, String> getTempToken() { - String tokenPublic = sharedPrefs.getString(KEY_TEMP_TOKEN_PUBLIC, null); - String tokenSecret = sharedPrefs.getString(KEY_TEMP_TOKEN_SECRET, null); + String tokenPublic = sharedPrefs.getString(KEY_TEMP_TWITTER_TOKEN_PUBLIC, null); + String tokenSecret = sharedPrefs.getString(KEY_TEMP_TWITTER_TOKEN_SECRET, null); return new ImmutablePair<String, String>(tokenPublic, tokenSecret); } @@ -1424,4 +1463,32 @@ public final class Settings { } }); } + + public static String getCacheTwitterMessage() { + // TODO make customizable from UI + return cacheTwitterMessage; + } + + public static String getTrackableTwitterMessage() { + // TODO make customizable from UI + return "I touched [NAME] ([URL])!"; + } + + public static void setCacheTwitterMessage(final String message) { + cacheTwitterMessage = message; + } + + public static int getLogImageScale() { + return sharedPrefs.getInt(KEY_LOG_IMAGE_SCALE, -1); + } + + public static void setLogImageScale(final int scale) { + editSharedSettings(new PrefRunnable() { + + @Override + public void edit(Editor edit) { + edit.putInt(KEY_LOG_IMAGE_SCALE, scale); + } + }); + } } diff --git a/main/src/cgeo/geocaching/SettingsActivity.java b/main/src/cgeo/geocaching/SettingsActivity.java index 0678617..872d5ee 100644 --- a/main/src/cgeo/geocaching/SettingsActivity.java +++ b/main/src/cgeo/geocaching/SettingsActivity.java @@ -5,6 +5,7 @@ import cgeo.geocaching.apps.cache.navi.NavigationAppFactory; import cgeo.geocaching.apps.cache.navi.NavigationAppFactory.NavigationAppsEnum; import cgeo.geocaching.compatibility.Compatibility; import cgeo.geocaching.connector.gc.Login; +import cgeo.geocaching.connector.oc.OCAuthorizationActivity; import cgeo.geocaching.enumerations.StatusCode; import cgeo.geocaching.files.SimpleDirChooser; import cgeo.geocaching.maps.MapProviderFactory; @@ -123,19 +124,9 @@ public class SettingsActivity extends AbstractActivity { } }; - public SettingsActivity() { - super("c:geo-configuration"); - } - @Override public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - // init - - setTheme(); - setContentView(R.layout.init); - setTitle(res.getString(R.string.settings)); + super.onCreate(savedInstanceState, R.layout.settings_activity); init(); } @@ -169,14 +160,13 @@ public class SettingsActivity extends AbstractActivity { @Override public boolean onCreateOptionsMenu(Menu menu) { - menu.add(0, 0, 0, res.getString(R.string.init_clear)).setIcon(R.drawable.ic_menu_delete); - + getMenuInflater().inflate(R.menu.settings_activity_options, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == 0) { + if (item.getItemId() == R.id.menu_clear) { ((EditText) findViewById(R.id.username)).setText(""); ((EditText) findViewById(R.id.password)).setText(""); ((EditText) findViewById(R.id.passvote)).setText(""); @@ -223,6 +213,15 @@ public class SettingsActivity extends AbstractActivity { public void init() { // geocaching.com settings + final CheckBox gcCheck = (CheckBox) findViewById(R.id.gc_option); + gcCheck.setChecked(Settings.isGCConnectorActive()); + gcCheck.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + Settings.setGCConnectorActive(gcCheck.isChecked()); + } + }); final ImmutablePair<String, String> login = Settings.getLogin(); if (login != null) { ((EditText) findViewById(R.id.username)).setText(login.left); @@ -252,10 +251,9 @@ public class SettingsActivity extends AbstractActivity { Settings.setOCConnectorActive(ocCheck.isChecked()); } }); - EditText ocUserEdit = (EditText) findViewById(R.id.oc_username); - if (ocUserEdit.getText().length() == 0) { - ocUserEdit.setText(Settings.getOCConnectorUserName()); - } + + Button checkOCUser = (Button) findViewById(R.id.register_oc_de); + checkOCUser.setOnClickListener(new OCDEAuthorizeCgeoListener()); // gcvote settings final ImmutablePair<String, String> gcvoteLogin = Settings.getGCvoteLogin(); @@ -839,7 +837,6 @@ public class SettingsActivity extends AbstractActivity { String signatureNew = ((EditText) findViewById(R.id.signature)).getText().toString(); String mapDirectoryNew = StringUtils.trimToEmpty(((EditText) findViewById(R.id.map_directory)).getText().toString()); String themesDirectoryNew = StringUtils.trimToEmpty(((EditText) findViewById(R.id.themefolder)).getText().toString()); - String ocUserName = StringUtils.trimToEmpty(((EditText) findViewById(R.id.oc_username)).getText().toString()); String altitudeNew = StringUtils.trimToNull(((EditText) findViewById(R.id.altitude)).getText().toString()); int altitudeNewInt = parseNumber(altitudeNew, 0); @@ -853,7 +850,6 @@ public class SettingsActivity extends AbstractActivity { final boolean status4 = Settings.setAltCorrection(altitudeNewInt); final boolean status5 = Settings.setMapFileDirectory(mapDirectoryNew); final boolean status6 = Settings.setCustomRenderThemeBaseFolder(themesDirectoryNew); - final boolean status7 = Settings.setOCConnectorUserName(ocUserName); Settings.setShowWaypointsThreshold(waypointThreshold); String importNew = StringUtils.trimToEmpty(((EditText) findViewById(R.id.gpx_importdir)).getText().toString()); @@ -861,7 +857,7 @@ public class SettingsActivity extends AbstractActivity { Settings.setGpxImportDir(importNew); Settings.setGpxExportDir(exportNew); - return status1 && status2 && status3 && status4 && status5 && status6 && status7; + return status1 && status2 && status3 && status4 && status5 && status6; } /** @@ -930,6 +926,15 @@ public class SettingsActivity extends AbstractActivity { } } + private class OCDEAuthorizeCgeoListener implements View.OnClickListener { + + @Override + public void onClick(View v) { + Intent authIntent = new Intent(SettingsActivity.this, OCAuthorizationActivity.class); + startActivity(authIntent); + } + } + private class WebAuthListener implements View.OnClickListener { @Override diff --git a/main/src/cgeo/geocaching/StaticMapsActivity.java b/main/src/cgeo/geocaching/StaticMapsActivity.java index 005ee9e..4658262 100644 --- a/main/src/cgeo/geocaching/StaticMapsActivity.java +++ b/main/src/cgeo/geocaching/StaticMapsActivity.java @@ -4,34 +4,39 @@ import cgeo.geocaching.activity.AbstractActivity; import cgeo.geocaching.enumerations.LoadFlags; import cgeo.geocaching.utils.Log; +import com.googlecode.androidannotations.annotations.EActivity; +import com.googlecode.androidannotations.annotations.Extra; +import com.googlecode.androidannotations.annotations.OptionsItem; +import com.googlecode.androidannotations.annotations.OptionsMenu; + import org.apache.commons.collections.CollectionUtils; import android.app.ProgressDialog; import android.content.Context; -import android.content.Intent; import android.graphics.Bitmap; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; import android.widget.ImageView; import android.widget.LinearLayout; import java.util.ArrayList; import java.util.List; +@EActivity +@OptionsMenu(R.menu.static_maps_activity_options) public class StaticMapsActivity extends AbstractActivity { private static final String EXTRAS_WAYPOINT = "waypoint"; private static final String EXTRAS_DOWNLOAD = "download"; private static final String EXTRAS_GEOCODE = "geocode"; - private static final int MENU_REFRESH = 1; + + @Extra(EXTRAS_DOWNLOAD) boolean download = false; + @Extra(EXTRAS_WAYPOINT) Integer waypoint_id = null; + @Extra(EXTRAS_GEOCODE) String geocode = null; + private final List<Bitmap> maps = new ArrayList<Bitmap>(); - private boolean download = false; - private Integer waypoint_id = null; - private String geocode = null; private LayoutInflater inflater = null; private ProgressDialog waitDialog = null; private LinearLayout smapsView = null; @@ -79,7 +84,7 @@ public class StaticMapsActivity extends AbstractActivity { for (final Bitmap image : maps) { if (image != null) { - final ImageView map = (ImageView) inflater.inflate(R.layout.map_static_item, null); + final ImageView map = (ImageView) inflater.inflate(R.layout.staticmaps_activity_item, null); map.setImageBitmap(image); smapsView.addView(map); } @@ -88,23 +93,7 @@ public class StaticMapsActivity extends AbstractActivity { @Override public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setTheme(); - setContentView(R.layout.map_static); - setTitle(res.getString(R.string.map_static_title)); - - // get parameters - final Bundle extras = getIntent().getExtras(); - - // try to get data from extras - if (extras != null) { - download = extras.getBoolean(EXTRAS_DOWNLOAD, false); - geocode = extras.getString(EXTRAS_GEOCODE); - if (extras.containsKey(EXTRAS_WAYPOINT)) { - waypoint_id = extras.getInt(EXTRAS_WAYPOINT); - } - } + super.onCreate(savedInstanceState, R.layout.staticmaps_activity); if (geocode == null) { showToast("Sorry, c:geo forgot for what cache you want to load static maps."); @@ -161,20 +150,10 @@ public class StaticMapsActivity extends AbstractActivity { } } - @Override - public boolean onCreateOptionsMenu(Menu menu) { - menu.add(0, MENU_REFRESH, 0, res.getString(R.string.cache_offline_refresh)); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == MENU_REFRESH) { - downloadStaticMaps(); - restartActivity(); - return true; - } - return super.onOptionsItemSelected(item); + @OptionsItem(R.id.menu_refresh) + void refreshMaps() { + downloadStaticMaps(); + restartActivity(); } private boolean downloadStaticMaps() { @@ -197,16 +176,10 @@ public class StaticMapsActivity extends AbstractActivity { } public static void startActivity(final Context activity, final String geocode, final boolean download, final Waypoint waypoint) { - final Intent intent = new Intent(activity, StaticMapsActivity.class); - // if resuming our app within this activity, finish it and return to the cache activity - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); - intent.putExtra(EXTRAS_GEOCODE, geocode); - if (download) { - intent.putExtra(EXTRAS_DOWNLOAD, true); - } + StaticMapsActivity_.IntentBuilder_ builder = StaticMapsActivity_.intent(activity).geocode(geocode).download(download); if (waypoint != null) { - intent.putExtra(EXTRAS_WAYPOINT, waypoint.getId()); + builder.waypoint_id(waypoint.getId()); } - activity.startActivity(intent); + builder.start(); } }
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/StaticMapsProvider.java b/main/src/cgeo/geocaching/StaticMapsProvider.java index cd88071..9a4c00b 100644 --- a/main/src/cgeo/geocaching/StaticMapsProvider.java +++ b/main/src/cgeo/geocaching/StaticMapsProvider.java @@ -10,7 +10,6 @@ import cgeo.geocaching.utils.Log; import ch.boye.httpclientandroidlib.HttpResponse; -import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import android.content.Context; @@ -81,10 +80,6 @@ public class StaticMapsProvider { } public static void downloadMaps(Geocache cache) { - if (cache == null) { - Log.e("downloadMaps - missing input parameter cache"); - return; - } if ((!Settings.isStoreOfflineMaps() && !Settings.isStoreOfflineWpMaps()) || StringUtils.isBlank(cache.getGeocode())) { return; } @@ -96,8 +91,8 @@ public class StaticMapsProvider { } // clean old and download static maps for waypoints if one is missing - if (Settings.isStoreOfflineWpMaps() && CollectionUtils.isNotEmpty(cache.getWaypoints())) { - for (Waypoint waypoint : cache.getWaypoints()) { + if (Settings.isStoreOfflineWpMaps()) { + for (final Waypoint waypoint : cache.getWaypoints()) { if (!hasAllStaticMapsForWaypoint(cache.getGeocode(), waypoint)) { refreshAllWpStaticMaps(cache, edge); } @@ -167,10 +162,6 @@ public class StaticMapsProvider { } public static void storeCachePreviewMap(final Geocache cache) { - if (cache == null) { - Log.e("storeCachePreviewMap - missing input parameter cache"); - return; - } final String latlonMap = cache.getCoords().format(Format.LAT_LON_DECDEGREE_COMMA); final Display display = ((WindowManager) cgeoapplication.getInstance().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); DisplayMetrics metrics = new DisplayMetrics(); @@ -183,12 +174,7 @@ public class StaticMapsProvider { private static int guessMaxDisplaySide() { Point displaySize = Compatibility.getDisplaySize(); - final int maxWidth = displaySize.x - 25; - final int maxHeight = displaySize.y - 25; - if (maxWidth > maxHeight) { - return maxWidth; - } - return maxHeight; + return Math.max(displaySize.x, displaySize.y) - 25; } private static void downloadMaps(final String geocode, final String markerUrl, final String prefix, final String latlonMap, final int edge, @@ -245,7 +231,7 @@ public class StaticMapsProvider { /** * Check if at least one map file exists for the given cache. - * + * * @param cache * @return <code>true</code> if at least one map file exists; <code>false</code> otherwise */ @@ -268,7 +254,7 @@ public class StaticMapsProvider { /** * Checks if at least one map file exists for the given geocode and waypoint ID. - * + * * @param geocode * @param waypoint * @return <code>true</code> if at least one map file exists; <code>false</code> otherwise @@ -287,7 +273,7 @@ public class StaticMapsProvider { /** * Checks if all map files exist for the given geocode and waypoint ID. - * + * * @param geocode * @param waypoint * @return <code>true</code> if all map files exist; <code>false</code> otherwise @@ -326,5 +312,4 @@ public class StaticMapsProvider { } return null; } - } diff --git a/main/src/cgeo/geocaching/StoredList.java b/main/src/cgeo/geocaching/StoredList.java index 5a6f132..55a155c 100644 --- a/main/src/cgeo/geocaching/StoredList.java +++ b/main/src/cgeo/geocaching/StoredList.java @@ -12,10 +12,13 @@ import android.content.res.Resources; import android.view.View; import android.widget.EditText; +import java.text.Collator; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.List; -public class StoredList { +public final class StoredList { public static final int TEMPORARY_LIST_ID = 0; public static final int STANDARD_LIST_ID = 1; public static final int ALL_LIST_ID = 2; @@ -68,8 +71,8 @@ public class StoredList { promptForListSelection(titleId, runAfterwards, false, -1); } - public void promptForListSelection(final int titleId, final RunnableWithArgument<Integer> runAfterwards, final boolean onlyMoveTargets, final int exceptListId) { - final List<StoredList> lists = cgData.getLists(); + public void promptForListSelection(final int titleId, final RunnableWithArgument<Integer> runAfterwards, final boolean onlyConcreteLists, final int exceptListId) { + final List<StoredList> lists = getSortedLists(); if (lists == null) { return; @@ -86,7 +89,7 @@ public class StoredList { for (StoredList list : lists) { listsTitle.add(list.getTitleAndCount()); } - if (!onlyMoveTargets) { + if (!onlyConcreteLists) { listsTitle.add("<" + res.getString(R.string.list_menu_all_lists) + ">"); } listsTitle.add("<" + res.getString(R.string.list_menu_create) + ">"); @@ -98,7 +101,7 @@ public class StoredList { builder.setItems(listsTitle.toArray(items), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int itemId) { - if (itemId == lists.size() && !onlyMoveTargets) { + if (itemId == lists.size() && !onlyConcreteLists) { // all lists runAfterwards.run(StoredList.ALL_LIST_ID); } else if (itemId >= lists.size()) { @@ -115,6 +118,19 @@ public class StoredList { builder.create().show(); } + private static List<StoredList> getSortedLists() { + final Collator collator = Collator.getInstance(); + final List<StoredList> lists = cgData.getLists(); + Collections.sort(lists, new Comparator<StoredList>() { + + @Override + public int compare(StoredList lhs, StoredList rhs) { + return collator.compare(lhs.getTitle(), rhs.getTitle()); + } + }); + return lists; + } + public void promptForListCreation(final RunnableWithArgument<Integer> runAfterwards) { handleListNameInput("", R.string.list_dialog_create_title, R.string.list_dialog_create, new RunnableWithArgument<String>() { @@ -176,4 +192,23 @@ public class StoredList { }); } } + + /** + * Get the list title. This method is not public by intention to make clients use the {@link UserInterface} class. + * + * @return + */ + protected String getTitle() { + return title; + } + + /** + * Return the given list, if it is a concrete list. Return the default list otherwise. + */ + public static int getConcreteList(int listId) { + if (listId == ALL_LIST_ID || listId == TEMPORARY_LIST_ID) { + return STANDARD_LIST_ID; + } + return listId; + } } diff --git a/main/src/cgeo/geocaching/Trackable.java b/main/src/cgeo/geocaching/Trackable.java index f777351..d532cda 100644 --- a/main/src/cgeo/geocaching/Trackable.java +++ b/main/src/cgeo/geocaching/Trackable.java @@ -1,7 +1,8 @@ package cgeo.geocaching; +import cgeo.geocaching.connector.ConnectorFactory; +import cgeo.geocaching.connector.trackable.TrackableConnector; import cgeo.geocaching.enumerations.LogType; -import cgeo.geocaching.utils.Log; import org.apache.commons.lang3.StringUtils; @@ -38,17 +39,11 @@ public class Trackable implements ILogable { private String trackingcode = null; public String getUrl() { - if (StringUtils.startsWithIgnoreCase(getGeocode(), "GK")) { - String hex = getGeocode().substring(3); - try { - int id = Integer.parseInt(hex, 16); - return "http://geokrety.org/konkret.php?id=" + id; - } catch (NumberFormatException e) { - Log.e("Trackable.getUrl", e); - return null; - } - } - return "http://www.geocaching.com//track/details.aspx?tracker=" + geocode; + return getConnector().getUrl(this); + } + + private TrackableConnector getConnector() { + return ConnectorFactory.getConnector(this); } public String getGuid() { @@ -208,7 +203,7 @@ public class Trackable implements ILogable { } public boolean isLoggable() { - return !StringUtils.startsWithIgnoreCase(getGeocode(), "GK"); + return getConnector().isLoggable(); } public String getTrackingcode() { @@ -220,7 +215,7 @@ public class Trackable implements ILogable { } static public List<LogType> getPossibleLogTypes() { - List<LogType> logTypes = new ArrayList<LogType>(); + final List<LogType> logTypes = new ArrayList<LogType>(); logTypes.add(LogType.RETRIEVED_IT); logTypes.add(LogType.GRABBED_IT); logTypes.add(LogType.NOTE); diff --git a/main/src/cgeo/geocaching/TrackableActivity.java b/main/src/cgeo/geocaching/TrackableActivity.java index fea4521..8dc29ee 100644 --- a/main/src/cgeo/geocaching/TrackableActivity.java +++ b/main/src/cgeo/geocaching/TrackableActivity.java @@ -1,17 +1,23 @@ package cgeo.geocaching; +import butterknife.InjectView; +import butterknife.Views; + import cgeo.geocaching.activity.AbstractActivity; import cgeo.geocaching.activity.AbstractViewPagerActivity; -import cgeo.geocaching.connector.gc.GCParser; +import cgeo.geocaching.connector.ConnectorFactory; +import cgeo.geocaching.connector.trackable.TrackableConnector; import cgeo.geocaching.enumerations.LogType; import cgeo.geocaching.geopoint.Units; import cgeo.geocaching.network.HtmlImage; import cgeo.geocaching.network.Network; import cgeo.geocaching.ui.AbstractCachingPageViewCreator; +import cgeo.geocaching.ui.AnchorAwareLinkMovementMethod; import cgeo.geocaching.ui.CacheDetailsCreator; import cgeo.geocaching.ui.Formatter; -import cgeo.geocaching.utils.BaseUtils; +import cgeo.geocaching.utils.HtmlUtils; import cgeo.geocaching.utils.Log; +import cgeo.geocaching.utils.TextUtils; import cgeo.geocaching.utils.UnknownTagsHandler; import org.apache.commons.lang3.StringUtils; @@ -26,7 +32,6 @@ import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.text.Html; -import android.text.method.LinkMovementMethod; import android.view.ContextMenu; import android.view.LayoutInflater; import android.view.Menu; @@ -56,8 +61,6 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi this.resId = resId; } } - private static final int MENU_LOG_TOUCH = 1; - private static final int MENU_BROWSER_TRACKABLE = 2; private Trackable trackable = null; private String geocode = null; private String name = null; @@ -66,7 +69,7 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi private String contextMenuUser = null; private LayoutInflater inflater = null; private ProgressDialog waitDialog = null; - private Handler loadTrackableHandler = new Handler() { + private final Handler loadTrackableHandler = new Handler() { @Override public void handleMessage(Message msg) { @@ -98,7 +101,7 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi invalidateOptionsMenuCompatible(); reinitializeViewPager(); - } catch (Exception e) { + } catch (final Exception e) { Log.e("TrackableActivity.loadTrackableHandler: ", e); } @@ -108,21 +111,16 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi } }; - public TrackableActivity() { - super("c:geo-trackable-details"); - } - @Override public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + super.onCreate(savedInstanceState, R.layout.trackable_activity); - setTheme(); - setContentView(R.layout.trackable_activity); + // set title in code, as the activity needs a hard coded title due to the intent filters setTitle(res.getString(R.string.trackable)); // get parameters - Bundle extras = getIntent().getExtras(); - Uri uri = getIntent().getData(); + final Bundle extras = getIntent().getExtras(); + final Uri uri = getIntent().getData(); // try to get data from extras if (extras != null) { @@ -134,7 +132,7 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi // try to get data from URI if (geocode == null && guid == null && id == null && uri != null) { - String uriHost = uri.getHost().toLowerCase(Locale.US); + final String uriHost = uri.getHost().toLowerCase(Locale.US); if (uriHost.contains("geocaching.com")) { geocode = uri.getQueryParameter("tracker"); guid = uri.getQueryParameter("guid"); @@ -158,7 +156,7 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi return; } } else if (uriHost.contains("coord.info")) { - String uriPath = uri.getPath().toLowerCase(Locale.US); + final String uriPath = uri.getPath().toLowerCase(Locale.US); if (uriPath != null && uriPath.startsWith("/tb")) { geocode = uriPath.substring(1).toUpperCase(Locale.US); guid = null; @@ -189,22 +187,25 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi waitDialog = ProgressDialog.show(this, message, res.getString(R.string.trackable_details_loading), true, true); createViewPager(0, null); - LoadTrackableThread thread = new LoadTrackableThread(loadTrackableHandler, geocode, guid, id); + final LoadTrackableThread thread = new LoadTrackableThread(loadTrackableHandler, geocode, guid, id); thread.start(); } @Override public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo info) { + + // FIXME: replace this context menu stuff by a UserActionsClickListener instead. + super.onCreateContextMenu(menu, view, info); final int viewId = view.getId(); if (viewId == R.id.author) { // Log item author contextMenuUser = ((TextView) view).getText().toString(); } else { // Trackable owner, and user holding trackable now - RelativeLayout itemLayout = (RelativeLayout) view.getParent(); - TextView itemName = (TextView) itemLayout.findViewById(R.id.name); + final RelativeLayout itemLayout = (RelativeLayout) view.getParent(); + final TextView itemName = (TextView) itemLayout.findViewById(R.id.name); - String selectedName = itemName.getText().toString(); + final String selectedName = itemName.getText().toString(); if (selectedName.equals(res.getString(R.string.trackable_owner))) { contextMenuUser = trackable.getOwner(); } else if (selectedName.equals(res.getString(R.string.trackable_spotted))) { @@ -241,18 +242,17 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi @Override public boolean onCreateOptionsMenu(Menu menu) { - menu.add(0, MENU_LOG_TOUCH, 0, res.getString(R.string.trackable_log_touch)).setIcon(R.drawable.ic_menu_agenda); // log touch - menu.add(0, MENU_BROWSER_TRACKABLE, 0, res.getString(R.string.trackable_browser_open)).setIcon(R.drawable.ic_menu_info_details); // browser + getMenuInflater().inflate(R.menu.trackable_activity, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { - case MENU_LOG_TOUCH: + case R.id.menu_log_touch: LogTrackableActivity.startActivity(this, trackable); return true; - case MENU_BROWSER_TRACKABLE: + case R.id.menu_browser_trackable: startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(trackable.getUrl()))); return true; default: @@ -263,8 +263,8 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi @Override public boolean onPrepareOptionsMenu(Menu menu) { if (trackable != null) { - menu.findItem(MENU_LOG_TOUCH).setEnabled(StringUtils.isNotBlank(geocode) && trackable.isLoggable()); - menu.findItem(MENU_BROWSER_TRACKABLE).setEnabled(StringUtils.isNotBlank(trackable.getUrl())); + menu.findItem(R.id.menu_log_touch).setEnabled(StringUtils.isNotBlank(geocode) && trackable.isLoggable()); + menu.findItem(R.id.menu_browser_trackable).setEnabled(StringUtils.isNotBlank(trackable.getUrl())); } return super.onPrepareOptionsMenu(menu); } @@ -286,8 +286,16 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi public void run() { trackable = cgData.loadTrackable(geocode); - if ((trackable == null || trackable.isLoggable()) && !StringUtils.startsWithIgnoreCase(geocode, "GK")) { - trackable = GCParser.searchTrackable(geocode, guid, id); + if (trackable == null || trackable.isLoggable()) { + // iterate over the connectors as some codes may be handled by multiple connectors + for (final TrackableConnector trackableConnector : ConnectorFactory.getTrackableConnectors()) { + if (trackableConnector.canHandleTrackable(geocode)) { + trackable = trackableConnector.searchTrackable(geocode, guid, id); + if (trackable != null) { + break; + } + } + } } handler.sendMessage(Message.obtain()); } @@ -304,7 +312,7 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi try { registerForContextMenu(view); openContextMenu(view); - } catch (Exception e) { + } catch (final Exception e) { Log.e("TrackableActivity.UserActionsListener.onClick ", e); } } @@ -326,12 +334,12 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi } try { - HtmlImage imgGetter = new HtmlImage(trackable.getGeocode(), false, 0, false); + final HtmlImage imgGetter = new HtmlImage(trackable.getGeocode(), false, 0, false); - BitmapDrawable image = imgGetter.getDrawable(url); - Message message = handler.obtainMessage(0, image); + final BitmapDrawable image = imgGetter.getDrawable(url); + final Message message = handler.obtainMessage(0, image); handler.sendMessage(message); - } catch (Exception e) { + } catch (final Exception e) { Log.e("TrackableActivity.TrackableIconThread.run: ", e); } } @@ -382,7 +390,7 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi @Override protected Pair<List<? extends Page>, Integer> getOrderedPages() { - List<Page> pages = new ArrayList<TrackableActivity.Page>(); + final List<Page> pages = new ArrayList<TrackableActivity.Page>(); pages.add(Page.DETAILS); if (!trackable.getLogs().isEmpty()) { pages.add(Page.LOGS); @@ -392,43 +400,21 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi public class LogsViewCreator extends AbstractCachingPageViewCreator<ListView> { - private class LogViewHolder { - - private final TextView added; - private final TextView type; - private final TextView author; - private final TextView location; - private final TextView log; - private final ImageView marker; - private final LinearLayout logImages; - - public LogViewHolder(View rowView) { - added = ((TextView) rowView.findViewById(R.id.added)); - type = ((TextView) rowView.findViewById(R.id.type)); - author = ((TextView) rowView.findViewById(R.id.author)); - location = ((TextView) rowView.findViewById(R.id.location)); - log = (TextView) rowView.findViewById(R.id.log); - marker = (ImageView) rowView.findViewById(R.id.log_mark); - logImages = (LinearLayout) rowView.findViewById(R.id.log_layout); - } - } - @Override public ListView getDispatchedView() { view = (ListView) getLayoutInflater().inflate(R.layout.trackable_logs_view, null); if (trackable != null && trackable.getLogs() != null) { - view.setAdapter(new ArrayAdapter<LogEntry>(TrackableActivity.this, R.layout.trackable_logs_item, trackable.getLogs()) { + view.setAdapter(new ArrayAdapter<LogEntry>(TrackableActivity.this, R.layout.logs_item, trackable.getLogs()) { @Override public View getView(int position, View convertView, android.view.ViewGroup parent) { View rowView = convertView; if (null == rowView) { - rowView = getLayoutInflater().inflate(R.layout.trackable_logs_item, null); + rowView = getLayoutInflater().inflate(R.layout.logs_item, null); } LogViewHolder holder = (LogViewHolder) rowView.getTag(); if (null == holder) { holder = new LogViewHolder(rowView); - rowView.setTag(holder); } final LogEntry log = getItem(position); @@ -440,21 +426,21 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi return view; } - protected void fillViewHolder(LogViewHolder holder, LogEntry log) { + protected void fillViewHolder(LogViewHolder holder, final LogEntry log) { if (log.date > 0) { - holder.added.setText(Formatter.formatShortDate(log.date)); + holder.date.setText(Formatter.formatShortDateVerbally(log.date)); } holder.type.setText(log.type.getL10n()); holder.author.setText(Html.fromHtml(log.author), TextView.BufferType.SPANNABLE); if (StringUtils.isBlank(log.cacheName)) { - holder.location.setVisibility(View.GONE); + holder.countOrLocation.setVisibility(View.GONE); } else { - holder.location.setText(Html.fromHtml(log.cacheName)); + holder.countOrLocation.setText(Html.fromHtml(log.cacheName)); final String cacheGuid = log.cacheGuid; final String cacheName = log.cacheName; - holder.location.setOnClickListener(new View.OnClickListener() { + holder.countOrLocation.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View arg0) { CacheDetailActivity.startActivityGuid(TrackableActivity.this, cacheGuid, Html.fromHtml(cacheName).toString()); @@ -462,11 +448,11 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi }); } - TextView logView = holder.log; - logView.setMovementMethod(LinkMovementMethod.getInstance()); + final TextView logView = holder.text; + logView.setMovementMethod(AnchorAwareLinkMovementMethod.getInstance()); String logText = log.log; - if (BaseUtils.containsHtml(logText)) { + if (TextUtils.containsHtml(logText)) { logText = log.getDisplayText(); logView.setText(Html.fromHtml(logText, new HtmlImage(null, false, StoredList.TEMPORARY_LIST_ID, false), null), TextView.BufferType.SPANNABLE); } @@ -474,9 +460,9 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi logView.setText(logText); } - ImageView statusMarker = holder.marker; + final ImageView statusMarker = holder.marker; // colored marker - int marker = log.type.markerId; + final int marker = log.type.markerId; if (marker != 0) { statusMarker.setVisibility(View.VISIBLE); statusMarker.setImageResource(marker); @@ -485,25 +471,18 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi statusMarker.setVisibility(View.GONE); } - // add LogImages - LinearLayout logLayout = holder.logImages; - + // images if (log.hasLogImages()) { - - final ArrayList<Image> logImages = new ArrayList<Image>(log.getLogImages()); - - final View.OnClickListener listener = new View.OnClickListener() { + holder.images.setText(log.getImageTitles()); + holder.images.setVisibility(View.VISIBLE); + holder.images.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - ImagesActivity.startActivityLogImages(TrackableActivity.this, trackable.getGeocode(), logImages); + ImagesActivity.startActivityLogImages(TrackableActivity.this, trackable.getGeocode(), new ArrayList<Image>(log.getLogImages())); } - }; - - LinearLayout log_imgView = (LinearLayout) getLayoutInflater().inflate(R.layout.trackable_logs_img, null); - TextView log_img_title = (TextView) log_imgView.findViewById(R.id.title); - log_img_title.setText(log.getImageTitles()); - log_img_title.setOnClickListener(listener); - logLayout.addView(log_imgView); + }); + } else { + holder.images.setVisibility(View.GONE); } holder.author.setOnClickListener(new UserActionsListener()); @@ -513,10 +492,20 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi public class DetailsViewCreator extends AbstractCachingPageViewCreator<ScrollView> { + @InjectView(R.id.goal_box) protected LinearLayout goalBox; + @InjectView(R.id.goal) protected TextView goalTextView; + @InjectView(R.id.details_box) protected LinearLayout detailsBox; + @InjectView(R.id.details) protected TextView detailsTextView; + @InjectView(R.id.image_box) protected LinearLayout imageBox; + @InjectView(R.id.details_list) protected LinearLayout detailsList; + @InjectView(R.id.image) protected LinearLayout imageView; + @Override public ScrollView getDispatchedView() { view = (ScrollView) getLayoutInflater().inflate(R.layout.trackable_details_view, null); - final CacheDetailsCreator details = new CacheDetailsCreator(TrackableActivity.this, (LinearLayout) view.findViewById(R.id.details_list)); + Views.inject(this, view); + + final CacheDetailsCreator details = new CacheDetailsCreator(TrackableActivity.this, detailsList); // action bar icon if (StringUtils.isNotBlank(trackable.getIconUrl())) { @@ -541,7 +530,7 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi details.add(R.string.trackable_code, trackable.getGeocode()); // trackable owner - TextView owner = details.add(R.string.trackable_owner, res.getString(R.string.trackable_unknown)); + final TextView owner = details.add(R.string.trackable_owner, res.getString(R.string.trackable_unknown)); if (StringUtils.isNotBlank(trackable.getOwner())) { owner.setText(Html.fromHtml(trackable.getOwner()), TextView.BufferType.SPANNABLE); owner.setOnClickListener(new UserActionsListener()); @@ -569,7 +558,7 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi // days since last spotting if (showTimeSpan && trackable.getLogs() != null) { - for (LogEntry log : trackable.getLogs()) { + for (final LogEntry log : trackable.getLogs()) { if (log.type == LogType.RETRIEVED_IT || log.type == LogType.GRABBED_IT || log.type == LogType.DISCOVERED_IT || log.type == LogType.PLACED_IT) { final int days = log.daysSinceLog(); text.append(" (").append(res.getQuantityString(R.plurals.days_ago, days, days)).append(')'); @@ -594,7 +583,7 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi // trackable origin if (StringUtils.isNotBlank(trackable.getOrigin())) { - TextView origin = details.add(R.string.trackable_origin, ""); + final TextView origin = details.add(R.string.trackable_origin, ""); origin.setText(Html.fromHtml(trackable.getOrigin()), TextView.BufferType.SPANNABLE); } @@ -609,28 +598,24 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi } // trackable goal - if (StringUtils.isNotBlank(trackable.getGoal())) { - view.findViewById(R.id.goal_box).setVisibility(View.VISIBLE); - TextView descView = (TextView) view.findViewById(R.id.goal); - descView.setVisibility(View.VISIBLE); - descView.setText(Html.fromHtml(trackable.getGoal(), new HtmlImage(geocode, true, 0, false), null), TextView.BufferType.SPANNABLE); - descView.setMovementMethod(LinkMovementMethod.getInstance()); + if (StringUtils.isNotBlank(HtmlUtils.extractText(trackable.getGoal()))) { + goalBox.setVisibility(View.VISIBLE); + goalTextView.setVisibility(View.VISIBLE); + goalTextView.setText(Html.fromHtml(trackable.getGoal(), new HtmlImage(geocode, true, 0, false), null), TextView.BufferType.SPANNABLE); + goalTextView.setMovementMethod(AnchorAwareLinkMovementMethod.getInstance()); } // trackable details - if (StringUtils.isNotBlank(trackable.getDetails())) { - view.findViewById(R.id.details_box).setVisibility(View.VISIBLE); - TextView descView = (TextView) view.findViewById(R.id.details); - descView.setVisibility(View.VISIBLE); - descView.setText(Html.fromHtml(trackable.getDetails(), new HtmlImage(geocode, true, 0, false), new UnknownTagsHandler()), TextView.BufferType.SPANNABLE); - descView.setMovementMethod(LinkMovementMethod.getInstance()); + if (StringUtils.isNotBlank(HtmlUtils.extractText(trackable.getDetails()))) { + detailsBox.setVisibility(View.VISIBLE); + detailsTextView.setVisibility(View.VISIBLE); + detailsTextView.setText(Html.fromHtml(trackable.getDetails(), new HtmlImage(geocode, true, 0, false), new UnknownTagsHandler()), TextView.BufferType.SPANNABLE); + detailsTextView.setMovementMethod(AnchorAwareLinkMovementMethod.getInstance()); } // trackable image if (StringUtils.isNotBlank(trackable.getImage())) { - view.findViewById(R.id.image_box).setVisibility(View.VISIBLE); - LinearLayout imgView = (LinearLayout) view.findViewById(R.id.image); - + imageBox.setVisibility(View.VISIBLE); final ImageView trackableImage = (ImageView) inflater.inflate(R.layout.trackable_image, null); trackableImage.setImageResource(R.drawable.image_not_loaded); @@ -648,7 +633,7 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi @Override public void handleMessage(Message message) { - BitmapDrawable image = (BitmapDrawable) message.obj; + final BitmapDrawable image = (BitmapDrawable) message.obj; if (image != null) { trackableImage.setImageDrawable((BitmapDrawable) message.obj); } @@ -660,18 +645,18 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi @Override public void run() { try { - HtmlImage imgGetter = new HtmlImage(geocode, true, 0, false); + final HtmlImage imgGetter = new HtmlImage(geocode, true, 0, false); - BitmapDrawable image = imgGetter.getDrawable(trackable.getImage()); - Message message = handler.obtainMessage(0, image); + final BitmapDrawable image = imgGetter.getDrawable(trackable.getImage()); + final Message message = handler.obtainMessage(0, image); handler.sendMessage(message); - } catch (Exception e) { - Log.e("cgeospoilers.onCreate.onClick.run: ", e); + } catch (final Exception e) { + Log.e("TrackableActivity.DetailsViewCreator.ImageGetterThread: ", e); } } }.start(); - imgView.addView(trackableImage); + imageView.addView(trackableImage); } return view; } diff --git a/main/src/cgeo/geocaching/UsefulAppsActivity.java b/main/src/cgeo/geocaching/UsefulAppsActivity.java index af643b3..8093bba 100644 --- a/main/src/cgeo/geocaching/UsefulAppsActivity.java +++ b/main/src/cgeo/geocaching/UsefulAppsActivity.java @@ -1,79 +1,107 @@ package cgeo.geocaching; +import butterknife.InjectView; +import butterknife.Views; + import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.ui.AbstractViewHolder; +import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.view.View; -import android.view.View.OnClickListener; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; import android.widget.ImageView; -import android.widget.LinearLayout; +import android.widget.ListView; import android.widget.TextView; -import java.util.Locale; - public class UsefulAppsActivity extends AbstractActivity { - private LinearLayout parentLayout; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + @InjectView(R.id.apps_list) protected ListView list; - // init - setTheme(); - setContentView(R.layout.useful_apps); - setTitle(res.getString(R.string.helpers)); - parentLayout = (LinearLayout) findViewById(R.id.parent); + protected static class ViewHolder extends AbstractViewHolder { + @InjectView(R.id.title) protected TextView title; + @InjectView(R.id.image) protected ImageView image; + @InjectView(R.id.description) protected TextView description; - final Locale loc = Locale.getDefault(); - final String language = loc.getLanguage(); + public ViewHolder(View rowView) { + super(rowView); + } + } - final String tutorialUrl; - if ("de".equalsIgnoreCase(language)) { - tutorialUrl = "gnu.android.app.cgeomanual.de"; + private static class HelperApp { + private final int titleId; + private final int descriptionId; + private final int iconId; + private final String packageName; + + public HelperApp(final int title, final int description, final int icon, final String packageName) { + this.titleId = title; + this.descriptionId = description; + this.iconId = icon; + this.packageName = packageName; } - else { - tutorialUrl = "gnu.android.app.cgeomanual.en"; + + private void installFromMarket(Activity activity) { + try { + Intent marketIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + packageName)); + marketIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); + activity.startActivity(marketIntent); + + } catch (Exception e) { + // market not available in standard emulator + } } - addApp(R.string.helper_manual_title, R.string.helper_manual_description, R.drawable.helper_manual, tutorialUrl); - addApp(R.string.helper_calendar_title, R.string.helper_calendar_description, R.drawable.cgeo, "cgeo.calendar"); - addApp(R.string.helper_locus_title, R.string.helper_locus_description, R.drawable.helper_locus, "menion.android.locus"); - addApp(R.string.helper_gpsstatus_title, R.string.helper_gpsstatus_description, R.drawable.helper_gpsstatus, "com.eclipsim.gpsstatus2"); - addApp(R.string.helper_bluetoothgps_title, R.string.helper_bluetoothgps_description, R.drawable.helper_bluetoothgps, "googoo.android.btgps"); - addApp(R.string.helper_barcode_title, R.string.helper_barcode_description, R.drawable.helper_barcode, "com.google.zxing.client.android"); } + private static final HelperApp[] HELPER_APPS = { + new HelperApp(R.string.helper_calendar_title, R.string.helper_calendar_description, R.drawable.cgeo, "cgeo.calendar"), + new HelperApp(R.string.helper_pocketquery_title, R.string.helper_pocketquery_description, R.drawable.helper_pocketquery, "org.pquery"), + new HelperApp(R.string.helper_locus_title, R.string.helper_locus_description, R.drawable.helper_locus, "menion.android.locus"), + new HelperApp(R.string.helper_gpsstatus_title, R.string.helper_gpsstatus_description, R.drawable.helper_gpsstatus, "com.eclipsim.gpsstatus2"), + new HelperApp(R.string.helper_bluetoothgps_title, R.string.helper_bluetoothgps_description, R.drawable.helper_bluetoothgps, "googoo.android.btgps"), + new HelperApp(R.string.helper_barcode_title, R.string.helper_barcode_description, R.drawable.helper_barcode, "com.google.zxing.client.android"), + }; + @Override - public void onResume() { - super.onResume(); + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState, R.layout.usefulapps_activity); - } + Views.inject(this); - private void installFromMarket(String marketId) { - try { - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://search?q=pname:" + marketId))); - } catch (Exception e) { - // market not available in standard emulator - } + list.setAdapter(new ArrayAdapter<HelperApp>(this, R.layout.usefulapps_item, HELPER_APPS) { + @Override + public View getView(int position, View convertView, android.view.ViewGroup parent) { + View rowView = convertView; + if (null == rowView) { + rowView = getLayoutInflater().inflate(R.layout.usefulapps_item, null); + } + ViewHolder holder = (ViewHolder) rowView.getTag(); + if (null == holder) { + holder = new ViewHolder(rowView); + } + + final HelperApp app = getItem(position); + fillViewHolder(holder, app); + return rowView; + } - finish(); - } + private void fillViewHolder(ViewHolder holder, HelperApp app) { + holder.title.setText(res.getString(app.titleId)); + holder.image.setImageDrawable(res.getDrawable(app.iconId)); + holder.description.setText(res.getString(app.descriptionId)); + } + }); - private void addApp(final int titleId, final int descriptionId, final int imageId, final String marketUrl) { - final LinearLayout layout = (LinearLayout) getLayoutInflater().inflate(R.layout.useful_apps_item, null); - ((TextView) layout.findViewById(R.id.title)).setText(res.getString(titleId)); - ((ImageView) layout.findViewById(R.id.image)).setImageDrawable(res.getDrawable(imageId)); - ((TextView) layout.findViewById(R.id.description)).setText(res.getString(descriptionId)); - layout.findViewById(R.id.app_layout).setOnClickListener(new OnClickListener() { + list.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override - public void onClick(View v) { - installFromMarket(marketUrl); + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + HelperApp helperApp = HELPER_APPS[position]; + helperApp.installFromMarket(UsefulAppsActivity.this); } }); - parentLayout.addView(layout); } - } diff --git a/main/src/cgeo/geocaching/Waypoint.java b/main/src/cgeo/geocaching/Waypoint.java index 48c9bc5..e39d26a 100644 --- a/main/src/cgeo/geocaching/Waypoint.java +++ b/main/src/cgeo/geocaching/Waypoint.java @@ -275,7 +275,8 @@ public class Waypoint implements IWaypoint, Comparable<Waypoint> { if (coords != null) { hash = coords.hashCode(); } - hash = hash ^ waypointType.markerId; + hash ^= waypointType.markerId; return (int) hash; } + } diff --git a/main/src/cgeo/geocaching/WaypointPopup.java b/main/src/cgeo/geocaching/WaypointPopup.java index 766d43d..a1ab36a 100644 --- a/main/src/cgeo/geocaching/WaypointPopup.java +++ b/main/src/cgeo/geocaching/WaypointPopup.java @@ -1,7 +1,11 @@ package cgeo.geocaching; +import butterknife.InjectView; +import butterknife.Views; + import cgeo.geocaching.apps.cache.navi.NavigationAppFactory; import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.geopoint.Units; import cgeo.geocaching.ui.CacheDetailsCreator; import cgeo.geocaching.utils.Log; @@ -17,16 +21,23 @@ import android.widget.LinearLayout; import android.widget.TextView; public class WaypointPopup extends AbstractPopupActivity { + @InjectView(R.id.actionbar_title) protected TextView actionBarTitle; + @InjectView(R.id.waypoint_details_list) protected LinearLayout waypointDetailsLayout; + @InjectView(R.id.edit) protected Button buttonEdit; + @InjectView(R.id.details_list) protected LinearLayout cacheDetailsLayout; + private int waypointId = 0; private Waypoint waypoint = null; + private TextView waypointDistance = null; public WaypointPopup() { - super("c:geo-waypoint-info", R.layout.waypoint_popup); + super(R.layout.waypoint_popup); } @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); + Views.inject(this); // get parameters final Bundle extras = getIntent().getExtras(); if (extras != null) { @@ -35,6 +46,14 @@ public class WaypointPopup extends AbstractPopupActivity { } @Override + public void onUpdateGeoData(IGeoData geo) { + if (geo.getCoords() != null && waypoint != null && waypoint.getCoords() != null) { + waypointDistance.setText(Units.getDistanceFromKilometers(geo.getCoords().distanceTo(waypoint.getCoords()))); + waypointDistance.bringToFront(); + } + } + + @Override protected void init() { super.init(); waypoint = cgData.loadWaypoint(waypointId); @@ -45,17 +64,16 @@ public class WaypointPopup extends AbstractPopupActivity { setTitle(waypoint.getGeocode()); } - // actionbar icon - ((TextView) findViewById(R.id.actionbar_title)).setCompoundDrawablesWithIntrinsicBounds(getResources().getDrawable(waypoint.getWaypointType().markerId), null, null, null); + actionBarTitle.setCompoundDrawablesWithIntrinsicBounds(getResources().getDrawable(waypoint.getWaypointType().markerId), null, null, null); - //Start filling waypoint details - details = new CacheDetailsCreator(this, (LinearLayout) findViewById(R.id.waypoint_details_list)); + details = new CacheDetailsCreator(this, waypointDetailsLayout); //Waypoint geocode details.add(R.string.cache_geocode, waypoint.getPrefix() + waypoint.getGeocode().substring(2)); + details.addDistance(waypoint, waypointDistance); + waypointDistance = details.getValueView(); + details.add(R.string.waypoint_note, waypoint.getNote()); - // Edit Button - final Button buttonEdit = (Button) findViewById(R.id.edit); buttonEdit.setOnClickListener(new OnClickListener() { @Override @@ -65,14 +83,13 @@ public class WaypointPopup extends AbstractPopupActivity { } }); - //Start filling cache details - details = new CacheDetailsCreator(this, (LinearLayout) findViewById(R.id.details_list)); + details = new CacheDetailsCreator(this, cacheDetailsLayout); details.add(R.string.cache_name, cache.getName()); addCacheDetails(); } catch (Exception e) { - Log.e("cgeopopup.init", e); + Log.e("WaypointPopup.init", e); } } diff --git a/main/src/cgeo/geocaching/activity/AbstractActivity.java b/main/src/cgeo/geocaching/activity/AbstractActivity.java index 557665e..964ef96 100644 --- a/main/src/cgeo/geocaching/activity/AbstractActivity.java +++ b/main/src/cgeo/geocaching/activity/AbstractActivity.java @@ -1,5 +1,7 @@ package cgeo.geocaching.activity; +import butterknife.Views; + import cgeo.geocaching.Settings; import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.compatibility.Compatibility; @@ -14,22 +16,15 @@ import android.widget.EditText; public abstract class AbstractActivity extends FragmentActivity implements IAbstractActivity { - final private String helpTopic; - protected cgeoapplication app = null; protected Resources res = null; private boolean keepScreenOn = false; protected AbstractActivity() { - this(null); - } - - protected AbstractActivity(final String helpTopic) { - this.helpTopic = helpTopic; + this(false); } - protected AbstractActivity(final String helpTopic, final boolean keepScreenOn) { - this(helpTopic); + protected AbstractActivity(final boolean keepScreenOn) { this.keepScreenOn = keepScreenOn; } @@ -38,20 +33,15 @@ public abstract class AbstractActivity extends FragmentActivity implements IAbst ActivityMixin.goHome(this); } - @Override - public void goManual(final View view) { - ActivityMixin.goManual(this, helpTopic); - } - - final public void setTitle(final String title) { + final protected void setTitle(final String title) { ActivityMixin.setTitle(this, title); } - final public void showProgress(final boolean show) { + final protected void showProgress(final boolean show) { ActivityMixin.showProgress(this, show); } - final public void setTheme() { + final protected void setTheme() { ActivityMixin.setTheme(this); } @@ -70,22 +60,14 @@ public abstract class AbstractActivity extends FragmentActivity implements IAbst ActivityMixin.helpDialog(this, title, message); } - public final void helpDialog(final String title, final String message, final Drawable icon) { + protected final void helpDialog(final String title, final String message, final Drawable icon) { ActivityMixin.helpDialog(this, title, message, icon); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - - // init - res = this.getResources(); - app = (cgeoapplication) this.getApplication(); - - // Restore cookie store if needed - Cookies.restoreCookieStore(Settings.getCookieStore()); - - ActivityMixin.keepScreenOn(this, keepScreenOn); + initializeCommonFields(); } protected static void disableSuggestions(final EditText edit) { @@ -128,4 +110,34 @@ public abstract class AbstractActivity extends FragmentActivity implements IAbst editText.setSelection(newCursor, newCursor); } + protected void onCreate(final Bundle savedInstanceState, final int resourceLayoutID) { + super.onCreate(savedInstanceState); + + initializeCommonFields(); + + // non declarative part of layout + setTheme(); + setContentView(resourceLayoutID); + + // create view variables + Views.inject(this); + } + + private void initializeCommonFields() { + // initialize commonly used members + res = this.getResources(); + app = (cgeoapplication) this.getApplication(); + + // only needed in some activities, but implemented in super class nonetheless + Cookies.restoreCookieStore(Settings.getCookieStore()); + ActivityMixin.keepScreenOn(this, keepScreenOn); + } + + @Override + public void setContentView(int layoutResID) { + super.setContentView(layoutResID); + + // initialize the action bar title with the activity title for single source + ActivityMixin.setTitle(this, getTitle()); + } } diff --git a/main/src/cgeo/geocaching/activity/AbstractListActivity.java b/main/src/cgeo/geocaching/activity/AbstractListActivity.java index f96a769..47c747f 100644 --- a/main/src/cgeo/geocaching/activity/AbstractListActivity.java +++ b/main/src/cgeo/geocaching/activity/AbstractListActivity.java @@ -12,35 +12,24 @@ import android.view.View; public abstract class AbstractListActivity extends FragmentListActivity implements IAbstractActivity { - private String helpTopic; private boolean keepScreenOn = false; protected cgeoapplication app = null; protected Resources res = null; protected AbstractListActivity() { - this(null); + this(false); } protected AbstractListActivity(final boolean keepScreenOn) { - this(null); this.keepScreenOn = keepScreenOn; } - protected AbstractListActivity(final String helpTopic) { - this.helpTopic = helpTopic; - } - @Override final public void goHome(View view) { ActivityMixin.goHome(this); } - @Override - public void goManual(View view) { - ActivityMixin.goManual(this, helpTopic); - } - final public void showProgress(final boolean show) { ActivityMixin.showProgress(this, show); } @@ -71,7 +60,10 @@ public abstract class AbstractListActivity extends FragmentListActivity implemen @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + initializeCommonFields(); + } + private void initializeCommonFields() { // init res = this.getResources(); app = (cgeoapplication) this.getApplication(); @@ -79,7 +71,7 @@ public abstract class AbstractListActivity extends FragmentListActivity implemen ActivityMixin.keepScreenOn(this, keepScreenOn); } - final public void setTitle(final String title) { + final protected void setTitle(final String title) { ActivityMixin.setTitle(this, title); } @@ -87,4 +79,20 @@ public abstract class AbstractListActivity extends FragmentListActivity implemen public void invalidateOptionsMenuCompatible() { Compatibility.invalidateOptionsMenu(this); } + + public void onCreate(Bundle savedInstanceState, int resourceLayoutID) { + super.onCreate(savedInstanceState); + initializeCommonFields(); + + setTheme(); + setContentView(resourceLayoutID); + } + + @Override + public void setContentView(int layoutResID) { + super.setContentView(layoutResID); + + // initialize action bar title with activity title + ActivityMixin.setTitle(this, getTitle()); + } } diff --git a/main/src/cgeo/geocaching/activity/AbstractViewPagerActivity.java b/main/src/cgeo/geocaching/activity/AbstractViewPagerActivity.java index 366a59d..0345633 100644 --- a/main/src/cgeo/geocaching/activity/AbstractViewPagerActivity.java +++ b/main/src/cgeo/geocaching/activity/AbstractViewPagerActivity.java @@ -14,6 +14,7 @@ import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager.OnPageChangeListener; import android.view.View; +import android.view.ViewGroup; import java.util.ArrayList; import java.util.HashMap; @@ -29,10 +30,6 @@ import java.util.Map; */ public abstract class AbstractViewPagerActivity<Page extends Enum<Page>> extends AbstractActivity { - protected AbstractViewPagerActivity(String helpTopic) { - super(helpTopic); - } - /** * A {@link List} of all available pages. * @@ -95,12 +92,12 @@ public abstract class AbstractViewPagerActivity<Page extends Enum<Page>> extends private class ViewPagerAdapter extends PagerAdapter implements TitleProvider { @Override - public void destroyItem(View container, int position, Object object) { + public void destroyItem(ViewGroup container, int position, Object object) { ((ViewPager) container).removeView((View) object); } @Override - public void finishUpdate(View container) { + public void finishUpdate(ViewGroup container) { } @Override @@ -109,7 +106,7 @@ public abstract class AbstractViewPagerActivity<Page extends Enum<Page>> extends } @Override - public Object instantiateItem(View container, int position) { + public Object instantiateItem(ViewGroup container, int position) { final Page page = pageOrder.get(position); PageViewCreator creator = viewCreators.get(page); @@ -150,7 +147,7 @@ public abstract class AbstractViewPagerActivity<Page extends Enum<Page>> extends } @Override - public void startUpdate(View arg0) { + public void startUpdate(ViewGroup arg0) { } @Override @@ -211,6 +208,7 @@ public abstract class AbstractViewPagerActivity<Page extends Enum<Page>> extends pageOrder.add(null); } } + viewPagerAdapter.notifyDataSetChanged(); viewPager.setCurrentItem(startPageIndex, false); } @@ -236,15 +234,19 @@ public abstract class AbstractViewPagerActivity<Page extends Enum<Page>> extends final Pair<List<? extends Page>, Integer> pagesAndIndex = getOrderedPages(); pageOrder.addAll(pagesAndIndex.getLeft()); + // Since we just added pages notifyDataSetChanged needs to be called before we possibly setCurrentItem below. + // But, calling it will reset current item and we won't be able to tell if we would have been out of bounds + final int currentItem = getCurrentItem(); + + // notify the adapter that the data has changed + viewPagerAdapter.notifyDataSetChanged(); + // switch to details page, if we're out of bounds final int defaultPage = pagesAndIndex.getRight(); - if (getCurrentItem() < 0 || getCurrentItem() >= viewPagerAdapter.getCount()) { + if (currentItem < 0 || currentItem >= viewPagerAdapter.getCount()) { viewPager.setCurrentItem(defaultPage, false); } - // notify the adapter that the data has changed - viewPagerAdapter.notifyDataSetChanged(); - // notify the indicator that the data has changed titleIndicator.notifyDataSetChanged(); } diff --git a/main/src/cgeo/geocaching/activity/ActivityMixin.java b/main/src/cgeo/geocaching/activity/ActivityMixin.java index c97cb9a..12ab0be 100644 --- a/main/src/cgeo/geocaching/activity/ActivityMixin.java +++ b/main/src/cgeo/geocaching/activity/ActivityMixin.java @@ -1,15 +1,14 @@ package cgeo.geocaching.activity; +import cgeo.geocaching.MainActivity; import cgeo.geocaching.R; import cgeo.geocaching.Settings; -import cgeo.geocaching.cgeo; import cgeo.geocaching.compatibility.Compatibility; import org.apache.commons.lang3.StringUtils; import android.app.Activity; import android.app.AlertDialog; -import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.graphics.drawable.Drawable; @@ -21,34 +20,17 @@ import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; -import gnu.android.app.appmanualclient.AppManualReaderClient; - public final class ActivityMixin { public final static void goHome(final Activity fromActivity) { - final Intent intent = new Intent(fromActivity, cgeo.class); + final Intent intent = new Intent(fromActivity, MainActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); fromActivity.startActivity(intent); fromActivity.finish(); } - public static void goManual(final Context context, final String helpTopic) { - if (StringUtils.isBlank(helpTopic)) { - return; - } - try { - AppManualReaderClient.openManual( - "c-geo", - helpTopic, - context, - "http://manual.cgeo.org/"); - } catch (Exception e) { - // nothing - } - } - - public static void setTitle(final Activity activity, final String text) { + public static void setTitle(final Activity activity, final CharSequence text) { if (StringUtils.isBlank(text)) { return; } diff --git a/main/src/cgeo/geocaching/activity/IAbstractActivity.java b/main/src/cgeo/geocaching/activity/IAbstractActivity.java index 04709c6..61c218b 100644 --- a/main/src/cgeo/geocaching/activity/IAbstractActivity.java +++ b/main/src/cgeo/geocaching/activity/IAbstractActivity.java @@ -6,8 +6,6 @@ public interface IAbstractActivity { public void goHome(View view); - public void goManual(View view); - public void showToast(String text); public void showShortToast(String text); diff --git a/main/src/cgeo/geocaching/apps/AbstractApp.java b/main/src/cgeo/geocaching/apps/AbstractApp.java index c95e8b4..f3715f3 100644 --- a/main/src/cgeo/geocaching/apps/AbstractApp.java +++ b/main/src/cgeo/geocaching/apps/AbstractApp.java @@ -1,10 +1,12 @@ package cgeo.geocaching.apps; import cgeo.geocaching.Geocache; -import cgeo.geocaching.cgeo; +import cgeo.geocaching.MainActivity; import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.utils.ProcessUtils; +import org.apache.commons.lang3.StringUtils; + import android.content.Intent; public abstract class AbstractApp implements App { @@ -26,10 +28,10 @@ public abstract class AbstractApp implements App { @Override public boolean isInstalled() { - if (ProcessUtils.isInstalled(packageName)) { + if (StringUtils.isNotEmpty(packageName) && ProcessUtils.isInstalled(packageName)) { return true; } - return cgeo.isIntentAvailable(intent); + return MainActivity.isIntentAvailable(intent); } protected Intent getLaunchIntent() { diff --git a/main/src/cgeo/geocaching/apps/cache/navi/CompassApp.java b/main/src/cgeo/geocaching/apps/cache/navi/CompassApp.java index 4811916..47010df 100644 --- a/main/src/cgeo/geocaching/apps/cache/navi/CompassApp.java +++ b/main/src/cgeo/geocaching/apps/cache/navi/CompassApp.java @@ -3,7 +3,7 @@ package cgeo.geocaching.apps.cache.navi; import cgeo.geocaching.Geocache; import cgeo.geocaching.R; import cgeo.geocaching.Waypoint; -import cgeo.geocaching.cgeonavigate; +import cgeo.geocaching.CompassActivity; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.ui.Formatter; @@ -22,18 +22,18 @@ class CompassApp extends AbstractPointNavigationApp { @Override public void navigate(Activity activity, Geopoint coords) { - cgeonavigate.startActivity(activity, getString(R.string.navigation_direct_navigation), getString(R.string.navigation_target), coords, null); + CompassActivity.startActivity(activity, getString(R.string.navigation_direct_navigation), getString(R.string.navigation_target), coords, null); } @Override public void navigate(Activity activity, Waypoint waypoint) { - cgeonavigate.startActivity(activity, waypoint.getPrefix() + "/" + waypoint.getLookup(), waypoint.getName(), waypoint.getCoords(), null, + CompassActivity.startActivity(activity, waypoint.getPrefix() + "/" + waypoint.getLookup(), waypoint.getName(), waypoint.getCoords(), null, waypoint.getWaypointType().getL10n()); } @Override public void navigate(Activity activity, Geocache cache) { - cgeonavigate.startActivity(activity, cache.getGeocode(), cache.getName(), cache.getCoords(), null, + CompassActivity.startActivity(activity, cache.getGeocode(), cache.getName(), cache.getCoords(), null, Formatter.formatCacheInfoShort(cache)); } diff --git a/main/src/cgeo/geocaching/apps/cache/navi/GoogleMapsApp.java b/main/src/cgeo/geocaching/apps/cache/navi/GoogleMapsApp.java index eac33cc..03fae9e 100644 --- a/main/src/cgeo/geocaching/apps/cache/navi/GoogleMapsApp.java +++ b/main/src/cgeo/geocaching/apps/cache/navi/GoogleMapsApp.java @@ -30,7 +30,7 @@ class GoogleMapsApp extends AbstractPointNavigationApp { } catch (Exception e) { // nothing } - Log.i("cgBase.runExternalMap: No maps application available."); + Log.i("GoogleMapsApp.navigate: No maps application available."); ActivityMixin.showToast(activity, getString(R.string.err_application_no)); } diff --git a/main/src/cgeo/geocaching/apps/cache/navi/GoogleMapsDirectionApp.java b/main/src/cgeo/geocaching/apps/cache/navi/GoogleMapsDirectionApp.java index db4fc1c..a84b7e8 100644 --- a/main/src/cgeo/geocaching/apps/cache/navi/GoogleMapsDirectionApp.java +++ b/main/src/cgeo/geocaching/apps/cache/navi/GoogleMapsDirectionApp.java @@ -40,7 +40,7 @@ public class GoogleMapsDirectionApp extends AbstractPointNavigationApp { } } catch (Exception e) { - Log.i("GoogleMapsDirection: application not available."); + Log.i("GoogleMapsDirectionApp: application not available.", e); } } diff --git a/main/src/cgeo/geocaching/apps/cache/navi/GoogleNavigationApp.java b/main/src/cgeo/geocaching/apps/cache/navi/GoogleNavigationApp.java index f1616ad..a3532a5 100644 --- a/main/src/cgeo/geocaching/apps/cache/navi/GoogleNavigationApp.java +++ b/main/src/cgeo/geocaching/apps/cache/navi/GoogleNavigationApp.java @@ -27,22 +27,28 @@ abstract class GoogleNavigationApp extends AbstractPointNavigationApp { try { activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri .parse("google.navigation:ll=" + coords.getLatitude() + "," - + coords.getLongitude() + mode))); + + coords.getLongitude() + "&mode=" + mode))); - } catch (Exception e) { - Log.i("cgBase.runNavigation: No navigation application available."); + } catch (final Exception e) { + Log.i("GoogleNavigationApp.navigate: No navigation application available.", e); } } static class GoogleNavigationWalkingApp extends GoogleNavigationApp { GoogleNavigationWalkingApp() { - super(R.string.cache_menu_navigation_walk, "&mode=w"); + super(R.string.cache_menu_navigation_walk, "w"); } } static class GoogleNavigationDrivingApp extends GoogleNavigationApp { GoogleNavigationDrivingApp() { - super(R.string.cache_menu_navigation_drive, "&mode=d"); + super(R.string.cache_menu_navigation_drive, "d"); + } + } + + static class GoogleNavigationBikeApp extends GoogleNavigationApp { + GoogleNavigationBikeApp() { + super(R.string.cache_menu_navigation_bike, "b"); } } }
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java b/main/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java index 5545936..7316d4c 100644 --- a/main/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java +++ b/main/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java @@ -11,6 +11,7 @@ import cgeo.geocaching.apps.App; import cgeo.geocaching.apps.cache.CacheBeaconApp; import cgeo.geocaching.apps.cache.GccApp; import cgeo.geocaching.apps.cache.WhereYouGoApp; +import cgeo.geocaching.apps.cache.navi.GoogleNavigationApp.GoogleNavigationBikeApp; import cgeo.geocaching.apps.cache.navi.GoogleNavigationApp.GoogleNavigationDrivingApp; import cgeo.geocaching.apps.cache.navi.GoogleNavigationApp.GoogleNavigationWalkingApp; import cgeo.geocaching.geopoint.Geopoint; @@ -59,6 +60,10 @@ public final class NavigationAppFactory extends AbstractAppFactory { */ GOOGLE_NAVIGATION_WALK(new GoogleNavigationWalkingApp(), 12), /** + * Google Navigation in walking mode + */ + GOOGLE_NAVIGATION_BIKE(new GoogleNavigationBikeApp(), 21), + /** * Google Maps Directions */ GOOGLE_MAPS_DIRECTIONS(new GoogleMapsDirectionApp(), 13), @@ -134,7 +139,7 @@ public final class NavigationAppFactory extends AbstractAppFactory { builder.setTitle(R.string.cache_menu_navigate); final List<NavigationAppsEnum> items = new ArrayList<NavigationAppFactory.NavigationAppsEnum>(); final int defaultNavigationTool = Settings.getDefaultNavigationTool(); - for (NavigationAppsEnum navApp : getInstalledNavigationApps()) { + for (final NavigationAppsEnum navApp : getInstalledNavigationApps()) { if ((showInternalMap || !(navApp.app instanceof InternalMap)) && (showDefaultNavigation || defaultNavigationTool != navApp.id)) { boolean add = false; @@ -161,8 +166,8 @@ public final class NavigationAppFactory extends AbstractAppFactory { builder.setAdapter(adapter, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int item) { - NavigationAppsEnum selectedItem = adapter.getItem(item); - App app = selectedItem.app; + final NavigationAppsEnum selectedItem = adapter.getItem(item); + final App app = selectedItem.app; if (cache != null) { navigateCache(activity, cache, app); } @@ -185,7 +190,7 @@ public final class NavigationAppFactory extends AbstractAppFactory { */ public static List<NavigationAppsEnum> getInstalledNavigationApps() { final List<NavigationAppsEnum> installedNavigationApps = new ArrayList<NavigationAppsEnum>(); - for (NavigationAppsEnum appEnum : NavigationAppsEnum.values()) { + for (final NavigationAppsEnum appEnum : NavigationAppsEnum.values()) { if (appEnum.app.isInstalled()) { installedNavigationApps.add(appEnum); } @@ -200,7 +205,7 @@ public final class NavigationAppFactory extends AbstractAppFactory { */ public static List<NavigationAppsEnum> getInstalledDefaultNavigationApps() { final List<NavigationAppsEnum> installedNavigationApps = new ArrayList<NavigationAppsEnum>(); - for (NavigationAppsEnum appEnum : NavigationAppsEnum.values()) { + for (final NavigationAppsEnum appEnum : NavigationAppsEnum.values()) { if (appEnum.app.isInstalled() && appEnum.app.isDefaultNavigationApp()) { installedNavigationApps.add(appEnum); } @@ -225,9 +230,9 @@ public final class NavigationAppFactory extends AbstractAppFactory { * @param menu */ public static void addMenuItems(final Menu menu, final Geocache cache) { - for (NavigationAppsEnum navApp : getInstalledNavigationApps()) { + for (final NavigationAppsEnum navApp : getInstalledNavigationApps()) { if (navApp.app instanceof CacheNavigationApp) { - CacheNavigationApp cacheApp = (CacheNavigationApp) navApp.app; + final CacheNavigationApp cacheApp = (CacheNavigationApp) navApp.app; if (cacheApp.isEnabled(cache)) { menu.add(0, MENU_ITEM_OFFSET + navApp.id, 0, navApp.app.getName()); } @@ -236,9 +241,9 @@ public final class NavigationAppFactory extends AbstractAppFactory { } public static void addMenuItems(final Menu menu, final Waypoint waypoint) { - for (NavigationAppsEnum navApp : getInstalledNavigationApps()) { + for (final NavigationAppsEnum navApp : getInstalledNavigationApps()) { if (navApp.app instanceof WaypointNavigationApp) { - WaypointNavigationApp waypointApp = (WaypointNavigationApp) navApp.app; + final WaypointNavigationApp waypointApp = (WaypointNavigationApp) navApp.app; if (waypointApp.isEnabled(waypoint)) { menu.add(0, MENU_ITEM_OFFSET + navApp.id, 0, navApp.app.getName()); } @@ -262,7 +267,7 @@ public final class NavigationAppFactory extends AbstractAppFactory { private static void navigateCache(Activity activity, Geocache cache, App app) { if (app instanceof CacheNavigationApp) { - CacheNavigationApp cacheApp = (CacheNavigationApp) app; + final CacheNavigationApp cacheApp = (CacheNavigationApp) app; cacheApp.navigate(activity, cache); } } @@ -275,21 +280,21 @@ public final class NavigationAppFactory extends AbstractAppFactory { private static void navigateWaypoint(Activity activity, Waypoint waypoint, App app) { if (app instanceof WaypointNavigationApp) { - WaypointNavigationApp waypointApp = (WaypointNavigationApp) app; + final WaypointNavigationApp waypointApp = (WaypointNavigationApp) app; waypointApp.navigate(activity, waypoint); } } private static void navigateGeopoint(Activity activity, Geopoint destination, App app) { if (app instanceof GeopointNavigationApp) { - GeopointNavigationApp geopointApp = (GeopointNavigationApp) app; + final GeopointNavigationApp geopointApp = (GeopointNavigationApp) app; geopointApp.navigate(activity, destination); } } private static App getAppFromMenuItem(MenuItem item) { final int id = item.getItemId(); - for (NavigationAppsEnum navApp : NavigationAppsEnum.values()) { + for (final NavigationAppsEnum navApp : NavigationAppsEnum.values()) { if (MENU_ITEM_OFFSET + navApp.id == id) { return navApp.app; } @@ -362,7 +367,7 @@ public final class NavigationAppFactory extends AbstractAppFactory { private static App getNavigationAppForId(final int navigationAppId) { final List<NavigationAppsEnum> installedNavigationApps = getInstalledNavigationApps(); - for (NavigationAppsEnum navigationApp : installedNavigationApps) { + for (final NavigationAppsEnum navigationApp : installedNavigationApps) { if (navigationApp.id == navigationAppId) { return navigationApp.app; } diff --git a/main/src/cgeo/geocaching/apps/cache/navi/StreetviewApp.java b/main/src/cgeo/geocaching/apps/cache/navi/StreetviewApp.java index 012b94f..e2c0828 100644 --- a/main/src/cgeo/geocaching/apps/cache/navi/StreetviewApp.java +++ b/main/src/cgeo/geocaching/apps/cache/navi/StreetviewApp.java @@ -4,6 +4,7 @@ import cgeo.geocaching.R; import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.activity.ActivityMixin; import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.utils.ProcessUtils; import android.app.Activity; import android.content.ActivityNotFoundException; @@ -12,13 +13,16 @@ import android.net.Uri; class StreetviewApp extends AbstractPointNavigationApp { + private static final String PACKAGE_NAME_STREET_VIEW = "com.google.android.street"; + private static final boolean INSTALLED = ProcessUtils.isInstalled(PACKAGE_NAME_STREET_VIEW); + StreetviewApp() { super(getString(R.string.cache_menu_streetview), null); } @Override public boolean isInstalled() { - return true; + return INSTALLED; } @Override @@ -26,7 +30,7 @@ class StreetviewApp extends AbstractPointNavigationApp { try { activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("google.streetview:cbll=" + point.getLatitude() + "," + point.getLongitude()))); - } catch (ActivityNotFoundException e) { + } catch (final ActivityNotFoundException e) { ActivityMixin.showToast(activity, cgeoapplication.getInstance().getString(R.string.err_application_no)); } } diff --git a/main/src/cgeo/geocaching/cgData.java b/main/src/cgeo/geocaching/cgData.java index 28485a5..2ef5b27 100644 --- a/main/src/cgeo/geocaching/cgData.java +++ b/main/src/cgeo/geocaching/cgData.java @@ -33,6 +33,7 @@ import java.io.File; import java.io.FilenameFilter; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.EnumSet; @@ -85,7 +86,7 @@ public class cgData { private static int[] cacheColumnIndex; private static CacheCache cacheCache = new CacheCache(); private static SQLiteDatabase database = null; - private static final int dbVersion = 66; + private static final int dbVersion = 67; public static final int customListIdOffset = 10; private static final String dbName = "data"; private static final String dbTableCaches = "cg_caches"; @@ -107,7 +108,7 @@ public class cgData { + "detailedupdate long, " + "visiteddate long, " + "geocode text unique not null, " - + "reason integer not null default 0, " // cached, favourite... + + "reason integer not null default 0, " // cached, favorite... + "cacheid text, " + "guid text, " + "type text, " @@ -674,6 +675,16 @@ public class cgData { } } + // issue2662 OC: Leichtes Klettern / Easy climbing + if (oldVersion < 67) { + try { + db.execSQL("update " + dbTableAttributes + " set attribute = 'easy_climbing_yes' where geocode like 'OC%' and attribute = 'climbing_yes'"); + db.execSQL("update " + dbTableAttributes + " set attribute = 'easy_climbing_no' where geocode like 'OC%' and attribute = 'climbing_no'"); + } catch (Exception e) { + Log.e("Failed to upgrade to ver. 67", e); + + } + } } db.setTransactionSuccessful(); @@ -962,25 +973,32 @@ public class cgData { throw new IllegalArgumentException("cache must not be null"); } - // merge always with data already stored in the CacheCache or DB - if (saveFlags.contains(SaveFlag.SAVE_CACHE)) { - cache.gatherMissingFrom(cacheCache.getCacheFromCache(cache.getGeocode())); - cacheCache.putCacheInCache(cache); - } + // Merge with the data already stored in the CacheCache or in the database if + // the cache had not been loaded before, and update the CacheCache. + // Also, a DB update is required if the merge data comes from the CacheCache + // (as it may be more recent than the version in the database), or if the + // version coming from the database is different than the version we are entering + // into the cache (that includes absence from the database). + final String geocode = cache.getGeocode(); + final Geocache cacheFromCache = cacheCache.getCacheFromCache(geocode); + final boolean dbUpdateRequired = + !cache.gatherMissingFrom(cacheFromCache != null ? + cacheFromCache : + loadCache(geocode, LoadFlags.LOAD_ALL_DB_ONLY)) || + cacheFromCache != null; + cache.addStorageLocation(StorageLocation.CACHE); + cacheCache.putCacheInCache(cache); + // Only save the cache in the database if it is requested by the caller and + // the cache contains detailed information. if (!saveFlags.contains(SaveFlag.SAVE_DB)) { return true; } - boolean updateRequired = !cache.gatherMissingFrom(loadCache(cache.getGeocode(), LoadFlags.LOAD_ALL_DB_ONLY)); - // only save a cache to the database if - // - the cache is detailed - // - there are changes - // - the cache is only stored in the CacheCache so far - if ((!updateRequired || !cache.isDetailed()) && cache.getStorageLocation().contains(StorageLocation.DATABASE)) { - return false; - } + return cache.isDetailed() && dbUpdateRequired && storeIntoDatabase(cache); + } + private static boolean storeIntoDatabase(final Geocache cache) { cache.addStorageLocation(StorageLocation.DATABASE); cacheCache.putCacheInCache(cache); Log.d("Saving " + cache.toString() + " (" + cache.getListId() + ") to DB"); @@ -1396,7 +1414,7 @@ public class cgData { * @param geocodes * @return Set of loaded caches. Never null. */ - public static Set<Geocache> loadCaches(final Set<String> geocodes, final EnumSet<LoadFlag> loadFlags) { + public static Set<Geocache> loadCaches(final Collection<String> geocodes, final EnumSet<LoadFlag> loadFlags) { if (CollectionUtils.isEmpty(geocodes)) { return new HashSet<Geocache>(); } @@ -1440,7 +1458,7 @@ public class cgData { } if (remaining.size() >= 1) { - Log.i("cgData.loadCaches(" + remaining.toString() + ") failed"); + Log.d("cgData.loadCaches(" + remaining.toString() + ") returned no results"); } return result; } diff --git a/main/src/cgeo/geocaching/cgeoapplication.java b/main/src/cgeo/geocaching/cgeoapplication.java index a1fd7d1..b8f63ee 100644 --- a/main/src/cgeo/geocaching/cgeoapplication.java +++ b/main/src/cgeo/geocaching/cgeoapplication.java @@ -103,8 +103,8 @@ public class cgeoapplication extends Application { boolean restored = atomic.get(); String message = restored ? res.getString(R.string.init_restore_success) : res.getString(R.string.init_restore_failed); ActivityMixin.helpDialog(fromActivity, res.getString(R.string.init_backup_restore), message); - if (fromActivity instanceof cgeo) { - ((cgeo) fromActivity).updateCacheCounter(); + if (fromActivity instanceof MainActivity) { + ((MainActivity) fromActivity).updateCacheCounter(); } } }; diff --git a/main/src/cgeo/geocaching/cgeocaches.java b/main/src/cgeo/geocaching/cgeocaches.java index 61a32f1..e321d59 100644 --- a/main/src/cgeo/geocaching/cgeocaches.java +++ b/main/src/cgeo/geocaching/cgeocaches.java @@ -2,7 +2,6 @@ package cgeo.geocaching; import cgeo.geocaching.activity.AbstractActivity; import cgeo.geocaching.activity.AbstractListActivity; -import cgeo.geocaching.activity.ActivityMixin; import cgeo.geocaching.activity.FilteredActivity; import cgeo.geocaching.activity.Progress; import cgeo.geocaching.apps.cache.navi.NavigationAppFactory; @@ -39,6 +38,7 @@ import cgeo.geocaching.sorting.VisitComparator; import cgeo.geocaching.ui.CacheListAdapter; import cgeo.geocaching.ui.LoggingUI; import cgeo.geocaching.ui.WeakReferenceHandler; +import cgeo.geocaching.utils.AsyncTaskWithProgress; import cgeo.geocaching.utils.DateUtils; import cgeo.geocaching.utils.GeoDirHandler; import cgeo.geocaching.utils.Log; @@ -74,6 +74,7 @@ import android.widget.ListView; import android.widget.TextView; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashSet; @@ -96,6 +97,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity private static final int MENU_REMOVE_FROM_HISTORY = 23; private static final int MENU_DROP_CACHE = 24; private static final int MENU_MOVE_TO_LIST = 25; + private static final int MENU_REFRESH = 26; private static final int MENU_SWITCH_SELECT_MODE = 52; private static final int SUBMENU_SHOW_MAP = 54; private static final int SUBMENU_MANAGE_LISTS = 55; @@ -175,7 +177,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity showFooterMoreCaches(); if (search != null && search.getError() == StatusCode.UNAPPROVED_LICENSE) { - AlertDialog.Builder dialog = new AlertDialog.Builder(this); + final AlertDialog.Builder dialog = new AlertDialog.Builder(this); dialog.setTitle(res.getString(R.string.license)); dialog.setMessage(res.getString(R.string.err_license)); dialog.setCancelable(true); @@ -196,7 +198,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity } }); - AlertDialog alert = dialog.create(); + final AlertDialog alert = dialog.create(); alert.show(); } else if (search != null && search.getError() != null) { showToast(res.getString(R.string.err_download_fail) + ' ' + search.getError().getErrorString(res) + '.'); @@ -209,7 +211,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity } setAdapterCurrentCoordinates(false); - } catch (Exception e) { + } catch (final Exception e) { showToast(res.getString(R.string.err_detail_cache_find_any)); Log.e("cgeocaches.loadCachesHandler", e); @@ -223,14 +225,14 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity try { hideLoading(); showProgress(false); - } catch (Exception e2) { + } catch (final Exception e2) { Log.e("cgeocaches.loadCachesHandler.2", e2); } adapter.setSelectMode(false); } - private Handler loadCachesHandler = new LoadCachesHandler(this); + private final Handler loadCachesHandler = new LoadCachesHandler(this); private static class LoadCachesHandler extends WeakReferenceHandler<cgeocaches> { @@ -275,7 +277,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity } protected void updateTitle() { - ArrayList<Integer> numbers = new ArrayList<Integer>(); + final ArrayList<Integer> numbers = new ArrayList<Integer>(); if (adapter.isFiltered()) { numbers.add(adapter.getCount()); } @@ -290,7 +292,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity } } - private Handler loadDetailsHandler = new Handler() { + private final Handler loadDetailsHandler = new Handler() { @Override public void handleMessage(Message msg) { @@ -301,8 +303,8 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity adapter.notifyDataSetChanged(); - int secondsElapsed = (int) ((System.currentTimeMillis() - detailProgressTime) / 1000); - int minutesRemaining = ((detailTotal - detailProgress) * secondsElapsed / ((detailProgress > 0) ? detailProgress : 1) / 60); + final int secondsElapsed = (int) ((System.currentTimeMillis() - detailProgressTime) / 1000); + final int minutesRemaining = ((detailTotal - detailProgress) * secondsElapsed / ((detailProgress > 0) ? detailProgress : 1) / 60); progress.setProgress(detailProgress); if (minutesRemaining < 1) { @@ -338,7 +340,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity /** * TODO Possibly parts should be a Thread not a Handler */ - private Handler downloadFromWebHandler = new Handler() { + private final Handler downloadFromWebHandler = new Handler() { @Override public void handleMessage(Message msg) { setAdapter(); @@ -373,7 +375,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity } } }; - private Handler dropDetailsHandler = new Handler() { + private final Handler clearOfflineLogsHandler = new Handler() { @Override public void handleMessage(Message msg) { @@ -388,23 +390,8 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity } } }; - private Handler clearOfflineLogsHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - if (msg.what != MSG_CANCEL) { - adapter.setSelectMode(false); - - refreshCurrentList(); - - replaceCacheListFromSearch(); - - progress.dismiss(); - } - } - }; - - private Handler importGpxAttachementFinishedHandler = new Handler() { + private final Handler importGpxAttachementFinishedHandler = new Handler() { @Override public void handleMessage(Message msg) { refreshCurrentList(); @@ -421,14 +408,14 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity super.onCreate(savedInstanceState); setTheme(); - setContentView(R.layout.caches); + setContentView(R.layout.cacheslist_activity); // get parameters Bundle extras = getIntent().getExtras(); if (extras != null) { - Object typeObject = extras.get(Intents.EXTRA_LIST_TYPE); + final Object typeObject = extras.get(Intents.EXTRA_LIST_TYPE); type = (typeObject instanceof CacheListType) ? (CacheListType) typeObject : CacheListType.OFFLINE; - coords = (Geopoint) extras.getParcelable(Intents.EXTRAS_COORDS); + coords = extras.getParcelable(Intents.EXTRA_COORDS); } else { extras = new Bundle(); @@ -440,6 +427,17 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity } } + // Add the list selection in code. This way we can leave the XML layout of the action bar the same as for other activities. + final View titleBar = findViewById(R.id.actionbar_title); + titleBar.setClickable(true); + titleBar.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + selectList(v); + } + }); + setTitle(title); setAdapter(); @@ -480,24 +478,14 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity } private void importGpxAttachement() { - new AlertDialog.Builder(this) - .setTitle(res.getString(R.string.gpx_import_title)) - .setMessage(res.getString(R.string.gpx_import_confirm)) - .setCancelable(false) - .setPositiveButton(getString(android.R.string.yes), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - new GPXImporter(cgeocaches.this, listId, importGpxAttachementFinishedHandler).importGPX(); - } - }) - .setNegativeButton(getString(android.R.string.no), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - dialog.cancel(); - } - }) - .create() - .show(); + new StoredList.UserInterface(this).promptForListSelection(R.string.gpx_import_select_list_title, new RunnableWithArgument<Integer>() { + + @Override + public void run(Integer listId) { + new GPXImporter(cgeocaches.this, listId, importGpxAttachementFinishedHandler).importGPX(); + switchListById(listId); + } + }, true, 0); } @Override @@ -516,7 +504,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity // refresh standard list if it has changed (new caches downloaded) if (type == CacheListType.OFFLINE && listId >= StoredList.STANDARD_LIST_ID && search != null) { - SearchResult newSearch = cgData.getBatchOfStoredCaches(coords, Settings.getCacheType(), listId); + final SearchResult newSearch = cgData.getBatchOfStoredCaches(coords, Settings.getCacheType(), listId); if (newSearch != null && newSearch.getTotal() != search.getTotal()) { refreshCurrentList(); } @@ -551,7 +539,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity menu.add(0, MENU_SWITCH_SELECT_MODE, 0, res.getString(R.string.caches_select_mode)).setIcon(R.drawable.ic_menu_agenda); menu.add(0, MENU_INVERT_SELECTION, 0, res.getString(R.string.caches_select_invert)).setIcon(R.drawable.ic_menu_mark); if (type == CacheListType.OFFLINE) { - SubMenu subMenu = menu.addSubMenu(0, SUBMENU_MANAGE_OFFLINE, 0, res.getString(R.string.caches_manage)).setIcon(R.drawable.ic_menu_save); + final SubMenu subMenu = menu.addSubMenu(0, SUBMENU_MANAGE_OFFLINE, 0, res.getString(R.string.caches_manage)).setIcon(R.drawable.ic_menu_save); subMenu.add(0, MENU_DROP_CACHES, 0, res.getString(R.string.caches_drop_all)); // delete saved caches subMenu.add(0, MENU_DROP_CACHES_AND_LIST, 0, res.getString(R.string.caches_drop_all_and_list)); subMenu.add(0, MENU_REFRESH_STORED, 0, res.getString(R.string.cache_offline_refresh)); // download details for all caches @@ -568,17 +556,20 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity subMenu.add(0, MENU_EXPORT, 0, res.getString(R.string.export)); // export caches } else { if (type == CacheListType.HISTORY) { - SubMenu subMenu = menu.addSubMenu(0, SUBMENU_MANAGE_HISTORY, 0, res.getString(R.string.caches_manage)).setIcon(R.drawable.ic_menu_save); + final SubMenu subMenu = menu.addSubMenu(0, SUBMENU_MANAGE_HISTORY, 0, res.getString(R.string.caches_manage)).setIcon(R.drawable.ic_menu_save); subMenu.add(0, MENU_REMOVE_FROM_HISTORY, 0, res.getString(R.string.cache_clear_history)); // remove from history subMenu.add(0, MENU_EXPORT, 0, res.getString(R.string.export)); // export caches + subMenu.add(0, MENU_CLEAR_OFFLINE_LOGS, 0, res.getString(R.string.caches_clear_offlinelogs)); + menu.add(0, MENU_REFRESH_STORED, 0, res.getString(R.string.cache_offline_refresh)).setIcon(R.drawable.ic_menu_set_as); + } else { + menu.add(0, MENU_REFRESH_STORED, 0, res.getString(R.string.caches_store_offline)).setIcon(R.drawable.ic_menu_set_as); // download details for all caches } - menu.add(0, MENU_REFRESH_STORED, 0, res.getString(R.string.caches_store_offline)).setIcon(R.drawable.ic_menu_set_as); // download details for all caches } navigationMenu = CacheListAppFactory.addMenuItems(menu, this, res); if (type == CacheListType.OFFLINE) { - SubMenu subMenu = menu.addSubMenu(0, SUBMENU_MANAGE_LISTS, 0, res.getString(R.string.list_menu)).setIcon(R.drawable.ic_menu_more); + final SubMenu subMenu = menu.addSubMenu(0, SUBMENU_MANAGE_LISTS, 0, res.getString(R.string.list_menu)).setIcon(R.drawable.ic_menu_more); subMenu.add(0, MENU_CREATE_LIST, 0, res.getString(R.string.list_menu_create)); subMenu.add(0, MENU_DROP_LIST, 0, res.getString(R.string.list_menu_drop)); subMenu.add(0, MENU_RENAME_LIST, 0, res.getString(R.string.list_menu_rename)); @@ -625,8 +616,6 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity setVisible(menu, MENU_EXPORT, !isEmpty); setVisible(menu, MENU_REMOVE_FROM_HISTORY, !isEmpty); setVisible(menu, MENU_CLEAR_OFFLINE_LOGS, !isEmpty && containsOfflineLogs()); - setVisible(menu, MENU_IMPORT_GPX, isConcrete); - setVisible(menu, MENU_IMPORT_WEB, isConcrete); if (navigationMenu != null) { navigationMenu.setVisible(!isEmpty); @@ -635,12 +624,12 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity final boolean hasSelection = adapter != null && adapter.getCheckedCount() > 0; final boolean isNonDefaultList = isConcrete && listId != StoredList.STANDARD_LIST_ID; - if (type == CacheListType.OFFLINE) { // only offline list + if (type == CacheListType.OFFLINE || type == CacheListType.HISTORY) { // only offline list setMenuItemLabel(menu, MENU_DROP_CACHES, R.string.caches_drop_selected, R.string.caches_drop_all); menu.findItem(MENU_DROP_CACHES_AND_LIST).setVisible(!hasSelection && isNonDefaultList && !adapter.isFiltered()); setMenuItemLabel(menu, MENU_REFRESH_STORED, R.string.caches_refresh_selected, R.string.caches_refresh_all); setMenuItemLabel(menu, MENU_MOVE_TO_LIST, R.string.caches_move_selected, R.string.caches_move_all); - } else { // search and history list (all other than offline) + } else { // search and global list (all other than offline and history) setMenuItemLabel(menu, MENU_REFRESH_STORED, R.string.caches_store_selected, R.string.caches_store_offline); } @@ -665,7 +654,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity setMenuItemLabel(menu, MENU_REMOVE_FROM_HISTORY, R.string.cache_remove_from_history, R.string.cache_clear_history); setMenuItemLabel(menu, MENU_EXPORT, R.string.export, R.string.export); - } catch (Exception e) { + } catch (final Exception e) { Log.e("cgeocaches.onPrepareOptionsMenu", e); } @@ -673,7 +662,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity } private boolean containsEvents() { - for (Geocache cache : adapter.getCheckedOrAllCaches()) { + for (final Geocache cache : adapter.getCheckedOrAllCaches()) { if (cache.isEventCache()) { return true; } @@ -682,7 +671,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity } private boolean containsOfflineLogs() { - for (Geocache cache : adapter.getCheckedOrAllCaches()) { + for (final Geocache cache : adapter.getCheckedOrAllCaches()) { if (cache.isLogOffline()) { return true; } @@ -695,7 +684,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity if (menuItem == null) { return; } - boolean hasSelection = adapter != null && adapter.getCheckedCount() > 0; + final boolean hasSelection = adapter != null && adapter.getCheckedCount() > 0; if (hasSelection) { menuItem.setTitle(res.getString(resIdSelection) + " (" + adapter.getCheckedCount() + ")"); } else { @@ -705,7 +694,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity @Override public boolean onOptionsItemSelected(MenuItem item) { - int itemId = item.getItemId(); + final int itemId = item.getItemId(); switch (itemId) { case MENU_SWITCH_SELECT_MODE: adapter.switchSelectMode(); @@ -794,9 +783,8 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity } public void deletePastEvents() { - progress.show(this, null, res.getString(R.string.caches_drop_progress), true, dropDetailsHandler.obtainMessage(MSG_CANCEL)); final List<Geocache> deletion = new ArrayList<Geocache>(); - for (Geocache cache : adapter.getCheckedOrAllCaches()) { + for (final Geocache cache : adapter.getCheckedOrAllCaches()) { if (cache.isEventCache()) { final Date eventDate = cache.getHiddenDate(); if (DateUtils.daysSince(eventDate.getTime()) > 0) { @@ -804,7 +792,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity } } } - new DropDetailsThread(dropDetailsHandler, deletion).start(); + new DropDetailsTask(false).execute(deletion.toArray(new Geocache[deletion.size()])); } public void clearOfflineLogs() { @@ -842,7 +830,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity AdapterContextMenuInfo adapterInfo = null; try { adapterInfo = (AdapterContextMenuInfo) info; - } catch (Exception e) { + } catch (final Exception e) { Log.w("cgeocaches.onCreateContextMenu", e); } @@ -858,13 +846,14 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity if (cache.getCoords() != null) { menu.add(0, MENU_DEFAULT_NAVIGATION, 0, NavigationAppFactory.getDefaultNavigationApplication().getName()); menu.add(1, MENU_NAVIGATION, 0, res.getString(R.string.cache_menu_navigate)).setIcon(R.drawable.ic_menu_mapmode); - LoggingUI.addMenuItems(menu, cache); + LoggingUI.addMenuItems(this, menu, cache); menu.add(0, MENU_CACHE_DETAILS, 0, res.getString(R.string.cache_menu_details)); } if (cache.isOffline()) { menu.add(0, MENU_DROP_CACHE, 0, res.getString(R.string.cache_offline_drop)); menu.add(0, MENU_MOVE_TO_LIST, 0, res.getString(R.string.cache_menu_move_list)); menu.add(0, MENU_EXPORT, 0, res.getString(R.string.export)); + menu.add(0, MENU_REFRESH, 0, res.getString(R.string.cache_menu_refresh)); } else { menu.add(0, MENU_STORE_CACHE, 0, res.getString(R.string.cache_offline_store)); @@ -898,7 +887,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity AdapterContextMenuInfo adapterInfo = null; try { adapterInfo = (AdapterContextMenuInfo) info; - } catch (Exception e) { + } catch (final Exception e) { Log.w("cgeocaches.onContextItemSelected", e); } @@ -944,6 +933,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity }, true, listId); break; case MENU_STORE_CACHE: + case MENU_REFRESH: refreshStored(Collections.singletonList(cache)); break; case MENU_EXPORT: @@ -1000,7 +990,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity if (inflater == null) { inflater = getLayoutInflater(); } - listFooter = inflater.inflate(R.layout.caches_footer, null); + listFooter = inflater.inflate(R.layout.cacheslist_footer, null); listFooter.setClickable(true); listFooter.setOnClickListener(new MoreCachesListener()); @@ -1082,6 +1072,11 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity return; } + if (!Network.isNetworkConnected(getApplicationContext())) { + showToast(getString(R.string.err_server)); + return; + } + if (Settings.getChooseList() && type != CacheListType.OFFLINE) { // let user select list to store cache in new StoredList.UserInterface(this).promptForListSelection(R.string.list_title, @@ -1101,7 +1096,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity showProgress(false); - int etaTime = ((detailTotal * 25) / 60); + final int etaTime = ((detailTotal * 25) / 60); String message; if (etaTime < 1) { message = res.getString(R.string.caches_downloading) + " " + res.getString(R.string.caches_eta_ltm); @@ -1119,7 +1114,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity } public void removeFromHistoryCheck() { - AlertDialog.Builder dialog = new AlertDialog.Builder(this); + final AlertDialog.Builder dialog = new AlertDialog.Builder(this); dialog.setCancelable(true); dialog.setTitle(res.getString(R.string.caches_removing_from_history)); dialog.setMessage((adapter != null && adapter.getCheckedCount() > 0) ? res.getString(R.string.cache_remove_from_history) @@ -1138,17 +1133,17 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity } }); - AlertDialog alert = dialog.create(); + final AlertDialog alert = dialog.create(); alert.show(); } public void removeFromHistory() { final List<Geocache> caches = adapter.getCheckedOrAllCaches(); - final String geocodes[] = new String[caches.size()]; + final String[] geocodes = new String[caches.size()]; for (int i = 0; i < geocodes.length; i++) { geocodes[i] = caches.get(i).getGeocode(); } - Bundle b = new Bundle(); + final Bundle b = new Bundle(); b.putStringArray(Intents.EXTRA_CACHELIST, geocodes); getSupportLoaderManager().initLoader(CacheListLoaderType.REMOVE_FROM_HISTORY.ordinal(), b, this); } @@ -1164,7 +1159,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity } public void dropStored(final boolean removeListAfterwards) { - AlertDialog.Builder dialog = new AlertDialog.Builder(this); + final AlertDialog.Builder dialog = new AlertDialog.Builder(this); dialog.setCancelable(true); dialog.setTitle(res.getString(R.string.caches_drop_stored)); @@ -1177,10 +1172,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity @Override public void onClick(DialogInterface dialog, int id) { - dropSelected(); - if (removeListAfterwards) { - removeList(false); - } + dropSelected(removeListAfterwards); dialog.cancel(); } }); @@ -1192,13 +1184,13 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity } }); - AlertDialog alert = dialog.create(); + final AlertDialog alert = dialog.create(); alert.show(); } - public void dropSelected() { - progress.show(this, null, res.getString(R.string.caches_drop_progress), true, dropDetailsHandler.obtainMessage(MSG_CANCEL)); - new DropDetailsThread(dropDetailsHandler, adapter.getCheckedOrAllCaches()).start(); + public void dropSelected(boolean removeListAfterwards) { + final List<Geocache> selected = adapter.getCheckedOrAllCaches(); + new DropDetailsTask(removeListAfterwards).execute(selected.toArray(new Geocache[selected.size()])); } /** @@ -1230,7 +1222,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity removeGeoAndDir(); final List<Geocache> cachesWithStaticMaps = new ArrayList<Geocache>(this.caches.size()); - for (Geocache cache : this.caches) { + for (final Geocache cache : this.caches) { if (Settings.isStoreOfflineMaps() && cache.hasStaticMap()) { cachesWithStaticMaps.add(cache); continue; @@ -1242,7 +1234,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity } } - for (Geocache cache : cachesWithStaticMaps) { + for (final Geocache cache : cachesWithStaticMaps) { if (!refreshCache(cache)) { break; } @@ -1274,7 +1266,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity } Log.i("Waiting for next cache " + delay + " ms"); - } catch (Exception e) { + } catch (final Exception e) { Log.e("cgeocaches.LoadDetailsThread.sleep", e); } } @@ -1289,10 +1281,10 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity handler.sendEmptyMessage(cacheList.indexOf(cache)); yield(); - } catch (InterruptedException e) { + } catch (final InterruptedException e) { Log.i(e.getMessage()); return false; - } catch (Exception e) { + } catch (final Exception e) { Log.e("cgeocaches.LoadDetailsThread", e); } @@ -1309,7 +1301,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity public LoadFromWebThread(Handler handlerIn, int listId) { handler = handlerIn; - listIdLFW = listId; + listIdLFW = StoredList.getConcreteList(listId); } public void kill() { @@ -1332,7 +1324,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity deviceCode = ""; } final Parameters params = new Parameters("code", deviceCode); - HttpResponse responseFromWeb = Network.getRequest("http://send2.cgeo.org/read.html", params); + final HttpResponse responseFromWeb = Network.getRequest("http://send2.cgeo.org/read.html", params); if (responseFromWeb != null && responseFromWeb.getStatusLine().getStatusCode() == 200) { final String response = Network.getResponseData(responseFromWeb); @@ -1372,7 +1364,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity sleep(500); //Cache was loaded 0.5s times = 0; } - } catch (InterruptedException e) { + } catch (final InterruptedException e) { Log.e("cgeocaches.LoadFromWebThread.sleep", e); } } @@ -1383,24 +1375,35 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity } } - private class DropDetailsThread extends Thread { + private class DropDetailsTask extends AsyncTaskWithProgress<Geocache, Void> { - final private Handler handler; - final private List<Geocache> selected; + private final boolean removeListAfterwards; - public DropDetailsThread(Handler handlerIn, List<Geocache> selectedIn) { - handler = handlerIn; - selected = selectedIn; + public DropDetailsTask(boolean removeListAfterwards) { + super(cgeocaches.this, null, res.getString(R.string.caches_drop_progress), true); + this.removeListAfterwards = removeListAfterwards; } @Override - public void run() { + protected Void doInBackgroundInternal(Geocache[] caches) { removeGeoAndDir(); - cgData.markDropped(selected); - handler.sendEmptyMessage(MSG_DONE); - + cgData.markDropped(Arrays.asList(caches)); startGeoAndDir(); + return null; + } + + @Override + protected void onPostExecuteInternal(Void result) { + // remove list in UI because of toast + if (removeListAfterwards) { + removeList(false); + } + + adapter.setSelectMode(false); + refreshCurrentList(); + replaceCacheListFromSearch(); } + } private class ClearOfflineLogsThread extends Thread { @@ -1463,7 +1466,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity return; } - StoredList list = cgData.getList(id); + final StoredList list = cgData.getList(id); if (list == null) { return; } @@ -1552,8 +1555,8 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity } // apply filter settings (if there's a filter) - Set<String> geocodes = new HashSet<String>(); - for (Geocache cache : adapter.getFilteredList()) { + final Set<String> geocodes = new HashSet<String>(); + for (final Geocache cache : adapter.getFilteredList()) { geocodes.add(cache.getGeocode()); } @@ -1566,21 +1569,6 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity CGeoMap.startActivitySearch(this, searchToUse, mapTitle); } - @Override - public void goManual(View view) { - switch (type) { - case OFFLINE: - ActivityMixin.goManual(this, "c:geo-stored"); - break; - case HISTORY: - ActivityMixin.goManual(this, "c:geo-history"); - break; - default: - ActivityMixin.goManual(this, "c:geo-nearby"); - break; - } - } - private void refreshCurrentList() { switchListById(listId); } @@ -1643,7 +1631,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity private void setDateComparatorForEventList() { if (CollectionUtils.isNotEmpty(cacheList)) { boolean eventsOnly = true; - for (Geocache cache : cacheList) { + for (final Geocache cache : cacheList) { if (!cache.isEventCache()) { eventsOnly = false; break; @@ -1667,7 +1655,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity } final Intent cachesIntent = new Intent(context, cgeocaches.class); cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.NEAREST); - cachesIntent.putExtra(Intents.EXTRAS_COORDS, coordsNow); + cachesIntent.putExtra(Intents.EXTRA_COORDS, coordsNow); context.startActivity(cachesIntent); } @@ -1680,7 +1668,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity public static void startActivityAddress(final Context context, final Geopoint coords, final String address) { final Intent addressIntent = new Intent(context, cgeocaches.class); addressIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.ADDRESS); - addressIntent.putExtra(Intents.EXTRAS_COORDS, coords); + addressIntent.putExtra(Intents.EXTRA_COORDS, coords); addressIntent.putExtra(Intents.EXTRA_ADDRESS, address); context.startActivity(addressIntent); } @@ -1691,7 +1679,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity } final Intent cachesIntent = new Intent(context, cgeocaches.class); cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.COORDINATE); - cachesIntent.putExtra(Intents.EXTRAS_COORDS, coords); + cachesIntent.putExtra(Intents.EXTRA_COORDS, coords); context.startActivity(cachesIntent); } @@ -1725,11 +1713,11 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity @Override public Loader<SearchResult> onCreateLoader(int type, Bundle extras) { - AbstractSearchLoader loader = null; if (type >= CacheListLoaderType.values().length) { throw new IllegalArgumentException("invalid loader type " + type); } - CacheListLoaderType enumType = CacheListLoaderType.values()[type]; + final CacheListLoaderType enumType = CacheListLoaderType.values()[type]; + AbstractSearchLoader loader = null; switch (enumType) { case OFFLINE: listId = Settings.getLastList(); diff --git a/main/src/cgeo/geocaching/compatibility/AndroidLevel8Emulation.java b/main/src/cgeo/geocaching/compatibility/AndroidLevel8Emulation.java index a60b48d..6d5781f 100644 --- a/main/src/cgeo/geocaching/compatibility/AndroidLevel8Emulation.java +++ b/main/src/cgeo/geocaching/compatibility/AndroidLevel8Emulation.java @@ -19,7 +19,11 @@ public class AndroidLevel8Emulation implements AndroidLevel8Interface { @Override public int getRotationOffset(Activity activity) { final Display display = activity.getWindowManager().getDefaultDisplay(); + + // the non deprecated method is available in API 8+ only, so we cannot deal better with this + @SuppressWarnings("deprecation") final int rotation = display.getOrientation(); + if (rotation == Configuration.ORIENTATION_LANDSCAPE) { return 90; } diff --git a/main/src/cgeo/geocaching/connector/AbstractConnector.java b/main/src/cgeo/geocaching/connector/AbstractConnector.java index 413291c..83c1b6f 100644 --- a/main/src/cgeo/geocaching/connector/AbstractConnector.java +++ b/main/src/cgeo/geocaching/connector/AbstractConnector.java @@ -1,11 +1,13 @@ package cgeo.geocaching.connector; import cgeo.geocaching.Geocache; -import cgeo.geocaching.enumerations.CacheRealm; +import cgeo.geocaching.R; import cgeo.geocaching.geopoint.Geopoint; import org.apache.commons.lang3.StringUtils; +import android.app.Activity; + public abstract class AbstractConnector implements IConnector { @Override @@ -19,6 +21,16 @@ public abstract class AbstractConnector implements IConnector { } @Override + public boolean addToWatchlist(Geocache cache) { + return false; + } + + @Override + public boolean removeFromWatchlist(Geocache cache) { + return false; + } + + @Override public boolean supportsOwnCoordinates() { return false; } @@ -36,6 +48,17 @@ public abstract class AbstractConnector implements IConnector { } /** + * Uploading personal note to website + * + * @param cache + * @return success + */ + @Override + public boolean uploadPersonalNote(Geocache cache) { + throw new UnsupportedOperationException(); + } + + /** * {@link IConnector} */ @Override @@ -54,6 +77,21 @@ public abstract class AbstractConnector implements IConnector { } @Override + public boolean supportsLogImages() { + return false; + } + + @Override + public boolean canLog(Geocache cache) { + return false; + } + + @Override + public ILoggingManager getLoggingManager(Activity activity, Geocache cache) { + return new NoLoggingManager(); + } + + @Override public String getLicenseText(final Geocache cache) { return null; } @@ -111,15 +149,15 @@ public abstract class AbstractConnector implements IConnector { * {@link IConnector} */ @Override - public CacheRealm getCacheRealm() { - return CacheRealm.OTHER; + public boolean isActivated() { + return false; } - /** - * {@link IConnector} - */ @Override - public boolean isActivated() { - return false; + public int getCacheMapMarkerId(boolean disabled) { + if (disabled) { + return R.drawable.marker_disabled_other; + } + return R.drawable.marker_other; } } diff --git a/main/src/cgeo/geocaching/connector/ConnectorFactory.java b/main/src/cgeo/geocaching/connector/ConnectorFactory.java index 561bae2..2cf2588 100644 --- a/main/src/cgeo/geocaching/connector/ConnectorFactory.java +++ b/main/src/cgeo/geocaching/connector/ConnectorFactory.java @@ -1,15 +1,21 @@ package cgeo.geocaching.connector; import cgeo.geocaching.ICache; +import cgeo.geocaching.R; import cgeo.geocaching.SearchResult; import cgeo.geocaching.Trackable; import cgeo.geocaching.connector.capability.ISearchByCenter; import cgeo.geocaching.connector.capability.ISearchByViewPort; import cgeo.geocaching.connector.gc.GCConnector; import cgeo.geocaching.connector.oc.OCApiConnector; +import cgeo.geocaching.connector.oc.OCApiConnector.ApiSupport; +import cgeo.geocaching.connector.oc.OCApiLiveConnector; import cgeo.geocaching.connector.oc.OCConnector; -import cgeo.geocaching.connector.oc.OCXMLApiConnector; import cgeo.geocaching.connector.ox.OXConnector; +import cgeo.geocaching.connector.trackable.GeokretyConnector; +import cgeo.geocaching.connector.trackable.TrackableConnector; +import cgeo.geocaching.connector.trackable.TravelBugConnector; +import cgeo.geocaching.connector.trackable.UnknownTrackableConnector; import cgeo.geocaching.geopoint.Viewport; import org.apache.commons.lang3.StringUtils; @@ -19,39 +25,47 @@ import java.util.List; public final class ConnectorFactory { private static final UnknownConnector UNKNOWN_CONNECTOR = new UnknownConnector(); - private static final IConnector[] connectors = new IConnector[] { + private static final IConnector[] CONNECTORS = new IConnector[] { GCConnector.getInstance(), - new OCXMLApiConnector("OpenCaching.DE", "www.opencaching.de", "OC"), + new OCApiLiveConnector("Opencaching.de", "www.opencaching.de", "OC", R.string.oc_de_okapi_consumer_key, R.string.oc_de_okapi_consumer_secret, ApiSupport.current), new OCConnector("OpenCaching.CZ", "www.opencaching.cz", "OZ"), - new OCApiConnector("OpenCaching.CO.UK", "www.opencaching.org.uk", "OK", "arU4okouc4GEjMniE2fq"), + new OCApiConnector("OpenCaching.CO.UK", "www.opencaching.org.uk", "OK", "arU4okouc4GEjMniE2fq", ApiSupport.oldapi), new OCConnector("OpenCaching.ES", "www.opencachingspain.es", "OC"), new OCConnector("OpenCaching.IT", "www.opencaching.it", "OC"), new OCConnector("OpenCaching.JP", "www.opencaching.jp", "OJ"), new OCConnector("OpenCaching.NO/SE", "www.opencaching.se", "OS"), - new OCApiConnector("OpenCaching.NL", "www.opencaching.nl", "OB", "PdzU8jzIlcfMADXaYN8j"), - new OCApiConnector("OpenCaching.PL", "www.opencaching.pl", "OP", "GkxM47WkUkLQXXsZ9qSh"), - new OCApiConnector("OpenCaching.US", "www.opencaching.us", "OU", "pTsYAYSXFcfcRQnYE6uA"), + new OCApiConnector("OpenCaching.NL", "www.opencaching.nl", "OB", "PdzU8jzIlcfMADXaYN8j", ApiSupport.current), + new OCApiConnector("OpenCaching.PL", "www.opencaching.pl", "OP", "GkxM47WkUkLQXXsZ9qSh", ApiSupport.current), + new OCApiConnector("OpenCaching.US", "www.opencaching.us", "OU", "pTsYAYSXFcfcRQnYE6uA", ApiSupport.oldapi), new OXConnector(), new GeocachingAustraliaConnector(), new GeopeitusConnector(), + new WaymarkingConnector(), UNKNOWN_CONNECTOR // the unknown connector MUST be the last one }; + public static final UnknownTrackableConnector UNKNOWN_TRACKABLE_CONNECTOR = new UnknownTrackableConnector(); + private static final TrackableConnector[] TRACKABLE_CONNECTORS = new TrackableConnector[] { + new GeokretyConnector(), // GK must be first, as it overlaps with the secret codes of travel bugs + new TravelBugConnector(), + UNKNOWN_TRACKABLE_CONNECTOR // must be last + }; + private static final ISearchByViewPort[] searchByViewPortConns; private static final ISearchByCenter[] searchByCenterConns; static { - List<ISearchByViewPort> vpConns = new ArrayList<ISearchByViewPort>(); - for (IConnector conn : connectors) { + final List<ISearchByViewPort> vpConns = new ArrayList<ISearchByViewPort>(); + for (final IConnector conn : CONNECTORS) { if (conn instanceof ISearchByViewPort) { vpConns.add((ISearchByViewPort) conn); } } searchByViewPortConns = vpConns.toArray(new ISearchByViewPort[vpConns.size()]); - List<ISearchByCenter> centerConns = new ArrayList<ISearchByCenter>(); - for (IConnector conn : connectors) { + final List<ISearchByCenter> centerConns = new ArrayList<ISearchByCenter>(); + for (final IConnector conn : CONNECTORS) { // GCConnector is handled specially, omit it here! if (conn instanceof ISearchByCenter && !(conn instanceof GCConnector)) { centerConns.add((ISearchByCenter) conn); @@ -61,7 +75,7 @@ public final class ConnectorFactory { } public static IConnector[] getConnectors() { - return connectors; + return CONNECTORS; } public static ISearchByCenter[] getSearchByCenterConnectors() { @@ -72,7 +86,7 @@ public final class ConnectorFactory { if (isInvalidGeocode(geocode)) { return false; } - for (IConnector connector : connectors) { + for (final IConnector connector : CONNECTORS) { if (connector.canHandle(geocode)) { return true; } @@ -84,8 +98,17 @@ public final class ConnectorFactory { return getConnector(cache.getGeocode()); } - public static IConnector getConnector(Trackable trackable) { - return getConnector(trackable.getGeocode()); + public static TrackableConnector getConnector(Trackable trackable) { + return getTrackableConnector(trackable.getGeocode()); + } + + public static TrackableConnector getTrackableConnector(String geocode) { + for (final TrackableConnector connector : TRACKABLE_CONNECTORS) { + if (connector.canHandleTrackable(geocode)) { + return connector; + } + } + return UNKNOWN_TRACKABLE_CONNECTOR; // avoid null checks by returning a non implementing connector } public static IConnector getConnector(final String geocodeInput) { @@ -94,12 +117,12 @@ public final class ConnectorFactory { if (isInvalidGeocode(geocode)) { return UNKNOWN_CONNECTOR; } - for (IConnector connector : connectors) { + for (final IConnector connector : CONNECTORS) { if (connector.canHandle(geocode)) { return connector; } } - // in case of errors, take UNKNOWN + // in case of errors, take UNKNOWN to avoid null checks everywhere return UNKNOWN_CONNECTOR; } @@ -110,10 +133,10 @@ public final class ConnectorFactory { /** @see ISearchByViewPort#searchByViewport */ public static SearchResult searchByViewport(final Viewport viewport, final String[] tokens) { - SearchResult result = new SearchResult(); - for (ISearchByViewPort vpconn : searchByViewPortConns) { + final SearchResult result = new SearchResult(); + for (final ISearchByViewPort vpconn : searchByViewPortConns) { if (vpconn.isActivated()) { - SearchResult temp = vpconn.searchByViewport(viewport, tokens); + final SearchResult temp = vpconn.searchByViewport(viewport, tokens); if (temp != null) { result.addGeocodes(temp.getGeocodes()); } @@ -123,8 +146,8 @@ public final class ConnectorFactory { } public static String getGeocodeFromURL(final String url) { - for (IConnector connector : connectors) { - String geocode = connector.getGeocodeFromUrl(url); + for (final IConnector connector : CONNECTORS) { + final String geocode = connector.getGeocodeFromUrl(url); if (StringUtils.isNotBlank(geocode)) { return geocode; } @@ -132,4 +155,8 @@ public final class ConnectorFactory { return null; } + public static TrackableConnector[] getTrackableConnectors() { + return TRACKABLE_CONNECTORS; + } + } diff --git a/main/src/cgeo/geocaching/connector/IConnector.java b/main/src/cgeo/geocaching/connector/IConnector.java index 9169b4a..c44b946 100644 --- a/main/src/cgeo/geocaching/connector/IConnector.java +++ b/main/src/cgeo/geocaching/connector/IConnector.java @@ -2,9 +2,10 @@ package cgeo.geocaching.connector; import cgeo.geocaching.Geocache; import cgeo.geocaching.ICache; -import cgeo.geocaching.enumerations.CacheRealm; import cgeo.geocaching.geopoint.Geopoint; +import android.app.Activity; + public interface IConnector { /** * get name for display (currently only used in links) @@ -45,6 +46,22 @@ public interface IConnector { public boolean supportsWatchList(); /** + * Add the cache to the watchlist + * + * @param cache + * @return True - success/False - failure + */ + public boolean addToWatchlist(Geocache cache); + + /** + * Remove the cache from the watchlist + * + * @param cache + * @return True - success/False - failure + */ + public boolean removeFromWatchlist(Geocache cache); + + /** * enable/disable favorite points controls in cache details * * @return @@ -59,6 +76,20 @@ public interface IConnector { public boolean supportsLogging(); /** + * enable/disable attaching image to log + * + * @return + */ + public boolean supportsLogImages(); + + /** + * Get an ILoggingManager to guide the logging process. + * + * @return + */ + public ILoggingManager getLoggingManager(Activity activity, Geocache cache); + + /** * get host name of the connector server for dynamic loading of data * * @return @@ -120,13 +151,12 @@ public interface IConnector { public boolean supportsOwnCoordinates(); /** - * Uploading modified coordinates to website + * Uploading personal note to website * * @param cache - * @param wpt * @return success */ - public boolean uploadModifiedCoordinates(Geocache cache, Geopoint wpt); + public boolean uploadPersonalNote(Geocache cache); /** * Reseting of modified coordinates on website to details @@ -137,11 +167,13 @@ public interface IConnector { public boolean deleteModifiedCoordinates(Geocache cache); /** - * The CacheRealm this cache belongs to + * Uploading modified coordinates to website * - * @return + * @param cache + * @param wpt + * @return success */ - public CacheRealm getCacheRealm(); + public boolean uploadModifiedCoordinates(Geocache cache, Geopoint wpt); /** * Return true if this connector is activated for online @@ -159,4 +191,22 @@ public interface IConnector { * @return <code>true</code> if the current user is the cache owner, <code>false</code> otherwise */ public boolean isOwner(final ICache cache); + + /** + * Check if the cache information is complete enough to be + * able to log online. + * + * @param geocache + * @return + */ + public boolean canLog(Geocache geocache); + + /** + * Return the marker id of the caches for this connector. This creates the different backgrounds for cache markers + * on the map. + * + * @param disabled + * Whether to return the enabled or disabled marker type + */ + public int getCacheMapMarkerId(boolean disabled); } diff --git a/main/src/cgeo/geocaching/connector/ILoggingManager.java b/main/src/cgeo/geocaching/connector/ILoggingManager.java new file mode 100644 index 0000000..f0029f9 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/ILoggingManager.java @@ -0,0 +1,32 @@ +package cgeo.geocaching.connector; + +import cgeo.geocaching.Geocache; +import cgeo.geocaching.TrackableLog; +import cgeo.geocaching.enumerations.LogType; + +import android.net.Uri; + +import java.util.Calendar; +import java.util.List; + +public interface ILoggingManager { + + LogResult postLog(Geocache cache, + LogType logType, + Calendar date, + String log, + List<TrackableLog> trackableLogs); + + ImageResult postLogImage(String logId, + String imageCaption, + String imageDescription, + Uri imageUri); + + public boolean hasLoaderError(); + + public List<TrackableLog> getTrackables(); + + public List<LogType> getPossibleLogTypes(); + + public void init(); +} diff --git a/main/src/cgeo/geocaching/connector/ImageResult.java b/main/src/cgeo/geocaching/connector/ImageResult.java new file mode 100644 index 0000000..9314cad --- /dev/null +++ b/main/src/cgeo/geocaching/connector/ImageResult.java @@ -0,0 +1,23 @@ +package cgeo.geocaching.connector; + +import cgeo.geocaching.enumerations.StatusCode; + +public class ImageResult { + + private final StatusCode postResult; + private final String imageUri; + + public ImageResult(StatusCode postResult, String imageUri) { + this.postResult = postResult; + this.imageUri = imageUri; + } + + public StatusCode getPostResult() { + return postResult; + } + + public String getImageUri() { + return imageUri; + } + +} diff --git a/main/src/cgeo/geocaching/connector/LogResult.java b/main/src/cgeo/geocaching/connector/LogResult.java new file mode 100644 index 0000000..62111a4 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/LogResult.java @@ -0,0 +1,23 @@ +package cgeo.geocaching.connector; + +import cgeo.geocaching.enumerations.StatusCode; + +public class LogResult { + + private final StatusCode postLogResult; + private final String logId; + + public LogResult(StatusCode postLogResult, String logId) { + this.postLogResult = postLogResult; + this.logId = logId; + } + + public StatusCode getPostLogResult() { + return postLogResult; + } + + public String getLogId() { + return logId; + } + +} diff --git a/main/src/cgeo/geocaching/connector/NoLoggingManager.java b/main/src/cgeo/geocaching/connector/NoLoggingManager.java new file mode 100644 index 0000000..bfea4ca --- /dev/null +++ b/main/src/cgeo/geocaching/connector/NoLoggingManager.java @@ -0,0 +1,46 @@ +package cgeo.geocaching.connector; + +import cgeo.geocaching.Geocache; +import cgeo.geocaching.TrackableLog; +import cgeo.geocaching.enumerations.LogType; +import cgeo.geocaching.enumerations.StatusCode; + +import android.net.Uri; + +import java.util.Calendar; +import java.util.Collections; +import java.util.List; + +public class NoLoggingManager implements ILoggingManager { + + @Override + public void init() { + // nothing to do + } + + @Override + public LogResult postLog(Geocache cache, LogType logType, Calendar date, String log, List<TrackableLog> trackableLogs) { + return new LogResult(StatusCode.LOG_POST_ERROR, ""); + } + + @Override + public ImageResult postLogImage(String logId, String imageCaption, String imageDescription, Uri imageUri) { + return new ImageResult(StatusCode.LOG_POST_ERROR, ""); + } + + @Override + public boolean hasLoaderError() { + return true; + } + + @Override + public List<TrackableLog> getTrackables() { + return Collections.emptyList(); + } + + @Override + public List<LogType> getPossibleLogTypes() { + return Collections.emptyList(); + } + +} diff --git a/main/src/cgeo/geocaching/connector/UnknownConnector.java b/main/src/cgeo/geocaching/connector/UnknownConnector.java index b6fc29a..e9fecb9 100644 --- a/main/src/cgeo/geocaching/connector/UnknownConnector.java +++ b/main/src/cgeo/geocaching/connector/UnknownConnector.java @@ -1,7 +1,7 @@ package cgeo.geocaching.connector; -import cgeo.geocaching.ICache; import cgeo.geocaching.Geocache; +import cgeo.geocaching.ICache; import org.apache.commons.lang3.StringUtils; @@ -36,4 +36,5 @@ public class UnknownConnector extends AbstractConnector { protected String getCacheUrlPrefix() { return null; } + } diff --git a/main/src/cgeo/geocaching/connector/WaymarkingConnector.java b/main/src/cgeo/geocaching/connector/WaymarkingConnector.java new file mode 100644 index 0000000..f184f6e --- /dev/null +++ b/main/src/cgeo/geocaching/connector/WaymarkingConnector.java @@ -0,0 +1,40 @@ +package cgeo.geocaching.connector; + +import cgeo.geocaching.Geocache; +import cgeo.geocaching.ICache; + +import org.apache.commons.lang3.StringUtils; + +public class WaymarkingConnector extends AbstractConnector { + + @Override + public String getName() { + return "Waymarking"; + } + + @Override + public String getCacheUrl(Geocache cache) { + return getCacheUrlPrefix() + cache.getGeocode(); + } + + @Override + public String getHost() { + return "www.waymarking.com"; + } + + @Override + public boolean isOwner(ICache cache) { + // this connector has no user management + return false; + } + + @Override + protected String getCacheUrlPrefix() { + return "http://" + getHost() + "/waymarks/"; + } + + @Override + public boolean canHandle(String geocode) { + return StringUtils.startsWith(geocode, "WM"); + } +} diff --git a/main/src/cgeo/geocaching/connector/gc/GCConnector.java b/main/src/cgeo/geocaching/connector/gc/GCConnector.java index 50bf096..fab4332 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCConnector.java +++ b/main/src/cgeo/geocaching/connector/gc/GCConnector.java @@ -7,10 +7,10 @@ import cgeo.geocaching.SearchResult; import cgeo.geocaching.Settings; import cgeo.geocaching.cgData; import cgeo.geocaching.connector.AbstractConnector; +import cgeo.geocaching.connector.ILoggingManager; import cgeo.geocaching.connector.capability.ISearchByCenter; import cgeo.geocaching.connector.capability.ISearchByGeocode; import cgeo.geocaching.connector.capability.ISearchByViewPort; -import cgeo.geocaching.enumerations.CacheRealm; import cgeo.geocaching.enumerations.StatusCode; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.Viewport; @@ -20,6 +20,8 @@ import cgeo.geocaching.utils.Log; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; +import android.app.Activity; + import java.util.regex.Pattern; public class GCConnector extends AbstractConnector implements ISearchByGeocode, ISearchByCenter, ISearchByViewPort { @@ -27,7 +29,16 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, private static final String CACHE_URL_SHORT = "http://coord.info/"; // Double slash is used to force open in browser private static final String CACHE_URL_LONG = "http://www.geocaching.com//seek/cache_details.aspx?wp="; - private static final Pattern gpxZipFilePattern = Pattern.compile("\\d{7,}(_.+)?\\.zip", Pattern.CASE_INSENSITIVE); + /** + * Pocket queries downloaded from the website use a numeric prefix. The pocket query creator Android app adds a + * verbatim "pocketquery" prefix. + */ + private static final Pattern gpxZipFilePattern = Pattern.compile("((\\d{7,})|(pocketquery))" + "(_.+)?" + "\\.zip", Pattern.CASE_INSENSITIVE); + + /** + * Pattern for GC codes + */ + private final static Pattern PATTERN_GC_CODE = Pattern.compile("GC[0-9A-Z]+", Pattern.CASE_INSENSITIVE); private GCConnector() { // singleton @@ -49,7 +60,7 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, if (geocode == null) { return false; } - return GCConstants.PATTERN_GC_CODE.matcher(geocode).matches() || GCConstants.PATTERN_TB_CODE.matcher(geocode).matches(); + return GCConnector.PATTERN_GC_CODE.matcher(geocode).matches(); } @Override @@ -78,6 +89,21 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, } @Override + public boolean supportsLogImages() { + return true; + } + + @Override + public ILoggingManager getLoggingManager(Activity activity, Geocache cache) { + return new GCLoggingManager(activity, cache); + } + + @Override + public boolean canLog(Geocache cache) { + return StringUtils.isNotBlank(cache.getCacheId()); + } + + @Override public String getName() { return "GeoCaching.com"; } @@ -149,7 +175,8 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, } - public static boolean addToWatchlist(Geocache cache) { + @Override + public boolean addToWatchlist(Geocache cache) { final boolean added = GCParser.addToWatchlist(cache); if (added) { cgData.saveChangedCache(cache); @@ -157,7 +184,8 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, return added; } - public static boolean removeFromWatchlist(Geocache cache) { + @Override + public boolean removeFromWatchlist(Geocache cache) { final boolean removed = GCParser.removeFromWatchlist(cache); if (removed) { cgData.saveChangedCache(cache); @@ -218,6 +246,15 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, } @Override + public boolean uploadPersonalNote(Geocache cache) { + final boolean uploaded = GCParser.uploadPersonalNote(cache); + if (uploaded) { + cgData.saveChangedCache(cache); + } + return uploaded; + } + + @Override public SearchResult searchByCenter(Geopoint center) { // TODO make search by coordinate use this method. currently it is just a marker that this connector supports search by center return null; @@ -234,12 +271,15 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, } @Override - public CacheRealm getCacheRealm() { - return CacheRealm.GC; + public boolean isActivated() { + return Settings.isGCConnectorActive(); } @Override - public boolean isActivated() { - return true; + public int getCacheMapMarkerId(boolean disabled) { + if (disabled) { + return R.drawable.marker_disabled; + } + return R.drawable.marker; } } diff --git a/main/src/cgeo/geocaching/connector/gc/GCConstants.java b/main/src/cgeo/geocaching/connector/gc/GCConstants.java index b66acb7..f2e2e69 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCConstants.java +++ b/main/src/cgeo/geocaching/connector/gc/GCConstants.java @@ -84,6 +84,7 @@ public final class GCConstants { public final static String ERROR_TB_DOES_NOT_EXIST = "does not exist in the system"; public final static String ERROR_TB_ELEMENT_EXCEPTION = "ElementNotFound Exception"; public final static String ERROR_TB_ARITHMETIC_OVERFLOW = "operation resulted in an overflow"; + public final static String ERROR_TB_NOT_ACTIVATED = "hasn't been activated"; /** * some parts of the webpage don't correctly encode the name, therefore this pattern must be checked with a * trackable name that needs HTML encoding @@ -152,17 +153,11 @@ public final class GCConstants { public final static Pattern PATTERN_MAINTENANCE = Pattern.compile("<span id=\"ctl00_ContentBody_LogBookPanel1_lbConfirm\"[^>]*>([^<]*<font[^>]*>)?([^<]+)(</font>[^<]*)?</span>", Pattern.CASE_INSENSITIVE); public final static Pattern PATTERN_OK1 = Pattern.compile("<h2[^>]*>[^<]*<span id=\"ctl00_ContentBody_lbHeading\"[^>]*>[^<]*</span>[^<]*</h2>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); public final static Pattern PATTERN_OK2 = Pattern.compile("<div id=[\"|']ctl00_ContentBody_LogBookPanel1_ViewLogPanel[\"|']>", Pattern.CASE_INSENSITIVE); - public final static Pattern PATTERN_OK_IMAGEUPLOAD = Pattern.compile("<div id=[\"|']ctl00_ContentBody_ImageUploadControl1_uxUploadDonePanel[\"|']>", Pattern.CASE_INSENSITIVE); + public final static Pattern PATTERN_IMAGE_UPLOAD_URL = Pattern.compile("title=\"Click for Larger Image\"\\s*src=\"(.*?)\"", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); public final static Pattern PATTERN_VIEWSTATEFIELDCOUNT = Pattern.compile("id=\"__VIEWSTATEFIELDCOUNT\"[^(value)]+value=\"(\\d+)\"[^>]+>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); public final static Pattern PATTERN_VIEWSTATES = Pattern.compile("id=\"__VIEWSTATE(\\d*)\"[^(value)]+value=\"([^\"]+)\"[^>]+>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); public final static Pattern PATTERN_USERTOKEN = Pattern.compile("userToken\\s*=\\s*'([^']+)'"); - /** - * Patterns for GC and TB codes - */ - public final static Pattern PATTERN_GC_CODE = Pattern.compile("GC[0-9A-Z]+", Pattern.CASE_INSENSITIVE); - public final static Pattern PATTERN_TB_CODE = Pattern.compile("TB[0-9A-Z]+", Pattern.CASE_INSENSITIVE); - /** Live Map since 14.02.2012 */ public final static Pattern PATTERN_USERSESSION = Pattern.compile("UserSession\\('([^']+)'"); public final static Pattern PATTERN_SESSIONTOKEN = Pattern.compile("sessionToken:'([^']+)'"); @@ -194,7 +189,7 @@ public final class GCConstants { */ public static long gccodeToGCId(final String gccode) { long base = GC_BASE31; - String geocodeWO = gccode.substring(2).toUpperCase(Locale.US); + final String geocodeWO = gccode.substring(2).toUpperCase(Locale.US); if ((geocodeWO.length() < 4) || (geocodeWO.length() == 4 && SEQUENCE_GCID.indexOf(geocodeWO.charAt(0)) < 16)) { base = GC_BASE16; diff --git a/main/src/cgeo/geocaching/connector/gc/GCLoggingManager.java b/main/src/cgeo/geocaching/connector/gc/GCLoggingManager.java new file mode 100644 index 0000000..4f2f8c4 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/gc/GCLoggingManager.java @@ -0,0 +1,132 @@ +package cgeo.geocaching.connector.gc; + +import cgeo.geocaching.Geocache; +import cgeo.geocaching.R; +import cgeo.geocaching.Settings; +import cgeo.geocaching.TrackableLog; +import cgeo.geocaching.LogCacheActivity; +import cgeo.geocaching.activity.ActivityMixin; +import cgeo.geocaching.connector.ILoggingManager; +import cgeo.geocaching.connector.ImageResult; +import cgeo.geocaching.connector.LogResult; +import cgeo.geocaching.enumerations.LogType; +import cgeo.geocaching.enumerations.StatusCode; +import cgeo.geocaching.loaders.UrlLoader; +import cgeo.geocaching.network.Parameters; +import cgeo.geocaching.utils.Log; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; + +import android.app.Activity; +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.Loader; + +import java.util.Calendar; +import java.util.Collections; +import java.util.List; + +public class GCLoggingManager implements ILoggingManager, LoaderManager.LoaderCallbacks<String> { + + private final LogCacheActivity activity; + private final Geocache cache; + + private String[] viewstates; + private List<TrackableLog> trackables; + private List<LogType> possibleLogTypes; + private boolean hasLoaderError = true; + + public GCLoggingManager(Activity activity, Geocache cache) { + this.activity = (LogCacheActivity) activity; + this.cache = cache; + } + + @Override + public Loader<String> onCreateLoader(int arg0, Bundle arg1) { + if (!Settings.isLogin()) { // allow offline logging + ActivityMixin.showToast(activity, activity.getResources().getString(R.string.err_login)); + return null; + } + return new UrlLoader(activity.getBaseContext(), "http://www.geocaching.com/seek/log.aspx", new Parameters("ID", cache.getCacheId())); + } + + @Override + public void onLoadFinished(Loader<String> arg0, String page) { + + if (page == null) { + hasLoaderError = true; + } else { + + viewstates = Login.getViewstates(page); + trackables = GCParser.parseTrackableLog(page); + possibleLogTypes = GCParser.parseTypes(page); + + hasLoaderError = possibleLogTypes.isEmpty(); + } + + activity.onLoadFinished(); + } + + @Override + public void onLoaderReset(Loader<String> arg0) { + // nothing to do + } + + @Override + public void init() { + activity.getSupportLoaderManager().initLoader(0, null, this); + } + + @Override + public LogResult postLog(Geocache cache, LogType logType, Calendar date, String log, List<TrackableLog> trackableLogs) { + + try { + final ImmutablePair<StatusCode, String> postResult = GCParser.postLog(cache.getGeocode(), cache.getCacheId(), viewstates, logType, + date.get(Calendar.YEAR), (date.get(Calendar.MONTH) + 1), date.get(Calendar.DATE), + log, trackableLogs); + + return new LogResult(postResult.left, postResult.right); + } catch (Exception e) { + Log.e("GCLoggingManager.postLog", e); + } + + return new LogResult(StatusCode.LOG_POST_ERROR, ""); + } + + @Override + public ImageResult postLogImage(String logId, String imageCaption, String imageDescription, Uri imageUri) { + + if (StringUtils.isNotBlank(imageUri.getPath())) { + + ImmutablePair<StatusCode, String> imageResult = GCParser.uploadLogImage(logId, imageCaption, imageDescription, imageUri); + + return new ImageResult(imageResult.left, imageResult.right); + } + + return new ImageResult(StatusCode.LOGIMAGE_POST_ERROR, ""); + } + + @Override + public boolean hasLoaderError() { + return hasLoaderError; + } + + @Override + public List<TrackableLog> getTrackables() { + if (hasLoaderError) { + return Collections.emptyList(); + } + return trackables; + } + + @Override + public List<LogType> getPossibleLogTypes() { + if (hasLoaderError) { + return Collections.emptyList(); + } + return possibleLogTypes; + } + +} diff --git a/main/src/cgeo/geocaching/connector/gc/GCMap.java b/main/src/cgeo/geocaching/connector/gc/GCMap.java index 49f61ef..643caf5 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCMap.java +++ b/main/src/cgeo/geocaching/connector/gc/GCMap.java @@ -92,7 +92,7 @@ public class GCMap { JSONObject ownerObj = dataObject.getJSONObject("owner"); cache.setOwnerDisplayName(ownerObj.getString("text")); - result.addCache(cache); + result.addAndPutInCache(cache); } } catch (JSONException e) { @@ -231,13 +231,13 @@ public class GCMap { exclude = true; } if (!exclude) { - searchResult.addCache(cache); + searchResult.addAndPutInCache(cache); } } Log.d("Retrieved " + searchResult.getCount() + " caches for tile " + tile.toString()); } catch (Exception e) { - Log.e("GCBase.parseMapJSON", e); + Log.e("GCMap.parseMapJSON", e); } return searchResult; @@ -283,7 +283,7 @@ public class GCMap { * @return */ private static SearchResult searchByViewport(final Viewport viewport, final String[] tokens, Strategy strategy) { - Log.d("GCBase.searchByViewport" + viewport.toString()); + Log.d("GCMap.searchByViewport" + viewport.toString()); final SearchResult searchResult = new SearchResult(); searchResult.setUrl(GCConstants.URL_LIVE_MAP + "?ll=" + viewport.getCenter().getLatitude() + "," + viewport.getCenter().getLongitude()); @@ -330,11 +330,11 @@ public class GCMap { String data = Tile.requestMapInfo(GCConstants.URL_MAP_INFO, params, GCConstants.URL_LIVE_MAP); if (StringUtils.isEmpty(data)) { - Log.w("GCBase.searchByViewport: No data from server for tile (" + tile.getX() + "/" + tile.getY() + ")"); + Log.w("GCMap.searchByViewport: No data from server for tile (" + tile.getX() + "/" + tile.getY() + ")"); } else { final SearchResult search = GCMap.parseMapJSON(data, tile, bitmap, strategy); if (search == null || CollectionUtils.isEmpty(search.getGeocodes())) { - Log.e("GCBase.searchByViewport: No cache parsed for viewport " + viewport); + Log.e("GCMap.searchByViewport: No cache parsed for viewport " + viewport); } else { searchResult.addGeocodes(search.getGeocodes()); diff --git a/main/src/cgeo/geocaching/connector/gc/GCParser.java b/main/src/cgeo/geocaching/connector/gc/GCParser.java index 6b456fd..3e26eb2 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCParser.java +++ b/main/src/cgeo/geocaching/connector/gc/GCParser.java @@ -28,10 +28,10 @@ import cgeo.geocaching.loaders.RecaptchaReceiver; import cgeo.geocaching.network.Network; import cgeo.geocaching.network.Parameters; import cgeo.geocaching.ui.DirectionImage; -import cgeo.geocaching.utils.BaseUtils; import cgeo.geocaching.utils.CancellableHandler; import cgeo.geocaching.utils.Log; import cgeo.geocaching.utils.MatcherWrapper; +import cgeo.geocaching.utils.TextUtils; import ch.boye.httpclientandroidlib.HttpResponse; @@ -79,14 +79,14 @@ public abstract class GCParser { // recaptcha String recaptchaChallenge = null; if (showCaptcha) { - String recaptchaJsParam = BaseUtils.getMatch(page, GCConstants.PATTERN_SEARCH_RECAPTCHA, false, null); + final String recaptchaJsParam = TextUtils.getMatch(page, GCConstants.PATTERN_SEARCH_RECAPTCHA, false, null); if (recaptchaJsParam != null) { final Parameters params = new Parameters("k", recaptchaJsParam.trim()); final String recaptchaJs = Network.getResponseData(Network.getRequest("http://www.google.com/recaptcha/api/challenge", params)); if (StringUtils.isNotBlank(recaptchaJs)) { - recaptchaChallenge = BaseUtils.getMatch(recaptchaJs, GCConstants.PATTERN_SEARCH_RECAPTCHACHALLENGE, true, 1, null, true); + recaptchaChallenge = TextUtils.getMatch(recaptchaJs, GCConstants.PATTERN_SEARCH_RECAPTCHACHALLENGE, true, 1, null, true); } } if (thread != null && StringUtils.isNotBlank(recaptchaChallenge)) { @@ -109,7 +109,7 @@ public abstract class GCParser { page = page.substring(startPos); // cut on <table startPos = page.indexOf('>'); - int endPos = page.indexOf("ctl00_ContentBody_UnitTxt"); + final int endPos = page.indexOf("ctl00_ContentBody_UnitTxt"); if (startPos == -1 || endPos == -1) { Log.e("GCParser.parseSearch: ID \"ctl00_ContentBody_UnitTxt\" not found on page"); return null; @@ -122,7 +122,7 @@ public abstract class GCParser { for (int z = 1; z < rows_count; z++) { final Geocache cache = new Geocache(); - String row = rows[z]; + final String row = rows[z]; // check for cache type presence if (!row.contains("images/wpttypes")) { @@ -150,7 +150,7 @@ public abstract class GCParser { } } } - } catch (Exception e) { + } catch (final Exception e) { // failed to parse GUID and/or Disabled Log.w("GCParser.parseSearch: Failed to parse GUID and/or Disabled data"); } @@ -160,21 +160,21 @@ public abstract class GCParser { continue; } - cache.setGeocode(BaseUtils.getMatch(row, GCConstants.PATTERN_SEARCH_GEOCODE, true, 1, cache.getGeocode(), true)); + cache.setGeocode(TextUtils.getMatch(row, GCConstants.PATTERN_SEARCH_GEOCODE, true, 1, cache.getGeocode(), true)); // cache type - cache.setType(CacheType.getByPattern(BaseUtils.getMatch(row, GCConstants.PATTERN_SEARCH_TYPE, true, 1, null, true))); + cache.setType(CacheType.getByPattern(TextUtils.getMatch(row, GCConstants.PATTERN_SEARCH_TYPE, true, 1, null, true))); // cache direction - image if (Settings.getLoadDirImg()) { - final String direction = BaseUtils.getMatch(row, GCConstants.PATTERN_SEARCH_DIRECTION_DISTANCE, false, 1, null, false); + final String direction = TextUtils.getMatch(row, GCConstants.PATTERN_SEARCH_DIRECTION_DISTANCE, false, 1, null, false); if (direction != null) { cache.setDirectionImg(direction); } } // cache distance - estimated distance for basic members - final String distance = BaseUtils.getMatch(row, GCConstants.PATTERN_SEARCH_DIRECTION_DISTANCE, false, 2, null, false); + final String distance = TextUtils.getMatch(row, GCConstants.PATTERN_SEARCH_DIRECTION_DISTANCE, false, 2, null, false); if (distance != null) { cache.setDistance(DistanceParser.parseDistance(distance, Settings.isUseMetricUnits())); } @@ -193,7 +193,7 @@ public abstract class GCParser { } // size - final String container = BaseUtils.getMatch(row, GCConstants.PATTERN_SEARCH_CONTAINER, false, 1, null, false); + final String container = TextUtils.getMatch(row, GCConstants.PATTERN_SEARCH_CONTAINER, false, 1, null, false); cache.setSize(CacheSize.getById(container)); // cache inventory @@ -203,7 +203,7 @@ public abstract class GCParser { if (matcherTbs.groupCount() > 0) { try { cache.setInventoryItems(Integer.parseInt(matcherTbs.group(1))); - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { Log.e("Error parsing trackables count", e); } inventoryPre = matcherTbs.group(2); @@ -229,7 +229,7 @@ public abstract class GCParser { cache.setFound(row.contains("/images/icons/16/found.png")); // id - String result = BaseUtils.getMatch(row, GCConstants.PATTERN_SEARCH_ID, null); + String result = TextUtils.getMatch(row, GCConstants.PATTERN_SEARCH_ID, null); if (null != result) { cache.setCacheId(result); cids.add(cache.getCacheId()); @@ -237,24 +237,24 @@ public abstract class GCParser { // favorite count try { - result = BaseUtils.getMatch(row, GCConstants.PATTERN_SEARCH_FAVORITE, false, 1, null, true); + result = TextUtils.getMatch(row, GCConstants.PATTERN_SEARCH_FAVORITE, false, 1, null, true); if (null != result) { cache.setFavoritePoints(Integer.parseInt(result)); } - } catch (NumberFormatException e) { - Log.w("GCParser.parseSearch: Failed to parse favourite count"); + } catch (final NumberFormatException e) { + Log.w("GCParser.parseSearch: Failed to parse favorite count"); } - searchResult.addCache(cache); + searchResult.addAndPutInCache(cache); } // total caches found try { - String result = BaseUtils.getMatch(page, GCConstants.PATTERN_SEARCH_TOTALCOUNT, false, 1, null, true); + final String result = TextUtils.getMatch(page, GCConstants.PATTERN_SEARCH_TOTALCOUNT, false, 1, null, true); if (null != result) { searchResult.setTotal(Integer.parseInt(result)); } - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { Log.w("GCParser.parseSearch: Failed to parse cache count"); } @@ -284,7 +284,7 @@ public abstract class GCParser { params.put("__VIEWSTATEFIELDCOUNT", String.valueOf(searchResult.viewstates.length)); } } - for (String cid : cids) { + for (final String cid : cids) { params.put("CID", cid); } @@ -308,7 +308,7 @@ public abstract class GCParser { LocParser.parseLoc(searchResult, coordinates); - } catch (Exception e) { + } catch (final Exception e) { Log.e("GCParser.parseSearch.CIDs", e); } } @@ -316,7 +316,7 @@ public abstract class GCParser { // get direction images if (Settings.getLoadDirImg()) { final Set<Geocache> caches = searchResult.getCachesFromSearchResult(LoadFlags.LOAD_CACHE_OR_DB); - for (Geocache cache : caches) { + for (final Geocache cache : caches) { if (cache.getCoords() == null && StringUtils.isNotEmpty(cache.getDirectionImg())) { DirectionImage.getDrawable(cache.getDirectionImg()); } @@ -327,15 +327,17 @@ public abstract class GCParser { } private static Float parseStars(final String value) { - float floatValue = Float.parseFloat(StringUtils.replaceChars(value, ',', '.')); + final float floatValue = Float.parseFloat(StringUtils.replaceChars(value, ',', '.')); return floatValue >= 0.5 && floatValue <= 5.0 ? floatValue : null; } static SearchResult parseCache(final String page, final CancellableHandler handler) { final SearchResult searchResult = parseCacheFromText(page, handler); + // attention: parseCacheFromText already stores implicitely through searchResult.addCache if (searchResult != null && !searchResult.getGeocodes().isEmpty()) { final Geocache cache = searchResult.getFirstCacheFromResult(LoadFlags.LOAD_CACHE_OR_DB); getExtraOnlineInfo(cache, page, handler); + // too late: it is already stored through parseCacheFromText cache.setDetailedUpdatedNow(); if (CancellableHandler.isCancelled(handler)) { return null; @@ -372,7 +374,7 @@ public abstract class GCParser { return searchResult; } - final String cacheName = Html.fromHtml(BaseUtils.getMatch(page, GCConstants.PATTERN_NAME, true, "")).toString(); + final String cacheName = Html.fromHtml(TextUtils.getMatch(page, GCConstants.PATTERN_NAME, true, "")).toString(); if (GCConstants.STRING_UNKNOWN_ERROR.equalsIgnoreCase(cacheName)) { searchResult.setError(StatusCode.UNKNOWN_ERROR); return searchResult; @@ -383,30 +385,30 @@ public abstract class GCParser { cache.setArchived(page.contains(GCConstants.STRING_ARCHIVED)); - cache.setPremiumMembersOnly(BaseUtils.matches(page, GCConstants.PATTERN_PREMIUMMEMBERS)); + cache.setPremiumMembersOnly(TextUtils.matches(page, GCConstants.PATTERN_PREMIUMMEMBERS)); - cache.setFavorite(BaseUtils.matches(page, GCConstants.PATTERN_FAVORITE)); + cache.setFavorite(TextUtils.matches(page, GCConstants.PATTERN_FAVORITE)); // cache geocode - cache.setGeocode(BaseUtils.getMatch(page, GCConstants.PATTERN_GEOCODE, true, cache.getGeocode())); + cache.setGeocode(TextUtils.getMatch(page, GCConstants.PATTERN_GEOCODE, true, cache.getGeocode())); // cache id - cache.setCacheId(BaseUtils.getMatch(page, GCConstants.PATTERN_CACHEID, true, cache.getCacheId())); + cache.setCacheId(TextUtils.getMatch(page, GCConstants.PATTERN_CACHEID, true, cache.getCacheId())); // cache guid - cache.setGuid(BaseUtils.getMatch(page, GCConstants.PATTERN_GUID, true, cache.getGuid())); + cache.setGuid(TextUtils.getMatch(page, GCConstants.PATTERN_GUID, true, cache.getGuid())); // name cache.setName(cacheName); // owner real name - cache.setOwnerUserId(Network.decode(BaseUtils.getMatch(page, GCConstants.PATTERN_OWNER_USERID, true, cache.getOwnerUserId()))); + cache.setOwnerUserId(Network.decode(TextUtils.getMatch(page, GCConstants.PATTERN_OWNER_USERID, true, cache.getOwnerUserId()))); cache.setUserModifiedCoords(false); String tableInside = page; - int pos = tableInside.indexOf(GCConstants.STRING_CACHEDETAILS); + final int pos = tableInside.indexOf(GCConstants.STRING_CACHEDETAILS); if (pos == -1) { Log.e("GCParser.parseCache: ID \"cacheDetails\" not found on page"); return null; @@ -416,96 +418,96 @@ public abstract class GCParser { if (StringUtils.isNotBlank(tableInside)) { // cache terrain - String result = BaseUtils.getMatch(tableInside, GCConstants.PATTERN_TERRAIN, true, null); + String result = TextUtils.getMatch(tableInside, GCConstants.PATTERN_TERRAIN, true, null); if (result != null) { try { cache.setTerrain(Float.parseFloat(StringUtils.replaceChars(result, '_', '.'))); - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { Log.e("Error parsing terrain value", e); } } // cache difficulty - result = BaseUtils.getMatch(tableInside, GCConstants.PATTERN_DIFFICULTY, true, null); + result = TextUtils.getMatch(tableInside, GCConstants.PATTERN_DIFFICULTY, true, null); if (result != null) { try { cache.setDifficulty(Float.parseFloat(StringUtils.replaceChars(result, '_', '.'))); - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { Log.e("Error parsing difficulty value", e); } } // owner - cache.setOwnerDisplayName(StringEscapeUtils.unescapeHtml4(BaseUtils.getMatch(tableInside, GCConstants.PATTERN_OWNER_DISPLAYNAME, true, cache.getOwnerDisplayName()))); + cache.setOwnerDisplayName(StringEscapeUtils.unescapeHtml4(TextUtils.getMatch(tableInside, GCConstants.PATTERN_OWNER_DISPLAYNAME, true, cache.getOwnerDisplayName()))); // hidden try { - String hiddenString = BaseUtils.getMatch(tableInside, GCConstants.PATTERN_HIDDEN, true, null); + String hiddenString = TextUtils.getMatch(tableInside, GCConstants.PATTERN_HIDDEN, true, null); if (StringUtils.isNotBlank(hiddenString)) { cache.setHidden(Login.parseGcCustomDate(hiddenString)); } if (cache.getHiddenDate() == null) { // event date - hiddenString = BaseUtils.getMatch(tableInside, GCConstants.PATTERN_HIDDENEVENT, true, null); + hiddenString = TextUtils.getMatch(tableInside, GCConstants.PATTERN_HIDDENEVENT, true, null); if (StringUtils.isNotBlank(hiddenString)) { cache.setHidden(Login.parseGcCustomDate(hiddenString)); } } - } catch (ParseException e) { + } catch (final ParseException e) { // failed to parse cache hidden date Log.w("GCParser.parseCache: Failed to parse cache hidden (event) date"); } - // favourite + // favorite try { - cache.setFavoritePoints(Integer.parseInt(BaseUtils.getMatch(tableInside, GCConstants.PATTERN_FAVORITECOUNT, true, "0"))); - } catch (NumberFormatException e) { - Log.e("Error parsing favourite count", e); + cache.setFavoritePoints(Integer.parseInt(TextUtils.getMatch(tableInside, GCConstants.PATTERN_FAVORITECOUNT, true, "0"))); + } catch (final NumberFormatException e) { + Log.e("Error parsing favorite count", e); } // cache size - cache.setSize(CacheSize.getById(BaseUtils.getMatch(tableInside, GCConstants.PATTERN_SIZE, true, CacheSize.NOT_CHOSEN.id))); + cache.setSize(CacheSize.getById(TextUtils.getMatch(tableInside, GCConstants.PATTERN_SIZE, true, CacheSize.NOT_CHOSEN.id))); } // cache found - cache.setFound(BaseUtils.matches(page, GCConstants.PATTERN_FOUND) || BaseUtils.matches(page, GCConstants.PATTERN_FOUND_ALTERNATIVE)); + cache.setFound(TextUtils.matches(page, GCConstants.PATTERN_FOUND) || TextUtils.matches(page, GCConstants.PATTERN_FOUND_ALTERNATIVE)); // cache found date try { - final String foundDateString = BaseUtils.getMatch(page, GCConstants.PATTERN_FOUND_DATE, true, null); + final String foundDateString = TextUtils.getMatch(page, GCConstants.PATTERN_FOUND_DATE, true, null); if (StringUtils.isNotBlank(foundDateString)) { cache.setVisitedDate(Login.parseGcCustomDate(foundDateString).getTime()); } - } catch (ParseException e) { + } catch (final ParseException e) { // failed to parse cache found date Log.w("GCParser.parseCache: Failed to parse cache found date"); } // cache type - cache.setType(CacheType.getByPattern(BaseUtils.getMatch(page, GCConstants.PATTERN_TYPE, true, cache.getType().id))); + cache.setType(CacheType.getByPattern(TextUtils.getMatch(page, GCConstants.PATTERN_TYPE, true, cache.getType().id))); // on watchlist - cache.setOnWatchlist(BaseUtils.matches(page, GCConstants.PATTERN_WATCHLIST)); + cache.setOnWatchlist(TextUtils.matches(page, GCConstants.PATTERN_WATCHLIST)); // latitude and longitude. Can only be retrieved if user is logged in - String latlon = BaseUtils.getMatch(page, GCConstants.PATTERN_LATLON, true, ""); + String latlon = TextUtils.getMatch(page, GCConstants.PATTERN_LATLON, true, ""); if (StringUtils.isNotEmpty(latlon)) { try { cache.setCoords(new Geopoint(latlon)); cache.setReliableLatLon(true); - } catch (Geopoint.GeopointException e) { + } catch (final Geopoint.GeopointException e) { Log.w("GCParser.parseCache: Failed to parse cache coordinates", e); } } // cache location - cache.setLocation(BaseUtils.getMatch(page, GCConstants.PATTERN_LOCATION, true, "")); + cache.setLocation(TextUtils.getMatch(page, GCConstants.PATTERN_LOCATION, true, "")); // cache hint - String result = BaseUtils.getMatch(page, GCConstants.PATTERN_HINT, false, null); + final String result = TextUtils.getMatch(page, GCConstants.PATTERN_HINT, false, null); if (result != null) { // replace linebreak and paragraph tags - String hint = GCConstants.PATTERN_LINEBREAK.matcher(result).replaceAll("\n"); + final String hint = GCConstants.PATTERN_LINEBREAK.matcher(result).replaceAll("\n"); if (hint != null) { cache.setHint(StringUtils.replace(hint, "</p>", "").trim()); } @@ -514,17 +516,17 @@ public abstract class GCParser { cache.checkFields(); // cache personal note - cache.setPersonalNote(BaseUtils.getMatch(page, GCConstants.PATTERN_PERSONALNOTE, true, cache.getPersonalNote())); + cache.setPersonalNote(TextUtils.getMatch(page, GCConstants.PATTERN_PERSONALNOTE, true, cache.getPersonalNote())); // cache short description - cache.setShortDescription(BaseUtils.getMatch(page, GCConstants.PATTERN_SHORTDESC, true, "")); + cache.setShortDescription(TextUtils.getMatch(page, GCConstants.PATTERN_SHORTDESC, true, "")); // cache description - cache.setDescription(BaseUtils.getMatch(page, GCConstants.PATTERN_DESC, true, "")); + cache.setDescription(TextUtils.getMatch(page, GCConstants.PATTERN_DESC, true, "")); // cache attributes try { - final String attributesPre = BaseUtils.getMatch(page, GCConstants.PATTERN_ATTRIBUTES, true, null); + final String attributesPre = TextUtils.getMatch(page, GCConstants.PATTERN_ATTRIBUTES, true, null); if (null != attributesPre) { final MatcherWrapper matcherAttributesInside = new MatcherWrapper(GCConstants.PATTERN_ATTRIBUTESINSIDE, attributesPre); @@ -537,8 +539,8 @@ public abstract class GCParser { // if the image name can be recognized, use the image name as attribute final String imageName = matcherAttributesInside.group(1).trim(); if (StringUtils.isNotEmpty(imageName)) { - int start = imageName.lastIndexOf('/'); - int end = imageName.lastIndexOf('.'); + final int start = imageName.lastIndexOf('/'); + final int end = imageName.lastIndexOf('.'); if (start >= 0 && end >= 0) { attribute = imageName.substring(start + 1, end).replace('-', '_').toLowerCase(Locale.US); } @@ -548,7 +550,7 @@ public abstract class GCParser { } cache.setAttributes(attributes); } - } catch (Exception e) { + } catch (final Exception e) { // failed to parse cache attributes Log.w("GCParser.parseCache: Failed to parse cache attributes"); } @@ -565,7 +567,7 @@ public abstract class GCParser { while (matcherSpoilersInside.find()) { // the original spoiler URL (include .../display/... contains a low-resolution image // if we shorten the URL we get the original-resolution image - String url = matcherSpoilersInside.group(1).replace("/display", ""); + final String url = matcherSpoilersInside.group(1).replace("/display", ""); String title = null; if (matcherSpoilersInside.group(3) != null) { @@ -577,7 +579,7 @@ public abstract class GCParser { } cache.addSpoiler(new Image(url, title, description)); } - } catch (Exception e) { + } catch (final Exception e) { // failed to parse cache spoilers Log.w("GCParser.parseCache: Failed to parse cache spoilers"); } @@ -611,20 +613,20 @@ public abstract class GCParser { } } } - } catch (Exception e) { + } catch (final Exception e) { // failed to parse cache inventory Log.w("GCParser.parseCache: Failed to parse cache inventory (2)"); } // cache logs counts try { - final String countlogs = BaseUtils.getMatch(page, GCConstants.PATTERN_COUNTLOGS, true, null); + final String countlogs = TextUtils.getMatch(page, GCConstants.PATTERN_COUNTLOGS, true, null); if (null != countlogs) { final MatcherWrapper matcherLog = new MatcherWrapper(GCConstants.PATTERN_COUNTLOG, countlogs); while (matcherLog.find()) { - String typeStr = matcherLog.group(1); - String countStr = matcherLog.group(2).replaceAll("[.,]", ""); + final String typeStr = matcherLog.group(1); + final String countStr = matcherLog.group(2).replaceAll("[.,]", ""); if (StringUtils.isNotBlank(typeStr) && LogType.UNKNOWN != LogType.getByIconName(typeStr) @@ -633,7 +635,7 @@ public abstract class GCParser { } } } - } catch (Exception e) { + } catch (final Exception e) { // failed to parse logs Log.w("GCParser.parseCache: Failed to parse cache log count"); } @@ -643,7 +645,7 @@ public abstract class GCParser { // add waypoint for original coordinates in case of user-modified listing-coordinates try { - final String originalCoords = BaseUtils.getMatch(page, GCConstants.PATTERN_LATLON_ORIG, false, null); + final String originalCoords = TextUtils.getMatch(page, GCConstants.PATTERN_LATLON_ORIG, false, null); if (null != originalCoords) { final Waypoint waypoint = new Waypoint(cgeoapplication.getInstance().getString(R.string.cache_coordinates_original), WaypointType.ORIGINAL, false); @@ -651,7 +653,7 @@ public abstract class GCParser { cache.addOrChangeWaypoint(waypoint, false); cache.setUserModifiedCoords(true); } - } catch (Geopoint.GeopointException e) { + } catch (final Geopoint.GeopointException e) { } int wpBegin = page.indexOf("<table class=\"Table\" id=\"ctl00_ContentBody_Waypoints\">"); @@ -685,21 +687,21 @@ public abstract class GCParser { // waypoint name // res is null during the unit tests - final String name = BaseUtils.getMatch(wp[6], GCConstants.PATTERN_WPNAME, true, 1, cgeoapplication.getInstance().getString(R.string.waypoint), true); + final String name = TextUtils.getMatch(wp[6], GCConstants.PATTERN_WPNAME, true, 1, cgeoapplication.getInstance().getString(R.string.waypoint), true); // waypoint type - final String resulttype = BaseUtils.getMatch(wp[3], GCConstants.PATTERN_WPTYPE, null); + final String resulttype = TextUtils.getMatch(wp[3], GCConstants.PATTERN_WPTYPE, null); final Waypoint waypoint = new Waypoint(name, WaypointType.findById(resulttype), false); // waypoint prefix - waypoint.setPrefix(BaseUtils.getMatch(wp[4], GCConstants.PATTERN_WPPREFIXORLOOKUPORLATLON, true, 2, waypoint.getPrefix(), false)); + waypoint.setPrefix(TextUtils.getMatch(wp[4], GCConstants.PATTERN_WPPREFIXORLOOKUPORLATLON, true, 2, waypoint.getPrefix(), false)); // waypoint lookup - waypoint.setLookup(BaseUtils.getMatch(wp[5], GCConstants.PATTERN_WPPREFIXORLOOKUPORLATLON, true, 2, waypoint.getLookup(), false)); + waypoint.setLookup(TextUtils.getMatch(wp[5], GCConstants.PATTERN_WPPREFIXORLOOKUPORLATLON, true, 2, waypoint.getLookup(), false)); // waypoint latitude and logitude - latlon = Html.fromHtml(BaseUtils.getMatch(wp[7], GCConstants.PATTERN_WPPREFIXORLOOKUPORLATLON, false, 2, "", false)).toString().trim(); + latlon = Html.fromHtml(TextUtils.getMatch(wp[7], GCConstants.PATTERN_WPPREFIXORLOOKUPORLATLON, false, 2, "", false)).toString().trim(); if (!StringUtils.startsWith(latlon, "???")) { waypoint.setLatlon(latlon); waypoint.setCoords(new Geopoint(latlon)); @@ -711,7 +713,7 @@ public abstract class GCParser { } // waypoint note - waypoint.setNote(BaseUtils.getMatch(wp[3], GCConstants.PATTERN_WPNOTE, waypoint.getNote())); + waypoint.setNote(TextUtils.getMatch(wp[3], GCConstants.PATTERN_WPNOTE, waypoint.getNote())); cache.addOrChangeWaypoint(waypoint, false); } @@ -729,7 +731,8 @@ public abstract class GCParser { return searchResult; } - searchResult.addCache(cache); + cache.setDetailedUpdatedNow(); + searchResult.addAndPutInCache(cache); return searchResult; } @@ -782,7 +785,7 @@ public abstract class GCParser { // save to application search.setError(searchResult.getError()); search.setViewstates(searchResult.viewstates); - for (String geocode : searchResult.getGeocodes()) { + for (final String geocode : searchResult.getGeocodes()) { search.addGeocode(geocode); } return search; @@ -893,7 +896,7 @@ public abstract class GCParser { return null; } try { - JSONObject response = Network.requestJSON("http://www.geocaching.com/api/geocode", new Parameters("q", address)); + final JSONObject response = Network.requestJSON("http://www.geocaching.com/api/geocode", new Parameters("q", address)); if (response == null) { return null; } @@ -903,12 +906,12 @@ public abstract class GCParser { if (!response.has("data")) { return null; } - JSONObject data = response.getJSONObject("data"); + final JSONObject data = response.getJSONObject("data"); if (data == null) { return null; } return searchByCoords(new Geopoint(data.getDouble("lat"), data.getDouble("lng")), cacheType, showCaptcha, recaptchaReceiver); - } catch (JSONException e) { + } catch (final JSONException e) { Log.w("GCParser.searchByAddress", e); } @@ -1005,7 +1008,7 @@ public abstract class GCParser { final String uri = new Uri.Builder().scheme("http").authority("www.geocaching.com").path("/seek/log.aspx").encodedQuery("ID=" + cacheid).build().toString(); String page = Login.postRequestLogged(uri, params); if (!Login.getLoginStatus(page)) { - Log.e("GCParser.postLogTrackable: Can not log in geocaching"); + Log.e("GCParser.postLog: Cannot log in geocaching"); return new ImmutablePair<StatusCode, String>(StatusCode.NOT_LOGGED_IN, ""); } @@ -1033,7 +1036,7 @@ public abstract class GCParser { if (trackables != null && !trackables.isEmpty()) { // we have some trackables to proceed final StringBuilder hdnSelected = new StringBuilder(); - for (TrackableLog tb : trackables) { + for (final TrackableLog tb : trackables) { final String action = Integer.toString(tb.id) + tb.action.action; final StringBuilder paramText = new StringBuilder("ctl00$ContentBody$LogBookPanel1$uxTrackables$repTravelBugs$ctl"); @@ -1054,7 +1057,7 @@ public abstract class GCParser { page = Network.getResponseData(Network.postRequest(uri, params)); } - } catch (Exception e) { + } catch (final Exception e) { Log.e("GCParser.postLog.confim", e); } @@ -1074,11 +1077,11 @@ public abstract class GCParser { Login.setActualCachesFound(Login.getActualCachesFound() + 1); } - final String logID = BaseUtils.getMatch(page, GCConstants.PATTERN_LOG_IMAGE_UPLOAD, ""); + final String logID = TextUtils.getMatch(page, GCConstants.PATTERN_LOG_IMAGE_UPLOAD, ""); return new ImmutablePair<StatusCode, String>(StatusCode.NO_ERROR, logID); } - } catch (Exception e) { + } catch (final Exception e) { Log.e("GCParser.postLog.check", e); } @@ -1099,20 +1102,13 @@ public abstract class GCParser { * the URI for the image to be uploaded * @return status code to indicate success or failure */ - public static StatusCode uploadLogImage(final String logId, final String caption, final String description, final Uri imageUri) { - final String uri = new Uri.Builder().scheme("http").authority("www.geocaching.com").path("/seek/upload.aspx").encodedQuery("LID=" + logId).build().toString(); - - String page = Network.getResponseData(Network.getRequest(uri)); + public static ImmutablePair<StatusCode, String> uploadLogImage(final String logId, final String caption, final String description, final Uri imageUri) { + final String uri = new Uri.Builder().scheme("http").authority("www.geocaching.com").path("/seek/upload.aspx").build().toString(); - if (!Login.getLoginStatus(page)) { - // Login.isActualLoginStatus() was wrong, we are not logged in - final StatusCode loginState = Login.login(); - if (loginState == StatusCode.NO_ERROR) { - page = Network.getResponseData(Network.getRequest(uri)); - } else { - Log.e("Image upload: No login (error: " + loginState + ')'); - return StatusCode.NOT_LOGGED_IN; - } + final String page = Login.getRequestLogged(uri, new Parameters("LID=", logId)); + if (StringUtils.isBlank(page)) { + Log.e("GCParser.uploadLogImage: No data from server"); + return new ImmutablePair<StatusCode, String>(StatusCode.UNKNOWN_ERROR, null); } final String[] viewstates = Login.getViewstates(page); @@ -1128,18 +1124,23 @@ public abstract class GCParser { final File image = new File(imageUri.getPath()); final String response = Network.getResponseData(Network.postRequest(uri, uploadParams, "ctl00$ContentBody$ImageUploadControl1$uxFileUpload", "image/jpeg", image)); - MatcherWrapper matcherOK = new MatcherWrapper(GCConstants.PATTERN_OK_IMAGEUPLOAD, response); + final MatcherWrapper matcherUrl = new MatcherWrapper(GCConstants.PATTERN_IMAGE_UPLOAD_URL, response); - if (matcherOK.find()) { + if (matcherUrl.find()) { Log.i("Logimage successfully uploaded."); - - return StatusCode.NO_ERROR; + final String uploadedImageUrl = matcherUrl.group(1); + return ImmutablePair.of(StatusCode.NO_ERROR, uploadedImageUrl); } Log.e("GCParser.uploadLogIMage: Failed to upload image because of unknown error"); - return StatusCode.LOGIMAGE_POST_ERROR; + return ImmutablePair.of(StatusCode.LOGIMAGE_POST_ERROR, null); } + /** + * Post a log to GC.com. + * + * @return status code of the upload and ID of the log + */ public static StatusCode postLogTrackable(final String tbid, final String trackingCode, final String[] viewstates, final LogType logType, final int year, final int month, final int day, final String log) { if (Login.isEmpty(viewstates)) { @@ -1187,7 +1188,7 @@ public abstract class GCParser { final String uri = new Uri.Builder().scheme("http").authority("www.geocaching.com").path("/track/log.aspx").encodedQuery("wid=" + tbid).build().toString(); final String page = Login.postRequestLogged(uri, params); if (!Login.getLoginStatus(page)) { - Log.e("GCParser.postLogTrackable: Can not log in geocaching"); + Log.e("GCParser.postLogTrackable: Cannot log in geocaching"); return StatusCode.NOT_LOGGED_IN; } @@ -1198,7 +1199,7 @@ public abstract class GCParser { Log.i("Log successfully posted to trackable #" + trackingCode); return StatusCode.NO_ERROR; } - } catch (Exception e) { + } catch (final Exception e) { Log.e("GCParser.postLogTrackable.check", e); } @@ -1215,14 +1216,14 @@ public abstract class GCParser { */ static boolean addToWatchlist(final Geocache cache) { final String uri = "http://www.geocaching.com/my/watchlist.aspx?w=" + cache.getCacheId(); - String page = Login.postRequestLogged(uri, null); + final String page = Login.postRequestLogged(uri, null); if (StringUtils.isBlank(page)) { Log.e("GCParser.addToWatchlist: No data from server"); return false; // error } - boolean guidOnPage = cache.isGuidContainedInPage(page); + final boolean guidOnPage = cache.isGuidContainedInPage(page); if (guidOnPage) { Log.i("GCParser.addToWatchlist: cache is on watchlist"); cache.setOnWatchlist(true); @@ -1256,7 +1257,7 @@ public abstract class GCParser { Login.transferViewstates(page, params); page = Network.getResponseData(Network.postRequest(uri, params)); - boolean guidOnPage = cache.isGuidContainedInPage(page); + final boolean guidOnPage = cache.isGuidContainedInPage(page); if (!guidOnPage) { Log.i("GCParser.removeFromWatchlist: cache removed from watchlist"); cache.setOnWatchlist(false); @@ -1294,14 +1295,14 @@ public abstract class GCParser { private static boolean changeFavorite(final Geocache cache, final boolean add) { final String page = requestHtmlPage(cache.getGeocode(), null, "n", "0"); - final String userToken = BaseUtils.getMatch(page, GCConstants.PATTERN_USERTOKEN, ""); + final String userToken = TextUtils.getMatch(page, GCConstants.PATTERN_USERTOKEN, ""); if (StringUtils.isEmpty(userToken)) { return false; } final String uri = "http://www.geocaching.com/datastore/favorites.svc/update?u=" + userToken + "&f=" + Boolean.toString(add); - HttpResponse response = Network.postRequest(uri, null); + final HttpResponse response = Network.postRequest(uri, null); if (response != null && response.getStatusLine().getStatusCode() == 200) { Log.i("GCParser.changeFavorite: cache added/removed to/from favorites"); @@ -1330,7 +1331,7 @@ public abstract class GCParser { * Parse a trackable HTML description into a Trackable object * * @param page - * the HTML page to parse, already processed through {@link BaseUtils#replaceWhitespace} + * the HTML page to parse, already processed through {@link TextUtils#replaceWhitespace} * @return the parsed trackable, or null if none could be parsed */ static Trackable parseTrackable(final String page, final String possibleTrackingcode) { @@ -1346,20 +1347,20 @@ public abstract class GCParser { final Trackable trackable = new Trackable(); // trackable geocode - trackable.setGeocode(BaseUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_GEOCODE, true, trackable.getGeocode())); + trackable.setGeocode(TextUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_GEOCODE, true, StringUtils.upperCase(possibleTrackingcode))); // trackable id - trackable.setGuid(BaseUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_GUID, true, trackable.getGuid())); + trackable.setGuid(TextUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_GUID, true, trackable.getGuid())); // trackable icon - trackable.setIconUrl(BaseUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_ICON, true, trackable.getIconUrl())); + trackable.setIconUrl(TextUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_ICON, true, trackable.getIconUrl())); // trackable name - trackable.setName(Html.fromHtml(BaseUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_NAME, true, "")).toString()); + trackable.setName(Html.fromHtml(TextUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_NAME, true, "")).toString()); // trackable type if (StringUtils.isNotBlank(trackable.getName())) { - trackable.setType(BaseUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_TYPE, true, trackable.getType())); + trackable.setType(TextUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_TYPE, true, trackable.getType())); } // trackable owner name @@ -1369,13 +1370,13 @@ public abstract class GCParser { trackable.setOwnerGuid(matcherOwner.group(1)); trackable.setOwner(matcherOwner.group(2).trim()); } - } catch (Exception e) { + } catch (final Exception e) { // failed to parse trackable owner name Log.w("GCParser.parseTrackable: Failed to parse trackable owner name"); } // trackable origin - trackable.setOrigin(BaseUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_ORIGIN, true, trackable.getOrigin())); + trackable.setOrigin(TextUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_ORIGIN, true, trackable.getOrigin())); // trackable spotted try { @@ -1393,39 +1394,43 @@ public abstract class GCParser { trackable.setSpottedType(Trackable.SPOTTED_USER); } - if (BaseUtils.matches(page, GCConstants.PATTERN_TRACKABLE_SPOTTEDUNKNOWN)) { + if (TextUtils.matches(page, GCConstants.PATTERN_TRACKABLE_SPOTTEDUNKNOWN)) { trackable.setSpottedType(Trackable.SPOTTED_UNKNOWN); } - if (BaseUtils.matches(page, GCConstants.PATTERN_TRACKABLE_SPOTTEDOWNER)) { + if (TextUtils.matches(page, GCConstants.PATTERN_TRACKABLE_SPOTTEDOWNER)) { trackable.setSpottedType(Trackable.SPOTTED_OWNER); } - } catch (Exception e) { + } catch (final Exception e) { // failed to parse trackable last known place Log.w("GCParser.parseTrackable: Failed to parse trackable last known place"); } // released date - can be missing on the page try { - String releaseString = BaseUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_RELEASES, false, null); + final String releaseString = TextUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_RELEASES, false, null); if (releaseString != null) { trackable.setReleased(dateTbIn1.parse(releaseString)); if (trackable.getReleased() == null) { trackable.setReleased(dateTbIn2.parse(releaseString)); } } - } catch (ParseException e1) { + } catch (final ParseException e1) { trackable.setReleased(null); } // trackable distance - final String distance = BaseUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_DISTANCE, false, null); + final String distance = TextUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_DISTANCE, false, null); if (null != distance) { - trackable.setDistance(DistanceParser.parseDistance(distance, Settings.isUseMetricUnits())); + try { + trackable.setDistance(DistanceParser.parseDistance(distance, Settings.isUseMetricUnits())); + } catch (final NumberFormatException e) { + Log.e("GCParser.parseTrackable: Failed to parse distance", e); + } } // trackable goal - trackable.setGoal(convertLinks(BaseUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_GOAL, true, trackable.getGoal()))); + trackable.setGoal(convertLinks(TextUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_GOAL, true, trackable.getGoal()))); // trackable details & image try { @@ -1441,10 +1446,13 @@ public abstract class GCParser { trackable.setDetails(convertLinks(details)); } } - } catch (Exception e) { + } catch (final Exception e) { // failed to parse trackable details & image Log.w("GCParser.parseTrackable: Failed to parse trackable details & image"); } + if (StringUtils.isEmpty(trackable.getDetails()) && page.contains(GCConstants.ERROR_TB_NOT_ACTIVATED)) { + trackable.setDetails(cgeoapplication.getInstance().getString(R.string.trackable_not_activated)); + } // trackable logs try { @@ -1462,7 +1470,7 @@ public abstract class GCParser { long date = 0; try { date = Login.parseGcCustomDate(matcherLogs.group(2)).getTime(); - } catch (ParseException e) { + } catch (final ParseException e) { } final LogEntry logDone = new LogEntry( @@ -1490,7 +1498,7 @@ public abstract class GCParser { trackable.getLogs().add(logDone); } - } catch (Exception e) { + } catch (final Exception e) { // failed to parse logs Log.w("GCParser.parseCache: Failed to parse cache logs", e); } @@ -1560,10 +1568,10 @@ public abstract class GCParser { } } else { // extract embedded JSON data from page - rawResponse = BaseUtils.getMatch(page, GCConstants.PATTERN_LOGBOOK, ""); + rawResponse = TextUtils.getMatch(page, GCConstants.PATTERN_LOGBOOK, ""); } - List<LogEntry> logs = new ArrayList<LogEntry>(); + final List<LogEntry> logs = new ArrayList<LogEntry>(); try { final JSONObject resp = new JSONObject(rawResponse); @@ -1584,7 +1592,7 @@ public abstract class GCParser { long date = 0; try { date = Login.parseGcCustomDate(entry.getString("Visited")).getTime(); - } catch (ParseException e) { + } catch (final ParseException e) { Log.e("GCParser.loadLogsFromDetails: failed to parse log date."); } @@ -1612,7 +1620,7 @@ public abstract class GCParser { logs.add(logDone); } - } catch (JSONException e) { + } catch (final JSONException e) { // failed to parse logs Log.w("GCParser.loadLogsFromDetails: Failed to parse cache logs", e); } @@ -1628,30 +1636,26 @@ public abstract class GCParser { final List<LogType> types = new ArrayList<LogType>(); final MatcherWrapper typeBoxMatcher = new MatcherWrapper(GCConstants.PATTERN_TYPEBOX, page); - String typesText = null; - if (typeBoxMatcher.find()) { - if (typeBoxMatcher.groupCount() > 0) { - typesText = typeBoxMatcher.group(1); - } - } - - if (typesText != null) { - + if (typeBoxMatcher.find() && typeBoxMatcher.groupCount() > 0) { + final String typesText = typeBoxMatcher.group(1); final MatcherWrapper typeMatcher = new MatcherWrapper(GCConstants.PATTERN_TYPE2, typesText); while (typeMatcher.find()) { if (typeMatcher.groupCount() > 1) { try { - int type = Integer.parseInt(typeMatcher.group(2)); + final int type = Integer.parseInt(typeMatcher.group(2)); if (type > 0) { types.add(LogType.getById(type)); } - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { Log.e("Error parsing log types", e); } } } } + // we don't support this log type + types.remove(LogType.UPDATE_COORDINATES); + return types; } @@ -1690,7 +1694,7 @@ public abstract class GCParser { Log.i("Trackable in inventory (#" + entry.ctl + "/" + entry.id + "): " + entry.trackCode + " - " + entry.name); trackableLogs.add(entry); } - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { Log.e("GCParser.parseTrackableLog", e); } } @@ -1719,10 +1723,10 @@ public abstract class GCParser { //cache.setLogs(loadLogsFromDetails(page, cache, false)); if (Settings.isFriendLogsWanted()) { CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_logs); - List<LogEntry> allLogs = cache.getLogs(); - List<LogEntry> friendLogs = loadLogsFromDetails(page, cache, true, false); + final List<LogEntry> allLogs = cache.getLogs(); + final List<LogEntry> friendLogs = loadLogsFromDetails(page, cache, true, false); if (friendLogs != null) { - for (LogEntry log : friendLogs) { + for (final LogEntry log : friendLogs) { if (allLogs.contains(log)) { allLogs.get(allLogs.indexOf(log)).friend = true; } else { @@ -1766,7 +1770,7 @@ public abstract class GCParser { public static boolean editModifiedCoordinates(Geocache cache, Geopoint wpt) { final String page = requestHtmlPage(cache.getGeocode(), null, "n", "0"); - final String userToken = BaseUtils.getMatch(page, GCConstants.PATTERN_USERTOKEN, ""); + final String userToken = TextUtils.getMatch(page, GCConstants.PATTERN_USERTOKEN, ""); if (StringUtils.isEmpty(userToken)) { return false; } @@ -1785,7 +1789,7 @@ public abstract class GCParser { final String uriSuffix = wpt != null ? "SetUserCoordinate" : "ResetUserCoordinate"; final String uriPrefix = "http://www.geocaching.com/seek/cache_details.aspx/"; - HttpResponse response = Network.postJsonRequest(uriPrefix + uriSuffix, jo); + final HttpResponse response = Network.postJsonRequest(uriPrefix + uriSuffix, jo); Log.i("Sending to " + uriPrefix + uriSuffix + " :" + jo.toString()); if (response != null && response.getStatusLine().getStatusCode() == 200) { @@ -1793,11 +1797,42 @@ public abstract class GCParser { return true; } - } catch (JSONException e) { + } catch (final JSONException e) { Log.e("Unknown exception with json wrap code", e); } Log.e("GCParser.deleteModifiedCoordinates - cannot delete modified coords"); return false; } + public static boolean uploadPersonalNote(Geocache cache) { + final String page = requestHtmlPage(cache.getGeocode(), null, "n", "0"); + final String userToken = TextUtils.getMatch(page, GCConstants.PATTERN_USERTOKEN, ""); + if (StringUtils.isEmpty(userToken)) { + return false; + } + + try { + final JSONObject jo = new JSONObject() + .put("dto", (new JSONObject() + .put("et", cache.getPersonalNote()) + .put("ut", userToken))); + + final String uriSuffix = "SetUserCacheNote"; + + final String uriPrefix = "http://www.geocaching.com/seek/cache_details.aspx/"; + final HttpResponse response = Network.postJsonRequest(uriPrefix + uriSuffix, jo); + Log.i("Sending to " + uriPrefix + uriSuffix + " :" + jo.toString()); + + if (response != null && response.getStatusLine().getStatusCode() == 200) { + Log.i("GCParser.uploadPersonalNote - uploaded to GC.com"); + return true; + } + + } catch (final JSONException e) { + Log.e("Unknown exception with json wrap code", e); + } + Log.e("GCParser.uploadPersonalNote - cannot upload personal note"); + return false; + } + } diff --git a/main/src/cgeo/geocaching/connector/gc/Login.java b/main/src/cgeo/geocaching/connector/gc/Login.java index 7351311..3146712 100644 --- a/main/src/cgeo/geocaching/connector/gc/Login.java +++ b/main/src/cgeo/geocaching/connector/gc/Login.java @@ -8,9 +8,9 @@ import cgeo.geocaching.network.Cookies; import cgeo.geocaching.network.HtmlImage; import cgeo.geocaching.network.Network; import cgeo.geocaching.network.Parameters; -import cgeo.geocaching.utils.BaseUtils; import cgeo.geocaching.utils.Log; import cgeo.geocaching.utils.MatcherWrapper; +import cgeo.geocaching.utils.TextUtils; import ch.boye.httpclientandroidlib.HttpResponse; @@ -52,9 +52,9 @@ public abstract class Login { "dd/MM/yyyy" }; - Map<String, SimpleDateFormat> map = new HashMap<String, SimpleDateFormat>(); + final Map<String, SimpleDateFormat> map = new HashMap<String, SimpleDateFormat>(); - for (String format : formats) { + for (final String format : formats) { map.put(format, new SimpleDateFormat(format, Locale.ENGLISH)); } @@ -77,7 +77,7 @@ public abstract class Login { Login.setActualStatus(cgeoapplication.getInstance().getString(R.string.init_login_popup_working)); HttpResponse loginResponse = Network.getRequest("https://www.geocaching.com/login/default.aspx"); String loginData = Network.getResponseData(loginResponse); - if (loginResponse != null && loginResponse.getStatusLine().getStatusCode() == 503 && BaseUtils.matches(loginData, GCConstants.PATTERN_MAINTENANCE)) { + if (loginResponse != null && loginResponse.getStatusLine().getStatusCode() == 503 && TextUtils.matches(loginData, GCConstants.PATTERN_MAINTENANCE)) { return StatusCode.MAINTENANCE; } @@ -147,9 +147,9 @@ public abstract class Login { } public static StatusCode logout() { - HttpResponse logoutResponse = Network.getRequest("https://www.geocaching.com/login/default.aspx?RESET=Y&redir=http%3a%2f%2fwww.geocaching.com%2fdefault.aspx%3f"); - String logoutData = Network.getResponseData(logoutResponse); - if (logoutResponse != null && logoutResponse.getStatusLine().getStatusCode() == 503 && BaseUtils.matches(logoutData, GCConstants.PATTERN_MAINTENANCE)) { + final HttpResponse logoutResponse = Network.getRequest("https://www.geocaching.com/login/default.aspx?RESET=Y&redir=http%3a%2f%2fwww.geocaching.com%2fdefault.aspx%3f"); + final String logoutData = Network.getResponseData(logoutResponse); + if (logoutResponse != null && logoutResponse.getStatusLine().getStatusCode() == 503 && TextUtils.matches(logoutData, GCConstants.PATTERN_MAINTENANCE)) { return StatusCode.MAINTENANCE; } @@ -205,17 +205,17 @@ public abstract class Login { setActualStatus(cgeoapplication.getInstance().getString(R.string.init_login_popup_ok)); // on every page except login page - setActualLoginStatus(BaseUtils.matches(page, GCConstants.PATTERN_LOGIN_NAME)); + setActualLoginStatus(TextUtils.matches(page, GCConstants.PATTERN_LOGIN_NAME)); if (isActualLoginStatus()) { - setActualUserName(BaseUtils.getMatch(page, GCConstants.PATTERN_LOGIN_NAME, true, "???")); + setActualUserName(TextUtils.getMatch(page, GCConstants.PATTERN_LOGIN_NAME, true, "???")); int cachesCount = 0; try { - cachesCount = Integer.parseInt(BaseUtils.getMatch(page, GCConstants.PATTERN_CACHES_FOUND, true, "0").replaceAll("[,.]", "")); - } catch (NumberFormatException e) { + cachesCount = Integer.parseInt(TextUtils.getMatch(page, GCConstants.PATTERN_CACHES_FOUND, true, "0").replaceAll("[,.]", "")); + } catch (final NumberFormatException e) { Log.e("getLoginStatus: bad cache count", e); } setActualCachesFound(cachesCount); - Settings.setMemberStatus(BaseUtils.getMatch(page, GCConstants.PATTERN_MEMBER_STATUS, true, null)); + Settings.setMemberStatus(TextUtils.getMatch(page, GCConstants.PATTERN_MEMBER_STATUS, true, null)); if ( page.contains(GCConstants.MEMBER_STATUS_RENEW) ) { Settings.setMemberStatus(GCConstants.MEMBER_STATUS_PM); } @@ -223,7 +223,7 @@ public abstract class Login { } // login page - setActualLoginStatus(BaseUtils.matches(page, GCConstants.PATTERN_LOGIN_NAME_LOGIN_PAGE)); + setActualLoginStatus(TextUtils.matches(page, GCConstants.PATTERN_LOGIN_NAME_LOGIN_PAGE)); if (isActualLoginStatus()) { setActualUserName(Settings.getUsername()); // number of caches found is not part of this page @@ -260,23 +260,23 @@ public abstract class Login { public static BitmapDrawable downloadAvatarAndGetMemberStatus() { try { - final String profile = BaseUtils.replaceWhitespace(Network.getResponseData(Network.getRequest("http://www.geocaching.com/my/"))); + final String profile = TextUtils.replaceWhitespace(Network.getResponseData(Network.getRequest("http://www.geocaching.com/my/"))); - Settings.setMemberStatus(BaseUtils.getMatch(profile, GCConstants.PATTERN_MEMBER_STATUS, true, null)); + Settings.setMemberStatus(TextUtils.getMatch(profile, GCConstants.PATTERN_MEMBER_STATUS, true, null)); if (profile.contains(GCConstants.MEMBER_STATUS_RENEW)) { Settings.setMemberStatus(GCConstants.MEMBER_STATUS_PM); } - setActualCachesFound(Integer.parseInt(BaseUtils.getMatch(profile, GCConstants.PATTERN_CACHES_FOUND, true, "-1").replaceAll("[,.]", ""))); + setActualCachesFound(Integer.parseInt(TextUtils.getMatch(profile, GCConstants.PATTERN_CACHES_FOUND, true, "-1").replaceAll("[,.]", ""))); - final String avatarURL = BaseUtils.getMatch(profile, GCConstants.PATTERN_AVATAR_IMAGE_PROFILE_PAGE, false, null); + final String avatarURL = TextUtils.getMatch(profile, GCConstants.PATTERN_AVATAR_IMAGE_PROFILE_PAGE, false, null); if (null != avatarURL) { final HtmlImage imgGetter = new HtmlImage("", false, 0, false); return imgGetter.getDrawable(avatarURL); } // No match? There may be no avatar set by user. Log.d("No avatar set for user"); - } catch (Exception e) { + } catch (final Exception e) { Log.w("Error when retrieving user avatar", e); } return null; @@ -294,7 +294,7 @@ public abstract class Login { return; } - String customDate = BaseUtils.getMatch(result, GCConstants.PATTERN_CUSTOMDATE, true, null); + final String customDate = TextUtils.getMatch(result, GCConstants.PATTERN_CUSTOMDATE, true, null); if (null != customDate) { Settings.setGcCustomDate(customDate); } @@ -310,14 +310,14 @@ public abstract class Login { if (gcCustomDateFormats.containsKey(format)) { try { return gcCustomDateFormats.get(format).parse(trimmed); - } catch (ParseException e) { + } catch (final ParseException e) { } } - for (SimpleDateFormat sdf : gcCustomDateFormats.values()) { + for (final SimpleDateFormat sdf : gcCustomDateFormats.values()) { try { return sdf.parse(trimmed); - } catch (ParseException e) { + } catch (final ParseException e) { } } @@ -347,7 +347,7 @@ public abstract class Login { return true; } - for (String s : a) { + for (final String s : a) { if (StringUtils.isNotEmpty(s)) { return false; } @@ -373,24 +373,24 @@ public abstract class Login { if (matcherViewstateCount.find()) { try { count = Integer.parseInt(matcherViewstateCount.group(1)); - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { Log.e("getViewStates", e); } } - String[] viewstates = new String[count]; + final String[] viewstates = new String[count]; // Get the viewstates final MatcherWrapper matcherViewstates = new MatcherWrapper(GCConstants.PATTERN_VIEWSTATES, page); while (matcherViewstates.find()) { - String sno = matcherViewstates.group(1); // number of viewstate + final String sno = matcherViewstates.group(1); // number of viewstate int no; if (StringUtils.isEmpty(sno)) { no = 0; } else { try { no = Integer.parseInt(sno); - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { Log.e("getViewStates", e); no = 0; } @@ -436,17 +436,17 @@ public abstract class Login { * @return */ public static String postRequestLogged(final String uri, final Parameters params) { - HttpResponse response = Network.postRequest(uri, params); - String data = Network.getResponseData(response); + final String data = Network.getResponseData(Network.postRequest(uri, params)); - if (!getLoginStatus(data)) { - if (login() == StatusCode.NO_ERROR) { - response = Network.postRequest(uri, params); - data = Network.getResponseData(response); - } else { - Log.i("Working as guest."); - } + if (getLoginStatus(data)) { + return data; } + + if (login() == StatusCode.NO_ERROR) { + return Network.getResponseData(Network.postRequest(uri, params)); + } + + Log.i("Working as guest."); return data; } @@ -476,8 +476,8 @@ public abstract class Login { public static String[] getMapTokens() { final HttpResponse response = Network.getRequest(GCConstants.URL_LIVE_MAP); final String data = Network.getResponseData(response); - final String userSession = BaseUtils.getMatch(data, GCConstants.PATTERN_USERSESSION, ""); - final String sessionToken = BaseUtils.getMatch(data, GCConstants.PATTERN_SESSIONTOKEN, ""); + final String userSession = TextUtils.getMatch(data, GCConstants.PATTERN_USERSESSION, ""); + final String sessionToken = TextUtils.getMatch(data, GCConstants.PATTERN_SESSIONTOKEN, ""); return new String[] { userSession, sessionToken }; } } diff --git a/main/src/cgeo/geocaching/connector/gc/SearchHandler.java b/main/src/cgeo/geocaching/connector/gc/SearchHandler.java index 840cad1..45832e4 100644 --- a/main/src/cgeo/geocaching/connector/gc/SearchHandler.java +++ b/main/src/cgeo/geocaching/connector/gc/SearchHandler.java @@ -102,7 +102,7 @@ public class SearchHandler extends Handler { imgHandler.sendEmptyMessage(0); } catch (IOException e) { - Log.e("Failed to download reCAPTCHA image"); + Log.e("Failed to download reCAPTCHA image", e); } } } diff --git a/main/src/cgeo/geocaching/connector/gc/Tile.java b/main/src/cgeo/geocaching/connector/gc/Tile.java index 0e5ffe7..dd7f352 100644 --- a/main/src/cgeo/geocaching/connector/gc/Tile.java +++ b/main/src/cgeo/geocaching/connector/gc/Tile.java @@ -243,7 +243,7 @@ public class Tile { try { return response != null ? BitmapFactory.decodeStream(response.getEntity().getContent()) : null; } catch (IOException e) { - Log.e("cgBase.requestMapTile() " + e.getMessage()); + Log.e("Tile.requestMapTile() ", e); } return null; } diff --git a/main/src/cgeo/geocaching/connector/oc/AttributeParser.java b/main/src/cgeo/geocaching/connector/oc/AttributeParser.java new file mode 100644 index 0000000..63bee77 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/oc/AttributeParser.java @@ -0,0 +1,327 @@ +// This is a generated file, do not change manually! + +package cgeo.geocaching.connector.oc; + +import java.util.HashMap; +import java.util.Map; + +public class AttributeParser { + + private final static Map<String, Integer> attrMapDe; + private final static Map<String, Integer> attrMapPl; + + static { + attrMapDe = new HashMap<String, Integer>(); + attrMapPl = new HashMap<String, Integer>(); + + // last header line + attrMapDe.put("Listed at Opencaching only", 6); + attrMapDe.put("DostÄ™pna tylko na Opencaching", 6); + attrMapDe.put("Nur bei Opencaching logbar", 6); + attrMapDe.put("Solo loggeable en Opencaching", 6); + attrMapDe.put("Loggabile solo su Opencaching", 6); + attrMapPl.put("Near a Survey Marker", 54); + attrMapPl.put("W pobliżu punktu geodezyjnego", 54); + attrMapPl.put("Whereigo Cache", 55); + attrMapPl.put("Whereigo Cache", 55); + attrMapPl.put("Whereigo Cache", 55); + attrMapDe.put("Letterbox Cache", 8); + attrMapPl.put("Letterbox Cache", 56); + attrMapDe.put("Skrzynka typu Letterbox", 8); + attrMapPl.put("Skrzynka typu Letterbox", 56); + attrMapDe.put("Letterbox (benötigt Stempel)", 8); + attrMapPl.put("Letterbox (benötigt Stempel)", 56); + attrMapDe.put("Letterbox (necesita un estampador)", 8); + attrMapPl.put("Letterbox (necesita un estampador)", 56); + attrMapDe.put("Letterbox (richiede un timbro)", 8); + attrMapPl.put("Letterbox (richiede un timbro)", 56); + attrMapPl.put("GeoHotel", 43); + attrMapPl.put("GeoHotel", 43); + attrMapPl.put("GeoHotel", 43); + attrMapPl.put("Magnetic cache", 49); + attrMapPl.put("Przyczepiona magnesem", 49); + attrMapPl.put("magnetischer Cache", 49); + attrMapPl.put("Description contains an audio file", 50); + attrMapPl.put("Opis zawiera plik audio", 50); + attrMapPl.put("Offset cache", 51); + attrMapPl.put("Offset cache", 51); + attrMapPl.put("Peilungscache", 51); + attrMapPl.put("Garmin's wireless beacon", 52); + attrMapPl.put("Beacon - Garmin Chirp", 52); + attrMapPl.put("Funksignal – Garmin Chirp", 52); + attrMapPl.put("Dead Drop USB cache", 53); + attrMapPl.put("Dead Drop USB skrzynka", 53); + attrMapDe.put("Has a moving target", 31); + attrMapDe.put("bewegliches Ziel", 31); + attrMapDe.put("Objetivo en movimiento", 31); + attrMapDe.put("Oggetto in movimento", 31); + attrMapDe.put("Webcam Cache", 32); + attrMapDe.put("Webcam Cache", 32); + attrMapDe.put("Webcam Cache", 32); + attrMapDe.put("Webcam Cache", 32); + attrMapDe.put("Other cache type", 57); + attrMapDe.put("sonstiger Cachetyp", 57); + attrMapDe.put("Otro tipo de cache", 57); + attrMapDe.put("Altro tipo di cache", 57); + attrMapDe.put("Investigation required", 54); + attrMapDe.put("Recherche", 54); + attrMapDe.put("Investigación", 54); + attrMapDe.put("Ricerca", 54); + attrMapDe.put("Puzzle / Mystery", 55); + attrMapDe.put("Rätsel", 55); + attrMapDe.put("Puzzle / Misterio", 55); + attrMapDe.put("Puzzle / Mystery", 55); + attrMapDe.put("Arithmetical problem", 56); + attrMapDe.put("Rechenaufgabe", 56); + attrMapDe.put("Problema matemático", 56); + attrMapDe.put("Problema matematico", 56); + attrMapDe.put("Ask owner for start conditions", 58); + attrMapDe.put("Startbedingungen beim Owner erfragen", 58); + attrMapDe.put("Ask owner for start conditions", 58); + attrMapDe.put("Ask owner for start conditions", 58); + attrMapPl.put("Wheelchair accessible", 44); + attrMapPl.put("DostÄ™pna dla niepeÅ‚nosprawnych", 44); + attrMapPl.put("rollstuhltauglich", 44); + attrMapDe.put("Near the parking area", 24); + attrMapDe.put("nahe beim Auto", 24); + attrMapDe.put("Cerca de un Parking", 24); + attrMapDe.put("Vicino all'area di parcheggio", 24); + attrMapPl.put("Access only by walk", 84); + attrMapPl.put("DostÄ™pna tylko pieszo", 84); + attrMapDe.put("Long walk", 25); + attrMapDe.put("längere Wanderung", 25); + attrMapDe.put("Larga caminata", 25); + attrMapDe.put("Lunga camminata", 25); + attrMapDe.put("Swamp, marsh or wading", 26); + attrMapDe.put("sumpfig/matschiges Gelände / waten", 26); + attrMapDe.put("Pantano / terreno fangoso", 26); + attrMapDe.put("Palude o marcita", 26); + attrMapDe.put("Hilly area", 27); + attrMapDe.put("hügeliges Gelände", 27); + attrMapDe.put("Terreno montañoso", 27); + attrMapDe.put("Area collinare", 27); + attrMapDe.put("Some climbing (no gear needed)", 28); + attrMapDe.put("leichtes Klettern (ohne Ausrüstung)", 28); + attrMapDe.put("fácil de subir (sin equipo)", 28); + attrMapDe.put("Arrampicata (attrezzatura non necessaria)", 28); + attrMapDe.put("Swimming required", 29); + attrMapDe.put("Schwimmen erforderlich", 29); + attrMapDe.put("Requiere nadar", 29); + attrMapDe.put("Nuoto necessario", 29); + attrMapDe.put("Access or parking fee", 36); + attrMapDe.put("Zugangs- bzw. Parkentgelt", 36); + attrMapDe.put("Acceso o parking pagando", 36); + attrMapDe.put("Tassa di ingresso o di parcheggio", 36); + attrMapPl.put("Bikes allowed", 85); + attrMapPl.put("DostÄ™pna rowerem", 85); + attrMapPl.put("Hidden in natural surroundings (forests, mountains, etc.)", 60); + attrMapPl.put("Umiejscowiona na Å‚onie natury (lasy, góry, itp.)", 60); + attrMapPl.put("Historic site", 61); + attrMapPl.put("Miejsce historyczne", 61); + attrMapDe.put("Point of interest", 30); + attrMapDe.put("interessanter Ort", 30); + attrMapDe.put("Punto de interes", 30); + attrMapDe.put("Punto di interesse", 30); + attrMapDe.put("Hidden wihin enclosed rooms (caves, buildings etc.)", 33); + attrMapDe.put("in geschlossenen Räumen (Höhle, Gebäude, etc.)", 33); + attrMapDe.put("en espacios confinados (cuevas, edificios, etc)", 33); + attrMapDe.put("All'interno di stanze chiuse (caverne, edifici, ecc.)", 33); + attrMapDe.put("Hidden under water", 34); + attrMapDe.put("Im Wasser versteckt", 34); + attrMapDe.put("En el agua", 34); + attrMapDe.put("Nell'acqua", 34); + attrMapDe.put("Parking area nearby", 18); + attrMapDe.put("Parkplatz in der Nähe", 18); + attrMapDe.put("Parking cercano", 18); + attrMapDe.put("Parcheggio nei pressi", 18); + attrMapDe.put("Public transportation", 19); + attrMapDe.put("erreichbar mit ÖVM", 19); + attrMapDe.put("Transporte Público", 19); + attrMapDe.put("Trasporto pubblico", 19); + attrMapDe.put("Drinking water nearby", 20); + attrMapDe.put("Trinkwasser in der Nähe", 20); + attrMapDe.put("Agua potable en las cercanias", 20); + attrMapDe.put("Acqua potabile nei pressi", 20); + attrMapDe.put("Public restrooms nearby", 21); + attrMapDe.put("öffentliche Toilette in der Nähe", 21); + attrMapDe.put("Aseos públicos cercanos", 21); + attrMapDe.put("Bagni pubblici nei pressi", 21); + attrMapDe.put("Public phone nearby", 22); + attrMapDe.put("Telefon in der Nähe", 22); + attrMapDe.put("Teléfono Público en las cercanias", 22); + attrMapDe.put("Telefono pubblico nei pressi", 22); + attrMapDe.put("First aid available", 23); + attrMapDe.put("Erste Hilfe verfügbar", 23); + attrMapDe.put("Disponible socorro rapido", 23); + attrMapDe.put("Disponibile pronto soccorso", 23); + attrMapDe.put("Available 24/7", 38); + attrMapDe.put("rund um die Uhr machbar", 38); + attrMapDe.put("Disponible las 24 horas", 38); + attrMapDe.put("Disponibile 24 ore", 38); + attrMapDe.put("Not 24/7", 39); + attrMapPl.put("Not 24/7", 80); + attrMapDe.put("DostÄ™pna w okreÅ›lonych godzinach", 39); + attrMapPl.put("DostÄ™pna w okreÅ›lonych godzinach", 80); + attrMapDe.put("nur zu bestimmten Uhrzeiten", 39); + attrMapPl.put("nur zu bestimmten Uhrzeiten", 80); + attrMapDe.put("Sólo disponible a ciertas horas", 39); + attrMapPl.put("Sólo disponible a ciertas horas", 80); + attrMapDe.put("Disponibile solo in certi orari", 39); + attrMapPl.put("Disponibile solo in certi orari", 80); + attrMapDe.put("Not recommended at night", 40); + attrMapDe.put("nur tagüber", 40); + attrMapDe.put("solo por el dÃa", 40); + attrMapDe.put("solo di giorno", 40); + attrMapPl.put("Recommended at night", 91); + attrMapPl.put("Zalecane szukanie nocÄ…", 91); + attrMapPl.put("am besten nachts findbar", 91); + attrMapDe.put("Only at night", 1); + attrMapDe.put("nur bei Nacht", 1); + attrMapDe.put("Sólo por la noche", 1); + attrMapDe.put("Solo di notte", 1); + attrMapDe.put("All seasons", 42); + attrMapDe.put("ganzjähig zugänglich", 42); + attrMapDe.put("Todas las temporadas", 42); + attrMapDe.put("Tutte le stagioni", 42); + attrMapDe.put("Only available during specified seasons", 60); + attrMapDe.put("Nur zu bestimmten Zeiten im Jahr", 60); + attrMapDe.put("Sólo disponible durante las estaciones especificadas", 60); + attrMapDe.put("Disponibile solo in certe stagioni", 60); + attrMapDe.put("Breeding season / protected nature", 43); + attrMapDe.put("Brutsaison / Naturschutz", 43); + attrMapDe.put("Temporada de reproducción / protección de la naturaleza", 43); + attrMapDe.put("Stagione di riproduzione / natura protetta", 43); + attrMapDe.put("Available during winter", 44); + attrMapDe.put("schneesicheres Versteck", 44); + attrMapDe.put("Nieve en el escondite", 44); + attrMapDe.put("Luogo a prova di neve", 44); + attrMapDe.put("Not at high water level", 41); + attrMapDe.put("nicht bei Hochwasser oder Flut", 41); + attrMapDe.put("Compass required", 47); + attrMapPl.put("Compass required", 47); + attrMapDe.put("Potrzebny kompas", 47); + attrMapPl.put("Potrzebny kompas", 47); + attrMapDe.put("Kompass", 47); + attrMapPl.put("Kompass", 47); + attrMapDe.put("Brújula", 47); + attrMapPl.put("Brújula", 47); + attrMapDe.put("Bussola", 47); + attrMapPl.put("Bussola", 47); + attrMapPl.put("Take something to write", 48); + attrMapPl.put("Weź coÅ› do pisania", 48); + attrMapPl.put("You may need a shovel", 81); + attrMapPl.put("Potrzebna Å‚opatka", 81); + attrMapDe.put("Flashlight required", 48); + attrMapPl.put("Flashlight required", 82); + attrMapDe.put("Potrzebna latarka", 48); + attrMapPl.put("Potrzebna latarka", 82); + attrMapDe.put("Taschenlampe", 48); + attrMapPl.put("Taschenlampe", 82); + attrMapDe.put("Linterna", 48); + attrMapPl.put("Linterna", 82); + attrMapDe.put("Lampada tascabile", 48); + attrMapPl.put("Lampada tascabile", 82); + attrMapDe.put("Climbing gear required", 49); + attrMapDe.put("Kletterzeug", 49); + attrMapDe.put("Equipo de escalada", 49); + attrMapDe.put("Attrezzatura per arrampicata", 49); + attrMapDe.put("Cave equipment required", 50); + attrMapDe.put("Höhlenzeug", 50); + attrMapDe.put("Equipación para cuevas", 50); + attrMapDe.put("Attrezzatura per grotta", 50); + attrMapDe.put("Diving equipment required", 51); + attrMapDe.put("Taucherausrüstung", 51); + attrMapDe.put("Diving equipment", 51); + attrMapDe.put("Equipo de buceo", 51); + attrMapDe.put("Special tools required", 46); + attrMapPl.put("Special tools required", 83); + attrMapDe.put("Wymagany dodatkowy sprzÄ™t", 46); + attrMapPl.put("Wymagany dodatkowy sprzÄ™t", 83); + attrMapDe.put("spezielle Ausrüstung", 46); + attrMapPl.put("spezielle Ausrüstung", 83); + attrMapDe.put("Equipamiento especial", 46); + attrMapPl.put("Equipamiento especial", 83); + attrMapDe.put("Equipaggiamento speciale", 46); + attrMapPl.put("Equipaggiamento speciale", 83); + attrMapDe.put("Requires a boat", 52); + attrMapPl.put("Requires a boat", 86); + attrMapDe.put("Wymaga sprzÄ™tu pÅ‚ywajÄ…cego", 52); + attrMapPl.put("Wymaga sprzÄ™tu pÅ‚ywajÄ…cego", 86); + attrMapDe.put("Wasserfahrzeug", 52); + attrMapPl.put("Wasserfahrzeug", 86); + attrMapDe.put("Barca", 52); + attrMapPl.put("Barca", 86); + attrMapDe.put("Barca", 52); + attrMapPl.put("Barca", 86); + attrMapDe.put("No GPS required", 35); + attrMapDe.put("ohne GPS findbar", 35); + attrMapDe.put("Sin GPS", 35); + attrMapDe.put("Senza GPS", 35); + attrMapDe.put("Dangerous area", 9); + attrMapPl.put("Dangerous area", 90); + attrMapDe.put("Skrzynka niebezpieczna", 9); + attrMapPl.put("Skrzynka niebezpieczna", 90); + attrMapDe.put("gefährliches Gebiet", 9); + attrMapPl.put("gefährliches Gebiet", 90); + attrMapDe.put("Zona Peligrosa", 9); + attrMapPl.put("Zona Peligrosa", 90); + attrMapDe.put("Area pericolosa", 9); + attrMapPl.put("Area pericolosa", 90); + attrMapDe.put("Active railway nearby", 10); + attrMapDe.put("aktive Eisenbahnlinie in der Nähe", 10); + attrMapDe.put("Cerca del ferrocarril activo", 10); + attrMapDe.put("Ferrovia attiva nei pressi", 10); + attrMapDe.put("Cliff / Rocks", 11); + attrMapDe.put("Klippen / Felsen", 11); + attrMapDe.put("Acantilado / Rocas", 11); + attrMapDe.put("Scogliera / Rocce", 11); + attrMapDe.put("Hunting", 12); + attrMapDe.put("Jagdgebiet", 12); + attrMapDe.put("Zona de Caza", 12); + attrMapDe.put("Caccia", 12); + attrMapDe.put("Thorns", 13); + attrMapDe.put("Dornen", 13); + attrMapDe.put("Espinas", 13); + attrMapDe.put("Spine", 13); + attrMapDe.put("Ticks", 14); + attrMapDe.put("Zecken", 14); + attrMapDe.put("Garrapatas", 14); + attrMapDe.put("Zecche", 14); + attrMapDe.put("Abandoned mines", 15); + attrMapDe.put("Folgen des Bergbaus", 15); + attrMapDe.put("Mina abandonada", 15); + attrMapDe.put("Miniere abbandonate", 15); + attrMapDe.put("Poisonous plants", 16); + attrMapDe.put("giftige Pflanzen", 16); + attrMapDe.put("Planta venenosa", 16); + attrMapDe.put("Piante velenose", 16); + attrMapDe.put("Dangerous animals", 17); + attrMapDe.put("giftige/gefährliche Tiere", 17); + attrMapDe.put("Animales Peligrosos", 17); + attrMapDe.put("Animali pericolosi", 17); + attrMapPl.put("Quick cache", 40); + attrMapPl.put("Szybka skrzynka", 40); + attrMapDe.put("Overnight stay necessary", 37); + attrMapDe.put("Übernachtung erforderlich", 37); + attrMapDe.put("Necesario pernoctar", 37); + attrMapDe.put("Necessario pernottamento", 37); + attrMapPl.put("Take your children", 41); + attrMapPl.put("Można zabrać dzieci", 41); + attrMapDe.put("Suited for children (10-12 yo)", 59); + attrMapDe.put("kindgerecht (10-12 Jahre)", 59); + attrMapDe.put("Apto para niños (10-12 años)", 59); + attrMapDe.put("Suited for children (10-12 anni)", 59); + // first trailer line + + } + + public static int getOcDeId(final String name) { + + int result = 0; + + if (attrMapDe.containsKey(name)) { + result = attrMapDe.get(name); + } + return result; + } +} diff --git a/main/src/cgeo/geocaching/connector/oc/OC11XMLParser.java b/main/src/cgeo/geocaching/connector/oc/OC11XMLParser.java deleted file mode 100644 index 621032f..0000000 --- a/main/src/cgeo/geocaching/connector/oc/OC11XMLParser.java +++ /dev/null @@ -1,754 +0,0 @@ -package cgeo.geocaching.connector.oc; - -import cgeo.geocaching.Geocache; -import cgeo.geocaching.Image; -import cgeo.geocaching.LogEntry; -import cgeo.geocaching.R; -import cgeo.geocaching.Settings; -import cgeo.geocaching.cgeoapplication; -import cgeo.geocaching.connector.ConnectorFactory; -import cgeo.geocaching.connector.IConnector; -import cgeo.geocaching.connector.gc.GCConnector; -import cgeo.geocaching.enumerations.CacheAttribute; -import cgeo.geocaching.enumerations.CacheSize; -import cgeo.geocaching.enumerations.CacheType; -import cgeo.geocaching.enumerations.LogType; -import cgeo.geocaching.geopoint.Geopoint; -import cgeo.geocaching.utils.Log; - -import org.apache.commons.lang3.StringUtils; -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; - -import android.content.res.Resources; -import android.sax.Element; -import android.sax.EndElementListener; -import android.sax.EndTextElementListener; -import android.sax.RootElement; -import android.sax.StartElementListener; -import android.util.Xml; - -import java.io.IOException; -import java.io.InputStream; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.TimeZone; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class OC11XMLParser { - - private static final String[] MARKUP = new String[] { "p", "span" }; - private static Pattern STRIP_DATE = Pattern.compile("\\+0([0-9]){1}\\:00"); - private static Pattern LOCAL_URL = Pattern.compile("href=\"(.*)\""); - private static final int CACHE_PARSE_LIMIT = 250; - private static final Resources res = cgeoapplication.getInstance().getResources(); - - private static ImageHolder imageHolder = null; - - private static class CacheHolder { - public Geocache cache; - public String latitude; - public String longitude; - } - - private static class CacheLog { - public String id; - public String cacheId; - public LogEntry logEntry; - } - - private static class CacheDescription { - public String cacheId; - public String shortDesc; - public String desc; - public String hint; - } - - private static class ImageHolder { - public String url; - public String objectId; - protected String title; - protected boolean isSpoiler = false; - } - - private static Date parseFullDate(final String date) { - final SimpleDateFormat ISO8601DATEFORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US); - ISO8601DATEFORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); - final String strippedDate = STRIP_DATE.matcher(date).replaceAll("+0$100"); - try { - return ISO8601DATEFORMAT.parse(strippedDate); - } catch (ParseException e) { - Log.e("OC11XMLParser.parseFullDate", e); - } - return null; - } - - private static Date parseDayDate(final String date) { - final SimpleDateFormat ISO8601DATEFORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.US); - ISO8601DATEFORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); - final String strippedDate = STRIP_DATE.matcher(date).replaceAll("+0$100"); - try { - return ISO8601DATEFORMAT.parse(strippedDate); - } catch (ParseException e) { - Log.e("OC11XMLParser.parseDayDate", e); - } - return null; - } - - private static CacheSize getCacheSize(final String sizeId) { - try { - int size = Integer.parseInt(sizeId); - - switch (size) { - case 1: - return CacheSize.OTHER; - case 2: - return CacheSize.MICRO; - case 3: - return CacheSize.SMALL; - case 4: - return CacheSize.REGULAR; - case 5: - case 6: - return CacheSize.LARGE; - case 8: - return CacheSize.VIRTUAL; - default: - break; - } - } catch (NumberFormatException e) { - Log.e("OC11XMLParser.getCacheSize", e); - } - return CacheSize.NOT_CHOSEN; - } - - private static CacheType getCacheType(final String typeId) { - try { - int type = Integer.parseInt(typeId); - switch (type) { - case 1: // Other/unbekannter Cachetyp - return CacheType.UNKNOWN; - case 2: // Trad./normaler Cache - return CacheType.TRADITIONAL; - case 3: // Multi/Multicache - return CacheType.MULTI; - case 4: // Virt./virtueller Cache - return CacheType.VIRTUAL; - case 5: // ICam./Webcam-Cache - return CacheType.WEBCAM; - case 6: // Event/Event-Cache - return CacheType.EVENT; - case 7: // Quiz/Rätselcache - return CacheType.MYSTERY; - case 8: // Math/Mathe-/Physikcache - return CacheType.MYSTERY; - case 9: // Moving/beweglicher Cache - return CacheType.VIRTUAL; - case 10: // Driv./Drive-In - return CacheType.TRADITIONAL; - default: - return CacheType.UNKNOWN; - } - } catch (NumberFormatException e) { - Log.e("OC11XMLParser.getCacheType", e); - } - return CacheType.UNKNOWN; - } - - private static LogType getLogType(final int typeId) { - switch (typeId) { - case 1: - return LogType.FOUND_IT; - case 2: - return LogType.DIDNT_FIND_IT; - case 3: - return LogType.NOTE; - case 7: - return LogType.ATTENDED; - case 8: - return LogType.WILL_ATTEND; - default: - return LogType.UNKNOWN; - } - } - - private static void setCacheStatus(final int statusId, final Geocache cache) { - switch (statusId) { - case 1: - cache.setArchived(false); - cache.setDisabled(false); - break; - case 2: - cache.setArchived(false); - cache.setDisabled(true); - break; - default: - cache.setArchived(true); - cache.setDisabled(false); - break; - } - } - - private static void resetCache(final CacheHolder cacheHolder) { - cacheHolder.cache = new Geocache(null); - cacheHolder.cache.setReliableLatLon(true); - cacheHolder.cache.setDescription(StringUtils.EMPTY); - cacheHolder.latitude = "0.0"; - cacheHolder.longitude = "0.0"; - } - - private static void resetLog(final CacheLog log) { - log.cacheId = StringUtils.EMPTY; - log.logEntry = new LogEntry("", 0, LogType.UNKNOWN, ""); - } - - private static void resetDesc(final CacheDescription desc) { - desc.cacheId = StringUtils.EMPTY; - desc.shortDesc = StringUtils.EMPTY; - desc.desc = StringUtils.EMPTY; - desc.hint = StringUtils.EMPTY; - } - - private static int attributeId; - - public static Collection<Geocache> parseCaches(final InputStream stream) throws IOException { - // parse and return caches without filtering - return parseCaches(stream, true); - } - - public static Collection<Geocache> parseCachesFiltered(final InputStream stream) throws IOException { - // parse caches and filter result - return parseCaches(stream, false); - } - - private static Collection<Geocache> parseCaches(final InputStream stream, boolean ignoreFiltersIn) throws IOException { - - final Map<String, Geocache> caches = new HashMap<String, Geocache>(); - final Map<String, LogEntry> logs = new HashMap<String, LogEntry>(); - - final CacheHolder cacheHolder = new CacheHolder(); - final CacheLog logHolder = new CacheLog(); - final CacheDescription descHolder = new CacheDescription(); - - final RootElement root = new RootElement("oc11xml"); - final Element cacheNode = root.getChild("cache"); - - final boolean ignoreFilters = ignoreFiltersIn; - - // cache - cacheNode.setStartElementListener(new StartElementListener() { - - @Override - public void start(Attributes attributes) { - resetCache(cacheHolder); - } - - }); - - cacheNode.setEndElementListener(new EndElementListener() { - - @Override - public void end() { - Geocache cache = cacheHolder.cache; - Geopoint coords = new Geopoint(cacheHolder.latitude, cacheHolder.longitude); - cache.setCoords(coords); - if (caches.size() < CACHE_PARSE_LIMIT && isValid(cache) && (ignoreFilters || !isExcluded(cache))) { - cache.setDetailedUpdatedNow(); - caches.put(cache.getCacheId(), cache); - } - } - - private boolean isExcluded(Geocache cache) { - if (cache.isArchived()) { - return true; - } - if (cache.isDisabled() && Settings.isExcludeDisabledCaches()) { - return true; - } - if ((cache.isFound() || cache.isOwner()) && Settings.isExcludeMyCaches()) { - return true; - } - return !Settings.getCacheType().contains(cache); - } - - private boolean isValid(Geocache cache) { - return StringUtils.isNotBlank(cache.getGeocode()) && !cache.getCoords().equals(Geopoint.ZERO); - } - }); - - // cache.id - cacheNode.getChild("id").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String body) { - cacheHolder.cache.setCacheId(body); - } - }); - - // cache.longitude - cacheNode.getChild("longitude").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String body) { - String longitude = body.trim(); - if (StringUtils.isNotBlank(longitude)) { - cacheHolder.longitude = longitude; - } - } - }); - - // cache.latitude - cacheNode.getChild("latitude").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String body) { - String latitude = body.trim(); - if (StringUtils.isNotBlank(latitude)) { - cacheHolder.latitude = latitude; - } - } - }); - - // cache.name - cacheNode.getChild("name").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String body) { - final String content = body.trim(); - cacheHolder.cache.setName(content); - } - }); - - // cache.waypoints[oc] - cacheNode.getChild("waypoints").setStartElementListener(new StartElementListener() { - - @Override - public void start(Attributes attrs) { - if (attrs.getIndex("oc") > -1) { - cacheHolder.cache.setGeocode(attrs.getValue("oc")); - } - if (attrs.getIndex("gccom") > -1) { - String gccode = attrs.getValue("gccom"); - if (!StringUtils.isBlank(gccode)) { - cacheHolder.cache.setDescription(res.getString(R.string.cache_listed_on, GCConnector.getInstance().getName()) + ": <a href=\"http://coord.info/" + gccode + "\">" + gccode + "</a><br /><br />"); - } - } - } - }); - - // cache.type[id] - cacheNode.getChild("type").setStartElementListener(new StartElementListener() { - - @Override - public void start(Attributes attrs) { - if (attrs.getIndex("id") > -1) { - final String typeId = attrs.getValue("id"); - cacheHolder.cache.setType(getCacheType(typeId)); - } - } - }); - - // cache.status[id] - cacheNode.getChild("status").setStartElementListener(new StartElementListener() { - - @Override - public void start(Attributes attrs) { - if (attrs.getIndex("id") > -1) { - try { - final int statusId = Integer.parseInt(attrs.getValue("id")); - setCacheStatus(statusId, cacheHolder.cache); - } catch (NumberFormatException e) { - Log.w(String.format("Failed to parse status of cache '%s'.", cacheHolder.cache.getGeocode())); - } - } - } - }); - - // cache.size[id] - cacheNode.getChild("size").setStartElementListener(new StartElementListener() { - - @Override - public void start(Attributes attrs) { - if (attrs.getIndex("id") > -1) { - final String typeId = attrs.getValue("id"); - cacheHolder.cache.setSize(getCacheSize(typeId)); - } - } - }); - - // cache.difficulty - cacheNode.getChild("difficulty").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String body) { - final String content = body.trim(); - try { - cacheHolder.cache.setDifficulty(Float.valueOf(content)); - } catch (NumberFormatException e) { - Log.e("OC11XMLParser: unknown difficulty " + content, e); - } - } - }); - - // cache.terrain - cacheNode.getChild("terrain").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String body) { - final String content = body.trim(); - try { - cacheHolder.cache.setTerrain(Float.valueOf(content)); - } catch (NumberFormatException e) { - Log.e("OC11XMLParser: unknown terrain " + content, e); - } - } - }); - - // cache.datehidden - cacheNode.getChild("datehidden").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String body) { - final String content = body.trim(); - cacheHolder.cache.setHidden(parseFullDate(content)); - } - }); - - // cache.userid - final Element useridNode = cacheNode.getChild("userid"); - - useridNode.setStartElementListener(new StartElementListener() { - - @Override - public void start(Attributes attributes) { - if (attributes.getIndex("id") > -1) { - cacheHolder.cache.setOwnerUserId(attributes.getValue("id")); - } - } - }); - - useridNode.setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String body) { - cacheHolder.cache.setOwnerDisplayName(body); - } - }); - - // cache.attributes.attribute - final Element attributeNode = cacheNode.getChild("attributes").getChild("attribute"); - - attributeNode.setStartElementListener(new StartElementListener() { - - @Override - public void start(Attributes attributes) { - if (attributes.getIndex("id") > -1) { - try { - attributeId = Integer.parseInt(attributes.getValue("id")); - } catch (NumberFormatException e) { - Log.w(String.format("Failed to parse attribute id of cache '%s'.", cacheHolder.cache.getGeocode())); - } - } - } - }); - - attributeNode.setEndTextElementListener(new EndTextElementListener() { - @Override - public void end(String body) { - CacheAttribute attribute = CacheAttribute.getByOcId(attributeId); - if (attribute != null) { - // semantic of attributes on opencaching is always "yes" - cacheHolder.cache.getAttributes().add(attribute.getAttributeName(true)); - } - else { - if (StringUtils.isNotBlank(body)) { - cacheHolder.cache.getAttributes().add(body.trim()); - } - } - } - }); - - // cachedesc - final Element cacheDesc = root.getChild("cachedesc"); - - cacheDesc.setStartElementListener(new StartElementListener() { - - @Override - public void start(Attributes attributes) { - resetDesc(descHolder); - } - }); - - cacheDesc.setEndElementListener(new EndElementListener() { - - @Override - public void end() { - final Geocache cache = caches.get(descHolder.cacheId); - if (cache != null) { - cache.setShortDescription(descHolder.shortDesc); - cache.setDescription(cache.getDescription() + descHolder.desc); - cache.setHint(descHolder.hint); - } - } - }); - - // cachedesc.cacheid - cacheDesc.getChild("cacheid").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String body) { - descHolder.cacheId = body; - } - }); - - // cachedesc.desc - cacheDesc.getChild("shortdesc").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String body) { - final String content = body.trim(); - descHolder.shortDesc = linkify(stripMarkup(content)); - } - }); - - // cachedesc.desc - cacheDesc.getChild("desc").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String body) { - final String content = body.trim(); - descHolder.desc = linkify(stripMarkup(content)); - } - }); - - // cachedesc.hint - cacheDesc.getChild("hint").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String body) { - descHolder.hint = body.trim(); - } - }); - - // cachelog - final Element cacheLog = root.getChild("cachelog"); - - cacheLog.setStartElementListener(new StartElementListener() { - - @Override - public void start(Attributes attrs) { - resetLog(logHolder); - } - }); - - cacheLog.setEndElementListener(new EndElementListener() { - - @Override - public void end() { - final Geocache cache = caches.get(logHolder.cacheId); - if (cache != null && logHolder.logEntry.type != LogType.UNKNOWN) { - logs.put(logHolder.id, logHolder.logEntry); - cache.getLogs().add(0, logHolder.logEntry); - if ((logHolder.logEntry.type == LogType.FOUND_IT || logHolder.logEntry.type == LogType.ATTENDED) - && StringUtils.equalsIgnoreCase(logHolder.logEntry.author, Settings.getOCConnectorUserName())) { - cache.setFound(true); - cache.setVisitedDate(logHolder.logEntry.date); - } - } - } - }); - - // cachelog.id - cacheLog.getChild("id").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String body) { - logHolder.id = StringUtils.trim(body); - } - }); - - // cachelog.cacheid - cacheLog.getChild("cacheid").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String body) { - logHolder.cacheId = body; - } - }); - - // cachelog.date - cacheLog.getChild("date").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String body) { - try { - logHolder.logEntry.date = parseDayDate(body).getTime(); - } catch (NullPointerException e) { - Log.w("Failed to parse log date", e); - } - } - }); - - // cachelog.logtype - cacheLog.getChild("logtype").setStartElementListener(new StartElementListener() { - - @Override - public void start(Attributes attrs) { - if (attrs.getIndex("id") > -1) { - final String id = attrs.getValue("id"); - try { - final int typeId = Integer.parseInt(id); - logHolder.logEntry.type = getLogType(typeId); - } catch (NumberFormatException e) { - Log.e("OC11XMLParser, unknown logtype " + id, e); - } - } - } - }); - - // cachelog.userid - cacheLog.getChild("userid").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String finderName) { - logHolder.logEntry.author = finderName; - } - }); - - // cachelog.text - cacheLog.getChild("text").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String logText) { - logHolder.logEntry.log = stripMarkup(logText); - } - }); - - // pictures - final Element picture = root.getChild("picture"); - - picture.setStartElementListener(new StartElementListener() { - - @Override - public void start(Attributes attrs) { - imageHolder = new ImageHolder(); - } - }); - - picture.setEndElementListener(new EndElementListener() { - - @Override - public void end() { - if (imageHolder.isSpoiler) { - final Geocache cache = caches.get(imageHolder.objectId); - if (cache != null) { - Image spoiler = new Image(imageHolder.url, imageHolder.title); - cache.addSpoiler(spoiler); - } - } - else { - final LogEntry log = logs.get(imageHolder.objectId); - if (log != null) { - log.addLogImage(new Image(imageHolder.url, imageHolder.title)); - } - } - } - }); - - // picture.object - picture.getChild("object").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String body) { - imageHolder.objectId = StringUtils.trim(body); - } - }); - - // picture.title - picture.getChild("title").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String body) { - imageHolder.title = StringUtils.trim(body); - } - }); - - // picture.url - picture.getChild("url").setEndTextElementListener(new EndTextElementListener() { - - @Override - public void end(String body) { - imageHolder.url = StringUtils.trim(body); - } - }); - - // picture.attributes - picture.getChild("attributes").setStartElementListener(new StartElementListener() { - - @Override - public void start(Attributes attributes) { - if (attributes.getIndex("spoiler") > -1) { - String spoiler = attributes.getValue("spoiler"); - imageHolder.isSpoiler = ("1".equals(spoiler)); - } - } - }); - - try { - Xml.parse(stream, Xml.Encoding.UTF_8, root.getContentHandler()); - return caches.values(); - } catch (SAXException e) { - Log.e("Cannot parse .gpx file as oc11xml: could not parse XML", e); - return null; - } - } - - /** - * Converts local links to absolute links targeting the OC website. - */ - private static String linkify(String input) { - String result = input; - Matcher matcher = LOCAL_URL.matcher(result); - while (matcher.find()) { - String url = matcher.group(1); - if (!url.contains(":/")) { - IConnector ocConnector = ConnectorFactory.getConnector("OCXXX"); - String prefix = "http://" + ocConnector.getHost() + "/"; - result = StringUtils.replace(result, url, prefix + url); - matcher = LOCAL_URL.matcher(result); - } - } - return result; - } - - /** - * Removes unneeded markup. Log texts are typically encapsulated in paragraph tags which lead to more empty space on - * rendering. - */ - protected static String stripMarkup(String input) { - if (!StringUtils.startsWith(input, "<")) { - return input; - } - String result = input.trim(); - for (String tagName : MARKUP) { - final String startTag = "<" + tagName + ">"; - if (StringUtils.startsWith(result, startTag)) { - final String endTag = "</" + tagName + ">"; - if (StringUtils.endsWith(result, endTag)) { - String inner = result.substring(startTag.length(), result.length() - endTag.length()).trim(); - String nested = stripMarkup(inner); - if (!nested.contains(startTag)) { - result = nested; - } - } - } - } - return result; - } -}
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/connector/oc/OCApiConnector.java b/main/src/cgeo/geocaching/connector/oc/OCApiConnector.java index 69cf8a4..4f365ec 100644 --- a/main/src/cgeo/geocaching/connector/oc/OCApiConnector.java +++ b/main/src/cgeo/geocaching/connector/oc/OCApiConnector.java @@ -7,13 +7,31 @@ import cgeo.geocaching.network.Parameters; import cgeo.geocaching.utils.CancellableHandler; import cgeo.geocaching.utils.CryptUtils; +import org.apache.commons.lang3.StringUtils; + public class OCApiConnector extends OCConnector implements ISearchByGeocode { + // Levels of Okapi we support + // oldapi is around rev 500 + // current is from rev 798 onwards + public enum ApiSupport { + oldapi, + current + } + + // Levels of OAuth-Authentication we support + public enum OAuthLevel { + Level1, + Level3 + } + private final String cK; + private final ApiSupport apiSupport; - public OCApiConnector(String name, String host, String prefix, String cK) { + public OCApiConnector(String name, String host, String prefix, String cK, ApiSupport apiSupport) { super(name, host, prefix); this.cK = cK; + this.apiSupport = apiSupport; } public void addAuthentication(final Parameters params) { @@ -23,7 +41,7 @@ public class OCApiConnector extends OCConnector implements ISearchByGeocode { @Override public String getLicenseText(final Geocache cache) { // NOT TO BE TRANSLATED - return "<a href=\"" + getCacheUrl(cache) + "\">" + getName() + "</a> data licensed under the Creative Commons BY-SA 3.0 License"; + return "© " + cache.getOwnerDisplayName() + ", <a href=\"" + getCacheUrl(cache) + "\">" + getName() + "</a>, CC-BY-NC-ND, alle Logeinträge © jeweiliger Autor"; } @Override @@ -40,4 +58,23 @@ public class OCApiConnector extends OCConnector implements ISearchByGeocode { // currently always active, but only for details download return true; } + + @SuppressWarnings("static-method") + public OAuthLevel getSupportedAuthLevel() { + return OAuthLevel.Level1; + } + + public String getCK() { + return CryptUtils.rot13(cK); + } + + @SuppressWarnings("static-method") + public String getCS() { + return StringUtils.EMPTY; + } + + public ApiSupport getApiSupport() { + return apiSupport; + } + } diff --git a/main/src/cgeo/geocaching/connector/oc/OCApiLiveConnector.java b/main/src/cgeo/geocaching/connector/oc/OCApiLiveConnector.java new file mode 100644 index 0000000..4c6db97 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/oc/OCApiLiveConnector.java @@ -0,0 +1,119 @@ +package cgeo.geocaching.connector.oc; + +import cgeo.geocaching.Geocache; +import cgeo.geocaching.SearchResult; +import cgeo.geocaching.Settings; +import cgeo.geocaching.cgData; +import cgeo.geocaching.cgeoapplication; +import cgeo.geocaching.connector.ILoggingManager; +import cgeo.geocaching.connector.capability.ISearchByCenter; +import cgeo.geocaching.connector.capability.ISearchByViewPort; +import cgeo.geocaching.connector.oc.OkapiClient.UserInfo; +import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.geopoint.Viewport; +import cgeo.geocaching.utils.CryptUtils; + +import org.apache.commons.lang3.StringUtils; + +import android.app.Activity; + +public class OCApiLiveConnector extends OCApiConnector implements ISearchByCenter, ISearchByViewPort { + + private String cS; + private UserInfo userInfo = new UserInfo(StringUtils.EMPTY, 0, false); + + public OCApiLiveConnector(String name, String host, String prefix, int cKResId, int cSResId, ApiSupport apiSupport) { + super(name, host, prefix, CryptUtils.rot13(cgeoapplication.getInstance().getResources().getString(cKResId)), apiSupport); + + cS = CryptUtils.rot13(cgeoapplication.getInstance().getResources().getString(cSResId)); + } + + @Override + public boolean isActivated() { + return Settings.isOCConnectorActive(); + } + + @Override + public SearchResult searchByViewport(Viewport viewport, String[] tokens) { + return new SearchResult(OkapiClient.getCachesBBox(viewport, this)); + } + + @Override + public SearchResult searchByCenter(Geopoint center) { + + return new SearchResult(OkapiClient.getCachesAround(center, this)); + } + + @Override + public OAuthLevel getSupportedAuthLevel() { + // TODO the tokens must be available connector specific + if (StringUtils.isNotBlank(Settings.getOCDETokenPublic()) && StringUtils.isNotBlank(Settings.getOCDETokenSecret())) { + return OAuthLevel.Level3; + } + return OAuthLevel.Level1; + } + + @Override + public String getCS() { + return CryptUtils.rot13(cS); + } + + @Override + public boolean supportsWatchList() { + return true; + } + + @Override + public boolean addToWatchlist(Geocache cache) { + final boolean added = OkapiClient.setWatchState(cache, true, this); + + if (added) { + cgData.saveChangedCache(cache); + } + + return added; + } + + @Override + public boolean removeFromWatchlist(Geocache cache) { + final boolean removed = OkapiClient.setWatchState(cache, false, this); + + if (removed) { + cgData.saveChangedCache(cache); + } + + return removed; + } + + @Override + public boolean supportsLogging() { + return true; + } + + @Override + public ILoggingManager getLoggingManager(Activity activity, Geocache cache) { + return new OkapiLoggingManager(activity, this, cache); + } + + @Override + public boolean canLog(Geocache cache) { + return true; + } + + public boolean supportsPersonalization() { + return getSupportedAuthLevel() == OAuthLevel.Level3; + } + + public boolean retrieveUserInfo() { + userInfo = OkapiClient.getUserInfo(this); + return userInfo.isRetrieveSuccessful(); + } + + public Object getUserName() { + return userInfo.getName(); + } + + public int getCachesFound() { + return userInfo.getFinds(); + } +} diff --git a/main/src/cgeo/geocaching/connector/oc/OCAuthorizationActivity.java b/main/src/cgeo/geocaching/connector/oc/OCAuthorizationActivity.java new file mode 100644 index 0000000..779c1c5 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/oc/OCAuthorizationActivity.java @@ -0,0 +1,109 @@ +package cgeo.geocaching.connector.oc; + +import cgeo.geocaching.R; +import cgeo.geocaching.Settings; +import cgeo.geocaching.cgeoapplication; +import cgeo.geocaching.network.OAuthAuthorizationActivity; + +import org.apache.commons.lang3.tuple.ImmutablePair; + +public class OCAuthorizationActivity extends OAuthAuthorizationActivity { + + private final int siteResId = R.string.auth_ocde; + + public OCAuthorizationActivity() { + super("www.opencaching.de", + "/okapi/services/oauth/request_token", + "/okapi/services/oauth/authorize", + "/okapi/services/oauth/access_token", + false, + cgeoapplication.getInstance().getResources().getString(R.string.oc_de_okapi_consumer_key), + cgeoapplication.getInstance().getResources().getString(R.string.oc_de_okapi_consumer_secret)); + } + + @Override + protected ImmutablePair<String, String> getTempToken() { + return Settings.getTempOCDEToken(); + } + + @Override + protected void setTempTokens(String tokenPublic, String tokenSecret) { + Settings.setOCDETempTokens(tokenPublic, tokenSecret); + } + + @Override + protected void setTokens(String tokenPublic, String tokenSecret, boolean enable) { + Settings.setOCDETokens(tokenPublic, tokenSecret, enable); + } + + @Override + protected String getAuthTitle() { + return res.getString(siteResId); + } + + @Override + protected String getAuthAgain() { + return res.getString(R.string.auth_again_oc); + } + + @Override + protected String getErrAuthInitialize() { + return res.getString(R.string.err_auth_initialize); + } + + @Override + protected String getAuthStart() { + return res.getString(R.string.auth_start_oc); + } + + @Override + protected String getAuthDialogCompleted() { + return res.getString(R.string.auth_dialog_completed_oc, getAuthTitle()); + } + + @Override + protected String getErrAuthProcess() { + return res.getString(R.string.err_auth_process); + } + + @Override + protected String getAuthDialogWait() { + return res.getString(R.string.auth_dialog_wait_oc, getAuthTitle()); + } + + @Override + protected String getAuthDialogPinTitle() { + return res.getString(R.string.auth_dialog_pin_title_oc); + } + + @Override + protected String getAuthDialogPinMessage() { + return res.getString(R.string.auth_dialog_pin_message_oc, getAuthTitle()); + } + + @Override + protected String getAboutAuth1() { + return res.getString(R.string.about_auth_1_oc, getAuthTitle()); + } + + @Override + protected String getAboutAuth2() { + return res.getString(R.string.about_auth_2_oc, getAuthTitle(), getAuthTitle()); + } + + @Override + protected String getAuthAuthorize() { + return res.getString(R.string.auth_authorize_oc); + } + + @Override + protected String getAuthPinHint() { + return res.getString(R.string.auth_pin_hint_oc, getAuthTitle()); + } + + @Override + protected String getAuthFinish() { + return res.getString(R.string.auth_finish_oc); + } + +} diff --git a/main/src/cgeo/geocaching/connector/oc/OCConnector.java b/main/src/cgeo/geocaching/connector/oc/OCConnector.java index 62dfb4c..01738c0 100644 --- a/main/src/cgeo/geocaching/connector/oc/OCConnector.java +++ b/main/src/cgeo/geocaching/connector/oc/OCConnector.java @@ -2,8 +2,8 @@ package cgeo.geocaching.connector.oc; import cgeo.geocaching.Geocache; import cgeo.geocaching.ICache; +import cgeo.geocaching.R; import cgeo.geocaching.connector.AbstractConnector; -import cgeo.geocaching.enumerations.CacheRealm; import java.util.regex.Pattern; @@ -59,8 +59,11 @@ public class OCConnector extends AbstractConnector { } @Override - public CacheRealm getCacheRealm() { - return CacheRealm.OC; + public int getCacheMapMarkerId(boolean disabled) { + if (disabled) { + return R.drawable.marker_disabled_oc; + } + return R.drawable.marker_oc; } } diff --git a/main/src/cgeo/geocaching/connector/oc/OCXMLApiConnector.java b/main/src/cgeo/geocaching/connector/oc/OCXMLApiConnector.java deleted file mode 100644 index 43fdcfc..0000000 --- a/main/src/cgeo/geocaching/connector/oc/OCXMLApiConnector.java +++ /dev/null @@ -1,67 +0,0 @@ -package cgeo.geocaching.connector.oc; - -import cgeo.geocaching.Geocache; -import cgeo.geocaching.ICache; -import cgeo.geocaching.SearchResult; -import cgeo.geocaching.Settings; -import cgeo.geocaching.connector.capability.ISearchByCenter; -import cgeo.geocaching.connector.capability.ISearchByGeocode; -import cgeo.geocaching.connector.capability.ISearchByViewPort; -import cgeo.geocaching.geopoint.Geopoint; -import cgeo.geocaching.geopoint.Viewport; -import cgeo.geocaching.ui.Formatter; -import cgeo.geocaching.utils.CancellableHandler; - -import org.apache.commons.lang3.StringUtils; - -public class OCXMLApiConnector extends OCConnector implements ISearchByGeocode, ISearchByCenter, ISearchByViewPort { - - private final static double SEARCH_DISTANCE_LIMIT = 15.0; - private final static double NEARBY_SEARCH_DISTANCE = 5.0; - - public OCXMLApiConnector(String name, String host, String prefix) { - super(name, host, prefix); - } - - @Override - public SearchResult searchByGeocode(final String geocode, final String guid, CancellableHandler handler) { - final Geocache cache = OCXMLClient.getCache(geocode); - if (cache == null) { - return null; - } - return new SearchResult(cache); - } - - @Override - public SearchResult searchByCenter(final Geopoint center) { - return new SearchResult(OCXMLClient.getCachesAround(center, NEARBY_SEARCH_DISTANCE)); - } - - @Override - public SearchResult searchByViewport(final Viewport viewport, final String[] tokens) { - final Geopoint center = viewport.getCenter(); - double distance = center.distanceTo(viewport.bottomLeft) * 1.15; - if (distance > SEARCH_DISTANCE_LIMIT) { - distance = SEARCH_DISTANCE_LIMIT; - } - return new SearchResult(OCXMLClient.getCachesAround(center, distance)); - } - - @Override - public boolean isActivated() { - // currently only tested and working with oc.de - return Settings.isOCConnectorActive(); - } - - @Override - public boolean isOwner(ICache cache) { - return StringUtils.equalsIgnoreCase(cache.getOwnerDisplayName(), Settings.getOCConnectorUserName()); - } - - @Override - public String getLicenseText(Geocache cache) { - // not to be translated - return "© " + cache.getOwnerDisplayName() + ", " + "<a href=\"" + getCacheUrl(cache) + "\">www.opencaching.de</a>, CC-BY-NC-ND, Stand: " + Formatter.formatFullDate(cache.getUpdated()); - } - -} diff --git a/main/src/cgeo/geocaching/connector/oc/OCXMLClient.java b/main/src/cgeo/geocaching/connector/oc/OCXMLClient.java deleted file mode 100644 index 6767b48..0000000 --- a/main/src/cgeo/geocaching/connector/oc/OCXMLClient.java +++ /dev/null @@ -1,115 +0,0 @@ -package cgeo.geocaching.connector.oc; - -import cgeo.geocaching.Geocache; -import cgeo.geocaching.cgData; -import cgeo.geocaching.connector.ConnectorFactory; -import cgeo.geocaching.connector.IConnector; -import cgeo.geocaching.enumerations.LoadFlags; -import cgeo.geocaching.geopoint.Geopoint; -import cgeo.geocaching.geopoint.GeopointFormatter; -import cgeo.geocaching.network.Network; -import cgeo.geocaching.network.Parameters; -import cgeo.geocaching.utils.Log; - -import ch.boye.httpclientandroidlib.HttpResponse; - -import org.apache.commons.lang3.StringUtils; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Collection; -import java.util.Collections; -import java.util.Locale; -import java.util.zip.GZIPInputStream; - -public class OCXMLClient { - - private static final String SERVICE_CACHE = "/xml/ocxml11.php"; - - // Url for single cache requests - // http://www.opencaching.de/xml/ocxml11.php?modifiedsince=20060320000000&user=0&cache=1&cachedesc=1&cachelog=1&picture=1&removedobject=0&session=0&doctype=0&charset=utf-8&wp=OCC9BE - - public static Geocache getCache(final String geoCode) { - try { - final Parameters params = getOCXmlQueryParameters(true, true, true); - params.put("wp", geoCode); - final InputStream data = request(ConnectorFactory.getConnector(geoCode), SERVICE_CACHE, params); - - if (data == null) { - return null; - } - - Collection<Geocache> caches = OC11XMLParser.parseCaches(new GZIPInputStream(data)); - if (caches.iterator().hasNext()) { - Geocache cache = caches.iterator().next(); - cgData.saveCache(cache, LoadFlags.SAVE_ALL); - return cache; - } - return null; - } catch (IOException e) { - Log.e("Error parsing cache '" + geoCode + "'", e); - return null; - } - } - - public static Collection<Geocache> getCachesAround(final Geopoint center, final double distance) { - try { - final Parameters params = getOCXmlQueryParameters(false, false, false); - params.put("lat", GeopointFormatter.format(GeopointFormatter.Format.LAT_DECDEGREE_RAW, center)); - params.put("lon", GeopointFormatter.format(GeopointFormatter.Format.LON_DECDEGREE_RAW, center)); - params.put("distance", String.format(Locale.US, "%f", distance)); - final InputStream data = request(ConnectorFactory.getConnector("OCXXX"), SERVICE_CACHE, params); - - if (data == null) { - return Collections.emptyList(); - } - - return OC11XMLParser.parseCachesFiltered(new GZIPInputStream(data)); - } catch (IOException e) { - Log.e("Error parsing nearby search result", e); - return Collections.emptyList(); - } - } - - private static InputStream request(final IConnector connector, final String service, final Parameters params) { - if (connector == null) { - return null; - } - if (!(connector instanceof OCXMLApiConnector)) { - return null; - } - - final String host = connector.getHost(); - if (StringUtils.isBlank(host)) { - return null; - } - - final String uri = "http://" + host + service; - HttpResponse resp = Network.getRequest(uri, params); - if (resp != null) { - try { - return resp.getEntity().getContent(); - } catch (IllegalStateException e) { - // fall through and return null - } catch (IOException e) { - // fall through and return null - } - } - return null; - } - - private static Parameters getOCXmlQueryParameters(final boolean withDescription, final boolean withLogs, final boolean withImages) { - return new Parameters("modifiedsince", "20000101000000", - "user", "0", - "cache", "1", - "cachedesc", withDescription ? "1" : "0", - "cachelog", withLogs ? "1" : "0", - "picture", withImages ? "1" : "0", - "removedobject", "0", - "session", "0", - "doctype", "0", - "charset", "utf-8", - "zip", "gzip", - "picturefromcachelog", withImages ? "1" : "0"); - } -} diff --git a/main/src/cgeo/geocaching/connector/oc/OkapiClient.java b/main/src/cgeo/geocaching/connector/oc/OkapiClient.java index 0673605..f818a7c 100644 --- a/main/src/cgeo/geocaching/connector/oc/OkapiClient.java +++ b/main/src/cgeo/geocaching/connector/oc/OkapiClient.java @@ -3,16 +3,29 @@ package cgeo.geocaching.connector.oc; import cgeo.geocaching.Geocache; import cgeo.geocaching.Image; import cgeo.geocaching.LogEntry; +import cgeo.geocaching.R; +import cgeo.geocaching.Settings; +import cgeo.geocaching.Waypoint; import cgeo.geocaching.cgData; +import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.connector.ConnectorFactory; import cgeo.geocaching.connector.IConnector; +import cgeo.geocaching.connector.LogResult; +import cgeo.geocaching.connector.gc.GCConnector; +import cgeo.geocaching.connector.oc.OCApiConnector.ApiSupport; +import cgeo.geocaching.connector.oc.OCApiConnector.OAuthLevel; +import cgeo.geocaching.enumerations.CacheAttribute; import cgeo.geocaching.enumerations.CacheSize; import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.enumerations.LoadFlags.SaveFlag; import cgeo.geocaching.enumerations.LogType; +import cgeo.geocaching.enumerations.StatusCode; +import cgeo.geocaching.enumerations.WaypointType; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.GeopointFormatter; +import cgeo.geocaching.geopoint.Viewport; import cgeo.geocaching.network.Network; +import cgeo.geocaching.network.OAuth; import cgeo.geocaching.network.Parameters; import cgeo.geocaching.utils.Log; @@ -22,17 +35,42 @@ import org.json.JSONException; import org.json.JSONObject; import android.net.Uri; -import android.text.Html; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; import java.util.Date; import java.util.EnumSet; +import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; +import java.util.Map; +import java.util.TimeZone; final public class OkapiClient { + + private static final char SEPARATOR = '|'; + private static final String SEPARATOR_STRING = Character.toString(SEPARATOR); + private static final SimpleDateFormat logDateFormat; + + static { + logDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ", Locale.US); + logDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + } + + private static final String CACHE_ATTRNAMES = "attrnames"; + private static final String WPT_LOCATION = "location"; + private static final String WPT_DESCRIPTION = "description"; + private static final String WPT_TYPE = "type"; + private static final String WPT_NAME = "name"; + private static final String CACHE_IS_WATCHED = "is_watched"; + private static final String CACHE_WPTS = "alt_wpts"; + private static final String CACHE_STATUS_ARCHIVED = "Archived"; + private static final String CACHE_STATUS_DISABLED = "Temporarily unavailable"; + private static final String CACHE_IS_FOUND = "is_found"; private static final String CACHE_SIZE = "size"; private static final String CACHE_VOTES = "rating_votes"; private static final String CACHE_NOTFOUNDS = "notfounds"; @@ -62,15 +100,35 @@ final public class OkapiClient { private static final String LOG_USER = "user"; private static final String USER_USERNAME = "username"; + private static final String USER_CACHES_FOUND = "caches_found"; + private static final String USER_INFO_FIELDS = "username|caches_found"; + + // the several realms of possible fields for cache retrieval: + // Core: for livemap requests (L3 - only with level 3 auth) + // Additional: additional fields for full cache (L3 - only for level 3 auth, current - only for connectors with current api) + private static final String SERVICE_CACHE_CORE_FIELDS = "code|name|location|type|status|difficulty|terrain|size"; + private static final String SERVICE_CACHE_CORE_L3_FIELDS = "is_found"; + private static final String SERVICE_CACHE_ADDITIONAL_FIELDS = "owner|founds|notfounds|rating|rating_votes|recommendations|description|hint|images|latest_logs|date_hidden|alt_wpts|attrnames"; + private static final String SERVICE_CACHE_ADDITIONAL_CURRENT_FIELDS = "gc_code|attribution_note"; + private static final String SERVICE_CACHE_ADDITIONAL_L3_FIELDS = "is_watched"; + + private static final String METHOD_SEARCH_NEAREST = "services/caches/search/nearest"; + private static final String METHOD_SEARCH_BBOX = "services/caches/search/bbox"; + private static final String METHOD_RETRIEVE_CACHES = "services/caches/geocaches"; - private static final String SERVICE_CACHE = "/okapi/services/caches/geocache"; - private static final String SERVICE_CACHE_FIELDS = "code|name|location|type|status|owner|founds|notfounds|size|difficulty|terrain|rating|rating_votes|recommendations|description|hint|images|latest_logs|date_hidden"; + public static Geocache getCache(final String geoCode) { + final Parameters params = new Parameters("cache_code", geoCode); + final IConnector connector = ConnectorFactory.getConnector(geoCode); + if (!(connector instanceof OCApiConnector)) { + return null; + } - private static final String SERVICE_NEAREST = "/okapi/services/caches/search/nearest"; + final OCApiConnector ocapiConn = (OCApiConnector) connector; - public static Geocache getCache(final String geoCode) { - final Parameters params = new Parameters("cache_code", geoCode, "fields", SERVICE_CACHE_FIELDS); - final JSONObject data = request(ConnectorFactory.getConnector(geoCode), SERVICE_CACHE, params); + params.add("fields", getFullFields(ocapiConn)); + params.add("attribution_append", "none"); + + final JSONObject data = request(ocapiConn, OkapiService.SERVICE_CACHE, params); if (data == null) { return null; @@ -79,57 +137,141 @@ final public class OkapiClient { return parseCache(data); } - public static List<Geocache> getCachesAround(final Geopoint center, IConnector connector) { - String centerString = GeopointFormatter.format(GeopointFormatter.Format.LAT_DECDEGREE_RAW, center) + "|" + GeopointFormatter.format(GeopointFormatter.Format.LON_DECDEGREE_RAW, center); - final Parameters params = new Parameters("center", centerString); - final JSONObject data = request(connector, SERVICE_NEAREST, params); + public static List<Geocache> getCachesAround(final Geopoint center, OCApiConnector connector) { + final String centerString = GeopointFormatter.format(GeopointFormatter.Format.LAT_DECDEGREE_RAW, center) + SEPARATOR + GeopointFormatter.format(GeopointFormatter.Format.LON_DECDEGREE_RAW, center); + final Parameters params = new Parameters("search_method", METHOD_SEARCH_NEAREST); + final Map<String, String> valueMap = new LinkedHashMap<String, String>(); + valueMap.put("center", centerString); + valueMap.put("limit", "20"); + + return requestCaches(connector, params, valueMap); + } + + private static List<Geocache> requestCaches(OCApiConnector connector, final Parameters params, final Map<String, String> valueMap) { + addFilterParams(valueMap, connector); + params.add("search_params", new JSONObject(valueMap).toString()); + addRetrieveParams(params, connector); + + final JSONObject data = request(connector, OkapiService.SERVICE_SEARCH_AND_RETRIEVE, params); if (data == null) { - return null; + return Collections.emptyList(); } return parseCaches(data); } + // Assumes level 3 OAuth + public static List<Geocache> getCachesBBox(final Viewport viewport, OCApiConnector connector) { + + if (viewport.getLatitudeSpan() == 0 || viewport.getLongitudeSpan() == 0) { + return Collections.emptyList(); + } + + final String bboxString = GeopointFormatter.format(GeopointFormatter.Format.LAT_DECDEGREE_RAW, viewport.bottomLeft) + + SEPARATOR + GeopointFormatter.format(GeopointFormatter.Format.LON_DECDEGREE_RAW, viewport.bottomLeft) + + SEPARATOR + GeopointFormatter.format(GeopointFormatter.Format.LAT_DECDEGREE_RAW, viewport.topRight) + + SEPARATOR + GeopointFormatter.format(GeopointFormatter.Format.LON_DECDEGREE_RAW, viewport.topRight); + final Parameters params = new Parameters("search_method", METHOD_SEARCH_BBOX); + final Map<String, String> valueMap = new LinkedHashMap<String, String>(); + valueMap.put("bbox", bboxString); + + return requestCaches(connector, params, valueMap); + } + + public static boolean setWatchState(final Geocache cache, final boolean watched, OCApiConnector connector) { + final Parameters params = new Parameters("cache_code", cache.getGeocode()); + params.add("watched", watched ? "true" : "false"); + + final JSONObject data = request(connector, OkapiService.SERVICE_MARK_CACHE, params); + + if (data == null) { + return false; + } + + cache.setOnWatchlist(watched); + + return true; + } + + public static LogResult postLog(final Geocache cache, LogType logType, Calendar date, String log, OCApiConnector connector) { + final Parameters params = new Parameters("cache_code", cache.getGeocode()); + params.add("logtype", logType.oc_type); + params.add("comment", log); + params.add("comment_format", "plaintext"); + params.add("when", logDateFormat.format(date.getTime())); + if (logType.equals(LogType.NEEDS_MAINTENANCE)) { + params.add("needs_maintenance", "true"); + } + + final JSONObject data = request(connector, OkapiService.SERVICE_SUBMIT_LOG, params); + + if (data == null) { + return new LogResult(StatusCode.LOG_POST_ERROR, ""); + } + + try { + if (data.getBoolean("success")) { + return new LogResult(StatusCode.NO_ERROR, data.getString("log_uuid")); + } + + return new LogResult(StatusCode.LOG_POST_ERROR, ""); + } catch (final JSONException e) { + Log.e("OkapiClient.postLog", e); + } + return new LogResult(StatusCode.LOG_POST_ERROR, ""); + } + private static List<Geocache> parseCaches(final JSONObject response) { try { - final JSONArray cachesResponse = response.getJSONArray("results"); + // Check for empty result + final String result = response.getString("results"); + if (StringUtils.isBlank(result) || StringUtils.equals(result, "[]")) { + return Collections.emptyList(); + } + + // Get and iterate result list + final JSONObject cachesResponse = response.getJSONObject("results"); if (cachesResponse != null) { - ArrayList<String> geocodes = new ArrayList<String>(cachesResponse.length()); - for (int i = 0; i < cachesResponse.length(); i++) { - String geocode = cachesResponse.getString(i); - if (StringUtils.isNotBlank(geocode)) { - geocodes.add(geocode); - } - } - List<Geocache> caches = new ArrayList<Geocache>(geocodes.size()); - for (String geocode : geocodes) { - Geocache cache = getCache(geocode); + final List<Geocache> caches = new ArrayList<Geocache>(cachesResponse.length()); + @SuppressWarnings("unchecked") + final + Iterator<String> keys = cachesResponse.keys(); + while (keys.hasNext()) { + final String key = keys.next(); + final Geocache cache = parseSmallCache(cachesResponse.getJSONObject(key)); if (cache != null) { caches.add(cache); } } return caches; } - } catch (JSONException e) { - Log.e("OkapiClient.parseCaches", e); + } catch (final JSONException e) { + Log.e("OkapiClient.parseCachesResult", e); } - return null; + return Collections.emptyList(); + } + + private static Geocache parseSmallCache(final JSONObject response) { + final Geocache cache = new Geocache(); + cache.setReliableLatLon(true); + try { + + parseCoreCache(response, cache); + + cgData.saveCache(cache, EnumSet.of(SaveFlag.SAVE_CACHE)); + } catch (final JSONException e) { + Log.e("OkapiClient.parseSmallCache", e); + } + return cache; } private static Geocache parseCache(final JSONObject response) { final Geocache cache = new Geocache(); cache.setReliableLatLon(true); try { - cache.setGeocode(response.getString(CACHE_CODE)); - cache.setName(response.getString(CACHE_NAME)); - // not used: names - setLocation(cache, response.getString(CACHE_LOCATION)); - cache.setType(getCacheType(response.getString(CACHE_TYPE))); - final String status = response.getString(CACHE_STATUS); - cache.setDisabled(status.equalsIgnoreCase("Temporarily unavailable")); - cache.setArchived(status.equalsIgnoreCase("Archived")); + parseCoreCache(response, cache); // not used: url final JSONObject owner = response.getJSONObject(CACHE_OWNER); @@ -137,9 +279,7 @@ final public class OkapiClient { cache.getLogCounts().put(LogType.FOUND_IT, response.getInt(CACHE_FOUNDS)); cache.getLogCounts().put(LogType.DIDNT_FIND_IT, response.getInt(CACHE_NOTFOUNDS)); - cache.setSize(getCacheSize(response)); - cache.setDifficulty((float) response.getDouble(CACHE_DIFFICULTY)); - cache.setTerrain((float) response.getDouble(CACHE_TERRAIN)); + if (!response.isNull(CACHE_RATING)) { cache.setRating((float) response.getDouble(CACHE_RATING)); } @@ -147,14 +287,29 @@ final public class OkapiClient { cache.setFavoritePoints(response.getInt(CACHE_RECOMMENDATIONS)); // not used: req_password - cache.setDescription(response.getString(CACHE_DESCRIPTION)); - cache.setHint(Html.fromHtml(response.getString(CACHE_HINT)).toString()); + // Prepend gc-link to description if available + final StringBuilder description = new StringBuilder(500); + if (!response.isNull("gc_code")) { + final String gccode = response.getString("gc_code"); + description.append(cgeoapplication.getInstance().getResources() + .getString(R.string.cache_listed_on, GCConnector.getInstance().getName())) + .append(": <a href=\"http://coord.info/") + .append(gccode) + .append("\">") + .append(gccode) + .append("</a><br /><br />"); + } + description.append(response.getString(CACHE_DESCRIPTION)); + cache.setDescription(description.toString()); + + // currently the hint is delivered as HTML (contrary to OKAPI documentation), so we can store it directly + cache.setHint(response.getString(CACHE_HINT)); // not used: hints final JSONArray images = response.getJSONArray(CACHE_IMAGES); if (images != null) { for (int i = 0; i < images.length(); i++) { - JSONObject imageResponse = images.getJSONObject(i); + final JSONObject imageResponse = images.getJSONObject(i); if (imageResponse.getBoolean(CACHE_IMAGE_IS_SPOILER)) { final String title = imageResponse.getString(CACHE_IMAGE_CAPTION); final String url = absoluteUrl(imageResponse.getString(CACHE_IMAGE_URL), cache.getGeocode()); @@ -163,19 +318,45 @@ final public class OkapiClient { } } - // not used: attrnames + cache.setAttributes(parseAttributes(response.getJSONArray(CACHE_ATTRNAMES))); cache.setLogs(parseLogs(response.getJSONArray(CACHE_LATEST_LOGS))); cache.setHidden(parseDate(response.getString(CACHE_HIDDEN))); + //TODO: Store license per cache + //cache.setLicense(response.getString("attribution_note")); + cache.setWaypoints(parseWaypoints(response.getJSONArray(CACHE_WPTS)), false); + if (!response.isNull(CACHE_IS_WATCHED)) { + cache.setOnWatchlist(response.getBoolean(CACHE_IS_WATCHED)); + } cache.setDetailedUpdatedNow(); // save full detailed caches cgData.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); - } catch (JSONException e) { + } catch (final JSONException e) { Log.e("OkapiClient.parseCache", e); } return cache; } + private static void parseCoreCache(final JSONObject response, final Geocache cache) throws JSONException { + cache.setGeocode(response.getString(CACHE_CODE)); + cache.setName(response.getString(CACHE_NAME)); + // not used: names + setLocation(cache, response.getString(CACHE_LOCATION)); + cache.setType(getCacheType(response.getString(CACHE_TYPE))); + + final String status = response.getString(CACHE_STATUS); + cache.setDisabled(status.equalsIgnoreCase(CACHE_STATUS_DISABLED)); + cache.setArchived(status.equalsIgnoreCase(CACHE_STATUS_ARCHIVED)); + + cache.setSize(getCacheSize(response)); + cache.setDifficulty((float) response.getDouble(CACHE_DIFFICULTY)); + cache.setTerrain((float) response.getDouble(CACHE_TERRAIN)); + + if (!response.isNull(CACHE_IS_FOUND)) { + cache.setFound(response.getBoolean(CACHE_IS_FOUND)); + } + } + private static String absoluteUrl(String url, String geocode) { final Uri uri = Uri.parse(url); @@ -197,8 +378,8 @@ final public class OkapiClient { List<LogEntry> result = null; for (int i = 0; i < logsJSON.length(); i++) { try { - JSONObject logResponse = logsJSON.getJSONObject(i); - LogEntry log = new LogEntry( + final JSONObject logResponse = logsJSON.getJSONObject(i); + final LogEntry log = new LogEntry( parseUser(logResponse.getJSONObject(LOG_USER)), parseDate(logResponse.getString(LOG_DATE)).getTime(), parseLogType(logResponse.getString(LOG_TYPE)), @@ -207,13 +388,37 @@ final public class OkapiClient { result = new ArrayList<LogEntry>(); } result.add(log); - } catch (JSONException e) { + } catch (final JSONException e) { Log.e("OkapiClient.parseLogs", e); } } return result; } + private static List<Waypoint> parseWaypoints(JSONArray wptsJson) { + List<Waypoint> result = null; + for (int i = 0; i < wptsJson.length(); i++) { + try { + final JSONObject wptResponse = wptsJson.getJSONObject(i); + final Waypoint wpt = new Waypoint(wptResponse.getString(WPT_NAME), + parseWptType(wptResponse.getString(WPT_TYPE)), + false); + wpt.setNote(wptResponse.getString(WPT_DESCRIPTION)); + final Geopoint pt = parseCoords(wptResponse.getString(WPT_LOCATION)); + if (pt != null) { + wpt.setCoords(pt); + } + if (result == null) { + result = new ArrayList<Waypoint>(); + } + result.add(wpt); + } catch (final JSONException e) { + Log.e("OkapiClient.parseWaypoints", e); + } + } + return result; + } + private static LogType parseLogType(String logType) { if ("Found it".equalsIgnoreCase(logType)) { return LogType.FOUND_IT; @@ -224,20 +429,75 @@ final public class OkapiClient { return LogType.NOTE; } + private static WaypointType parseWptType(String wptType) { + if ("parking".equalsIgnoreCase(wptType)) { + return WaypointType.PARKING; + } + if ("path".equalsIgnoreCase(wptType)) { + return WaypointType.TRAILHEAD; + } + if ("stage".equalsIgnoreCase(wptType)) { + return WaypointType.STAGE; + } + if ("physical-stage".equalsIgnoreCase(wptType)) { + return WaypointType.STAGE; + } + if ("virtual-stage".equalsIgnoreCase(wptType)) { + return WaypointType.PUZZLE; + } + if ("final".equalsIgnoreCase(wptType)) { + return WaypointType.FINAL; + } + if ("poi".equalsIgnoreCase(wptType)) { + return WaypointType.TRAILHEAD; + } + return WaypointType.WAYPOINT; + } + private static Date parseDate(final String date) { final SimpleDateFormat ISO8601DATEFORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.getDefault()); final String strippedDate = date.replaceAll("\\+0([0-9]){1}\\:00", "+0$100"); try { return ISO8601DATEFORMAT.parse(strippedDate); - } catch (ParseException e) { + } catch (final ParseException e) { Log.e("OkapiClient.parseDate", e); } return null; } + private static Geopoint parseCoords(final String location) { + final String latitude = StringUtils.substringBefore(location, SEPARATOR_STRING); + final String longitude = StringUtils.substringAfter(location, SEPARATOR_STRING); + if (StringUtils.isNotBlank(latitude) && StringUtils.isNotBlank(longitude)) { + return new Geopoint(latitude, longitude); + } + + return null; + } + + private static List<String> parseAttributes(JSONArray nameList) { + + final List<String> result = new ArrayList<String>(); + + for (int i = 0; i < nameList.length(); i++) { + try { + final String name = nameList.getString(i); + final CacheAttribute attr = CacheAttribute.getByOcId(AttributeParser.getOcDeId(name)); + + if (attr != null) { + result.add(attr.rawName); + } + } catch (final JSONException e) { + Log.e("OkapiClient.parseAttributes", e); + } + } + + return result; + } + private static void setLocation(final Geocache cache, final String location) { - final String latitude = StringUtils.substringBefore(location, "|"); - final String longitude = StringUtils.substringAfter(location, "|"); + final String latitude = StringUtils.substringBefore(location, SEPARATOR_STRING); + final String longitude = StringUtils.substringAfter(location, SEPARATOR_STRING); cache.setCoords(new Geopoint(latitude, longitude)); } @@ -248,7 +508,7 @@ final public class OkapiClient { double size = 0; try { size = response.getDouble(CACHE_SIZE); - } catch (JSONException e) { + } catch (final JSONException e) { Log.e("OkapiClient.getCacheSize", e); } switch ((int) Math.round(size)) { @@ -281,14 +541,57 @@ final public class OkapiClient { if (cacheType.equalsIgnoreCase("Virtual")) { return CacheType.VIRTUAL; } + if (cacheType.equalsIgnoreCase("Event")) { + return CacheType.EVENT; + } + if (cacheType.equalsIgnoreCase("Webcam")) { + return CacheType.WEBCAM; + } + if (cacheType.equalsIgnoreCase("Math/Physics")) { + return CacheType.MYSTERY; + } + if (cacheType.equalsIgnoreCase("Drive-In")) { + return CacheType.TRADITIONAL; + } return CacheType.UNKNOWN; } - private static JSONObject request(final IConnector connector, final String service, final Parameters params) { + private static String getCoreFields(OCApiConnector connector) { if (connector == null) { - return null; + Log.e("OkapiClient.getCoreFields called with invalid connector"); + return StringUtils.EMPTY; } - if (!(connector instanceof OCApiConnector)) { + + if (connector.getSupportedAuthLevel() == OAuthLevel.Level3) { + return SERVICE_CACHE_CORE_FIELDS + SEPARATOR + SERVICE_CACHE_CORE_L3_FIELDS; + } + + return SERVICE_CACHE_CORE_FIELDS; + } + + private static String getFullFields(OCApiConnector connector) { + if (connector == null) { + Log.e("OkapiClient.getFullFields called with invalid connector"); + return StringUtils.EMPTY; + } + + final StringBuilder res = new StringBuilder(500); + + res.append(SERVICE_CACHE_CORE_FIELDS); + res.append(SEPARATOR).append(SERVICE_CACHE_ADDITIONAL_FIELDS); + if (connector.getSupportedAuthLevel() == OAuthLevel.Level3) { + res.append(SEPARATOR).append(SERVICE_CACHE_CORE_L3_FIELDS); + res.append(SEPARATOR).append(SERVICE_CACHE_ADDITIONAL_L3_FIELDS); + } + if (connector.getApiSupport() == ApiSupport.current) { + res.append(SEPARATOR).append(SERVICE_CACHE_ADDITIONAL_CURRENT_FIELDS); + } + + return res.toString(); + } + + private static JSONObject request(final OCApiConnector connector, final OkapiService service, final Parameters params) { + if (connector == null) { return null; } @@ -297,10 +600,15 @@ final public class OkapiClient { return null; } - ((OCApiConnector) connector).addAuthentication(params); params.add("langpref", getPreferredLanguage()); - final String uri = "http://" + host + service; + if (connector.getSupportedAuthLevel() == OAuthLevel.Level3) { + OAuth.signOAuth(host, service.methodName, "GET", false, params, Settings.getOCDETokenPublic(), Settings.getOCDETokenSecret(), connector.getCK(), connector.getCS()); + } else { + connector.addAuthentication(params); + } + + final String uri = "http://" + host + service.methodName; return Network.requestJSON(uri, params); } @@ -311,4 +619,106 @@ final public class OkapiClient { } return "en"; } + + private static void addFilterParams(final Map<String, String> valueMap, OCApiConnector connector) { + if (!Settings.isExcludeDisabledCaches()) { + valueMap.put("status", "Available|Temporarily unavailable"); + } + if (Settings.isExcludeMyCaches() && connector.getSupportedAuthLevel() == OAuthLevel.Level3) { + valueMap.put("exclude_my_own", "true"); + valueMap.put("found_status", "notfound_only"); + } + if (Settings.getCacheType() != CacheType.ALL) { + valueMap.put("type", getFilterFromType(Settings.getCacheType())); + } + } + + private static void addRetrieveParams(final Parameters params, OCApiConnector connector) { + params.add("retr_method", METHOD_RETRIEVE_CACHES); + params.add("retr_params", "{\"fields\": \"" + getCoreFields(connector) + "\"}"); + params.add("wrap", "true"); + } + + private static String getFilterFromType(CacheType cacheType) { + switch (cacheType) { + case EVENT: + return "Event"; + case MULTI: + return "Multi"; + case MYSTERY: + return "Quiz"; + case TRADITIONAL: + return "Traditional"; + case VIRTUAL: + return "Virtual"; + case WEBCAM: + return "Webcam"; + default: + return ""; + } + } + + public static UserInfo getUserInfo(OCApiLiveConnector connector) { + final Parameters params = new Parameters("fields", USER_INFO_FIELDS); + + final JSONObject data = request(connector, OkapiService.SERVICE_USER, params); + + if (data == null) { + return new UserInfo(StringUtils.EMPTY, 0, false); + } + + String name = StringUtils.EMPTY; + int finds = 0; + boolean success = true; + + if (!data.isNull(USER_USERNAME)) { + try { + name = data.getString(USER_USERNAME); + } catch (final JSONException e) { + Log.e("OkapiClient.getUserInfo - name", e); + success = false; + } + } else { + success = false; + } + + if (!data.isNull(USER_CACHES_FOUND)) { + try { + finds = data.getInt(USER_CACHES_FOUND); + } catch (final JSONException e) { + Log.e("OkapiClient.getUserInfo - finds", e); + success = false; + } + } else { + success = false; + } + + return new UserInfo(name, finds, success); + } + + public static class UserInfo { + + private final String name; + private final int finds; + private final boolean retrieveSuccessful; + + UserInfo(String name, int finds, boolean retrieveSuccessful) { + this.name = name; + this.finds = finds; + this.retrieveSuccessful = retrieveSuccessful; + } + + public String getName() { + return name; + } + + public int getFinds() { + return finds; + } + + public boolean isRetrieveSuccessful() { + return retrieveSuccessful; + } + } + } diff --git a/main/src/cgeo/geocaching/connector/oc/OkapiLoggingManager.java b/main/src/cgeo/geocaching/connector/oc/OkapiLoggingManager.java new file mode 100644 index 0000000..8a94218 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/oc/OkapiLoggingManager.java @@ -0,0 +1,68 @@ +package cgeo.geocaching.connector.oc; + +import cgeo.geocaching.Geocache; +import cgeo.geocaching.TrackableLog; +import cgeo.geocaching.LogCacheActivity; +import cgeo.geocaching.connector.ILoggingManager; +import cgeo.geocaching.connector.ImageResult; +import cgeo.geocaching.connector.LogResult; +import cgeo.geocaching.enumerations.LogType; +import cgeo.geocaching.enumerations.StatusCode; + +import android.app.Activity; +import android.net.Uri; + +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collections; +import java.util.List; + +public class OkapiLoggingManager implements ILoggingManager { + + private final OCApiConnector connector; + private final Geocache cache; + private LogCacheActivity activity; + + private final static List<LogType> standardLogTypes = Arrays.asList(LogType.FOUND_IT, LogType.DIDNT_FIND_IT, LogType.NOTE, LogType.NEEDS_MAINTENANCE); + private final static List<LogType> eventLogTypes = Arrays.asList(LogType.WILL_ATTEND, LogType.ATTENDED, LogType.NOTE); + + public OkapiLoggingManager(Activity activity, OCApiConnector connector, Geocache cache) { + this.connector = connector; + this.cache = cache; + this.activity = (LogCacheActivity) activity; + } + + @Override + public void init() { + activity.onLoadFinished(); + } + + @Override + public LogResult postLog(Geocache cache, LogType logType, Calendar date, String log, List<TrackableLog> trackableLogs) { + return OkapiClient.postLog(cache, logType, date, log, connector); + } + + @Override + public ImageResult postLogImage(String logId, String imageCaption, String imageDescription, Uri imageUri) { + return new ImageResult(StatusCode.LOG_POST_ERROR, ""); + } + + @Override + public boolean hasLoaderError() { + return false; + } + + @Override + public List<TrackableLog> getTrackables() { + return Collections.emptyList(); + } + + @Override + public List<LogType> getPossibleLogTypes() { + if (cache.isEventCache()) { + return eventLogTypes; + } + + return standardLogTypes; + } +} diff --git a/main/src/cgeo/geocaching/connector/oc/OkapiService.java b/main/src/cgeo/geocaching/connector/oc/OkapiService.java new file mode 100644 index 0000000..ec09527 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/oc/OkapiService.java @@ -0,0 +1,21 @@ +package cgeo.geocaching.connector.oc; + +import cgeo.geocaching.connector.oc.OCApiConnector.OAuthLevel; + + +enum OkapiService { + SERVICE_CACHE("/okapi/services/caches/geocache", OAuthLevel.Level1), + SERVICE_SEARCH_AND_RETRIEVE("/okapi/services/caches/shortcuts/search_and_retrieve", OAuthLevel.Level1), + SERVICE_MARK_CACHE("/okapi/services/caches/mark", OAuthLevel.Level3), + SERVICE_SUBMIT_LOG("/okapi/services/logs/submit", OAuthLevel.Level3), + SERVICE_USER("/okapi/services/users/user", OAuthLevel.Level1); + + final String methodName; + final OAuthLevel level; + + OkapiService(final String methodName, final OAuthLevel level) { + this.methodName = methodName; + this.level = level; + } + +} diff --git a/main/src/cgeo/geocaching/connector/trackable/AbstractTrackableConnector.java b/main/src/cgeo/geocaching/connector/trackable/AbstractTrackableConnector.java new file mode 100644 index 0000000..cd32d87 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/trackable/AbstractTrackableConnector.java @@ -0,0 +1,10 @@ +package cgeo.geocaching.connector.trackable; + + +public abstract class AbstractTrackableConnector implements TrackableConnector { + + @Override + public boolean isLoggable() { + return false; + } +} diff --git a/main/src/cgeo/geocaching/connector/trackable/GeokretyConnector.java b/main/src/cgeo/geocaching/connector/trackable/GeokretyConnector.java new file mode 100644 index 0000000..8387076 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/trackable/GeokretyConnector.java @@ -0,0 +1,42 @@ +package cgeo.geocaching.connector.trackable; + +import cgeo.geocaching.Trackable; +import cgeo.geocaching.network.Network; +import cgeo.geocaching.utils.Log; + +import java.util.regex.Pattern; + +public class GeokretyConnector extends AbstractTrackableConnector { + + private static final Pattern PATTERN_GK_CODE = Pattern.compile("GK[0-9A-F]{4,}"); + + @Override + public boolean canHandleTrackable(String geocode) { + return geocode != null && PATTERN_GK_CODE.matcher(geocode).matches(); + } + + @Override + public String getUrl(Trackable trackable) { + return "http://geokrety.org/konkret.php?id=" + getId(trackable.getGeocode()); + } + + @Override + public Trackable searchTrackable(String geocode, String guid, String id) { + final String page = Network.getResponseData(Network.getRequest("http://geokrety.org/export2.php?gkid=" + getId(geocode))); + if (page == null) { + return null; + } + return GeokretyParser.parse(page); + } + + private static int getId(String geocode) { + try { + final String hex = geocode.substring(2); + return Integer.parseInt(hex, 16); + } catch (final NumberFormatException e) { + Log.e("Trackable.getUrl", e); + } + return -1; + } + +} diff --git a/main/src/cgeo/geocaching/connector/trackable/GeokretyParser.java b/main/src/cgeo/geocaching/connector/trackable/GeokretyParser.java new file mode 100644 index 0000000..66ca5f7 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/trackable/GeokretyParser.java @@ -0,0 +1,82 @@ +package cgeo.geocaching.connector.trackable; + +import cgeo.geocaching.R; +import cgeo.geocaching.Trackable; +import cgeo.geocaching.cgeoapplication; +import cgeo.geocaching.utils.Log; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; + +import android.sax.Element; +import android.sax.EndTextElementListener; +import android.sax.RootElement; +import android.sax.StartElementListener; +import android.util.Xml; + +public class GeokretyParser { + + public static Trackable parse(final String page) { + final Trackable trackable = new Trackable(); + + final RootElement root = new RootElement("gkxml"); + final Element geokret = root.getChild("geokrety").getChild("geokret"); + + geokret.setEndTextElementListener(new EndTextElementListener() { + + @Override + public void end(String name) { + trackable.setName(name); + } + }); + + geokret.setStartElementListener(new StartElementListener() { + + @Override + public void start(Attributes attributes) { + try { + if (attributes.getIndex("id") > -1) { + trackable.setGeocode(geocode(Integer.valueOf(attributes.getValue("id")))); + } + if (attributes.getIndex("dist") > -1) { + trackable.setDistance(Float.valueOf(attributes.getValue("dist"))); + } + if (attributes.getIndex("type") > -1) { + trackable.setType(getType(Integer.valueOf(attributes.getValue("type")))); + } + } catch (final NumberFormatException e) { + Log.e("Parsing geokret", e); + } + } + }); + + try { + Xml.parse(page, root.getContentHandler()); + return trackable; + } catch (final SAXException e) { + Log.w("Cannot parse geokrety", e); + } + + return null; + } + + protected static String getType(int type) { + switch (type) { + case 0: + return cgeoapplication.getInstance().getString(R.string.geokret_type_traditional); + case 1: + return cgeoapplication.getInstance().getString(R.string.geokret_type_book_or_media); + case 2: + return cgeoapplication.getInstance().getString(R.string.geokret_type_human); + case 3: + return cgeoapplication.getInstance().getString(R.string.geokret_type_coin); + case 4: + return cgeoapplication.getInstance().getString(R.string.geokret_type_post); + } + return null; + } + + protected static String geocode(final int id) { + return String.format("GK%04X", id); + } +} diff --git a/main/src/cgeo/geocaching/connector/trackable/TrackableConnector.java b/main/src/cgeo/geocaching/connector/trackable/TrackableConnector.java new file mode 100644 index 0000000..c09dc07 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/trackable/TrackableConnector.java @@ -0,0 +1,19 @@ +package cgeo.geocaching.connector.trackable; + +import cgeo.geocaching.Trackable; + +/** + * Methods to be implemented by any connector for handling trackables + * + */ +public interface TrackableConnector { + + public boolean canHandleTrackable(final String geocode); + + public String getUrl(final Trackable trackable); + + public boolean isLoggable(); + + public Trackable searchTrackable(String geocode, String guid, String id); + +} diff --git a/main/src/cgeo/geocaching/connector/trackable/TravelBugConnector.java b/main/src/cgeo/geocaching/connector/trackable/TravelBugConnector.java new file mode 100644 index 0000000..9cae016 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/trackable/TravelBugConnector.java @@ -0,0 +1,34 @@ +package cgeo.geocaching.connector.trackable; + +import cgeo.geocaching.Trackable; +import cgeo.geocaching.connector.gc.GCParser; + +import java.util.regex.Pattern; + +public class TravelBugConnector extends AbstractTrackableConnector { + + /** + * TB codes really start with TB1, there is no padding or minimum length + */ + private final static Pattern PATTERN_TB_CODE = Pattern.compile("(TB[0-9A-Z]+)|([0-9A-Z]{6})", Pattern.CASE_INSENSITIVE); + + @Override + public boolean canHandleTrackable(String geocode) { + return TravelBugConnector.PATTERN_TB_CODE.matcher(geocode).matches(); + } + + @Override + public String getUrl(Trackable trackable) { + return "http://www.geocaching.com//track/details.aspx?tracker=" + trackable.getGeocode(); + } + + @Override + public boolean isLoggable() { + return true; + } + + @Override + public Trackable searchTrackable(String geocode, String guid, String id) { + return GCParser.searchTrackable(geocode, guid, id); + } +}
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/connector/trackable/UnknownTrackableConnector.java b/main/src/cgeo/geocaching/connector/trackable/UnknownTrackableConnector.java new file mode 100644 index 0000000..0295927 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/trackable/UnknownTrackableConnector.java @@ -0,0 +1,24 @@ +package cgeo.geocaching.connector.trackable; + +import cgeo.geocaching.Trackable; + +import org.apache.commons.lang3.StringUtils; + +public class UnknownTrackableConnector extends AbstractTrackableConnector { + + @Override + public boolean canHandleTrackable(String geocode) { + return false; + } + + @Override + public String getUrl(Trackable trackable) { + return StringUtils.EMPTY; + } + + @Override + public Trackable searchTrackable(String geocode, String guid, String id) { + return null; + } + +} diff --git a/main/src/cgeo/geocaching/enumerations/CacheAttribute.java b/main/src/cgeo/geocaching/enumerations/CacheAttribute.java index 530869f..243f63d 100644 --- a/main/src/cgeo/geocaching/enumerations/CacheAttribute.java +++ b/main/src/cgeo/geocaching/enumerations/CacheAttribute.java @@ -12,8 +12,8 @@ import java.util.HashMap; import java.util.Map; public enum CacheAttribute { - // THIS LIST IS GENERATED: don't change anything here but in - // project/attributes/makeEnum.sh + // THIS LIST IS GENERATED: don't change anything here but read + // project/attributes/readme.txt DOGS(1, -1, "dogs", R.drawable.attribute_dogs, R.string.attribute_dogs_yes, R.string.attribute_dogs_no), BICYCLES(32, -1, "bicycles", R.drawable.attribute_bicycles, R.string.attribute_bicycles_yes, R.string.attribute_bicycles_no), MOTORCYCLES(33, -1, "motorcycles", R.drawable.attribute_motorcycles, R.string.attribute_motorcycles_yes, R.string.attribute_motorcycles_no), @@ -27,7 +27,7 @@ public enum CacheAttribute { ONEHOUR(7, -1, "onehour", R.drawable.attribute_onehour, R.string.attribute_onehour_yes, R.string.attribute_onehour_no), SCENIC(8, -1, "scenic", R.drawable.attribute_scenic, R.string.attribute_scenic_yes, R.string.attribute_scenic_no), HIKING(9, 25, "hiking", R.drawable.attribute_hiking, R.string.attribute_hiking_yes, R.string.attribute_hiking_no), - CLIMBING(10, 28, "climbing", R.drawable.attribute_climbing, R.string.attribute_climbing_yes, R.string.attribute_climbing_no), + CLIMBING(10, -1, "climbing", R.drawable.attribute_climbing, R.string.attribute_climbing_yes, R.string.attribute_climbing_no), WADING(11, -1, "wading", R.drawable.attribute_wading, R.string.attribute_wading_yes, R.string.attribute_wading_no), SWIMMING(12, 29, "swimming", R.drawable.attribute_swimming, R.string.attribute_swimming_yes, R.string.attribute_swimming_no), AVAILABLE(13, 38, "available", R.drawable.attribute_available, R.string.attribute_available_yes, R.string.attribute_available_no), @@ -86,6 +86,7 @@ public enum CacheAttribute { SYRINGE(-1, 23, "syringe", R.drawable.attribute_syringe, R.string.attribute_syringe_yes, R.string.attribute_syringe_no), SWAMP(-1, 26, "swamp", R.drawable.attribute_swamp, R.string.attribute_swamp_yes, R.string.attribute_swamp_no), HILLS(-1, 27, "hills", R.drawable.attribute_hills, R.string.attribute_hills_yes, R.string.attribute_hills_no), + EASY_CLIMBING(-1, 28, "easy_climbing", R.drawable.attribute_easy_climbing, R.string.attribute_easy_climbing_yes, R.string.attribute_easy_climbing_no), POI(-1, 30, "poi", R.drawable.attribute_poi, R.string.attribute_poi_yes, R.string.attribute_poi_no), MOVING_TARGET(-1, 31, "moving_target", R.drawable.attribute_moving_target, R.string.attribute_moving_target_yes, R.string.attribute_moving_target_no), WEBCAM(-1, 32, "webcam", R.drawable.attribute_webcam, R.string.attribute_webcam_yes, R.string.attribute_webcam_no), @@ -107,9 +108,10 @@ public enum CacheAttribute { ARITHMETIC(-1, 56, "arithmetic", R.drawable.attribute_arithmetic, R.string.attribute_arithmetic_yes, R.string.attribute_arithmetic_no), OTHER_CACHE(-1, 57, "other_cache", R.drawable.attribute_other_cache, R.string.attribute_other_cache_yes, R.string.attribute_other_cache_no), ASK_OWNER(-1, 58, "ask_owner", R.drawable.attribute_ask_owner, R.string.attribute_ask_owner_yes, R.string.attribute_ask_owner_no), - UNKNOWN(-1, -1, "unknown", R.drawable.attribute_unknown, R.string.attribute_unknown_yes, R.string.attribute_unknown_no); - // THIS LIST IS GENERATED: don't change anything here but in - // project/attributes/makeEnum.sh + UNKNOWN(-1, -1, "unknown", R.drawable.attribute_unknown, R.string.attribute_unknown_yes, R.string.attribute_unknown_no), + GEOTOUR(67, -1, "geotour", R.drawable.attribute_geotour, R.string.attribute_geotour_yes, R.string.attribute_geotour_no); + // THIS LIST IS GENERATED: don't change anything here but read + // project/attributes/readme.txt private static final String INTERNAL_YES = "_yes"; private static final String INTERNAL_NO = "_no"; @@ -146,30 +148,20 @@ public enum CacheAttribute { } private final static Map<String, CacheAttribute> FIND_BY_GCRAWNAME; + private final static SparseArray<CacheAttribute> FIND_BY_GCID = new SparseArray<CacheAttribute>(); + private final static SparseArray<CacheAttribute> FIND_BY_OCID = new SparseArray<CacheAttribute>(); static { final HashMap<String, CacheAttribute> mapGcRawNames = new HashMap<String, CacheAttribute>(); for (CacheAttribute attr : values()) { mapGcRawNames.put(attr.rawName, attr); - } - FIND_BY_GCRAWNAME = Collections.unmodifiableMap(mapGcRawNames); - } - - private final static SparseArray<CacheAttribute> FIND_BY_GCID = new SparseArray<CacheAttribute>(); - static { - for (CacheAttribute attr : values()) { if (attr.gcid != NO_ID) { FIND_BY_GCID.put(attr.gcid, attr); } - } - } - - private final static SparseArray<CacheAttribute> FIND_BY_OCID = new SparseArray<CacheAttribute>(); - static { - for (CacheAttribute attr : values()) { if (attr.ocid != NO_ID) { FIND_BY_OCID.put(attr.ocid, attr); } } + FIND_BY_GCRAWNAME = Collections.unmodifiableMap(mapGcRawNames); } public static CacheAttribute getByRawName(final String rawName) { diff --git a/main/src/cgeo/geocaching/enumerations/CacheRealm.java b/main/src/cgeo/geocaching/enumerations/CacheRealm.java deleted file mode 100644 index 5a203ab..0000000 --- a/main/src/cgeo/geocaching/enumerations/CacheRealm.java +++ /dev/null @@ -1,22 +0,0 @@ -package cgeo.geocaching.enumerations; - -import cgeo.geocaching.R; - -public enum CacheRealm { - - GC("gc", "geocaching.com", R.drawable.marker, R.drawable.marker_disabled), - OC("oc", "OpenCaching Network", R.drawable.marker_oc, R.drawable.marker_disabled_oc), - OTHER("other", "Other", R.drawable.marker_other, R.drawable.marker_disabled_other); - - public final String id; - public final String name; - public final int markerId; - public final int markerDisabledId; - - CacheRealm(String id, String name, int markerId, int markerDisabledId) { - this.id = id; - this.name = name; - this.markerId = markerId; - this.markerDisabledId = markerDisabledId; - } -} diff --git a/main/src/cgeo/geocaching/enumerations/CacheType.java b/main/src/cgeo/geocaching/enumerations/CacheType.java index 528d3fa..8dbadfd 100644 --- a/main/src/cgeo/geocaching/enumerations/CacheType.java +++ b/main/src/cgeo/geocaching/enumerations/CacheType.java @@ -29,6 +29,7 @@ public enum CacheType { PROJECT_APE("ape", "Project Ape Cache", "2555690d-b2bc-4b55-b5ac-0cb704c0b768", R.string.ape, R.drawable.type_ape), GCHQ("gchq", "Groundspeak HQ", "416f2494-dc17-4b6a-9bab-1a29dd292d8c", R.string.gchq, R.drawable.type_hq), GPS_EXHIBIT("gps", "GPS Cache Exhibit", "72e69af2-7986-4990-afd9-bc16cbbb4ce3", R.string.gps, R.drawable.type_traditional), // icon missing + BLOCK_PARTY("block", "Groundspeak Block Party", "bc2f3df2-1aab-4601-b2ff-b5091f6c02e3", R.string.block, R.drawable.type_event), // icon missing UNKNOWN("unknown", "unknown", "", R.string.unknown, R.drawable.type_unknown), /** No real cache type -> filter */ ALL("all", "display all caches", "9a79e6ce-3344-409c-bbe9-496530baf758", R.string.all_types, R.drawable.type_unknown); @@ -88,7 +89,8 @@ public enum CacheType { } public boolean isEvent() { - return CacheType.EVENT == this || CacheType.MEGA_EVENT == this || CacheType.CITO == this || CacheType.LOSTANDFOUND == this; + return CacheType.EVENT == this || CacheType.MEGA_EVENT == this || CacheType.CITO == this || + CacheType.LOSTANDFOUND == this || CacheType.BLOCK_PARTY == this; } @Override @@ -98,7 +100,7 @@ public enum CacheType { /** * Whether this type contains the given cache. - * + * * @param cache * @return true if this is the ALL type or if this type equals the type of the cache. */ @@ -111,4 +113,12 @@ public enum CacheType { } return cache.getType() == this; } + + public boolean applyDistanceRule() { + return !isVirtual() && !isEvent(); + } + + public boolean isVirtual() { + return VIRTUAL == this || WEBCAM == this || EARTH == this; + } } diff --git a/main/src/cgeo/geocaching/enumerations/LogType.java b/main/src/cgeo/geocaching/enumerations/LogType.java index 71a5146..9902d3f 100644 --- a/main/src/cgeo/geocaching/enumerations/LogType.java +++ b/main/src/cgeo/geocaching/enumerations/LogType.java @@ -15,50 +15,52 @@ import java.util.Map; */ public enum LogType { - FOUND_IT(2, "2", "found it", R.string.log_found, R.drawable.mark_green), - DIDNT_FIND_IT(3, "3", "didn't find it", R.string.log_dnf, R.drawable.mark_red), - NOTE(4, "4", "write note", R.string.log_note), - PUBLISH_LISTING(1003, "24", "publish listing", R.string.log_published, R.drawable.mark_green_more), - ENABLE_LISTING(23, "23", "enable listing", R.string.log_enabled, R.drawable.mark_green_more), - ARCHIVE(5, "5", "archive", R.string.log_archived, R.drawable.mark_red_more), - UNARCHIVE(12, "12", "unarchive", R.string.log_unarchived, R.drawable.mark_green_more), - TEMP_DISABLE_LISTING(22, "22", "temporarily disable listing", R.string.log_disabled, R.drawable.mark_red_more), - NEEDS_ARCHIVE(7, "7", "needs archived", R.string.log_needs_archived, R.drawable.mark_red), - WILL_ATTEND(9, "9", "will attend", R.string.log_attend), - ATTENDED(10, "10", "attended", R.string.log_attended, R.drawable.mark_green), - RETRIEVED_IT(13, "13", "retrieved it", R.string.log_retrieved, R.drawable.mark_green_more), - PLACED_IT(14, "14", "placed it", R.string.log_placed, R.drawable.mark_green_more), - GRABBED_IT(19, "19", "grabbed it", R.string.log_grabbed, R.drawable.mark_green_more), - NEEDS_MAINTENANCE(45, "45", "needs maintenance", R.string.log_maintenance_needed, R.drawable.mark_red), - OWNER_MAINTENANCE(46, "46", "owner maintenance", R.string.log_maintained, R.drawable.mark_green_more), - UPDATE_COORDINATES(47, "47", "update coordinates", R.string.log_update), - DISCOVERED_IT(48, "48", "discovered it", R.string.log_discovered, R.drawable.mark_green), - POST_REVIEWER_NOTE(18, "68", "post reviewer note", R.string.log_reviewer), - VISIT(1001, "75", "visit", R.string.log_tb_visit, R.drawable.mark_green), - WEBCAM_PHOTO_TAKEN(11, "11", "webcam photo taken", R.string.log_webcam, R.drawable.mark_green), - ANNOUNCEMENT(74, "74", "announcement", R.string.log_announcement), - MOVE_COLLECTION(69, "69", "unused_collection", R.string.log_movecollection), - MOVE_INVENTORY(70, "70", "unused_inventory", R.string.log_moveinventory), - RETRACT(25, "25", "retract listing", R.string.log_retractlisting), - MARKED_MISSING(16, "16", "marked missing", R.string.log_marked_missing, R.drawable.mark_red), - UNKNOWN(0, "unknown", "", R.string.err_unknown, R.drawable.mark_red); // LogType not init. yet + FOUND_IT(2, "2", "found it", "Found it", R.string.log_found, R.drawable.mark_green), + DIDNT_FIND_IT(3, "3", "didn't find it", "Didn't find it", R.string.log_dnf, R.drawable.mark_red), + NOTE(4, "4", "write note", "Comment", R.string.log_note), + PUBLISH_LISTING(1003, "24", "publish listing", "", R.string.log_published, R.drawable.mark_green_more), + ENABLE_LISTING(23, "23", "enable listing", "", R.string.log_enabled, R.drawable.mark_green_more), + ARCHIVE(5, "5", "archive", "", R.string.log_archived, R.drawable.mark_red_more), + UNARCHIVE(12, "12", "unarchive", "", R.string.log_unarchived, R.drawable.mark_green_more), + TEMP_DISABLE_LISTING(22, "22", "temporarily disable listing", "", R.string.log_disabled, R.drawable.mark_red_more), + NEEDS_ARCHIVE(7, "7", "needs archived", "", R.string.log_needs_archived, R.drawable.mark_red), + WILL_ATTEND(9, "9", "will attend", "Will attend", R.string.log_attend), + ATTENDED(10, "10", "attended", "Attended", R.string.log_attended, R.drawable.mark_green), + RETRIEVED_IT(13, "13", "retrieved it", "", R.string.log_retrieved, R.drawable.mark_green_more), + PLACED_IT(14, "14", "placed it", "", R.string.log_placed, R.drawable.mark_green_more), + GRABBED_IT(19, "19", "grabbed it", "", R.string.log_grabbed, R.drawable.mark_green_more), + NEEDS_MAINTENANCE(45, "45", "needs maintenance", "Comment", R.string.log_maintenance_needed, R.drawable.mark_red), + OWNER_MAINTENANCE(46, "46", "owner maintenance", "", R.string.log_maintained, R.drawable.mark_green_more), + UPDATE_COORDINATES(47, "47", "update coordinates", "", R.string.log_update), + DISCOVERED_IT(48, "48", "discovered it", "", R.string.log_discovered, R.drawable.mark_green), + POST_REVIEWER_NOTE(18, "68", "post reviewer note", "", R.string.log_reviewer), + VISIT(1001, "75", "visit", "", R.string.log_tb_visit, R.drawable.mark_green), + WEBCAM_PHOTO_TAKEN(11, "11", "webcam photo taken", "", R.string.log_webcam, R.drawable.mark_green), + ANNOUNCEMENT(74, "74", "announcement", "", R.string.log_announcement), + MOVE_COLLECTION(69, "69", "unused_collection", "", R.string.log_movecollection), + MOVE_INVENTORY(70, "70", "unused_inventory", "", R.string.log_moveinventory), + RETRACT(25, "25", "retract listing", "", R.string.log_retractlisting), + MARKED_MISSING(16, "16", "marked missing", "", R.string.log_marked_missing, R.drawable.mark_red), + UNKNOWN(0, "unknown", "", "", R.string.err_unknown, R.drawable.mark_red); // LogType not init. yet public final int id; public final String iconName; public final String type; + public final String oc_type; private final int stringId; public final int markerId; - LogType(int id, String iconName, String type, int stringId, int markerId) { + LogType(int id, String iconName, String type, String oc_type, int stringId, int markerId) { this.id = id; this.iconName = iconName; this.type = type; + this.oc_type = oc_type; this.stringId = stringId; this.markerId = markerId; } - LogType(int id, String iconName, String type, int stringId) { - this(id, iconName, type, stringId, R.drawable.mark_gray); + LogType(int id, String iconName, String type, String oc_type, int stringId) { + this(id, iconName, type, oc_type, stringId, R.drawable.mark_gray); } private final static Map<String, LogType> FIND_BY_ICONNAME; diff --git a/main/src/cgeo/geocaching/enumerations/WaypointType.java b/main/src/cgeo/geocaching/enumerations/WaypointType.java index 748c432..79c8106 100644 --- a/main/src/cgeo/geocaching/enumerations/WaypointType.java +++ b/main/src/cgeo/geocaching/enumerations/WaypointType.java @@ -72,4 +72,8 @@ public enum WaypointType { public final String toString() { return getL10n(); } + + public boolean applyDistanceRule() { + return (this == FINAL || this == STAGE); + } } diff --git a/main/src/cgeo/geocaching/export/AbstractExport.java b/main/src/cgeo/geocaching/export/AbstractExport.java index 72ea544..e4ba5f0 100644 --- a/main/src/cgeo/geocaching/export/AbstractExport.java +++ b/main/src/cgeo/geocaching/export/AbstractExport.java @@ -1,5 +1,6 @@ package cgeo.geocaching.export; +import cgeo.geocaching.R; import cgeo.geocaching.cgeoapplication; abstract class AbstractExport implements Export { @@ -27,7 +28,7 @@ abstract class AbstractExport implements Export { /** * Generates a localized string from a resource id. - * + * * @param resourceId * the resource id of the string * @param params @@ -43,4 +44,8 @@ abstract class AbstractExport implements Export { // used in the array adapter of the dialog showing the exports return getName(); } + + protected String getProgressTitle() { + return getString(R.string.export) + ": " + getName(); + } } diff --git a/main/src/cgeo/geocaching/export/FieldnoteExport.java b/main/src/cgeo/geocaching/export/FieldnoteExport.java index 5e1805a..38b603c 100644 --- a/main/src/cgeo/geocaching/export/FieldnoteExport.java +++ b/main/src/cgeo/geocaching/export/FieldnoteExport.java @@ -5,11 +5,11 @@ import cgeo.geocaching.LogEntry; import cgeo.geocaching.R; import cgeo.geocaching.cgData; import cgeo.geocaching.activity.ActivityMixin; -import cgeo.geocaching.activity.Progress; import cgeo.geocaching.connector.gc.Login; import cgeo.geocaching.enumerations.StatusCode; import cgeo.geocaching.network.Network; import cgeo.geocaching.network.Parameters; +import cgeo.geocaching.utils.AsyncTaskWithProgress; import cgeo.geocaching.utils.IOUtils; import cgeo.geocaching.utils.Log; @@ -20,12 +20,12 @@ import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; -import android.os.AsyncTask; import android.os.Environment; import android.view.ContextThemeWrapper; import android.view.View; import android.widget.CheckBox; +import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -57,22 +57,23 @@ class FieldnoteExport extends AbstractExport { } @Override - public void export(final List<Geocache> caches, final Activity activity) { + public void export(final List<Geocache> cachesList, final Activity activity) { + final Geocache[] caches = cachesList.toArray(new Geocache[cachesList.size()]); if (null == activity) { // No activity given, so no user interaction possible. // Start export with default parameters. - new ExportTask(caches, null, false, false).execute((Void) null); + new ExportTask(null, false, false).execute(caches); } else { // Show configuration dialog getExportOptionsDialog(caches, activity).show(); } } - private Dialog getExportOptionsDialog(final List<Geocache> caches, final Activity activity) { - AlertDialog.Builder builder = new AlertDialog.Builder(activity); + private Dialog getExportOptionsDialog(final Geocache[] caches, final Activity activity) { + final AlertDialog.Builder builder = new AlertDialog.Builder(activity); // AlertDialog has always dark style, so we have to apply it as well always - View layout = View.inflate(new ContextThemeWrapper(activity, R.style.dark), R.layout.fieldnote_export_dialog, null); + final View layout = View.inflate(new ContextThemeWrapper(activity, R.style.dark), R.layout.fieldnote_export_dialog, null); builder.setView(layout); final CheckBox uploadOption = (CheckBox) layout.findViewById(R.id.upload); @@ -91,32 +92,27 @@ class FieldnoteExport extends AbstractExport { public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); new ExportTask( - caches, activity, uploadOption.isChecked(), onlyNewOption.isChecked()) - .execute((Void) null); + .execute(caches); } }); return builder.create(); } - private class ExportTask extends AsyncTask<Void, Integer, Boolean> { - private final List<Geocache> caches; + private class ExportTask extends AsyncTaskWithProgress<Geocache, Boolean> { private final Activity activity; private final boolean upload; private final boolean onlyNew; - private final Progress progress = new Progress(); private File exportFile; private static final int STATUS_UPLOAD = -1; /** - * Instantiates and configurates the task for exporting field notes. + * Instantiates and configures the task for exporting field notes. * - * @param caches - * The {@link List} of {@link cgeo.geocaching.Geocache} to be exported * @param activity * optional: Show a progress bar and toasts * @param upload @@ -124,32 +120,25 @@ class FieldnoteExport extends AbstractExport { * @param onlyNew * Upload/export only new logs since last export */ - public ExportTask(final List<Geocache> caches, final Activity activity, final boolean upload, final boolean onlyNew) { - this.caches = caches; + public ExportTask(final Activity activity, final boolean upload, final boolean onlyNew) { + super(activity, getProgressTitle(), getString(R.string.export_fieldnotes_creating)); this.activity = activity; this.upload = upload; this.onlyNew = onlyNew; } @Override - protected void onPreExecute() { - if (null != activity) { - progress.show(activity, getString(R.string.export) + ": " + getName(), getString(R.string.export_fieldnotes_creating), true, null); - } - } - - @Override - protected Boolean doInBackground(Void... params) { + protected Boolean doInBackgroundInternal(Geocache[] caches) { final StringBuilder fieldNoteBuffer = new StringBuilder(); try { int i = 0; - for (Geocache cache : caches) { + for (final Geocache cache : caches) { if (cache.isLogOffline()) { appendFieldNote(fieldNoteBuffer, cache, cgData.loadLogOffline(cache.getGeocode())); publishProgress(++i); } } - } catch (Exception e) { + } catch (final Exception e) { Log.e("FieldnoteExport.ExportTask generation", e); return false; } @@ -162,19 +151,22 @@ class FieldnoteExport extends AbstractExport { exportLocation.mkdirs(); - SimpleDateFormat fileNameDateFormat = new SimpleDateFormat("yyyyMMddHHmmss", Locale.US); + final SimpleDateFormat fileNameDateFormat = new SimpleDateFormat("yyyyMMddHHmmss", Locale.US); exportFile = new File(exportLocation.toString() + '/' + fileNameDateFormat.format(new Date()) + ".txt"); - Writer fw = null; + Writer fileWriter = null; + BufferedOutputStream buffer = null; try { - OutputStream os = new FileOutputStream(exportFile); - fw = new OutputStreamWriter(os, CharEncoding.UTF_16); - fw.write(fieldNoteBuffer.toString()); - } catch (IOException e) { + final OutputStream os = new FileOutputStream(exportFile); + buffer = new BufferedOutputStream(os); + fileWriter = new OutputStreamWriter(buffer, CharEncoding.UTF_16); + fileWriter.write(fieldNoteBuffer.toString()); + } catch (final IOException e) { Log.e("FieldnoteExport.ExportTask export", e); return false; } finally { - IOUtils.closeQuietly(fw); + IOUtils.closeQuietly(fileWriter); + IOUtils.closeQuietly(buffer); } if (upload) { @@ -189,17 +181,11 @@ class FieldnoteExport extends AbstractExport { } final String uri = "http://www.geocaching.com/my/uploadfieldnotes.aspx"; - String page = Network.getResponseData(Network.getRequest(uri)); + final String page = Login.getRequestLogged(uri, null); - if (!Login.getLoginStatus(page)) { - // Login.isActualLoginStatus() was wrong, we are not logged in - final StatusCode loginState = Login.login(); - if (loginState == StatusCode.NO_ERROR) { - page = Network.getResponseData(Network.getRequest(uri)); - } else { - Log.e("FieldnoteExport.ExportTask upload: No login (error: " + loginState + ')'); - return false; - } + if (StringUtils.isBlank(page)) { + Log.e("FieldnoteExport.ExportTask get page: No data from server"); + return false; } final String[] viewstates = Login.getViewstates(page); @@ -227,10 +213,8 @@ class FieldnoteExport extends AbstractExport { } @Override - protected void onPostExecute(Boolean result) { + protected void onPostExecuteInternal(Boolean result) { if (null != activity) { - progress.dismiss(); - if (result) { // if (onlyNew) { // // update last export time in settings when doing it ourself (currently we use the date check from gc.com) @@ -248,12 +232,12 @@ class FieldnoteExport extends AbstractExport { } @Override - protected void onProgressUpdate(Integer... status) { + protected void onProgressUpdateInternal(int status) { if (null != activity) { - if (STATUS_UPLOAD == status[0]) { - progress.setMessage(getString(R.string.export_fieldnotes_uploading)); + if (STATUS_UPLOAD == status) { + setMessage(getString(R.string.export_fieldnotes_uploading)); } else { - progress.setMessage(getString(R.string.export_fieldnotes_creating) + " (" + status[0] + ')'); + setMessage(getString(R.string.export_fieldnotes_creating) + " (" + status + ')'); } } } diff --git a/main/src/cgeo/geocaching/export/GpxExport.java b/main/src/cgeo/geocaching/export/GpxExport.java index b22f80f..e6b52bc 100644 --- a/main/src/cgeo/geocaching/export/GpxExport.java +++ b/main/src/cgeo/geocaching/export/GpxExport.java @@ -1,52 +1,37 @@ package cgeo.geocaching.export; import cgeo.geocaching.Geocache; -import cgeo.geocaching.LogEntry; import cgeo.geocaching.R; import cgeo.geocaching.Settings; -import cgeo.geocaching.Waypoint; -import cgeo.geocaching.cgData; +import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.activity.ActivityMixin; -import cgeo.geocaching.activity.Progress; -import cgeo.geocaching.enumerations.CacheAttribute; -import cgeo.geocaching.enumerations.LoadFlags; -import cgeo.geocaching.geopoint.Geopoint; -import cgeo.geocaching.utils.BaseUtils; +import cgeo.geocaching.utils.AsyncTaskWithProgress; import cgeo.geocaching.utils.Log; -import cgeo.geocaching.utils.XmlUtils; -import cgeo.org.kxml2.io.KXmlSerializer; - -import org.apache.commons.lang3.StringUtils; -import org.xmlpull.v1.XmlSerializer; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; -import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; -import android.os.AsyncTask; import android.os.Environment; import android.view.ContextThemeWrapper; import android.view.View; import android.widget.CheckBox; import android.widget.TextView; +import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Locale; class GpxExport extends AbstractExport { - private static final SimpleDateFormat dateFormatZ = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US); - public static final String PREFIX_XSI = "http://www.w3.org/2001/XMLSchema-instance"; - public static final String PREFIX_GPX = "http://www.topografix.com/GPX/1/0"; - public static final String PREFIX_GROUNDSPEAK = "http://www.groundspeak.com/cache/1/0"; protected GpxExport() { super(getString(R.string.export_gpx)); @@ -54,22 +39,23 @@ class GpxExport extends AbstractExport { @Override public void export(final List<Geocache> caches, final Activity activity) { + final String[] geocodes = getGeocodes(caches); if (null == activity) { // No activity given, so no user interaction possible. // Start export with default parameters. - new ExportTask(caches, null).execute((Void) null); + new ExportTask(null).execute(geocodes); } else { // Show configuration dialog - getExportDialog(caches, activity).show(); + getExportDialog(geocodes, activity).show(); } } - private Dialog getExportDialog(final List<Geocache> caches, final Activity activity) { - AlertDialog.Builder builder = new AlertDialog.Builder(activity); + private Dialog getExportDialog(final String[] geocodes, final Activity activity) { + final AlertDialog.Builder builder = new AlertDialog.Builder(activity); // AlertDialog has always dark style, so we have to apply it as well always - View layout = View.inflate(new ContextThemeWrapper(activity, R.style.dark), R.layout.gpx_export_dialog, null); + final View layout = View.inflate(new ContextThemeWrapper(activity, R.style.dark), R.layout.gpx_export_dialog, null); builder.setView(layout); final TextView text = (TextView) layout.findViewById(R.id.info); @@ -91,136 +77,68 @@ class GpxExport extends AbstractExport { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); - new ExportTask(caches, activity).execute((Void) null); + new ExportTask(activity).execute(geocodes); } }); return builder.create(); } - protected class ExportTask extends AsyncTask<Void, Integer, File> { - private final List<Geocache> caches; + private static String[] getGeocodes(final List<Geocache> caches) { + final ArrayList<String> allGeocodes = new ArrayList<String>(caches.size()); + for (final Geocache geocache : caches) { + allGeocodes.add(geocache.getGeocode()); + } + return allGeocodes.toArray(new String[allGeocodes.size()]); + } + + protected class ExportTask extends AsyncTaskWithProgress<String, File> { private final Activity activity; - private final Progress progress = new Progress(); /** * Instantiates and configures the task for exporting field notes. * - * @param caches - * The {@link List} of {@link cgeo.geocaching.Geocache} to be exported * @param activity * optional: Show a progress bar and toasts */ - public ExportTask(final List<Geocache> caches, final Activity activity) { - this.caches = caches; + public ExportTask(final Activity activity) { + super(activity, getProgressTitle()); this.activity = activity; } @Override - protected void onPreExecute() { - if (null != activity) { - progress.show(activity, null, getString(R.string.export) + ": " + getName(), ProgressDialog.STYLE_HORIZONTAL, null); - progress.setMaxProgressAndReset(caches.size()); - } - } - - @Override - protected File doInBackground(Void... params) { + protected File doInBackgroundInternal(String[] geocodes) { // quick check for being able to write the GPX file if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { return null; } + final List<String> allGeocodes = new ArrayList<String>(Arrays.asList(geocodes)); + + setMessage(cgeoapplication.getInstance().getResources().getQuantityString(R.plurals.cache_counts, allGeocodes.size(), allGeocodes.size())); + final SimpleDateFormat fileNameDateFormat = new SimpleDateFormat("yyyyMMddHHmmss", Locale.US); final File exportFile = new File(Settings.getGpxExportDir() + File.separatorChar + "export_" + fileNameDateFormat.format(new Date()) + ".gpx"); - FileWriter writer = null; + BufferedWriter writer = null; try { final File exportLocation = new File(Settings.getGpxExportDir()); exportLocation.mkdirs(); - final XmlSerializer gpx = new KXmlSerializer(); - writer = new FileWriter(exportFile); - gpx.setOutput(writer); - - gpx.startDocument("UTF-8", true); - gpx.setPrefix("", PREFIX_GPX); - gpx.setPrefix("xsi", PREFIX_XSI); - gpx.setPrefix("groundspeak", PREFIX_GROUNDSPEAK); - gpx.startTag(PREFIX_GPX, "gpx"); - gpx.attribute("", "version", "1.0"); - gpx.attribute("", "creator", "c:geo - http://www.cgeo.org/"); - gpx.attribute(PREFIX_XSI, "schemaLocation", - PREFIX_GPX + " http://www.topografix.com/GPX/1/0/gpx.xsd " + - PREFIX_GROUNDSPEAK + " http://www.groundspeak.com/cache/1/0/1/cache.xsd"); + writer = new BufferedWriter(new FileWriter(exportFile)); + new GpxSerializer().writeGPX(allGeocodes, writer, new GpxSerializer.ProgressListener() { - for (int i = 0; i < caches.size(); i++) { - final Geocache cache = cgData.loadCache(caches.get(i).getGeocode(), LoadFlags.LOAD_ALL_DB_ONLY); - - gpx.startTag(PREFIX_GPX, "wpt"); - gpx.attribute("", "lat", Double.toString(cache.getCoords().getLatitude())); - gpx.attribute("", "lon", Double.toString(cache.getCoords().getLongitude())); - - final Date hiddenDate = cache.getHiddenDate(); - if (hiddenDate != null) { - XmlUtils.simpleText(gpx, PREFIX_GPX, "time", dateFormatZ.format(hiddenDate)); + @Override + public void publishProgress(int countExported) { + this.publishProgress(countExported); } - - XmlUtils.multipleTexts(gpx, PREFIX_GPX, - "name", cache.getGeocode(), - "desc", cache.getName(), - "url", cache.getUrl(), - "urlname", cache.getName(), - "sym", cache.isFound() ? "Geocache Found" : "Geocache", - "type", "Geocache|" + cache.getType().pattern); - - gpx.startTag(PREFIX_GROUNDSPEAK, "cache"); - gpx.attribute("", "id", cache.getCacheId()); - gpx.attribute("", "available", !cache.isDisabled() ? "True" : "False"); - gpx.attribute("", "archives", cache.isArchived() ? "True" : "False"); - - XmlUtils.multipleTexts(gpx, PREFIX_GROUNDSPEAK, - "name", cache.getName(), - "placed_by", cache.getOwnerDisplayName(), - "owner", cache.getOwnerUserId(), - "type", cache.getType().pattern, - "container", cache.getSize().id, - "difficulty", Float.toString(cache.getDifficulty()), - "terrain", Float.toString(cache.getTerrain()), - "country", cache.getLocation(), - "state", "", - "encoded_hints", cache.getHint()); - - writeAttributes(gpx, cache); - - gpx.startTag(PREFIX_GROUNDSPEAK, "short_description"); - gpx.attribute("", "html", BaseUtils.containsHtml(cache.getShortDescription()) ? "True" : "False"); - gpx.text(cache.getShortDescription()); - gpx.endTag(PREFIX_GROUNDSPEAK, "short_description"); - - gpx.startTag(PREFIX_GROUNDSPEAK, "long_description"); - gpx.attribute("", "html", BaseUtils.containsHtml(cache.getDescription()) ? "True" : "False"); - gpx.text(cache.getDescription()); - gpx.endTag(PREFIX_GROUNDSPEAK, "long_description"); - - writeLogs(gpx, cache); - - gpx.endTag(PREFIX_GROUNDSPEAK, "cache"); - gpx.endTag(PREFIX_GPX, "wpt"); - - writeWaypoints(gpx, cache); - - publishProgress(i + 1); - } - - gpx.endTag(PREFIX_GPX, "gpx"); - gpx.endDocument(); - } catch (final IOException e) { + }); + } catch (final Exception e) { Log.e("GpxExport.ExportTask export", e); if (writer != null) { try { writer.close(); - } catch (IOException e1) { + } catch (final IOException e1) { // Ignore double error } } @@ -235,124 +153,13 @@ class GpxExport extends AbstractExport { return exportFile; } - private void writeWaypoints(final XmlSerializer gpx, final Geocache cache) throws IOException { - List<Waypoint> waypoints = cache.getWaypoints(); - List<Waypoint> ownWaypoints = new ArrayList<Waypoint>(waypoints.size()); - List<Waypoint> originWaypoints = new ArrayList<Waypoint>(waypoints.size()); - for (Waypoint wp : cache.getWaypoints()) { - if (wp.isUserDefined()) { - ownWaypoints.add(wp); - } else { - originWaypoints.add(wp); - } - } - int maxPrefix = 0; - for (Waypoint wp : originWaypoints) { - String prefix = wp.getPrefix(); - try { - final int numericPrefix = Integer.parseInt(prefix); - maxPrefix = Math.max(numericPrefix, maxPrefix); - } catch (NumberFormatException ex) { - // ignore non numeric prefix, as it should be unique in the list of non-own waypoints already - } - writeCacheWaypoint(gpx, wp, prefix); - } - // Prefixes must be unique. There use numeric strings as prefixes in OWN waypoints - for (Waypoint wp : ownWaypoints) { - maxPrefix++; - String prefix = StringUtils.leftPad(String.valueOf(maxPrefix), 2, '0'); - writeCacheWaypoint(gpx, wp, prefix); - } - } - - /** - * Writes one waypoint entry for cache waypoint. - * - * @param cache - * The - * @param wp - * @param prefix - * @throws IOException - */ - private void writeCacheWaypoint(final XmlSerializer gpx, final Waypoint wp, final String prefix) throws IOException { - final Geopoint coords = wp.getCoords(); - // TODO: create some extension to GPX to include waypoint without coords - if (coords != null) { - gpx.startTag(PREFIX_GPX, "wpt"); - gpx.attribute("", "lat", Double.toString(coords.getLatitude())); - gpx.attribute("", "lon", Double.toString(coords.getLongitude())); - XmlUtils.multipleTexts(gpx, PREFIX_GPX, - "name", prefix + wp.getGeocode().substring(2), - "cmt", wp.getNote(), - "desc", wp.getName(), - "sym", wp.getWaypointType().toString(), //TODO: Correct identifier string - "type", "Waypoint|" + wp.getWaypointType().toString()); //TODO: Correct identifier string - gpx.endTag(PREFIX_GPX, "wpt"); - } - } - - private void writeLogs(final XmlSerializer gpx, final Geocache cache) throws IOException { - if (cache.getLogs().isEmpty()) { - return; - } - gpx.startTag(PREFIX_GROUNDSPEAK, "logs"); - - for (LogEntry log : cache.getLogs()) { - gpx.startTag(PREFIX_GROUNDSPEAK, "log"); - gpx.attribute("", "id", Integer.toString(log.id)); - - XmlUtils.multipleTexts(gpx, PREFIX_GROUNDSPEAK, - "date", dateFormatZ.format(new Date(log.date)), - "type", log.type.type); - - gpx.startTag(PREFIX_GROUNDSPEAK, "finder"); - gpx.attribute("", "id", ""); - gpx.text(log.author); - gpx.endTag(PREFIX_GROUNDSPEAK, "finder"); - - gpx.startTag(PREFIX_GROUNDSPEAK, "text"); - gpx.attribute("", "encoded", "False"); - gpx.text(log.log); - gpx.endTag(PREFIX_GROUNDSPEAK, "text"); - - gpx.endTag(PREFIX_GROUNDSPEAK, "log"); - } - - gpx.endTag(PREFIX_GROUNDSPEAK, "logs"); - } - - private void writeAttributes(final XmlSerializer gpx, final Geocache cache) throws IOException { - if (cache.getAttributes().isEmpty()) { - return; - } - //TODO: Attribute conversion required: English verbose name, gpx-id - gpx.startTag(PREFIX_GROUNDSPEAK, "attributes"); - - for (String attribute : cache.getAttributes()) { - final CacheAttribute attr = CacheAttribute.getByRawName(CacheAttribute.trimAttributeName(attribute)); - if (attr == null) { - continue; - } - final boolean enabled = CacheAttribute.isEnabled(attribute); - - gpx.startTag(PREFIX_GROUNDSPEAK, "attribute"); - gpx.attribute("", "id", Integer.toString(attr.gcid)); - gpx.attribute("", "inc", enabled ? "1" : "0"); - gpx.text(attr.getL10n(enabled)); - gpx.endTag(PREFIX_GROUNDSPEAK, "attribute"); - } - - gpx.endTag(PREFIX_GROUNDSPEAK, "attributes"); - } - @Override - protected void onPostExecute(final File exportFile) { + protected void onPostExecuteInternal(final File exportFile) { if (null != activity) { - progress.dismiss(); if (exportFile != null) { ActivityMixin.showToast(activity, getName() + ' ' + getString(R.string.export_exportedto) + ": " + exportFile.toString()); if (Settings.getShareAfterExport()) { - Intent shareIntent = new Intent(); + final Intent shareIntent = new Intent(); shareIntent.setAction(Intent.ACTION_SEND); shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(exportFile)); shareIntent.setType("application/xml"); @@ -364,11 +171,5 @@ class GpxExport extends AbstractExport { } } - @Override - protected void onProgressUpdate(Integer... status) { - if (null != activity) { - progress.setProgress(status[0]); - } - } } } diff --git a/main/src/cgeo/geocaching/export/GpxSerializer.java b/main/src/cgeo/geocaching/export/GpxSerializer.java new file mode 100644 index 0000000..2c50721 --- /dev/null +++ b/main/src/cgeo/geocaching/export/GpxSerializer.java @@ -0,0 +1,256 @@ +package cgeo.geocaching.export; + +import cgeo.geocaching.Geocache; +import cgeo.geocaching.LogEntry; +import cgeo.geocaching.Waypoint; +import cgeo.geocaching.cgData; +import cgeo.geocaching.enumerations.CacheAttribute; +import cgeo.geocaching.enumerations.LoadFlags; +import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.utils.TextUtils; +import cgeo.geocaching.utils.XmlUtils; +import cgeo.org.kxml2.io.KXmlSerializer; + +import org.apache.commons.lang3.StringUtils; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.io.Writer; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +public final class GpxSerializer { + + private static final SimpleDateFormat dateFormatZ = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US); + public static final String PREFIX_XSI = "http://www.w3.org/2001/XMLSchema-instance"; + public static final String PREFIX_GPX = "http://www.topografix.com/GPX/1/0"; + public static final String PREFIX_GROUNDSPEAK = "http://www.groundspeak.com/cache/1/0"; + + /** + * During the export, only this number of geocaches is fully loaded into memory. + */ + public static final int CACHES_PER_BATCH = 100; + + /** + * counter for exported caches, used for progress reporting + */ + private int countExported; + private ProgressListener progressListener; + private XmlSerializer gpx; + + protected static interface ProgressListener { + + void publishProgress(int countExported); + + } + + public void writeGPX(List<String> allGeocodesIn, Writer writer, final ProgressListener progressListener) throws IOException { + // create a copy of the geocode list, as we need to modify it, but it might be immutable + final ArrayList<String> allGeocodes = new ArrayList<String>(allGeocodesIn); + + this.progressListener = progressListener; + gpx = new KXmlSerializer(); + gpx.setOutput(writer); + + gpx.startDocument("UTF-8", true); + gpx.setPrefix("", PREFIX_GPX); + gpx.setPrefix("xsi", PREFIX_XSI); + gpx.setPrefix("groundspeak", PREFIX_GROUNDSPEAK); + gpx.startTag(PREFIX_GPX, "gpx"); + gpx.attribute("", "version", "1.0"); + gpx.attribute("", "creator", "c:geo - http://www.cgeo.org/"); + gpx.attribute(PREFIX_XSI, "schemaLocation", + PREFIX_GPX + " http://www.topografix.com/GPX/1/0/gpx.xsd " + + PREFIX_GROUNDSPEAK + " http://www.groundspeak.com/cache/1/0/1/cache.xsd"); + + // Split the overall set of geocodes into small chunks. That is a compromise between memory efficiency (because + // we don't load all caches fully into memory) and speed (because we don't query each cache separately). + while (!allGeocodes.isEmpty()) { + final List<String> batch = allGeocodes.subList(0, Math.min(CACHES_PER_BATCH, allGeocodes.size())); + exportBatch(gpx, batch); + batch.clear(); + } + + gpx.endTag(PREFIX_GPX, "gpx"); + gpx.endDocument(); + } + + private void exportBatch(final XmlSerializer gpx, Collection<String> geocodesOfBatch) throws IOException { + final Set<Geocache> caches = cgData.loadCaches(geocodesOfBatch, LoadFlags.LOAD_ALL_DB_ONLY); + for (final Geocache cache : caches) { + gpx.startTag(PREFIX_GPX, "wpt"); + gpx.attribute("", "lat", Double.toString(cache.getCoords().getLatitude())); + gpx.attribute("", "lon", Double.toString(cache.getCoords().getLongitude())); + + final Date hiddenDate = cache.getHiddenDate(); + if (hiddenDate != null) { + XmlUtils.simpleText(gpx, PREFIX_GPX, "time", dateFormatZ.format(hiddenDate)); + } + + XmlUtils.multipleTexts(gpx, PREFIX_GPX, + "name", cache.getGeocode(), + "desc", cache.getName(), + "url", cache.getUrl(), + "urlname", cache.getName(), + "sym", cache.isFound() ? "Geocache Found" : "Geocache", + "type", "Geocache|" + cache.getType().pattern); + + gpx.startTag(PREFIX_GROUNDSPEAK, "cache"); + gpx.attribute("", "id", cache.getCacheId()); + gpx.attribute("", "available", !cache.isDisabled() ? "True" : "False"); + gpx.attribute("", "archives", cache.isArchived() ? "True" : "False"); + + XmlUtils.multipleTexts(gpx, PREFIX_GROUNDSPEAK, + "name", cache.getName(), + "placed_by", cache.getOwnerDisplayName(), + "owner", cache.getOwnerUserId(), + "type", cache.getType().pattern, + "container", cache.getSize().id, + "difficulty", Float.toString(cache.getDifficulty()), + "terrain", Float.toString(cache.getTerrain()), + "country", cache.getLocation(), + "state", "", + "encoded_hints", cache.getHint()); + + writeAttributes(cache); + + gpx.startTag(PREFIX_GROUNDSPEAK, "short_description"); + gpx.attribute("", "html", TextUtils.containsHtml(cache.getShortDescription()) ? "True" : "False"); + gpx.text(cache.getShortDescription()); + gpx.endTag(PREFIX_GROUNDSPEAK, "short_description"); + + gpx.startTag(PREFIX_GROUNDSPEAK, "long_description"); + gpx.attribute("", "html", TextUtils.containsHtml(cache.getDescription()) ? "True" : "False"); + gpx.text(cache.getDescription()); + gpx.endTag(PREFIX_GROUNDSPEAK, "long_description"); + + writeLogs(cache); + + gpx.endTag(PREFIX_GROUNDSPEAK, "cache"); + gpx.endTag(PREFIX_GPX, "wpt"); + + writeWaypoints(cache); + + countExported++; + if (progressListener != null) { + progressListener.publishProgress(countExported); + } + } + } + + private void writeWaypoints(final Geocache cache) throws IOException { + final List<Waypoint> waypoints = cache.getWaypoints(); + final List<Waypoint> ownWaypoints = new ArrayList<Waypoint>(waypoints.size()); + final List<Waypoint> originWaypoints = new ArrayList<Waypoint>(waypoints.size()); + for (final Waypoint wp : cache.getWaypoints()) { + if (wp.isUserDefined()) { + ownWaypoints.add(wp); + } else { + originWaypoints.add(wp); + } + } + int maxPrefix = 0; + for (final Waypoint wp : originWaypoints) { + final String prefix = wp.getPrefix(); + try { + final int numericPrefix = Integer.parseInt(prefix); + maxPrefix = Math.max(numericPrefix, maxPrefix); + } catch (final NumberFormatException ex) { + // ignore non numeric prefix, as it should be unique in the list of non-own waypoints already + } + writeCacheWaypoint(wp, prefix); + } + // Prefixes must be unique. There use numeric strings as prefixes in OWN waypoints + for (final Waypoint wp : ownWaypoints) { + maxPrefix++; + final String prefix = StringUtils.leftPad(String.valueOf(maxPrefix), 2, '0'); + writeCacheWaypoint(wp, prefix); + } + } + + /** + * Writes one waypoint entry for cache waypoint. + * + * @param cache + * The + * @param wp + * @param prefix + * @throws IOException + */ + private void writeCacheWaypoint(final Waypoint wp, final String prefix) throws IOException { + final Geopoint coords = wp.getCoords(); + // TODO: create some extension to GPX to include waypoint without coords + if (coords != null) { + gpx.startTag(PREFIX_GPX, "wpt"); + gpx.attribute("", "lat", Double.toString(coords.getLatitude())); + gpx.attribute("", "lon", Double.toString(coords.getLongitude())); + XmlUtils.multipleTexts(gpx, PREFIX_GPX, + "name", prefix + wp.getGeocode().substring(2), + "cmt", wp.getNote(), + "desc", wp.getName(), + "sym", wp.getWaypointType().toString(), //TODO: Correct identifier string + "type", "Waypoint|" + wp.getWaypointType().toString()); //TODO: Correct identifier string + gpx.endTag(PREFIX_GPX, "wpt"); + } + } + + private void writeLogs(final Geocache cache) throws IOException { + if (cache.getLogs().isEmpty()) { + return; + } + gpx.startTag(PREFIX_GROUNDSPEAK, "logs"); + + for (final LogEntry log : cache.getLogs()) { + gpx.startTag(PREFIX_GROUNDSPEAK, "log"); + gpx.attribute("", "id", Integer.toString(log.id)); + + XmlUtils.multipleTexts(gpx, PREFIX_GROUNDSPEAK, + "date", dateFormatZ.format(new Date(log.date)), + "type", log.type.type); + + gpx.startTag(PREFIX_GROUNDSPEAK, "finder"); + gpx.attribute("", "id", ""); + gpx.text(log.author); + gpx.endTag(PREFIX_GROUNDSPEAK, "finder"); + + gpx.startTag(PREFIX_GROUNDSPEAK, "text"); + gpx.attribute("", "encoded", "False"); + gpx.text(log.log); + gpx.endTag(PREFIX_GROUNDSPEAK, "text"); + + gpx.endTag(PREFIX_GROUNDSPEAK, "log"); + } + + gpx.endTag(PREFIX_GROUNDSPEAK, "logs"); + } + + private void writeAttributes(final Geocache cache) throws IOException { + if (cache.getAttributes().isEmpty()) { + return; + } + //TODO: Attribute conversion required: English verbose name, gpx-id + gpx.startTag(PREFIX_GROUNDSPEAK, "attributes"); + + for (final String attribute : cache.getAttributes()) { + final CacheAttribute attr = CacheAttribute.getByRawName(CacheAttribute.trimAttributeName(attribute)); + if (attr == null) { + continue; + } + final boolean enabled = CacheAttribute.isEnabled(attribute); + + gpx.startTag(PREFIX_GROUNDSPEAK, "attribute"); + gpx.attribute("", "id", Integer.toString(attr.gcid)); + gpx.attribute("", "inc", enabled ? "1" : "0"); + gpx.text(attr.getL10n(enabled)); + gpx.endTag(PREFIX_GROUNDSPEAK, "attribute"); + } + + gpx.endTag(PREFIX_GROUNDSPEAK, "attributes"); + } + +} diff --git a/main/src/cgeo/geocaching/files/AbstractFileListActivity.java b/main/src/cgeo/geocaching/files/AbstractFileListActivity.java index 5ff0d91..8b02eeb 100644 --- a/main/src/cgeo/geocaching/files/AbstractFileListActivity.java +++ b/main/src/cgeo/geocaching/files/AbstractFileListActivity.java @@ -89,7 +89,6 @@ public abstract class AbstractFileListActivity<T extends ArrayAdapter<File>> ext setTheme(); setContentView(R.layout.gpx); - setTitle(); Bundle extras = getIntent().getExtras(); if (extras != null) { @@ -146,11 +145,6 @@ public abstract class AbstractFileListActivity<T extends ArrayAdapter<File>> ext */ protected abstract List<File> getBaseFolders(); - /** - * Triggers the deriving class to set the title - */ - protected abstract void setTitle(); - private class SearchFilesThread extends Thread { private final FileListSelector selector = new FileListSelector(); diff --git a/main/src/cgeo/geocaching/files/FileParser.java b/main/src/cgeo/geocaching/files/FileParser.java index 50b65a1..f979d74 100644 --- a/main/src/cgeo/geocaching/files/FileParser.java +++ b/main/src/cgeo/geocaching/files/FileParser.java @@ -2,7 +2,9 @@ package cgeo.geocaching.files; import cgeo.geocaching.Geocache; import cgeo.geocaching.utils.CancellableHandler; +import cgeo.geocaching.utils.IOUtils; +import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; @@ -16,7 +18,7 @@ import java.util.concurrent.CancellationException; public abstract class FileParser { /** * Parses caches from input stream. - * + * * @param stream * @param progressHandler * for reporting parsing progress (in bytes read from input stream) @@ -38,11 +40,11 @@ public abstract class FileParser { * @throws ParserException */ public Collection<Geocache> parse(final File file, final CancellableHandler progressHandler) throws IOException, ParserException { - FileInputStream fis = new FileInputStream(file); + BufferedInputStream stream = new BufferedInputStream(new FileInputStream(file)); try { - return parse(fis, progressHandler); + return parse(stream, progressHandler); } finally { - fis.close(); + IOUtils.closeQuietly(stream); } } @@ -59,7 +61,7 @@ public abstract class FileParser { } return buffer; } finally { - input.close(); + IOUtils.closeQuietly(input); } } diff --git a/main/src/cgeo/geocaching/files/GPXImporter.java b/main/src/cgeo/geocaching/files/GPXImporter.java index b8dcbb3..34c4d02 100644 --- a/main/src/cgeo/geocaching/files/GPXImporter.java +++ b/main/src/cgeo/geocaching/files/GPXImporter.java @@ -1,458 +1,490 @@ -package cgeo.geocaching.files;
-
-import cgeo.geocaching.Geocache;
-import cgeo.geocaching.R;
-import cgeo.geocaching.SearchResult;
-import cgeo.geocaching.Settings;
-import cgeo.geocaching.StaticMapsProvider;
-import cgeo.geocaching.cgData;
-import cgeo.geocaching.activity.IAbstractActivity;
-import cgeo.geocaching.activity.Progress;
-import cgeo.geocaching.enumerations.LoadFlags;
-import cgeo.geocaching.utils.CancellableHandler;
-import cgeo.geocaching.utils.Log;
-
-import org.apache.commons.lang3.StringUtils;
-
-import android.app.Activity;
-import android.app.ProgressDialog;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Message;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.CancellationException;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-
-public class GPXImporter {
- static final int IMPORT_STEP_START = 0;
- static final int IMPORT_STEP_READ_FILE = 1;
- static final int IMPORT_STEP_READ_WPT_FILE = 2;
- static final int IMPORT_STEP_STORE_STATIC_MAPS = 4;
- static final int IMPORT_STEP_FINISHED = 5;
- static final int IMPORT_STEP_FINISHED_WITH_ERROR = 6;
- static final int IMPORT_STEP_CANCEL = 7;
- static final int IMPORT_STEP_CANCELED = 8;
- static final int IMPORT_STEP_STATIC_MAPS_SKIPPED = 9;
-
- public static final String GPX_FILE_EXTENSION = ".gpx";
- public static final String ZIP_FILE_EXTENSION = ".zip";
- public static final String WAYPOINTS_FILE_SUFFIX = "-wpts";
- public static final String WAYPOINTS_FILE_SUFFIX_AND_EXTENSION = WAYPOINTS_FILE_SUFFIX + GPX_FILE_EXTENSION;
-
- private static final List<String> GPX_MIME_TYPES = Arrays.asList("text/xml", "application/xml");
- private static final List<String> ZIP_MIME_TYPES = Arrays.asList("application/zip", "application/x-compressed", "application/x-zip-compressed", "application/x-zip", "application/octet-stream");
-
- private Progress progress = new Progress(true);
-
- private Resources res;
- private int listId;
- private IAbstractActivity fromActivity;
- private Handler importFinishedHandler;
-
- public GPXImporter(final IAbstractActivity fromActivity, final int listId, final Handler importFinishedHandler) {
- this.listId = listId;
- this.fromActivity = fromActivity;
- res = ((Activity) fromActivity).getResources();
- this.importFinishedHandler = importFinishedHandler;
- }
-
- /**
- * Import GPX file. Currently supports *.gpx, *.zip (containing gpx files, e.g. PQ queries) or *.loc files.
- *
- * @param file
- * the file to import
- */
- public void importGPX(final File file) {
- if (StringUtils.endsWithIgnoreCase(file.getName(), GPX_FILE_EXTENSION)) {
- new ImportGpxFileThread(file, listId, importStepHandler, progressHandler).start();
- } else if (StringUtils.endsWithIgnoreCase(file.getName(), ZIP_FILE_EXTENSION)) {
- new ImportGpxZipFileThread(file, listId, importStepHandler, progressHandler).start();
- } else {
- new ImportLocFileThread(file, listId, importStepHandler, progressHandler).start();
- }
- }
-
- /**
- * Import GPX provided via intent of activity that instantiated this GPXImporter.
- */
- public void importGPX() {
- final ContentResolver contentResolver = ((Activity) fromActivity).getContentResolver();
- final Intent intent = ((Activity) fromActivity).getIntent();
- final Uri uri = intent.getData();
-
- String mimeType = intent.getType();
- // if mimetype can't be determined (e.g. for emulators email app), derive it from uri file extension
- // contentResolver.getType(uri) doesn't help but throws exception for emulators email app
- // Permission Denial: reading com.android.email.provider.EmailProvider uri
- // Google search says: there is no solution for this problem
- // Gmail doesn't work at all, see #967
- if (mimeType == null) {
- if (StringUtils.endsWithIgnoreCase(uri.getPath(), GPX_FILE_EXTENSION)) {
- mimeType = "application/xml";
- } else {
- // if we can't determine a better type, default to zip import
- // emulator email sends e.g. content://com.android.email.attachmentprovider/1/1/RAW, mimetype=null
- mimeType = "application/zip";
- }
- }
-
- Log.i("importGPX: " + uri + ", mimetype=" + mimeType);
- if (GPX_MIME_TYPES.contains(mimeType)) {
- new ImportGpxAttachmentThread(uri, contentResolver, listId, importStepHandler, progressHandler).start();
- } else if (ZIP_MIME_TYPES.contains(mimeType)) {
- new ImportGpxZipAttachmentThread(uri, contentResolver, listId, importStepHandler, progressHandler).start();
- } else {
- importFinished();
- }
- }
-
- static abstract class ImportThread extends Thread {
- final int listId;
- final Handler importStepHandler;
- final CancellableHandler progressHandler;
-
- protected ImportThread(int listId, Handler importStepHandler, CancellableHandler progressHandler) {
- this.listId = listId;
- this.importStepHandler = importStepHandler;
- this.progressHandler = progressHandler;
- }
-
- @Override
- public void run() {
- try {
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_START));
- final Collection<Geocache> caches = doImport();
- Log.i("Imported successfully " + caches.size() + " caches.");
-
- final SearchResult search = new SearchResult();
- for (Geocache cache : caches) {
- search.addCache(cache);
- }
-
- if (Settings.isStoreOfflineMaps() || Settings.isStoreOfflineWpMaps()) {
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_STORE_STATIC_MAPS, R.string.gpx_import_store_static_maps, search.getCount()));
- boolean finishedWithoutCancel = importStaticMaps(search);
- // Skip last message if static maps where canceled
- if (!finishedWithoutCancel) {
- return;
- }
- }
-
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_FINISHED, search.getCount(), 0, search));
- } catch (IOException e) {
- Log.i("Importing caches failed - error reading data: " + e.getMessage());
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_FINISHED_WITH_ERROR, R.string.gpx_import_error_io, 0, e.getLocalizedMessage()));
- } catch (ParserException e) {
- Log.i("Importing caches failed - data format error" + e.getMessage());
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_FINISHED_WITH_ERROR, R.string.gpx_import_error_parser, 0, e.getLocalizedMessage()));
- } catch (CancellationException e) {
- Log.i("Importing caches canceled");
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_CANCELED));
- } catch (Exception e) {
- Log.e("Importing caches failed - unknown error: ", e);
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_FINISHED_WITH_ERROR, R.string.gpx_import_error_unexpected, 0, e.getLocalizedMessage()));
- }
- }
-
- protected abstract Collection<Geocache> doImport() throws IOException, ParserException;
-
- private boolean importStaticMaps(final SearchResult importedCaches) {
- int storedCacheMaps = 0;
- for (String geocode : importedCaches.getGeocodes()) {
- Geocache cache = cgData.loadCache(geocode, LoadFlags.LOAD_WAYPOINTS);
- Log.d("GPXImporter.ImportThread.importStaticMaps start downloadMaps for cache " + geocode);
- StaticMapsProvider.downloadMaps(cache);
- storedCacheMaps++;
- if (progressHandler.isCancelled()) {
- return false;
- }
- progressHandler.sendMessage(progressHandler.obtainMessage(0, storedCacheMaps, 0));
- }
- return true;
- }
- }
-
- static class ImportLocFileThread extends ImportThread {
- private final File file;
-
- public ImportLocFileThread(final File file, int listId, Handler importStepHandler, CancellableHandler progressHandler) {
- super(listId, importStepHandler, progressHandler);
- this.file = file;
- }
-
- @Override
- protected Collection<Geocache> doImport() throws IOException, ParserException {
- Log.i("Import LOC file: " + file.getAbsolutePath());
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_FILE, R.string.gpx_import_loading_caches, (int) file.length()));
- LocParser parser = new LocParser(listId);
- return parser.parse(file, progressHandler);
- }
- }
-
- static abstract class ImportGpxThread extends ImportThread {
-
- protected ImportGpxThread(int listId, Handler importStepHandler, CancellableHandler progressHandler) {
- super(listId, importStepHandler, progressHandler);
- }
-
- @Override
- protected Collection<Geocache> doImport() throws IOException, ParserException {
- try {
- // try to parse cache file as GPX 10
- return doImport(new GPX10Parser(listId));
- } catch (ParserException pe) {
- // didn't work -> lets try GPX11
- return doImport(new GPX11Parser(listId));
- }
- }
-
- protected abstract Collection<Geocache> doImport(GPXParser parser) throws IOException, ParserException;
- }
-
- static class ImportGpxFileThread extends ImportGpxThread {
- private final File cacheFile;
-
- public ImportGpxFileThread(final File file, int listId, Handler importStepHandler, CancellableHandler progressHandler) {
- super(listId, importStepHandler, progressHandler);
- this.cacheFile = file;
- }
-
- @Override
- protected Collection<Geocache> doImport(GPXParser parser) throws IOException, ParserException {
- Log.i("Import GPX file: " + cacheFile.getAbsolutePath());
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_FILE, R.string.gpx_import_loading_caches, (int) cacheFile.length()));
- Collection<Geocache> caches = parser.parse(cacheFile, progressHandler);
-
- final String wptsFilename = getWaypointsFileNameForGpxFile(cacheFile);
- if (wptsFilename != null) {
- final File wptsFile = new File(cacheFile.getParentFile(), wptsFilename);
- if (wptsFile.canRead()) {
- Log.i("Import GPX waypoint file: " + wptsFile.getAbsolutePath());
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_WPT_FILE, R.string.gpx_import_loading_waypoints, (int) wptsFile.length()));
- caches = parser.parse(wptsFile, progressHandler);
- }
- }
- return caches;
- }
- }
-
- static class ImportGpxAttachmentThread extends ImportGpxThread {
- private final Uri uri;
- private ContentResolver contentResolver;
-
- public ImportGpxAttachmentThread(Uri uri, ContentResolver contentResolver, int listId, Handler importStepHandler, CancellableHandler progressHandler) {
- super(listId, importStepHandler, progressHandler);
- this.uri = uri;
- this.contentResolver = contentResolver;
- }
-
- @Override
- protected Collection<Geocache> doImport(GPXParser parser) throws IOException, ParserException {
- Log.i("Import GPX from uri: " + uri);
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_FILE, R.string.gpx_import_loading_caches, -1));
- InputStream is = contentResolver.openInputStream(uri);
- try {
- return parser.parse(is, progressHandler);
- } finally {
- is.close();
- }
- }
- }
-
- static abstract class ImportGpxZipThread extends ImportGpxThread {
-
- protected ImportGpxZipThread(int listId, Handler importStepHandler, CancellableHandler progressHandler) {
- super(listId, importStepHandler, progressHandler);
- }
-
- @Override
- protected Collection<Geocache> doImport(GPXParser parser) throws IOException, ParserException {
- Collection<Geocache> caches = Collections.emptySet();
- // can't assume that GPX file comes before waypoint file in zip -> so we need two passes
- // 1. parse GPX files
- ZipInputStream zis = new ZipInputStream(getInputStream());
- try {
- for (ZipEntry zipEntry = zis.getNextEntry(); zipEntry != null; zipEntry = zis.getNextEntry()) {
- if (StringUtils.endsWithIgnoreCase(zipEntry.getName(), GPX_FILE_EXTENSION)) {
- if (!StringUtils.endsWithIgnoreCase(zipEntry.getName(), WAYPOINTS_FILE_SUFFIX_AND_EXTENSION)) {
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_FILE, R.string.gpx_import_loading_caches, (int) zipEntry.getSize()));
- caches = parser.parse(new NoCloseInputStream(zis), progressHandler);
- }
- } else {
- throw new ParserException("Imported zip is not a GPX zip file.");
- }
- zis.closeEntry();
- }
- } finally {
- zis.close();
- }
-
- // 2. parse waypoint files
- zis = new ZipInputStream(getInputStream());
- try {
- for (ZipEntry zipEntry = zis.getNextEntry(); zipEntry != null; zipEntry = zis.getNextEntry()) {
- if (StringUtils.endsWithIgnoreCase(zipEntry.getName(), WAYPOINTS_FILE_SUFFIX_AND_EXTENSION)) {
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_WPT_FILE, R.string.gpx_import_loading_waypoints, (int) zipEntry.getSize()));
- caches = parser.parse(new NoCloseInputStream(zis), progressHandler);
- }
- zis.closeEntry();
- }
- } finally {
- zis.close();
- }
-
- return caches;
- }
-
- protected abstract InputStream getInputStream() throws IOException;
- }
-
- static class ImportGpxZipFileThread extends ImportGpxZipThread {
- private final File cacheFile;
-
- public ImportGpxZipFileThread(final File file, int listId, Handler importStepHandler, CancellableHandler progressHandler) {
- super(listId, importStepHandler, progressHandler);
- this.cacheFile = file;
- Log.i("Import zipped GPX: " + file);
- }
-
- @Override
- protected InputStream getInputStream() throws IOException {
- return new FileInputStream(cacheFile);
- }
- }
-
- static class ImportGpxZipAttachmentThread extends ImportGpxZipThread {
- private final Uri uri;
- private ContentResolver contentResolver;
-
- public ImportGpxZipAttachmentThread(Uri uri, ContentResolver contentResolver, int listId, Handler importStepHandler, CancellableHandler progressHandler) {
- super(listId, importStepHandler, progressHandler);
- this.uri = uri;
- this.contentResolver = contentResolver;
- Log.i("Import zipped GPX from uri: " + uri);
- }
-
- @Override
- protected InputStream getInputStream() throws IOException {
- return contentResolver.openInputStream(uri);
- }
- }
-
- final private CancellableHandler progressHandler = new CancellableHandler() {
- @Override
- public void handleRegularMessage(Message msg) {
- progress.setProgress(msg.arg1);
- }
- };
-
- final private Handler importStepHandler = new Handler() {
- private boolean showProgressAfterCancel = false;
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case IMPORT_STEP_START:
- Message cancelMessage = importStepHandler.obtainMessage(IMPORT_STEP_CANCEL);
- progress.show((Context) fromActivity, res.getString(R.string.gpx_import_title_reading_file), res.getString(R.string.gpx_import_loading_caches), ProgressDialog.STYLE_HORIZONTAL, cancelMessage);
- break;
-
- case IMPORT_STEP_READ_FILE:
- case IMPORT_STEP_READ_WPT_FILE:
- progress.setMessage(res.getString(msg.arg1));
- progress.setMaxProgressAndReset(msg.arg2);
- break;
-
- case IMPORT_STEP_STORE_STATIC_MAPS:
- progress.dismiss();
- Message skipMessage = importStepHandler.obtainMessage(IMPORT_STEP_STATIC_MAPS_SKIPPED, msg.arg2, 0);
- progress.show((Context) fromActivity, res.getString(R.string.gpx_import_title_static_maps), res.getString(R.string.gpx_import_store_static_maps), ProgressDialog.STYLE_HORIZONTAL, skipMessage);
- progress.setMaxProgressAndReset(msg.arg2);
- break;
-
- case IMPORT_STEP_STATIC_MAPS_SKIPPED:
- progress.dismiss();
- progressHandler.cancel();
- StringBuilder bufferSkipped = new StringBuilder(20);
- bufferSkipped.append(res.getString(R.string.gpx_import_static_maps_skipped)).append(", ").append(msg.arg1).append(' ').append(res.getString(R.string.gpx_import_caches_imported));
- fromActivity.helpDialog(res.getString(R.string.gpx_import_title_caches_imported), bufferSkipped.toString());
- importFinished();
- break;
-
- case IMPORT_STEP_FINISHED:
- progress.dismiss();
- fromActivity.helpDialog(res.getString(R.string.gpx_import_title_caches_imported), msg.arg1 + " " + res.getString(R.string.gpx_import_caches_imported));
- importFinished();
- break;
-
- case IMPORT_STEP_FINISHED_WITH_ERROR:
- progress.dismiss();
- fromActivity.helpDialog(res.getString(R.string.gpx_import_title_caches_import_failed), res.getString(msg.arg1) + "\n\n" + msg.obj);
- importFinished();
- break;
-
- case IMPORT_STEP_CANCEL:
- progress.dismiss();
- progressHandler.cancel();
- break;
-
- case IMPORT_STEP_CANCELED:
- StringBuilder bufferCanceled = new StringBuilder(20);
- bufferCanceled.append(res.getString(R.string.gpx_import_canceled));
- if (showProgressAfterCancel) {
- bufferCanceled.append(", ").append(progress.getProgress()).append(' ').append(res.getString(R.string.gpx_import_caches_imported));
- }
- fromActivity.showShortToast(bufferCanceled.toString());
- importFinished();
- break;
-
- default:
- break;
- }
- }
- };
-
- /**
- * @param gpxfile
- * the gpx file
- * @return the expected file name of the waypoints file
- */
- static String getWaypointsFileNameForGpxFile(final File gpxfile) {
- if (gpxfile == null || !gpxfile.canRead()) {
- return null;
- }
- final String gpxFileName = gpxfile.getName();
- File dir = gpxfile.getParentFile();
- String[] filenameList = dir.list();
- for (String filename : filenameList) {
- if (!StringUtils.containsIgnoreCase(filename, WAYPOINTS_FILE_SUFFIX)) {
- continue;
- }
- String expectedGpxFileName = StringUtils.substringBeforeLast(filename, WAYPOINTS_FILE_SUFFIX)
- + StringUtils.substringAfterLast(filename, WAYPOINTS_FILE_SUFFIX);
- if (gpxFileName.equals(expectedGpxFileName)) {
- return filename;
- }
- }
- return null;
- }
-
- protected void importFinished() {
- if (importFinishedHandler != null) {
- importFinishedHandler.sendEmptyMessage(0);
- }
- }
-}
+package cgeo.geocaching.files; + +import cgeo.geocaching.Geocache; +import cgeo.geocaching.R; +import cgeo.geocaching.SearchResult; +import cgeo.geocaching.Settings; +import cgeo.geocaching.StaticMapsProvider; +import cgeo.geocaching.cgData; +import cgeo.geocaching.activity.IAbstractActivity; +import cgeo.geocaching.activity.Progress; +import cgeo.geocaching.enumerations.LoadFlags; +import cgeo.geocaching.utils.CancellableHandler; +import cgeo.geocaching.utils.Log; + +import org.apache.commons.lang3.StringUtils; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.net.Uri; +import android.os.Handler; +import android.os.Message; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CancellationException; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +public class GPXImporter { + static final int IMPORT_STEP_START = 0; + static final int IMPORT_STEP_READ_FILE = 1; + static final int IMPORT_STEP_READ_WPT_FILE = 2; + static final int IMPORT_STEP_STORE_STATIC_MAPS = 4; + static final int IMPORT_STEP_FINISHED = 5; + static final int IMPORT_STEP_FINISHED_WITH_ERROR = 6; + static final int IMPORT_STEP_CANCEL = 7; + static final int IMPORT_STEP_CANCELED = 8; + static final int IMPORT_STEP_STATIC_MAPS_SKIPPED = 9; + + public static final String GPX_FILE_EXTENSION = ".gpx"; + public static final String LOC_FILE_EXTENSION = ".loc"; + public static final String ZIP_FILE_EXTENSION = ".zip"; + public static final String WAYPOINTS_FILE_SUFFIX = "-wpts"; + public static final String WAYPOINTS_FILE_SUFFIX_AND_EXTENSION = WAYPOINTS_FILE_SUFFIX + GPX_FILE_EXTENSION; + + private static final List<String> GPX_MIME_TYPES = Arrays.asList("text/xml", "application/xml"); + private static final List<String> ZIP_MIME_TYPES = Arrays.asList("application/zip", "application/x-compressed", "application/x-zip-compressed", "application/x-zip", "application/octet-stream"); + + private final Progress progress = new Progress(true); + + private final Resources res; + private final int listId; + private final IAbstractActivity fromActivity; + private final Handler importFinishedHandler; + + public GPXImporter(final IAbstractActivity fromActivity, final int listId, final Handler importFinishedHandler) { + this.listId = listId; + this.fromActivity = fromActivity; + res = ((Activity) fromActivity).getResources(); + this.importFinishedHandler = importFinishedHandler; + } + + /** + * Import GPX file. Currently supports *.gpx, *.zip (containing gpx files, e.g. PQ queries) or *.loc files. + * + * @param file + * the file to import + */ + public void importGPX(final File file) { + if (StringUtils.endsWithIgnoreCase(file.getName(), GPX_FILE_EXTENSION)) { + new ImportGpxFileThread(file, listId, importStepHandler, progressHandler).start(); + } else if (StringUtils.endsWithIgnoreCase(file.getName(), ZIP_FILE_EXTENSION)) { + new ImportGpxZipFileThread(file, listId, importStepHandler, progressHandler).start(); + } else { + new ImportLocFileThread(file, listId, importStepHandler, progressHandler).start(); + } + } + + /** + * Import GPX provided via intent of activity that instantiated this GPXImporter. + */ + public void importGPX() { + final ContentResolver contentResolver = ((Activity) fromActivity).getContentResolver(); + final Intent intent = ((Activity) fromActivity).getIntent(); + final Uri uri = intent.getData(); + + String mimeType = intent.getType(); + // if mimetype can't be determined (e.g. for emulators email app), derive it from uri file extension + // contentResolver.getType(uri) doesn't help but throws exception for emulators email app + // Permission Denial: reading com.android.email.provider.EmailProvider uri + // Google search says: there is no solution for this problem + // Gmail doesn't work at all, see #967 + if (mimeType == null) { + if (StringUtils.endsWithIgnoreCase(uri.getPath(), GPX_FILE_EXTENSION) || StringUtils.endsWithIgnoreCase(uri.getPath(), LOC_FILE_EXTENSION)) { + mimeType = "application/xml"; + } else { + // if we can't determine a better type, default to zip import + // emulator email sends e.g. content://com.android.email.attachmentprovider/1/1/RAW, mimetype=null + mimeType = "application/zip"; + } + } + + Log.i("importGPX: " + uri + ", mimetype=" + mimeType); + if (GPX_MIME_TYPES.contains(mimeType)) { + if (StringUtils.endsWithIgnoreCase(uri.getPath(), LOC_FILE_EXTENSION)) { + new ImportLocAttachmentThread(uri, contentResolver, listId, importStepHandler, progressHandler).start(); + } else { + new ImportGpxAttachmentThread(uri, contentResolver, listId, importStepHandler, progressHandler).start(); + } + } else if (ZIP_MIME_TYPES.contains(mimeType)) { + new ImportGpxZipAttachmentThread(uri, contentResolver, listId, importStepHandler, progressHandler).start(); + } else { + importFinished(); + } + } + + static abstract class ImportThread extends Thread { + final int listId; + final Handler importStepHandler; + final CancellableHandler progressHandler; + + protected ImportThread(int listId, Handler importStepHandler, CancellableHandler progressHandler) { + this.listId = listId; + this.importStepHandler = importStepHandler; + this.progressHandler = progressHandler; + } + + @Override + public void run() { + try { + importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_START)); + final Collection<Geocache> caches = doImport(); + Log.i("Imported successfully " + caches.size() + " caches."); + + final SearchResult search = new SearchResult(caches); + // Do not put imported caches into the cachecache. That would consume lots of memory for no benefit. + + if (Settings.isStoreOfflineMaps() || Settings.isStoreOfflineWpMaps()) { + importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_STORE_STATIC_MAPS, R.string.gpx_import_store_static_maps, search.getCount())); + final boolean finishedWithoutCancel = importStaticMaps(search); + // Skip last message if static maps where canceled + if (!finishedWithoutCancel) { + return; + } + } + + importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_FINISHED, search.getCount(), 0, search)); + } catch (final IOException e) { + Log.i("Importing caches failed - error reading data: ", e); + importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_FINISHED_WITH_ERROR, R.string.gpx_import_error_io, 0, e.getLocalizedMessage())); + } catch (final ParserException e) { + Log.i("Importing caches failed - data format error", e); + importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_FINISHED_WITH_ERROR, R.string.gpx_import_error_parser, 0, e.getLocalizedMessage())); + } catch (final CancellationException e) { + Log.i("Importing caches canceled"); + importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_CANCELED)); + } catch (final Exception e) { + Log.e("Importing caches failed - unknown error: ", e); + importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_FINISHED_WITH_ERROR, R.string.gpx_import_error_unexpected, 0, e.getLocalizedMessage())); + } + } + + protected abstract Collection<Geocache> doImport() throws IOException, ParserException; + + private boolean importStaticMaps(final SearchResult importedCaches) { + int storedCacheMaps = 0; + for (final String geocode : importedCaches.getGeocodes()) { + final Geocache cache = cgData.loadCache(geocode, LoadFlags.LOAD_WAYPOINTS); + if (cache != null) { + Log.d("GPXImporter.ImportThread.importStaticMaps start downloadMaps for cache " + geocode); + StaticMapsProvider.downloadMaps(cache); + } else { + Log.d("GPXImporter.ImportThread.importStaticMaps: no data found for " + geocode); + } + storedCacheMaps++; + if (progressHandler.isCancelled()) { + return false; + } + progressHandler.sendMessage(progressHandler.obtainMessage(0, storedCacheMaps, 0)); + } + return true; + } + } + + static class ImportLocFileThread extends ImportThread { + private final File file; + + public ImportLocFileThread(final File file, int listId, Handler importStepHandler, CancellableHandler progressHandler) { + super(listId, importStepHandler, progressHandler); + this.file = file; + } + + @Override + protected Collection<Geocache> doImport() throws IOException, ParserException { + Log.i("Import LOC file: " + file.getAbsolutePath()); + importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_FILE, R.string.gpx_import_loading_caches, (int) file.length())); + final LocParser parser = new LocParser(listId); + return parser.parse(file, progressHandler); + } + } + + static class ImportLocAttachmentThread extends ImportThread { + private final Uri uri; + private final ContentResolver contentResolver; + + public ImportLocAttachmentThread(Uri uri, ContentResolver contentResolver, int listId, Handler importStepHandler, CancellableHandler progressHandler) { + super(listId, importStepHandler, progressHandler); + this.uri = uri; + this.contentResolver = contentResolver; + } + + @Override + protected Collection<Geocache> doImport() throws IOException, ParserException { + Log.i("Import LOC from uri: " + uri); + importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_FILE, R.string.gpx_import_loading_caches, -1)); + final InputStream is = contentResolver.openInputStream(uri); + final LocParser parser = new LocParser(listId); + try { + return parser.parse(is, progressHandler); + } finally { + is.close(); + } + } + } + + static abstract class ImportGpxThread extends ImportThread { + + protected ImportGpxThread(int listId, Handler importStepHandler, CancellableHandler progressHandler) { + super(listId, importStepHandler, progressHandler); + } + + @Override + protected Collection<Geocache> doImport() throws IOException, ParserException { + try { + // try to parse cache file as GPX 10 + return doImport(new GPX10Parser(listId)); + } catch (final ParserException pe) { + // didn't work -> lets try GPX11 + return doImport(new GPX11Parser(listId)); + } + } + + protected abstract Collection<Geocache> doImport(GPXParser parser) throws IOException, ParserException; + } + + static class ImportGpxFileThread extends ImportGpxThread { + private final File cacheFile; + + public ImportGpxFileThread(final File file, int listId, Handler importStepHandler, CancellableHandler progressHandler) { + super(listId, importStepHandler, progressHandler); + this.cacheFile = file; + } + + @Override + protected Collection<Geocache> doImport(GPXParser parser) throws IOException, ParserException { + Log.i("Import GPX file: " + cacheFile.getAbsolutePath()); + importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_FILE, R.string.gpx_import_loading_caches, (int) cacheFile.length())); + Collection<Geocache> caches = parser.parse(cacheFile, progressHandler); + + final String wptsFilename = getWaypointsFileNameForGpxFile(cacheFile); + if (wptsFilename != null) { + final File wptsFile = new File(cacheFile.getParentFile(), wptsFilename); + if (wptsFile.canRead()) { + Log.i("Import GPX waypoint file: " + wptsFile.getAbsolutePath()); + importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_WPT_FILE, R.string.gpx_import_loading_waypoints, (int) wptsFile.length())); + caches = parser.parse(wptsFile, progressHandler); + } + } + return caches; + } + } + + static class ImportGpxAttachmentThread extends ImportGpxThread { + private final Uri uri; + private final ContentResolver contentResolver; + + public ImportGpxAttachmentThread(Uri uri, ContentResolver contentResolver, int listId, Handler importStepHandler, CancellableHandler progressHandler) { + super(listId, importStepHandler, progressHandler); + this.uri = uri; + this.contentResolver = contentResolver; + } + + @Override + protected Collection<Geocache> doImport(GPXParser parser) throws IOException, ParserException { + Log.i("Import GPX from uri: " + uri); + importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_FILE, R.string.gpx_import_loading_caches, -1)); + final InputStream is = contentResolver.openInputStream(uri); + try { + return parser.parse(is, progressHandler); + } finally { + is.close(); + } + } + } + + static abstract class ImportGpxZipThread extends ImportGpxThread { + + protected ImportGpxZipThread(int listId, Handler importStepHandler, CancellableHandler progressHandler) { + super(listId, importStepHandler, progressHandler); + } + + @Override + protected Collection<Geocache> doImport(GPXParser parser) throws IOException, ParserException { + Collection<Geocache> caches = Collections.emptySet(); + // can't assume that GPX file comes before waypoint file in zip -> so we need two passes + // 1. parse GPX files + ZipInputStream zis = new ZipInputStream(new BufferedInputStream(getInputStream())); + try { + for (ZipEntry zipEntry = zis.getNextEntry(); zipEntry != null; zipEntry = zis.getNextEntry()) { + if (StringUtils.endsWithIgnoreCase(zipEntry.getName(), GPX_FILE_EXTENSION)) { + if (!StringUtils.endsWithIgnoreCase(zipEntry.getName(), WAYPOINTS_FILE_SUFFIX_AND_EXTENSION)) { + importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_FILE, R.string.gpx_import_loading_caches, (int) zipEntry.getSize())); + caches = parser.parse(new NoCloseInputStream(zis), progressHandler); + } + } else { + throw new ParserException("Imported zip is not a GPX zip file."); + } + zis.closeEntry(); + } + } finally { + zis.close(); + } + + // 2. parse waypoint files + zis = new ZipInputStream(new BufferedInputStream(getInputStream())); + try { + for (ZipEntry zipEntry = zis.getNextEntry(); zipEntry != null; zipEntry = zis.getNextEntry()) { + if (StringUtils.endsWithIgnoreCase(zipEntry.getName(), WAYPOINTS_FILE_SUFFIX_AND_EXTENSION)) { + importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_WPT_FILE, R.string.gpx_import_loading_waypoints, (int) zipEntry.getSize())); + caches = parser.parse(new NoCloseInputStream(zis), progressHandler); + } + zis.closeEntry(); + } + } finally { + zis.close(); + } + + return caches; + } + + protected abstract InputStream getInputStream() throws IOException; + } + + static class ImportGpxZipFileThread extends ImportGpxZipThread { + private final File cacheFile; + + public ImportGpxZipFileThread(final File file, int listId, Handler importStepHandler, CancellableHandler progressHandler) { + super(listId, importStepHandler, progressHandler); + this.cacheFile = file; + Log.i("Import zipped GPX: " + file); + } + + @Override + protected InputStream getInputStream() throws IOException { + return new FileInputStream(cacheFile); + } + } + + static class ImportGpxZipAttachmentThread extends ImportGpxZipThread { + private final Uri uri; + private final ContentResolver contentResolver; + + public ImportGpxZipAttachmentThread(Uri uri, ContentResolver contentResolver, int listId, Handler importStepHandler, CancellableHandler progressHandler) { + super(listId, importStepHandler, progressHandler); + this.uri = uri; + this.contentResolver = contentResolver; + Log.i("Import zipped GPX from uri: " + uri); + } + + @Override + protected InputStream getInputStream() throws IOException { + return contentResolver.openInputStream(uri); + } + } + + final private CancellableHandler progressHandler = new CancellableHandler() { + @Override + public void handleRegularMessage(Message msg) { + progress.setProgress(msg.arg1); + } + }; + + final private Handler importStepHandler = new Handler() { + private final boolean showProgressAfterCancel = false; + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case IMPORT_STEP_START: + final Message cancelMessage = importStepHandler.obtainMessage(IMPORT_STEP_CANCEL); + progress.show((Context) fromActivity, res.getString(R.string.gpx_import_title_reading_file), res.getString(R.string.gpx_import_loading_caches), ProgressDialog.STYLE_HORIZONTAL, cancelMessage); + break; + + case IMPORT_STEP_READ_FILE: + case IMPORT_STEP_READ_WPT_FILE: + progress.setMessage(res.getString(msg.arg1)); + progress.setMaxProgressAndReset(msg.arg2); + break; + + case IMPORT_STEP_STORE_STATIC_MAPS: + progress.dismiss(); + final Message skipMessage = importStepHandler.obtainMessage(IMPORT_STEP_STATIC_MAPS_SKIPPED, msg.arg2, 0); + progress.show((Context) fromActivity, res.getString(R.string.gpx_import_title_static_maps), res.getString(R.string.gpx_import_store_static_maps), ProgressDialog.STYLE_HORIZONTAL, skipMessage); + progress.setMaxProgressAndReset(msg.arg2); + break; + + case IMPORT_STEP_STATIC_MAPS_SKIPPED: + progress.dismiss(); + progressHandler.cancel(); + final StringBuilder bufferSkipped = new StringBuilder(20); + bufferSkipped.append(res.getString(R.string.gpx_import_static_maps_skipped)).append(", ").append(msg.arg1).append(' ').append(res.getString(R.string.gpx_import_caches_imported)); + fromActivity.helpDialog(res.getString(R.string.gpx_import_title_caches_imported), bufferSkipped.toString()); + importFinished(); + break; + + case IMPORT_STEP_FINISHED: + progress.dismiss(); + fromActivity.helpDialog(res.getString(R.string.gpx_import_title_caches_imported), msg.arg1 + " " + res.getString(R.string.gpx_import_caches_imported)); + importFinished(); + break; + + case IMPORT_STEP_FINISHED_WITH_ERROR: + progress.dismiss(); + fromActivity.helpDialog(res.getString(R.string.gpx_import_title_caches_import_failed), res.getString(msg.arg1) + "\n\n" + msg.obj); + importFinished(); + break; + + case IMPORT_STEP_CANCEL: + progress.dismiss(); + progressHandler.cancel(); + break; + + case IMPORT_STEP_CANCELED: + final StringBuilder bufferCanceled = new StringBuilder(20); + bufferCanceled.append(res.getString(R.string.gpx_import_canceled)); + if (showProgressAfterCancel) { + bufferCanceled.append(", ").append(progress.getProgress()).append(' ').append(res.getString(R.string.gpx_import_caches_imported)); + } + fromActivity.showShortToast(bufferCanceled.toString()); + importFinished(); + break; + + default: + break; + } + } + }; + + /** + * @param gpxfile + * the gpx file + * @return the expected file name of the waypoints file + */ + static String getWaypointsFileNameForGpxFile(final File gpxfile) { + if (gpxfile == null || !gpxfile.canRead()) { + return null; + } + final String gpxFileName = gpxfile.getName(); + final File dir = gpxfile.getParentFile(); + final String[] filenameList = dir.list(); + for (final String filename : filenameList) { + if (!StringUtils.containsIgnoreCase(filename, WAYPOINTS_FILE_SUFFIX)) { + continue; + } + final String expectedGpxFileName = StringUtils.substringBeforeLast(filename, WAYPOINTS_FILE_SUFFIX) + + StringUtils.substringAfterLast(filename, WAYPOINTS_FILE_SUFFIX); + if (gpxFileName.equals(expectedGpxFileName)) { + return filename; + } + } + return null; + } + + protected void importFinished() { + if (importFinishedHandler != null) { + importFinishedHandler.sendEmptyMessage(0); + } + } +} diff --git a/main/src/cgeo/geocaching/files/GPXParser.java b/main/src/cgeo/geocaching/files/GPXParser.java index 96c90cc..f5ae381 100644 --- a/main/src/cgeo/geocaching/files/GPXParser.java +++ b/main/src/cgeo/geocaching/files/GPXParser.java @@ -183,6 +183,7 @@ public abstract class GPXParser extends FileParser { R.string.attribute_treeclimbing_yes, // 64 R.string.attribute_frontyard_yes, // 65 R.string.attribute_teamwork_yes, // 66 + R.string.attribute_geotour_yes, // 67 }; private static final String YES = "_yes"; private static final String NO = "_no"; @@ -205,7 +206,7 @@ public abstract class GPXParser extends FileParser { String stringName; try { stringName = cgeoapplication.getInstance().getResources().getResourceName(stringId); - } catch (NullPointerException e) { + } catch (final NullPointerException e) { return null; } if (stringName == null) { @@ -271,7 +272,7 @@ public abstract class GPXParser extends FileParser { Double.valueOf(longitude))); } } - } catch (Exception e) { + } catch (final Exception e) { Log.w("Failed to parse waypoint's latitude and/or longitude."); } } @@ -295,11 +296,7 @@ public abstract class GPXParser extends FileParser { } } - if (StringUtils.isNotBlank(cache.getGeocode()) - && cache.getCoords() != null - && ((type == null && sym == null) - || StringUtils.contains(type, "geocache") - || StringUtils.contains(sym, "geocache"))) { + if (isValidForImport()) { fixCache(cache); cache.setListId(listId); cache.setDetailed(true); @@ -316,6 +313,8 @@ public abstract class GPXParser extends FileParser { // finally store the cache in the database result.add(geocode); cgData.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); + + // avoid the cachecache using lots of memory for caches which the user did not actually look at cgData.removeAllFromCache(); showProgressMessage(progressHandler, progressStream.getProgress()); } else if (StringUtils.isNotBlank(cache.getName()) @@ -364,7 +363,7 @@ public abstract class GPXParser extends FileParser { public void end(String body) { try { cache.setHidden(parseDate(body)); - } catch (Exception e) { + } catch (final Exception e) { Log.w("Failed to parse cache date", e); } } @@ -444,18 +443,29 @@ public abstract class GPXParser extends FileParser { } final MatcherWrapper matcherCode = new MatcherWrapper(patternUrlGeocode, url); if (matcherCode.matches()) { - String geocode = matcherCode.group(1); + final String geocode = matcherCode.group(1); cache.setGeocode(geocode); } } }); + // waypoint.urlname (name for waymarks) + waypoint.getChild(namespace, "urlname").setEndTextElementListener(new EndTextElementListener() { + + @Override + public void end(String urlName) { + if (cache.getName().equals(cache.getGeocode()) && StringUtils.startsWith(cache.getGeocode(), "WM")) { + cache.setName(StringUtils.trim(urlName)); + } + } + }); + // for GPX 1.0, cache info comes from waypoint node (so called private children, // for GPX 1.1 from extensions node final Element cacheParent = getCacheParent(waypoint); // GSAK extensions - for (String gsakNamespace : GSAK_NS) { + for (final String gsakNamespace : GSAK_NS) { final Element gsak = cacheParent.getChild(gsakNamespace, "wptExtension"); gsak.getChild(gsakNamespace, "Watch").setEndTextElementListener(new EndTextElementListener() { @@ -473,7 +483,7 @@ public abstract class GPXParser extends FileParser { } // 3 different versions of the GC schema - for (String nsGC : nsGCList) { + for (final String nsGC : nsGCList) { // waypoints.cache final Element gcCache = cacheParent.getChild(nsGC, "cache"); @@ -491,7 +501,7 @@ public abstract class GPXParser extends FileParser { if (attrs.getIndex("available") > -1) { cache.setDisabled(!attrs.getValue("available").equalsIgnoreCase("true")); } - } catch (Exception e) { + } catch (final Exception e) { Log.w("Failed to parse cache attributes."); } } @@ -560,14 +570,14 @@ public abstract class GPXParser extends FileParser { public void start(Attributes attrs) { try { if (attrs.getIndex("id") > -1 && attrs.getIndex("inc") > -1) { - int attributeId = Integer.parseInt(attrs.getValue("id")); - boolean attributeActive = Integer.parseInt(attrs.getValue("inc")) != 0; - String internalId = CacheAttributeTranslator.getInternalId(attributeId, attributeActive); + final int attributeId = Integer.parseInt(attrs.getValue("id")); + final boolean attributeActive = Integer.parseInt(attrs.getValue("inc")) != 0; + final String internalId = CacheAttributeTranslator.getInternalId(attributeId, attributeActive); if (internalId != null) { cache.getAttributes().add(internalId); } } - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { // nothing } } @@ -580,7 +590,7 @@ public abstract class GPXParser extends FileParser { public void end(String body) { try { cache.setDifficulty(Float.parseFloat(body)); - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { Log.w("Failed to parse difficulty", e); } } @@ -593,7 +603,7 @@ public abstract class GPXParser extends FileParser { public void end(String body) { try { cache.setTerrain(Float.parseFloat(body)); - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { Log.w("Failed to parse terrain", e); } } @@ -617,7 +627,7 @@ public abstract class GPXParser extends FileParser { @Override public void end(String state) { - String trimmedState = state.trim(); + final String trimmedState = state.trim(); if (StringUtils.isNotEmpty(trimmedState)) { // state can be completely empty if (StringUtils.isBlank(cache.getLocation())) { cache.setLocation(validate(state)); @@ -670,7 +680,7 @@ public abstract class GPXParser extends FileParser { if (attrs.getIndex("ref") > -1) { trackable.setGeocode(attrs.getValue("ref")); } - } catch (Exception e) { + } catch (final Exception e) { // nothing } } @@ -714,7 +724,7 @@ public abstract class GPXParser extends FileParser { if (attrs.getIndex("id") > -1) { log.id = Integer.parseInt(attrs.getValue("id")); } - } catch (Exception e) { + } catch (final Exception e) { // nothing } } @@ -737,7 +747,7 @@ public abstract class GPXParser extends FileParser { public void end(String body) { try { log.date = parseDate(body).getTime(); - } catch (Exception e) { + } catch (final Exception e) { Log.w("Failed to parse log date", e); } } @@ -776,7 +786,7 @@ public abstract class GPXParser extends FileParser { progressStream = new ProgressInputStream(stream); Xml.parse(progressStream, Xml.Encoding.UTF_8, root.getContentHandler()); return cgData.loadCaches(result, EnumSet.of(LoadFlag.LOAD_DB_MINIMAL)); - } catch (SAXException e) { + } catch (final SAXException e) { Log.w("Cannot parse .gpx file as GPX " + version + ": could not parse XML - ", e); throw new ParserException("Cannot parse .gpx file as GPX " + version + ": could not parse XML", e); } @@ -823,7 +833,7 @@ public abstract class GPXParser extends FileParser { return WaypointType.FINAL; } // this is not fully correct, but lets also look for localized waypoint types - for (WaypointType waypointType : WaypointType.ALL_TYPES_EXCEPT_OWN_AND_ORIGINAL) { + for (final WaypointType waypointType : WaypointType.ALL_TYPES_EXCEPT_OWN_AND_ORIGINAL) { final String localized = waypointType.getL10n(); if (StringUtils.isNotEmpty(localized)) { if (localized.equalsIgnoreCase(sym)) { @@ -891,4 +901,17 @@ public abstract class GPXParser extends FileParser { } } } + + private boolean isValidForImport() { + if (StringUtils.isBlank(cache.getGeocode())) { + return false; + } + if (cache.getCoords() == null) { + return false; + } + return ((type == null && sym == null) + || StringUtils.contains(type, "geocache") + || StringUtils.contains(sym, "geocache") + || StringUtils.containsIgnoreCase(sym, "waymark")); + } } diff --git a/main/src/cgeo/geocaching/files/LocParser.java b/main/src/cgeo/geocaching/files/LocParser.java index fe290c3..1cfb2a3 100644 --- a/main/src/cgeo/geocaching/files/LocParser.java +++ b/main/src/cgeo/geocaching/files/LocParser.java @@ -27,19 +27,13 @@ import java.util.regex.Pattern; public final class LocParser extends FileParser { + private static final String NAME_OWNER_SEPARATOR = " by "; private static final Pattern patternGeocode = Pattern .compile("name id=\"([^\"]+)\""); private static final Pattern patternLat = Pattern .compile("lat=\"([^\"]+)\""); private static final Pattern patternLon = Pattern .compile("lon=\"([^\"]+)\""); - // premium only >> - private static final Pattern patternDifficulty = Pattern - .compile("<difficulty>([^<]+)</difficulty>"); - private static final Pattern patternTerrain = Pattern - .compile("<terrain>([^<]+)</terrain>"); - private static final Pattern patternContainer = Pattern - .compile("<container>([^<]+)</container>"); private static final Pattern patternName = Pattern.compile("CDATA\\[([^\\]]+)\\]"); private static final CacheSize[] SIZES = { @@ -82,6 +76,7 @@ public final class LocParser extends FileParser { if (StringUtils.isBlank(cache.getName())) { cache.setName(coord.getName()); } + cache.setOwnerUserId(coord.getOwnerUserId()); } static Map<String, Geocache> parseCoordinates(final String fileContent) { @@ -110,7 +105,7 @@ public final class LocParser extends FileParser { try { return new Geopoint(Double.valueOf(latitude), Double.valueOf(longitude)); } catch (NumberFormatException e) { - Log.e("LOC format has changed"); + Log.e("LOC format has changed", e); } // fall back to parser, just in case the format changes return new Geopoint(latitude, longitude); @@ -156,7 +151,11 @@ public final class LocParser extends FileParser { final MatcherWrapper matcherName = new MatcherWrapper(patternName, pointString); if (matcherName.find()) { final String name = matcherName.group(1).trim(); - cache.setName(StringUtils.substringBeforeLast(name, " by ").trim()); + String ownerName = StringUtils.trim(StringUtils.substringAfterLast(name, NAME_OWNER_SEPARATOR)); + if (StringUtils.isEmpty(cache.getOwnerUserId()) && StringUtils.isNotEmpty(ownerName)) { + cache.setOwnerUserId(ownerName); + } + cache.setName(StringUtils.substringBeforeLast(name, NAME_OWNER_SEPARATOR).trim()); } else { cache.setName(cache.getGeocode()); } @@ -167,20 +166,20 @@ public final class LocParser extends FileParser { cache.setCoords(parsePoint(matcherLat.group(1).trim(), matcherLon.group(1).trim())); } - final MatcherWrapper matcherDifficulty = new MatcherWrapper(patternDifficulty, pointString); + final String difficulty = StringUtils.substringBetween(pointString, "<difficulty>", "</difficulty>"); + final String terrain = StringUtils.substringBetween(pointString, "<terrain>", "</terrain>"); + final String container = StringUtils.substringBetween(pointString, "<container>", "</container"); try { - if (matcherDifficulty.find()) { - cache.setDifficulty(Float.parseFloat(matcherDifficulty.group(1).trim())); + if (StringUtils.isNotBlank(difficulty)) { + cache.setDifficulty(Float.parseFloat(difficulty.trim())); } - final MatcherWrapper matcherTerrain = new MatcherWrapper(patternTerrain, pointString); - if (matcherTerrain.find()) { - cache.setTerrain(Float.parseFloat(matcherTerrain.group(1).trim())); + if (StringUtils.isNotBlank(terrain)) { + cache.setTerrain(Float.parseFloat(terrain.trim())); } - final MatcherWrapper matcherContainer = new MatcherWrapper(patternContainer, pointString); - if (matcherContainer.find()) { - final int size = Integer.parseInt(matcherContainer.group(1).trim()); + if (StringUtils.isNotBlank(container)) { + final int size = Integer.parseInt(container.trim()); if (size >= 1 && size <= 8) { cache.setSize(SIZES[size - 1]); } diff --git a/main/src/cgeo/geocaching/files/LocalStorage.java b/main/src/cgeo/geocaching/files/LocalStorage.java index f59f15c..0f3e0e1 100644 --- a/main/src/cgeo/geocaching/files/LocalStorage.java +++ b/main/src/cgeo/geocaching/files/LocalStorage.java @@ -2,14 +2,18 @@ package cgeo.geocaching.files; import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.utils.CryptUtils; +import cgeo.geocaching.utils.IOUtils; import cgeo.geocaching.utils.Log; import ch.boye.httpclientandroidlib.Header; import ch.boye.httpclientandroidlib.HttpResponse; + import org.apache.commons.lang3.StringUtils; import android.os.Environment; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; @@ -275,19 +279,14 @@ public class LocalStorage { destination.getParentFile().mkdirs(); InputStream input = null; - OutputStream output; + OutputStream output = null; try { - input = new FileInputStream(source); - output = new FileOutputStream(destination); + input = new BufferedInputStream(new FileInputStream(source)); + output = new BufferedOutputStream(new FileOutputStream(destination)); } catch (FileNotFoundException e) { Log.e("LocalStorage.copy: could not open file", e); - if (input != null) { - try { - input.close(); - } catch (IOException e1) { - // ignore - } - } + IOUtils.closeQuietly(input); + IOUtils.closeQuietly(output); return false; } diff --git a/main/src/cgeo/geocaching/files/SimpleDirChooser.java b/main/src/cgeo/geocaching/files/SimpleDirChooser.java index 7520e2e..6b2366c 100644 --- a/main/src/cgeo/geocaching/files/SimpleDirChooser.java +++ b/main/src/cgeo/geocaching/files/SimpleDirChooser.java @@ -2,11 +2,11 @@ package cgeo.geocaching.files; import cgeo.geocaching.Intents; import cgeo.geocaching.R; +import cgeo.geocaching.activity.AbstractListActivity; import cgeo.geocaching.activity.ActivityMixin; import org.apache.commons.lang3.StringUtils; -import android.app.ListActivity; import android.content.Context; import android.content.Intent; import android.net.Uri; @@ -31,7 +31,7 @@ import java.util.List; /** * Dialog for choosing a file or directory. */ -public class SimpleDirChooser extends ListActivity { +public class SimpleDirChooser extends AbstractListActivity { private static final String PARENT_DIR = ".. "; private File currentDir; private FileArrayAdapter adapter; @@ -46,7 +46,6 @@ public class SimpleDirChooser extends ListActivity { ActivityMixin.setTheme(this); setContentView(R.layout.simple_dir_chooser); - setTitle(this.getResources().getString(R.string.simple_dir_chooser_title)); fill(currentDir); @@ -106,13 +105,13 @@ public class SimpleDirChooser extends ListActivity { public class FileArrayAdapter extends ArrayAdapter<Option> { - private Context content; + private Context context; private int id; private List<Option> items; public FileArrayAdapter(Context context, int simpleDirItemResId, List<Option> objects) { super(context, simpleDirItemResId, objects); - this.content = context; + this.context = context; this.id = simpleDirItemResId; this.items = objects; } @@ -126,7 +125,7 @@ public class SimpleDirChooser extends ListActivity { public View getView(int position, View convertView, ViewGroup parent) { View v = convertView; if (v == null) { - LayoutInflater vi = (LayoutInflater) content.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + LayoutInflater vi = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); v = vi.inflate(id, null); } @@ -187,13 +186,12 @@ public class SimpleDirChooser extends ListActivity { if (currentOption != lastOption) { currentOption.setChecked(true); lastPosition = position; - okButton.setEnabled(true); - okButton.setVisibility(View.VISIBLE); } else { lastPosition = -1; - okButton.setEnabled(false); - okButton.setVisibility(View.INVISIBLE); } + final boolean enabled = currentOption.isChecked() && !currentOption.getName().equals(PARENT_DIR); + okButton.setEnabled(enabled); + okButton.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE); adapter.notifyDataSetChanged(); } } @@ -238,7 +236,7 @@ public class SimpleDirChooser extends ListActivity { @Override public boolean accept(File dir, String filename) { File file = new File(dir, filename); - return file.isDirectory(); + return file.isDirectory() && file.canWrite(); } } diff --git a/main/src/cgeo/geocaching/filter/AttributeFilter.java b/main/src/cgeo/geocaching/filter/AttributeFilter.java index 4b6f382..cadcf49 100644 --- a/main/src/cgeo/geocaching/filter/AttributeFilter.java +++ b/main/src/cgeo/geocaching/filter/AttributeFilter.java @@ -1,16 +1,16 @@ package cgeo.geocaching.filter; -import cgeo.geocaching.R; import cgeo.geocaching.Geocache; +import cgeo.geocaching.R; import cgeo.geocaching.cgData; import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.enumerations.LoadFlags.LoadFlag; -import org.apache.commons.lang3.StringUtils; - import android.content.res.Resources; import java.util.EnumSet; +import java.util.LinkedList; +import java.util.List; class AttributeFilter extends AbstractFilter { @@ -24,13 +24,7 @@ class AttributeFilter extends AbstractFilter { private static String getName(final String attribute, final Resources res, final String packageName) { // dynamically search for a translation of the attribute final int id = res.getIdentifier(attribute, "string", packageName); - if (id > 0) { - final String translated = res.getString(id); - if (StringUtils.isNotBlank(translated)) { - return translated; - } - } - return attribute; + return id > 0 ? res.getString(id) : attribute; } @Override @@ -45,14 +39,13 @@ class AttributeFilter extends AbstractFilter { public static class Factory implements IFilterFactory { @Override - public IFilter[] getFilters() { + public List<IFilter> getFilters() { final String packageName = cgeoapplication.getInstance().getBaseContext().getPackageName(); final Resources res = cgeoapplication.getInstance().getResources(); - final String[] ids = res.getStringArray(R.array.attribute_ids); - final IFilter[] filters = new IFilter[ids.length]; - for (int i = 0; i < ids.length; i++) { - filters[i] = new AttributeFilter(getName("attribute_" + ids[i], res, packageName), ids[i]); + final List<IFilter> filters = new LinkedList<IFilter>(); + for (final String id: res.getStringArray(R.array.attribute_ids)) { + filters.add(new AttributeFilter(getName("attribute_" + id, res, packageName), id)); } return filters; } diff --git a/main/src/cgeo/geocaching/filter/DifficultyFilter.java b/main/src/cgeo/geocaching/filter/DifficultyFilter.java index c0ec61a..8099a51 100644 --- a/main/src/cgeo/geocaching/filter/DifficultyFilter.java +++ b/main/src/cgeo/geocaching/filter/DifficultyFilter.java @@ -4,6 +4,7 @@ import cgeo.geocaching.Geocache; import cgeo.geocaching.R; import java.util.ArrayList; +import java.util.List; class DifficultyFilter extends AbstractRangeFilter { @@ -19,12 +20,12 @@ class DifficultyFilter extends AbstractRangeFilter { public static class Factory implements IFilterFactory { @Override - public IFilter[] getFilters() { + public List<IFilter> getFilters() { final ArrayList<IFilter> filters = new ArrayList<IFilter>(5); for (int difficulty = 1; difficulty <= 5; difficulty++) { filters.add(new DifficultyFilter(difficulty)); } - return filters.toArray(new IFilter[filters.size()]); + return filters; } } diff --git a/main/src/cgeo/geocaching/filter/FilterUserInterface.java b/main/src/cgeo/geocaching/filter/FilterUserInterface.java index be63a08..a1d42cc 100644 --- a/main/src/cgeo/geocaching/filter/FilterUserInterface.java +++ b/main/src/cgeo/geocaching/filter/FilterUserInterface.java @@ -16,6 +16,7 @@ import android.widget.ArrayAdapter; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.List; public final class FilterUserInterface { @@ -101,9 +102,9 @@ public final class FilterUserInterface { } private void selectFromFactory(final IFilterFactory factory, final String menuTitle, final RunnableWithArgument<IFilter> runAfterwards) { - final IFilter[] filters = factory.getFilters(); - if (filters.length == 1) { - runAfterwards.run(filters[0]); + final List<IFilter> filters = Collections.unmodifiableList(factory.getFilters()); + if (filters.size() == 1) { + runAfterwards.run(filters.get(0)); return; } @@ -114,7 +115,7 @@ public final class FilterUserInterface { builder.setAdapter(adapter, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int item) { - runAfterwards.run(filters[item]); + runAfterwards.run(filters.get(item)); } }); diff --git a/main/src/cgeo/geocaching/filter/IFilterFactory.java b/main/src/cgeo/geocaching/filter/IFilterFactory.java index 3491fd7..e750639 100644 --- a/main/src/cgeo/geocaching/filter/IFilterFactory.java +++ b/main/src/cgeo/geocaching/filter/IFilterFactory.java @@ -1,5 +1,7 @@ package cgeo.geocaching.filter; +import java.util.List; + interface IFilterFactory { - public IFilter[] getFilters(); + public List<? extends IFilter> getFilters(); } diff --git a/main/src/cgeo/geocaching/filter/ModifiedFilter.java b/main/src/cgeo/geocaching/filter/ModifiedFilter.java index f3e57de..74befda 100644 --- a/main/src/cgeo/geocaching/filter/ModifiedFilter.java +++ b/main/src/cgeo/geocaching/filter/ModifiedFilter.java @@ -4,6 +4,9 @@ import cgeo.geocaching.Geocache; import cgeo.geocaching.R; import cgeo.geocaching.cgeoapplication; +import java.util.Collections; +import java.util.List; + class ModifiedFilter extends AbstractFilter implements IFilterFactory { public ModifiedFilter() { @@ -17,7 +20,7 @@ class ModifiedFilter extends AbstractFilter implements IFilterFactory { } @Override - public IFilter[] getFilters() { - return new IFilter[] { this }; + public List<ModifiedFilter> getFilters() { + return Collections.singletonList(this); } } diff --git a/main/src/cgeo/geocaching/filter/OriginFilter.java b/main/src/cgeo/geocaching/filter/OriginFilter.java index a880092..bd4e41e 100644 --- a/main/src/cgeo/geocaching/filter/OriginFilter.java +++ b/main/src/cgeo/geocaching/filter/OriginFilter.java @@ -7,6 +7,7 @@ import cgeo.geocaching.connector.IConnector; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.List; public class OriginFilter extends AbstractFilter { @@ -25,7 +26,7 @@ public class OriginFilter extends AbstractFilter { public static final class Factory implements IFilterFactory { @Override - public IFilter[] getFilters() { + public List<OriginFilter> getFilters() { final ArrayList<OriginFilter> filters = new ArrayList<OriginFilter>(); for (IConnector connector : ConnectorFactory.getConnectors()) { filters.add(new OriginFilter(connector)); @@ -40,7 +41,7 @@ public class OriginFilter extends AbstractFilter { } }); - return filters.toArray(new OriginFilter[filters.size()]); + return filters; } } diff --git a/main/src/cgeo/geocaching/filter/SizeFilter.java b/main/src/cgeo/geocaching/filter/SizeFilter.java index 7a34c83..8ddc475 100644 --- a/main/src/cgeo/geocaching/filter/SizeFilter.java +++ b/main/src/cgeo/geocaching/filter/SizeFilter.java @@ -3,7 +3,8 @@ package cgeo.geocaching.filter; import cgeo.geocaching.Geocache; import cgeo.geocaching.enumerations.CacheSize; -import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; class SizeFilter extends AbstractFilter { private final CacheSize cacheSize; @@ -26,15 +27,15 @@ class SizeFilter extends AbstractFilter { public static class Factory implements IFilterFactory { @Override - public IFilter[] getFilters() { + public List<IFilter> getFilters() { final CacheSize[] cacheSizes = CacheSize.values(); - final ArrayList<SizeFilter> filters = new ArrayList<SizeFilter>(); + final List<IFilter> filters = new LinkedList<IFilter>(); for (CacheSize cacheSize : cacheSizes) { if (cacheSize != CacheSize.UNKNOWN) { filters.add(new SizeFilter(cacheSize)); } } - return filters.toArray(new SizeFilter[filters.size()]); + return filters; } } diff --git a/main/src/cgeo/geocaching/filter/StateFilter.java b/main/src/cgeo/geocaching/filter/StateFilter.java index 0df47c1..e18128d 100644 --- a/main/src/cgeo/geocaching/filter/StateFilter.java +++ b/main/src/cgeo/geocaching/filter/StateFilter.java @@ -9,6 +9,7 @@ import android.content.res.Resources; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.List; abstract class StateFilter extends AbstractFilter { @@ -86,17 +87,41 @@ abstract class StateFilter extends AbstractFilter { } } + static class StateStoredFilter extends StateFilter { + public StateStoredFilter() { + super(res.getString(R.string.cache_status_stored)); + } + + @Override + public boolean accepts(Geocache cache) { + return cache.isOffline(); + } + } + + static class StateNotStoredFilter extends StateFilter { + public StateNotStoredFilter() { + super(res.getString(R.string.cache_status_not_stored)); + } + + @Override + public boolean accepts(Geocache cache) { + return !cache.isOffline(); + } + } + public static class Factory implements IFilterFactory { @Override - public IFilter[] getFilters() { - final ArrayList<StateFilter> filters = new ArrayList<StateFilter>(); + public List<StateFilter> getFilters() { + final List<StateFilter> filters = new ArrayList<StateFilter>(6); filters.add(new StateFoundFilter()); filters.add(new StateArchivedFilter()); filters.add(new StateDisabledFilter()); filters.add(new StatePremiumFilter()); filters.add(new StateNonPremiumFilter()); filters.add(new StateOfflineLogFilter()); + filters.add(new StateStoredFilter()); + filters.add(new StateNotStoredFilter()); Collections.sort(filters, new Comparator<StateFilter>() { @@ -106,7 +131,7 @@ abstract class StateFilter extends AbstractFilter { } }); - return filters.toArray(new StateFilter[filters.size()]); + return filters; } } diff --git a/main/src/cgeo/geocaching/filter/TerrainFilter.java b/main/src/cgeo/geocaching/filter/TerrainFilter.java index f7703d5..87372c6 100644 --- a/main/src/cgeo/geocaching/filter/TerrainFilter.java +++ b/main/src/cgeo/geocaching/filter/TerrainFilter.java @@ -1,10 +1,10 @@ package cgeo.geocaching.filter; - -import cgeo.geocaching.R; import cgeo.geocaching.Geocache; +import cgeo.geocaching.R; import java.util.ArrayList; +import java.util.List; class TerrainFilter extends AbstractRangeFilter { @@ -19,12 +19,12 @@ class TerrainFilter extends AbstractRangeFilter { public static class Factory implements IFilterFactory { @Override - public IFilter[] getFilters() { + public List<IFilter> getFilters() { final ArrayList<IFilter> filters = new ArrayList<IFilter>(5); for (int terrain = 1; terrain <= 5; terrain++) { filters.add(new TerrainFilter(terrain)); } - return filters.toArray(new IFilter[filters.size()]); + return filters; } } diff --git a/main/src/cgeo/geocaching/filter/TrackablesFilter.java b/main/src/cgeo/geocaching/filter/TrackablesFilter.java index 3225daa..5eff8a7 100644 --- a/main/src/cgeo/geocaching/filter/TrackablesFilter.java +++ b/main/src/cgeo/geocaching/filter/TrackablesFilter.java @@ -1,9 +1,12 @@ package cgeo.geocaching.filter; -import cgeo.geocaching.R; import cgeo.geocaching.Geocache; +import cgeo.geocaching.R; import cgeo.geocaching.cgeoapplication; +import java.util.Collections; +import java.util.List; + class TrackablesFilter extends AbstractFilter implements IFilterFactory { public TrackablesFilter() { super(cgeoapplication.getInstance().getString(R.string.caches_filter_track)); @@ -15,8 +18,8 @@ class TrackablesFilter extends AbstractFilter implements IFilterFactory { } @Override - public IFilter[] getFilters() { - return new IFilter[] { this }; + public List<TrackablesFilter> getFilters() { + return Collections.singletonList(this); } } diff --git a/main/src/cgeo/geocaching/filter/TypeFilter.java b/main/src/cgeo/geocaching/filter/TypeFilter.java index eeab552..ea0ccff 100644 --- a/main/src/cgeo/geocaching/filter/TypeFilter.java +++ b/main/src/cgeo/geocaching/filter/TypeFilter.java @@ -3,7 +3,8 @@ package cgeo.geocaching.filter; import cgeo.geocaching.Geocache; import cgeo.geocaching.enumerations.CacheType; -import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; class TypeFilter extends AbstractFilter { private final CacheType cacheType; @@ -26,15 +27,15 @@ class TypeFilter extends AbstractFilter { public static class Factory implements IFilterFactory { @Override - public IFilter[] getFilters() { + public List<IFilter> getFilters() { final CacheType[] types = CacheType.values(); - final ArrayList<IFilter> filters = new ArrayList<IFilter>(types.length); + final List<IFilter> filters = new LinkedList<IFilter>(); for (CacheType cacheType : types) { if (cacheType != CacheType.ALL) { filters.add(new TypeFilter(cacheType)); } } - return filters.toArray(new IFilter[filters.size()]); + return filters; } } diff --git a/main/src/cgeo/geocaching/gcvote/GCVote.java b/main/src/cgeo/geocaching/gcvote/GCVote.java index a053f31..f6cfb84 100644 --- a/main/src/cgeo/geocaching/gcvote/GCVote.java +++ b/main/src/cgeo/geocaching/gcvote/GCVote.java @@ -173,12 +173,15 @@ public final class GCVote { /** * Transmit user vote to gcvote.com - * + * * @param cache * @param vote - * @return + * @return {@code true} if the rating was submitted successfully */ public static boolean setRating(Geocache cache, double vote) { + if (!Settings.isGCvoteLogin()) { + return false; + } if (!cache.supportsGCVote()) { return false; } diff --git a/main/src/cgeo/geocaching/geopoint/Geopoint.java b/main/src/cgeo/geocaching/geopoint/Geopoint.java index dbe0aa4..1973b03 100644 --- a/main/src/cgeo/geocaching/geopoint/Geopoint.java +++ b/main/src/cgeo/geocaching/geopoint/Geopoint.java @@ -297,7 +297,7 @@ public final class Geopoint implements ICoordinates, Parcelable { /** * Returns formatted coordinates with default format. * Default format is decimalminutes, e.g. N 52° 36.123 E 010° 03.456 - * + * * @return formatted coordinates */ @Override @@ -345,7 +345,7 @@ public final class Geopoint implements ICoordinates, Parcelable { return result.getDouble("elevation"); } } catch (Exception e) { - Log.w("cgBase.getElevation", e); + Log.w("Geopoint.getElevation", e); } return null; diff --git a/main/src/cgeo/geocaching/loaders/CoordsGeocacheListLoader.java b/main/src/cgeo/geocaching/loaders/CoordsGeocacheListLoader.java index ca2461c..09ea459 100644 --- a/main/src/cgeo/geocaching/loaders/CoordsGeocacheListLoader.java +++ b/main/src/cgeo/geocaching/loaders/CoordsGeocacheListLoader.java @@ -19,7 +19,11 @@ public class CoordsGeocacheListLoader extends AbstractSearchLoader { @Override public SearchResult runSearch() { - SearchResult search = GCParser.searchByCoords(coords, Settings.getCacheType(), Settings.isShowCaptcha(), this); + + SearchResult search = new SearchResult(); + if (Settings.isGCConnectorActive()) { + search = GCParser.searchByCoords(coords, Settings.getCacheType(), Settings.isShowCaptcha(), this); + } for (ISearchByCenter centerConn : ConnectorFactory.getSearchByCenterConnectors()) { if (centerConn.isActivated()) { diff --git a/main/src/cgeo/geocaching/loaders/UrlLoader.java b/main/src/cgeo/geocaching/loaders/UrlLoader.java index abafd5f..9f6c3d5 100644 --- a/main/src/cgeo/geocaching/loaders/UrlLoader.java +++ b/main/src/cgeo/geocaching/loaders/UrlLoader.java @@ -28,7 +28,7 @@ public class UrlLoader extends AsyncTaskLoader<String> { try { return Network.getResponseData(Network.getRequest(url, params)); } catch (final Exception e) { - Log.w("cgeovisit.UrlLoader.loadInBackground", e); + Log.w("UrlLoader.loadInBackground", e); return null; } } diff --git a/main/src/cgeo/geocaching/maps/AbstractMap.java b/main/src/cgeo/geocaching/maps/AbstractMap.java index c028e51..d9ee751 100644 --- a/main/src/cgeo/geocaching/maps/AbstractMap.java +++ b/main/src/cgeo/geocaching/maps/AbstractMap.java @@ -63,8 +63,6 @@ public abstract class AbstractMap { public abstract void goHome(View view); - public abstract void goManual(View view); - public abstract void onSaveInstanceState(final Bundle outState); } diff --git a/main/src/cgeo/geocaching/maps/CGeoMap.java b/main/src/cgeo/geocaching/maps/CGeoMap.java index 30bbadf..c876192 100644 --- a/main/src/cgeo/geocaching/maps/CGeoMap.java +++ b/main/src/cgeo/geocaching/maps/CGeoMap.java @@ -86,7 +86,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto /** max. number of caches displayed in the Live Map */ public static final int MAX_CACHES = 500; - /** Controls the behaviour of the map */ + /** Controls the behavior of the map */ public enum MapMode { /** Live Map */ LIVE, @@ -116,24 +116,10 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto private static final String EXTRAS_MAP_MODE = "mapMode"; private static final String EXTRAS_LIVE_ENABLED = "liveEnabled"; - private static final int MENU_SELECT_MAPVIEW = 1; - private static final int MENU_MAP_LIVE = 2; - private static final int MENU_STORE_CACHES = 3; - private static final int SUBMENU_MODES = 4; - private static final int MENU_TRAIL_MODE = 81; - private static final int MENU_THEME_MODE = 82; - private static final int MENU_CIRCLE_MODE = 83; - private static final int SUBMENU_STRATEGY = 5; - private static final int MENU_STRATEGY_FASTEST = 51; - private static final int MENU_STRATEGY_FAST = 52; - private static final int MENU_STRATEGY_AUTO = 53; - private static final int MENU_STRATEGY_DETAILED = 74; - - private static final int MENU_AS_LIST = 7; - private static final String BUNDLE_MAP_SOURCE = "mapSource"; private static final String BUNDLE_MAP_STATE = "mapState"; private static final String BUNDLE_LIVE_ENABLED = "liveEnabled"; + private static final String BUNDLE_TRAIL_HISTORY = "trailHistory"; private Resources res = null; private MapItemFactory mapItemFactory = null; @@ -173,8 +159,6 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto private static final int[][] INSET_FOUND = { { 0, 0, 21, 28 }, { 0, 0, 25, 35 } }; // top left, 12x12 / 16x16 private static final int[][] INSET_USERMODIFIEDCOORDS = { { 21, 28, 0, 0 }, { 19, 25, 0, 0 } }; // bottom right, 12x12 / 26x26 private static final int[][] INSET_PERSONALNOTE = { { 0, 28, 21, 0 }, { 0, 25, 19, 0 } }; // bottom left, 12x12 / 26x26 - private static final int MENU_GROUP_MAP_SOURCES = 1; - private static final int MENU_GROUP_MAP_STRATEGY = 2; private SparseArray<LayerDrawable> overlaysCache = new SparseArray<LayerDrawable>(); /** Count of caches currently visible */ @@ -360,6 +344,9 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto outState.putInt(BUNDLE_MAP_SOURCE, currentSourceId); outState.putIntArray(BUNDLE_MAP_STATE, currentMapState()); outState.putBoolean(BUNDLE_LIVE_ENABLED, isLiveEnabled); + if (overlayPosition != null) { + outState.putParcelableArrayList(BUNDLE_TRAIL_HISTORY, overlayPosition.getHistory()); + } } @Override @@ -382,9 +369,9 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto if (extras != null) { mapMode = (MapMode) extras.get(EXTRAS_MAP_MODE); isLiveEnabled = extras.getBoolean(EXTRAS_LIVE_ENABLED, false); - searchIntent = (SearchResult) extras.getParcelable(EXTRAS_SEARCH); + searchIntent = extras.getParcelable(EXTRAS_SEARCH); geocodeIntent = extras.getString(EXTRAS_GEOCODE); - coordsIntent = (Geopoint) extras.getParcelable(EXTRAS_COORDS); + coordsIntent = extras.getParcelable(EXTRAS_COORDS); waypointTypeIntent = WaypointType.findById(extras.getString(EXTRAS_WPTTYPE)); mapStateIntent = extras.getIntArray(EXTRAS_MAPSTATE); mapTitle = extras.getString(EXTRAS_MAP_TITLE); @@ -397,11 +384,14 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto mapTitle = res.getString(R.string.map_map); } + ArrayList<Location> trailHistory = null; + // Get fresh map information from the bundle if any if (savedInstanceState != null) { currentSourceId = savedInstanceState.getInt(BUNDLE_MAP_SOURCE, Settings.getMapSource().getNumericalId()); mapStateIntent = savedInstanceState.getIntArray(BUNDLE_MAP_STATE); isLiveEnabled = savedInstanceState.getBoolean(BUNDLE_LIVE_ENABLED, false); + trailHistory = savedInstanceState.getParcelableArrayList(BUNDLE_TRAIL_HISTORY); } else { currentSourceId = Settings.getMapSource().getNumericalId(); } @@ -438,6 +428,9 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto if (overlayPosition == null) { overlayPosition = mapView.createAddPositionOverlay(activity); + if (trailHistory != null) { + overlayPosition.setHistory(trailHistory); + } } if (overlayScale == null) { @@ -541,36 +534,16 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto @Override public boolean onCreateOptionsMenu(Menu menu) { + // menu inflation happens in Google/Mapsforge specific classes + super.onCreateOptionsMenu(menu); - SubMenu submenu = menu.addSubMenu(0, MENU_SELECT_MAPVIEW, 0, res.getString(R.string.map_view_map)).setIcon(R.drawable.ic_menu_mapmode); - addMapViewMenuItems(submenu); - - menu.add(0, MENU_MAP_LIVE, 0, res.getString(R.string.map_live_disable)).setIcon(R.drawable.ic_menu_refresh); - menu.add(0, MENU_STORE_CACHES, 0, res.getString(R.string.caches_store_offline)).setIcon(R.drawable.ic_menu_set_as).setEnabled(false); - SubMenu subMenuModes = menu.addSubMenu(0, SUBMENU_MODES, 0, res.getString(R.string.map_modes)).setIcon(R.drawable.ic_menu_mark); - subMenuModes.add(0, MENU_TRAIL_MODE, 0, res.getString(R.string.map_trail_hide)).setIcon(R.drawable.ic_menu_trail); - subMenuModes.add(0, MENU_CIRCLE_MODE, 0, res.getString(R.string.map_circles_hide)).setIcon(R.drawable.ic_menu_circle); - subMenuModes.add(0, MENU_THEME_MODE, 0, res.getString(R.string.map_theme_select)).setIcon(R.drawable.ic_menu_preferences); + MapProviderFactory.addMapviewMenuItems(menu); - Strategy strategy = Settings.getLiveMapStrategy(); - SubMenu subMenuStrategy = menu.addSubMenu(0, SUBMENU_STRATEGY, 0, res.getString(R.string.map_strategy)).setIcon(R.drawable.ic_menu_preferences); + final SubMenu subMenuStrategy = menu.findItem(R.id.submenu_strategy).getSubMenu(); subMenuStrategy.setHeaderTitle(res.getString(R.string.map_strategy_title)); - subMenuStrategy.add(MENU_GROUP_MAP_STRATEGY, MENU_STRATEGY_FASTEST, 0, Strategy.FASTEST.getL10n()).setCheckable(true).setChecked(strategy == Strategy.FASTEST); - subMenuStrategy.add(MENU_GROUP_MAP_STRATEGY, MENU_STRATEGY_FAST, 0, Strategy.FAST.getL10n()).setCheckable(true).setChecked(strategy == Strategy.FAST); - subMenuStrategy.add(MENU_GROUP_MAP_STRATEGY, MENU_STRATEGY_AUTO, 0, Strategy.AUTO.getL10n()).setCheckable(true).setChecked(strategy == Strategy.AUTO); - subMenuStrategy.add(MENU_GROUP_MAP_STRATEGY, MENU_STRATEGY_DETAILED, 0, Strategy.DETAILED.getL10n()).setCheckable(true).setChecked(strategy == Strategy.DETAILED); - subMenuStrategy.setGroupCheckable(MENU_GROUP_MAP_STRATEGY, true, true); - - menu.add(0, MENU_AS_LIST, 0, res.getString(R.string.map_as_list)).setIcon(R.drawable.ic_menu_agenda); - return true; } - private static void addMapViewMenuItems(final Menu menu) { - MapProviderFactory.addMapviewMenuItems(menu, MENU_GROUP_MAP_SOURCES); - menu.setGroupCheckable(MENU_GROUP_MAP_SOURCES, true, true); - } - @Override public boolean onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); @@ -582,14 +555,14 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } try { - MenuItem item = menu.findItem(MENU_TRAIL_MODE); + MenuItem item = menu.findItem(R.id.menu_trail_mode); if (Settings.isMapTrail()) { item.setTitle(res.getString(R.string.map_trail_hide)); } else { item.setTitle(res.getString(R.string.map_trail_show)); } - item = menu.findItem(MENU_MAP_LIVE); // live map + item = menu.findItem(R.id.menu_map_live); // live map if (isLiveEnabled) { item.setTitle(res.getString(R.string.map_live_disable)); } else { @@ -597,23 +570,37 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } final Set<String> geocodesInViewport = getGeocodesForCachesInViewport(); - menu.findItem(MENU_STORE_CACHES).setEnabled(!isLoading() && CollectionUtils.isNotEmpty(geocodesInViewport) && new SearchResult(geocodesInViewport).hasUnsavedCaches()); + menu.findItem(R.id.menu_store_caches).setEnabled(!isLoading() && CollectionUtils.isNotEmpty(geocodesInViewport) && new SearchResult(geocodesInViewport).hasUnsavedCaches()); - item = menu.findItem(MENU_CIRCLE_MODE); // show circles + item = menu.findItem(R.id.menu_circle_mode); // show circles if (overlayCaches != null && overlayCaches.getCircles()) { item.setTitle(res.getString(R.string.map_circles_hide)); } else { item.setTitle(res.getString(R.string.map_circles_show)); } - item = menu.findItem(MENU_THEME_MODE); // show theme selection + item = menu.findItem(R.id.menu_theme_mode); // show theme selection item.setVisible(mapView.hasMapThemes()); - menu.findItem(MENU_AS_LIST).setEnabled(isLiveEnabled && !isLoading()); + menu.findItem(R.id.menu_as_list).setEnabled(isLiveEnabled && !isLoading()); - menu.findItem(SUBMENU_STRATEGY).setEnabled(isLiveEnabled); + menu.findItem(R.id.submenu_strategy).setEnabled(isLiveEnabled); + + switch (Settings.getLiveMapStrategy()) { + case FASTEST: + menu.findItem(R.id.menu_strategy_fastest).setChecked(true); + break; + case FAST: + menu.findItem(R.id.menu_strategy_fast).setChecked(true); + break; + case AUTO: + menu.findItem(R.id.menu_strategy_auto).setChecked(true); + break; + default: // DETAILED + menu.findItem(R.id.menu_strategy_detailed).setChecked(true); + } } catch (Exception e) { - Log.e("cgeomap.onPrepareOptionsMenu", e); + Log.e("CGeoMap.onPrepareOptionsMenu", e); } return true; @@ -623,12 +610,12 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto public boolean onOptionsItemSelected(MenuItem item) { final int id = item.getItemId(); switch (id) { - case MENU_TRAIL_MODE: + case R.id.menu_trail_mode: Settings.setMapTrail(!Settings.isMapTrail()); mapView.repaintRequired(overlayPosition); ActivityMixin.invalidateOptionsMenu(activity); return true; - case MENU_MAP_LIVE: + case R.id.menu_map_live: isLiveEnabled = !isLiveEnabled; if (mapMode == MapMode.LIVE) { Settings.setLiveMap(isLiveEnabled); @@ -638,7 +625,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto searchIntent = null; ActivityMixin.invalidateOptionsMenu(activity); return true; - case MENU_STORE_CACHES: + case R.id.menu_store_caches: if (!isLoading()) { final Set<String> geocodesInViewport = getGeocodesForCachesInViewport(); final List<String> geocodes = new ArrayList<String>(); @@ -672,7 +659,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } } return true; - case MENU_CIRCLE_MODE: + case R.id.menu_circle_mode: if (overlayCaches == null) { return false; } @@ -681,29 +668,29 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto mapView.repaintRequired(overlayCaches); ActivityMixin.invalidateOptionsMenu(activity); return true; - case MENU_THEME_MODE: + case R.id.menu_theme_mode: selectMapTheme(); return true; - case MENU_AS_LIST: { + case R.id.menu_as_list: { cgeocaches.startActivityMap(activity, new SearchResult(getGeocodesForCachesInViewport())); return true; } - case MENU_STRATEGY_FASTEST: { + case R.id.menu_strategy_fastest: { item.setChecked(true); Settings.setLiveMapStrategy(Strategy.FASTEST); return true; } - case MENU_STRATEGY_FAST: { + case R.id.menu_strategy_fast: { item.setChecked(true); Settings.setLiveMapStrategy(Strategy.FAST); return true; } - case MENU_STRATEGY_AUTO: { + case R.id.menu_strategy_auto: { item.setChecked(true); Settings.setLiveMapStrategy(Strategy.AUTO); return true; } - case MENU_STRATEGY_DETAILED: { + case R.id.menu_strategy_detailed: { item.setChecked(true); Settings.setLiveMapStrategy(Strategy.DETAILED); return true; @@ -751,9 +738,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto @Override public void onClick(DialogInterface dialog, int newItem) { - if (newItem == selectedItem) { - // no change - } else { + if (newItem != selectedItem) { // Adjust index because of <default> selection if (newItem > 0) { Settings.setCustomRenderThemeFile(themeFiles[newItem - 1].getPath()); @@ -1055,7 +1040,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto yield(); } catch (Exception e) { - Log.w("cgeomap.LoadTimer.run", e); + Log.w("CGeoMap.LoadTimer.run", e); } } } @@ -1179,7 +1164,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto searchResult = ConnectorFactory.searchByViewport(viewport.resize(0.8), tokens); if (searchResult != null) { downloaded = true; - if (searchResult.getError() == StatusCode.NOT_LOGGED_IN) { + if (searchResult.getError() == StatusCode.NOT_LOGGED_IN && Settings.isGCConnectorActive()) { Login.login(); tokens = null; } else { @@ -1351,7 +1336,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto geoDirUpdate.startDir(); } catch (Exception e) { - Log.e("cgeocaches.onPrepareOptionsMenu.onCancel", e); + Log.e("CGeoMap.storeCaches.onCancel", e); } } }); @@ -1429,7 +1414,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto Geocache.storeCache(null, geocode, listId, false, handler); } } catch (Exception e) { - Log.e("cgeocaches.LoadDetails.run", e); + Log.e("CGeoMap.LoadDetails.run", e); } finally { // one more cache over detailProgress++; @@ -1578,12 +1563,6 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto ActivityMixin.goHome(activity); } - // open manual entry - @Override - public void goManual(View view) { - ActivityMixin.goManual(activity, "c:geo-live-map"); - } - @Override public View makeView() { ImageView imageView = new ImageView(activity); @@ -1645,13 +1624,13 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } private CachesOverlayItemImpl getCacheItem(final Geocache cache) { - final CachesOverlayItemImpl item = mapItemFactory.getCachesOverlayItem(cache, cache.getType()); + final CachesOverlayItemImpl item = mapItemFactory.getCachesOverlayItem(cache, cache.getType().applyDistanceRule()); final int hashcode = new HashCodeBuilder() .append(cache.isReliableLatLon()) .append(cache.getType().id) .append(cache.isDisabled() || cache.isArchived()) - .append(cache.getCacheRealm().id) + .append(cache.getMapMarkerId()) .append(cache.isOwner()) .append(cache.isFound()) .append(cache.hasUserModifiedCoords()) @@ -1660,18 +1639,21 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto .append(cache.getListId() > 0) .toHashCode(); - final LayerDrawable ldFromCache = overlaysCache.get(hashcode); - if (ldFromCache != null) { - item.setMarker(ldFromCache); - return item; + LayerDrawable drawable = overlaysCache.get(hashcode); + if (drawable == null) { + drawable = createCacheItem(cache, hashcode); } + item.setMarker(drawable); + return item; + } + private LayerDrawable createCacheItem(final Geocache cache, final int hashcode) { // Set initial capacities to the maximum of layers and insets to avoid dynamic reallocation final ArrayList<Drawable> layers = new ArrayList<Drawable>(9); final ArrayList<int[]> insets = new ArrayList<int[]>(8); // background: disabled or not - final Drawable marker = getResources().getDrawable(cache.isDisabled() || cache.isArchived() ? cache.getCacheRealm().markerDisabledId : cache.getCacheRealm().markerId); + final Drawable marker = getResources().getDrawable(cache.getMapMarkerId()); layers.add(marker); final int resolution = marker.getIntrinsicWidth() > 40 ? 1 : 0; // reliable or not @@ -1719,13 +1701,11 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } overlaysCache.put(hashcode, ld); - - item.setMarker(ld); - return item; + return ld; } private CachesOverlayItemImpl getWaypointItem(final Waypoint waypoint) { - final CachesOverlayItemImpl item = mapItemFactory.getCachesOverlayItem(waypoint, null); + final CachesOverlayItemImpl item = mapItemFactory.getCachesOverlayItem(waypoint, waypoint.getWaypointType().applyDistanceRule()); Drawable marker = getResources().getDrawable(!waypoint.isVisited() ? R.drawable.marker : R.drawable.marker_transparent); final Drawable[] layers = new Drawable[] { marker, diff --git a/main/src/cgeo/geocaching/maps/CachesOverlay.java b/main/src/cgeo/geocaching/maps/CachesOverlay.java index 9bb4cef..f1dd9b3 100644 --- a/main/src/cgeo/geocaching/maps/CachesOverlay.java +++ b/main/src/cgeo/geocaching/maps/CachesOverlay.java @@ -1,11 +1,11 @@ package cgeo.geocaching.maps; import cgeo.geocaching.CachePopup; +import cgeo.geocaching.Geocache; import cgeo.geocaching.IWaypoint; import cgeo.geocaching.R; import cgeo.geocaching.Settings; import cgeo.geocaching.WaypointPopup; -import cgeo.geocaching.Geocache; import cgeo.geocaching.cgData; import cgeo.geocaching.activity.Progress; import cgeo.geocaching.connector.gc.GCMap; @@ -58,14 +58,14 @@ public class CachesOverlay extends AbstractItemizedOverlay { mapItemFactory = mapProvider.getMapItemFactory(); } - public void updateItems(CachesOverlayItemImpl item) { + void updateItems(CachesOverlayItemImpl item) { List<CachesOverlayItemImpl> itemsPre = new ArrayList<CachesOverlayItemImpl>(); itemsPre.add(item); updateItems(itemsPre); } - public void updateItems(List<CachesOverlayItemImpl> itemsPre) { + void updateItems(List<CachesOverlayItemImpl> itemsPre) { if (itemsPre == null) { return; } @@ -86,11 +86,11 @@ public class CachesOverlay extends AbstractItemizedOverlay { } } - public boolean getCircles() { + boolean getCircles() { return displayCircles; } - public void switchCircles() { + void switchCircles() { displayCircles = !displayCircles; } @@ -126,20 +126,17 @@ public class CachesOverlay extends AbstractItemizedOverlay { final Point center = new Point(); for (CachesOverlayItemImpl item : items) { - final Geopoint itemCoord = item.getCoord().getCoords(); - final GeoPointImpl itemGeo = mapItemFactory.getGeoPointBase(itemCoord); - projection.toPixels(itemGeo, center); + if (item.applyDistanceRule()) { + final Geopoint itemCoord = item.getCoord().getCoords(); + final GeoPointImpl itemGeo = mapItemFactory.getGeoPointBase(itemCoord); + projection.toPixels(itemGeo, center); - final CacheType type = item.getType(); - if (type == null || type == CacheType.MULTI || type == CacheType.MYSTERY || type == CacheType.VIRTUAL || type.isEvent()) { - blockedCircle.setColor(0x66000000); - blockedCircle.setStyle(Style.STROKE); - canvas.drawCircle(center.x, center.y, radius, blockedCircle); - } else { + // dashed circle around the waypoint blockedCircle.setColor(0x66BB0000); blockedCircle.setStyle(Style.STROKE); canvas.drawCircle(center.x, center.y, radius, blockedCircle); + // filling the circle area with a transparent color blockedCircle.setColor(0x44BB0000); blockedCircle.setStyle(Style.FILL); canvas.drawCircle(center.x, center.y, radius, blockedCircle); @@ -209,9 +206,9 @@ public class CachesOverlay extends AbstractItemizedOverlay { progress.show(context, context.getResources().getString(R.string.map_live), context.getResources().getString(R.string.cache_dialog_loading_details), true, null); + CachesOverlayItemImpl item = null; // prevent concurrent changes getOverlayImpl().lock(); - CachesOverlayItemImpl item = null; try { if (index < items.size()) { item = items.get(index); @@ -247,7 +244,7 @@ public class CachesOverlay extends AbstractItemizedOverlay { progress.dismiss(); } catch (Exception e) { - Log.e("cgMapOverlay.onTap", e); + Log.e("CachesOverlay.onTap", e); if (progress != null) { progress.dismiss(); } @@ -261,7 +258,7 @@ public class CachesOverlay extends AbstractItemizedOverlay { try { return items.get(index); } catch (Exception e) { - Log.e("cgMapOverlay.createItem", e); + Log.e("CachesOverlay.createItem", e); } return null; @@ -272,7 +269,7 @@ public class CachesOverlay extends AbstractItemizedOverlay { try { return items.size(); } catch (Exception e) { - Log.e("cgMapOverlay.size", e); + Log.e("CachesOverlay.size", e); } return 0; diff --git a/main/src/cgeo/geocaching/maps/MapProviderFactory.java b/main/src/cgeo/geocaching/maps/MapProviderFactory.java index 483189f..5ce8ab6 100644 --- a/main/src/cgeo/geocaching/maps/MapProviderFactory.java +++ b/main/src/cgeo/geocaching/maps/MapProviderFactory.java @@ -1,5 +1,6 @@ package cgeo.geocaching.maps; +import cgeo.geocaching.R; import cgeo.geocaching.Settings; import cgeo.geocaching.maps.google.GoogleMapProvider; import cgeo.geocaching.maps.interfaces.MapProvider; @@ -7,6 +8,7 @@ import cgeo.geocaching.maps.interfaces.MapSource; import cgeo.geocaching.maps.mapsforge.MapsforgeMapProvider; import android.view.Menu; +import android.view.SubMenu; import java.util.ArrayList; import java.util.List; @@ -43,13 +45,16 @@ public class MapProviderFactory { return provider1 == provider2 && provider1.isSameActivity(source1, source2); } - public static void addMapviewMenuItems(final Menu parentMenu, final int groupId) { + public static void addMapviewMenuItems(Menu menu) { + final SubMenu parentMenu = menu.findItem(R.id.menu_select_mapview).getSubMenu(); + final int currentSource = Settings.getMapSource().getNumericalId(); for (int i = 0; i < mapSources.size(); i++) { final MapSource mapSource = mapSources.get(i); final int id = mapSource.getNumericalId(); - parentMenu.add(groupId, id, i, mapSource.getName()).setCheckable(true).setChecked(id == currentSource); + parentMenu.add(R.id.menu_group_map_sources, id, i, mapSource.getName()).setCheckable(true).setChecked(id == currentSource); } + parentMenu.setGroupCheckable(R.id.menu_group_map_sources, true, true); } public static MapSource getMapSource(int id) { diff --git a/main/src/cgeo/geocaching/maps/PositionOverlay.java b/main/src/cgeo/geocaching/maps/PositionOverlay.java index fec67ef..08acd2f 100644 --- a/main/src/cgeo/geocaching/maps/PositionOverlay.java +++ b/main/src/cgeo/geocaching/maps/PositionOverlay.java @@ -22,7 +22,6 @@ import android.graphics.Point; import android.location.Location; import java.util.ArrayList; -import java.util.List; public class PositionOverlay implements GeneralOverlay { private Location coordinates = null; @@ -39,7 +38,7 @@ public class PositionOverlay implements GeneralOverlay { private PaintFlagsDrawFilter setfil = null; private PaintFlagsDrawFilter remfil = null; private Location historyRecent = null; - private List<Location> history = new ArrayList<Location>(); + private ArrayList<Location> history = new ArrayList<Location>(); private Point historyPointN = new Point(); private Point historyPointP = new Point(); private Activity activity; @@ -229,4 +228,12 @@ public class PositionOverlay implements GeneralOverlay { public OverlayImpl getOverlayImpl() { return this.ovlImpl; } + + public ArrayList<Location> getHistory() { + return history; + } + + public void setHistory(ArrayList<Location> inHistory) { + history = inHistory; + } } diff --git a/main/src/cgeo/geocaching/maps/google/GoogleCacheOverlay.java b/main/src/cgeo/geocaching/maps/google/GoogleCacheOverlay.java index 4868a30..21d78a0 100644 --- a/main/src/cgeo/geocaching/maps/google/GoogleCacheOverlay.java +++ b/main/src/cgeo/geocaching/maps/google/GoogleCacheOverlay.java @@ -73,12 +73,12 @@ public class GoogleCacheOverlay extends ItemizedOverlay<GoogleCacheOverlayItem> @Override public Drawable superBoundCenter(Drawable markerIn) { - return super.boundCenter(markerIn); + return ItemizedOverlay.boundCenter(markerIn); } @Override public Drawable superBoundCenterBottom(Drawable marker) { - return super.boundCenterBottom(marker); + return ItemizedOverlay.boundCenterBottom(marker); } @Override diff --git a/main/src/cgeo/geocaching/maps/google/GoogleCacheOverlayItem.java b/main/src/cgeo/geocaching/maps/google/GoogleCacheOverlayItem.java index 2ac66af..b26654a 100644 --- a/main/src/cgeo/geocaching/maps/google/GoogleCacheOverlayItem.java +++ b/main/src/cgeo/geocaching/maps/google/GoogleCacheOverlayItem.java @@ -1,21 +1,20 @@ package cgeo.geocaching.maps.google; import cgeo.geocaching.IWaypoint; -import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.maps.interfaces.CachesOverlayItemImpl; import com.google.android.maps.GeoPoint; import com.google.android.maps.OverlayItem; public class GoogleCacheOverlayItem extends OverlayItem implements CachesOverlayItemImpl { - final private CacheType cacheType; final private IWaypoint coord; + final private boolean applyDistanceRule; - public GoogleCacheOverlayItem(final IWaypoint coordinate, final CacheType type) { + public GoogleCacheOverlayItem(final IWaypoint coordinate, boolean applyDistanceRule) { super(new GeoPoint(coordinate.getCoords().getLatitudeE6(), coordinate.getCoords().getLongitudeE6()), coordinate.getName(), ""); - this.cacheType = type; this.coord = coordinate; + this.applyDistanceRule = applyDistanceRule; } @Override @@ -24,8 +23,8 @@ public class GoogleCacheOverlayItem extends OverlayItem implements CachesOverlay } @Override - public CacheType getType() { - return cacheType; + public boolean applyDistanceRule() { + return applyDistanceRule; } } diff --git a/main/src/cgeo/geocaching/maps/google/GoogleMapActivity.java b/main/src/cgeo/geocaching/maps/google/GoogleMapActivity.java index 5649d19..dcff363 100644 --- a/main/src/cgeo/geocaching/maps/google/GoogleMapActivity.java +++ b/main/src/cgeo/geocaching/maps/google/GoogleMapActivity.java @@ -1,5 +1,6 @@ package cgeo.geocaching.maps.google; +import cgeo.geocaching.R; import cgeo.geocaching.activity.FilteredActivity; import cgeo.geocaching.maps.AbstractMap; import cgeo.geocaching.maps.CGeoMap; @@ -83,7 +84,9 @@ public class GoogleMapActivity extends MapActivity implements MapActivityImpl, F @Override public boolean superOnCreateOptionsMenu(Menu menu) { - return super.onCreateOptionsMenu(menu); + final boolean result = super.onCreateOptionsMenu(menu); + getMenuInflater().inflate(R.menu.map_activity, menu); + return result; } @Override @@ -122,12 +125,6 @@ public class GoogleMapActivity extends MapActivity implements MapActivityImpl, F mapBase.goHome(view); } - // open manual entry - @Override - public void goManual(View view) { - mapBase.goManual(view); - } - @Override public void showFilterMenu(View view) { // do nothing, the filter bar only shows the global filter diff --git a/main/src/cgeo/geocaching/maps/google/GoogleMapItemFactory.java b/main/src/cgeo/geocaching/maps/google/GoogleMapItemFactory.java index f40c799..c708dc5 100644 --- a/main/src/cgeo/geocaching/maps/google/GoogleMapItemFactory.java +++ b/main/src/cgeo/geocaching/maps/google/GoogleMapItemFactory.java @@ -1,7 +1,6 @@ package cgeo.geocaching.maps.google; import cgeo.geocaching.IWaypoint; -import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.maps.interfaces.CachesOverlayItemImpl; import cgeo.geocaching.maps.interfaces.GeoPointImpl; @@ -15,7 +14,7 @@ public class GoogleMapItemFactory implements MapItemFactory { } @Override - public CachesOverlayItemImpl getCachesOverlayItem(final IWaypoint coordinate, final CacheType type) { - return new GoogleCacheOverlayItem(coordinate, type); + public CachesOverlayItemImpl getCachesOverlayItem(final IWaypoint coordinate, boolean applyDistanceRule) { + return new GoogleCacheOverlayItem(coordinate, applyDistanceRule); } } diff --git a/main/src/cgeo/geocaching/maps/interfaces/CachesOverlayItemImpl.java b/main/src/cgeo/geocaching/maps/interfaces/CachesOverlayItemImpl.java index 5bf3ed2..03fa21f 100644 --- a/main/src/cgeo/geocaching/maps/interfaces/CachesOverlayItemImpl.java +++ b/main/src/cgeo/geocaching/maps/interfaces/CachesOverlayItemImpl.java @@ -1,7 +1,6 @@ package cgeo.geocaching.maps.interfaces; import cgeo.geocaching.IWaypoint; -import cgeo.geocaching.enumerations.CacheType; /** * Covers the common functions of the provider-specific @@ -11,6 +10,5 @@ public interface CachesOverlayItemImpl extends OverlayItemImpl { public IWaypoint getCoord(); - public CacheType getType(); - + public boolean applyDistanceRule(); } diff --git a/main/src/cgeo/geocaching/maps/interfaces/MapActivityImpl.java b/main/src/cgeo/geocaching/maps/interfaces/MapActivityImpl.java index dc7dca5..e7deebd 100644 --- a/main/src/cgeo/geocaching/maps/interfaces/MapActivityImpl.java +++ b/main/src/cgeo/geocaching/maps/interfaces/MapActivityImpl.java @@ -35,6 +35,4 @@ public interface MapActivityImpl { public abstract void goHome(View view); - public abstract void goManual(View view); - } diff --git a/main/src/cgeo/geocaching/maps/interfaces/MapItemFactory.java b/main/src/cgeo/geocaching/maps/interfaces/MapItemFactory.java index e02d472..22c6698 100644 --- a/main/src/cgeo/geocaching/maps/interfaces/MapItemFactory.java +++ b/main/src/cgeo/geocaching/maps/interfaces/MapItemFactory.java @@ -1,13 +1,12 @@ package cgeo.geocaching.maps.interfaces; import cgeo.geocaching.IWaypoint; -import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.geopoint.Geopoint; public interface MapItemFactory { public GeoPointImpl getGeoPointBase(final Geopoint coords); - public CachesOverlayItemImpl getCachesOverlayItem(final IWaypoint iWaypoint, final CacheType type); + public CachesOverlayItemImpl getCachesOverlayItem(final IWaypoint iWaypoint, final boolean applyDistanceRule); } diff --git a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeCacheOverlay.java b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeCacheOverlay.java index 0bd2484..9e14e36 100644 --- a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeCacheOverlay.java +++ b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeCacheOverlay.java @@ -71,12 +71,12 @@ public class MapsforgeCacheOverlay extends ItemizedOverlay<MapsforgeCacheOverlay @Override public Drawable superBoundCenter(Drawable markerIn) { - return super.boundCenter(markerIn); + return ItemizedOverlay.boundCenter(markerIn); } @Override public Drawable superBoundCenterBottom(Drawable marker) { - return super.boundCenterBottom(marker); + return ItemizedOverlay.boundCenterBottom(marker); } @Override diff --git a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeCacheOverlayItem.java b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeCacheOverlayItem.java index 29f13b3..27ca664 100644 --- a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeCacheOverlayItem.java +++ b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeCacheOverlayItem.java @@ -1,7 +1,6 @@ package cgeo.geocaching.maps.mapsforge; import cgeo.geocaching.IWaypoint; -import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.maps.interfaces.CachesOverlayItemImpl; import org.mapsforge.android.maps.overlay.OverlayItem; @@ -10,14 +9,14 @@ import org.mapsforge.core.GeoPoint; import android.graphics.drawable.Drawable; public class MapsforgeCacheOverlayItem extends OverlayItem implements CachesOverlayItemImpl { - final private CacheType cacheType; final private IWaypoint coord; + final private boolean applyDistanceRule; - public MapsforgeCacheOverlayItem(IWaypoint coordinate, final CacheType type) { + public MapsforgeCacheOverlayItem(IWaypoint coordinate, boolean applyDistanceRule) { super(new GeoPoint(coordinate.getCoords().getLatitudeE6(), coordinate.getCoords().getLongitudeE6()), coordinate.getName(), ""); - this.cacheType = type; this.coord = coordinate; + this.applyDistanceRule = applyDistanceRule; } @Override @@ -26,13 +25,13 @@ public class MapsforgeCacheOverlayItem extends OverlayItem implements CachesOver } @Override - public CacheType getType() { - return cacheType; + public Drawable getMarker(int index) { + return getMarker(); } @Override - public Drawable getMarker(int index) { - return getMarker(); + public boolean applyDistanceRule() { + return applyDistanceRule; } } diff --git a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapActivity.java b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapActivity.java index f850402..232fe3c 100644 --- a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapActivity.java +++ b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapActivity.java @@ -1,5 +1,6 @@ package cgeo.geocaching.maps.mapsforge; +import cgeo.geocaching.R; import cgeo.geocaching.activity.FilteredActivity; import cgeo.geocaching.maps.AbstractMap; import cgeo.geocaching.maps.CGeoMap; @@ -78,7 +79,9 @@ public class MapsforgeMapActivity extends MapActivity implements MapActivityImpl @Override public boolean superOnCreateOptionsMenu(Menu menu) { - return super.onCreateOptionsMenu(menu); + final boolean result = super.onCreateOptionsMenu(menu); + getMenuInflater().inflate(R.menu.map_activity, menu); + return result; } @Override @@ -117,12 +120,6 @@ public class MapsforgeMapActivity extends MapActivity implements MapActivityImpl mapBase.goHome(view); } - // open manual entry - @Override - public void goManual(View view) { - mapBase.goManual(view); - } - @Override public void showFilterMenu(View view) { // do nothing, the filter bar only shows the global filter diff --git a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapItemFactory.java b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapItemFactory.java index 10fcb03..4ade09c 100644 --- a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapItemFactory.java +++ b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapItemFactory.java @@ -1,7 +1,6 @@ package cgeo.geocaching.maps.mapsforge; import cgeo.geocaching.IWaypoint; -import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.maps.interfaces.CachesOverlayItemImpl; import cgeo.geocaching.maps.interfaces.GeoPointImpl; @@ -15,8 +14,8 @@ public class MapsforgeMapItemFactory implements MapItemFactory { } @Override - public CachesOverlayItemImpl getCachesOverlayItem(final IWaypoint coordinate, final CacheType type) { - return new MapsforgeCacheOverlayItem(coordinate, type); + public CachesOverlayItemImpl getCachesOverlayItem(final IWaypoint coordinate, boolean applyDistanceRule) { + return new MapsforgeCacheOverlayItem(coordinate, applyDistanceRule); } } diff --git a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapProvider.java b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapProvider.java index dc0dbf8..7cf18fb 100644 --- a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapProvider.java +++ b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapProvider.java @@ -68,7 +68,7 @@ public final class MapsforgeMapProvider extends AbstractMapProvider { Collections.sort(mapFileList, String.CASE_INSENSITIVE_ORDER); return mapFileList; } catch (Exception e) { - Log.e("Settings.getOfflineMaps: " + e); + Log.e("MapsforgeMapProvider.getOfflineMaps: ", e); } return Collections.emptyList(); } @@ -167,7 +167,7 @@ public final class MapsforgeMapProvider extends AbstractMapProvider { final List<String> offlineMaps = getOfflineMaps(); for (String mapFile : offlineMaps) { final String mapName = StringUtils.capitalize(StringUtils.substringBeforeLast(new File(mapFile).getName(), ".")); - registerMapSource(new OfflineMapSource(mapFile, this, resources.getString(R.string.map_source_osm_offline) + " - " + mapName, MapGeneratorInternal.DATABASE_RENDERER)); + registerMapSource(new OfflineMapSource(mapFile, this, mapName + " (" + resources.getString(R.string.map_source_osm_offline) + ")", MapGeneratorInternal.DATABASE_RENDERER)); } // have a default entry, if no map files are available. otherwise we cannot select "offline" in the settings if (offlineMaps.isEmpty()) { diff --git a/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeCacheOverlay.java b/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeCacheOverlay.java index 581548f..30355fd 100644 --- a/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeCacheOverlay.java +++ b/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeCacheOverlay.java @@ -71,12 +71,12 @@ public class MapsforgeCacheOverlay extends ItemizedOverlay<MapsforgeCacheOverlay @Override public Drawable superBoundCenter(Drawable markerIn) { - return super.boundCenter(markerIn); + return ItemizedOverlay.boundCenter(markerIn); } @Override public Drawable superBoundCenterBottom(Drawable marker) { - return super.boundCenterBottom(marker); + return ItemizedOverlay.boundCenterBottom(marker); } @Override diff --git a/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeCacheOverlayItem.java b/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeCacheOverlayItem.java index 6b74de5..4e4a358 100644 --- a/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeCacheOverlayItem.java +++ b/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeCacheOverlayItem.java @@ -1,7 +1,6 @@ package cgeo.geocaching.maps.mapsforge.v024; import cgeo.geocaching.IWaypoint; -import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.maps.interfaces.CachesOverlayItemImpl; import org.mapsforge.android.mapsold.GeoPoint; @@ -10,14 +9,14 @@ import org.mapsforge.android.mapsold.OverlayItem; import android.graphics.drawable.Drawable; public class MapsforgeCacheOverlayItem extends OverlayItem implements CachesOverlayItemImpl { - final private CacheType cacheType; final private IWaypoint coord; + final private boolean applyDistanceRule; - public MapsforgeCacheOverlayItem(IWaypoint coordinate, final CacheType type) { + public MapsforgeCacheOverlayItem(IWaypoint coordinate, boolean applyDistanceRule) { super(new GeoPoint(coordinate.getCoords().getLatitudeE6(), coordinate.getCoords().getLongitudeE6()), coordinate.getName(), ""); - this.cacheType = type; this.coord = coordinate; + this.applyDistanceRule = applyDistanceRule; } @Override @@ -26,13 +25,13 @@ public class MapsforgeCacheOverlayItem extends OverlayItem implements CachesOver } @Override - public CacheType getType() { - return cacheType; + public Drawable getMarker(int index) { + return getMarker(); } @Override - public Drawable getMarker(int index) { - return getMarker(); + public boolean applyDistanceRule() { + return applyDistanceRule; } } diff --git a/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapActivity024.java b/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapActivity024.java index ed8a7bc..33ed30e 100644 --- a/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapActivity024.java +++ b/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapActivity024.java @@ -117,12 +117,6 @@ public class MapsforgeMapActivity024 extends MapActivity implements MapActivityI mapBase.goHome(view); } - // open manual entry - @Override - public void goManual(View view) { - mapBase.goManual(view); - } - @Override public void showFilterMenu(View view) { // do nothing, the filter bar only shows the global filter diff --git a/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapItemFactory024.java b/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapItemFactory024.java index 5c64592..4f1d34c 100644 --- a/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapItemFactory024.java +++ b/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapItemFactory024.java @@ -1,7 +1,6 @@ package cgeo.geocaching.maps.mapsforge.v024; import cgeo.geocaching.IWaypoint; -import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.maps.interfaces.CachesOverlayItemImpl; import cgeo.geocaching.maps.interfaces.GeoPointImpl; @@ -15,7 +14,7 @@ public class MapsforgeMapItemFactory024 implements MapItemFactory { } @Override - public CachesOverlayItemImpl getCachesOverlayItem(final IWaypoint coordinate, final CacheType type) { - return new MapsforgeCacheOverlayItem(coordinate, type); + public CachesOverlayItemImpl getCachesOverlayItem(final IWaypoint coordinate, boolean applyDistanceRule) { + return new MapsforgeCacheOverlayItem(coordinate, applyDistanceRule); } } diff --git a/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapView024.java b/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapView024.java index 85d61fe..cc8bc66 100644 --- a/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapView024.java +++ b/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapView024.java @@ -53,7 +53,7 @@ public class MapsforgeMapView024 extends MapView implements MapViewImpl { super.draw(canvas); } catch (Exception e) { - Log.e("MapsforgeMapView.draw", e); + Log.e("MapsforgeMapView024.draw", e); } } @@ -208,7 +208,7 @@ public class MapsforgeMapView024 extends MapView implements MapViewImpl { } } catch (Exception e) { - Log.e("MapsforgeMapView.repaintRequired", e); + Log.e("MapsforgeMapView024.repaintRequired", e); } } } diff --git a/main/src/cgeo/geocaching/network/HtmlImage.java b/main/src/cgeo/geocaching/network/HtmlImage.java index 38498d6..0649e12 100644 --- a/main/src/cgeo/geocaching/network/HtmlImage.java +++ b/main/src/cgeo/geocaching/network/HtmlImage.java @@ -6,7 +6,8 @@ import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.compatibility.Compatibility; import cgeo.geocaching.connector.ConnectorFactory; import cgeo.geocaching.files.LocalStorage; -import cgeo.geocaching.utils.ImageHelper; +import cgeo.geocaching.utils.IOUtils; +import cgeo.geocaching.utils.ImageUtils; import cgeo.geocaching.utils.Log; import ch.boye.httpclientandroidlib.HttpResponse; @@ -21,10 +22,10 @@ import android.graphics.drawable.BitmapDrawable; import android.net.Uri; import android.text.Html; +import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; -import java.io.IOException; import java.util.Date; public class HtmlImage implements Html.ImageGetter { @@ -42,7 +43,8 @@ public class HtmlImage implements Html.ImageGetter { "hitwebcounter.com", "kostenloser-counter.eu", "trendcounter.com", - "hit-counter-download.com" + "hit-counter-download.com", + "gcwetterau.de/counter" }; public static final String SHARED = "shared"; @@ -66,6 +68,7 @@ public class HtmlImage implements Html.ImageGetter { bfOptions = new BitmapFactory.Options(); bfOptions.inTempStorage = new byte[16 * 1024]; + bfOptions.inPreferredConfig = Bitmap.Config.RGB_565; Point displaySize = Compatibility.getDisplaySize(); this.maxWidth = displaySize.x - 25; @@ -129,7 +132,7 @@ public class HtmlImage implements Html.ImageGetter { } } - return imagePre != null ? ImageHelper.scaleBitmapToFitDisplay(imagePre) : null; + return imagePre != null ? ImageUtils.scaleBitmapToFitDisplay(imagePre) : null; } /** @@ -194,7 +197,11 @@ public class HtmlImage implements Html.ImageGetter { if (file.exists()) { if (listId >= StoredList.STANDARD_LIST_ID || file.lastModified() > (new Date().getTime() - (24 * 60 * 60 * 1000)) || forceKeep) { setSampleSize(file); - return BitmapFactory.decodeFile(file.getPath(), bfOptions); + final Bitmap image = BitmapFactory.decodeFile(file.getPath(), bfOptions); + if (image == null) { + Log.e("Cannot decode bitmap from " + file.getPath()); + } + return image; } } return null; @@ -205,20 +212,14 @@ public class HtmlImage implements Html.ImageGetter { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; - FileInputStream fis = null; + BufferedInputStream stream = null; try { - fis = new FileInputStream(file); - BitmapFactory.decodeStream(fis, null, options); + stream = new BufferedInputStream(new FileInputStream(file)); + BitmapFactory.decodeStream(stream, null, options); } catch (FileNotFoundException e) { Log.e("HtmlImage.setSampleSize", e); } finally { - if (fis != null) { - try { - fis.close(); - } catch (IOException e) { - // ignore - } - } + IOUtils.closeQuietly(stream); } int scale = 1; diff --git a/main/src/cgeo/geocaching/network/Network.java b/main/src/cgeo/geocaching/network/Network.java index eb6a6ac..57196c5 100644 --- a/main/src/cgeo/geocaching/network/Network.java +++ b/main/src/cgeo/geocaching/network/Network.java @@ -2,7 +2,7 @@ package cgeo.geocaching.network; import cgeo.geocaching.Settings; import cgeo.geocaching.files.LocalStorage; -import cgeo.geocaching.utils.BaseUtils; +import cgeo.geocaching.utils.TextUtils; import cgeo.geocaching.utils.Log; import ch.boye.httpclientandroidlib.Header; @@ -40,6 +40,9 @@ import org.apache.commons.lang3.StringUtils; import org.json.JSONException; import org.json.JSONObject; +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; import android.net.Uri; import java.io.File; @@ -415,7 +418,7 @@ public abstract class Network { private static String getResponseDataNoError(final HttpResponse response, boolean replaceWhitespace) { try { String data = EntityUtils.toString(response.getEntity(), CharEncoding.UTF_8); - return replaceWhitespace ? BaseUtils.replaceWhitespace(data) : data; + return replaceWhitespace ? TextUtils.replaceWhitespace(data) : data; } catch (Exception e) { Log.e("getResponseData", e); return null; @@ -425,7 +428,7 @@ public abstract class Network { /** * Get the body of a HTTP response. * - * {@link BaseUtils#replaceWhitespace(String)} will be called on the result + * {@link TextUtils#replaceWhitespace(String)} will be called on the result * * @param response a HTTP response, which can be null * @return the body if the response comes from a successful HTTP request, <code>null</code> otherwise @@ -438,7 +441,7 @@ public abstract class Network { * Get the body of a HTTP response. * * @param response a HTTP response, which can be null - * @param replaceWhitespace <code>true</code> if {@link BaseUtils#replaceWhitespace(String)} + * @param replaceWhitespace <code>true</code> if {@link TextUtils#replaceWhitespace(String)} * should be called on the body * @return the body if the response comes from a successful HTTP request, <code>null</code> otherwise */ @@ -471,4 +474,19 @@ public abstract class Network { return null; } + /** + * Checks if the device has network connection. + * + * @param context + * context of the application, cannot be null + * + * @return <code>true</code> if the device is connected to the network. + */ + public static boolean isNetworkConnected(Context context) { + ConnectivityManager conMan = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo activeNetwork = conMan.getActiveNetworkInfo(); + + return activeNetwork != null && activeNetwork.isConnected(); + } + } diff --git a/main/src/cgeo/geocaching/network/OAuth.java b/main/src/cgeo/geocaching/network/OAuth.java index 0b7a261..6740096 100644 --- a/main/src/cgeo/geocaching/network/OAuth.java +++ b/main/src/cgeo/geocaching/network/OAuth.java @@ -1,9 +1,9 @@ package cgeo.geocaching.network; -import cgeo.geocaching.Settings; import cgeo.geocaching.utils.CryptUtils; import ch.boye.httpclientandroidlib.NameValuePair; + import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; @@ -11,9 +11,17 @@ import java.util.Date; import java.util.List; public class OAuth { - public static void signOAuth(final String host, final String path, final String method, final boolean https, final Parameters params, final String token, final String tokenSecret) { + public static void signOAuth(final String host, + final String path, + final String method, + final boolean https, + final Parameters params, + final String token, + final String tokenSecret, + final String consumerKey, + final String consumerSecret) { params.put( - "oauth_consumer_key", Settings.getKeyConsumerPublic(), + "oauth_consumer_key", consumerKey, "oauth_nonce", CryptUtils.md5(Long.toString(System.currentTimeMillis())), "oauth_signature_method", "HMAC-SHA1", "oauth_timestamp", Long.toString(new Date().getTime() / 1000), @@ -26,7 +34,7 @@ public class OAuth { paramsEncoded.add(nameValue.getName() + "=" + Network.rfc3986URLEncode(nameValue.getValue())); } - final String keysPacked = Settings.getKeyConsumerSecret() + "&" + StringUtils.defaultString(tokenSecret); // both even if empty some of them! + final String keysPacked = consumerSecret + "&" + StringUtils.defaultString(tokenSecret); // both even if empty some of them! final String requestPacked = method + "&" + Network.rfc3986URLEncode((https ? "https" : "http") + "://" + host + path) + "&" + Network.rfc3986URLEncode(StringUtils.join(paramsEncoded.toArray(), '&')); params.put("oauth_signature", CryptUtils.base64Encode(CryptUtils.hashHmac(requestPacked, keysPacked))); } diff --git a/main/src/cgeo/geocaching/network/OAuthAuthorizationActivity.java b/main/src/cgeo/geocaching/network/OAuthAuthorizationActivity.java new file mode 100644 index 0000000..751443e --- /dev/null +++ b/main/src/cgeo/geocaching/network/OAuthAuthorizationActivity.java @@ -0,0 +1,326 @@ +package cgeo.geocaching.network; + +import cgeo.geocaching.R; +import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.utils.Log; +import cgeo.geocaching.utils.MatcherWrapper; + +import ch.boye.httpclientandroidlib.client.entity.UrlEncodedFormEntity; +import ch.boye.httpclientandroidlib.util.EntityUtils; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; + +import android.app.ProgressDialog; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; + +import java.util.regex.Pattern; + +public abstract class OAuthAuthorizationActivity extends AbstractActivity { + + private String host; + private String pathRequest; + private String pathAuthorize; + private String pathAccess; + private boolean https; + private String consumerKey; + private String consumerSecret; + private String OAtoken = null; + private String OAtokenSecret = null; + private final Pattern paramsPattern1 = Pattern.compile("oauth_token=([a-zA-Z0-9\\-\\_.]+)"); + private final Pattern paramsPattern2 = Pattern.compile("oauth_token_secret=([a-zA-Z0-9\\-\\_.]+)"); + private Button startButton = null; + private EditText pinEntry = null; + private Button pinEntryButton = null; + private ProgressDialog requestTokenDialog = null; + private ProgressDialog changeTokensDialog = null; + private Handler requestTokenHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + if (requestTokenDialog != null && requestTokenDialog.isShowing()) { + requestTokenDialog.dismiss(); + } + + startButton.setOnClickListener(new StartListener()); + startButton.setEnabled(true); + + if (msg.what == 1) { + startButton.setText(getAuthAgain()); + + pinEntry.setVisibility(View.VISIBLE); + pinEntryButton.setVisibility(View.VISIBLE); + pinEntryButton.setOnClickListener(new ConfirmPINListener()); + } else { + showToast(getErrAuthInitialize()); + startButton.setText(getAuthStart()); + } + } + + }; + private Handler changeTokensHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + if (changeTokensDialog != null && changeTokensDialog.isShowing()) { + changeTokensDialog.dismiss(); + } + + pinEntryButton.setOnClickListener(new ConfirmPINListener()); + pinEntryButton.setEnabled(true); + + if (msg.what == 1) { + showToast(getAuthDialogCompleted()); + + pinEntryButton.setVisibility(View.GONE); + + finish(); + } else { + showToast(getErrAuthProcess()); + + pinEntry.setVisibility(View.GONE); + pinEntryButton.setVisibility(View.GONE); + startButton.setText(getAuthStart()); + } + } + }; + + public OAuthAuthorizationActivity(String host, + String pathRequest, + String pathAuthorize, + String pathAccess, + boolean https, + String consumerKey, + String consumerSecret) { + this.host = host; + this.pathRequest = pathRequest; + this.pathAuthorize = pathAuthorize; + this.pathAccess = pathAccess; + this.https = https; + this.consumerKey = consumerKey; + this.consumerSecret = consumerSecret; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState, R.layout.authorization_activity); + + setTitle(getAuthTitle()); + + init(); + } + + private void init() { + startButton = (Button) findViewById(R.id.start); + pinEntry = (EditText) findViewById(R.id.pin); + pinEntryButton = (Button) findViewById(R.id.pin_button); + + TextView auth = (TextView) findViewById(R.id.auth_1); + auth.setText(getAboutAuth1()); + auth = (TextView) findViewById(R.id.auth_2); + auth.setText(getAboutAuth2()); + + ImmutablePair<String, String> tempToken = getTempToken(); + OAtoken = tempToken.left; + OAtokenSecret = tempToken.right; + + startButton.setText(getAuthAuthorize()); + pinEntryButton.setText(getAuthFinish()); + + startButton.setEnabled(true); + startButton.setOnClickListener(new StartListener()); + + if (StringUtils.isBlank(OAtoken) && StringUtils.isBlank(OAtokenSecret)) { + // start authorization process + startButton.setText(getAuthStart()); + } else { + // already have temporary tokens, continue from pin + startButton.setText(getAuthAgain()); + + pinEntry.setHint(getAuthPinHint()); + pinEntry.setVisibility(View.VISIBLE); + pinEntryButton.setVisibility(View.VISIBLE); + pinEntryButton.setOnClickListener(new ConfirmPINListener()); + } + } + + private void requestToken() { + + int status = 0; + try { + final Parameters params = new Parameters(); + params.put("oauth_callback", "oob"); + final String method = "GET"; + OAuth.signOAuth(host, pathRequest, method, https, params, null, null, consumerKey, consumerSecret); + final String line = Network.getResponseData(Network.getRequest(getUrlPrefix() + host + pathRequest, params)); + + if (StringUtils.isNotBlank(line)) { + final MatcherWrapper paramsMatcher1 = new MatcherWrapper(paramsPattern1, line); + if (paramsMatcher1.find()) { + OAtoken = paramsMatcher1.group(1); + } + final MatcherWrapper paramsMatcher2 = new MatcherWrapper(paramsPattern2, line); + if (paramsMatcher2.find()) { + OAtokenSecret = paramsMatcher2.group(1); + } + + if (StringUtils.isNotBlank(OAtoken) && StringUtils.isNotBlank(OAtokenSecret)) { + setTempTokens(OAtoken, OAtokenSecret); + try { + final Parameters paramsBrowser = new Parameters(); + paramsBrowser.put("oauth_token", OAtoken); + final String encodedParams = EntityUtils.toString(new UrlEncodedFormEntity(paramsBrowser)); + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(getUrlPrefix() + host + pathAuthorize + "?" + encodedParams))); + status = 1; + } catch (Exception e) { + Log.e("OAuthAuthorizationActivity.requestToken(2)", e); + } + } + } + } catch (Exception e) { + Log.e("OAuthAuthorizationActivity.requestToken(1)", e); + } + + requestTokenHandler.sendEmptyMessage(status); + } + + private void changeToken() { + + int status = 0; + + try { + final Parameters params = new Parameters("oauth_verifier", pinEntry.getText().toString()); + + final String method = "POST"; + OAuth.signOAuth(host, pathAccess, method, https, params, OAtoken, OAtokenSecret, consumerKey, consumerSecret); + final String line = StringUtils.defaultString(Network.getResponseData(Network.postRequest(getUrlPrefix() + host + pathAccess, params))); + + OAtoken = ""; + OAtokenSecret = ""; + + final MatcherWrapper paramsMatcher1 = new MatcherWrapper(paramsPattern1, line); + if (paramsMatcher1.find()) { + OAtoken = paramsMatcher1.group(1); + } + final MatcherWrapper paramsMatcher2 = new MatcherWrapper(paramsPattern2, line); + if (paramsMatcher2.find() && paramsMatcher2.groupCount() > 0) { + OAtokenSecret = paramsMatcher2.group(1); + } + + if (StringUtils.isBlank(OAtoken) && StringUtils.isBlank(OAtokenSecret)) { + OAtoken = ""; + OAtokenSecret = ""; + setTokens(null, null, false); + } else { + setTokens(OAtoken, OAtokenSecret, true); + status = 1; + } + } catch (Exception e) { + Log.e("OAuthAuthorizationActivity.changeToken", e); + } + + changeTokensHandler.sendEmptyMessage(status); + } + + private String getUrlPrefix() { + return https ? "https://" : "http://"; + } + + private class StartListener implements View.OnClickListener { + + @Override + public void onClick(View arg0) { + if (requestTokenDialog == null) { + requestTokenDialog = new ProgressDialog(OAuthAuthorizationActivity.this); + requestTokenDialog.setCancelable(false); + requestTokenDialog.setMessage(getAuthDialogWait()); + } + requestTokenDialog.show(); + startButton.setEnabled(false); + startButton.setOnTouchListener(null); + startButton.setOnClickListener(null); + + setTempTokens(null, null); + (new Thread() { + + @Override + public void run() { + requestToken(); + } + }).start(); + } + } + + private class ConfirmPINListener implements View.OnClickListener { + + @Override + public void onClick(View arg0) { + if (StringUtils.isEmpty(((EditText) findViewById(R.id.pin)).getText().toString())) { + helpDialog(getAuthDialogPinTitle(), getAuthDialogPinMessage()); + return; + } + + if (changeTokensDialog == null) { + changeTokensDialog = new ProgressDialog(OAuthAuthorizationActivity.this); + changeTokensDialog.setCancelable(false); + changeTokensDialog.setMessage(getAuthDialogWait()); + } + changeTokensDialog.show(); + pinEntryButton.setEnabled(false); + pinEntryButton.setOnTouchListener(null); + pinEntryButton.setOnClickListener(null); + + (new Thread() { + + @Override + public void run() { + changeToken(); + } + }).start(); + } + } + + protected abstract ImmutablePair<String, String> getTempToken(); + + protected abstract void setTempTokens(String tokenPublic, String tokenSecret); + + protected abstract void setTokens(String tokenPublic, String tokenSecret, boolean enable); + + // get resources from derived class + + protected abstract String getAuthTitle(); + + protected abstract String getAuthAgain(); + + protected abstract String getErrAuthInitialize(); + + protected abstract String getAuthStart(); + + protected abstract String getAuthDialogCompleted(); + + protected abstract String getErrAuthProcess(); + + protected abstract String getAuthDialogWait(); + + protected abstract String getAuthDialogPinTitle(); + + protected abstract String getAuthDialogPinMessage(); + + protected abstract String getAboutAuth1(); + + protected abstract String getAboutAuth2(); + + protected abstract String getAuthAuthorize(); + + protected abstract String getAuthPinHint(); + + protected abstract String getAuthFinish(); +} diff --git a/main/src/cgeo/geocaching/network/StatusUpdater.java b/main/src/cgeo/geocaching/network/StatusUpdater.java index 93ae1ee..d54f706 100644 --- a/main/src/cgeo/geocaching/network/StatusUpdater.java +++ b/main/src/cgeo/geocaching/network/StatusUpdater.java @@ -3,6 +3,7 @@ package cgeo.geocaching.network; import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.utils.MemorySubject; import cgeo.geocaching.utils.PeriodicHandler; +import cgeo.geocaching.utils.PeriodicHandler.PeriodicHandlerListener; import cgeo.geocaching.utils.Version; import org.json.JSONException; @@ -14,7 +15,7 @@ import android.os.Looper; import java.util.Locale; -public class StatusUpdater extends MemorySubject<StatusUpdater.Status> implements Runnable { +public class StatusUpdater extends MemorySubject<StatusUpdater.Status> implements Runnable, PeriodicHandlerListener { static public class Status { final public String message; @@ -37,7 +38,8 @@ public class StatusUpdater extends MemorySubject<StatusUpdater.Status> implement } } - private void requestUpdate() { + @Override + public void onPeriodic() { final JSONObject response = Network.requestJSON("http://status.cgeo.org/api/status.json", new Parameters("version_code", String.valueOf(Version.getVersionCode(cgeoapplication.getInstance())), @@ -59,12 +61,7 @@ public class StatusUpdater extends MemorySubject<StatusUpdater.Status> implement @Override public void run() { Looper.prepare(); - new PeriodicHandler(1800000L) { - @Override - public void act() { - requestUpdate(); - } - }.start(); + new PeriodicHandler(1800000L, this).start(); Looper.loop(); } diff --git a/main/src/cgeo/geocaching/speech/SpeechService.java b/main/src/cgeo/geocaching/speech/SpeechService.java new file mode 100644 index 0000000..7226014 --- /dev/null +++ b/main/src/cgeo/geocaching/speech/SpeechService.java @@ -0,0 +1,188 @@ +package cgeo.geocaching.speech; + +import cgeo.geocaching.DirectionProvider; +import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.utils.GeoDirHandler; +import cgeo.geocaching.utils.Log; + +import org.apache.commons.lang3.StringUtils; + +import android.app.Activity; +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; +import android.speech.tts.TextToSpeech; +import android.speech.tts.TextToSpeech.OnInitListener; + +import java.util.Locale; + +/** + * Service to speak the compass directions. + * + */ +public class SpeechService extends Service implements OnInitListener { + + private static final int SPEECH_MINPAUSE_SECONDS = 5; + private static final int SPEECH_MAXPAUSE_SECONDS = 30; + private static final String EXTRA_TARGET_COORDS = "target"; + private static Activity startingActivity; + private static boolean isRunning = false; + /** + * Text to speech API of Android + */ + private TextToSpeech tts; + /** + * TTS has been initialized and we can speak. + */ + private boolean initialized = false; + protected float direction; + protected Geopoint position; + protected boolean directionInitialized; + protected boolean positionInitialized; + + GeoDirHandler geoHandler = new GeoDirHandler() { + @Override + protected void updateDirection(float newDirection) { + direction = DirectionProvider.getDirectionNow(startingActivity, newDirection); + directionInitialized = true; + updateCompass(); + } + + @Override + protected void updateGeoData(cgeo.geocaching.IGeoData newGeo) { + position = newGeo.getCoords(); + positionInitialized = true; + if (newGeo.getSpeed() > 5) { + direction = newGeo.getBearing(); + directionInitialized = true; + } + updateCompass(); + } + }; + /** + * remember when we talked the last time + */ + private long lastSpeechTime = 0; + private float lastSpeechDistance = 0.0f; + private Geopoint target; + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + protected void updateCompass() { + // make sure we have both sensor values before talking + if (!positionInitialized || !directionInitialized) { + return; + } + + // avoid any calculation, if the delay since the last output is not long enough + final long now = System.currentTimeMillis(); + if (now - lastSpeechTime <= SPEECH_MINPAUSE_SECONDS * 1000) { + return; + } + + // to speak, we want max pause to have elapsed or distance to geopoint to have changed by a given amount + final float distance = position.distanceTo(target); + if (now - lastSpeechTime <= SPEECH_MAXPAUSE_SECONDS * 1000) { + if (Math.abs(lastSpeechDistance - distance) < getDeltaForDistance(distance)) { + return; + } + } + + final String text = TextFactory.getText(position, target, direction); + if (StringUtils.isNotEmpty(text)) { + lastSpeechTime = System.currentTimeMillis(); + lastSpeechDistance = distance; + speak(text); + } + } + + /** + * Return distance required to be moved based on overall distance.<br> + * + * @param distance + * in km + * @return delta in km + */ + private static float getDeltaForDistance(final float distance) { + if (distance > 1.0) { + return 0.2f; + } else if (distance > 0.05) { + return distance / 5.0f; + } + + return 0f; + } + + @Override + public void onCreate() { + super.onCreate(); + tts = new TextToSpeech(this, this); + } + + @Override + public void onDestroy() { + geoHandler.stopGeoAndDir(); + if (tts != null) { + tts.stop(); + tts.shutdown(); + } + super.onDestroy(); + } + + @Override + public void onInit(int status) { + // The text to speech system takes some time to initialize. + if (status != TextToSpeech.SUCCESS) { + Log.e("Text to speech cannot be initialized."); + return; + } + + int switchLocale = tts.setLanguage(Locale.getDefault()); + + if (switchLocale == TextToSpeech.LANG_MISSING_DATA + || switchLocale == TextToSpeech.LANG_NOT_SUPPORTED) { + Log.e("Current languge not supported by text to speech."); + return; + } + + initialized = true; + + geoHandler.startGeoAndDir(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (intent != null) { + target = intent.getParcelableExtra(EXTRA_TARGET_COORDS); + } + return START_NOT_STICKY; + } + + private void speak(final String text) { + if (!initialized) { + return; + } + tts.speak(text, TextToSpeech.QUEUE_FLUSH, null); + } + + public static void startService(final Activity activity, Geopoint dstCoords) { + isRunning = true; + startingActivity = activity; + Intent talkingService = new Intent(activity, SpeechService.class); + talkingService.putExtra(EXTRA_TARGET_COORDS, dstCoords); + activity.startService(talkingService); + } + + public static void stopService(final Activity activity) { + isRunning = false; + activity.stopService(new Intent(activity, SpeechService.class)); + } + + public static boolean isRunning() { + return isRunning; + } + +} diff --git a/main/src/cgeo/geocaching/speech/TextFactory.java b/main/src/cgeo/geocaching/speech/TextFactory.java new file mode 100644 index 0000000..d4f3a48 --- /dev/null +++ b/main/src/cgeo/geocaching/speech/TextFactory.java @@ -0,0 +1,101 @@ +package cgeo.geocaching.speech; + +import cgeo.geocaching.R; +import cgeo.geocaching.Settings; +import cgeo.geocaching.cgeoapplication; +import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.geopoint.IConversion; +import cgeo.geocaching.utils.AngleUtils; + +import java.util.Locale; + +/** + * Creates the output to be read by TTS. + * + */ +public class TextFactory { + public static String getText(Geopoint position, Geopoint target, float direction) { + if (position == null || target == null) { + return null; + } + return getDirection(position, target, direction) + ". " + getDistance(position, target); + } + + private static String getDistance(Geopoint position, Geopoint target) { + float kilometers = position.distanceTo(target); + + if (Settings.isUseMetricUnits()) { + return getDistance(kilometers, (int) (kilometers * 1000.0), + 5.0f, 1.0f, 50, + R.plurals.tts_kilometers, R.string.tts_one_kilometer, + R.plurals.tts_meters, R.string.tts_one_meter); + } + return getDistance(kilometers / IConversion.MILES_TO_KILOMETER, + (int) (kilometers * 1000.0 * IConversion.METERS_TO_FEET), + 3.0f, 0.2f, 300, + R.plurals.tts_miles, R.string.tts_one_mile, + R.plurals.tts_feet, R.string.tts_one_foot); + } + + private static String getDistance(float farDistance, int nearDistance, + float farFarAway, float farNearAway, int nearFarAway, + int farId, int farOneId, int nearId, int nearOneId) { + if (farDistance >= farFarAway) { + // example: "5 kilometers" - always without decimal digits + int quantity = Math.round(farDistance); + if (quantity == 1) { + return getString(farOneId, quantity, String.valueOf(quantity)); + } + return getQuantityString(farId, quantity, String.valueOf(quantity)); + } + if (farDistance >= farNearAway) { + // example: "2.2 kilometers" - decimals if necessary + float precision1 = Math.round(farDistance * 10.0f) / 10.0f; + float precision0 = Math.round(farDistance); + if (precision1 == precision0) { + // this is an int - e.g. 2 kilometers + int quantity = (int) precision0; + if (quantity == 1) { + return getString(farOneId, quantity, String.valueOf(quantity)); + } + return getQuantityString(farId, quantity, String.valueOf(quantity)); + } + // this is no int - e.g. 1.7 kilometers + String digits = String.format(Locale.getDefault(), "%.1f", farDistance); + // always use the plural (9 leads to plural) + return getQuantityString(farId, 9, digits); + } + // example: "34 meters" + int quantity = nearDistance; + if (quantity > nearFarAway) { + // example: "120 meters" - rounded to 10 meters + quantity = (int) Math.round(quantity / 10.0) * 10; + } + if (quantity == 1) { + return getString(nearOneId, quantity, String.valueOf(quantity)); + } + return getQuantityString(nearId, quantity, String.valueOf(quantity)); + } + + private static String getString(int resourceId, Object... formatArgs) { + return cgeoapplication.getInstance().getString(resourceId, formatArgs); + } + + private static String getQuantityString(int resourceId, int quantity, Object... formatArgs) { + return cgeoapplication.getInstance().getResources().getQuantityString(resourceId, quantity, formatArgs); + } + + private static String getDirection(Geopoint position, Geopoint target, float direction) { + final int bearing = (int) position.bearingTo(target); + int degrees = (int) AngleUtils.normalize(bearing - direction); + + int hours = (degrees + 15) / 30; + if (hours == 0) { + hours = 12; + } + if (hours == 1) { + return getString(R.string.tts_one_oclock, String.valueOf(hours)); + } + return getString(R.string.tts_oclock, String.valueOf(hours)); + } +} diff --git a/main/src/cgeo/geocaching/twitter/Twitter.java b/main/src/cgeo/geocaching/twitter/Twitter.java index f30830e..3f1f749 100644 --- a/main/src/cgeo/geocaching/twitter/Twitter.java +++ b/main/src/cgeo/geocaching/twitter/Twitter.java @@ -15,7 +15,10 @@ import cgeo.geocaching.utils.Log; import ch.boye.httpclientandroidlib.HttpResponse; +import org.apache.commons.lang3.StringUtils; + public final class Twitter { + private static final String HASH_PREFIX_WITH_BLANK = " #"; public static final int MAX_TWEET_SIZE = 140; public static void postTweet(final cgeoapplication app, final String status, final Geopoint coords) { @@ -35,7 +38,7 @@ public final class Twitter { "display_coordinates", "true"); } - OAuth.signOAuth("api.twitter.com", "/1/statuses/update.json", "POST", false, parameters, Settings.getTokenPublic(), Settings.getTokenSecret()); + OAuth.signOAuth("api.twitter.com", "/1/statuses/update.json", "POST", false, parameters, Settings.getTokenPublic(), Settings.getTokenSecret(), Settings.getKeyConsumerPublic(), Settings.getKeyConsumerSecret()); final HttpResponse httpResponse = Network.postRequest("http://api.twitter.com/1/statuses/update.json", parameters); if (httpResponse != null && httpResponse.getStatusLine().getStatusCode() == 200) { Log.i("Tweet posted"); @@ -43,53 +46,60 @@ public final class Twitter { Log.e("Tweet could not be posted"); } } catch (Exception e) { - Log.e("cgBase.postTweet", e); + Log.e("Twitter.postTweet", e); } } - public static String appendHashTag(final String status, final String tag) { - String result = status; - if (result.length() + 2 + tag.length() <= 140) { - result += " #" + tag; + public static void appendHashTag(final StringBuilder status, final String tag) { + if (status.length() + HASH_PREFIX_WITH_BLANK.length() + tag.length() <= MAX_TWEET_SIZE) { + final String tagWithPrefix = HASH_PREFIX_WITH_BLANK + tag; + if (status.indexOf(tagWithPrefix, 0) == -1) { + status.append(tagWithPrefix); + } } - return result; } public static void postTweetCache(String geocode) { - final Geocache cache = cgData.loadCache(geocode, LoadFlags.LOAD_CACHE_OR_DB); - String status; - final String url = cache.getUrl(); - if (url.length() >= 100) { - status = "I found " + url; + if (!Settings.isUseTwitter()) { + return; } - else { - String name = cache.getName(); - status = "I found " + name + " (" + url + ")"; - if (status.length() > MAX_TWEET_SIZE) { - name = name.substring(0, name.length() - (status.length() - MAX_TWEET_SIZE) - 1) + '…'; - } - status = "I found " + name + " (" + url + ")"; - status = appendHashTag(status, "cgeo"); - status = appendHashTag(status, "geocaching"); + if (!Settings.isTwitterLoginValid()) { + return; } + final Geocache cache = cgData.loadCache(geocode, LoadFlags.LOAD_CACHE_OR_DB); + postTweet(cgeoapplication.getInstance(), getStatusMessage(cache), null); + } + + static String getStatusMessage(Geocache cache) { + String name = cache.getName(); + if (name.length() > 100) { + name = name.substring(0, 100) + '…'; + } + final String url = StringUtils.defaultString(cache.getUrl()); + return fillTemplate(Settings.getCacheTwitterMessage(), name, url); + } - postTweet(cgeoapplication.getInstance(), status, null); + private static String fillTemplate(String template, String name, final String url) { + String result = StringUtils.replace(template, "[NAME]", name); + result = StringUtils.replace(result, "[URL]", url); + StringBuilder builder = new StringBuilder(result); + appendHashTag(builder, "cgeo"); + appendHashTag(builder, "geocaching"); + return builder.toString(); } public static void postTweetTrackable(String geocode) { final Trackable trackable = cgData.loadTrackable(geocode); + postTweet(cgeoapplication.getInstance(), getStatusMessage(trackable), null); + } + + static String getStatusMessage(Trackable trackable) { String name = trackable.getName(); if (name.length() > 82) { name = name.substring(0, 81) + '…'; } - StringBuilder builder = new StringBuilder("I touched "); - builder.append(name); - if (trackable.getUrl() != null) { - builder.append(" (").append(trackable.getUrl()).append(')'); - } - builder.append('!'); - String status = appendHashTag(builder.toString(), "cgeo"); - status = appendHashTag(status, "geocaching"); - postTweet(cgeoapplication.getInstance(), status, null); + String url = StringUtils.defaultString(trackable.getUrl()); + String status = Settings.getTrackableTwitterMessage(); + return fillTemplate(status, name, url); } } diff --git a/main/src/cgeo/geocaching/twitter/TwitterAuthorizationActivity.java b/main/src/cgeo/geocaching/twitter/TwitterAuthorizationActivity.java index 3d9f283..7146a62 100644 --- a/main/src/cgeo/geocaching/twitter/TwitterAuthorizationActivity.java +++ b/main/src/cgeo/geocaching/twitter/TwitterAuthorizationActivity.java @@ -2,268 +2,105 @@ package cgeo.geocaching.twitter; import cgeo.geocaching.R; import cgeo.geocaching.Settings; -import cgeo.geocaching.activity.AbstractActivity; -import cgeo.geocaching.network.Network; -import cgeo.geocaching.network.OAuth; -import cgeo.geocaching.network.Parameters; -import cgeo.geocaching.utils.Log; -import cgeo.geocaching.utils.MatcherWrapper; +import cgeo.geocaching.network.OAuthAuthorizationActivity; -import ch.boye.httpclientandroidlib.client.entity.UrlEncodedFormEntity; -import ch.boye.httpclientandroidlib.util.EntityUtils; - -import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; -import android.app.ProgressDialog; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.view.View; -import android.widget.Button; -import android.widget.EditText; - -import java.util.regex.Pattern; - -public class TwitterAuthorizationActivity extends AbstractActivity { - private String OAtoken = null; - private String OAtokenSecret = null; - private final Pattern paramsPattern1 = Pattern.compile("oauth_token=([a-zA-Z0-9\\-\\_.]+)"); - private final Pattern paramsPattern2 = Pattern.compile("oauth_token_secret=([a-zA-Z0-9\\-\\_.]+)"); - private Button startButton = null; - private EditText pinEntry = null; - private Button pinEntryButton = null; - private ProgressDialog requestTokenDialog = null; - private ProgressDialog changeTokensDialog = null; - private Handler requestTokenHandler = new Handler() { - - @Override - public void handleMessage(Message msg) { - if (requestTokenDialog != null && requestTokenDialog.isShowing()) { - requestTokenDialog.dismiss(); - } - - startButton.setOnClickListener(new StartListener()); - startButton.setEnabled(true); - - if (msg.what == 1) { - startButton.setText(res.getString(R.string.auth_again)); - - pinEntry.setVisibility(View.VISIBLE); - pinEntryButton.setVisibility(View.VISIBLE); - pinEntryButton.setOnClickListener(new ConfirmPINListener()); - } else { - showToast(res.getString(R.string.err_auth_initialize)); - startButton.setText(res.getString(R.string.auth_start)); - } - } - }; - private Handler changeTokensHandler = new Handler() { +public class TwitterAuthorizationActivity extends OAuthAuthorizationActivity { - @Override - public void handleMessage(Message msg) { - if (changeTokensDialog != null && changeTokensDialog.isShowing()) { - changeTokensDialog.dismiss(); - } - - pinEntryButton.setOnClickListener(new ConfirmPINListener()); - pinEntryButton.setEnabled(true); - - if (msg.what == 1) { - showToast(res.getString(R.string.auth_dialog_completed)); - - pinEntryButton.setVisibility(View.GONE); - - finish(); - } else { - showToast(res.getString(R.string.err_auth_process)); - - pinEntry.setVisibility(View.GONE); - pinEntryButton.setVisibility(View.GONE); - startButton.setText(res.getString(R.string.auth_start)); - } - } - }; + public TwitterAuthorizationActivity() { + super("api.twitter.com", + "/oauth/request_token", + "/oauth/authorize", + "/oauth/access_token", + true, + Settings.getKeyConsumerPublic(), + Settings.getKeyConsumerSecret()); + } @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setTheme(); - setContentView(R.layout.twitter_authorization_activity); - setTitle(res.getString(R.string.auth_twitter)); - - init(); + protected ImmutablePair<String, String> getTempToken() { + return Settings.getTempToken(); } @Override - public void onResume() { - super.onResume(); - + protected void setTempTokens(String tokenPublic, String tokenSecret) { + Settings.setTwitterTempTokens(tokenPublic, tokenSecret); } - private void init() { - startButton = (Button) findViewById(R.id.start); - pinEntry = (EditText) findViewById(R.id.pin); - pinEntryButton = (Button) findViewById(R.id.pin_button); - - ImmutablePair<String, String> tempToken = Settings.getTempToken(); - OAtoken = tempToken.left; - OAtokenSecret = tempToken.right; - - startButton.setEnabled(true); - startButton.setOnClickListener(new StartListener()); - - if (StringUtils.isBlank(OAtoken) && StringUtils.isBlank(OAtokenSecret)) { - // start authorization process - startButton.setText(res.getString(R.string.auth_start)); - } else { - // already have temporary tokens, continue from pin - startButton.setText(res.getString(R.string.auth_again)); - - pinEntry.setVisibility(View.VISIBLE); - pinEntryButton.setVisibility(View.VISIBLE); - pinEntryButton.setOnClickListener(new ConfirmPINListener()); - } + @Override + protected void setTokens(String tokenPublic, String tokenSecret, boolean enable) { + Settings.setTwitterTokens(tokenPublic, tokenSecret, enable); } - private void requestToken() { - - int status = 0; - try { - final Parameters params = new Parameters(); - final String method = "GET"; - final String pathRequest = "/oauth/request_token"; - final String host = "api.twitter.com"; - OAuth.signOAuth(host, pathRequest, method, true, params, null, null); - final String line = Network.getResponseData(Network.getRequest("https://" + host + pathRequest, params)); - - - if (StringUtils.isNotBlank(line)) { - final MatcherWrapper paramsMatcher1 = new MatcherWrapper(paramsPattern1, line); - if (paramsMatcher1.find()) { - OAtoken = paramsMatcher1.group(1); - } - final MatcherWrapper paramsMatcher2 = new MatcherWrapper(paramsPattern2, line); - if (paramsMatcher2.find()) { - OAtokenSecret = paramsMatcher2.group(1); - } - - if (StringUtils.isNotBlank(OAtoken) && StringUtils.isNotBlank(OAtokenSecret)) { - Settings.setTwitterTempTokens(OAtoken, OAtokenSecret); - try { - final Parameters paramsBrowser = new Parameters(); - paramsBrowser.put("oauth_callback", "oob"); - final String pathAuthorize = "/oauth/authorize"; - OAuth.signOAuth(host, pathAuthorize, "GET", true, paramsBrowser, OAtoken, OAtokenSecret); - final String encodedParams = EntityUtils.toString(new UrlEncodedFormEntity(paramsBrowser)); - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://" + host + pathAuthorize + "?" + encodedParams))); - status = 1; - } catch (Exception e) { - Log.e("TwitterAuthorizationActivity.requestToken(2)", e); - } - } - } - } catch (Exception e) { - Log.e("TwitterAuthorizationActivity.requestToken(1)", e); - } - - requestTokenHandler.sendEmptyMessage(status); + @Override + protected String getAuthTitle() { + return res.getString(R.string.auth_twitter); } - private void changeToken() { - - int status = 0; - - try { - final Parameters params = new Parameters("oauth_verifier", pinEntry.getText().toString()); - - final String method = "POST"; - final String path = "/oauth/access_token"; - final String host = "api.twitter.com"; - OAuth.signOAuth(host, path, method, true, params, OAtoken, OAtokenSecret); - final String line = StringUtils.defaultString(Network.getResponseData(Network.postRequest("https://" + host + path, params))); - - OAtoken = ""; - OAtokenSecret = ""; + @Override + protected String getAuthAgain() { + return res.getString(R.string.auth_again); + } - final MatcherWrapper paramsMatcher1 = new MatcherWrapper(paramsPattern1, line); - if (paramsMatcher1.find()) { - OAtoken = paramsMatcher1.group(1); - } - final MatcherWrapper paramsMatcher2 = new MatcherWrapper(paramsPattern2, line); - if (paramsMatcher2.find() && paramsMatcher2.groupCount() > 0) { - OAtokenSecret = paramsMatcher2.group(1); - } + @Override + protected String getErrAuthInitialize() { + return res.getString(R.string.err_auth_initialize); + } - if (StringUtils.isBlank(OAtoken) && StringUtils.isBlank(OAtokenSecret)) { - OAtoken = ""; - OAtokenSecret = ""; - Settings.setTwitterTokens(null, null, false); - } else { - Settings.setTwitterTokens(OAtoken, OAtokenSecret, true); - status = 1; - } - } catch (Exception e) { - Log.e("TwitterAuthorizationActivity.changeToken", e); - } + @Override + protected String getAuthStart() { + return res.getString(R.string.auth_start); + } - changeTokensHandler.sendEmptyMessage(status); + @Override + protected String getAuthDialogCompleted() { + return res.getString(R.string.auth_dialog_completed); } - private class StartListener implements View.OnClickListener { + @Override + protected String getErrAuthProcess() { + return res.getString(R.string.err_auth_process); + } - @Override - public void onClick(View arg0) { - if (requestTokenDialog == null) { - requestTokenDialog = new ProgressDialog(TwitterAuthorizationActivity.this); - requestTokenDialog.setCancelable(false); - requestTokenDialog.setMessage(res.getString(R.string.auth_dialog_wait)); - } - requestTokenDialog.show(); - startButton.setEnabled(false); - startButton.setOnTouchListener(null); - startButton.setOnClickListener(null); + @Override + protected String getAuthDialogWait() { + return res.getString(R.string.auth_dialog_wait); + } - Settings.setTwitterTempTokens(null, null); - (new Thread() { + @Override + protected String getAuthDialogPinTitle() { + return res.getString(R.string.auth_dialog_pin_title); + } - @Override - public void run() { - requestToken(); - } - }).start(); - } + @Override + protected String getAuthDialogPinMessage() { + return res.getString(R.string.auth_dialog_pin_message); } - private class ConfirmPINListener implements View.OnClickListener { + @Override + protected String getAboutAuth1() { + return res.getString(R.string.about_auth_1); + } - @Override - public void onClick(View arg0) { - if (StringUtils.isEmpty(((EditText) findViewById(R.id.pin)).getText().toString())) { - helpDialog(res.getString(R.string.auth_dialog_pin_title), res.getString(R.string.auth_dialog_pin_message)); - return; - } + @Override + protected String getAboutAuth2() { + return res.getString(R.string.about_auth_2); + } - if (changeTokensDialog == null) { - changeTokensDialog = new ProgressDialog(TwitterAuthorizationActivity.this); - changeTokensDialog.setCancelable(false); - changeTokensDialog.setMessage(res.getString(R.string.auth_dialog_wait)); - } - changeTokensDialog.show(); - pinEntryButton.setEnabled(false); - pinEntryButton.setOnTouchListener(null); - pinEntryButton.setOnClickListener(null); + @Override + protected String getAuthAuthorize() { + return res.getString(R.string.auth_authorize); + } - (new Thread() { + @Override + protected String getAuthPinHint() { + return res.getString(R.string.auth_pin_hint); + } - @Override - public void run() { - changeToken(); - } - }).start(); - } + @Override + protected String getAuthFinish() { + return res.getString(R.string.auth_finish); } + } diff --git a/main/src/cgeo/geocaching/ui/AbstractUserClickListener.java b/main/src/cgeo/geocaching/ui/AbstractUserClickListener.java new file mode 100644 index 0000000..b5e5c9a --- /dev/null +++ b/main/src/cgeo/geocaching/ui/AbstractUserClickListener.java @@ -0,0 +1,77 @@ +package cgeo.geocaching.ui; + +import cgeo.geocaching.Geocache; +import cgeo.geocaching.R; +import cgeo.geocaching.cgeocaches; +import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.network.Network; + +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.Resources; +import android.net.Uri; +import android.view.View; + +abstract class AbstractUserClickListener implements View.OnClickListener { + + protected final Geocache cache; + + public AbstractUserClickListener(final Geocache cache) { + this.cache = cache; + } + + @Override + public void onClick(View view) { + if (view == null) { + return; + } + if (!cache.supportsUserActions()) { + return; + } + + showUserActionsDialog(getUserName(view), view); + } + + protected abstract CharSequence getUserName(View view); + + /** + * Opens a dialog to do actions on an user name + */ + protected static void showUserActionsDialog(final CharSequence name, final View view) { + final AbstractActivity context = (AbstractActivity) view.getContext(); + final Resources res = context.getResources(); + final CharSequence[] items = { res.getString(R.string.user_menu_view_hidden), + res.getString(R.string.user_menu_view_found), + res.getString(R.string.user_menu_open_browser), + res.getString(R.string.user_menu_send_message) + }; + + final AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(res.getString(R.string.user_menu_title) + " " + name); + builder.setItems(items, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int item) { + switch (item) { + case 0: + cgeocaches.startActivityOwner(context, name.toString()); + return; + case 1: + cgeocaches.startActivityUserName(context, name.toString()); + return; + case 2: + context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.geocaching.com/profile/?u=" + Network.encode(name.toString())))); + return; + case 3: + context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.geocaching.com/email/?u=" + Network.encode(name.toString())))); + return; + default: + break; + } + } + }); + final AlertDialog alert = builder.create(); + alert.show(); + } + +} diff --git a/main/src/cgeo/geocaching/ui/AbstractViewHolder.java b/main/src/cgeo/geocaching/ui/AbstractViewHolder.java new file mode 100644 index 0000000..cc5cd4d --- /dev/null +++ b/main/src/cgeo/geocaching/ui/AbstractViewHolder.java @@ -0,0 +1,19 @@ +package cgeo.geocaching.ui; + +import butterknife.Views; + +import android.view.View; + +/** + * Abstract super class for all view holders. It is responsible for the invocation of the view injection code and for + * the tagging of views. + * + */ +public abstract class AbstractViewHolder { + + protected AbstractViewHolder(View view) { + Views.inject(this, view); + view.setTag(this); + } + +} diff --git a/main/src/cgeo/geocaching/ui/AddressListAdapter.java b/main/src/cgeo/geocaching/ui/AddressListAdapter.java index eb8b516..736c036 100644 --- a/main/src/cgeo/geocaching/ui/AddressListAdapter.java +++ b/main/src/cgeo/geocaching/ui/AddressListAdapter.java @@ -1,5 +1,7 @@ package cgeo.geocaching.ui; +import butterknife.InjectView; + import cgeo.geocaching.R; import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.cgeocaches; @@ -24,9 +26,13 @@ public class AddressListAdapter extends ArrayAdapter<Address> { final private LayoutInflater inflater; final private Geopoint location; - private static final class ViewHolder { - TextView label; - TextView distance; + protected static final class ViewHolder extends AbstractViewHolder { + @InjectView(R.id.label) protected TextView label; + @InjectView(R.id.distance) protected TextView distance; + + public ViewHolder(View view) { + super(view); + } } public AddressListAdapter(final Context context) { @@ -44,13 +50,8 @@ public class AddressListAdapter extends ArrayAdapter<Address> { // holder pattern implementation final ViewHolder holder; if (view == null) { - view = inflater.inflate(R.layout.addresses_item, null); - - holder = new ViewHolder(); - holder.label = (TextView) view.findViewById(R.id.label); - holder.distance = (TextView) view.findViewById(R.id.distance); - - view.setTag(holder); + view = inflater.inflate(R.layout.addresslist_item, null); + holder = new ViewHolder(view); } else { holder = (ViewHolder) view.getTag(); } diff --git a/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java b/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java index e98bd77..9059a6b 100644 --- a/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java +++ b/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java @@ -2,6 +2,7 @@ package cgeo.geocaching.ui; import cgeo.geocaching.Geocache; import cgeo.geocaching.R; +import cgeo.geocaching.Waypoint; import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.Units; @@ -67,7 +68,7 @@ public final class CacheDetailsCreator { final LayoutInflater inflater = LayoutInflater.from(activity); for (int i = 0; i < 5; i++) { - ImageView star = (ImageView) inflater.inflate(R.layout.star, null); + ImageView star = (ImageView) inflater.inflate(R.layout.star_image, null); if (value - i >= 0.75) { star.setImageResource(R.drawable.star_on); } else if (value - i >= 0.25) { @@ -154,4 +155,24 @@ public final class CacheDetailsCreator { } add(R.string.cache_distance, text); } + + public void addDistance(final Waypoint wpt, final TextView waypointDistanceView) { + Float distance = null; + if (wpt.getCoords() != null) { + final Geopoint currentCoords = cgeoapplication.getInstance().currentGeo().getCoords(); + if (currentCoords != null) { + distance = currentCoords.distanceTo(wpt); + } + } + String text = "--"; + if (distance != null) { + text = Units.getDistanceFromKilometers(distance); + } + else if (waypointDistanceView != null) { + // if there is already a distance in waypointDistance, use it instead of resetting to default. + // this prevents displaying "--" while waiting for a new position update (See bug #1468) + text = waypointDistanceView.getText().toString(); + } + add(R.string.cache_distance, text); + } } diff --git a/main/src/cgeo/geocaching/ui/CacheListAdapter.java b/main/src/cgeo/geocaching/ui/CacheListAdapter.java index 42b774e..d95363e 100644 --- a/main/src/cgeo/geocaching/ui/CacheListAdapter.java +++ b/main/src/cgeo/geocaching/ui/CacheListAdapter.java @@ -1,5 +1,7 @@ package cgeo.geocaching.ui; +import butterknife.InjectView; + import cgeo.geocaching.CacheDetailActivity; import cgeo.geocaching.Geocache; import cgeo.geocaching.IGeoData; @@ -77,13 +79,13 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> { private static final int[] RATING_BACKGROUND = new int[3]; static { if (Settings.isLightSkin()) { - RATING_BACKGROUND[0] = R.drawable.favourite_background_red_light; - RATING_BACKGROUND[1] = R.drawable.favourite_background_orange_light; - RATING_BACKGROUND[2] = R.drawable.favourite_background_green_light; + RATING_BACKGROUND[0] = R.drawable.favorite_background_red_light; + RATING_BACKGROUND[1] = R.drawable.favorite_background_orange_light; + RATING_BACKGROUND[2] = R.drawable.favorite_background_green_light; } else { - RATING_BACKGROUND[0] = R.drawable.favourite_background_red_dark; - RATING_BACKGROUND[1] = R.drawable.favourite_background_orange_dark; - RATING_BACKGROUND[2] = R.drawable.favourite_background_green_dark; + RATING_BACKGROUND[0] = R.drawable.favorite_background_red_dark; + RATING_BACKGROUND[1] = R.drawable.favorite_background_orange_dark; + RATING_BACKGROUND[2] = R.drawable.favorite_background_green_dark; } } @@ -91,16 +93,20 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> { * view holder for the cache list adapter * */ - private static class ViewHolder { - CheckBox checkbox; - ImageView logStatusMark; - TextView text; - TextView favourite; - TextView info; - ImageView inventory; - DistanceView distance; - CompassMiniView direction; - ImageView dirImg; + protected static class ViewHolder extends AbstractViewHolder { + @InjectView(R.id.checkbox) protected CheckBox checkbox; + @InjectView(R.id.log_status_mark) protected ImageView logStatusMark; + @InjectView(R.id.text) protected TextView text; + @InjectView(R.id.distance) protected DistanceView distance; + @InjectView(R.id.favorite) protected TextView favorite; + @InjectView(R.id.info) protected TextView info; + @InjectView(R.id.inventory) protected ImageView inventory; + @InjectView(R.id.direction) protected CompassMiniView direction; + @InjectView(R.id.dirimg) protected ImageView dirImg; + + public ViewHolder(View view) { + super(view); + } } public CacheListAdapter(final Activity activity, final List<Geocache> list, CacheListType cacheListType) { @@ -348,20 +354,9 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> { final ViewHolder holder; if (v == null) { - v = inflater.inflate(R.layout.caches_item, null); - - holder = new ViewHolder(); - holder.checkbox = (CheckBox) v.findViewById(R.id.checkbox); - holder.logStatusMark = (ImageView) v.findViewById(R.id.log_status_mark); - holder.text = (TextView) v.findViewById(R.id.text); - holder.distance = (DistanceView) v.findViewById(R.id.distance); - holder.direction = (CompassMiniView) v.findViewById(R.id.direction); - holder.dirImg = (ImageView) v.findViewById(R.id.dirimg); - holder.inventory = (ImageView) v.findViewById(R.id.inventory); - holder.favourite = (TextView) v.findViewById(R.id.favourite); - holder.info = (TextView) v.findViewById(R.id.info); - - v.setTag(holder); + v = inflater.inflate(R.layout.cacheslist_item, null); + + holder = new ViewHolder(v); } else { holder = (ViewHolder) v.getTag(); } @@ -453,14 +448,14 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> { holder.direction.setVisibility(View.GONE); } - holder.favourite.setText(Integer.toString(cache.getFavoritePoints())); + holder.favorite.setText(Integer.toString(cache.getFavoritePoints())); int favoriteBack; // set default background, neither vote nor rating may be available if (lightSkin) { - favoriteBack = R.drawable.favourite_background_light; + favoriteBack = R.drawable.favorite_background_light; } else { - favoriteBack = R.drawable.favourite_background_dark; + favoriteBack = R.drawable.favorite_background_dark; } final float myVote = cache.getMyVote(); if (myVote > 0) { // use my own rating for display, if I have voted @@ -481,7 +476,7 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> { favoriteBack = RATING_BACKGROUND[0]; } } - holder.favourite.setBackgroundResource(favoriteBack); + holder.favorite.setBackgroundResource(favoriteBack); if (cacheListType == CacheListType.HISTORY && cache.getVisitedDate() > 0) { holder.info.setText(Formatter.formatCacheInfoHistory(cache)); diff --git a/main/src/cgeo/geocaching/ui/CompassView.java b/main/src/cgeo/geocaching/ui/CompassView.java index 0ef3a43..b73a2a9 100644 --- a/main/src/cgeo/geocaching/ui/CompassView.java +++ b/main/src/cgeo/geocaching/ui/CompassView.java @@ -3,6 +3,7 @@ package cgeo.geocaching.ui; import cgeo.geocaching.R; import cgeo.geocaching.utils.AngleUtils; import cgeo.geocaching.utils.PeriodicHandler; +import cgeo.geocaching.utils.PeriodicHandler.PeriodicHandlerListener; import android.content.Context; import android.graphics.Bitmap; @@ -14,7 +15,7 @@ import android.util.AttributeSet; import android.util.FloatMath; import android.view.View; -public class CompassView extends View { +public class CompassView extends View implements PeriodicHandlerListener { private Context context = null; private Bitmap compassUnderlay = null; @@ -48,7 +49,7 @@ public class CompassView extends View { private int compassOverlayWidth = 0; private int compassOverlayHeight = 0; private boolean initialDisplay; - private final RedrawHandler redrawHandler = new RedrawHandler(); + private final PeriodicHandler redrawHandler = new PeriodicHandler(40, this); public CompassView(Context contextIn) { super(contextIn); @@ -145,26 +146,18 @@ public class CompassView extends View { return AngleUtils.normalize(actual + offset); } - private class RedrawHandler extends PeriodicHandler { - - public RedrawHandler() { - super(40); - } - - @Override - public void act() { - final float newAzimuthShown = smoothUpdate(northMeasured, azimuthShown); - final float newCacheHeadingShown = smoothUpdate(cacheHeadingMeasured, cacheHeadingShown); - if (Math.abs(AngleUtils.difference(azimuthShown, newAzimuthShown)) >= 2 || - Math.abs(AngleUtils.difference(cacheHeadingShown, newCacheHeadingShown)) >= 2) { - synchronized(CompassView.this) { - azimuthShown = newAzimuthShown; - cacheHeadingShown = newCacheHeadingShown; - } - invalidate(); + @Override + public void onPeriodic() { + final float newAzimuthShown = smoothUpdate(northMeasured, azimuthShown); + final float newCacheHeadingShown = smoothUpdate(cacheHeadingMeasured, cacheHeadingShown); + if (Math.abs(AngleUtils.difference(azimuthShown, newAzimuthShown)) >= 2 || + Math.abs(AngleUtils.difference(cacheHeadingShown, newCacheHeadingShown)) >= 2) { + synchronized(this) { + azimuthShown = newAzimuthShown; + cacheHeadingShown = newCacheHeadingShown; } + invalidate(); } - } @Override @@ -178,12 +171,12 @@ public class CompassView extends View { headingDrawn = cacheHeadingShown; } - float azimuthTemp = azimuthDrawn; + final float azimuthTemp = azimuthDrawn; final float azimuthRelative = AngleUtils.normalize(azimuthTemp - headingDrawn); // compass margins - int canvasCenterX = (compassRoseWidth / 2) + ((getWidth() - compassRoseWidth) / 2); - int canvasCenterY = (compassRoseHeight / 2) + ((getHeight() - compassRoseHeight) / 2); + final int canvasCenterX = (compassRoseWidth / 2) + ((getWidth() - compassRoseWidth) / 2); + final int canvasCenterY = (compassRoseHeight / 2) + ((getHeight() - compassRoseHeight) / 2); super.onDraw(canvas); @@ -224,38 +217,36 @@ public class CompassView extends View { } private int measureWidth(int measureSpec) { - int result; - int specMode = MeasureSpec.getMode(measureSpec); - int specSize = MeasureSpec.getSize(measureSpec); + final int specMode = MeasureSpec.getMode(measureSpec); + final int specSize = MeasureSpec.getSize(measureSpec); if (specMode == MeasureSpec.EXACTLY) { - result = specSize; - } else { - result = compassArrow.getWidth() + getPaddingLeft() + getPaddingRight(); + return specSize; + } - if (specMode == MeasureSpec.AT_MOST) { - result = Math.min(result, specSize); - } + final int desired = compassArrow.getWidth() + getPaddingLeft() + getPaddingRight(); + if (specMode == MeasureSpec.AT_MOST) { + return Math.min(desired, specSize); } - return result; + return desired; } private int measureHeight(int measureSpec) { - int result; - int specMode = MeasureSpec.getMode(measureSpec); - int specSize = MeasureSpec.getSize(measureSpec); + // The duplicated code in measureHeight and measureWidth cannot be avoided. + // Those methods must be efficient, therefore we cannot extract the code differences and unify the remainder. + final int specMode = MeasureSpec.getMode(measureSpec); + final int specSize = MeasureSpec.getSize(measureSpec); if (specMode == MeasureSpec.EXACTLY) { - result = specSize; - } else { - result = compassArrow.getHeight() + getPaddingTop() + getPaddingBottom(); + return specSize; + } - if (specMode == MeasureSpec.AT_MOST) { - result = Math.min(result, specSize); - } + final int desired = compassArrow.getHeight() + getPaddingTop() + getPaddingBottom(); + if (specMode == MeasureSpec.AT_MOST) { + return Math.min(desired, specSize); } - return result; + return desired; } } diff --git a/main/src/cgeo/geocaching/ui/CoordinatesFormatSwitcher.java b/main/src/cgeo/geocaching/ui/CoordinatesFormatSwitcher.java new file mode 100644 index 0000000..afadb33 --- /dev/null +++ b/main/src/cgeo/geocaching/ui/CoordinatesFormatSwitcher.java @@ -0,0 +1,38 @@ +package cgeo.geocaching.ui; + +import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.geopoint.GeopointFormatter; + +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.TextView; + +/** + * view click listener to automatically switch different coordinate formats + * + */ +public class CoordinatesFormatSwitcher implements OnClickListener { + + private static GeopointFormatter.Format[] availableFormats = new GeopointFormatter.Format[] { + GeopointFormatter.Format.LAT_LON_DECMINUTE, + GeopointFormatter.Format.LAT_LON_DECSECOND, + GeopointFormatter.Format.LAT_LON_DECDEGREE + }; + + private int position = 0; + + private final Geopoint coordinates; + + public CoordinatesFormatSwitcher(final Geopoint coordinates) { + this.coordinates = coordinates; + } + + @Override + public void onClick(View view) { + position = (position + 1) % availableFormats.length; + TextView textView = (TextView) view; + // rotate coordinate formats on click + textView.setText(coordinates.format(availableFormats[position])); + } + +}
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/ui/DecryptTextClickListener.java b/main/src/cgeo/geocaching/ui/DecryptTextClickListener.java index 4ba88ae..f10e13a 100644 --- a/main/src/cgeo/geocaching/ui/DecryptTextClickListener.java +++ b/main/src/cgeo/geocaching/ui/DecryptTextClickListener.java @@ -16,6 +16,12 @@ public class DecryptTextClickListener implements View.OnClickListener { try {
final TextView logView = (TextView) view;
+
+ // do not run the click listener if a link was clicked
+ if (logView.getSelectionStart() != -1 || logView.getSelectionEnd() != -1) {
+ return;
+ }
+
CharSequence text = logView.getText();
if (text instanceof Spannable) {
Spannable span = (Spannable) text;
diff --git a/main/src/cgeo/geocaching/ui/EditNoteDialog.java b/main/src/cgeo/geocaching/ui/EditNoteDialog.java new file mode 100644 index 0000000..bbf0618 --- /dev/null +++ b/main/src/cgeo/geocaching/ui/EditNoteDialog.java @@ -0,0 +1,70 @@ +package cgeo.geocaching.ui; + +import cgeo.geocaching.R; +import cgeo.geocaching.R.string; + +import android.os.Bundle; +import android.support.v4.app.DialogFragment; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager.LayoutParams; +import android.view.inputmethod.EditorInfo; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.TextView.OnEditorActionListener; + +public class EditNoteDialog extends DialogFragment implements OnEditorActionListener { + + public interface EditNoteDialogListener { + void onFinishEditNoteDialog(final String inputText); + } + + public static final String ARGUMENT_INITIAL_NOTE = "initialNote"; + + private EditText mEditText; + private String initialNote; + + public static EditNoteDialog newInstance(final String initialNote) { + EditNoteDialog dialog = new EditNoteDialog(); + + Bundle arguments = new Bundle(); + arguments.putString(EditNoteDialog.ARGUMENT_INITIAL_NOTE, initialNote); + dialog.setArguments(arguments); + + return dialog; + } + + @Override + public View onCreateView(final LayoutInflater inflater, final ViewGroup container, + final Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_edit_note, container); + mEditText = (EditText) view.findViewById(R.id.note); + initialNote = getArguments().getString(ARGUMENT_INITIAL_NOTE); + if (initialNote != null) { + mEditText.setText(initialNote); + initialNote = null; + } + getDialog().setTitle(string.cache_personal_note); + mEditText.requestFocus(); + getDialog().getWindow().setSoftInputMode( + LayoutParams.SOFT_INPUT_STATE_VISIBLE); + mEditText.setOnEditorActionListener(this); + + return view; + } + + @Override + public boolean onEditorAction(final TextView v, final int actionId, final KeyEvent event) { + if (EditorInfo.IME_ACTION_DONE == actionId) { + final EditNoteDialogListener activity = (EditNoteDialogListener) getActivity(); + activity.onFinishEditNoteDialog(mEditText.getText().toString()); + dismiss(); + return true; + } + return false; + } + + +} diff --git a/main/src/cgeo/geocaching/ui/FileSelectionListAdapter.java b/main/src/cgeo/geocaching/ui/FileSelectionListAdapter.java index 1db3f21..c325f50 100644 --- a/main/src/cgeo/geocaching/ui/FileSelectionListAdapter.java +++ b/main/src/cgeo/geocaching/ui/FileSelectionListAdapter.java @@ -1,5 +1,7 @@ package cgeo.geocaching.ui; +import butterknife.InjectView; + import cgeo.geocaching.R; import cgeo.geocaching.files.IFileSelectionView; import cgeo.geocaching.utils.Log; @@ -17,23 +19,20 @@ import java.util.List; public class FileSelectionListAdapter extends ArrayAdapter<File> { - private IFileSelectionView parentView; - private LayoutInflater inflater; + private final IFileSelectionView parentView; + private final LayoutInflater inflater; public FileSelectionListAdapter(IFileSelectionView parentIn, List<File> listIn) { super(parentIn.getContext(), 0, listIn); parentView = parentIn; + inflater = ((Activity) getContext()).getLayoutInflater(); } @Override public View getView(final int position, final View rowView, final ViewGroup parent) { - if (inflater == null) { - inflater = ((Activity) getContext()).getLayoutInflater(); - } - if (position > getCount()) { - Log.w("cgGPXListAdapter.getView: Attempt to access missing item #" + position); + Log.w("FileSelectionListAdapter.getView: Attempt to access missing item #" + position); return null; } @@ -44,12 +43,7 @@ public class FileSelectionListAdapter extends ArrayAdapter<File> { ViewHolder holder; if (v == null) { v = inflater.inflate(R.layout.mapfile_item, null); - - holder = new ViewHolder(); - holder.filepath = (TextView) v.findViewById(R.id.mapfilepath); - holder.filename = (TextView) v.findViewById(R.id.mapfilename); - - v.setTag(holder); + holder = new ViewHolder(v); } else { holder = (ViewHolder) v.getTag(); } @@ -85,8 +79,12 @@ public class FileSelectionListAdapter extends ArrayAdapter<File> { } } - private static final class ViewHolder { - TextView filepath; - TextView filename; + protected static final class ViewHolder extends AbstractViewHolder { + @InjectView(R.id.mapfilepath) protected TextView filepath; + @InjectView(R.id.mapfilename) protected TextView filename; + + public ViewHolder(View view) { + super(view); + } } } diff --git a/main/src/cgeo/geocaching/ui/GPXListAdapter.java b/main/src/cgeo/geocaching/ui/GPXListAdapter.java index 9f6c14c..7f3c33f 100644 --- a/main/src/cgeo/geocaching/ui/GPXListAdapter.java +++ b/main/src/cgeo/geocaching/ui/GPXListAdapter.java @@ -1,7 +1,9 @@ package cgeo.geocaching.ui; -import cgeo.geocaching.R; +import butterknife.InjectView; + import cgeo.geocaching.GpxFileListActivity; +import cgeo.geocaching.R; import cgeo.geocaching.files.GPXImporter; import cgeo.geocaching.utils.Log; @@ -18,28 +20,29 @@ import java.io.File; import java.util.List; public class GPXListAdapter extends ArrayAdapter<File> { - private GpxFileListActivity activity = null; - private LayoutInflater inflater = null; + private final GpxFileListActivity activity; + private final LayoutInflater inflater; - private static class ViewHolder { - TextView filepath; - TextView filename; + protected static class ViewHolder extends AbstractViewHolder { + @InjectView(R.id.filepath) protected TextView filepath; + @InjectView(R.id.filename) protected TextView filename; + + public ViewHolder(View view) { + super(view); + } } public GPXListAdapter(GpxFileListActivity parentIn, List<File> listIn) { super(parentIn, 0, listIn); activity = parentIn; + inflater = ((Activity) getContext()).getLayoutInflater(); } @Override public View getView(final int position, final View rowView, final ViewGroup parent) { - if (inflater == null) { - inflater = ((Activity) getContext()).getLayoutInflater(); - } - if (position > getCount()) { - Log.w("cgGPXListAdapter.getView: Attempt to access missing item #" + position); + Log.w("GPXListAdapter.getView: Attempt to access missing item #" + position); return null; } @@ -50,12 +53,7 @@ public class GPXListAdapter extends ArrayAdapter<File> { final ViewHolder holder; if (view == null) { view = inflater.inflate(R.layout.gpx_item, null); - - holder = new ViewHolder(); - holder.filepath = (TextView) view.findViewById(R.id.filepath); - holder.filename = (TextView) view.findViewById(R.id.filename); - - view.setTag(holder); + holder = new ViewHolder(view); } else { holder = (ViewHolder) view.getTag(); } diff --git a/main/src/cgeo/geocaching/ui/ImagesList.java b/main/src/cgeo/geocaching/ui/ImagesList.java index 9464114..0f860c4 100644 --- a/main/src/cgeo/geocaching/ui/ImagesList.java +++ b/main/src/cgeo/geocaching/ui/ImagesList.java @@ -11,7 +11,6 @@ import cgeo.geocaching.utils.Log; import org.apache.commons.lang3.StringUtils; import android.app.Activity; -import android.app.ProgressDialog; import android.content.Intent; import android.content.res.Resources; import android.graphics.Bitmap; @@ -31,6 +30,7 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.util.Collection; @@ -39,23 +39,18 @@ import java.util.List; public class ImagesList { - private static final int MENU_FILE = 201; - private static final int MENU_BROWSER = 202; - private BitmapDrawable currentDrawable; private Image currentImage; public enum ImageType { - LogImages(R.string.cache_log_images_title, R.string.cache_log_images_loading), - SpoilerImages(R.string.cache_spoiler_images_title, R.string.cache_spoiler_images_loading), - AllImages(R.string.cache_images_title, R.string.cache_images_loading); + LogImages(R.string.cache_log_images_title), + SpoilerImages(R.string.cache_spoiler_images_title), + AllImages(R.string.cache_images_title); private final int titleResId; - private final int loadingResId; - ImageType(final int title, final int loading) { + ImageType(final int title) { this.titleResId = title; - this.loadingResId = loading; } public int getTitle() { @@ -64,9 +59,6 @@ public class ImagesList { } private LayoutInflater inflater = null; - private ProgressDialog progressDialog = null; - private int count = 0; - private int countDone = 0; private final Activity activity; // We could use a Set here, but we will insert no duplicates, so there is no need to check for uniqueness. private final Collection<Bitmap> bitmaps = new LinkedList<Bitmap>(); @@ -83,18 +75,10 @@ public class ImagesList { inflater = activity.getLayoutInflater(); } - public void loadImages(final View parentView, final List<Image> images, ImageType imageType, final boolean offline) { + public void loadImages(final View parentView, final List<Image> images, final boolean offline) { imagesView = (LinearLayout) parentView.findViewById(R.id.spoiler_list); - count = images.size(); - progressDialog = new ProgressDialog(activity); - progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); - progressDialog.setMessage(activity.getString(imageType.loadingResId)); - progressDialog.setCancelable(true); - progressDialog.setMax(count); - progressDialog.show(); - for (final Image img : images) { LinearLayout rowView = (LinearLayout) inflater.inflate(R.layout.cache_image_item, null); @@ -154,19 +138,12 @@ public class ImagesList { imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); imageView.setLayoutParams(new LayoutParams(bounds.width(), bounds.height())); + view.findViewById(R.id.progress_bar).setVisibility(View.GONE); view.addView(imageView); imageView.setId(image.hashCode()); images.put(imageView.getId(), img); } - - synchronized (activity) { - countDone++; - progressDialog.setProgress(countDone); - if (progressDialog.getProgress() >= count) { - progressDialog.dismiss(); - } - } } } @@ -179,10 +156,9 @@ public class ImagesList { } public void onCreateContextMenu(ContextMenu menu, View v) { + activity.getMenuInflater().inflate(R.menu.images_list_context, menu); final Resources res = activity.getResources(); menu.setHeaderTitle(res.getString(R.string.cache_image)); - menu.add(0, MENU_FILE, 0, res.getString(R.string.cache_image_open_file)); - menu.add(0, MENU_BROWSER, 0, res.getString(R.string.cache_image_open_browser)); final ImageView view = (ImageView) v; currentDrawable = (BitmapDrawable) view.getDrawable(); currentImage = images.get(view.getId()); @@ -190,10 +166,10 @@ public class ImagesList { public boolean onContextItemSelected(MenuItem item) { switch (item.getItemId()) { - case MENU_FILE: + case R.id.image_open_file: viewImageInStandardApp(currentDrawable); return true; - case MENU_BROWSER: + case R.id.image_open_browser: if (currentImage != null) { currentImage.openInBrowser(activity); } @@ -205,15 +181,15 @@ public class ImagesList { private void viewImageInStandardApp(final BitmapDrawable image) { final File file = LocalStorage.getStorageFile(null, "temp.jpg", false, true); - FileOutputStream fos = null; + BufferedOutputStream stream = null; try { - fos = new FileOutputStream(file); - image.getBitmap().compress(CompressFormat.JPEG, 100, fos); + stream = new BufferedOutputStream(new FileOutputStream(file)); + image.getBitmap().compress(CompressFormat.JPEG, 100, stream); } catch (Exception e) { - Log.e("ImagesActivity.handleMessage.onClick", e); + Log.e("ImagesList.viewImageInStandardApp", e); return; } finally { - IOUtils.closeQuietly(fos); + IOUtils.closeQuietly(stream); } final Intent intent = new Intent(); diff --git a/main/src/cgeo/geocaching/ui/LoggingUI.java b/main/src/cgeo/geocaching/ui/LoggingUI.java index 2615947..ac74dd3 100644 --- a/main/src/cgeo/geocaching/ui/LoggingUI.java +++ b/main/src/cgeo/geocaching/ui/LoggingUI.java @@ -61,31 +61,12 @@ public class LoggingUI extends AbstractUIFactory { } } - private static final int MENU_ICON_LOG_VISIT = R.drawable.ic_menu_edit; - private static final int MENU_LOG_VISIT = 100; - private static final int MENU_LOG_VISIT_OFFLINE = 101; - - public static void addMenuItems(final Menu menu, final Geocache cache) { - if (cache == null) { - return; - } - if (!cache.supportsLogging()) { - return; - } - if (Settings.getLogOffline()) { - menu.add(0, MENU_LOG_VISIT_OFFLINE, 0, res.getString(R.string.cache_menu_visit_offline)).setIcon(MENU_ICON_LOG_VISIT); - } - else { - menu.add(0, MENU_LOG_VISIT, 0, res.getString(R.string.cache_menu_visit)).setIcon(MENU_ICON_LOG_VISIT); - } - } - public static boolean onMenuItemSelected(final MenuItem item, IAbstractActivity activity, Geocache cache) { switch (item.getItemId()) { - case MENU_LOG_VISIT: + case R.id.menu_log_visit: cache.logVisit(activity); return true; - case MENU_LOG_VISIT_OFFLINE: + case R.id.menu_log_visit_offline: showOfflineMenu(cache, (Activity) activity); return true; default: @@ -123,7 +104,7 @@ public class LoggingUI extends AbstractUIFactory { break; case CLEAR_LOG: - cgData.clearLogOffline(cache.getGeocode()); + cache.clearOfflineLog(); break; } } else { @@ -136,10 +117,17 @@ public class LoggingUI extends AbstractUIFactory { } - public static void onPrepareOptionsMenu(Menu menu) { - final MenuItem item = menu.findItem(MENU_LOG_VISIT); - if (item != null) { - item.setEnabled(Settings.isLogin()); - } + public static void onPrepareOptionsMenu(Menu menu, Geocache cache) { + final MenuItem itemLog = menu.findItem(R.id.menu_log_visit); + itemLog.setVisible(cache.supportsLogging() && !Settings.getLogOffline()); + itemLog.setEnabled(Settings.isLogin()); + + final MenuItem itemOffline = menu.findItem(R.id.menu_log_visit_offline); + itemOffline.setVisible(cache.supportsLogging() && Settings.getLogOffline()); + } + + public static void addMenuItems(Activity activity, Menu menu, Geocache cache) { + activity.getMenuInflater().inflate(R.menu.logging_ui, menu); + onPrepareOptionsMenu(menu, cache); } } diff --git a/main/src/cgeo/geocaching/ui/OwnerActionsClickListener.java b/main/src/cgeo/geocaching/ui/OwnerActionsClickListener.java new file mode 100644 index 0000000..e7b04a5 --- /dev/null +++ b/main/src/cgeo/geocaching/ui/OwnerActionsClickListener.java @@ -0,0 +1,28 @@ +package cgeo.geocaching.ui; + +import cgeo.geocaching.Geocache; + +import org.apache.commons.lang3.StringUtils; + +import android.view.View; +import android.widget.TextView; + +/** + * Listener for clicks on owner name + */ +public class OwnerActionsClickListener extends AbstractUserClickListener { + + public OwnerActionsClickListener(Geocache cache) { + super(cache); + } + + @Override + protected String getUserName(View view) { + // Use real owner name vice the one owner chose to display + if (StringUtils.isNotBlank(cache.getOwnerUserId())) { + return cache.getOwnerUserId(); + } + return ((TextView) view).getText().toString(); + } +} + diff --git a/main/src/cgeo/geocaching/ui/UserActionsClickListener.java b/main/src/cgeo/geocaching/ui/UserActionsClickListener.java new file mode 100644 index 0000000..8235446 --- /dev/null +++ b/main/src/cgeo/geocaching/ui/UserActionsClickListener.java @@ -0,0 +1,22 @@ +package cgeo.geocaching.ui; + +import cgeo.geocaching.Geocache; + +import android.view.View; +import android.widget.TextView; + +/** + * Listener for clicks on user name + */ +public class UserActionsClickListener extends AbstractUserClickListener { + + public UserActionsClickListener(Geocache cache) { + super(cache); + } + + @Override + protected CharSequence getUserName(View view) { + return ((TextView) view).getText().toString(); + } +} + diff --git a/main/src/cgeo/geocaching/ui/dialog/CoordinatesInputDialog.java b/main/src/cgeo/geocaching/ui/dialog/CoordinatesInputDialog.java index dada8fd..60116f9 100644 --- a/main/src/cgeo/geocaching/ui/dialog/CoordinatesInputDialog.java +++ b/main/src/cgeo/geocaching/ui/dialog/CoordinatesInputDialog.java @@ -13,13 +13,10 @@ import cgeo.geocaching.geopoint.GeopointFormatter; import org.apache.commons.lang3.StringUtils; -import android.app.Dialog; import android.os.Bundle; import android.text.Editable; import android.text.TextWatcher; import android.view.View; -import android.view.ViewGroup.LayoutParams; -import android.view.Window; import android.widget.AdapterView; import android.widget.AdapterView.OnItemSelectedListener; import android.widget.ArrayAdapter; @@ -28,7 +25,7 @@ import android.widget.EditText; import android.widget.Spinner; import android.widget.TextView; -public class CoordinatesInputDialog extends Dialog { +public class CoordinatesInputDialog extends NoTitleDialog { final private AbstractActivity context; final private IGeoData geo; @@ -47,7 +44,7 @@ public class CoordinatesInputDialog extends Dialog { private coordInputFormatEnum currentFormat = null; public CoordinatesInputDialog(final AbstractActivity context, final Geocache cache, final Geopoint gp, final IGeoData geo) { - super(context, ActivityMixin.getTheme()); + super(context, ActivityMixin.getDialogTheme()); this.context = context; this.geo = geo; this.cache = cache; @@ -65,22 +62,7 @@ public class CoordinatesInputDialog extends Dialog { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - try { - requestWindowFeature(Window.FEATURE_NO_TITLE); - getWindow().setLayout(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); - } catch (Exception e) { - // nothing - } - - setContentView(R.layout.coords); - - findViewById(R.id.actionBarManualbutton).setOnClickListener(new View.OnClickListener() { - - @Override - public void onClick(View view) { - ActivityMixin.goManual(context, "c:geo-geocoordinate-input"); - } - }); + setContentView(R.layout.coordinatesinput_dialog); final Spinner spinner = (Spinner) findViewById(R.id.spinnerCoordinateFormats); final ArrayAdapter<CharSequence> adapter = @@ -346,7 +328,7 @@ public class CoordinatesInputDialog extends Dialog { if (currentFormat == coordInputFormatEnum.Plain) { try { gp = new Geopoint(eLat.getText().toString(), eLon.getText().toString()); - } catch (Geopoint.ParseException e) { + } catch (final Geopoint.ParseException e) { if (signalError) { context.showToast(context.getResources().getString(R.string.err_parse_lat_lon)); } @@ -355,20 +337,20 @@ public class CoordinatesInputDialog extends Dialog { return true; } - String latDir = bLat.getText().toString(); - String lonDir = bLon.getText().toString(); - String latDeg = eLatDeg.getText().toString(); - String lonDeg = eLonDeg.getText().toString(); - String latDegFrac = eLatMin.getText().toString(); - String lonDegFrac = eLonMin.getText().toString(); - String latMin = eLatMin.getText().toString(); - String lonMin = eLonMin.getText().toString(); - String latMinFrac = eLatSec.getText().toString(); - String lonMinFrac = eLonSec.getText().toString(); - String latSec = eLatSec.getText().toString(); - String lonSec = eLonSec.getText().toString(); - String latSecFrac = eLatSub.getText().toString(); - String lonSecFrac = eLonSub.getText().toString(); + final String latDir = bLat.getText().toString(); + final String lonDir = bLon.getText().toString(); + final String latDeg = eLatDeg.getText().toString(); + final String lonDeg = eLonDeg.getText().toString(); + final String latDegFrac = eLatMin.getText().toString(); + final String lonDegFrac = eLonMin.getText().toString(); + final String latMin = eLatMin.getText().toString(); + final String lonMin = eLonMin.getText().toString(); + final String latMinFrac = eLatSec.getText().toString(); + final String lonMinFrac = eLonSec.getText().toString(); + final String latSec = eLatSec.getText().toString(); + final String lonSec = eLonSec.getText().toString(); + final String latSecFrac = eLatSub.getText().toString(); + final String lonSecFrac = eLonSub.getText().toString(); switch (currentFormat) { case Deg: diff --git a/main/src/cgeo/geocaching/ui/dialog/CustomProgressDialog.java b/main/src/cgeo/geocaching/ui/dialog/CustomProgressDialog.java index c2b722c..e80c446 100644 --- a/main/src/cgeo/geocaching/ui/dialog/CustomProgressDialog.java +++ b/main/src/cgeo/geocaching/ui/dialog/CustomProgressDialog.java @@ -19,7 +19,7 @@ import java.lang.reflect.Method; public class CustomProgressDialog extends ProgressDialog { public CustomProgressDialog(Context context) { - super(context, ActivityMixin.getTheme()); + super(context, ActivityMixin.getDialogTheme()); } @Override diff --git a/main/src/cgeo/geocaching/ui/dialog/DateDialog.java b/main/src/cgeo/geocaching/ui/dialog/DateDialog.java index a9c579c..18f8e2e 100644 --- a/main/src/cgeo/geocaching/ui/dialog/DateDialog.java +++ b/main/src/cgeo/geocaching/ui/dialog/DateDialog.java @@ -3,15 +3,12 @@ package cgeo.geocaching.ui.dialog; import cgeo.geocaching.R; import android.app.Activity; -import android.app.Dialog; import android.os.Bundle; -import android.view.ViewGroup.LayoutParams; -import android.view.Window; import android.widget.DatePicker; import java.util.Calendar; -public class DateDialog extends Dialog { +public class DateDialog extends NoTitleDialog { public interface DateDialogParent { abstract public void setDate(final Calendar date); @@ -32,13 +29,6 @@ public class DateDialog extends Dialog { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - try { - requestWindowFeature(Window.FEATURE_NO_TITLE); - getWindow().setLayout(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); - } catch (Exception e) { - // nothing - } - setContentView(R.layout.date); final DatePicker picker = (DatePicker) findViewById(R.id.picker); diff --git a/main/src/cgeo/geocaching/ui/dialog/EditorDialog.java b/main/src/cgeo/geocaching/ui/dialog/EditorDialog.java deleted file mode 100644 index 4db69e5..0000000 --- a/main/src/cgeo/geocaching/ui/dialog/EditorDialog.java +++ /dev/null @@ -1,60 +0,0 @@ -package cgeo.geocaching.ui.dialog; - -import cgeo.geocaching.CacheDetailActivity; -import cgeo.geocaching.R; -import cgeo.geocaching.activity.ActivityMixin; - -import android.app.Dialog; -import android.os.Bundle; -import android.view.View; -import android.view.ViewGroup.LayoutParams; -import android.view.Window; -import android.widget.Button; -import android.widget.EditText; - -public class EditorDialog extends Dialog { - - private CharSequence editorText; - private EditorUpdate editorUpdate; - - public EditorDialog(CacheDetailActivity cacheDetailActivity, CharSequence editable) { - super(cacheDetailActivity, ActivityMixin.getTheme()); - this.editorText = editable; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - requestWindowFeature(Window.FEATURE_NO_TITLE); - setContentView(R.layout.editor); - - final EditText editText = (EditText) findViewById(R.id.editorEditText); - editText.setText(editorText); - - final Button buttonSave = (Button) findViewById(R.id.editorSave); - buttonSave.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - editorUpdate.update(editText.getEditableText()); - EditorDialog.this.hide(); - } - }); - } - - public interface EditorUpdate { - public void update(CharSequence editorText); - } - - public void setOnEditorUpdate(EditorUpdate editorUpdate) { - this.editorUpdate = editorUpdate; - - } - - @Override - public void show() { - super.show(); - getWindow().setLayout(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); - } - -} diff --git a/main/src/cgeo/geocaching/ui/dialog/NoTitleDialog.java b/main/src/cgeo/geocaching/ui/dialog/NoTitleDialog.java new file mode 100644 index 0000000..fc5ebe6 --- /dev/null +++ b/main/src/cgeo/geocaching/ui/dialog/NoTitleDialog.java @@ -0,0 +1,30 @@ +package cgeo.geocaching.ui.dialog; + +import android.app.Dialog; +import android.content.Context; +import android.os.Bundle; +import android.view.ViewGroup.LayoutParams; +import android.view.Window; + +public abstract class NoTitleDialog extends Dialog { + + public NoTitleDialog(Context context) { + super(context); + } + + public NoTitleDialog(Context context, int theme) { + super(context, theme); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + try { + requestWindowFeature(Window.FEATURE_NO_TITLE); + getWindow().setLayout(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); + } catch (final Exception e) { + // nothing + } + } +} diff --git a/main/src/cgeo/geocaching/utils/AngleUtils.java b/main/src/cgeo/geocaching/utils/AngleUtils.java index 6e59a91..fdd9a9d 100644 --- a/main/src/cgeo/geocaching/utils/AngleUtils.java +++ b/main/src/cgeo/geocaching/utils/AngleUtils.java @@ -1,6 +1,6 @@ package cgeo.geocaching.utils; -public class AngleUtils { +public final class AngleUtils { private AngleUtils() { // Do not instantiate @@ -8,7 +8,7 @@ public class AngleUtils { /** * Return the angle to turn of to go from an angle to the other - * + * * @param from * the origin angle in degrees * @param to diff --git a/main/src/cgeo/geocaching/utils/AsyncTaskWithProgress.java b/main/src/cgeo/geocaching/utils/AsyncTaskWithProgress.java new file mode 100644 index 0000000..7526d92 --- /dev/null +++ b/main/src/cgeo/geocaching/utils/AsyncTaskWithProgress.java @@ -0,0 +1,139 @@ +package cgeo.geocaching.utils; + +import cgeo.geocaching.activity.Progress; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.os.AsyncTask; + +/** + * AsyncTask which automatically shows a progress dialog. Use it like the {@code AsyncTask} class, but leave away the + * middle template parameter. Override {@link #doInBackgroundInternal(Object[])} and related methods. + * <p> + * If no style is given, the progress dialog uses "determinate" style with known maximum. The progress maximum is + * automatically derived from the number of {@code Params} given to the task in {@link #execute(Object...)}. + * </p> + * + * @param <Params> + * @param <Result> + */ +public abstract class AsyncTaskWithProgress<Params, Result> extends AsyncTask<Params, Integer, Result> { + + private final Progress progress = new Progress(); + private final Activity activity; + private final String progressTitle; + private final String progressMessage; + private boolean indeterminate = false; + + /** + * Creates an AsyncTask with progress dialog. + * + * @param activity + * @param progressTitle + * @param progressMessage + */ + public AsyncTaskWithProgress(final Activity activity, final String progressTitle, final String progressMessage) { + this(activity, progressTitle, progressMessage, false); + } + + /** + * Creates an AsyncTask with progress dialog. + * + * @param activity + * @param progressTitle + */ + public AsyncTaskWithProgress(final Activity activity, final String progressTitle) { + this(activity, progressTitle, null); + } + + /** + * Creates an AsyncTask with progress dialog. + * + * @param activity + * @param progressTitle + * @param progressMessage + */ + public AsyncTaskWithProgress(final Activity activity, final String progressTitle, final String progressMessage, boolean indeterminate) { + this.activity = activity; + this.progressTitle = progressTitle; + this.progressMessage = progressMessage; + this.indeterminate = indeterminate; + } + + /** + * Creates an AsyncTask with progress dialog. + * + * @param activity + * @param progressTitle + */ + public AsyncTaskWithProgress(final Activity activity, final String progressTitle, boolean indeterminate) { + this(activity, progressTitle, null, indeterminate); + } + + @Override + protected final void onPreExecute() { + if (null != activity) { + if (indeterminate) { + progress.show(activity, progressTitle, progressMessage, true, null); + } + else { + progress.show(activity, progressTitle, progressMessage, ProgressDialog.STYLE_HORIZONTAL, null); + } + } + onPreExecuteInternal(); + } + + /** + * This method should typically be overridden by sub classes instead of {@link #onPreExecute()}. + */ + protected void onPreExecuteInternal() { + // empty by default + } + + @Override + protected final void onPostExecute(Result result) { + onPostExecuteInternal(result); + if (null != activity) { + progress.dismiss(); + } + } + + /** + * This method should typically be overridden by sub classes instead of {@link #onPostExecute(Object)}. + * + * @param result + */ + protected void onPostExecuteInternal(Result result) { + // empty by default + } + + @Override + protected final void onProgressUpdate(Integer... status) { + final int progressValue = status[0]; + if (null != activity && progressValue >= 0) { + progress.setProgress(progressValue); + } + onProgressUpdateInternal(progressValue); + } + + /** + * This method should by overridden by sub classes instead of {@link #onProgressUpdate(Integer...)}. + */ + protected void onProgressUpdateInternal(@SuppressWarnings("unused") int progress) { + // empty by default + } + + protected void setMessage(final String message) { + progress.setMessage(message); + } + + @Override + protected final Result doInBackground(Params... params) { + if (params != null) { + progress.setMaxProgressAndReset(params.length); + } + return doInBackgroundInternal(params); + } + + protected abstract Result doInBackgroundInternal(Params[] params); +} diff --git a/main/src/cgeo/geocaching/utils/ClipboardUtils.java b/main/src/cgeo/geocaching/utils/ClipboardUtils.java index e6779ad..67069b2 100644 --- a/main/src/cgeo/geocaching/utils/ClipboardUtils.java +++ b/main/src/cgeo/geocaching/utils/ClipboardUtils.java @@ -3,7 +3,6 @@ package cgeo.geocaching.utils; import cgeo.geocaching.cgeoapplication; import android.content.Context; -import android.text.ClipboardManager; /** * Clipboard Utilities. Functions to copy data to the Android clipboard. @@ -13,6 +12,10 @@ import android.text.ClipboardManager; @SuppressWarnings("deprecation") public final class ClipboardUtils { + private ClipboardUtils() { + // utility class + } + /** * Places the text passed in onto the clipboard as text * @@ -20,7 +23,8 @@ public final class ClipboardUtils { * The text to place in the clipboard. */ public static void copyToClipboard(final CharSequence text) { - final ClipboardManager clipboard = (ClipboardManager) cgeoapplication.getInstance().getSystemService(Context.CLIPBOARD_SERVICE); + // fully qualified name used here to avoid buggy deprecation warning (of javac) on the import statement + final android.text.ClipboardManager clipboard = (android.text.ClipboardManager) cgeoapplication.getInstance().getSystemService(Context.CLIPBOARD_SERVICE); clipboard.setText(text); } diff --git a/main/src/cgeo/geocaching/utils/CryptUtils.java b/main/src/cgeo/geocaching/utils/CryptUtils.java index df2baa0..18a337d 100644 --- a/main/src/cgeo/geocaching/utils/CryptUtils.java +++ b/main/src/cgeo/geocaching/utils/CryptUtils.java @@ -12,6 +12,10 @@ import javax.crypto.spec.SecretKeySpec; public final class CryptUtils { + private CryptUtils() { + // utility class + } + private static char[] base64map1 = new char[64]; private static byte[] base64map2 = new byte[128]; @@ -37,28 +41,36 @@ public final class CryptUtils { } } + private static class Rot13Encryption { + private boolean plaintext = false; + + char getNextEncryptedCharacter(final char c) { + int result = c; + if (result == '[') { + plaintext = true; + } else if (result == ']') { + plaintext = false; + } else if (!plaintext) { + int capitalized = result & 32; + result &= ~capitalized; + result = ((result >= 'A') && (result <= 'Z') ? ((result - 'A' + 13) % 26 + 'A') : result) + | capitalized; + } + return (char) result; + } + } + public static String rot13(String text) { if (text == null) { return ""; } final StringBuilder result = new StringBuilder(); - // plaintext flag (do not convert) - boolean plaintext = false; + Rot13Encryption rot13 = new Rot13Encryption(); final int length = text.length(); for (int index = 0; index < length; index++) { - int c = text.charAt(index); - if (c == '[') { - plaintext = true; - } else if (c == ']') { - plaintext = false; - } else if (!plaintext) { - int capitalized = c & 32; - c &= ~capitalized; - c = ((c >= 'A') && (c <= 'Z') ? ((c - 'A' + 13) % 26 + 'A') : c) - | capitalized; - } - result.append((char) c); + char c = text.charAt(index); + result.append(rot13.getNextEncryptedCharacter(c)); } return result.toString(); } @@ -71,7 +83,7 @@ public final class CryptUtils { digest.update(text.getBytes(), 0, text.length()); hashed = new BigInteger(1, digest.digest()).toString(16); } catch (Exception e) { - Log.e("cgBase.md5", e); + Log.e("CryptUtils.md5", e); } return hashed; @@ -85,7 +97,7 @@ public final class CryptUtils { digest.update(text.getBytes(), 0, text.length()); hashed = new BigInteger(1, digest.digest()).toString(16); } catch (Exception e) { - Log.e("cgBase.sha1", e); + Log.e("CryptUtils.sha1", e); } return hashed; @@ -100,7 +112,7 @@ public final class CryptUtils { mac.init(secretKeySpec); macBytes = mac.doFinal(text.getBytes()); } catch (Exception e) { - Log.e("cgBase.hashHmac", e); + Log.e("CryptUtils.hashHmac", e); } return macBytes; @@ -111,22 +123,12 @@ public final class CryptUtils { // a SpannableStringBuilder instead of the pure text and we must replace each character inline. // Otherwise we loose all the images, colors and so on... final SpannableStringBuilder buffer = new SpannableStringBuilder(span); - boolean plaintext = false; + Rot13Encryption rot13 = new Rot13Encryption(); final int length = span.length(); for (int index = 0; index < length; index++) { - int c = span.charAt(index); - if (c == '[') { - plaintext = true; - } else if (c == ']') { - plaintext = false; - } else if (!plaintext) { - int capitalized = c & 32; - c &= ~capitalized; - c = ((c >= 'A') && (c <= 'Z') ? ((c - 'A' + 13) % 26 + 'A') : c) - | capitalized; - } - buffer.replace(index, index + 1, String.valueOf((char) c)); + char c = span.charAt(index); + buffer.replace(index, index + 1, String.valueOf(rot13.getNextEncryptedCharacter(c))); } return buffer; } diff --git a/main/src/cgeo/geocaching/utils/DateUtils.java b/main/src/cgeo/geocaching/utils/DateUtils.java index fd5ef4a..b148979 100644 --- a/main/src/cgeo/geocaching/utils/DateUtils.java +++ b/main/src/cgeo/geocaching/utils/DateUtils.java @@ -2,7 +2,12 @@ package cgeo.geocaching.utils; import java.util.Calendar; -public class DateUtils { +public final class DateUtils { + + private DateUtils() { + // utility class + } + public static int daysSince(long date) { final Calendar logDate = Calendar.getInstance(); logDate.setTimeInMillis(date); diff --git a/main/src/cgeo/geocaching/utils/EditUtils.java b/main/src/cgeo/geocaching/utils/EditUtils.java index f2f89e7..d975df9 100644 --- a/main/src/cgeo/geocaching/utils/EditUtils.java +++ b/main/src/cgeo/geocaching/utils/EditUtils.java @@ -8,6 +8,10 @@ import android.widget.TextView; public final class EditUtils { + private EditUtils() { + // utility class + } + public static void setActionListener(final EditText editText, final Runnable runnable) { editText.setOnEditorActionListener(new TextView.OnEditorActionListener() { diff --git a/main/src/cgeo/geocaching/utils/FileUtils.java b/main/src/cgeo/geocaching/utils/FileUtils.java index 6fefc02..5ab8fcc 100644 --- a/main/src/cgeo/geocaching/utils/FileUtils.java +++ b/main/src/cgeo/geocaching/utils/FileUtils.java @@ -9,12 +9,16 @@ import java.io.File; import java.util.List; /** - * Utiliy class for files + * Utility class for files * * @author rsudev * */ -public class FileUtils { +public final class FileUtils { + + private FileUtils() { + // utility class + } public static void listDir(List<File> result, File directory, FileSelector chooser, Handler feedBackHandler) { diff --git a/main/src/cgeo/geocaching/utils/GeoDirHandler.java b/main/src/cgeo/geocaching/utils/GeoDirHandler.java index 21b2562..78455c4 100644 --- a/main/src/cgeo/geocaching/utils/GeoDirHandler.java +++ b/main/src/cgeo/geocaching/utils/GeoDirHandler.java @@ -9,10 +9,19 @@ import android.os.Message; /** * GeoData and Direction handler. Manipulating geodata and direction information - * through a GeoDirHandler ensures that all listeners are registered from a - * {@link android.os.Looper} thread. + * through a GeoDirHandler ensures that all listeners are registered from a {@link android.os.Looper} thread. + * <p> + * To use this class, override at least one of {@link #updateDirection(float)} or {@link #updateGeoData(IGeoData)}. You + * need to start the handler using one of + * <ul> + * <li>{@link #startDir()}</li> + * <li>{@link #startGeo()}</li> + * <li>{@link #startGeoAndDir()}</li> + * </ul> + * A good place might be the {@code onResume} method of the Activity. Stop the Handler accordingly in {@code onPause}. + * </p> */ -public class GeoDirHandler extends Handler implements IObserver<Object> { +public abstract class GeoDirHandler extends Handler implements IObserver<Object> { private static final int OBSERVABLE = 1 << 1; private static final int START_GEO = 1 << 2; @@ -57,7 +66,8 @@ public class GeoDirHandler extends Handler implements IObserver<Object> { /** * Update method called when new IGeoData is available. * - * @param data the new data + * @param data + * the new data */ protected void updateGeoData(final IGeoData data) { // Override this in children @@ -66,7 +76,8 @@ public class GeoDirHandler extends Handler implements IObserver<Object> { /** * Update method called when new direction data is available. * - * @param direction the new direction + * @param direction + * the new direction */ protected void updateDirection(final float direction) { // Override this in children @@ -118,4 +129,3 @@ public class GeoDirHandler extends Handler implements IObserver<Object> { sendEmptyMessage(STOP_GEO | STOP_DIR); } } - diff --git a/main/src/cgeo/geocaching/utils/HtmlUtils.java b/main/src/cgeo/geocaching/utils/HtmlUtils.java index 8d4eed1..5717a37 100644 --- a/main/src/cgeo/geocaching/utils/HtmlUtils.java +++ b/main/src/cgeo/geocaching/utils/HtmlUtils.java @@ -10,7 +10,11 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; -public class HtmlUtils { +public final class HtmlUtils { + + private HtmlUtils() { + // utility class + } /** * Extract the text from a HTML based string. This is similar to what HTML.fromHtml(...) does, but this method also @@ -20,6 +24,9 @@ public class HtmlUtils { * @return */ public static String extractText(CharSequence html) { + if (StringUtils.isBlank(html)) { + return StringUtils.EMPTY; + } String result = html.toString(); // recognize images in textview HTML contents diff --git a/main/src/cgeo/geocaching/utils/ImageHelper.java b/main/src/cgeo/geocaching/utils/ImageUtils.java index 98cad64..ea7d3ff 100644 --- a/main/src/cgeo/geocaching/utils/ImageHelper.java +++ b/main/src/cgeo/geocaching/utils/ImageUtils.java @@ -8,10 +8,13 @@ import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; -public class ImageHelper { +import java.io.BufferedOutputStream; +import java.io.FileOutputStream; - // Do not let this class be instantiated, this is a utility class. - private ImageHelper() { +public final class ImageUtils { + + private ImageUtils() { + // Do not let this class be instantiated, this is a utility class. } /** @@ -22,11 +25,21 @@ public class ImageHelper { * @return BitmapDrawable The scaled image */ public static BitmapDrawable scaleBitmapToFitDisplay(final Bitmap image) { - final cgeoapplication app = cgeoapplication.getInstance(); Point displaySize = Compatibility.getDisplaySize(); final int maxWidth = displaySize.x - 25; final int maxHeight = displaySize.y - 25; + return scaleBitmapTo(image, maxWidth, maxHeight); + } + /** + * Scales a bitmap to the given bounds if it is larger, otherwise returns the original bitmap. + * + * @param image + * The bitmap to scale + * @return BitmapDrawable The scaled image + */ + public static BitmapDrawable scaleBitmapTo(final Bitmap image, final int maxWidth, final int maxHeight) { + final cgeoapplication app = cgeoapplication.getInstance(); Bitmap result = image; int width = image.getWidth(); int height = image.getHeight(); @@ -43,4 +56,27 @@ public class ImageHelper { return resultDrawable; } + /** + * Store a bitmap to file. + * + * @param bitmap + * The bitmap to store + * @param format + * The image format + * @param quality + * The image quality + * @param pathOfOutputImage + * Path to store to + */ + public static void storeBitmap(final Bitmap bitmap, final Bitmap.CompressFormat format, final int quality, final String pathOfOutputImage) { + try { + FileOutputStream out = new FileOutputStream(pathOfOutputImage); + BufferedOutputStream bos = new BufferedOutputStream(out); + bitmap.compress(format, quality, bos); + bos.flush(); + bos.close(); + } catch (Exception e) { + Log.e("ImageHelper.storeBitmap", e); + } + } } diff --git a/main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java b/main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java index 698d38a..708dff0 100644 --- a/main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java +++ b/main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java @@ -10,13 +10,13 @@ import java.util.List; /** * Synchronized set wrapper for the LeastRecentlyUsedMap. * - * This code is heavily based on the HashSet code that represent Map as a Set. + * This code is heavily based on the HashSet code that represents Map as a Set. * Unfortunately HashSet does not allow to use a custom Map as its Storage. * Therefore overriding removeEldestEntry() is impossible for a normal LinkedHashSet. * * Synchronization is added to guard against concurrent modification. Iterator * access has to be guarded externally or the synchronized getAsList method can be used - * to get a clone for iteration + * to get a clone for iteration. */ public class LeastRecentlyUsedSet<E> extends AbstractSet<E> implements Cloneable, java.io.Serializable { @@ -28,7 +28,7 @@ public class LeastRecentlyUsedSet<E> extends AbstractSet<E> public LeastRecentlyUsedSet(int maxEntries, int initialCapacity, float loadFactor) { // because we don't use any Map.get() methods from the Set, BOUNDED and LRU_CACHE have the exact same Behaviour - // So we useLRU_CACHE mode because it should perform a bit better (as it doesn't re-add explicitly) + // So we use LRU_CACHE mode because it should perform a bit better (as it doesn't re-add explicitly) map = new LeastRecentlyUsedMap.LruCache<E, Object>(maxEntries, initialCapacity, loadFactor); } diff --git a/main/src/cgeo/geocaching/utils/Log.java b/main/src/cgeo/geocaching/utils/Log.java index 6d57b75..f912ddd 100644 --- a/main/src/cgeo/geocaching/utils/Log.java +++ b/main/src/cgeo/geocaching/utils/Log.java @@ -2,6 +2,7 @@ package cgeo.geocaching.utils; import android.os.Environment; +import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; @@ -100,12 +101,14 @@ final public class Log { first = false; file.delete(); } + Writer writer = null; try { - final Writer writer = new FileWriter(file, true); + writer = new BufferedWriter(new FileWriter(file, true)); writer.write(msg); - writer.close(); } catch (final IOException e) { Log.e("logToFile: cannot write to " + file, e); + } finally { + IOUtils.closeQuietly(writer); } } } diff --git a/main/src/cgeo/geocaching/utils/LogTemplateProvider.java b/main/src/cgeo/geocaching/utils/LogTemplateProvider.java index 0a8e547..2576e64 100644 --- a/main/src/cgeo/geocaching/utils/LogTemplateProvider.java +++ b/main/src/cgeo/geocaching/utils/LogTemplateProvider.java @@ -188,7 +188,7 @@ public class LogTemplateProvider { } try { - return Integer.parseInt(BaseUtils.getMatch(page, GCConstants.PATTERN_CACHES_FOUND, true, "-1").replaceAll("[,.]", "")); + return Integer.parseInt(TextUtils.getMatch(page, GCConstants.PATTERN_CACHES_FOUND, true, "-1").replaceAll("[,.]", "")); } catch (NumberFormatException e) { Log.e("parseFindCount", e); return -1; diff --git a/main/src/cgeo/geocaching/utils/PeriodicHandler.java b/main/src/cgeo/geocaching/utils/PeriodicHandler.java index 2759580..288bbb0 100644 --- a/main/src/cgeo/geocaching/utils/PeriodicHandler.java +++ b/main/src/cgeo/geocaching/utils/PeriodicHandler.java @@ -3,16 +3,26 @@ package cgeo.geocaching.utils; import android.os.Handler; import android.os.Message; +import java.lang.ref.WeakReference; + /** * A PeriodicHandler class helps with the implementation of a periodic * action embedded in a thread with a looper such as the UI thread. - * The act() method will be called periodically. The clock may drift - * as the implementation does not target real-time actions. + * The onPeriodic() method of the listener will be called periodically. + * The clock may drift as the implementation does not target real-time + * actions. * * The handler will be interrupted if the device goes to sleep. * + * The handler only keeps a weak reference to the listener. If the listener + * is garbage-collected without having stopped the timer, the handler will + * stop itself. */ -abstract public class PeriodicHandler extends Handler { +final public class PeriodicHandler extends Handler { + + public static interface PeriodicHandlerListener { + public void onPeriodic(); + } final static private int START = 0; final static private int STOP = 1; @@ -20,21 +30,19 @@ abstract public class PeriodicHandler extends Handler { final private long period; + final private WeakReference<PeriodicHandlerListener> listenerRef; + /** * Create a new PeriodicHandler object. * * @param period * The period in milliseconds. */ - protected PeriodicHandler(final long period) { + public PeriodicHandler(final long period, final PeriodicHandlerListener listener) { this.period = period; + listenerRef = new WeakReference<PeriodicHandlerListener>(listener); } - /** - * Subclasses of PeriodicHandler must implement this method. - */ - abstract public void act(); - @Override public void handleMessage(final Message msg) { switch (msg.what) { @@ -46,8 +54,11 @@ abstract public class PeriodicHandler extends Handler { removeMessages(ACT); break; case ACT: - sendEmptyMessageDelayed(ACT, period); - act(); + final PeriodicHandlerListener listener = listenerRef.get(); + if (listener != null) { + sendEmptyMessageDelayed(ACT, period); + listener.onPeriodic(); + } break; default: throw new UnsupportedOperationException(); diff --git a/main/src/cgeo/geocaching/utils/ProcessUtils.java b/main/src/cgeo/geocaching/utils/ProcessUtils.java index 53991fb..1c05e84 100644 --- a/main/src/cgeo/geocaching/utils/ProcessUtils.java +++ b/main/src/cgeo/geocaching/utils/ProcessUtils.java @@ -3,14 +3,37 @@ package cgeo.geocaching.utils; import cgeo.geocaching.cgeoapplication; import android.content.Intent; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -public class ProcessUtils { +import java.util.List; + +public final class ProcessUtils { + + private ProcessUtils() { + // utility class + } public static boolean isInstalled(final String packageName) { - return getLaunchIntent(packageName) != null; + return (getLaunchIntent(packageName) != null) || hasPackageInstalled(packageName); + } + + /** + * This will find installed applications even without launch intent (e.g. the streetview plugin). + */ + private static boolean hasPackageInstalled(final String packageName) { + final List<PackageInfo> packs = cgeoapplication.getInstance().getPackageManager().getInstalledPackages(0); + for (final PackageInfo packageInfo : packs) { + if (packageName.equals(packageInfo.packageName)) { + return true; + } + } + return false; } + /** + * This will find applications, which can be launched. + */ public static Intent getLaunchIntent(final String packageName) { if (packageName == null) { return null; @@ -20,7 +43,7 @@ public class ProcessUtils { // This can throw an exception where the exception type is only defined on API Level > 3 // therefore surround with try-catch return packageManager.getLaunchIntentForPackage(packageName); - } catch (Exception e) { + } catch (final Exception e) { return null; } } diff --git a/main/src/cgeo/geocaching/utils/BaseUtils.java b/main/src/cgeo/geocaching/utils/TextUtils.java index 82e48cb..68ac595 100644 --- a/main/src/cgeo/geocaching/utils/BaseUtils.java +++ b/main/src/cgeo/geocaching/utils/TextUtils.java @@ -9,10 +9,14 @@ import java.util.regex.Pattern; /** * Misc. utils. All methods don't use Android specific stuff to use these methods in plain JUnit tests. */ -public final class BaseUtils { +public final class TextUtils { private static final Pattern PATTERN_REMOVE_NONPRINTABLE = Pattern.compile("\\p{Cntrl}"); + private TextUtils() { + // utility class + } + /** * Searches for the pattern p in the data. If the pattern is not found defaultValue is returned * @@ -69,7 +73,7 @@ public final class BaseUtils { * @return defaultValue or the first group if the pattern matches (trimmed if wanted) */ public static String getMatch(final String data, final Pattern p, final boolean trim, final String defaultValue) { - return BaseUtils.getMatch(data, p, trim, 1, defaultValue, false); + return TextUtils.getMatch(data, p, trim, 1, defaultValue, false); } /** @@ -84,7 +88,7 @@ public final class BaseUtils { * @return defaultValue or the first group if the pattern matches (trimmed) */ public static String getMatch(final String data, final Pattern p, final String defaultValue) { - return BaseUtils.getMatch(data, p, true, 1, defaultValue, false); + return TextUtils.getMatch(data, p, true, 1, defaultValue, false); } /** @@ -137,10 +141,11 @@ public final class BaseUtils { } /** - * Quick and naive check for possible html-content of a string. + * Quick and naive check for possible rich HTML content in a string. * - * @param str - * @return True, if <code>str</code> could contain html + * @param str A string containing HTML code. + * @return <tt>true</tt> if <tt>str</tt> contains HTML code that needs to go through a HTML renderer before + * being displayed, <tt>false</tt> if it can be displayed as-is without any loss */ public static boolean containsHtml(final String str) { return str.indexOf('<') != -1 || str.indexOf('&') != -1; diff --git a/main/src/cgeo/geocaching/utils/TranslationUtils.java b/main/src/cgeo/geocaching/utils/TranslationUtils.java index 4d318f0..05045ee 100644 --- a/main/src/cgeo/geocaching/utils/TranslationUtils.java +++ b/main/src/cgeo/geocaching/utils/TranslationUtils.java @@ -19,6 +19,10 @@ public final class TranslationUtils { public static final int translationTextLengthToWarn = 500; private static final String TRANSLATION_APP = "com.google.android.apps.translate"; + private TranslationUtils() { + // utility class + } + /** * Build a URI for Google Translate * diff --git a/main/src/cgeo/geocaching/utils/XmlUtils.java b/main/src/cgeo/geocaching/utils/XmlUtils.java index 4e08f42..2d85950 100644 --- a/main/src/cgeo/geocaching/utils/XmlUtils.java +++ b/main/src/cgeo/geocaching/utils/XmlUtils.java @@ -4,7 +4,7 @@ import org.xmlpull.v1.XmlSerializer; import java.io.IOException; -public class XmlUtils { +public final class XmlUtils { private XmlUtils() { // Do not instantiate diff --git a/main/src/cgeo/org/kxml2/io/KXmlSerializer.java b/main/src/cgeo/org/kxml2/io/KXmlSerializer.java deleted file mode 100644 index 027ff53..0000000 --- a/main/src/cgeo/org/kxml2/io/KXmlSerializer.java +++ /dev/null @@ -1,605 +0,0 @@ -/* - * Copyright (c) 2002,2003, Stefan Haustein, Oberhausen, Rhld., Germany - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -package cgeo.org.kxml2.io; - -import org.apache.commons.lang3.StringUtils; -import org.xmlpull.v1.XmlSerializer; - -import java.io.BufferedWriter; -import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.util.Locale; - -public class KXmlSerializer implements XmlSerializer { - - // static final String UNDEFINED = ":"; - - // BEGIN android-added - /** size (in characters) for the write buffer */ - private static final int WRITE_BUFFER_SIZE = 500; - // END android-added - - // BEGIN android-changed - // (Guarantee that the writer is always buffered.) - private BufferedWriter writer; - // END android-changed - - private boolean pending; - private int auto; - private int depth; - - private String[] elementStack = new String[12]; - //nsp/prefix/name - private int[] nspCounts = new int[4]; - private String[] nspStack = new String[8]; - //prefix/nsp; both empty are "" - private boolean[] indent = new boolean[4]; - private boolean unicode; - private String encoding; - - private final void check(boolean close) throws IOException { - if (!pending) { - return; - } - - depth++; - pending = false; - - if (indent.length <= depth) { - boolean[] hlp = new boolean[depth + 4]; - System.arraycopy(indent, 0, hlp, 0, depth); - indent = hlp; - } - indent[depth] = indent[depth - 1]; - - for (int i = nspCounts[depth - 1]; i < nspCounts[depth]; i++) { - writer.write(' '); - writer.write("xmlns"); - if (!StringUtils.isEmpty(nspStack[i * 2])) { - writer.write(':'); - writer.write(nspStack[i * 2]); - } - else if (StringUtils.isEmpty(getNamespace()) && !StringUtils.isEmpty(nspStack[i * 2 + 1])) { - throw new IllegalStateException("Cannot set default namespace for elements in no namespace"); - } - writer.write("=\""); - writeEscaped(nspStack[i * 2 + 1], '"'); - writer.write('"'); - } - - if (nspCounts.length <= depth + 1) { - int[] hlp = new int[depth + 8]; - System.arraycopy(nspCounts, 0, hlp, 0, depth + 1); - nspCounts = hlp; - } - - nspCounts[depth + 1] = nspCounts[depth]; - // nspCounts[depth + 2] = nspCounts[depth]; - - writer.write(close ? " />" : ">"); - } - - private final void writeEscaped(String s, int quot) throws IOException { - for (int i = 0; i < s.length(); i++) { - char c = s.charAt(i); - switch (c) { - case '\n': - case '\r': - case '\t': - if (quot == -1) { - writer.write(c); - } else { - writer.write("&#"+((int) c)+';'); - } - break; - case '&': - writer.write("&"); - break; - case '>': - writer.write(">"); - break; - case '<': - writer.write("<"); - break; - default: - if (c == quot) { - writer.write(c == '"' ? """ : "'"); - break; - } - // BEGIN android-changed: refuse to output invalid characters - // See http://www.w3.org/TR/REC-xml/#charsets for definition. - // Corrected for c:geo to handle utf-16 codepoint surrogates correctly - // See http://en.wikipedia.org/wiki/UTF-16#Code_points_U.2B10000_to_U.2B10FFFF - // Note: tab, newline, and carriage return have already been - // handled above. - // Check for lead surrogate - if (c >= 0xd800 && c <= 0xdbff) { - - if (i + 1 < s.length()) { - writer.write(s.substring(i, i + 1)); - i++; - break; - } - // if the lead surrogate is at the string end, it's not valid utf-16 - reportInvalidCharacter(c); - } - boolean valid = (c >= 0x20 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xfffd); - if (!valid) { - reportInvalidCharacter(c); - } - if (unicode || c < 127) { - writer.write(c); - } else { - writer.write("&#" + ((int) c) + ";"); - } - // END android-changed - } - } - } - - // BEGIN android-added - private static void reportInvalidCharacter(char ch) { - throw new IllegalArgumentException("Illegal character (" + Integer.toHexString((int) ch) + ")"); - } - // END android-added - - /* - * private final void writeIndent() throws IOException { - * writer.write("\r\n"); - * for (int i = 0; i < depth; i++) - * writer.write(' '); - * } - */ - - public void docdecl(String dd) throws IOException { - writer.write("<!DOCTYPE"); - writer.write(dd); - writer.write(">"); - } - - public void endDocument() throws IOException { - while (depth > 0) { - endTag(elementStack[depth * 3 - 3], elementStack[depth * 3 - 1]); - } - flush(); - } - - public void entityRef(String name) throws IOException { - check(false); - writer.write('&'); - writer.write(name); - writer.write(';'); - } - - public boolean getFeature(String name) { - //return false; - return ("http://xmlpull.org/v1/doc/features.html#indent-output" - .equals( - name)) - ? indent[depth] - : false; - } - - public String getPrefix(String namespace, boolean create) { - try { - return getPrefix(namespace, false, create); - } catch (IOException e) { - throw new RuntimeException(e.toString()); - } - } - - private final String getPrefix( - String namespace, - boolean includeDefault, - boolean create) - throws IOException { - - for (int i = nspCounts[depth + 1] * 2 - 2; i >= 0; i -= 2) { - if (nspStack[i + 1].equals(namespace) - && (includeDefault || !StringUtils.isEmpty(nspStack[i]))) { - String cand = nspStack[i]; - for (int j = i + 2; j < nspCounts[depth + 1] * 2; j++) { - if (nspStack[j].equals(cand)) { - cand = null; - break; - } - } - if (cand != null) { - return cand; - } - } - } - - if (!create) { - return null; - } - - String prefix; - - if (StringUtils.isEmpty(namespace)) { - prefix = ""; - } else { - do { - prefix = "n" + (auto++); - for (int i = nspCounts[depth + 1] * 2 - 2; i >= 0; i -= 2) { - if (prefix.equals(nspStack[i])) { - prefix = null; - break; - } - } - } while (prefix == null); - } - - boolean p = pending; - pending = false; - setPrefix(prefix, namespace); - pending = p; - return prefix; - } - - public Object getProperty(String name) { - throw new RuntimeException("Unsupported property"); - } - - public void ignorableWhitespace(String s) - throws IOException { - text(s); - } - - public void setFeature(String name, boolean value) { - if ("http://xmlpull.org/v1/doc/features.html#indent-output" - .equals(name)) { - indent[depth] = value; - } else { - throw new RuntimeException("Unsupported Feature"); - } - } - - public void setProperty(String name, Object value) { - throw new RuntimeException( - "Unsupported Property:" + value); - } - - public void setPrefix(String prefix, String namespace) - throws IOException { - - check(false); - if (prefix == null) { - prefix = ""; - } - if (namespace == null) { - namespace = ""; - } - - String defined = getPrefix(namespace, true, false); - - // boil out if already defined - - if (prefix.equals(defined)) { - return; - } - - int pos = (nspCounts[depth + 1]++) << 1; - - if (nspStack.length < pos + 1) { - String[] hlp = new String[nspStack.length + 16]; - System.arraycopy(nspStack, 0, hlp, 0, pos); - nspStack = hlp; - } - - nspStack[pos++] = prefix; - nspStack[pos] = namespace; - } - - public void setOutput(Writer writer) { - // BEGIN android-changed - // Guarantee that the writer is always buffered. - if (writer instanceof BufferedWriter) { - this.writer = (BufferedWriter) writer; - } else { - this.writer = new BufferedWriter(writer, WRITE_BUFFER_SIZE); - } - // END android-changed - - // elementStack = new String[12]; //nsp/prefix/name - //nspCounts = new int[4]; - //nspStack = new String[8]; //prefix/nsp - //indent = new boolean[4]; - - nspCounts[0] = 2; - nspCounts[1] = 2; - nspStack[0] = ""; - nspStack[1] = ""; - nspStack[2] = "xml"; - nspStack[3] = "http://www.w3.org/XML/1998/namespace"; - pending = false; - auto = 0; - depth = 0; - - unicode = false; - } - - public void setOutput(OutputStream os, String encoding) - throws IOException { - if (os == null) { - throw new IllegalArgumentException("os == null"); - } - setOutput(encoding == null - ? new OutputStreamWriter(os) - : new OutputStreamWriter(os, encoding)); - this.encoding = encoding; - if (encoding != null && encoding.toLowerCase(Locale.US).startsWith("utf")) { - unicode = true; - } - } - - public void startDocument(String encoding, Boolean standalone) throws IOException { - writer.write("<?xml version='1.0' "); - - if (encoding != null) { - this.encoding = encoding; - if (encoding.toLowerCase(Locale.US).startsWith("utf")) { - unicode = true; - } - } - - if (this.encoding != null) { - writer.write("encoding='"); - writer.write(this.encoding); - writer.write("' "); - } - - if (standalone != null) { - writer.write("standalone='"); - writer.write( - standalone.booleanValue() ? "yes" : "no"); - writer.write("' "); - } - writer.write("?>"); - } - - public XmlSerializer startTag(String namespace, String name) - throws IOException { - check(false); - - // if (namespace == null) - // namespace = ""; - - if (indent[depth]) { - writer.write("\r\n"); - for (int i = 0; i < depth; i++) { - writer.write(" "); - } - } - - int esp = depth * 3; - - if (elementStack.length < esp + 3) { - String[] hlp = new String[elementStack.length + 12]; - System.arraycopy(elementStack, 0, hlp, 0, esp); - elementStack = hlp; - } - - String prefix = - namespace == null - ? "" - : getPrefix(namespace, true, true); - - if (namespace != null && StringUtils.isEmpty(namespace)) { - for (int i = nspCounts[depth]; i < nspCounts[depth + 1]; i++) { - if (StringUtils.isEmpty(nspStack[i * 2]) && !StringUtils.isEmpty(nspStack[i * 2 + 1])) { - throw new IllegalStateException("Cannot set default namespace for elements in no namespace"); - } - } - } - - elementStack[esp++] = namespace; - elementStack[esp++] = prefix; - elementStack[esp] = name; - - writer.write('<'); - if (!StringUtils.isEmpty(prefix)) { - writer.write(prefix); - writer.write(':'); - } - - writer.write(name); - - pending = true; - - return this; - } - - public XmlSerializer attribute( - String namespace, - String name, - String value) - throws IOException { - if (!pending) { - throw new IllegalStateException("illegal position for attribute"); - } - - // int cnt = nspCounts[depth]; - - if (namespace == null) { - namespace = ""; - } - - // depth--; - // pending = false; - - String prefix = - StringUtils.isEmpty(namespace) - ? "" - : getPrefix(namespace, false, true); - - // pending = true; - // depth++; - - /* - * if (cnt != nspCounts[depth]) { - * writer.write(' '); - * writer.write("xmlns"); - * if (nspStack[cnt * 2] != null) { - * writer.write(':'); - * writer.write(nspStack[cnt * 2]); - * } - * writer.write("=\""); - * writeEscaped(nspStack[cnt * 2 + 1], '"'); - * writer.write('"'); - * } - */ - - writer.write(' '); - if (!StringUtils.isEmpty(prefix)) { - writer.write(prefix); - writer.write(':'); - } - writer.write(name); - writer.write('='); - char q = value.indexOf('"') == -1 ? '"' : '\''; - writer.write(q); - writeEscaped(value, q); - writer.write(q); - - return this; - } - - public void flush() throws IOException { - check(false); - writer.flush(); - } - - /* - * public void close() throws IOException { - * check(); - * writer.close(); - * } - */ - public XmlSerializer endTag(String namespace, String name) - throws IOException { - - if (!pending) - { - depth--; - // if (namespace == null) - // namespace = ""; - } - - if ((namespace == null - && elementStack[depth * 3] != null) - || (namespace != null - && !namespace.equals(elementStack[depth * 3])) - || !elementStack[depth * 3 + 2].equals(name)) { - throw new IllegalArgumentException("</{"+namespace+"}"+name+"> does not match start"); - } - - if (pending) { - check(true); - depth--; - } - else { - if (indent[depth + 1]) { - writer.write("\r\n"); - for (int i = 0; i < depth; i++) { - writer.write(" "); - } - } - - writer.write("</"); - String prefix = elementStack[depth * 3 + 1]; - if (!StringUtils.isEmpty(prefix)) { - writer.write(prefix); - writer.write(':'); - } - writer.write(name); - writer.write('>'); - } - - nspCounts[depth + 1] = nspCounts[depth]; - return this; - } - - public String getNamespace() { - return getDepth() == 0 ? null : elementStack[getDepth() * 3 - 3]; - } - - public String getName() { - return getDepth() == 0 ? null : elementStack[getDepth() * 3 - 1]; - } - - public int getDepth() { - return pending ? depth + 1 : depth; - } - - public XmlSerializer text(String text) throws IOException { - check(false); - indent[depth] = false; - writeEscaped(text, -1); - return this; - } - - public XmlSerializer text(char[] text, int start, int len) - throws IOException { - text(new String(text, start, len)); - return this; - } - - public void cdsect(String data) throws IOException { - check(false); - // BEGIN android-changed: ]]> is not allowed within a CDATA, - // so break and start a new one when necessary. - data = data.replace("]]>", "]]]]><![CDATA[>"); - char[] chars = data.toCharArray(); - // We also aren't allowed any invalid characters. - for (char ch : chars) { - boolean valid = (ch >= 0x20 && ch <= 0xd7ff) || - (ch == '\t' || ch == '\n' || ch == '\r') || - (ch >= 0xe000 && ch <= 0xfffd); - if (!valid) { - reportInvalidCharacter(ch); - } - } - writer.write("<![CDATA["); - writer.write(chars, 0, chars.length); - writer.write("]]>"); - // END android-changed - } - - public void comment(String comment) throws IOException { - check(false); - writer.write("<!--"); - writer.write(comment); - writer.write("-->"); - } - - public void processingInstruction(String pi) - throws IOException { - check(false); - writer.write("<?"); - writer.write(pi); - writer.write("?>"); - } -}
\ No newline at end of file diff --git a/main/src/com/viewpagerindicator/PageIndicator.java b/main/src/com/viewpagerindicator/PageIndicator.java deleted file mode 100644 index 26414d8..0000000 --- a/main/src/com/viewpagerindicator/PageIndicator.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2011 Patrik Akerfeldt - * Copyright (C) 2011 Jake Wharton - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.viewpagerindicator; - -import android.support.v4.view.ViewPager; - -/** - * A PageIndicator is responsible to show an visual indicator on the total views - * number and the current visible view. - */ -public interface PageIndicator extends ViewPager.OnPageChangeListener { - /** - * Bind the indicator to a ViewPager. - * - * @param view - */ - public void setViewPager(ViewPager view); - - /** - * Bind the indicator to a ViewPager. - * - * @param view - * @param initialPosition - */ - public void setViewPager(ViewPager view, int initialPosition); - - /** - * <p>Set the current page of both the ViewPager and indicator.</p> - * - * <p>This <strong>must</strong> be used if you need to set the page before - * the views are drawn on screen (e.g., default start page).</p> - * - * @param item - */ - public void setCurrentItem(int item); - - /** - * Set a page change listener which will receive forwarded events. - * - * @param listener - */ - public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener); - - /** - * Notify the indicator that the fragment list has changed. - */ - public void notifyDataSetChanged(); -} diff --git a/main/src/com/viewpagerindicator/TitlePageIndicator.java b/main/src/com/viewpagerindicator/TitlePageIndicator.java deleted file mode 100644 index 94ac962..0000000 --- a/main/src/com/viewpagerindicator/TitlePageIndicator.java +++ /dev/null @@ -1,771 +0,0 @@ -/* - * Copyright (C) 2011 Patrik Akerfeldt - * Copyright (C) 2011 Francisco Figueiredo Jr. - * Copyright (C) 2011 Jake Wharton - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.viewpagerindicator; - -import cgeo.geocaching.R; - -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.RectF; -import android.os.Parcel; -import android.os.Parcelable; -import android.support.v4.view.MotionEventCompat; -import android.support.v4.view.PagerAdapter; -import android.support.v4.view.ViewConfigurationCompat; -import android.support.v4.view.ViewPager; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewConfiguration; - -import java.util.ArrayList; - -/** - * A TitlePageIndicator is a PageIndicator which displays the title of left view - * (if exist), the title of the current select view (centered) and the title of - * the right view (if exist). When the user scrolls the ViewPager then titles are - * also scrolled. - */ -public class TitlePageIndicator extends View implements PageIndicator { - /** - * Percentage indicating what percentage of the screen width away from - * center should the underline be fully faded. A value of 0.25 means that - * halfway between the center of the screen and an edge. - */ - private static final float SELECTION_FADE_PERCENTAGE = 0.25f; - - /** - * Percentage indicating what percentage of the screen width away from - * center should the selected text bold turn off. A value of 0.05 means - * that 10% between the center and an edge. - */ - private static final float BOLD_FADE_PERCENTAGE = 0.05f; - - public enum IndicatorStyle { - None(0), Triangle(1), Underline(2); - - public final int value; - - IndicatorStyle(int value) { - this.value = value; - } - - public static IndicatorStyle fromValue(int value) { - for (IndicatorStyle style : IndicatorStyle.values()) { - if (style.value == value) { - return style; - } - } - return null; - } - } - - private ViewPager mViewPager; - private ViewPager.OnPageChangeListener mListener; - private TitleProvider mTitleProvider; - private int mCurrentPage; - private int mCurrentOffset; - private int mScrollState; - private final Paint mPaintText; - private boolean mBoldText; - private int mColorText; - private int mColorSelected; - private Path mPath = new Path(); - private final Paint mPaintFooterLine; - private IndicatorStyle mFooterIndicatorStyle; - private final Paint mPaintFooterIndicator; - private float mFooterIndicatorHeight; - private float mFooterIndicatorUnderlinePadding; - private float mFooterPadding; - private float mTitlePadding; - private float mTopPadding; - /** Left and right side padding for not active view titles. */ - private float mClipPadding; - private float mFooterLineHeight; - - private static final int INVALID_POINTER = -1; - - private int mTouchSlop; - private float mLastMotionX = -1; - private int mActivePointerId = INVALID_POINTER; - private boolean mIsDragging; - - - public TitlePageIndicator(Context context) { - this(context, null); - } - - public TitlePageIndicator(Context context, AttributeSet attrs) { - this(context, attrs, R.attr.vpiTitlePageIndicatorStyle); - } - - public TitlePageIndicator(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - //Load defaults from resources - final Resources res = getResources(); - final int defaultFooterColor = res.getColor(R.color.default_title_indicator_footer_color); - final float defaultFooterLineHeight = res.getDimension(R.dimen.default_title_indicator_footer_line_height); - final int defaultFooterIndicatorStyle = res.getInteger(R.integer.default_title_indicator_footer_indicator_style); - final float defaultFooterIndicatorHeight = res.getDimension(R.dimen.default_title_indicator_footer_indicator_height); - final float defaultFooterIndicatorUnderlinePadding = res.getDimension(R.dimen.default_title_indicator_footer_indicator_underline_padding); - final float defaultFooterPadding = res.getDimension(R.dimen.default_title_indicator_footer_padding); - final int defaultSelectedColor = res.getColor(R.color.default_title_indicator_selected_color); - final boolean defaultSelectedBold = res.getBoolean(R.bool.default_title_indicator_selected_bold); - final int defaultTextColor = res.getColor(R.color.default_title_indicator_text_color); - final float defaultTextSize = res.getDimension(R.dimen.default_title_indicator_text_size); - final float defaultTitlePadding = res.getDimension(R.dimen.default_title_indicator_title_padding); - final float defaultClipPadding = res.getDimension(R.dimen.default_title_indicator_clip_padding); - final float defaultTopPadding = res.getDimension(R.dimen.default_title_indicator_top_padding); - - //Retrieve styles attributes - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TitlePageIndicator, defStyle, R.style.Widget_TitlePageIndicator); - - //Retrieve the colors to be used for this view and apply them. - mFooterLineHeight = a.getDimension(R.styleable.TitlePageIndicator_footerLineHeight, defaultFooterLineHeight); - mFooterIndicatorStyle = IndicatorStyle.fromValue(a.getInteger(R.styleable.TitlePageIndicator_footerIndicatorStyle, defaultFooterIndicatorStyle)); - mFooterIndicatorHeight = a.getDimension(R.styleable.TitlePageIndicator_footerIndicatorHeight, defaultFooterIndicatorHeight); - mFooterIndicatorUnderlinePadding = a.getDimension(R.styleable.TitlePageIndicator_footerIndicatorUnderlinePadding, defaultFooterIndicatorUnderlinePadding); - mFooterPadding = a.getDimension(R.styleable.TitlePageIndicator_footerPadding, defaultFooterPadding); - mTopPadding = a.getDimension(R.styleable.TitlePageIndicator_topPadding, defaultTopPadding); - mTitlePadding = a.getDimension(R.styleable.TitlePageIndicator_titlePadding, defaultTitlePadding); - mClipPadding = a.getDimension(R.styleable.TitlePageIndicator_clipPadding, defaultClipPadding); - mColorSelected = a.getColor(R.styleable.TitlePageIndicator_selectedColor, defaultSelectedColor); - mColorText = a.getColor(R.styleable.TitlePageIndicator_textColor, defaultTextColor); - mBoldText = a.getBoolean(R.styleable.TitlePageIndicator_selectedBold, defaultSelectedBold); - - final float textSize = a.getDimension(R.styleable.TitlePageIndicator_textSize, defaultTextSize); - final int footerColor = a.getColor(R.styleable.TitlePageIndicator_footerColor, defaultFooterColor); - mPaintText = new Paint(); - mPaintText.setTextSize(textSize); - mPaintText.setAntiAlias(true); - mPaintFooterLine = new Paint(); - mPaintFooterLine.setStyle(Paint.Style.FILL_AND_STROKE); - mPaintFooterLine.setStrokeWidth(mFooterLineHeight); - mPaintFooterLine.setColor(footerColor); - mPaintFooterIndicator = new Paint(); - mPaintFooterIndicator.setStyle(Paint.Style.FILL_AND_STROKE); - mPaintFooterIndicator.setColor(footerColor); - - a.recycle(); - - final ViewConfiguration configuration = ViewConfiguration.get(context); - mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration); - } - - - public int getFooterColor() { - return mPaintFooterLine.getColor(); - } - - public void setFooterColor(int footerColor) { - mPaintFooterLine.setColor(footerColor); - mPaintFooterIndicator.setColor(footerColor); - invalidate(); - } - - public float getFooterLineHeight() { - return mFooterLineHeight; - } - - public void setFooterLineHeight(float footerLineHeight) { - mFooterLineHeight = footerLineHeight; - mPaintFooterLine.setStrokeWidth(mFooterLineHeight); - invalidate(); - } - - public float getFooterIndicatorHeight() { - return mFooterIndicatorHeight; - } - - public void setFooterIndicatorHeight(float footerTriangleHeight) { - mFooterIndicatorHeight = footerTriangleHeight; - invalidate(); - } - - public float getFooterIndicatorPadding() { - return mFooterPadding; - } - - public void setFooterIndicatorPadding(float footerIndicatorPadding) { - mFooterPadding = footerIndicatorPadding; - invalidate(); - } - - public IndicatorStyle getFooterIndicatorStyle() { - return mFooterIndicatorStyle; - } - - public void setFooterIndicatorStyle(IndicatorStyle indicatorStyle) { - mFooterIndicatorStyle = indicatorStyle; - invalidate(); - } - - public int getSelectedColor() { - return mColorSelected; - } - - public void setSelectedColor(int selectedColor) { - mColorSelected = selectedColor; - invalidate(); - } - - public boolean isSelectedBold() { - return mBoldText; - } - - public void setSelectedBold(boolean selectedBold) { - mBoldText = selectedBold; - invalidate(); - } - - public int getTextColor() { - return mColorText; - } - - public void setTextColor(int textColor) { - mPaintText.setColor(textColor); - mColorText = textColor; - invalidate(); - } - - public float getTextSize() { - return mPaintText.getTextSize(); - } - - public void setTextSize(float textSize) { - mPaintText.setTextSize(textSize); - invalidate(); - } - - public float getTitlePadding() { - return this.mTitlePadding; - } - - public void setTitlePadding(float titlePadding) { - mTitlePadding = titlePadding; - invalidate(); - } - - public float getTopPadding() { - return this.mTopPadding; - } - - public void setTopPadding(float topPadding) { - mTopPadding = topPadding; - invalidate(); - } - - public float getClipPadding() { - return this.mClipPadding; - } - - public void setClipPadding(float clipPadding) { - mClipPadding = clipPadding; - invalidate(); - } - - /* - * (non-Javadoc) - * - * @see android.view.View#onDraw(android.graphics.Canvas) - */ - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - if (mViewPager == null) { - return; - } - final int count = mViewPager.getAdapter().getCount(); - if (count == 0) { - return; - } - - //Calculate views bounds - ArrayList<RectF> bounds = calculateAllBounds(mPaintText); - - //Make sure we're on a page that still exists - if (mCurrentPage >= bounds.size()) { - setCurrentItem(bounds.size()-1); - } - - final int countMinusOne = count - 1; - final float halfWidth = getWidth() / 2f; - final int left = getLeft(); - final float leftClip = left + mClipPadding; - final int width = getWidth(); - final int height = getHeight(); - final int right = left + width; - final float rightClip = right - mClipPadding; - - int page = mCurrentPage; - float offsetPercent; - if (mCurrentOffset <= halfWidth) { - offsetPercent = 1.0f * mCurrentOffset / width; - } else { - page += 1; - offsetPercent = 1.0f * (width - mCurrentOffset) / width; - } - final boolean currentSelected = (offsetPercent <= SELECTION_FADE_PERCENTAGE); - final boolean currentBold = (offsetPercent <= BOLD_FADE_PERCENTAGE); - final float selectedPercent = (SELECTION_FADE_PERCENTAGE - offsetPercent) / SELECTION_FADE_PERCENTAGE; - - //Verify if the current view must be clipped to the screen - RectF curPageBound = bounds.get(mCurrentPage); - float curPageWidth = curPageBound.right - curPageBound.left; - if (curPageBound.left < leftClip) { - //Try to clip to the screen (left side) - clipViewOnTheLeft(curPageBound, curPageWidth, left); - } - if (curPageBound.right > rightClip) { - //Try to clip to the screen (right side) - clipViewOnTheRight(curPageBound, curPageWidth, right); - } - - //Left views starting from the current position - if (mCurrentPage > 0) { - for (int i = mCurrentPage - 1; i >= 0; i--) { - RectF bound = bounds.get(i); - //Is left side is outside the screen - if (bound.left < leftClip) { - float w = bound.right - bound.left; - //Try to clip to the screen (left side) - clipViewOnTheLeft(bound, w, left); - //Except if there's an intersection with the right view - RectF rightBound = bounds.get(i + 1); - //Intersection - if (bound.right + mTitlePadding > rightBound.left) { - bound.left = rightBound.left - w - mTitlePadding; - bound.right = bound.left + w; - } - } - } - } - //Right views starting from the current position - if (mCurrentPage < countMinusOne) { - for (int i = mCurrentPage + 1 ; i < count; i++) { - RectF bound = bounds.get(i); - //If right side is outside the screen - if (bound.right > rightClip) { - float w = bound.right - bound.left; - //Try to clip to the screen (right side) - clipViewOnTheRight(bound, w, right); - //Except if there's an intersection with the left view - RectF leftBound = bounds.get(i - 1); - //Intersection - if (bound.left - mTitlePadding < leftBound.right) { - bound.left = leftBound.right + mTitlePadding; - bound.right = bound.left + w; - } - } - } - } - - //Now draw views - for (int i = 0; i < count; i++) { - //Get the title - RectF bound = bounds.get(i); - //Only if one side is visible - if ((bound.left > left && bound.left < right) || (bound.right > left && bound.right < right)) { - final boolean currentPage = (i == page); - //Only set bold if we are within bounds - mPaintText.setFakeBoldText(currentPage && currentBold && mBoldText); - - //Draw text as unselected - mPaintText.setColor(mColorText); - canvas.drawText(mTitleProvider.getTitle(i), bound.left, bound.bottom + mTopPadding, mPaintText); - - //If we are within the selected bounds draw the selected text - if (currentPage && currentSelected) { - mPaintText.setColor(mColorSelected); - mPaintText.setAlpha((int)((mColorSelected >>> 24) * selectedPercent)); - canvas.drawText(mTitleProvider.getTitle(i), bound.left, bound.bottom + mTopPadding, mPaintText); - } - } - } - - //Draw the footer line - mPath.reset(); - mPath.moveTo(0, height - mFooterLineHeight / 2f); - mPath.lineTo(width, height - mFooterLineHeight / 2f); - mPath.close(); - canvas.drawPath(mPath, mPaintFooterLine); - - switch (mFooterIndicatorStyle) { - case Triangle: - mPath.reset(); - mPath.moveTo(halfWidth, height - mFooterLineHeight - mFooterIndicatorHeight); - mPath.lineTo(halfWidth + mFooterIndicatorHeight, height - mFooterLineHeight); - mPath.lineTo(halfWidth - mFooterIndicatorHeight, height - mFooterLineHeight); - mPath.close(); - canvas.drawPath(mPath, mPaintFooterIndicator); - break; - - case Underline: - if (!currentSelected) { - break; - } - - RectF underlineBounds = bounds.get(page); - mPath.reset(); - mPath.moveTo(underlineBounds.left - mFooterIndicatorUnderlinePadding, height - mFooterLineHeight); - mPath.lineTo(underlineBounds.right + mFooterIndicatorUnderlinePadding, height - mFooterLineHeight); - mPath.lineTo(underlineBounds.right + mFooterIndicatorUnderlinePadding, height - mFooterLineHeight - mFooterIndicatorHeight); - mPath.lineTo(underlineBounds.left - mFooterIndicatorUnderlinePadding, height - mFooterLineHeight - mFooterIndicatorHeight); - mPath.close(); - - mPaintFooterIndicator.setAlpha((int)(0xFF * selectedPercent)); - canvas.drawPath(mPath, mPaintFooterIndicator); - mPaintFooterIndicator.setAlpha(0xFF); - break; - - default: - break; - } - } - - @Override - public boolean onTouchEvent(android.view.MotionEvent ev) { - if ((mViewPager == null) || (mViewPager.getAdapter().getCount() == 0)) { - return false; - } - - final int action = ev.getAction(); - - switch (action & MotionEventCompat.ACTION_MASK) { - case MotionEvent.ACTION_DOWN: - mActivePointerId = MotionEventCompat.getPointerId(ev, 0); - mLastMotionX = ev.getX(); - break; - - case MotionEvent.ACTION_MOVE: { - final int activePointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); - final float x = MotionEventCompat.getX(ev, activePointerIndex); - final float deltaX = x - mLastMotionX; - - if (!mIsDragging) { - if (Math.abs(deltaX) > mTouchSlop) { - mIsDragging = true; - } - } - - if (mIsDragging) { - if (!mViewPager.isFakeDragging()) { - mViewPager.beginFakeDrag(); - } - - mLastMotionX = x; - - mViewPager.fakeDragBy(deltaX); - } - - break; - } - - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - if (!mIsDragging) { - final int count = mViewPager.getAdapter().getCount(); - final int width = getWidth(); - final float halfWidth = width / 2f; - final float sixthWidth = width / 6f; - - if ((mCurrentPage > 0) && (ev.getX() < halfWidth - sixthWidth)) { - mViewPager.setCurrentItem(mCurrentPage - 1); - return true; - } else if ((mCurrentPage < count - 1) && (ev.getX() > halfWidth + sixthWidth)) { - mViewPager.setCurrentItem(mCurrentPage + 1); - return true; - } - } - - mIsDragging = false; - mActivePointerId = INVALID_POINTER; - if (mViewPager.isFakeDragging()) { - mViewPager.endFakeDrag(); - } - break; - - case MotionEventCompat.ACTION_POINTER_DOWN: { - final int index = MotionEventCompat.getActionIndex(ev); - mLastMotionX = MotionEventCompat.getX(ev, index); - mActivePointerId = MotionEventCompat.getPointerId(ev, index); - break; - } - - case MotionEventCompat.ACTION_POINTER_UP: - final int pointerIndex = MotionEventCompat.getActionIndex(ev); - final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex); - if (pointerId == mActivePointerId) { - final int newPointerIndex = pointerIndex == 0 ? 1 : 0; - mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex); - } - mLastMotionX = MotionEventCompat.getX(ev, MotionEventCompat.findPointerIndex(ev, mActivePointerId)); - break; - } - - return true; - } - - /** - * Set bounds for the right textView including clip padding. - * - * @param curViewBound - * current bounds. - * @param curViewWidth - * width of the view. - */ - private void clipViewOnTheRight(RectF curViewBound, float curViewWidth, int right) { - curViewBound.right = right - mClipPadding; - curViewBound.left = curViewBound.right - curViewWidth; - } - - /** - * Set bounds for the left textView including clip padding. - * - * @param curViewBound - * current bounds. - * @param curViewWidth - * width of the view. - */ - private void clipViewOnTheLeft(RectF curViewBound, float curViewWidth, int left) { - curViewBound.left = left + mClipPadding; - curViewBound.right = mClipPadding + curViewWidth; - } - - /** - * Calculate views bounds and scroll them according to the current index - * - * @param paint - * @param currentIndex - * @return - */ - private ArrayList<RectF> calculateAllBounds(Paint paint) { - ArrayList<RectF> list = new ArrayList<RectF>(); - //For each views (If no values then add a fake one) - final int count = mViewPager.getAdapter().getCount(); - final int width = getWidth(); - final int halfWidth = width / 2; - for (int i = 0; i < count; i++) { - RectF bounds = calcBounds(i, paint); - float w = (bounds.right - bounds.left); - float h = (bounds.bottom - bounds.top); - bounds.left = (halfWidth) - (w / 2) - mCurrentOffset + ((i - mCurrentPage) * width); - bounds.right = bounds.left + w; - bounds.top = 0; - bounds.bottom = h; - list.add(bounds); - } - - return list; - } - - /** - * Calculate the bounds for a view's title - * - * @param index - * @param paint - * @return - */ - private RectF calcBounds(int index, Paint paint) { - //Calculate the text bounds - RectF bounds = new RectF(); - bounds.right = paint.measureText(mTitleProvider.getTitle(index)); - bounds.bottom = paint.descent() - paint.ascent(); - return bounds; - } - - @Override - public void setViewPager(ViewPager view) { - final PagerAdapter adapter = view.getAdapter(); - if (adapter == null) { - throw new IllegalStateException("ViewPager does not have adapter instance."); - } - if (!(adapter instanceof TitleProvider)) { - throw new IllegalStateException("ViewPager adapter must implement TitleProvider to be used with TitlePageIndicator."); - } - mViewPager = view; - mViewPager.setOnPageChangeListener(this); - mTitleProvider = (TitleProvider)adapter; - invalidate(); - } - - @Override - public void setViewPager(ViewPager view, int initialPosition) { - setViewPager(view); - setCurrentItem(initialPosition); - } - - @Override - public void notifyDataSetChanged() { - invalidate(); - } - - @Override - public void setCurrentItem(int item) { - if (mViewPager == null) { - throw new IllegalStateException("ViewPager has not been bound."); - } - mViewPager.setCurrentItem(item); - mCurrentPage = item; - invalidate(); - } - - @Override - public void onPageScrollStateChanged(int state) { - mScrollState = state; - - if (mListener != null) { - mListener.onPageScrollStateChanged(state); - } - } - - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - mCurrentPage = position; - mCurrentOffset = positionOffsetPixels; - invalidate(); - - if (mListener != null) { - mListener.onPageScrolled(position, positionOffset, positionOffsetPixels); - } - } - - @Override - public void onPageSelected(int position) { - if (mScrollState == ViewPager.SCROLL_STATE_IDLE) { - mCurrentPage = position; - invalidate(); - } - - if (mListener != null) { - mListener.onPageSelected(position); - } - } - - @Override - public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) { - mListener = listener; - } - - /* - * (non-Javadoc) - * - * @see android.view.View#onMeasure(int, int) - */ - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec)); - } - - /** - * Determines the width of this view - * - * @param measureSpec - * A measureSpec packed into an int - * @return The width of the view, honoring constraints from measureSpec - */ - private int measureWidth(int measureSpec) { - int specMode = MeasureSpec.getMode(measureSpec); - int specSize = MeasureSpec.getSize(measureSpec); - - if (specMode != MeasureSpec.EXACTLY) { - throw new IllegalStateException(getClass().getSimpleName() + " can only be used in EXACTLY mode."); - } - return specSize; - } - - /** - * Determines the height of this view - * - * @param measureSpec - * A measureSpec packed into an int - * @return The height of the view, honoring constraints from measureSpec - */ - private int measureHeight(int measureSpec) { - float result; - int specMode = MeasureSpec.getMode(measureSpec); - int specSize = MeasureSpec.getSize(measureSpec); - - if (specMode == MeasureSpec.EXACTLY) { - //We were told how big to be - result = specSize; - } else { - //Calculate the text bounds - RectF bounds = new RectF(); - bounds.bottom = mPaintText.descent()-mPaintText.ascent(); - result = bounds.bottom - bounds.top + mFooterLineHeight + mFooterPadding + mTopPadding; - if (mFooterIndicatorStyle != IndicatorStyle.None) { - result += mFooterIndicatorHeight; - } - } - return (int)result; - } - - @Override - public void onRestoreInstanceState(Parcelable state) { - SavedState savedState = (SavedState)state; - super.onRestoreInstanceState(savedState.getSuperState()); - mCurrentPage = savedState.currentPage; - requestLayout(); - } - - @Override - public Parcelable onSaveInstanceState() { - Parcelable superState = super.onSaveInstanceState(); - SavedState savedState = new SavedState(superState); - savedState.currentPage = mCurrentPage; - return savedState; - } - - static class SavedState extends BaseSavedState { - int currentPage; - - public SavedState(Parcelable superState) { - super(superState); - } - - private SavedState(Parcel in) { - super(in); - currentPage = in.readInt(); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - super.writeToParcel(dest, flags); - dest.writeInt(currentPage); - } - - public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() { - @Override - public SavedState createFromParcel(Parcel in) { - return new SavedState(in); - } - - @Override - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - } -} diff --git a/main/src/com/viewpagerindicator/TitleProvider.java b/main/src/com/viewpagerindicator/TitleProvider.java deleted file mode 100644 index 2a04b65..0000000 --- a/main/src/com/viewpagerindicator/TitleProvider.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2011 Patrik Akerfeldt - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.viewpagerindicator; - -/** - * A TitleProvider provides the title to display according to a view. - */ -public interface TitleProvider { - /** - * Returns the title of the view at position - * @param position - * @return - */ - public String getTitle(int position); -} diff --git a/main/src/gnu/android/app/appmanualclient/AppManualReaderClient.java b/main/src/gnu/android/app/appmanualclient/AppManualReaderClient.java deleted file mode 100644 index af4c03e..0000000 --- a/main/src/gnu/android/app/appmanualclient/AppManualReaderClient.java +++ /dev/null @@ -1,375 +0,0 @@ -package gnu.android.app.appmanualclient; - -import android.content.ActivityNotFoundException; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.net.Uri; -import android.util.Log; - -import java.util.List; - -/** - * The "App Manual Reader" client is a class to be used in applications which - * want to offer their users manuals through the gnu.android.appmanualreader - * application. Such applications do not need to include the whole - * "App Manual Reader" app but instead just have to include only this little - * package. This package then provides the mechanism to open suitable installed - * manuals. It does not include any manuals itself. - * <p> - * - * (c) 2011 Geocrasher (geocrasher@gmx.eu) - * <p> - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation, either version 3 of the License, or (at your option) any - * later version. - * <p> - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - * details. - * <p> - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - * - * @author Geocrasher - */ -public class AppManualReaderClient { - - /** - * The URI scheme used to identify application manual URIs when flinging - * Intents around within an Android device, in the hope that there are - * activities registered which will handle such application manual URIs. - * Usually, there won't be just a single activity registered but instead - * many, depending on how many manuals are installed on an Android device. - */ - public static final String URI_SCHEME_APPMANUAL = "appmanual"; - - /** - * Standardized topic for opening a manual at its beginning. - * - * @see #openManual(String, String, Context) - * @see #openManual(String, String, Context, String) - */ - public static final String TOPIC_HOME = "andtw-home"; - /** - * Standardized topic for opening the index of a manual. - * - * @see #openManual(String, String, Context) - * @see #openManual(String, String, Context, String) - */ - public static final String TOPIC_INDEX = "andtw-index"; - /** - * Standardized topic for opening a manual's "about" topic. - * - * @see #openManual(String, String, Context) - * @see #openManual(String, String, Context, String) - */ - public static final String TOPIC_ABOUT_MANUAL = "andtw-about"; - - /** - * Convenience function to open a manual at a specific topic. See - * {@link #openManual(String, String, Context, String)} for a detailed - * description. - * - * @param manualIdentifier - * the identifier of the manual to open. This identifier must - * uniquely identify the manual as such, independent of the - * particular locale the manual is intended for. - * @param topic - * the topic to open. Please do not use spaces for topic names. - * With respect to the TiddlyWiki infrastructure used for manuals - * the topic needs to the tag of a (single) tiddler. This way - * manuals can be localized (especially their topic titles) - * without breaking an app's knowledge about topics. Some - * standardized topics are predefined, such as {@link #TOPIC_HOME}, {@link #TOPIC_INDEX}, and - * {@link #TOPIC_ABOUT_MANUAL}. - * @param context - * the context (usually an Activity) from which the manual is to - * be opened. In particular, this context is required to derive - * the proper current locale configuration in order to open - * appropriate localized manuals, if installed. - * - * @exception ActivityNotFoundException - * there is no suitable manual installed and all combinations - * of locale scope failed to activate any manual. - * - * @see #openManual(String, String, Context, String, boolean) - */ - public static void openManual(String manualIdentifier, String topic, - Context context) throws ActivityNotFoundException { - openManual(manualIdentifier, topic, context, null, false); - } - - /** - * Convenience function to open a manual at a specific topic. See - * {@link #openManual(String, String, Context, String)} for a detailed - * description. - * - * @param manualIdentifier - * the identifier of the manual to open. This identifier must - * uniquely identify the manual as such, independent of the - * particular locale the manual is intended for. - * @param topic - * the topic to open. Please do not use spaces for topic names. - * With respect to the TiddlyWiki infrastructure used for manuals - * the topic needs to the tag of a (single) tiddler. This way - * manuals can be localized (especially their topic titles) - * without breaking an app's knowledge about topics. Some - * standardized topics are predefined, such as {@link #TOPIC_HOME}, {@link #TOPIC_INDEX}, and - * {@link #TOPIC_ABOUT_MANUAL}. - * @param context - * the context (usually an Activity) from which the manual is to - * be opened. In particular, this context is required to derive - * the proper current locale configuration in order to open - * appropriate localized manuals, if installed. - * @param fallbackUri - * either <code>null</code> or a fallback URI to be used in case - * the user has not installed any suitable manual. - * - * @exception ActivityNotFoundException - * there is no suitable manual installed and all combinations - * of locale scope failed to activate any manual. - * - * @see #openManual(String, String, Context, String, boolean) - */ - public static void openManual(String manualIdentifier, String topic, - Context context, String fallbackUri) - throws ActivityNotFoundException { - openManual(manualIdentifier, topic, context, fallbackUri, false); - } - - /** - * Opens a manual at a specific topic. At least it tries to open a manual. - * As manuals are (usually) installed separately and we use late binding in - * form of implicit intents, a lot of things can go wrong. - * - * We use late binding and the intent architecture in particular as follows: - * first, we use our own URI scheme called "appmanual". Second, we use the - * host field as a unique manual identifier (such as "c-geo" for the app - * manuals for a map which must not be named by the powers that wanna be). - * Third, a localized manual is differentiated as a path with a single - * element in form of (in this precedence) "/lang_country_variant", - * "/lang__variant", "/lang_country", "/lang", or "/". Fourth, the topic to - * open is encoded as the a fragment "#topic=mytopic". - * - * In order to support localization, manuals can register themselves with - * different URIs. - * - * @param manualIdentifier - * the identifier of the manual to open. This identifier must - * uniquely identify the manual as such, independent of the - * particular locale the manual is intended for. - * @param topic - * the topic to open. Please do not use spaces for topic names. - * With respect to the TiddlyWiki infrastructure used for manuals - * the topic needs to the tag of a (single) tiddler. This way - * manuals can be localized (especially their topic titles) - * without breaking an app's knowledge about topics. Some - * standardized topics are predefined, such as - * {@link #TOPIC_HOME}, {@link #TOPIC_INDEX}, and - * {@link #TOPIC_ABOUT_MANUAL}. - * @param context - * the context (usually an Activity) from which the manual is to - * be opened. In particular, this context is required to derive - * the proper current locale configuration in order to open - * appropriate localized manuals, if installed. - * @param fallbackUri - * either <code>null</code> or a fallback URI to be used in case - * the user has not installed any suitable manual. - * @param contextAffinity - * if <code>true</code>, then we try to open the manual within - * the context, if possible. That is, if the package of the - * calling context also offers suitable activity registrations, - * then we will prefer them over any other registrations. If you - * don't know what this means, then you probably don't need this - * very special capability and should specify <code>false</code> - * for this parameter. - * - * @exception ActivityNotFoundException - * there is no suitable manual installed and all combinations - * of locale scope failed to activate any manual and no - * {@literal fallbackUri} was given. - */ - public static void openManual(String manualIdentifier, String topic, - Context context, String fallbackUri, boolean contextAffinity) - throws ActivityNotFoundException { - // - // The path of an "appmanual:" URI consists simply of the locale - // information. This allows manual packages to register themselves - // for both very specific locales as well as very broad ones. - // - String localePath = "/" - + context.getResources().getConfiguration().locale.toString(); - // - // We later need this intent in order to try to launch an appropriate - // manual (respectively its manual viewer). And yes, we need to set - // the intent's category explicitly, even as we will later use - // startActivity(): if we don't do this, the proper activity won't be - // started albeit the filter almost matches. That dirty behavior (it is - // documented wrong) had cost me half a day until I noticed some - // informational log entry generated from the ActivityManager. Grrrr! - // - Intent intent = new Intent(Intent.ACTION_VIEW); - int defaultIntentFlags = intent.getFlags(); - intent.addCategory(Intent.CATEGORY_DEFAULT); - // - // Try to open the manual in the following order (subject to - // availability): - // 1. manualIdentifier_lang_country_variant (can also be - // manualIdentifier_lang__variant in some cases) - // 2. manualIdentifier_lang_country - // 3. manualIdentifier_lang - // 4. manualIdentifier - // Of course, manuals are free to register more than one Intent, - // in particular, the should register also the plain manualIdentifier - // as a suitable fallback strategy. Even when installing multiple - // manuals this doesn't matter, as the user then can choose which - // one to use on a single or permanent basis. - // - while (true) { - Uri uri = Uri.parse(URI_SCHEME_APPMANUAL + "://" + manualIdentifier - + localePath + "#topic='" + topic + "'"); - // Note: we do not use a MIME type for this. - intent.setData(uri); - intent.setFlags(defaultIntentFlags); - if ( contextAffinity ) { - // - // What is happening here? Well, here we try something that we - // would like to call "package affinity activity resolving". - // Given an implicit(!) intent we try to figure out whether the - // package of the context which is trying to open the manual is - // able to resolve the intent. If this is the case, then we - // simply turn the implicit intent into an explicit(!) intent. - // We do this by setting the concrete module, that is: package - // name (eventually the one of the calling context) and class - // name within the package. - // - List<ResolveInfo> capableActivities = context - .getPackageManager() - .queryIntentActivityOptions(null, null, intent, - PackageManager.MATCH_DEFAULT_ONLY); - int capables = capableActivities.size(); - if ( capables > 1 ) { - for ( int idx = 0; idx < capables; ++idx ) { - ActivityInfo activityInfo = capableActivities.get(idx).activityInfo; - if ( activityInfo.packageName.contentEquals(context - .getPackageName()) ) { - intent.setClassName(activityInfo.packageName, - activityInfo.name); - // - // First match is okay, so we quit searching and - // continue with the usual attempt to start the - // activity. This should not fail, as we already - // found a match; yet the code is very forgiving in - // this respect and would just try another round - // with "downsized" locale requirements. - // - break; - } - } - } - // FIXME - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); - } else { - // - // No context affinity required, thus we need to set some flags: - // - // ...NEW_TASK: we want to start the manual reader activity as a - // separate - // task so that it can be kept open, yet in the background when - // returning to the application from which the manual was - // opened. - // - // ...SINGLE_TOP: - // - // ...RESET_TASK_IF_NEEDED: clear the manual reader activities - // down to the root activity. We've set the required - // ...CLEAR_WHEN_TASK_RESET above when opening the meta-manual - // with the context affinity. - // - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_SINGLE_TOP - | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); - } - try { - String logTag = "appmanualclient"; - if ( Log.isLoggable(logTag, Log.INFO) ) { - Log.i(logTag, - "Trying to activate manual: uri=" + uri.toString()); - } - context.startActivity(intent); - // - // We could successfully activate the manual activity, so no - // further trials are required. - // - return; - } catch ( ActivityNotFoundException noActivity ) { - // - // Ensure that we switch back to implicit intent resolving for - // the next round. - // - intent.setComponent(null); - // - // As long as we still have some locale information, reduce it - // and try again a broader locale. - // - if ( localePath.length() > 1 ) { - int underscore = localePath.lastIndexOf('_'); - if ( underscore > 0 ) { - localePath = localePath.substring(0, underscore); - // - // Handle the case where we have a locale variant, yet - // no locale country, thus two underscores in immediate - // series. Get rid of both. - // - if ( localePath.endsWith("_") ) { - localePath = localePath - .substring(0, underscore - 1); - } - } else { - // - // Ready for the last round: try without any locale - // modifiers. - // - localePath = "/"; - } - } else { - // - // We've tried all combinations, so we've run out of them - // and bail out. - // - break; - } - } - // - // Okay, go for the next round, we've updated (or rather trimmed) - // the localeIdent, so let us try this. - // - } - // - // If we reach this code point then no suitable activity could be found - // and activated. In case the caller specified a fallback URI we will - // try to open that. As this will activate a suitable browser and this - // is an asynchronous activity we won't get back any negative results, - // such as 404's. Here we will only see such problems that prevented the - // start of a suitable browsing activity. - // - if ( fallbackUri != null ) { - intent = new Intent(Intent.ACTION_VIEW, Uri.parse(fallbackUri)); - intent.addCategory(Intent.CATEGORY_BROWSABLE); - context.startActivity(intent); - } - // - // We could not activate any manual and there was no fallback URI to - // open, so we finally bail out unsuccessful with an exception. - // - throw new ActivityNotFoundException(); - } -} diff --git a/main/src/org/openintents/intents/FileManagerIntents.java b/main/src/org/openintents/intents/FileManagerIntents.java deleted file mode 100644 index 8ff10c8..0000000 --- a/main/src/org/openintents/intents/FileManagerIntents.java +++ /dev/null @@ -1,127 +0,0 @@ -/*
- * Copyright (C) 2008 OpenIntents.org
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.openintents.intents;
-
-/**
- * Provides OpenIntents actions, extras, and categories used by providers.
- * <p>These specifiers extend the standard Android specifiers.</p>
- */
-public final class FileManagerIntents {
-
- /**
- * Activity Action: Pick a file through the file manager, or let user
- * specify a custom file name.
- * Data is the current file name or file name suggestion.
- * Returns a new file name as file URI in data.
- *
- * <p>Constant Value: "org.openintents.action.PICK_FILE"</p>
- */
- public static final String ACTION_PICK_FILE = "org.openintents.action.PICK_FILE";
-
- /**
- * Activity Action: Pick a directory through the file manager, or let user
- * specify a custom file name.
- * Data is the current directory name or directory name suggestion.
- * Returns a new directory name as file URI in data.
- *
- * <p>Constant Value: "org.openintents.action.PICK_DIRECTORY"</p>
- */
- public static final String ACTION_PICK_DIRECTORY = "org.openintents.action.PICK_DIRECTORY";
-
- /**
- * Activity Action: Move, copy or delete after select entries.
- * Data is the current directory name or directory name suggestion.
- *
- * <p>Constant Value: "org.openintents.action.MULTI_SELECT"</p>
- */
- public static final String ACTION_MULTI_SELECT = "org.openintents.action.MULTI_SELECT";
-
- public static final String ACTION_SEARCH_STARTED = "org.openintents.action.SEARCH_STARTED";
-
- public static final String ACTION_SEARCH_FINISHED = "org.openintens.action.SEARCH_FINISHED";
-
- /**
- * The title to display.
- *
- * <p>This is shown in the title bar of the file manager.</p>
- *
- * <p>Constant Value: "org.openintents.extra.TITLE"</p>
- */
- public static final String EXTRA_TITLE = "org.openintents.extra.TITLE";
-
- /**
- * The text on the button to display.
- *
- * <p>Depending on the use, it makes sense to set this to "Open" or "Save".</p>
- *
- * <p>Constant Value: "org.openintents.extra.BUTTON_TEXT"</p>
- */
- public static final String EXTRA_BUTTON_TEXT = "org.openintents.extra.BUTTON_TEXT";
-
- /**
- * Flag indicating to show only writeable files and folders.
- *
- * <p>Constant Value: "org.openintents.extra.WRITEABLE_ONLY"</p>
- */
- public static final String EXTRA_WRITEABLE_ONLY = "org.openintents.extra.WRITEABLE_ONLY";
-
- /**
- * The path to prioritize in search. Usually denotes the path the user was on when the search was initiated.
- *
- * <p>Constant Value: "org.openintents.extra.SEARCH_INIT_PATH"</p>
- */
- public static final String EXTRA_SEARCH_INIT_PATH = "org.openintents.extra.SEARCH_INIT_PATH";
-
- /**
- * The search query as sent to SearchService.
- *
- * <p>Constant Value: "org.openintents.extra.SEARCH_QUERY"</p>
- */
- public static final String EXTRA_SEARCH_QUERY = "org.openintents.extra.SEARCH_QUERY";
-
- /**
- * <p>Constant Value: "org.openintents.extra.DIR_PATH"</p>
- */
- public static final String EXTRA_DIR_PATH = "org.openintents.extra.DIR_PATH";
-
- /**
- * Extension by which to filter.
- *
- * <p>Constant Value: "org.openintents.extra.FILTER_FILETYPE"</p>
- */
- public static final String EXTRA_FILTER_FILETYPE = "org.openintents.extra.FILTER_FILETYPE";
-
- /**
- * Mimetype by which to filter.
- *
- * <p>Constant Value: "org.openintents.extra.FILTER_MIMETYPE"</p>
- */
- public static final String EXTRA_FILTER_MIMETYPE = "org.openintents.extra.FILTER_MIMETYPE";
-
- /**
- * Only show directories.
- *
- * <p>Constant Value: "org.openintents.extra.DIRECTORIES_ONLY"</p>
- */
- public static final String EXTRA_DIRECTORIES_ONLY = "org.openintents.extra.DIRECTORIES_ONLY";
-
- public static final String EXTRA_DIALOG_FILE_HOLDER = "org.openintents.extra.DIALOG_FILE";
-
- public static final String EXTRA_IS_GET_CONTENT_INITIATED = "org.openintents.extra.ENABLE_ACTIONS";
-
- public static final String EXTRA_FILENAME = "org.openintents.extra.FILENAME";
-}
|
