diff options
Diffstat (limited to 'main/src')
231 files changed, 9404 insertions, 6109 deletions
diff --git a/main/src/android/support/v4/app/FragmentListActivity.java b/main/src/android/support/v4/app/FragmentListActivity.java new file mode 100644 index 0000000..e3ed42c --- /dev/null +++ b/main/src/android/support/v4/app/FragmentListActivity.java @@ -0,0 +1,316 @@ +/* + * 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 b7a14ff..c154ffb 100644 --- a/main/src/cgeo/geocaching/AboutActivity.java +++ b/main/src/cgeo/geocaching/AboutActivity.java @@ -17,7 +17,7 @@ public class AboutActivity extends AbstractActivity { super.onCreate(savedInstanceState); setTheme(); - setContentView(R.layout.about); + setContentView(R.layout.about_activity); setTitle(res.getString(R.string.about)); ((TextView) findViewById(R.id.about_version_string)).setText(Version.getVersionName(this)); diff --git a/main/src/cgeo/geocaching/AbstractLoggingActivity.java b/main/src/cgeo/geocaching/AbstractLoggingActivity.java index 62cb5cb..37c3643 100644 --- a/main/src/cgeo/geocaching/AbstractLoggingActivity.java +++ b/main/src/cgeo/geocaching/AbstractLoggingActivity.java @@ -20,7 +20,7 @@ public abstract class AbstractLoggingActivity extends AbstractActivity { private static final int MENU_SIGNATURE = 1; private static final int MENU_SMILEY = 2; - public AbstractLoggingActivity(String helpTopic) { + protected AbstractLoggingActivity(String helpTopic) { super(helpTopic); } @@ -51,11 +51,11 @@ public abstract class AbstractLoggingActivity extends AbstractActivity { menu.findItem(MENU_SIGNATURE).setVisible(signatureAvailable); boolean smileyVisible = false; - final cgCache cache = getLogContext().getCache(); + final Geocache cache = getLogContext().getCache(); if (cache != null && ConnectorFactory.getConnector(cache).equals(GCConnector.getInstance())) { smileyVisible = true; } - final cgTrackable trackable = getLogContext().getTrackable(); + final Trackable trackable = getLogContext().getTrackable(); if (trackable != null && ConnectorFactory.getConnector(trackable).equals(GCConnector.getInstance())) { smileyVisible = true; } diff --git a/main/src/cgeo/geocaching/AbstractPopupActivity.java b/main/src/cgeo/geocaching/AbstractPopupActivity.java index 830d61a..f903d00 100644 --- a/main/src/cgeo/geocaching/AbstractPopupActivity.java +++ b/main/src/cgeo/geocaching/AbstractPopupActivity.java @@ -1,6 +1,7 @@ package cgeo.geocaching; import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.activity.ActivityMixin; import cgeo.geocaching.apps.cache.navi.NavigationAppFactory; import cgeo.geocaching.enumerations.CacheSize; import cgeo.geocaching.enumerations.LoadFlags; @@ -37,9 +38,8 @@ public abstract class AbstractPopupActivity extends AbstractActivity { 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 static final String EXTRA_GEOCODE = "geocode"; - protected cgCache cache = null; + protected Geocache cache = null; protected String geocode = null; protected CacheDetailsCreator details; @@ -73,7 +73,7 @@ public abstract class AbstractPopupActivity extends AbstractActivity { } }; - public AbstractPopupActivity(String helpTopic, int layout) { + protected AbstractPopupActivity(String helpTopic, int layout) { super(helpTopic); this.layout = layout; } @@ -109,7 +109,7 @@ public abstract class AbstractPopupActivity extends AbstractActivity { } protected void init() { - cache = app.loadCache(geocode, LoadFlags.LOAD_CACHE_OR_DB); + cache = cgData.loadCache(geocode, LoadFlags.LOAD_CACHE_OR_DB); if (cache == null) { showToast(res.getString(R.string.err_detail_cache_find)); @@ -118,7 +118,7 @@ public abstract class AbstractPopupActivity extends AbstractActivity { return; } - geocode = cache.getGeocode().toUpperCase(); + geocode = cache.getGeocode(); } private void showInBrowser() { @@ -130,6 +130,8 @@ public abstract class AbstractPopupActivity extends AbstractActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + // set theme + this.setTheme(ActivityMixin.getDialogTheme()); // set layout setContentView(layout); setTitle(res.getString(R.string.detail)); @@ -137,7 +139,7 @@ public abstract class AbstractPopupActivity extends AbstractActivity { // get parameters final Bundle extras = getIntent().getExtras(); if (extras != null) { - geocode = extras.getString(EXTRA_GEOCODE); + geocode = extras.getString(Intents.EXTRA_GEOCODE); } if (StringUtils.isBlank(geocode)) { @@ -186,10 +188,10 @@ public abstract class AbstractPopupActivity extends AbstractActivity { case MENU_SHOW_IN_BROWSER: showInBrowser(); return true; - } - - if (LoggingUI.onMenuItemSelected(item, this, cache)) { - return true; + default: + if (LoggingUI.onMenuItemSelected(item, this, cache)) { + return true; + } } return true; @@ -251,7 +253,7 @@ public abstract class AbstractPopupActivity extends AbstractActivity { final String cacheSize = cache.getSize() != CacheSize.UNKNOWN ? " (" + cache.getSize().getL10n() + ")" : ""; details.add(R.string.cache_type, cacheType + cacheSize); - details.add(R.string.cache_geocode, cache.getGeocode().toUpperCase()); + details.add(R.string.cache_geocode, cache.getGeocode()); details.addCacheState(cache); details.addDistance(cache, cacheDistance); @@ -276,7 +278,7 @@ public abstract class AbstractPopupActivity extends AbstractActivity { @Override public void onClick(View arg0) { - CacheDetailActivity.startActivity(AbstractPopupActivity.this, geocode.toUpperCase()); + CacheDetailActivity.startActivity(AbstractPopupActivity.this, geocode); finish(); } }); diff --git a/main/src/cgeo/geocaching/AdressListActivity.java b/main/src/cgeo/geocaching/AddressListActivity.java index 8ce16dd..b1de065 100644 --- a/main/src/cgeo/geocaching/AdressListActivity.java +++ b/main/src/cgeo/geocaching/AddressListActivity.java @@ -16,7 +16,7 @@ import android.os.Bundle; import java.util.List; import java.util.Locale; -public class AdressListActivity extends AbstractListActivity { +public class AddressListActivity extends AbstractListActivity { @Override public void onCreate(Bundle savedInstanceState) { @@ -27,7 +27,7 @@ public class AdressListActivity extends AbstractListActivity { setTitle(res.getString(R.string.search_address_result)); // get parameters - final String keyword = getIntent().getStringExtra("keyword"); + final String keyword = getIntent().getStringExtra(Intents.EXTRA_KEYWORD); if (keyword == null) { showToast(res.getString(R.string.err_search_address_forgot)); @@ -46,7 +46,7 @@ public class AdressListActivity extends AbstractListActivity { @Override protected List<Address> doInBackground(Void... params) { - final Geocoder geocoder = new Geocoder(AdressListActivity.this, Locale.getDefault()); + final Geocoder geocoder = new Geocoder(AddressListActivity.this, Locale.getDefault()); try { return geocoder.getFromLocationName(keyword, 20); } catch (Exception e) { @@ -55,7 +55,7 @@ public class AdressListActivity extends AbstractListActivity { Log.i("No geocoder available"); } else { - Log.e("AdressListActivity.doInBackground", e); + Log.e("AddressListActivity.doInBackground", e); } return null; } @@ -70,7 +70,7 @@ public class AdressListActivity extends AbstractListActivity { } } else { finish(); - cgeocaches.startActivityAddress(AdressListActivity.this, null, keyword); + cgeocaches.startActivityAddress(AddressListActivity.this, null, keyword); } } diff --git a/main/src/cgeo/geocaching/CacheCache.java b/main/src/cgeo/geocaching/CacheCache.java index e027a8a..e70b7a0 100644 --- a/main/src/cgeo/geocaching/CacheCache.java +++ b/main/src/cgeo/geocaching/CacheCache.java @@ -20,10 +20,10 @@ import java.util.Set; public class CacheCache { private static final int MAX_CACHED_CACHES = 1000; - final private LeastRecentlyUsedMap<String, cgCache> cachesCache; + final private LeastRecentlyUsedMap<String, Geocache> cachesCache; public CacheCache() { - cachesCache = new LeastRecentlyUsedMap.LruCache<String, cgCache>(MAX_CACHED_CACHES); + cachesCache = new LeastRecentlyUsedMap.LruCache<String, Geocache>(MAX_CACHED_CACHES); cachesCache.setRemoveHandler(new CacheRemoveHandler()); } @@ -51,7 +51,7 @@ public class CacheCache { * Cache * */ - public void putCacheInCache(final cgCache cache) { + public void putCacheInCache(final Geocache cache) { if (cache == null) { throw new IllegalArgumentException("cache must not be null"); } @@ -69,7 +69,7 @@ public class CacheCache { * Geocode of the cache to retrieve from the cache * @return cache if found, null else */ - public cgCache getCacheFromCache(final String geocode) { + public Geocache getCacheFromCache(final String geocode) { if (StringUtils.isBlank(geocode)) { throw new IllegalArgumentException("geocode must not be empty"); } @@ -80,14 +80,14 @@ public class CacheCache { public synchronized Set<String> getInViewport(final Viewport viewport, final CacheType cacheType) { final Set<String> geocodes = new HashSet<String>(); - for (final cgCache cache : cachesCache.values()) { + for (final Geocache cache : cachesCache.values()) { if (cache.getCoords() == null) { // FIXME: this kludge must be removed, it is only present to help us debug the cases where // caches contain null coordinates. Log.e("CacheCache.getInViewport: got cache with null coordinates: " + cache.getGeocode()); continue; } - if ((CacheType.ALL == cacheType || cache.getType() == cacheType) && viewport.contains(cache)) { + if (cacheType.contains(cache) && viewport.contains(cache)) { geocodes.add(cache.getGeocode()); } } @@ -99,10 +99,10 @@ public class CacheCache { return StringUtils.join(cachesCache.keySet(), ' '); } - private static class CacheRemoveHandler implements RemoveHandler<cgCache> { + private static class CacheRemoveHandler implements RemoveHandler<Geocache> { @Override - public void onRemove(final cgCache removed) { + public void onRemove(final Geocache removed) { // FIXME: as above, we sometimes get caches with null coordinates, that may then provoke // a NullPointerException down the invocation chain. if (removed.getCoords() != null) { diff --git a/main/src/cgeo/geocaching/CacheDetailActivity.java b/main/src/cgeo/geocaching/CacheDetailActivity.java index 0eb13c9..43ce65f 100644 --- a/main/src/cgeo/geocaching/CacheDetailActivity.java +++ b/main/src/cgeo/geocaching/CacheDetailActivity.java @@ -1,10 +1,10 @@ package cgeo.geocaching; import cgeo.calendar.ICalendar; -import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.activity.AbstractViewPagerActivity; import cgeo.geocaching.activity.Progress; -import cgeo.geocaching.apps.cache.GeneralAppsFactory; import cgeo.geocaching.apps.cache.navi.NavigationAppFactory; +import cgeo.geocaching.compatibility.Compatibility; import cgeo.geocaching.connector.ConnectorFactory; import cgeo.geocaching.connector.IConnector; import cgeo.geocaching.connector.gc.GCConnector; @@ -12,18 +12,22 @@ import cgeo.geocaching.enumerations.CacheAttribute; import cgeo.geocaching.enumerations.LoadFlags; import cgeo.geocaching.enumerations.LoadFlags.SaveFlag; import cgeo.geocaching.enumerations.LogType; +import cgeo.geocaching.enumerations.WaypointType; import cgeo.geocaching.geopoint.GeopointFormatter; import cgeo.geocaching.geopoint.Units; import cgeo.geocaching.network.HtmlImage; +import cgeo.geocaching.network.Network; import cgeo.geocaching.network.Parameters; +import cgeo.geocaching.ui.AbstractCachingPageViewCreator; import cgeo.geocaching.ui.AnchorAwareLinkMovementMethod; import cgeo.geocaching.ui.CacheDetailsCreator; import cgeo.geocaching.ui.DecryptTextClickListener; -import cgeo.geocaching.ui.EditorDialog; 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.WeakReferenceHandler; +import cgeo.geocaching.ui.dialog.EditorDialog; import cgeo.geocaching.utils.BaseUtils; import cgeo.geocaching.utils.CancellableHandler; import cgeo.geocaching.utils.ClipboardUtils; @@ -32,23 +36,26 @@ import cgeo.geocaching.utils.GeoDirHandler; import cgeo.geocaching.utils.HtmlUtils; import cgeo.geocaching.utils.ImageHelper; import cgeo.geocaching.utils.Log; +import cgeo.geocaching.utils.MatcherWrapper; +import cgeo.geocaching.utils.RunnableWithArgument; import cgeo.geocaching.utils.TranslationUtils; import cgeo.geocaching.utils.UnknownTagsHandler; -import com.viewpagerindicator.TitlePageIndicator; -import com.viewpagerindicator.TitleProvider; - import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringEscapeUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; import android.R.color; import android.app.AlertDialog; +import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Typeface; import android.graphics.drawable.BitmapDrawable; @@ -58,10 +65,6 @@ import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.os.Message; -import android.os.Parcelable; -import android.support.v4.view.PagerAdapter; -import android.support.v4.view.ViewPager; -import android.support.v4.view.ViewPager.OnPageChangeListener; import android.text.Editable; import android.text.Html; import android.text.Spannable; @@ -71,16 +74,17 @@ import android.text.method.LinkMovementMethod; import android.text.style.ForegroundColorSpan; import android.text.style.StrikethroughSpan; import android.text.style.StyleSpan; +import android.util.TypedValue; import android.view.ContextMenu; import android.view.Menu; import android.view.MenuItem; import android.view.SubMenu; import android.view.View; +import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.ViewParent; -import android.view.WindowManager; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; @@ -93,17 +97,14 @@ import android.widget.ScrollView; import android.widget.TextView; import android.widget.TextView.BufferType; -import java.net.URLEncoder; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.EnumSet; -import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; -import java.util.regex.Matcher; import java.util.regex.Pattern; /** @@ -111,9 +112,8 @@ import java.util.regex.Pattern; * * e.g. details, description, logs, waypoints, inventory... */ -public class CacheDetailActivity extends AbstractActivity { +public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailActivity.Page> { - private static final String EXTRAS_PAGE = "page"; private static final int MENU_FIELD_COPY = 1; private static final int MENU_FIELD_TRANSLATE = 2; private static final int MENU_FIELD_TRANSLATE_EN = 3; @@ -130,10 +130,12 @@ public class CacheDetailActivity extends AbstractActivity { private static final int CONTEXT_MENU_WAYPOINT_NAVIGATE = 1238; private static final int CONTEXT_MENU_WAYPOINT_CACHES_AROUND = 1239; 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}" + "\""); + public static final String STATE_PAGE_INDEX = "cgeo.geocaching.pageIndex"; - private cgCache cache; + private Geocache cache; private final Progress progress = new Progress(); private SearchResult search; @@ -170,33 +172,6 @@ public class CacheDetailActivity extends AbstractActivity { private int contextMenuWPIndex = -1; /** - * A {@link List} of all available pages. - * - * TODO Move to adapter - */ - private final List<Page> pageOrder = new ArrayList<Page>(); - - /** - * Instances of all {@link PageViewCreator}. - */ - private final Map<Page, PageViewCreator> viewCreators = new HashMap<Page, PageViewCreator>(); - - /** - * The {@link ViewPager} for this activity. - */ - private ViewPager viewPager; - - /** - * The {@link ViewPagerAdapter} for this activity. - */ - private ViewPagerAdapter viewPagerAdapter; - - /** - * The {@link TitlePageIndicator} for this activity. - */ - private TitlePageIndicator titleIndicator; - - /** * If another activity is called and can modify the data of this activity, we refresh it on resume. */ private boolean refreshOnResume = false; @@ -227,8 +202,6 @@ public class CacheDetailActivity extends AbstractActivity { setTitle(res.getString(R.string.cache)); String geocode = null; - String guid = null; - String name = null; // TODO Why can it happen that search is not null? onCreate should be called only once and it is not set before. if (search != null) { @@ -243,16 +216,18 @@ public class CacheDetailActivity extends AbstractActivity { final Uri uri = getIntent().getData(); // try to get data from extras + String name = null; + String guid = null; if (geocode == null && extras != null) { - geocode = extras.getString("geocode"); - name = extras.getString("name"); - guid = extras.getString("guid"); + geocode = extras.getString(Intents.EXTRA_GEOCODE); + name = extras.getString(Intents.EXTRA_NAME); + guid = extras.getString(Intents.EXTRA_GUID); } // try to get data from URI if (geocode == null && guid == null && uri != null) { - String uriHost = uri.getHost().toLowerCase(); - String uriPath = uri.getPath().toLowerCase(); + String uriHost = uri.getHost().toLowerCase(Locale.US); + String uriPath = uri.getPath().toLowerCase(Locale.US); String uriQuery = uri.getQuery(); if (uriQuery != null) { @@ -266,11 +241,11 @@ public class CacheDetailActivity extends AbstractActivity { guid = uri.getQueryParameter("guid"); if (StringUtils.isNotBlank(geocode)) { - geocode = geocode.toUpperCase(); + geocode = geocode.toUpperCase(Locale.US); guid = null; } else if (StringUtils.isNotBlank(guid)) { geocode = null; - guid = guid.toLowerCase(); + guid = guid.toLowerCase(Locale.US); } else { showToast(res.getString(R.string.err_detail_open)); finish(); @@ -278,7 +253,7 @@ public class CacheDetailActivity extends AbstractActivity { } } else if (uriHost.contains("coord.info")) { if (uriPath != null && uriPath.startsWith("/gc")) { - geocode = uriPath.substring(1).toUpperCase(); + geocode = uriPath.substring(1).toUpperCase(Locale.US); } else { showToast(res.getString(R.string.err_detail_open)); finish(); @@ -301,7 +276,7 @@ public class CacheDetailActivity extends AbstractActivity { if (StringUtils.isNotBlank(name)) { title = name; } else if (null != geocode && StringUtils.isNotBlank(geocode)) { // can't be null, but the compiler doesn't understand StringUtils.isNotBlank() - title = geocode.toUpperCase(); + title = geocode; } progress.show(this, title, res.getString(R.string.cache_dialog_loading_details), true, loadCacheHandler.cancelMessage()); } catch (Exception e) { @@ -317,55 +292,34 @@ public class CacheDetailActivity extends AbstractActivity { } }); - // initialize ViewPager - viewPager = (ViewPager) findViewById(R.id.viewpager); - viewPagerAdapter = new ViewPagerAdapter(); - viewPager.setAdapter(viewPagerAdapter); + final int pageToOpen = savedInstanceState != null ? + savedInstanceState.getInt(STATE_PAGE_INDEX, 0) : + Settings.isOpenLastDetailsPage() ? Settings.getLastDetailsPage() : 1; + createViewPager(pageToOpen, new OnPageSelectedListener() { - titleIndicator = (TitlePageIndicator) findViewById(R.id.pager_indicator); - titleIndicator.setViewPager(viewPager); - titleIndicator.setOnPageChangeListener(new OnPageChangeListener() { @Override public void onPageSelected(int position) { if (Settings.isOpenLastDetailsPage()) { Settings.setLastDetailsPage(position); } // lazy loading of cache images - if (pageOrder.get(position) == Page.IMAGES) { + if (getPage(position) == Page.IMAGES) { loadCacheImages(); } } - - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - } - - @Override - public void onPageScrollStateChanged(int state) { - } }); - // switch to entry page (last used or 2) - int entryPageIndex; - if (extras != null && extras.get(EXTRAS_PAGE) != null) { - entryPageIndex = extras.getInt(EXTRAS_PAGE); - } - else { - entryPageIndex = Settings.isOpenLastDetailsPage() ? Settings.getLastDetailsPage() : 1; - } - if (viewPagerAdapter.getCount() < entryPageIndex) { - for (int i = 0; i <= entryPageIndex; i++) { - // we can't switch to a page that is out of bounds, so we add null-pages - pageOrder.add(null); - } - } - viewPager.setCurrentItem(entryPageIndex, false); - // Initialization done. Let's load the data with the given information. new LoadCacheThread(geocode, guid, loadCacheHandler).start(); } @Override + public void onSaveInstanceState(final Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt(STATE_PAGE_INDEX, getCurrentItem()); + } + + @Override public void onResume() { super.onResume(); @@ -396,9 +350,10 @@ public class CacheDetailActivity extends AbstractActivity { final int viewId = view.getId(); contextMenuWPIndex = -1; switch (viewId) { - case R.id.value: // coordinates + case R.id.value: // coordinates, gc-code, name clickedItemText = ((TextView) view).getText(); - buildOptionsContextmenu(menu, viewId, res.getString(R.string.cache_coordinates), true); + String itemTitle = (String) ((TextView) ((View) view.getParent()).findViewById(R.id.name)).getText(); + buildOptionsContextmenu(menu, viewId, itemTitle, true); break; case R.id.shortdesc: clickedItemText = ((TextView) view).getText(); @@ -432,15 +387,19 @@ public class CacheDetailActivity extends AbstractActivity { final ViewGroup parent = ((ViewGroup) view.getParent()); for (int i = 0; i < parent.getChildCount(); i++) { if (parent.getChildAt(i) == view) { - final List<cgWaypoint> sortedWaypoints = new ArrayList<cgWaypoint>(cache.getWaypoints()); + final List<Waypoint> sortedWaypoints = new ArrayList<Waypoint>(cache.getWaypoints()); Collections.sort(sortedWaypoints); - final cgWaypoint waypoint = sortedWaypoints.get(i); + final Waypoint waypoint = sortedWaypoints.get(i); final int index = cache.getWaypoints().indexOf(waypoint); menu.setHeaderTitle(res.getString(R.string.waypoint)); - menu.add(CONTEXT_MENU_WAYPOINT_EDIT, index, 0, R.string.waypoint_edit); - menu.add(CONTEXT_MENU_WAYPOINT_DUPLICATE, index, 0, R.string.waypoint_duplicate); + if (waypoint.getWaypointType().equals(WaypointType.ORIGINAL)) { + menu.add(CONTEXT_MENU_WAYPOINT_RESET_ORIGINAL_CACHE_COORDINATES, index, 0, R.string.waypoint_reset_cache_coords); + } else { + menu.add(CONTEXT_MENU_WAYPOINT_EDIT, index, 0, R.string.waypoint_edit); + menu.add(CONTEXT_MENU_WAYPOINT_DUPLICATE, index, 0, R.string.waypoint_duplicate); + } contextMenuWPIndex = index; - if (waypoint.isUserDefined()) { + if (waypoint.isUserDefined() && !waypoint.getWaypointType().equals(WaypointType.ORIGINAL)) { menu.add(CONTEXT_MENU_WAYPOINT_DELETE, index, 0, R.string.waypoint_delete); } if (waypoint.getCoords() != null) { @@ -512,54 +471,63 @@ public class CacheDetailActivity extends AbstractActivity { } break; - case CONTEXT_MENU_WAYPOINT_EDIT: { - final cgWaypoint waypoint = cache.getWaypoint(index); - if (waypoint != null) { - EditWaypointActivity.startActivityEditWaypoint(this, waypoint.getId()); + case CONTEXT_MENU_WAYPOINT_EDIT: + final Waypoint waypointEdit = cache.getWaypoint(index); + if (waypointEdit != null) { + EditWaypointActivity.startActivityEditWaypoint(this, waypointEdit.getId()); refreshOnResume = true; } break; - } case CONTEXT_MENU_WAYPOINT_DUPLICATE: - if (cache.duplicateWaypoint(index)) { - app.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); + final Waypoint waypointDuplicate = cache.getWaypoint(index); + if (cache.duplicateWaypoint(waypointDuplicate)) { + cgData.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); notifyDataSetChanged(); } break; case CONTEXT_MENU_WAYPOINT_DELETE: - if (cache.deleteWaypoint(index)) { - app.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); + final Waypoint waypointDelete = cache.getWaypoint(index); + if (cache.deleteWaypoint(waypointDelete)) { + cgData.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); notifyDataSetChanged(); } break; - case CONTEXT_MENU_WAYPOINT_DEFAULT_NAVIGATION: { - final cgWaypoint waypoint = cache.getWaypoint(index); - if (waypoint != null) { - NavigationAppFactory.startDefaultNavigationApplication(1, this, waypoint); + case CONTEXT_MENU_WAYPOINT_DEFAULT_NAVIGATION: + final Waypoint waypointNavigation = cache.getWaypoint(index); + if (waypointNavigation != null) { + NavigationAppFactory.startDefaultNavigationApplication(1, this, waypointNavigation); } - } break; - case CONTEXT_MENU_WAYPOINT_NAVIGATE: { - final cgWaypoint waypoint = cache.getWaypoint(contextMenuWPIndex); - if (waypoint != null) { - NavigationAppFactory.showNavigationMenu(this, null, waypoint, null); + case CONTEXT_MENU_WAYPOINT_NAVIGATE: + final Waypoint waypointNav = cache.getWaypoint(contextMenuWPIndex); + if (waypointNav != null) { + NavigationAppFactory.showNavigationMenu(this, null, waypointNav, null); } - } break; - case CONTEXT_MENU_WAYPOINT_CACHES_AROUND: { - final cgWaypoint waypoint = cache.getWaypoint(index); - if (waypoint != null) { - cgeocaches.startActivityCoordinates(this, waypoint.getCoords()); + case CONTEXT_MENU_WAYPOINT_CACHES_AROUND: + final Waypoint waypointAround = cache.getWaypoint(index); + if (waypointAround != null) { + cgeocaches.startActivityCoordinates(this, waypointAround.getCoords()); + } + break; + + case CONTEXT_MENU_WAYPOINT_RESET_ORIGINAL_CACHE_COORDINATES: + final Waypoint waypointReset = cache.getWaypoint(index); + if (ConnectorFactory.getConnector(cache).supportsOwnCoordinates()) { + createResetCacheCoordinatesDialog(cache, waypointReset).show(); + } + else { + final ProgressDialog progressDialog = ProgressDialog.show(this, getString(R.string.cache), getString(R.string.waypoint_reset), true); + final HandlerResetCoordinates handler = new HandlerResetCoordinates(this, progressDialog, false); + new ResetCoordsThread(cache, handler, waypointReset, true, false, progressDialog).start(); } - } break; - default: { + + default: if (imagesList != null && imagesList.onContextItemSelected(item)) { return true; } - return onOptionsItemSelected(item); - } } return false; } @@ -571,7 +539,6 @@ public class CacheDetailActivity extends AbstractActivity { final SubMenu subMenu = menu.addSubMenu(0, 0, 0, res.getString(R.string.cache_menu_navigate)).setIcon(R.drawable.ic_menu_mapmode); NavigationAppFactory.addMenuItems(subMenu, cache); - GeneralAppsFactory.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); @@ -584,10 +551,12 @@ public class CacheDetailActivity extends AbstractActivity { @Override public boolean onPrepareOptionsMenu(Menu menu) { - menu.findItem(MENU_DEFAULT_NAVIGATION).setVisible(null != cache.getCoords()); - menu.findItem(MENU_CALENDAR).setVisible(cache.canBeAddedToCalendar()); - menu.findItem(MENU_CACHES_AROUND).setVisible(null != cache.getCoords() && cache.supportsCachesAround()); - menu.findItem(MENU_BROWSER).setVisible(cache.canOpenInBrowser()); + if (cache != null) { + menu.findItem(MENU_DEFAULT_NAVIGATION).setVisible(null != cache.getCoords()); + menu.findItem(MENU_CALENDAR).setVisible(cache.canBeAddedToCalendar()); + menu.findItem(MENU_CACHES_AROUND).setVisible(null != cache.getCoords() && cache.supportsCachesAround()); + menu.findItem(MENU_BROWSER).setVisible(cache.canOpenInBrowser()); + } return super.onPrepareOptionsMenu(menu); } @@ -595,7 +564,7 @@ public class CacheDetailActivity extends AbstractActivity { public boolean onOptionsItemSelected(MenuItem item) { final int menuItem = item.getItemId(); - switch(menuItem) { + switch (menuItem) { case 0: // no menu selected, but a new sub menu shown return false; @@ -617,16 +586,14 @@ public class CacheDetailActivity extends AbstractActivity { return true; } return false; - } - if (NavigationAppFactory.onMenuItemSelected(item, this, cache)) { - return true; - } - if (GeneralAppsFactory.onMenuItemSelected(item, this, cache)) { - return true; - } - if (LoggingUI.onMenuItemSelected(item, this, cache)) { - refreshOnResume = true; - return true; + default: + if (NavigationAppFactory.onMenuItemSelected(item, this, cache)) { + return true; + } + if (LoggingUI.onMenuItemSelected(item, this, cache)) { + refreshOnResume = true; + return true; + } } return true; @@ -641,6 +608,7 @@ public class CacheDetailActivity extends AbstractActivity { if (search == null) { showToast(res.getString(R.string.err_dwld_details_failed)); + progress.dismiss(); finish(); return; } @@ -648,6 +616,7 @@ public class CacheDetailActivity extends AbstractActivity { if (search.getError() != null) { showToast(res.getString(R.string.err_dwld_details_failed) + " " + search.getError().getErrorString(res) + "."); + progress.dismiss(); finish(); return; } @@ -689,51 +658,18 @@ public class CacheDetailActivity extends AbstractActivity { // allow cache to notify CacheDetailActivity when it changes so it can be reloaded cache.setChangeNotificationHandler(cacheChangeNotificationHandler); - // notify all creators that the data has changed - for (PageViewCreator creator : viewCreators.values()) { - creator.notifyDataSetChanged(); - } - - // action bar: title and icon (default: mystery icon) + // action bar: title and icon if (StringUtils.isNotBlank(cache.getName())) { - setTitle(cache.getName() + " (" + cache.getGeocode().toUpperCase() + ')'); + setTitle(cache.getName() + " (" + cache.getGeocode() + ')'); } else { - setTitle(cache.getGeocode().toUpperCase()); + setTitle(cache.getGeocode()); } ((TextView) findViewById(R.id.actionbar_title)).setCompoundDrawablesWithIntrinsicBounds(getResources().getDrawable(cache.getType().markerId), null, null, null); - // add available pages (remove old pages first) - pageOrder.clear(); - - pageOrder.add(Page.WAYPOINTS); - pageOrder.add(Page.DETAILS); - final int detailsIndex = pageOrder.size() - 1; - pageOrder.add(Page.DESCRIPTION); - if (cache.getLogs().isNotEmpty()) { - pageOrder.add(Page.LOGS); - } - if (CollectionUtils.isNotEmpty(cache.getFriendsLogs())) { - pageOrder.add(Page.LOGSFRIENDS); - } - if (CollectionUtils.isNotEmpty(cache.getInventory())) { - pageOrder.add(Page.INVENTORY); - } - if (CollectionUtils.isNotEmpty(cache.getImages())) { - pageOrder.add(Page.IMAGES); - } - - // switch to details page, if we're out of bounds - if (viewPager.getCurrentItem() < 0 || viewPager.getCurrentItem() >= viewPagerAdapter.getCount()) { - viewPager.setCurrentItem(detailsIndex, false); - } - - // notify the adapter that the data has changed - viewPagerAdapter.notifyDataSetChanged(); - - // notify the indicator that the data has changed - titleIndicator.notifyDataSetChanged(); + reinitializeViewPager(); // rendering done! remove progress popup if any there + invalidateOptionsMenuCompatible(); progress.dismiss(); } @@ -752,6 +688,7 @@ public class CacheDetailActivity extends AbstractActivity { if (StringUtils.isBlank(geocode) && StringUtils.isBlank(guid)) { showToast(res.getString(R.string.err_detail_cache_forgot)); + progress.dismiss(); finish(); return; } @@ -762,7 +699,7 @@ public class CacheDetailActivity extends AbstractActivity { @Override public void run() { - search = cgCache.searchByGeocode(geocode, StringUtils.isBlank(geocode) ? guid : null, 0, false, handler); + search = Geocache.searchByGeocode(geocode, StringUtils.isBlank(geocode) ? guid : null, 0, false, handler); handler.sendMessage(Message.obtain()); } } @@ -842,14 +779,14 @@ public class CacheDetailActivity extends AbstractActivity { } /** - * Tries to navigate to the {@link cgCache} of this activity. + * Tries to navigate to the {@link Geocache} of this activity. */ private void startDefaultNavigation() { NavigationAppFactory.startDefaultNavigationApplication(1, this, cache); } /** - * Tries to navigate to the {@link cgCache} of this activity. + * Tries to navigate to the {@link Geocache} of this activity. */ private void startDefaultNavigation2() { NavigationAppFactory.startDefaultNavigationApplication(2, this, cache); @@ -918,7 +855,8 @@ public class CacheDetailActivity extends AbstractActivity { 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_open_browser), + res.getString(R.string.user_menu_send_message) }; AlertDialog.Builder builder = new AlertDialog.Builder(this); @@ -934,7 +872,10 @@ public class CacheDetailActivity extends AbstractActivity { cgeocaches.startActivityUserName(CacheDetailActivity.this, name.toString()); return; case 2: - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.geocaching.com/profile/?u=" + URLEncoder.encode(name.toString())))); + 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; @@ -949,7 +890,7 @@ public class CacheDetailActivity extends AbstractActivity { if (imagesList != null) { return; } - PageViewCreator creator = viewCreators.get(Page.IMAGES); + PageViewCreator creator = getViewCreator(Page.IMAGES); if (creator == null) { return; } @@ -957,139 +898,17 @@ public class CacheDetailActivity extends AbstractActivity { if (imageView == null) { return; } - imagesList = new ImagesList(CacheDetailActivity.this, cache.getGeocode()); + imagesList = new ImagesList(this, cache.getGeocode()); imagesList.loadImages(imageView, cache.getImages(), ImageType.AllImages, false); } public static void startActivity(final Context context, final String geocode) { final Intent detailIntent = new Intent(context, CacheDetailActivity.class); - detailIntent.putExtra("geocode", geocode.toUpperCase()); - context.startActivity(detailIntent); - } - - public static void startActivity(final Context context, final String geocode, final int page) { - final Intent detailIntent = new Intent(context, CacheDetailActivity.class); - detailIntent.putExtra("geocode", geocode.toUpperCase()); - detailIntent.putExtra(EXTRAS_PAGE, page); + detailIntent.putExtra(Intents.EXTRA_GEOCODE, geocode); context.startActivity(detailIntent); } /** - * The ViewPagerAdapter for scrolling through pages of the CacheDetailActivity. - */ - private class ViewPagerAdapter extends PagerAdapter implements TitleProvider { - - @Override - public void destroyItem(View container, int position, Object object) { - ((ViewPager) container).removeView((View) object); - } - - @Override - public void finishUpdate(View container) { - } - - @Override - public int getCount() { - return pageOrder.size(); - } - - @Override - public Object instantiateItem(View container, int position) { - final Page page = pageOrder.get(position); - - PageViewCreator creator = viewCreators.get(page); - - if (null == creator && null != page) { - // The creator is not instantiated yet, let's do it. - switch (page) { - case DETAILS: - creator = new DetailsViewCreator(); - break; - - case DESCRIPTION: - creator = new DescriptionViewCreator(); - break; - - case LOGS: - creator = new LogsViewCreator(true); - break; - - case LOGSFRIENDS: - creator = new LogsViewCreator(false); - break; - - case WAYPOINTS: - creator = new WaypointsViewCreator(); - break; - - case INVENTORY: - creator = new InventoryViewCreator(); - break; - - case IMAGES: - creator = new ImagesViewCreator(); - break; - } - viewCreators.put(page, creator); - } - - View view = null; - - try { - if (null != creator) { - // Result from getView() is maybe cached, but it should be valid because the - // creator should be informed about data-changes with notifyDataSetChanged() - view = creator.getView(); - ((ViewPager) container).addView(view, 0); - } - } catch (Exception e) { - Log.e("ViewPagerAdapter.instantiateItem ", e); - } - - return view; - } - - @Override - public boolean isViewFromObject(View view, Object object) { - return view == object; - } - - @Override - public void restoreState(Parcelable arg0, ClassLoader arg1) { - } - - @Override - public Parcelable saveState() { - return null; - } - - @Override - public void startUpdate(View arg0) { - } - - @Override - public int getItemPosition(Object object) { - // We are doing the caching. So pretend that the view is gone. - // The ViewPager will get it back in instantiateItem() - return POSITION_NONE; - } - - @Override - public String getTitle(int position) { - final Page page = pageOrder.get(position); - if (null == page) { - return ""; - } - // show number of waypoints directly in waypoint title - if (page == Page.WAYPOINTS) { - final int waypointCount = cache.getWaypoints().size(); - return res.getQuantityString(R.plurals.waypoints, waypointCount, waypointCount); - } - return res.getString(page.titleStringId); - } - } - - /** * Enum of all possible pages with methods to get the view and a title. */ public enum Page { @@ -1103,7 +922,7 @@ public class CacheDetailActivity extends AbstractActivity { final private int titleStringId; - private Page(final int titleStringId) { + Page(final int titleStringId) { this.titleStringId = titleStringId; } } @@ -1113,9 +932,7 @@ public class CacheDetailActivity extends AbstractActivity { private ViewGroup attributeDescriptionsLayout; // layout for attribute descriptions private boolean attributesShowAsIcons = true; // default: show icons /** - * True, if the cache was imported with an older version of c:geo. - * These older versions parsed the attribute description from the tooltip in the web - * page and put them into the DB. No icons can be matched for these. + * If the cache is from a non GC source, it might be without icons. Disable switching in those cases. */ private boolean noAttributeIconsFound = false; private int attributeBoxMaxWidth; @@ -1125,8 +942,7 @@ public class CacheDetailActivity extends AbstractActivity { attributeBox.removeAllViews(); // maximum width for attribute icons is screen width - paddings of parents - attributeBoxMaxWidth = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)) - .getDefaultDisplay().getWidth(); + attributeBoxMaxWidth = Compatibility.getDisplayWidth(); ViewParent child = attributeBox; do { if (child instanceof View) { @@ -1233,8 +1049,8 @@ public class CacheDetailActivity extends AbstractActivity { } final boolean strikethru = !CacheAttribute.isEnabled(attributeName); - final CacheAttribute attrib = CacheAttribute.getByGcRawName(CacheAttribute.trimAttributeName(attributeName)); - if (attrib != CacheAttribute.UNKNOWN) { + final CacheAttribute attrib = CacheAttribute.getByRawName(CacheAttribute.trimAttributeName(attributeName)); + if (attrib != null) { noAttributeIconsFound = false; Drawable d = res.getDrawable(attrib.drawableId); iv.setImageDrawable(d); @@ -1248,7 +1064,7 @@ public class CacheDetailActivity extends AbstractActivity { fl.addView(strikethruImage); } } else { - Drawable d = res.getDrawable(R.drawable.attribute_icon_not_found); + Drawable d = res.getDrawable(R.drawable.attribute_unknown); iv.setImageDrawable(d); } @@ -1275,56 +1091,27 @@ public class CacheDetailActivity extends AbstractActivity { for (String attributeName : cache.getAttributes()) { final boolean enabled = CacheAttribute.isEnabled(attributeName); // search for a translation of the attribute - CacheAttribute attrib = CacheAttribute.getByGcRawName(CacheAttribute.trimAttributeName(attributeName)); - if (attrib != CacheAttribute.UNKNOWN) { - attributeName = attrib.getL10n(enabled); + CacheAttribute attrib = CacheAttribute.getByRawName(CacheAttribute.trimAttributeName(attributeName)); + if (attrib == null) { + attrib = CacheAttribute.UNKNOWN; } + attributeName = attrib.getL10n(enabled); if (buffer.length() > 0) { buffer.append('\n'); } buffer.append(attributeName); } - if (noAttributeIconsFound) { - buffer.append("\n\n").append(res.getString(R.string.cache_attributes_no_icons)); - } - attribView.setText(buffer); return descriptions; } } - private interface PageViewCreator { - /** - * Returns a validated view. - * - * @return - */ - public View getDispatchedView(); - - /** - * Returns a (maybe cached) view. - * - * @return - */ - public View getView(); - - /** - * Handles changed data-sets. - */ - public void notifyDataSetChanged(); - } - /** * Creator for details-view. */ - private class DetailsViewCreator implements PageViewCreator { - /** - * The main view for this creator - */ - private ScrollView view; - + private class DetailsViewCreator extends AbstractCachingPageViewCreator<ScrollView> { /** * Reference to the details list, so that the helper-method can access it without an additional argument */ @@ -1336,22 +1123,7 @@ public class CacheDetailActivity extends AbstractActivity { private Thread watchlistThread; @Override - public void notifyDataSetChanged() { - // There is a lot of data in this view, let's update everything - view = null; - } - - @Override - public View getView() { - if (view == null) { - view = (ScrollView) getDispatchedView(); - } - - return view; - } - - @Override - public View getDispatchedView() { + public ScrollView getDispatchedView() { if (cache == null) { // something is really wrong return null; @@ -1376,10 +1148,10 @@ public class CacheDetailActivity extends AbstractActivity { span.setSpan(new ForegroundColorSpan(res.getColor(R.color.archived_cache_color)), 0, span.toString().length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } - details.add(R.string.cache_name, span); + registerForContextMenu(details.add(R.string.cache_name, span)); details.add(R.string.cache_type, cache.getType().getL10n()); details.addSize(cache); - details.add(R.string.cache_geocode, cache.getGeocode().toUpperCase()); + registerForContextMenu(details.add(R.string.cache_geocode, cache.getGeocode())); details.addCacheState(cache); details.addDistance(cache, cacheDistanceView); @@ -1389,8 +1161,10 @@ public class CacheDetailActivity extends AbstractActivity { details.addTerrain(cache); details.addRating(cache); - // favourite count - details.add(R.string.cache_favourite, cache.getFavoritePoints() + "×"); + // favorite count + if (cache.getFavoritePoints() > 0) { + details.add(R.string.cache_favourite, cache.getFavoritePoints() + "×"); + } // own rating if (cache.getMyVote() > 0) { @@ -1429,32 +1203,32 @@ public class CacheDetailActivity extends AbstractActivity { 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; + 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 = (TextView) view.findViewById(R.id.value); + valueView.setText(cache.getCoords().format(availableFormats[position])); + } + }); registerForContextMenu(valueView); } // cache attributes - if (cache.getAttributes().isNotEmpty()) { + if (!cache.getAttributes().isEmpty()) { new AttributeViewBuilder().fillView((LinearLayout) view.findViewById(R.id.attributes_innerbox)); view.findViewById(R.id.attributes_box).setVisibility(View.VISIBLE); } - updateOfflineBox(); + updateOfflineBox(view, cache, res, new RefreshCacheClickListener(), new DropCacheClickListener(), new StoreCacheClickListener()); // watchlist Button buttonWatchlistAdd = (Button) view.findViewById(R.id.add_to_watchlist); @@ -1466,23 +1240,15 @@ public class CacheDetailActivity extends AbstractActivity { // favorite points Button buttonFavPointAdd = (Button) view.findViewById(R.id.add_to_favpoint); Button buttonFavPointRemove = (Button) view.findViewById(R.id.remove_from_favpoint); - buttonFavPointAdd.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - GCConnector.addToFavorites(cache); - updateFavPointBox(); - } - }); - buttonFavPointRemove.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - GCConnector.removeFromFavorites(cache); - updateFavPointBox(); - } - }); - + buttonFavPointAdd.setOnClickListener(new FavoriteAddClickListener()); + buttonFavPointRemove.setOnClickListener(new FavoriteRemoveClickListener()); updateFavPointBox(); + // list + Button buttonChangeList = (Button) view.findViewById(R.id.change_list); + buttonChangeList.setOnClickListener(new ChangeListClickListener()); + updateListBox(); + // data license IConnector connector = ConnectorFactory.getConnector(cache); if (connector != null) { @@ -1552,6 +1318,21 @@ public class CacheDetailActivity extends AbstractActivity { return; } + if (Settings.getChooseList()) { + // let user select list to store cache in + new StoredList.UserInterface(CacheDetailActivity.this).promptForListSelection(R.string.list_title, + new RunnableWithArgument<Integer>() { + @Override + public void run(final Integer selectedListId) { + storeCache(selectedListId); + } + }, true, StoredList.TEMPORARY_LIST_ID); + } else { + storeCache(StoredList.TEMPORARY_LIST_ID); + } + } + + protected void storeCache(int listId) { final StoreCacheHandler storeCacheHandler = new StoreCacheHandler(); progress.show(CacheDetailActivity.this, res.getString(R.string.cache_dialog_offline_save_title), res.getString(R.string.cache_dialog_offline_save_message), true, storeCacheHandler.cancelMessage()); @@ -1560,7 +1341,7 @@ public class CacheDetailActivity extends AbstractActivity { storeThread.interrupt(); } - storeThread = new StoreCacheThread(storeCacheHandler); + storeThread = new StoreCacheThread(listId, storeCacheHandler); storeThread.start(); } } @@ -1587,15 +1368,17 @@ public class CacheDetailActivity extends AbstractActivity { } private class StoreCacheThread extends Thread { + final private int listId; final private CancellableHandler handler; - public StoreCacheThread(final CancellableHandler handler) { + public StoreCacheThread(final int listId, final CancellableHandler handler) { + this.listId = listId; this.handler = handler; } @Override public void run() { - cache.store(handler); + cache.store(listId, handler); } } @@ -1715,6 +1498,102 @@ public class CacheDetailActivity extends AbstractActivity { } } + /** Thread to add this cache to the favourite list of the user */ + private class FavoriteAddThread extends Thread { + private final Handler handler; + + public FavoriteAddThread(Handler handler) { + this.handler = handler; + } + + @Override + public void run() { + handler.sendEmptyMessage(GCConnector.addToFavorites(cache) ? 1 : -1); + } + } + + /** Thread to remove this cache to the favourite list of the user */ + private class FavoriteRemoveThread extends Thread { + private final Handler handler; + + public FavoriteRemoveThread(Handler handler) { + this.handler = handler; + } + + @Override + public void run() { + handler.sendEmptyMessage(GCConnector.removeFromFavorites(cache) ? 1 : -1); + } + } + + private class FavoriteUpdateHandler extends Handler { + @Override + public void handleMessage(Message msg) { + progress.dismiss(); + if (msg.what == -1) { + showToast(res.getString(R.string.err_favorite_failed)); + } else { + CacheDetailActivity.this.notifyDataSetChanged(); // reload cache details + } + } + } + + /** + * Listener for "add to favourites" 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, + new FavoriteAddThread(new FavoriteUpdateHandler())); + } + } + + /** + * Listener for "remove from favourites" 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, + new FavoriteRemoveThread(new FavoriteUpdateHandler())); + } + } + + /** + * Listener for "change list" button + */ + private class ChangeListClickListener implements View.OnClickListener { + @Override + public void onClick(View view) { + new StoredList.UserInterface(CacheDetailActivity.this).promptForListSelection(R.string.list_title, + new RunnableWithArgument<Integer>() { + @Override + public void run(final Integer selectedListId) { + switchListById(selectedListId); + } + }, true, cache.getListId()); + } + } + + /** + * move cache to another list + * + * @param listId + * the ID of the list + */ + public void switchListById(int listId) { + if (listId < 0) { + return; + } + + Settings.saveLastList(listId); + cgData.moveToList(cache, listId); + updateListBox(); + } + /** * shows/hides buttons, sets text in watchlist box */ @@ -1729,7 +1608,7 @@ public class CacheDetailActivity extends AbstractActivity { Button buttonRemove = (Button) view.findViewById(R.id.remove_from_watchlist); TextView text = (TextView) view.findViewById(R.id.watchlist_text); - if (cache.isOnWatchlist() || cache.isOwn()) { + if (cache.isOnWatchlist() || cache.isOwner()) { buttonAdd.setVisibility(View.GONE); buttonRemove.setVisibility(View.VISIBLE); text.setText(R.string.cache_watchlist_on); @@ -1740,7 +1619,7 @@ public class CacheDetailActivity extends AbstractActivity { } // the owner of a cache has it always on his watchlist. Adding causes an error - if (cache.isOwn()) { + if (cache.isOwner()) { buttonAdd.setEnabled(false); buttonAdd.setVisibility(View.GONE); buttonRemove.setEnabled(false); @@ -1756,7 +1635,7 @@ public class CacheDetailActivity extends AbstractActivity { LinearLayout layout = (LinearLayout) view.findViewById(R.id.favpoint_box); boolean supportsFavoritePoints = cache.supportsFavoritePoints(); layout.setVisibility(supportsFavoritePoints ? View.VISIBLE : View.GONE); - if (!supportsFavoritePoints || cache.isOwn() || !Settings.isPremiumMember()) { + if (!supportsFavoritePoints || cache.isOwner() || !Settings.isPremiumMember()) { return; } Button buttonAdd = (Button) view.findViewById(R.id.add_to_favpoint); @@ -1783,6 +1662,31 @@ public class CacheDetailActivity extends AbstractActivity { } /** + * shows/hides/updates list box + */ + private void updateListBox() { + 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()); + if (list != null) { + text.setText(res.getString(R.string.cache_list_text) + " " + list.title); + } else { + // this should not happen + text.setText(R.string.cache_list_unknown); + } + } else { + // hide box + box.setVisibility(View.GONE); + } + } + + /** * Handler, called when watchlist add or remove is done */ private class WatchlistHandler extends Handler { @@ -1793,51 +1697,11 @@ public class CacheDetailActivity extends AbstractActivity { if (msg.what == -1) { showToast(res.getString(R.string.err_watchlist_failed)); } else { - updateWatchlistBox(); + CacheDetailActivity.this.notifyDataSetChanged(); // reload cache details } } } - private void updateOfflineBox() { - // offline use - final TextView offlineText = (TextView) view.findViewById(R.id.offline_text); - final Button offlineRefresh = (Button) view.findViewById(R.id.offline_refresh); - final Button offlineStore = (Button) view.findViewById(R.id.offline_store); - - if (cache.isOffline()) { - long diff = (System.currentTimeMillis() / (60 * 1000)) - (cache.getDetailedUpdate() / (60 * 1000)); // minutes - - String ago; - if (diff < 15) { - ago = res.getString(R.string.cache_offline_time_mins_few); - } else if (diff < 50) { - ago = res.getString(R.string.cache_offline_time_about) + " " + diff + " " + res.getString(R.string.cache_offline_time_mins); - } else if (diff < 90) { - ago = res.getString(R.string.cache_offline_time_about) + " " + res.getString(R.string.cache_offline_time_hour); - } else if (diff < (48 * 60)) { - ago = res.getString(R.string.cache_offline_time_about) + " " + (diff / 60) + " " + res.getString(R.string.cache_offline_time_hours); - } else { - ago = res.getString(R.string.cache_offline_time_about) + " " + (diff / (24 * 60)) + " " + res.getString(R.string.cache_offline_time_days); - } - - offlineText.setText(res.getString(R.string.cache_offline_stored) + "\n" + ago); - offlineRefresh.setOnClickListener(new RefreshCacheClickListener()); - - offlineStore.setText(res.getString(R.string.cache_offline_drop)); - offlineStore.setClickable(true); - offlineStore.setOnClickListener(new DropCacheClickListener()); - } else { - offlineText.setText(res.getString(R.string.cache_offline_not_ready)); - offlineRefresh.setOnClickListener(new RefreshCacheClickListener()); - - offlineStore.setText(res.getString(R.string.cache_offline_store)); - offlineStore.setClickable(true); - offlineStore.setOnClickListener(new StoreCacheClickListener()); - } - offlineRefresh.setVisibility(cache.supportsRefresh() ? View.VISIBLE : View.GONE); - offlineRefresh.setClickable(true); - } - private class PreviewMapTask extends AsyncTask<Void, Void, BitmapDrawable> { @Override protected BitmapDrawable doInBackground(Void... parameters) { @@ -1860,7 +1724,7 @@ public class CacheDetailActivity extends AbstractActivity { } } - private Bitmap decode(final cgCache cache) { + private Bitmap decode(final Geocache cache) { return StaticMapsProvider.getPreviewMap(cache.getGeocode()); } @@ -1870,38 +1734,26 @@ public class CacheDetailActivity extends AbstractActivity { return; } - final Bitmap bitmap = image.getBitmap(); - if (bitmap == null || bitmap.getWidth() <= 10) { - return; - } + try { + final Bitmap bitmap = image.getBitmap(); + if (bitmap == null || bitmap.getWidth() <= 10) { + return; + } - ((ImageView) view.findViewById(R.id.map_preview)).setImageDrawable(image); - view.findViewById(R.id.map_preview_box).setVisibility(View.VISIBLE); + ((ImageView) view.findViewById(R.id.map_preview)).setImageDrawable(image); + view.findViewById(R.id.map_preview_box).setVisibility(View.VISIBLE); + } catch (Exception e) { + Log.e("CacheDetailActivity.PreviewMapTask", e); + } } } } - private class DescriptionViewCreator implements PageViewCreator { - - private ScrollView view; - - @Override - public void notifyDataSetChanged() { - view = null; - } + private class DescriptionViewCreator extends AbstractCachingPageViewCreator<ScrollView> { @Override - public View getView() { - if (view == null) { - view = (ScrollView) getDispatchedView(); - } - - return view; - } - - @Override - public View getDispatchedView() { + public ScrollView getDispatchedView() { if (cache == null) { // something is really wrong return null; @@ -1948,7 +1800,7 @@ public class CacheDetailActivity extends AbstractActivity { public void update(CharSequence editorText) { cache.setPersonalNote(editorText.toString()); setPersonalNote(personalNoteView); - app.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); + cgData.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); } }); editor.show(); @@ -2032,6 +1884,21 @@ public class CacheDetailActivity extends AbstractActivity { } + private static class HtmlImageCounter implements Html.ImageGetter { + + private int imageCount = 0; + + @Override + public Drawable getDrawable(String url) { + imageCount++; + return null; + } + + public int getImageCount() { + return imageCount; + } + } + /** * Loads the description in background. <br /> * <br /> @@ -2048,20 +1915,6 @@ public class CacheDetailActivity extends AbstractActivity { private String descriptionString; private Spanned description; - private class HtmlImageCounter implements Html.ImageGetter { - - private int imageCount = 0; - - @Override - public Drawable getDrawable(String url) { - imageCount++; - return null; - } - - public int getImageCount() { - return imageCount; - } - } @Override protected Void doInBackground(Object... params) { @@ -2086,7 +1939,9 @@ public class CacheDetailActivity extends AbstractActivity { // sometimes technically incorrect. if (unknownTagsHandler.isProblematicDetected() && descriptionView != null) { final int startPos = description.length(); - ((Editable) description).append("\n\n").append(res.getString(R.string.cache_description_table_note)); + final IConnector connector = ConnectorFactory.getConnector(cache); + 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); publishProgress(); } @@ -2135,7 +1990,7 @@ public class CacheDetailActivity extends AbstractActivity { backcolor = color.darker_gray; } else { - Matcher matcher = DARK_COLOR_PATTERN.matcher(text); + MatcherWrapper matcher = new MatcherWrapper(DARK_COLOR_PATTERN, text); if (matcher.find()) { backcolor = color.darker_gray; } @@ -2144,31 +1999,15 @@ public class CacheDetailActivity extends AbstractActivity { } } - private class LogsViewCreator implements PageViewCreator { - private ListView view; - private boolean allLogs; + private class LogsViewCreator extends AbstractCachingPageViewCreator<ListView> { + private final boolean allLogs; LogsViewCreator(boolean allLogs) { - super(); this.allLogs = allLogs; } @Override - public void notifyDataSetChanged() { - view = null; - } - - @Override - public View getView() { - if (view == null) { - view = (ListView) getDispatchedView(); - } - - return view; - } - - @Override - public View getDispatchedView() { + public ListView getDispatchedView() { if (cache == null) { // something is really wrong return null; @@ -2187,7 +2026,7 @@ public class CacheDetailActivity extends AbstractActivity { } } - if (sortedLogCounts.size() > 0) { + if (!sortedLogCounts.isEmpty()) { // sort the log counts by type id ascending. that way the FOUND, DNF log types are the first and most visible ones Collections.sort(sortedLogCounts, new Comparator<Entry<LogType, Integer>>() { @@ -2208,7 +2047,7 @@ public class CacheDetailActivity extends AbstractActivity { } } - final List<LogEntry> logs = allLogs ? cache.getLogs().asList() : cache.getFriendsLogs(); + 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(); final DecryptTextClickListener decryptTextClickListener = new DecryptTextClickListener(); @@ -2241,7 +2080,7 @@ public class CacheDetailActivity extends AbstractActivity { holder.count.setVisibility(View.VISIBLE); if (log.found == -1) { holder.count.setVisibility(View.GONE); - } else { + } else { holder.count.setText(res.getQuantityString(R.plurals.cache_counts, log.found, log.found)); } @@ -2249,7 +2088,15 @@ public class CacheDetailActivity extends AbstractActivity { String logText = log.log; if (BaseUtils.containsHtml(logText)) { logText = log.getDisplayText(); - holder.text.setText(Html.fromHtml(logText, new HtmlImage(cache.getGeocode(), false, cache.getListId(), false), null), TextView.BufferType.SPANNABLE); + // Fast preview: parse only HTML without loading any images + HtmlImageCounter imageCounter = new HtmlImageCounter(); + final UnknownTagsHandler unknownTagsHandler = new UnknownTagsHandler(); + holder.text.setText(Html.fromHtml(logText, imageCounter, unknownTagsHandler)); + 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 }); + } } else { holder.text.setText(logText); @@ -2262,7 +2109,7 @@ public class CacheDetailActivity extends AbstractActivity { holder.images.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - ImagesActivity.startActivityLogImages(CacheDetailActivity.this, cache.getGeocode(), new ArrayList<cgImage>(log.getLogImages())); + ImagesActivity.startActivityLogImages(CacheDetailActivity.this, cache.getGeocode(), new ArrayList<Image>(log.getLogImages())); } }); } else { @@ -2270,23 +2117,12 @@ public class CacheDetailActivity extends AbstractActivity { } // colored marker - holder.statusMarker.setVisibility(View.VISIBLE); - if (log.type == LogType.FOUND_IT - || log.type == LogType.WEBCAM_PHOTO_TAKEN - || log.type == LogType.ATTENDED) { - holder.statusMarker.setImageResource(R.drawable.mark_green); - } else if (log.type == LogType.PUBLISH_LISTING - || log.type == LogType.ENABLE_LISTING - || log.type == LogType.OWNER_MAINTENANCE) { - holder.statusMarker.setImageResource(R.drawable.mark_green_more); - } else if (log.type == LogType.DIDNT_FIND_IT - || log.type == LogType.NEEDS_MAINTENANCE - || log.type == LogType.NEEDS_ARCHIVE) { - holder.statusMarker.setImageResource(R.drawable.mark_red); - } else if (log.type == LogType.TEMP_DISABLE_LISTING - || log.type == LogType.ARCHIVE) { - holder.statusMarker.setImageResource(R.drawable.mark_red_more); - } else { + int marker = log.type.markerId; + if (marker != 0) { + holder.statusMarker.setVisibility(View.VISIBLE); + holder.statusMarker.setImageResource(marker); + } + else { holder.statusMarker.setVisibility(View.GONE); } @@ -2305,6 +2141,27 @@ public class CacheDetailActivity extends AbstractActivity { return view; } + /** Loads the Log Images outside the ui thread. */ + + private class LogImageLoader extends AsyncTask<String, Progress, Spanned> { + private LogViewHolder holder; + + public LogImageLoader(LogViewHolder holder) { + this.holder = holder; + } + + @Override + protected Spanned doInBackground(String... logtext) { + return Html.fromHtml(logtext[0], new HtmlImage(cache.getGeocode(), false, cache.getListId(), false), null); //, TextView.BufferType.SPANNABLE) + } + + @Override + protected void onPostExecute(Spanned result) { + holder.text.setText(result); + } + + } + private class LogViewHolder { final TextView date; final TextView type; @@ -2326,26 +2183,10 @@ public class CacheDetailActivity extends AbstractActivity { } } - private class WaypointsViewCreator implements PageViewCreator { - - private ScrollView view; - - @Override - public void notifyDataSetChanged() { - view = null; - } - - @Override - public View getView() { - if (view == null) { - view = (ScrollView) getDispatchedView(); - } - - return view; - } + private class WaypointsViewCreator extends AbstractCachingPageViewCreator<ScrollView> { @Override - public View getDispatchedView() { + public ScrollView getDispatchedView() { if (cache == null) { // something is really wrong return null; @@ -2356,10 +2197,10 @@ public class CacheDetailActivity extends AbstractActivity { final LinearLayout waypoints = (LinearLayout) view.findViewById(R.id.waypoints); // sort waypoints: PP, Sx, FI, OWN - final List<cgWaypoint> sortedWaypoints = new ArrayList<cgWaypoint>(cache.getWaypoints()); + final List<Waypoint> sortedWaypoints = new ArrayList<Waypoint>(cache.getWaypoints()); Collections.sort(sortedWaypoints); - for (final cgWaypoint wpt : sortedWaypoints) { + for (final Waypoint wpt : sortedWaypoints) { final LinearLayout waypointView = (LinearLayout) getLayoutInflater().inflate(R.layout.waypoint_item, null); // coordinates @@ -2388,6 +2229,16 @@ public class CacheDetailActivity extends AbstractActivity { } wpt.setIcon(res, nameView); + // visited + if (wpt.isVisited()) { + 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! + nameView.setTextColor(a.data); + } + } + // note if (StringUtils.isNotBlank(wpt.getNote())) { final TextView noteView = (TextView) waypointView.findViewById(R.id.note); @@ -2441,26 +2292,10 @@ public class CacheDetailActivity extends AbstractActivity { } } - private class InventoryViewCreator implements PageViewCreator { - - private ListView view; - - @Override - public void notifyDataSetChanged() { - view = null; - } - - @Override - public View getView() { - if (view == null) { - view = (ListView) getDispatchedView(); - } - - return view; - } + private class InventoryViewCreator extends AbstractCachingPageViewCreator<ListView> { @Override - public View getDispatchedView() { + public ListView getDispatchedView() { if (cache == null) { // something is really wrong return null; @@ -2470,14 +2305,14 @@ public class CacheDetailActivity extends AbstractActivity { // TODO: fix layout, then switch back to Android-resource and delete copied one // this copy is modified to respect the text color - view.setAdapter(new ArrayAdapter<cgTrackable>(CacheDetailActivity.this, R.layout.simple_list_item_1, cache.getInventory())); + view.setAdapter(new ArrayAdapter<Trackable>(CacheDetailActivity.this, R.layout.simple_list_item_1, cache.getInventory())); view.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) { Object selection = arg0.getItemAtPosition(arg2); - if (selection instanceof cgTrackable) { - cgTrackable trackable = (cgTrackable) selection; - cgeotrackable.startActivity(CacheDetailActivity.this, trackable.getGuid(), trackable.getGeocode(), trackable.getName()); + if (selection instanceof Trackable) { + Trackable trackable = (Trackable) selection; + TrackableActivity.startActivity(CacheDetailActivity.this, trackable.getGuid(), trackable.getGeocode(), trackable.getName()); } } }); @@ -2486,23 +2321,7 @@ public class CacheDetailActivity extends AbstractActivity { } } - private class ImagesViewCreator implements PageViewCreator { - - private View view; - - @Override - public void notifyDataSetChanged() { - view = null; - } - - @Override - public View getView() { - if (view == null) { - view = getDispatchedView(); - } - - return view; - } + private class ImagesViewCreator extends AbstractCachingPageViewCreator<View> { @Override public View getDispatchedView() { @@ -2511,7 +2330,7 @@ public class CacheDetailActivity extends AbstractActivity { } view = getLayoutInflater().inflate(R.layout.caches_images, null); - if (imagesList == null && viewPager.getCurrentItem() == pageOrder.indexOf(Page.IMAGES)) { + if (imagesList == null && isCurrentPage(Page.IMAGES)) { loadCacheImages(); } return view; @@ -2520,15 +2339,240 @@ public class CacheDetailActivity extends AbstractActivity { public static void startActivity(final Context context, final String geocode, final String cacheName) { final Intent cachesIntent = new Intent(context, CacheDetailActivity.class); - cachesIntent.putExtra("geocode", geocode); - cachesIntent.putExtra("name", cacheName); + cachesIntent.putExtra(Intents.EXTRA_GEOCODE, geocode); + cachesIntent.putExtra(Intents.EXTRA_NAME, cacheName); context.startActivity(cachesIntent); } public static void startActivityGuid(final Context context, final String guid, final String cacheName) { final Intent cacheIntent = new Intent(context, CacheDetailActivity.class); - cacheIntent.putExtra("guid", guid); - cacheIntent.putExtra("name", cacheName); + cacheIntent.putExtra(Intents.EXTRA_GUID, guid); + cacheIntent.putExtra(Intents.EXTRA_NAME, cacheName); context.startActivity(cacheIntent); } + + /** + * A dialog to allow the user to select reseting coordinates local/remote/both. + */ + private AlertDialog createResetCacheCoordinatesDialog(final Geocache cache, final Waypoint wpt) { + + 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) }; + builder.setSingleChoiceItems(items, 0, new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, final int which) { + dialog.dismiss(); + final ProgressDialog progressDialog = ProgressDialog.show(CacheDetailActivity.this, getString(R.string.cache), getString(R.string.waypoint_reset), true); + final HandlerResetCoordinates handler = new HandlerResetCoordinates(CacheDetailActivity.this, progressDialog, which == 1); + new ResetCoordsThread(cache, handler, wpt, which == 0 || which == 1, which == 1, progressDialog).start(); + } + }); + return builder.create(); + } + + private static class HandlerResetCoordinates extends WeakReferenceHandler<CacheDetailActivity> { + private boolean remoteFinished = false; + private boolean localFinished = false; + private final ProgressDialog progressDialog; + private final boolean resetRemote; + + protected HandlerResetCoordinates(CacheDetailActivity activity, ProgressDialog progressDialog, boolean resetRemote) { + super(activity); + this.progressDialog = progressDialog; + this.resetRemote = resetRemote; + } + + @Override + public void handleMessage(Message msg) { + if (msg.what == ResetCoordsThread.LOCAL) { + localFinished = true; + } else { + remoteFinished = true; + } + + if (localFinished && (remoteFinished || !resetRemote)) { + progressDialog.dismiss(); + final CacheDetailActivity activity = getActivity(); + if (activity != null) { + activity.notifyDataSetChanged(); + } + } + } + + } + + private class ResetCoordsThread extends Thread { + + private final Geocache cache; + private final Handler handler; + private final boolean local; + private final boolean remote; + private final Waypoint wpt; + private ProgressDialog progress; + public static final int LOCAL = 0; + public static final int ON_WEBSITE = 1; + + public ResetCoordsThread(Geocache cache, Handler handler, final Waypoint wpt, boolean local, boolean remote, final ProgressDialog progress) { + this.cache = cache; + this.handler = handler; + this.local = local; + this.remote = remote; + this.wpt = wpt; + this.progress = progress; + } + + @Override + public void run() { + + if (local) { + runOnUiThread(new Runnable() { + @Override + public void run() { + progress.setMessage(res.getString(R.string.waypoint_reset_cache_coords)); + } + }); + cache.setCoords(wpt.getCoords()); + cache.setUserModifiedCoords(false); + cache.deleteWaypointForce(wpt); + cgData.saveChangedCache(cache); + handler.sendEmptyMessage(LOCAL); + } + + IConnector con = ConnectorFactory.getConnector(cache); + if (remote && con.supportsOwnCoordinates()) { + runOnUiThread(new Runnable() { + @Override + public void run() { + progress.setMessage(res.getString(R.string.waypoint_coordinates_being_reset_on_website)); + } + }); + + final boolean result = con.deleteModifiedCoordinates(cache); + + runOnUiThread(new Runnable() { + + @Override + public void run() { + if (result) { + showToast(getString(R.string.waypoint_coordinates_has_been_reset_on_website)); + } else { + showToast(getString(R.string.waypoint_coordinates_upload_error)); + } + handler.sendEmptyMessage(ON_WEBSITE); + notifyDataSetChanged(); + } + + }); + + } + } + } + + @Override + protected String getTitle(Page page) { + // show number of waypoints directly in waypoint title + if (page == Page.WAYPOINTS) { + final int waypointCount = cache.getWaypoints().size(); + return res.getQuantityString(R.plurals.waypoints, waypointCount, waypointCount); + } + return res.getString(page.titleStringId); + } + + @Override + protected Pair<List<? extends Page>, Integer> getOrderedPages() { + final ArrayList<Page> pages = new ArrayList<Page>(); + pages.add(Page.WAYPOINTS); + pages.add(Page.DETAILS); + final int detailsIndex = pages.size() - 1; + pages.add(Page.DESCRIPTION); + if (!cache.getLogs().isEmpty()) { + pages.add(Page.LOGS); + } + if (CollectionUtils.isNotEmpty(cache.getFriendsLogs())) { + pages.add(Page.LOGSFRIENDS); + } + if (CollectionUtils.isNotEmpty(cache.getInventory())) { + pages.add(Page.INVENTORY); + } + if (CollectionUtils.isNotEmpty(cache.getImages())) { + pages.add(Page.IMAGES); + } + return new ImmutablePair<List<? extends Page>, Integer>(pages, detailsIndex); + } + + @Override + protected AbstractViewPagerActivity.PageViewCreator createViewCreator(Page page) { + switch (page) { + case DETAILS: + return new DetailsViewCreator(); + + case DESCRIPTION: + return new DescriptionViewCreator(); + + case LOGS: + return new LogsViewCreator(true); + + case LOGSFRIENDS: + return new LogsViewCreator(false); + + case WAYPOINTS: + return new WaypointsViewCreator(); + + case INVENTORY: + return new InventoryViewCreator(); + + case IMAGES: + return new ImagesViewCreator(); + + default: + throw new IllegalArgumentException(); + } + } + + static void updateOfflineBox(final View view, final Geocache cache, final Resources res, + final OnClickListener refreshCacheClickListener, + final OnClickListener dropCacheClickListener, + final OnClickListener storeCacheClickListener) { + // offline use + final TextView offlineText = (TextView) view.findViewById(R.id.offline_text); + final Button offlineRefresh = (Button) view.findViewById(R.id.offline_refresh); + final Button offlineStore = (Button) view.findViewById(R.id.offline_store); + + if (cache.isOffline()) { + long diff = (System.currentTimeMillis() / (60 * 1000)) - (cache.getDetailedUpdate() / (60 * 1000)); // minutes + + String ago; + if (diff < 15) { + ago = res.getString(R.string.cache_offline_time_mins_few); + } else if (diff < 50) { + ago = res.getString(R.string.cache_offline_time_about) + " " + diff + " " + res.getString(R.string.cache_offline_time_mins); + } else if (diff < 90) { + ago = res.getString(R.string.cache_offline_time_about) + " " + res.getString(R.string.cache_offline_time_hour); + } else if (diff < (48 * 60)) { + ago = res.getString(R.string.cache_offline_time_about) + " " + (diff / 60) + " " + res.getString(R.string.cache_offline_time_hours); + } else { + ago = res.getString(R.string.cache_offline_time_about) + " " + (diff / (24 * 60)) + " " + res.getString(R.string.cache_offline_time_days); + } + + offlineText.setText(res.getString(R.string.cache_offline_stored) + "\n" + ago); + offlineRefresh.setOnClickListener(refreshCacheClickListener); + + offlineStore.setText(res.getString(R.string.cache_offline_drop)); + offlineStore.setClickable(true); + offlineStore.setOnClickListener(dropCacheClickListener); + } else { + offlineText.setText(res.getString(R.string.cache_offline_not_ready)); + offlineRefresh.setOnClickListener(refreshCacheClickListener); + + offlineStore.setText(res.getString(R.string.cache_offline_store)); + offlineStore.setClickable(true); + offlineStore.setOnClickListener(storeCacheClickListener); + } + offlineRefresh.setVisibility(cache.supportsRefresh() ? View.VISIBLE : View.GONE); + offlineRefresh.setClickable(true); + } + } diff --git a/main/src/cgeo/geocaching/CachePopup.java b/main/src/cgeo/geocaching/CachePopup.java index e01e4a0..e6d0148 100644 --- a/main/src/cgeo/geocaching/CachePopup.java +++ b/main/src/cgeo/geocaching/CachePopup.java @@ -6,6 +6,7 @@ import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.ui.CacheDetailsCreator; import cgeo.geocaching.utils.CancellableHandler; import cgeo.geocaching.utils.Log; +import cgeo.geocaching.utils.RunnableWithArgument; import org.apache.commons.lang3.StringUtils; @@ -15,7 +16,6 @@ import android.content.res.Configuration; import android.os.Handler; import android.os.Message; import android.view.View; -import android.widget.Button; import android.widget.LinearLayout; import android.widget.TextView; @@ -79,7 +79,7 @@ public class CachePopup extends AbstractPopupActivity { if (StringUtils.isNotBlank(cache.getName())) { setTitle(cache.getName()); } else { - setTitle(geocode.toUpperCase()); + setTitle(geocode); } // actionbar icon @@ -89,52 +89,11 @@ public class CachePopup extends AbstractPopupActivity { addCacheDetails(); - findViewById(R.id.offline_box).setVisibility(View.VISIBLE); - // offline use - final TextView offlineText = (TextView) findViewById(R.id.offline_text); - final Button offlineRefresh = (Button) findViewById(R.id.offline_refresh); - final Button offlineStore = (Button) findViewById(R.id.offline_store); - - if (cache.getListId() > 0) { - final long diff = (System.currentTimeMillis() / (60 * 1000)) - (cache.getDetailedUpdate() / (60 * 1000)); // minutes - - String ago; - if (diff < 15) { - ago = res.getString(R.string.cache_offline_time_mins_few); - } else if (diff < 50) { - ago = res.getString(R.string.cache_offline_time_about) + " " + diff + " " + res.getString(R.string.cache_offline_time_mins); - } else if (diff < 90) { - ago = res.getString(R.string.cache_offline_time_about) + " " + res.getString(R.string.cache_offline_time_hour); - } else if (diff < (48 * 60)) { - ago = res.getString(R.string.cache_offline_time_about) + " " + (diff / 60) + " " + res.getString(R.string.cache_offline_time_hours); - } else { - ago = res.getString(R.string.cache_offline_time_about) + " " + (diff / (24 * 60)) + " " + res.getString(R.string.cache_offline_time_days); - } - - offlineText.setText(res.getString(R.string.cache_offline_stored) + "\n" + ago); - - offlineRefresh.setVisibility(View.VISIBLE); - offlineRefresh.setEnabled(true); - offlineRefresh.setOnClickListener(new RefreshCacheClickListener()); - - offlineStore.setText(res.getString(R.string.cache_offline_drop)); - offlineStore.setEnabled(true); - offlineStore.setOnClickListener(new DropCacheClickListener()); - } else { - offlineText.setText(res.getString(R.string.cache_offline_not_ready)); + CacheDetailActivity.updateOfflineBox(findViewById(android.R.id.content), cache, res, new RefreshCacheClickListener(), new DropCacheClickListener(), new StoreCacheClickListener()); - offlineRefresh.setVisibility(View.GONE); - offlineRefresh.setEnabled(false); - offlineRefresh.setOnTouchListener(null); - offlineRefresh.setOnClickListener(null); - - offlineStore.setText(res.getString(R.string.cache_offline_store)); - offlineStore.setEnabled(true); - offlineStore.setOnClickListener(new StoreCacheClickListener()); - } } catch (Exception e) { - Log.e("cgeopopup.init: " + e.toString()); + Log.e("cgeopopup.init", e); } // cache is loaded. remove progress-popup if any there @@ -156,22 +115,39 @@ public class CachePopup extends AbstractPopupActivity { return; } + if (Settings.getChooseList()) { + // let user select list to store cache in + new StoredList.UserInterface(CachePopup.this).promptForListSelection(R.string.list_title, + new RunnableWithArgument<Integer>() { + @Override + public void run(final Integer selectedListId) { + storeCache(selectedListId); + } + }, true, StoredList.TEMPORARY_LIST_ID); + } else { + storeCache(StoredList.TEMPORARY_LIST_ID); + } + } + + protected void storeCache(final int listId) { final StoreCacheHandler storeCacheHandler = new StoreCacheHandler(); progress.show(CachePopup.this, res.getString(R.string.cache_dialog_offline_save_title), res.getString(R.string.cache_dialog_offline_save_message), true, storeCacheHandler.cancelMessage()); - new StoreCacheThread(storeCacheHandler).start(); + new StoreCacheThread(listId, storeCacheHandler).start(); } } private class StoreCacheThread extends Thread { + final private int listId; final private CancellableHandler handler; - public StoreCacheThread(final CancellableHandler handler) { + public StoreCacheThread(final int listId, final CancellableHandler handler) { + this.listId = listId; this.handler = handler; } @Override public void run() { - cache.store(handler); + cache.store(listId, handler); invalidateOptionsMenuCompatible(); } } @@ -237,7 +213,7 @@ public class CachePopup extends AbstractPopupActivity { } /** - * Tries to navigate to the {@link cgCache} of this activity. + * Tries to navigate to the {@link Geocache} of this activity. */ @Override protected void startDefaultNavigation2() { @@ -251,7 +227,7 @@ public class CachePopup extends AbstractPopupActivity { public static void startActivity(final Context context, final String geocode) { final Intent popupIntent = new Intent(context, CachePopup.class); - popupIntent.putExtra(EXTRA_GEOCODE, geocode); + popupIntent.putExtra(Intents.EXTRA_GEOCODE, geocode); context.startActivity(popupIntent); } diff --git a/main/src/cgeo/geocaching/Destination.java b/main/src/cgeo/geocaching/Destination.java index 441e959..10d51be 100644 --- a/main/src/cgeo/geocaching/Destination.java +++ b/main/src/cgeo/geocaching/Destination.java @@ -34,7 +34,13 @@ public final class Destination implements ICoordinates { @Override public boolean equals(final Object obj) { - return obj != null && obj instanceof Destination && ((Destination) obj).coords.equals(coords); + if (this == obj) { + return true; + } + if (!(obj instanceof Destination)) { + return false; + } + return ((Destination) obj).coords.equals(coords); } public long getId() { diff --git a/main/src/cgeo/geocaching/DirectionProvider.java b/main/src/cgeo/geocaching/DirectionProvider.java index 14fd283..c1f83ac 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 signalled to observers to avoid resending the same value when the + // Previous values signaled to observers to avoid resending 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. @@ -38,14 +38,14 @@ public class DirectionProvider extends MemorySubject<Float> implements SensorEve @Override public void onAccuracyChanged(final Sensor sensor, int accuracy) { /* - * There is a bug in Android, which appearently causes this method to be called every - * time the sensor _value_ changed, even if the _accuracy_ did not change. So logging - * 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 - */ + * There is a bug in Android, which apparently causes this method to be called every + * time the sensor _value_ changed, even if the _accuracy_ did not change. So logging + * 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 + */ //Log.i(Settings.tag, "Compass' accuracy is low (" + accuracy + ")"); } diff --git a/main/src/cgeo/geocaching/EditWaypointActivity.java b/main/src/cgeo/geocaching/EditWaypointActivity.java index ef2a2cc..e886dea 100644 --- a/main/src/cgeo/geocaching/EditWaypointActivity.java +++ b/main/src/cgeo/geocaching/EditWaypointActivity.java @@ -2,12 +2,16 @@ 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; import cgeo.geocaching.enumerations.LoadFlags; import cgeo.geocaching.enumerations.LoadFlags.SaveFlag; import cgeo.geocaching.enumerations.WaypointType; 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.GeoDirHandler; import cgeo.geocaching.utils.Log; @@ -17,6 +21,7 @@ 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; import android.os.Message; @@ -27,7 +32,11 @@ 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.Spinner; import java.util.ArrayList; @@ -39,12 +48,13 @@ public class EditWaypointActivity extends AbstractActivity { private String geocode = null; private int id = -1; private ProgressDialog waitDialog = null; - private cgWaypoint waypoint = 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 = ""; @@ -65,6 +75,7 @@ public class EditWaypointActivity extends AbstractActivity { 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)); @@ -77,15 +88,18 @@ public class EditWaypointActivity extends AbstractActivity { else { ((EditText) findViewById(R.id.note)).setText(StringUtils.trimToEmpty(waypoint.getNote())); } + 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.toString()); + Log.e("EditWaypointActivity.loadWaypointHandler", e); } finally { if (waitDialog != null) { waitDialog.dismiss(); @@ -100,15 +114,15 @@ public class EditWaypointActivity extends AbstractActivity { super.onCreate(savedInstanceState); setTheme(); - setContentView(R.layout.waypoint_new); + setContentView(R.layout.edit_waypoint_activity); setTitle("waypoint"); // get parameters Bundle extras = getIntent().getExtras(); if (extras != null) { - geocode = extras.getString("geocode"); - wpCount = extras.getInt("count", 0); - id = extras.getInt("waypoint"); + geocode = extras.getString(Intents.EXTRA_GEOCODE); + wpCount = extras.getInt(Intents.EXTRA_COUNT, 0); + id = extras.getInt(Intents.EXTRA_WAYPOINT_ID); } if (StringUtils.isBlank(geocode) && id <= 0) { @@ -125,15 +139,15 @@ public class EditWaypointActivity extends AbstractActivity { } Button buttonLat = (Button) findViewById(R.id.buttonLatitude); - buttonLat.setOnClickListener(new coordDialogListener()); + buttonLat.setOnClickListener(new CoordDialogListener()); Button buttonLon = (Button) findViewById(R.id.buttonLongitude); - buttonLon.setOnClickListener(new coordDialogListener()); + buttonLon.setOnClickListener(new CoordDialogListener()); Button addWaypoint = (Button) findViewById(R.id.add_waypoint); - addWaypoint.setOnClickListener(new coordsListener()); + addWaypoint.setOnClickListener(new CoordsListener()); List<String> wayPointNames = new ArrayList<String>(); - for (WaypointType wpt : WaypointType.ALL_TYPES_EXCEPT_OWN) { + for (WaypointType wpt : WaypointType.ALL_TYPES_EXCEPT_OWN_AND_ORIGINAL) { wayPointNames.add(wpt.getL10n()); } AutoCompleteTextView textView = (AutoCompleteTextView) findViewById(R.id.name); @@ -147,16 +161,39 @@ public class EditWaypointActivity extends AbstractActivity { waitDialog = ProgressDialog.show(this, null, res.getString(R.string.waypoint_loading), true); waitDialog.setCancelable(true); - (new loadWaypoint()).start(); + (new LoadWaypointThread()).start(); } else { 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; + } + }); + initializeDistanceUnitSelector(); disableSuggestions((EditText) findViewById(R.id.distance)); } + 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); + } else { + findViewById(R.id.modify_cache_coordinates_group).setVisibility(View.GONE); + findViewById(R.id.modify_cache_coordinates_local_and_remote).setVisibility(View.GONE); + } + } + @Override public void onResume() { super.onResume(); @@ -168,7 +205,7 @@ public class EditWaypointActivity extends AbstractActivity { waitDialog = ProgressDialog.show(this, null, res.getString(R.string.waypoint_loading), true); waitDialog.setCancelable(true); - (new loadWaypoint()).start(); + (new LoadWaypointThread()).start(); } } } @@ -193,7 +230,7 @@ public class EditWaypointActivity extends AbstractActivity { Spinner waypointTypeSelector = (Spinner) findViewById(R.id.type); - wpTypes = new ArrayList<WaypointType>(WaypointType.ALL_TYPES_EXCEPT_OWN); + 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); @@ -204,7 +241,7 @@ public class EditWaypointActivity extends AbstractActivity { } waypointTypeSelector.setSelection(typeIndex); - waypointTypeSelector.setOnItemSelectedListener(new changeWaypointType(this)); + waypointTypeSelector.setOnItemSelectedListener(new ChangeWaypointType(this)); waypointTypeSelector.setVisibility(View.VISIBLE); } @@ -223,13 +260,12 @@ public class EditWaypointActivity extends AbstractActivity { } } - distanceUnitSelector.setOnItemSelectedListener(new changeDistanceUnit(this)); + distanceUnitSelector.setOnItemSelectedListener(new ChangeDistanceUnit(this)); } final private GeoDirHandler geoDirHandler = new GeoDirHandler() { @Override public void updateGeoData(final IGeoData geo) { - Log.d("EditWaypointActivity.updateLocation called"); if (geo.getCoords() == null) { return; } @@ -239,27 +275,27 @@ public class EditWaypointActivity extends AbstractActivity { 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)); - } catch (Exception e) { - Log.w("Failed to update location."); + } catch (final Exception e) { + Log.e("failed to update location", e); } } }; - private class loadWaypoint extends Thread { + private class LoadWaypointThread extends Thread { @Override public void run() { try { - waypoint = app.loadWaypoint(id); + waypoint = cgData.loadWaypoint(id); loadWaypointHandler.sendMessage(Message.obtain()); } catch (Exception e) { - Log.e("cgeowaypoint.loadWaypoint.run: " + e.toString()); + Log.e("cgeowaypoint.loadWaypoint.run", e); } } } - private class coordDialogListener implements View.OnClickListener { + private class CoordDialogListener implements View.OnClickListener { @Override public void onClick(View arg0) { @@ -269,10 +305,10 @@ public class EditWaypointActivity extends AbstractActivity { } else if (gpTemp != null) { gp = gpTemp; } - cgCache cache = app.loadCache(geocode, LoadFlags.LOAD_WAYPOINTS); - cgeocoords coordsDialog = new cgeocoords(EditWaypointActivity.this, cache, gp, app.currentGeo()); + Geocache cache = cgData.loadCache(geocode, LoadFlags.LOAD_WAYPOINTS); + CoordinatesInputDialog coordsDialog = new CoordinatesInputDialog(EditWaypointActivity.this, cache, gp, app.currentGeo()); coordsDialog.setCancelable(true); - coordsDialog.setOnCoordinateUpdate(new cgeocoords.CoordinateUpdate() { + coordsDialog.setOnCoordinateUpdate(new CoordinatesInputDialog.CoordinateUpdate() { @Override public void update(final Geopoint gp) { ((Button) findViewById(R.id.buttonLatitude)).setText(gp.format(GeopointFormatter.Format.LAT_DECMINUTE)); @@ -288,9 +324,9 @@ public class EditWaypointActivity extends AbstractActivity { } } - private class changeWaypointType implements OnItemSelectedListener { + private static class ChangeWaypointType implements OnItemSelectedListener { - private changeWaypointType(EditWaypointActivity wpView) { + private ChangeWaypointType(EditWaypointActivity wpView) { this.wpView = wpView; } @@ -312,9 +348,9 @@ public class EditWaypointActivity extends AbstractActivity { } } - private class changeDistanceUnit implements OnItemSelectedListener { + private static class ChangeDistanceUnit implements OnItemSelectedListener { - private changeDistanceUnit(EditWaypointActivity unitView) { + private ChangeDistanceUnit(EditWaypointActivity unitView) { this.unitView = unitView; } @@ -331,7 +367,14 @@ public class EditWaypointActivity extends AbstractActivity { } } - private class coordsListener implements View.OnClickListener { + public static final int SUCCESS = 0; + public static final int UPLOAD_START = 1; + public static final int UPLOAD_ERROR = 2; + public static final int UPLOAD_NOT_POSSIBLE = 3; + public static final int UPLOAD_SUCCESS = 4; + public static final int SAVE_ERROR = 5; + + private class CoordsListener implements View.OnClickListener { @Override public void onClick(View arg0) { @@ -386,53 +429,124 @@ public class EditWaypointActivity extends AbstractActivity { coords = coords.project(bearing, distance); } - String name = ((EditText) findViewById(R.id.name)).getText().toString().trim(); // if no name is given, just give the waypoint its number as name - if (name.length() == 0) { - name = res.getString(R.string.waypoint) + " " + String.valueOf(wpCount + 1); - } + final String givenName = ((EditText) findViewById(R.id.name)).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 Geopoint coordsToSave = coords; + final ProgressDialog progress = ProgressDialog.show(EditWaypointActivity.this, getString(R.string.cache), getString(R.string.waypoint_being_saved), true); + final Handler finishHandler = new Handler() { - final cgWaypoint waypoint = new cgWaypoint(name, type, own); - waypoint.setGeocode(geocode); - waypoint.setPrefix(prefix); - waypoint.setLookup(lookup); - waypoint.setCoords(coords); - waypoint.setNote(note); - waypoint.setId(id); - - cgCache cache = app.loadCache(geocode, LoadFlags.LOAD_WAYPOINTS); - if (null != cache && cache.addOrChangeWaypoint(waypoint, true)) { - app.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); - StaticMapsProvider.removeWpStaticMaps(id, geocode); - if (Settings.isStoreOfflineWpMaps()) { - StaticMapsProvider.storeWaypointStaticMap(cache, EditWaypointActivity.this, waypoint, false); + @Override + public void handleMessage(Message msg) { + // TODO: The order of showToast, progress.dismiss and finish is different in these cases. Why? + switch (msg.what) { + case UPLOAD_SUCCESS: + showToast(getString(R.string.waypoint_coordinates_has_been_modified_on_website, coordsToSave)); + progress.dismiss(); + finish(); + break; + case SUCCESS: + progress.dismiss(); + finish(); + break; + case UPLOAD_START: + progress.setMessage(getString(R.string.waypoint_coordinates_uploading_to_website, coordsToSave)); + break; + case UPLOAD_ERROR: + progress.dismiss(); + finish(); + showToast(getString(R.string.waypoint_coordinates_upload_error)); + break; + case UPLOAD_NOT_POSSIBLE: + progress.dismiss(); + finish(); + showToast(getString(R.string.waypoint_coordinates_couldnt_be_modified_on_website)); + break; + case SAVE_ERROR: + progress.dismiss(); + finish(); //TODO: should we close activity here ? + showToast(res.getString(R.string.err_waypoint_add_failed)); + break; + default: + throw new UnsupportedOperationException(); + } + } + }; + + class SaveWptTask extends AsyncTask<Void, Void, Void> { + + @Override + protected Void doInBackground(Void... params) { + final Waypoint waypoint = new Waypoint(name, type, own); + waypoint.setGeocode(geocode); + waypoint.setPrefix(prefix); + waypoint.setLookup(lookup); + waypoint.setCoords(coordsToSave); + waypoint.setNote(note); + waypoint.setVisited(visited); + waypoint.setId(id); + + Geocache cache = cgData.loadCache(geocode, LoadFlags.LOAD_WAYPOINTS); + if (null != cache && cache.addOrChangeWaypoint(waypoint, true)) { + cgData.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); + StaticMapsProvider.removeWpStaticMaps(id, geocode); + if (Settings.isStoreOfflineWpMaps()) { + 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); + origWaypoint.setCoords(cache.getCoords()); + cache.addOrChangeWaypoint(origWaypoint, false); + cache.setUserModifiedCoords(true); + } + cache.setCoords(waypoint.getCoords()); + cgData.saveChangedCache(cache); + } + if (modifyBoth.isChecked() && waypoint.getCoords() != null) { + finishHandler.sendEmptyMessage(UPLOAD_START); + + if (cache.supportsOwnCoordinates()) { + boolean result = uploadModifiedCoords(cache, waypoint.getCoords()); + finishHandler.sendEmptyMessage(result ? SUCCESS : UPLOAD_ERROR); + } else { + showToast(getString(R.string.waypoint_coordinates_couldnt_be_modified_on_website)); + finishHandler.sendEmptyMessage(UPLOAD_NOT_POSSIBLE); + } + } else { + finishHandler.sendEmptyMessage(SUCCESS); + } + } else { + finishHandler.sendEmptyMessage(SAVE_ERROR); + } + return null; } - finish(); - } else { - showToast(res.getString(R.string.err_waypoint_add_failed)); } + new SaveWptTask().execute(); } } + private static boolean uploadModifiedCoords(final Geocache cache, final Geopoint waypointUploaded) { + final IConnector con = ConnectorFactory.getConnector(cache); + return con.supportsOwnCoordinates() && con.uploadModifiedCoordinates(cache, waypointUploaded); + } + @Override - public void goManual(View view) { - if (id >= 0) { - ActivityMixin.goManual(this, "c:geo-waypoint-edit"); - } else { - ActivityMixin.goManual(this, "c:geo-waypoint-new"); - } + 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) { - final Intent editIntent = new Intent(context, EditWaypointActivity.class); - editIntent.putExtra("waypoint", waypointId); - context.startActivity(editIntent); + context.startActivity(new Intent(context, EditWaypointActivity.class) + .putExtra(Intents.EXTRA_WAYPOINT_ID, waypointId)); } - public static void startActivityAddWaypoint(final Context context, final cgCache cache) { - final Intent addWptIntent = new Intent(context, EditWaypointActivity.class); - addWptIntent.putExtra("geocode", cache.getGeocode()).putExtra("count", cache.getWaypoints().size()); - context.startActivity(addWptIntent); + 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())); } } diff --git a/main/src/cgeo/geocaching/cgCache.java b/main/src/cgeo/geocaching/Geocache.java index 7035d65..e672ca8 100644 --- a/main/src/cgeo/geocaching/cgCache.java +++ b/main/src/cgeo/geocaching/Geocache.java @@ -11,6 +11,7 @@ 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; @@ -27,6 +28,7 @@ import cgeo.geocaching.utils.LazyInitializedList; import cgeo.geocaching.utils.Log; import cgeo.geocaching.utils.LogTemplateProvider; import cgeo.geocaching.utils.LogTemplateProvider.LogContext; +import cgeo.geocaching.utils.MatcherWrapper; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; @@ -38,7 +40,6 @@ import android.net.Uri; import android.os.Handler; import android.os.Message; import android.text.Html; -import android.text.Spannable; import java.util.ArrayList; import java.util.Calendar; @@ -46,15 +47,15 @@ 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.Map; -import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Internal c:geo representation of a "cache" */ -public class cgCache implements ICache, IWaypoint { +public class Geocache implements ICache, IWaypoint { private long updated = 0; private long detailedUpdate = 0; @@ -66,56 +67,65 @@ public class cgCache implements ICache, IWaypoint { private String guid = ""; private CacheType cacheType = CacheType.UNKNOWN; private String name = ""; - private Spannable nameSp = null; private String ownerDisplayName = ""; private String ownerUserId = ""; private Date hidden = null; - private String hint = ""; + /** + * lazy initialized + */ + private String hint = null; private CacheSize size = CacheSize.UNKNOWN; private float difficulty = 0; private float terrain = 0; private Float direction = null; private Float distance = null; - private String latlon = ""; - private String location = ""; + /** + * lazy initialized + */ + private String location = null; private Geopoint coords = null; private boolean reliableLatLon = false; private Double elevation = null; private String personalNote = null; - private String shortdesc = ""; + /** + * lazy initialized + */ + private String shortdesc = null; + /** + * lazy initialized + */ private String description = null; private boolean disabled = false; private boolean archived = false; private boolean premiumMembersOnly = false; private boolean found = false; private boolean favorite = false; - private boolean own = false; private int favoritePoints = 0; private float rating = 0; // valid ratings are larger than zero private int votes = 0; private float myVote = 0; // valid ratings are larger than zero private int inventoryItems = 0; private boolean onWatchlist = false; - private LazyInitializedList<String> attributes = new LazyInitializedList<String>() { + private List<String> attributes = new LazyInitializedList<String>() { @Override - protected List<String> loadFromDatabase() { - return cgeoapplication.getInstance().loadAttributes(geocode); + public List<String> call() { + return cgData.loadAttributes(geocode); } }; - private LazyInitializedList<cgWaypoint> waypoints = new LazyInitializedList<cgWaypoint>() { + private List<Waypoint> waypoints = new LazyInitializedList<Waypoint>() { @Override - protected List<cgWaypoint> loadFromDatabase() { - return cgeoapplication.getInstance().loadWaypoints(geocode); + public List<Waypoint> call() { + return cgData.loadWaypoints(geocode); } }; - private List<cgImage> spoilers = null; - private LazyInitializedList<LogEntry> logs = new LazyInitializedList<LogEntry>() { + private List<Image> spoilers = null; + private List<LogEntry> logs = new LazyInitializedList<LogEntry>() { @Override - protected List<LogEntry> loadFromDatabase() { - return cgeoapplication.getInstance().loadLogs(geocode); + public List<LogEntry> call() { + return cgData.loadLogs(geocode); } }; - private List<cgTrackable> inventory = null; + private List<Trackable> inventory = null; private Map<LogType, Integer> logCounts = new HashMap<LogType, Integer>(); private boolean logOffline = false; private boolean userModifiedCoords = false; @@ -134,7 +144,7 @@ public class cgCache implements ICache, IWaypoint { /** * Create a new cache. To be used everywhere except for the GPX parser */ - public cgCache() { + public Geocache() { // empty } @@ -144,10 +154,10 @@ public class cgCache implements ICache, IWaypoint { * * @param gpxParser */ - public cgCache(GPXParser gpxParser) { + public Geocache(GPXParser gpxParser) { setReliableLatLon(true); setAttributes(Collections.<String> emptyList()); - setWaypoints(Collections.<cgWaypoint> emptyList(), false); + setWaypoints(Collections.<Waypoint> emptyList(), false); setLogs(Collections.<LogEntry> emptyList()); } @@ -171,7 +181,7 @@ public class cgCache implements ICache, IWaypoint { * the other version, or null if non-existent * @return true if this cache is "equal" to the other version */ - public boolean gatherMissingFrom(final cgCache other) { + public boolean gatherMissingFrom(final Geocache other) { if (other == null) { return false; } @@ -188,7 +198,6 @@ public class cgCache implements ICache, IWaypoint { reliableLatLon = other.reliableLatLon; archived = other.archived; found = other.found; - own = other.own; disabled = other.disabled; favorite = other.favorite; onWatchlist = other.onWatchlist; @@ -227,9 +236,6 @@ public class cgCache implements ICache, IWaypoint { if (StringUtils.isBlank(name)) { name = other.name; } - if (StringUtils.isBlank(nameSp)) { - nameSp = other.nameSp; - } if (StringUtils.isBlank(ownerDisplayName)) { ownerDisplayName = other.ownerDisplayName; } @@ -239,8 +245,8 @@ public class cgCache implements ICache, IWaypoint { if (hidden == null) { hidden = other.hidden; } - if (StringUtils.isBlank(hint)) { - hint = other.hint; + if (StringUtils.isBlank(getHint())) { + hint = other.getHint(); } if (size == null || CacheSize.UNKNOWN == size) { size = other.size; @@ -257,11 +263,8 @@ public class cgCache implements ICache, IWaypoint { if (distance == null) { distance = other.distance; } - if (StringUtils.isBlank(latlon)) { - latlon = other.latlon; - } - if (StringUtils.isBlank(location)) { - location = other.location; + if (StringUtils.isBlank(getLocation())) { + location = other.getLocation(); } if (coords == null) { coords = other.coords; @@ -272,12 +275,14 @@ public class cgCache implements ICache, IWaypoint { if (personalNote == null) { // don't use StringUtils.isBlank here. Otherwise we cannot recognize a note which was deleted on GC personalNote = other.personalNote; } - if (StringUtils.isBlank(shortdesc)) { - shortdesc = other.shortdesc; + if (StringUtils.isBlank(getShortDescription())) { + shortdesc = other.getShortDescription(); } - if (StringUtils.isBlank(description)) { - description = other.description; + if (StringUtils.isBlank(getDescription())) { + description = other.getDescription(); } + // FIXME: this makes no sense to favor this over the other. 0 should not be a special case here as it is + // in the range of acceptable values. This is probably the case at other places (rating, votes, etc.) too. if (favoritePoints == 0) { favoritePoints = other.favoritePoints; } @@ -291,15 +296,18 @@ public class cgCache implements ICache, IWaypoint { myVote = other.myVote; } if (attributes.isEmpty()) { - attributes.set(other.attributes); + attributes.clear(); + if (other.attributes != null) { + attributes.addAll(other.attributes); + } } if (waypoints.isEmpty()) { - waypoints.set(other.waypoints); + this.setWaypoints(other.waypoints, false); } else { - ArrayList<cgWaypoint> newPoints = new ArrayList<cgWaypoint>(waypoints.asList()); - cgWaypoint.mergeWayPoints(newPoints, other.getWaypoints(), false); - waypoints.set(newPoints); + ArrayList<Waypoint> newPoints = new ArrayList<Waypoint>(waypoints); + Waypoint.mergeWayPoints(newPoints, other.waypoints, false); + this.setWaypoints(newPoints, false); } if (spoilers == null) { spoilers = other.spoilers; @@ -313,14 +321,26 @@ public class cgCache implements ICache, IWaypoint { inventoryItems = other.inventoryItems; } if (logs.isEmpty()) { // keep last known logs if none - logs.set(other.logs); + logs.clear(); + if (other.logs != null) { + logs.addAll(other.logs); + } } - if (logCounts.size() == 0) { + if (logCounts.isEmpty()) { logCounts = other.logCounts; } - if (!userModifiedCoords) { - userModifiedCoords = other.userModifiedCoords; + + // if cache has ORIGINAL type waypoint ... it is considered that it has modified coordinates, otherwise not + userModifiedCoords = false; + if (waypoints != null) { + for (Waypoint wpt : waypoints) { + if (wpt.getWaypointType() == WaypointType.ORIGINAL) { + userModifiedCoords = true; + break; + } + } } + if (!reliableLatLon) { reliableLatLon = other.reliableLatLon; } @@ -328,29 +348,23 @@ public class cgCache implements ICache, IWaypoint { zoomlevel = other.zoomlevel; } - boolean isEqual = isEqualTo(other); - - if (!isEqual) { - notifyChange(); - } - - return isEqual; + return isEqualTo(other); } /** * Compare two caches quickly. For map and list fields only the references are compared ! * - * @param other the other cache to compare this one to + * @param other + * the other cache to compare this one to * @return true if both caches have the same content */ - private boolean isEqualTo(final cgCache other) { + private boolean isEqualTo(final Geocache other) { return detailed == other.detailed && StringUtils.equalsIgnoreCase(geocode, other.geocode) && StringUtils.equalsIgnoreCase(name, other.name) && cacheType == other.cacheType && size == other.size && found == other.found && - own == other.own && premiumMembersOnly == other.premiumMembersOnly && difficulty == other.difficulty && terrain == other.terrain && @@ -361,22 +375,20 @@ public class cgCache implements ICache, IWaypoint { listId == other.listId && StringUtils.equalsIgnoreCase(ownerDisplayName, other.ownerDisplayName) && StringUtils.equalsIgnoreCase(ownerUserId, other.ownerUserId) && - StringUtils.equalsIgnoreCase(description, other.description) && + StringUtils.equalsIgnoreCase(getDescription(), other.getDescription()) && StringUtils.equalsIgnoreCase(personalNote, other.personalNote) && - StringUtils.equalsIgnoreCase(shortdesc, other.shortdesc) && - StringUtils.equalsIgnoreCase(latlon, other.latlon) && - StringUtils.equalsIgnoreCase(location, other.location) && + StringUtils.equalsIgnoreCase(getShortDescription(), other.getShortDescription()) && + StringUtils.equalsIgnoreCase(getLocation(), other.getLocation()) && favorite == other.favorite && favoritePoints == other.favoritePoints && onWatchlist == other.onWatchlist && (hidden != null ? hidden.equals(other.hidden) : null == other.hidden) && StringUtils.equalsIgnoreCase(guid, other.guid) && - StringUtils.equalsIgnoreCase(hint, other.hint) && + StringUtils.equalsIgnoreCase(getHint(), other.getHint()) && StringUtils.equalsIgnoreCase(cacheId, other.cacheId) && (direction != null ? direction.equals(other.direction) : null == other.direction) && (distance != null ? distance.equals(other.distance) : null == other.distance) && (elevation != null ? elevation.equals(other.elevation) : null == other.elevation) && - nameSp == other.nameSp && rating == other.rating && votes == other.votes && myVote == other.myVote && @@ -426,7 +438,7 @@ public class cgCache implements ICache, IWaypoint { return false; } final Boolean found = Pattern.compile(guid, Pattern.CASE_INSENSITIVE).matcher(page).find(); - Log.i("cgCache.isGuidContainedInPage: guid '" + guid + "' " + (found ? "" : "not ") + "found"); + Log.i("Geocache.isGuidContainedInPage: guid '" + guid + "' " + (found ? "" : "not ") + "found"); return found; } @@ -441,8 +453,7 @@ public class cgCache implements ICache, IWaypoint { } Intent logVisitIntent = new Intent((Activity) fromActivity, VisitCacheActivity.class); logVisitIntent.putExtra(VisitCacheActivity.EXTRAS_ID, cacheId); - logVisitIntent.putExtra(VisitCacheActivity.EXTRAS_GEOCODE, geocode.toUpperCase()); - logVisitIntent.putExtra(VisitCacheActivity.EXTRAS_FOUND, found); + logVisitIntent.putExtra(VisitCacheActivity.EXTRAS_GEOCODE, geocode); ((Activity) fromActivity).startActivity(logVisitIntent); } @@ -457,13 +468,12 @@ public class cgCache implements ICache, IWaypoint { if (logType == LogType.UNKNOWN) { return; } - cgeoapplication app = (cgeoapplication) fromActivity.getApplication(); - final boolean status = app.saveLogOffline(geocode, date.getTime(), logType, log); + final boolean status = cgData.saveLogOffline(geocode, date.getTime(), logType, log); Resources res = fromActivity.getResources(); if (status) { ActivityMixin.showToast(fromActivity, res.getString(R.string.info_log_saved)); - app.saveVisitDate(geocode); + cgData.saveVisitDate(geocode); logOffline = true; notifyChange(); @@ -473,14 +483,13 @@ public class cgCache implements ICache, IWaypoint { } public List<LogType> getPossibleLogTypes() { - boolean isOwner = getOwnerUserId() != null && getOwnerUserId().equalsIgnoreCase(Settings.getUsername()); - List<LogType> logTypes = new ArrayList<LogType>(); + final List<LogType> logTypes = new LinkedList<LogType>(); if (isEventCache()) { logTypes.add(LogType.WILL_ATTEND); logTypes.add(LogType.NOTE); logTypes.add(LogType.ATTENDED); logTypes.add(LogType.NEEDS_ARCHIVE); - if (isOwner) { + if (isOwner()) { logTypes.add(LogType.ANNOUNCEMENT); } } else if (CacheType.WEBCAM == cacheType) { @@ -496,7 +505,7 @@ public class cgCache implements ICache, IWaypoint { logTypes.add(LogType.NEEDS_ARCHIVE); logTypes.add(LogType.NEEDS_MAINTENANCE); } - if (isOwner) { + if (isOwner()) { logTypes.add(LogType.OWNER_MAINTENANCE); logTypes.add(LogType.TEMP_DISABLE_LISTING); logTypes.add(LogType.ENABLE_LISTING); @@ -538,6 +547,14 @@ public class cgCache implements ICache, IWaypoint { return getConnector().supportsLogging(); } + public boolean supportsOwnCoordinates() { + return getConnector().supportsOwnCoordinates(); + } + + public CacheRealm getCacheRealm() { + return getConnector().getCacheRealm(); + } + @Override public float getDifficulty() { return difficulty; @@ -586,8 +603,8 @@ public class cgCache implements ICache, IWaypoint { } @Override - public boolean isOwn() { - return own; + public boolean isOwner() { + return getConnector().isOwner(this); } @Override @@ -595,21 +612,64 @@ public class cgCache implements ICache, IWaypoint { return ownerUserId; } + /** + * Attention, calling this method may trigger a database access for the cache! + */ @Override public String getHint() { + initializeCacheTexts(); + assertTextNotNull(hint, "Hint"); return hint; } + /** + * After lazy loading the lazily loaded field must be non {@code null}. + * + */ + private static void assertTextNotNull(final String field, final String name) throws InternalError { + if (field == null) { + throw new InternalError(name + " field is not allowed to be null here"); + } + } + + /** + * Attention, calling this method may trigger a database access for the cache! + */ @Override public String getDescription() { - if (description == null) { - description = StringUtils.defaultString(cgeoapplication.getInstance().getCacheDescription(geocode)); - } + initializeCacheTexts(); + assertTextNotNull(description, "Description"); return description; } + /** + * loads long text parts of a cache on demand (but all fields together) + */ + private void initializeCacheTexts() { + if (description == null || shortdesc == null || hint == null || location == null) { + Geocache partial = cgData.loadCacheTexts(this.getGeocode()); + if (description == null) { + setDescription(partial.getDescription()); + } + if (shortdesc == null) { + setShortDescription(partial.getShortDescription()); + } + if (hint == null) { + setHint(partial.getHint()); + } + if (location == null) { + setLocation(partial.getLocation()); + } + } + } + + /** + * Attention, calling this method may trigger a database access for the cache! + */ @Override public String getShortDescription() { + initializeCacheTexts(); + assertTextNotNull(shortdesc, "Short description"); return shortdesc; } @@ -632,8 +692,13 @@ public class cgCache implements ICache, IWaypoint { return guid; } + /** + * Attention, calling this method may trigger a database access for the cache! + */ @Override public String getLocation() { + initializeCacheTexts(); + assertTextNotNull(location, "Location"); return location; } @@ -661,7 +726,7 @@ public class cgCache implements ICache, IWaypoint { } StringBuilder subject = new StringBuilder("Geocache "); - subject.append(geocode.toUpperCase()); + subject.append(geocode); if (StringUtils.isNotBlank(name)) { subject.append(" - ").append(name); } @@ -700,7 +765,6 @@ public class cgCache implements ICache, IWaypoint { this.favorite = favourite; } - @Override public boolean isWatchlist() { return onWatchlist; @@ -712,24 +776,24 @@ public class cgCache implements ICache, IWaypoint { } @Override - public LazyInitializedList<String> getAttributes() { + public List<String> getAttributes() { return attributes; } @Override - public List<cgTrackable> getInventory() { + public List<Trackable> getInventory() { return inventory; } - public void addSpoiler(final cgImage spoiler) { + public void addSpoiler(final Image spoiler) { if (spoilers == null) { - spoilers = new ArrayList<cgImage>(); + spoilers = new ArrayList<Image>(); } spoilers.add(spoiler); } @Override - public List<cgImage> getSpoilers() { + public List<Image> getSpoilers() { if (spoilers == null) { return Collections.emptyList(); } @@ -749,7 +813,7 @@ public class cgCache implements ICache, IWaypoint { @Override public String getNameForSorting() { if (null == nameForSorting) { - final Matcher matcher = NUMBER_PATTERN.matcher(name); + final MatcherWrapper matcher = new MatcherWrapper(NUMBER_PATTERN, name); if (matcher.find()) { nameForSorting = name.replace(matcher.group(), StringUtils.leftPad(matcher.group(), 6, '0')); } @@ -809,14 +873,6 @@ public class cgCache implements ICache, IWaypoint { this.detailed = detailed; } - public Spannable getNameSp() { - return nameSp; - } - - public void setNameSp(Spannable nameSp) { - this.nameSp = nameSp; - } - public void setHidden(final Date hidden) { if (hidden == null) { this.hidden = null; @@ -842,14 +898,6 @@ public class cgCache implements ICache, IWaypoint { this.distance = distance; } - public String getLatlon() { - return latlon; - } - - public void setLatlon(String latlon) { - this.latlon = latlon; - } - @Override public Geopoint getCoords() { return coords; @@ -878,11 +926,7 @@ public class cgCache implements ICache, IWaypoint { this.elevation = elevation; } - public String getShortdesc() { - return shortdesc; - } - - public void setShortdesc(String shortdesc) { + public void setShortDescription(String shortdesc) { this.shortdesc = shortdesc; } @@ -935,8 +979,8 @@ public class cgCache implements ICache, IWaypoint { * * @return always non <code>null</code> */ - public List<cgWaypoint> getWaypoints() { - return waypoints.asList(); + public List<Waypoint> getWaypoints() { + return waypoints; } /** @@ -947,25 +991,27 @@ public class cgCache implements ICache, IWaypoint { * called while loading or building a cache * @return <code>true</code> if waypoints successfully added to waypoint database */ - public boolean setWaypoints(List<cgWaypoint> waypoints, boolean saveToDatabase) { - this.waypoints.set(waypoints); + public boolean setWaypoints(List<Waypoint> waypoints, boolean saveToDatabase) { + this.waypoints.clear(); + if (waypoints != null) { + this.waypoints.addAll(waypoints); + } finalDefined = false; if (waypoints != null) { - for (cgWaypoint waypoint : waypoints) { + for (Waypoint waypoint : waypoints) { waypoint.setGeocode(geocode); if (waypoint.isFinalWithCoords()) { finalDefined = true; } } } - - return saveToDatabase && cgeoapplication.getInstance().saveWaypoints(this); + return saveToDatabase && cgData.saveWaypoints(this); } /** * @return never <code>null</code> */ - public LazyInitializedList<LogEntry> getLogs() { + public List<LogEntry> getLogs() { return logs; } @@ -987,7 +1033,10 @@ public class cgCache implements ICache, IWaypoint { * the log entries */ public void setLogs(List<LogEntry> logs) { - this.logs.set(logs); + this.logs.clear(); + if (logs != null) { + this.logs.addAll(logs); + } } public boolean isLogOffline() { @@ -1015,7 +1064,7 @@ public class cgCache implements ICache, IWaypoint { } public void setGeocode(String geocode) { - this.geocode = geocode; + this.geocode = StringUtils.upperCase(geocode); } public void setCacheId(String cacheId) { @@ -1079,19 +1128,18 @@ public class cgCache implements ICache, IWaypoint { this.found = found; } - public void setOwn(boolean own) { - this.own = own; - } - public void setAttributes(List<String> attributes) { - this.attributes.set(attributes); + this.attributes.clear(); + if (attributes != null) { + this.attributes.addAll(attributes); + } } - public void setSpoilers(List<cgImage> spoilers) { + public void setSpoilers(List<Image> spoilers) { this.spoilers = spoilers; } - public void setInventory(List<cgTrackable> inventory) { + public void setInventory(List<Trackable> inventory) { this.inventory = inventory; } @@ -1149,10 +1197,10 @@ public class cgCache implements ICache, IWaypoint { * called while loading or building a cache * @return <code>true</code> if waypoint successfully added to waypoint database */ - public boolean addOrChangeWaypoint(final cgWaypoint waypoint, boolean saveToDatabase) { + public boolean addOrChangeWaypoint(final Waypoint waypoint, boolean saveToDatabase) { waypoint.setGeocode(geocode); - if (waypoint.getId() <= 0) { // this is a new waypoint + if (waypoint.getId() < 0) { // this is a new waypoint waypoints.add(waypoint); if (waypoint.isFinalWithCoords()) { finalDefined = true; @@ -1166,8 +1214,7 @@ public class cgCache implements ICache, IWaypoint { // when waypoint was edited, finalDefined may have changed resetFinalDefined(); } - - return saveToDatabase && cgeoapplication.getInstance().saveWaypoint(waypoint.getId(), geocode, waypoint); + return saveToDatabase && cgData.saveWaypoint(waypoint.getId(), geocode, waypoint); } public boolean hasWaypoints() { @@ -1188,7 +1235,7 @@ public class cgCache implements ICache, IWaypoint { */ private void resetFinalDefined() { finalDefined = false; - for (cgWaypoint wp : waypoints) { + for (Waypoint wp : waypoints) { if (wp.isFinalWithCoords()) { finalDefined = true; break; @@ -1201,43 +1248,47 @@ public class cgCache implements ICache, IWaypoint { } public void setUserModifiedCoords(boolean coordsChanged) { - this.userModifiedCoords = coordsChanged; + userModifiedCoords = coordsChanged; } /** * Duplicate a waypoint. * - * @param index the waypoint to duplicate + * @param original + * the waypoint to duplicate * @return <code>true</code> if the waypoint was duplicated, <code>false</code> otherwise (invalid index) */ - public boolean duplicateWaypoint(final int index) { - final cgWaypoint original = getWaypoint(index); + public boolean duplicateWaypoint(final Waypoint original) { if (original == null) { return false; } - final cgWaypoint copy = new cgWaypoint(original); + final int index = getWaypointIndex(original); + final Waypoint copy = new Waypoint(original); copy.setUserDefined(); copy.setName(cgeoapplication.getInstance().getString(R.string.waypoint_copy_of) + " " + copy.getName()); waypoints.add(index + 1, copy); - return cgeoapplication.getInstance().saveWaypoint(-1, geocode, copy); + return cgData.saveWaypoint(-1, geocode, copy); } /** * delete a user defined waypoint * - * @param index - * of the waypoint in cache's waypoint list + * @param waypoint + * to be removed from cache * @return <code>true</code>, if the waypoint was deleted */ - public boolean deleteWaypoint(final int index) { - final cgWaypoint waypoint = getWaypoint(index); + public boolean deleteWaypoint(final Waypoint waypoint) { if (waypoint == null) { return false; } + if (waypoint.getId() < 0) { + return false; + } if (waypoint.isUserDefined()) { + final int index = getWaypointIndex(waypoint); waypoints.remove(index); - cgeoapplication.getInstance().deleteWaypoint(waypoint.getId()); - cgeoapplication.getInstance().removeCache(geocode, EnumSet.of(RemoveFlag.REMOVE_CACHE)); + cgData.deleteWaypoint(waypoint.getId()); + cgData.removeCache(geocode, EnumSet.of(RemoveFlag.REMOVE_CACHE)); // Check status if Final is defined if (waypoint.isFinalWithCoords()) { resetFinalDefined(); @@ -1248,19 +1299,17 @@ public class cgCache implements ICache, IWaypoint { } /** - * delete a user defined waypoint + * deletes any waypoint * * @param waypoint - * to be removed from cache - * @return <code>true</code>, if the waypoint was deleted */ - public boolean deleteWaypoint(final cgWaypoint waypoint) { - if (waypoint.getId() <= 0) { - return false; - } + public void deleteWaypointForce(Waypoint waypoint) { final int index = getWaypointIndex(waypoint); - return index >= 0 && deleteWaypoint(index); + waypoints.remove(index); + cgData.deleteWaypoint(waypoint.getId()); + cgData.removeCache(geocode, EnumSet.of(RemoveFlag.REMOVE_CACHE)); + resetFinalDefined(); } /** @@ -1270,7 +1319,7 @@ public class cgCache implements ICache, IWaypoint { * to find index for * @return index in <code>waypoints</code> if found, -1 otherwise */ - private int getWaypointIndex(final cgWaypoint waypoint) { + private int getWaypointIndex(final Waypoint waypoint) { final int id = waypoint.getId(); for (int index = 0; index < waypoints.size(); index++) { if (waypoints.get(index).getId() == id) { @@ -1283,21 +1332,23 @@ public class cgCache implements ICache, IWaypoint { /** * Retrieve a given waypoint. * - * @param index the index of the waypoint + * @param index + * the index of the waypoint * @return waypoint or <code>null</code> if index is out of range */ - public cgWaypoint getWaypoint(final int index) { + public Waypoint getWaypoint(final int index) { return index >= 0 && index < waypoints.size() ? waypoints.get(index) : null; } /** * Lookup a waypoint by its id. * - * @param id the id of the waypoint to look for + * @param id + * the id of the waypoint to look for * @return waypoint or <code>null</code> */ - public cgWaypoint getWaypointById(final int id) { - for (final cgWaypoint waypoint : waypoints) { + public Waypoint getWaypointById(final int id) { + for (final Waypoint waypoint : waypoints) { if (waypoint.getId() == id) { return waypoint; } @@ -1313,14 +1364,14 @@ public class cgCache implements ICache, IWaypoint { final Pattern coordPattern = Pattern.compile("\\b[nNsS]{1}\\s*\\d"); // begin of coordinates int count = 1; String note = getPersonalNote(); - Matcher matcher = coordPattern.matcher(note); + MatcherWrapper matcher = new MatcherWrapper(coordPattern, note); while (matcher.find()) { try { final Geopoint point = new Geopoint(note.substring(matcher.start())); // coords must have non zero latitude and longitude and at least one part shall have fractional degrees if (point.getLatitudeE6() != 0 && point.getLongitudeE6() != 0 && ((point.getLatitudeE6() % 1000) != 0 || (point.getLongitudeE6() % 1000) != 0)) { final String name = cgeoapplication.getInstance().getString(R.string.cache_personal_note) + " " + count; - final cgWaypoint waypoint = new cgWaypoint(name, WaypointType.WAYPOINT, false); + final Waypoint waypoint = new Waypoint(name, WaypointType.WAYPOINT, false); waypoint.setCoords(point); addOrChangeWaypoint(waypoint, false); count++; @@ -1330,10 +1381,10 @@ public class cgCache implements ICache, IWaypoint { } note = note.substring(matcher.start() + 1); - matcher = coordPattern.matcher(note); + matcher = new MatcherWrapper(coordPattern, note); } } catch (Exception e) { - Log.e("cgCache.parseWaypointsFromNote: " + e.toString()); + Log.e("Geocache.parseWaypointsFromNote", e); } } @@ -1358,16 +1409,22 @@ public class cgCache implements ICache, IWaypoint { if (this == obj) { return true; } - if (!(obj instanceof cgCache)) { + if (!(obj instanceof Geocache)) { return false; } // just compare the geocode even if that is not what "equals" normally does - return StringUtils.isNotBlank(geocode) && geocode.equals(((cgCache) obj).geocode); + return StringUtils.isNotBlank(geocode) && geocode.equals(((Geocache) obj).geocode); } public void store(CancellableHandler handler) { - final int listId = Math.max(getListId(), StoredList.STANDARD_LIST_ID); - storeCache(this, null, listId, false, handler); + store(StoredList.TEMPORARY_LIST_ID, handler); + } + + public void store(final int listId, CancellableHandler handler) { + int newListId = listId < StoredList.STANDARD_LIST_ID + ? Math.max(getListId(), StoredList.STANDARD_LIST_ID) + : listId; + storeCache(this, null, newListId, false, handler); } public void setZoomlevel(int zoomlevel) { @@ -1391,8 +1448,8 @@ public class cgCache implements ICache, IWaypoint { public void drop(Handler handler) { try { - cgeoapplication.getInstance().markDropped(Collections.singletonList(this)); - cgeoapplication.getInstance().removeCache(getGeocode(), EnumSet.of(RemoveFlag.REMOVE_CACHE)); + cgData.markDropped(Collections.singletonList(this)); + cgData.removeCache(getGeocode(), EnumSet.of(RemoveFlag.REMOVE_CACHE)); handler.sendMessage(Message.obtain()); } catch (Exception e) { @@ -1402,54 +1459,54 @@ public class cgCache implements ICache, IWaypoint { public void checkFields() { if (StringUtils.isBlank(getGeocode())) { - Log.e("geo code not parsed correctly"); + Log.w("geo code not parsed correctly for " + geocode); } if (StringUtils.isBlank(getName())) { - Log.e("name not parsed correctly"); + Log.w("name not parsed correctly for " + geocode); } if (StringUtils.isBlank(getGuid())) { - Log.e("guid not parsed correctly"); + Log.w("guid not parsed correctly for " + geocode); } if (getTerrain() == 0.0) { - Log.e("terrain not parsed correctly"); + Log.w("terrain not parsed correctly for " + geocode); } if (getDifficulty() == 0.0) { - Log.e("difficulty not parsed correctly"); + Log.w("difficulty not parsed correctly for " + geocode); } if (StringUtils.isBlank(getOwnerDisplayName())) { - Log.e("owner display name not parsed correctly"); + Log.w("owner display name not parsed correctly for " + geocode); } if (StringUtils.isBlank(getOwnerUserId())) { - Log.e("owner user id real not parsed correctly"); + Log.w("owner user id real not parsed correctly for " + geocode); } if (getHiddenDate() == null) { - Log.e("hidden not parsed correctly"); + Log.w("hidden not parsed correctly for " + geocode); } if (getFavoritePoints() < 0) { - Log.e("favoriteCount not parsed correctly"); + Log.w("favoriteCount not parsed correctly for " + geocode); } if (getSize() == null) { - Log.e("size not parsed correctly"); + Log.w("size not parsed correctly for " + geocode); } if (getType() == null || getType() == CacheType.UNKNOWN) { - Log.e("type not parsed correctly"); + Log.w("type not parsed correctly for " + geocode); } if (getCoords() == null) { - Log.e("coordinates not parsed correctly"); + Log.w("coordinates not parsed correctly for " + geocode); } if (StringUtils.isBlank(getLocation())) { - Log.e("location not parsed correctly"); + Log.w("location not parsed correctly for " + geocode); } } public void refresh(int newListId, CancellableHandler handler) { - cgeoapplication.getInstance().removeCache(geocode, EnumSet.of(RemoveFlag.REMOVE_CACHE)); + cgData.removeCache(geocode, EnumSet.of(RemoveFlag.REMOVE_CACHE)); storeCache(null, geocode, newListId, true, handler); } - public static void storeCache(cgCache origCache, String geocode, int listId, boolean forceRedownload, CancellableHandler handler) { + public static void storeCache(Geocache origCache, String geocode, int listId, boolean forceRedownload, CancellableHandler handler) { try { - cgCache cache; + Geocache cache; // get cache details, they may not yet be complete if (origCache != null) { // only reload the cache if it was already stored or doesn't have full details (by checking the description) @@ -1491,7 +1548,7 @@ public class cgCache implements ICache, IWaypoint { // store spoilers if (CollectionUtils.isNotEmpty(cache.getSpoilers())) { - for (cgImage oneSpoiler : cache.getSpoilers()) { + for (Image oneSpoiler : cache.getSpoilers()) { imgGetter.getDrawable(oneSpoiler.getUrl()); } } @@ -1504,7 +1561,7 @@ public class cgCache implements ICache, IWaypoint { if (Settings.isStoreLogImages()) { for (LogEntry log : cache.getLogs()) { if (log.hasLogImages()) { - for (cgImage oneLogImg : log.getLogImages()) { + for (Image oneLogImg : log.getLogImages()) { imgGetter.getDrawable(oneLogImg.getUrl()); } } @@ -1516,7 +1573,7 @@ public class cgCache implements ICache, IWaypoint { } cache.setListId(listId); - cgeoapplication.getInstance().saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); + cgData.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); if (CancellableHandler.isCancelled(handler)) { return; @@ -1534,14 +1591,13 @@ public class cgCache implements ICache, IWaypoint { public static SearchResult searchByGeocode(final String geocode, final String guid, final int listId, final boolean forceReload, final CancellableHandler handler) { if (StringUtils.isBlank(geocode) && StringUtils.isBlank(guid)) { - Log.e("cgCache.searchByGeocode: No geocode nor guid given"); + Log.e("Geocache.searchByGeocode: No geocode nor guid given"); return null; } - final cgeoapplication app = cgeoapplication.getInstance(); - if (!forceReload && listId == StoredList.TEMPORARY_LIST_ID && (app.isOffline(geocode, guid) || app.isThere(geocode, guid, true, true))) { + if (!forceReload && listId == StoredList.TEMPORARY_LIST_ID && (cgData.isOffline(geocode, guid) || cgData.isThere(geocode, guid, true, true))) { final SearchResult search = new SearchResult(); - final String realGeocode = StringUtils.isNotBlank(geocode) ? geocode : app.getGeocode(guid); + final String realGeocode = StringUtils.isNotBlank(geocode) ? geocode : cgData.getGeocodeForGuid(guid); search.addGeocode(realGeocode); return search; } @@ -1573,7 +1629,7 @@ public class cgCache implements ICache, IWaypoint { } // 12:34 final Pattern time = Pattern.compile("\\b(\\d{1,2})\\:(\\d\\d)\\b"); - final Matcher matcher = time.matcher(getDescription()); + final MatcherWrapper matcher = new MatcherWrapper(time, getDescription()); while (matcher.find()) { try { final int hours = Integer.valueOf(matcher.group(1)); @@ -1589,7 +1645,7 @@ public class cgCache implements ICache, IWaypoint { final String hourLocalized = cgeoapplication.getInstance().getString(R.string.cache_time_full_hours); if (StringUtils.isNotBlank(hourLocalized)) { final Pattern fullHours = Pattern.compile("\\b(\\d{1,2})\\s+" + Pattern.quote(hourLocalized), Pattern.CASE_INSENSITIVE); - final Matcher matcherHours = fullHours.matcher(getDescription()); + final MatcherWrapper matcherHours = new MatcherWrapper(fullHours, getDescription()); if (matcherHours.find()) { try { final int hours = Integer.valueOf(matcherHours.group(1)); @@ -1613,8 +1669,7 @@ public class cgCache implements ICache, IWaypoint { * @return */ public boolean hasAttribute(CacheAttribute attribute, boolean yes) { - // lazy loading of attributes - cgCache fullCache = cgeoapplication.getInstance().loadCache(getGeocode(), EnumSet.of(LoadFlag.LOAD_ATTRIBUTES)); + Geocache fullCache = cgData.loadCache(getGeocode(), EnumSet.of(LoadFlag.LOAD_ATTRIBUTES)); if (fullCache == null) { fullCache = this; } @@ -1625,12 +1680,32 @@ public class cgCache implements ICache, IWaypoint { return StaticMapsProvider.hasStaticMap(this); } - public List<cgImage> getImages() { - List<cgImage> result = new ArrayList<cgImage>(); + public List<Image> getImages() { + List<Image> result = new ArrayList<Image>(); result.addAll(getSpoilers()); for (LogEntry log : getLogs()) { result.addAll(log.getLogImages()); } return result; } + + public void setDetailedUpdatedNow() { + final long now = System.currentTimeMillis(); + setUpdated(now); + setDetailedUpdate(now); + setDetailed(true); + } + + /** + * Gets whether the user has logged the specific log type for this cache. Only checks the currently stored logs of + * the cache, so the result might be wrong. + */ + public boolean hasOwnLog(LogType logType) { + for (LogEntry logEntry : getLogs()) { + if (logEntry.type == logType && logEntry.isOwn()) { + return true; + } + } + return false; + } } diff --git a/main/src/cgeo/geocaching/cgeogpxes.java b/main/src/cgeo/geocaching/GpxFileListActivity.java index 909263f..f12a30c 100644 --- a/main/src/cgeo/geocaching/cgeogpxes.java +++ b/main/src/cgeo/geocaching/GpxFileListActivity.java @@ -2,7 +2,7 @@ package cgeo.geocaching; import cgeo.geocaching.connector.ConnectorFactory;
import cgeo.geocaching.connector.IConnector;
-import cgeo.geocaching.files.FileList;
+import cgeo.geocaching.files.AbstractFileListActivity;
import cgeo.geocaching.files.GPXImporter;
import cgeo.geocaching.ui.GPXListAdapter;
@@ -10,21 +10,17 @@ import org.apache.commons.lang3.StringUtils; import android.app.Activity;
import android.content.Intent;
-import android.os.Bundle;
import java.io.File;
import java.util.Collections;
import java.util.List;
-public class cgeogpxes extends FileList<GPXListAdapter> {
- private static final String EXTRAS_LIST_ID = "list";
+public class GpxFileListActivity extends AbstractFileListActivity<GPXListAdapter> {
- public cgeogpxes() {
+ public GpxFileListActivity() {
super(new String[] { "gpx", "loc", "zip" });
}
- private int listId = StoredList.STANDARD_LIST_ID;
-
@Override
protected GPXListAdapter getAdapter(List<File> files) {
return new GPXListAdapter(this, files);
@@ -36,26 +32,13 @@ public class cgeogpxes extends FileList<GPXListAdapter> { }
@Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- final Bundle extras = getIntent().getExtras();
- if (extras != null) {
- listId = extras.getInt(EXTRAS_LIST_ID);
- }
- if (listId <= StoredList.TEMPORARY_LIST_ID) {
- listId = StoredList.STANDARD_LIST_ID;
- }
- }
-
- @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, cgeogpxes.class);
- intent.putExtra(EXTRAS_LIST_ID, listId);
+ final Intent intent = new Intent(fromActivity, GpxFileListActivity.class);
+ intent.putExtra(Intents.EXTRA_LIST_ID, listId);
fromActivity.startActivityForResult(intent, 0);
}
diff --git a/main/src/cgeo/geocaching/ICache.java b/main/src/cgeo/geocaching/ICache.java index 8f8baf9..6d0d89a 100644 --- a/main/src/cgeo/geocaching/ICache.java +++ b/main/src/cgeo/geocaching/ICache.java @@ -4,7 +4,6 @@ package cgeo.geocaching; import cgeo.geocaching.enumerations.LogType; -import cgeo.geocaching.utils.LazyInitializedList; import java.util.Date; import java.util.List; @@ -28,7 +27,7 @@ public interface ICache extends IBasicCache { /** * @return true if the user is the owner of the cache, false else */ - public boolean isOwn(); + public boolean isOwner(); /** * @return true is the cache is archived, false else @@ -107,17 +106,17 @@ public interface ICache extends IBasicCache { * * @return the list of attributes for this cache */ - public LazyInitializedList<String> getAttributes(); + public List<String> getAttributes(); /** * @return the list of trackables in this cache */ - public List<cgTrackable> getInventory(); + public List<Trackable> getInventory(); /** * @return the list of spoiler images */ - public List<cgImage> getSpoilers(); + public List<Image> getSpoilers(); /** * @return a statistic how often the caches has been found, disabled, archived etc. diff --git a/main/src/cgeo/geocaching/IWaypoint.java b/main/src/cgeo/geocaching/IWaypoint.java index 78e3b43..2b992e1 100644 --- a/main/src/cgeo/geocaching/IWaypoint.java +++ b/main/src/cgeo/geocaching/IWaypoint.java @@ -1,12 +1,14 @@ -/** - * - */ package cgeo.geocaching; import cgeo.geocaching.enumerations.WaypointType; public interface IWaypoint extends ILogable, ICoordinates { + /** + * Return an unique waypoint id. + * + * @return a non-negative id if set, -1 if unset + */ public abstract int getId(); public abstract WaypointType getWaypointType(); diff --git a/main/src/cgeo/geocaching/cgImage.java b/main/src/cgeo/geocaching/Image.java index b313ef5..22c76aa 100644 --- a/main/src/cgeo/geocaching/cgImage.java +++ b/main/src/cgeo/geocaching/Image.java @@ -8,22 +8,22 @@ import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; -public class cgImage implements Parcelable { +public class Image implements Parcelable { private final String url; private final String title; private final String description; - public cgImage(final String url, final String title, final String description) { + public Image(final String url, final String title, final String description) { this.url = url; this.title = title; this.description = description; } - public cgImage(final String url, final String title) { + public Image(final String url, final String title) { this(url, title, null); } - public cgImage(final Parcel in) { + public Image(final Parcel in) { url = in.readString(); title = in.readString(); description = in.readString(); @@ -41,15 +41,15 @@ public class cgImage implements Parcelable { dest.writeString(description); } - public static final Parcelable.Creator<cgImage> CREATOR = new Parcelable.Creator<cgImage>() { + public static final Parcelable.Creator<Image> CREATOR = new Parcelable.Creator<Image>() { @Override - public cgImage createFromParcel(Parcel in) { - return new cgImage(in); + public Image createFromParcel(Parcel in) { + return new Image(in); } @Override - public cgImage[] newArray(int size) { - return new cgImage[size]; + public Image[] newArray(int size) { + return new Image[size]; } }; diff --git a/main/src/cgeo/geocaching/ImageSelectActivity.java b/main/src/cgeo/geocaching/ImageSelectActivity.java new file mode 100644 index 0000000..08622a9 --- /dev/null +++ b/main/src/cgeo/geocaching/ImageSelectActivity.java @@ -0,0 +1,252 @@ +package cgeo.geocaching; + +import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.compatibility.Compatibility; +import cgeo.geocaching.utils.Log; + +import org.apache.commons.lang3.StringUtils; + +import android.content.Intent; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.Bundle; +import android.provider.MediaStore; +import android.provider.MediaStore.MediaColumns; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ImageView; + +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +public class ImageSelectActivity extends AbstractActivity { + static final String EXTRAS_CAPTION = "caption"; + static final String EXTRAS_DESCRIPTION = "description"; + static final String EXTRAS_URI_AS_STRING = "uri"; + + 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 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 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)); + + imageCaption = ""; + imageDescription = ""; + imageUri = Uri.EMPTY; + + // Get parameters from intent and basic cache information from database + final Bundle extras = getIntent().getExtras(); + if (extras != null) { + imageCaption = extras.getString(EXTRAS_CAPTION); + imageDescription = extras.getString(EXTRAS_DESCRIPTION); + imageUri = Uri.parse(extras.getString(EXTRAS_URI_AS_STRING)); + } + + // Restore previous state + if (savedInstanceState != null) { + imageCaption = savedInstanceState.getString(SAVED_STATE_IMAGE_CAPTION); + imageDescription = savedInstanceState.getString(SAVED_STATE_IMAGE_DESCRIPTION); + imageUri = Uri.parse(savedInstanceState.getString(SAVED_STATE_IMAGE_URI)); + } + + final Button cameraButton = (Button) findViewById(R.id.camera); + cameraButton.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View view) { + selectImageFromCamera(); + } + }); + + final Button storedButton = (Button) findViewById(R.id.stored); + storedButton.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View view) { + selectImageFromStorage(); + } + }); + + 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); + saveButton.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + saveImageInfo(true); + } + }); + + final Button clearButton = (Button) findViewById(R.id.cancel); + clearButton.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + saveImageInfo(false); + } + }); + + loadImagePreview(); + } + + @Override + protected void onSaveInstanceState(final Bundle outState) { + super.onSaveInstanceState(outState); + syncEditTexts(); + 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); + } + + public void saveImageInfo(boolean saveInfo) { + if (saveInfo) { + Intent intent = new Intent(); + syncEditTexts(); + intent.putExtra(EXTRAS_CAPTION, imageCaption); + intent.putExtra(EXTRAS_DESCRIPTION, imageDescription); + intent.putExtra(EXTRAS_URI_AS_STRING, imageUri.toString()); + + setResult(RESULT_OK, intent); + } else { + setResult(RESULT_CANCELED); + } + + finish(); + } + + private void syncEditTexts() { + imageCaption = captionView.getText().toString(); + imageDescription = descriptionView.getText().toString(); + } + + private void selectImageFromCamera() { + // create Intent to take a picture and return control to the calling application + Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + + imageUri = getOutputImageFileUri(); // create a file to save the image + intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri); // set the image file name + + // start the image capture Intent + startActivityForResult(intent, SELECT_NEW_IMAGE); + } + + private void selectImageFromStorage() { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType("image/jpeg"); + + startActivityForResult(Intent.createChooser(intent, "Select Image"), SELECT_STORED_IMAGE); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode == RESULT_CANCELED) { + // User cancelled the image capture + showToast(getResources().getString(R.string.info_select_logimage_cancelled)); + return; + } + + if (resultCode == RESULT_OK) { + if (data != null) { + Uri selectedImage = data.getData(); + String[] filePathColumn = { MediaColumns.DATA }; + + Cursor cursor = getContentResolver().query(selectedImage, filePathColumn, null, null, null); + cursor.moveToFirst(); + + int columnIndex = cursor.getColumnIndex(filePathColumn[0]); + String filePath = cursor.getString(columnIndex); + imageUri = Uri.parse(filePath); + cursor.close(); + + Log.d("SELECT IMAGE data = " + data.toString()); + } else { + Log.d("SELECT IMAGE data is null"); + } + + if (requestCode == SELECT_NEW_IMAGE) { + showToast(getResources().getString(R.string.info_stored_image) + "\n" + imageUri); + } + } else { + // Image capture failed, advise user + showToast(getResources().getString(R.string.err_aquire_image_failed)); + return; + } + + loadImagePreview(); + } + + private void loadImagePreview() { + if (!new File(imageUri.getPath()).exists()) { + Log.i("Image does not exist"); + 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); + imagePreview.setImageBitmap(bitmap); + imagePreview.setVisibility(View.VISIBLE); + } + + private static Uri getOutputImageFileUri() { + return Uri.fromFile(getOutputImageFile()); + } + + /** Create a File for saving an image or video */ + private static File getOutputImageFile() { + // To be safe, you should check that the SDCard is mounted + // using Environment.getExternalStorageState() before doing this. + + File mediaStorageDir = new File(Compatibility.getExternalPictureDir(), "cgeo"); + // This location works best if you want the created images to be shared + // between applications and persist after your app has been uninstalled. + + // Create the storage directory if it does not exist + if (!mediaStorageDir.exists()) { + if (!mediaStorageDir.mkdirs()) { + Log.w("Failed to create directory"); + return null; + } + } + + // Create a media file name + String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date()); + return new File(mediaStorageDir.getPath() + File.separator + "IMG_" + timeStamp + ".jpg"); + } +} diff --git a/main/src/cgeo/geocaching/ImagesActivity.java b/main/src/cgeo/geocaching/ImagesActivity.java index 0accf12..24f699e 100644 --- a/main/src/cgeo/geocaching/ImagesActivity.java +++ b/main/src/cgeo/geocaching/ImagesActivity.java @@ -24,11 +24,10 @@ public class ImagesActivity extends AbstractActivity { private static final String EXTRAS_GEOCODE = "geocode"; private boolean offline; - private ArrayList<cgImage> imageNames; + private ArrayList<Image> imageNames; private ImagesList imagesList; private ImageType imgType = ImageType.SpoilerImages; - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -62,7 +61,7 @@ public class ImagesActivity extends AbstractActivity { return; } - offline = app.isOffline(geocode, null) && (imgType == ImageType.SpoilerImages || Settings.isStoreLogImages()); + offline = cgData.isOffline(geocode, null) && (imgType == ImageType.SpoilerImages || Settings.isStoreLogImages()); } @Override @@ -78,24 +77,24 @@ public class ImagesActivity extends AbstractActivity { super.onStop(); } - public static void startActivityLogImages(final Context fromActivity, final String geocode, List<cgImage> logImages) { + public static void startActivityLogImages(final Context fromActivity, final String geocode, List<Image> logImages) { startActivity(fromActivity, geocode, logImages, ImageType.LogImages); } - private static void startActivity(final Context fromActivity, final String geocode, List<cgImage> logImages, ImageType imageType) { + private static void startActivity(final Context fromActivity, final String geocode, List<Image> logImages, ImageType imageType) { 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.toUpperCase()) + .putExtra(EXTRAS_GEOCODE, geocode) .putExtra(EXTRAS_TYPE, imageType); // avoid forcing the array list as parameter type - final ArrayList<cgImage> arrayList = new ArrayList<cgImage>(logImages); + final ArrayList<Image> arrayList = new ArrayList<Image>(logImages); logImgIntent.putParcelableArrayListExtra(EXTRAS_IMAGES, arrayList); fromActivity.startActivity(logImgIntent); } - public static void startActivitySpoilerImages(final Context fromActivity, String geocode, List<cgImage> spoilers) { + public static void startActivitySpoilerImages(final Context fromActivity, String geocode, List<Image> spoilers) { startActivity(fromActivity, geocode, spoilers, ImageType.SpoilerImages); } diff --git a/main/src/cgeo/geocaching/Intents.java b/main/src/cgeo/geocaching/Intents.java new file mode 100644 index 0000000..07e1c13 --- /dev/null +++ b/main/src/cgeo/geocaching/Intents.java @@ -0,0 +1,28 @@ +package cgeo.geocaching; + +public class Intents { + + private Intents() { + // Do not instantiate + } + + 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_COUNT = PREFIX + "count"; + public static final String EXTRA_GEOCODE = PREFIX + "geocode"; + public static final String EXTRA_GUID = PREFIX + "guid"; + 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"; + public static final String EXTRA_LIST_ID = PREFIX + "list_id"; + public static final String EXTRA_LIST_TYPE = PREFIX + "list_type"; + public static final String EXTRA_MAP_FILE = PREFIX + "map_file"; + public static final String EXTRA_NAME = PREFIX + "name"; + 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_USERNAME = PREFIX + "username"; + public static final String EXTRA_WAYPOINT_ID = PREFIX + "waypoint_id"; +} diff --git a/main/src/cgeo/geocaching/LiveMapInfo.java b/main/src/cgeo/geocaching/LiveMapInfo.java deleted file mode 100644 index 2fee940..0000000 --- a/main/src/cgeo/geocaching/LiveMapInfo.java +++ /dev/null @@ -1,52 +0,0 @@ -package cgeo.geocaching; - -import cgeo.geocaching.activity.AbstractActivity; - -import android.os.Bundle; -import android.view.View; -import android.widget.Button; -import android.widget.CheckBox; - -public class LiveMapInfo extends AbstractActivity { - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setContentView(R.layout.livemapinfo); - - final int showCount = Settings.getLiveMapHintShowCount(); - - if (showCount > 2) { - final CheckBox cb = (CheckBox) findViewById(R.id.live_map_hint_hide); - cb.setVisibility(View.VISIBLE); - } - - final Button closeButton = (Button) findViewById(R.id.live_map_hint_ok); - closeButton.setOnClickListener(new View.OnClickListener() { - - @Override - public void onClick(View v) { - final CheckBox cb = (CheckBox) findViewById(R.id.live_map_hint_hide); - if (cb.isChecked()) { - Settings.setHideLiveHint(true); - } - finish(); - } - }); - - Settings.setLiveMapHintShowCount(showCount + 1); - } - - @Override - protected void onStop() { - - final CheckBox cb = (CheckBox) findViewById(R.id.live_map_hint_hide); - if (cb.isChecked()) { - Settings.setHideLiveHint(true); - } - - super.onStop(); - } - -} diff --git a/main/src/cgeo/geocaching/LogEntry.java b/main/src/cgeo/geocaching/LogEntry.java index b625bb5..df1038f 100644 --- a/main/src/cgeo/geocaching/LogEntry.java +++ b/main/src/cgeo/geocaching/LogEntry.java @@ -2,6 +2,7 @@ package cgeo.geocaching; import cgeo.geocaching.enumerations.LogType; import cgeo.geocaching.utils.DateUtils; +import cgeo.geocaching.utils.MatcherWrapper; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; @@ -10,7 +11,6 @@ import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.List; -import java.util.regex.Matcher; import java.util.regex.Pattern; public final class LogEntry { @@ -25,7 +25,7 @@ public final class LogEntry { public int found = -1; /** Friend's log entry */ public boolean friend = false; - private List<cgImage> logImages = null; + private List<Image> logImages = null; public String cacheName = ""; // used for trackables public String cacheGuid = ""; // used for trackables @@ -64,9 +64,9 @@ public final class LogEntry { log.compareTo(otherLog.log) == 0; } - public void addLogImage(final cgImage image) { + public void addLogImage(final Image image) { if (logImages == null) { - logImages = new ArrayList<cgImage>(); + logImages = new ArrayList<Image>(); } logImages.add(image); } @@ -74,7 +74,7 @@ public final class LogEntry { /** * @return the log images or an empty list, never <code>null</code> */ - public List<cgImage> getLogImages() { + public List<Image> getLogImages() { if (logImages == null) { return Collections.emptyList(); } @@ -87,7 +87,7 @@ public final class LogEntry { public CharSequence getImageTitles() { final List<String> titles = new ArrayList<String>(5); - for (cgImage image : getLogImages()) { + for (Image image : getLogImages()) { if (StringUtils.isNotBlank(image.getTitle())) { titles.add(image.getTitle()); } @@ -107,9 +107,13 @@ public final class LogEntry { */ public String getDisplayText() { if (Settings.getPlainLogs()) { - Matcher matcher = PATTERN_REMOVE_COLORS.matcher(log); + MatcherWrapper matcher = new MatcherWrapper(PATTERN_REMOVE_COLORS, log); return matcher.replaceAll(""); } return log; } + + public boolean isOwn() { + return author.equalsIgnoreCase(Settings.getUsername()); + } } diff --git a/main/src/cgeo/geocaching/LogTrackableActivity.java b/main/src/cgeo/geocaching/LogTrackableActivity.java index 4ddec72..b8983ba 100644 --- a/main/src/cgeo/geocaching/LogTrackableActivity.java +++ b/main/src/cgeo/geocaching/LogTrackableActivity.java @@ -7,8 +7,8 @@ import cgeo.geocaching.enumerations.StatusCode; import cgeo.geocaching.network.Network; import cgeo.geocaching.network.Parameters; import cgeo.geocaching.twitter.Twitter; -import cgeo.geocaching.ui.DateDialog; import cgeo.geocaching.ui.Formatter; +import cgeo.geocaching.ui.dialog.DateDialog; import cgeo.geocaching.utils.Log; import cgeo.geocaching.utils.LogTemplateProvider.LogContext; @@ -47,7 +47,7 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat private int attempts = 0; private CheckBox tweetCheck = null; private LinearLayout tweetBox = null; - private cgTrackable trackable; + private Trackable trackable; private Handler showProgressHandler = new Handler() { @Override @@ -119,20 +119,20 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat // get parameters final Bundle extras = getIntent().getExtras(); if (extras != null) { - geocode = extras.getString("geocode"); - guid = extras.getString("guid"); + geocode = extras.getString(Intents.EXTRA_GEOCODE); + guid = extras.getString(Intents.EXTRA_GUID); - if (StringUtils.isNotBlank(extras.getString("trackingcode"))) { - ((EditText) findViewById(R.id.tracking)).setText(extras.getString("trackingcode")); + if (StringUtils.isNotBlank(extras.getString(Intents.EXTRA_TRACKING_CODE))) { + ((EditText) findViewById(R.id.tracking)).setText(extras.getString(Intents.EXTRA_TRACKING_CODE)); } } - trackable = app.getTrackableByGeocode(geocode); + trackable = cgData.loadTrackable(geocode); if (StringUtils.isNotBlank(trackable.getName())) { setTitle(res.getString(R.string.trackable_touch) + ": " + trackable.getName()); } else { - setTitle(res.getString(R.string.trackable_touch) + ": " + trackable.getGeocode().toUpperCase()); + setTitle(res.getString(R.string.trackable_touch) + ": " + trackable.getGeocode()); } if (guid == null) { @@ -207,7 +207,7 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat tweetCheck.setChecked(true); if (CollectionUtils.isEmpty(possibleLogTypes)) { - possibleLogTypes = cgTrackable.getPossibleLogTypes(); + possibleLogTypes = Trackable.getPossibleLogTypes(); } final Button buttonPost = (Button) findViewById(R.id.post); @@ -311,7 +311,7 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat possibleLogTypes.addAll(typesPre); } } catch (Exception e) { - Log.e("LogTrackableActivity.LoadDataThread.run: " + e.toString()); + Log.e("LogTrackableActivity.LoadDataThread.run", e); } loadDataHandler.sendEmptyMessage(0); @@ -349,17 +349,17 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat return status; } catch (Exception e) { - Log.e("LogTrackableActivity.postLogFn: " + e.toString()); + Log.e("LogTrackableActivity.postLogFn", e); } return StatusCode.LOG_POST_ERROR; } - public static void startActivity(final Context context, final cgTrackable trackable) { + public static void startActivity(final Context context, final Trackable trackable) { final Intent logTouchIntent = new Intent(context, LogTrackableActivity.class); - logTouchIntent.putExtra("geocode", trackable.getGeocode().toUpperCase()); - logTouchIntent.putExtra("guid", trackable.getGuid()); - logTouchIntent.putExtra("trackingcode", trackable.getTrackingcode()); + logTouchIntent.putExtra(Intents.EXTRA_GEOCODE, trackable.getGeocode()); + logTouchIntent.putExtra(Intents.EXTRA_GUID, trackable.getGuid()); + logTouchIntent.putExtra(Intents.EXTRA_TRACKING_CODE, trackable.getTrackingcode()); context.startActivity(logTouchIntent); } diff --git a/main/src/cgeo/geocaching/cgeopoint.java b/main/src/cgeo/geocaching/NavigateAnyPointActivity.java index 025b96a..74f656b 100644 --- a/main/src/cgeo/geocaching/cgeopoint.java +++ b/main/src/cgeo/geocaching/NavigateAnyPointActivity.java @@ -6,6 +6,7 @@ import cgeo.geocaching.geopoint.DistanceParser; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.GeopointFormatter; import cgeo.geocaching.ui.Formatter; +import cgeo.geocaching.ui.dialog.CoordinatesInputDialog; import cgeo.geocaching.utils.GeoDirHandler; import cgeo.geocaching.utils.Log; @@ -35,7 +36,7 @@ import android.widget.TextView; import java.util.List; -public class cgeopoint extends AbstractActivity { +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; @@ -101,7 +102,7 @@ public class cgeopoint extends AbstractActivity { String distanceUnit = ""; - public cgeopoint() { + public NavigateAnyPointActivity() { super("c:geo-navigate-any"); } @@ -207,7 +208,7 @@ public class cgeopoint extends AbstractActivity { private List<Destination> getHistoryOfSearchedLocations() { if (historyOfSearchedLocations == null) { // Load from database - historyOfSearchedLocations = app.getHistoryOfSearchedLocations(); + historyOfSearchedLocations = cgData.loadHistoryOfSearchedLocations(); } return historyOfSearchedLocations; @@ -247,8 +248,8 @@ public class cgeopoint extends AbstractActivity { latButton = (Button) findViewById(R.id.buttonLatitude); lonButton = (Button) findViewById(R.id.buttonLongitude); - latButton.setOnClickListener(new coordDialogListener()); - lonButton.setOnClickListener(new coordDialogListener()); + latButton.setOnClickListener(new CoordDialogListener()); + lonButton.setOnClickListener(new CoordDialogListener()); final Geopoint coords = Settings.getAnyCoordinates(); if (coords != null) { @@ -257,7 +258,7 @@ public class cgeopoint extends AbstractActivity { } Button buttonCurrent = (Button) findViewById(R.id.current); - buttonCurrent.setOnClickListener(new currentListener()); + buttonCurrent.setOnClickListener(new CurrentListener()); getDestionationHistoryAdapter().notifyDataSetChanged(); disableSuggestions((EditText) findViewById(R.id.distance)); @@ -279,10 +280,10 @@ public class cgeopoint extends AbstractActivity { } } - distanceUnitSelector.setOnItemSelectedListener(new changeDistanceUnit(this)); + distanceUnitSelector.setOnItemSelectedListener(new ChangeDistanceUnit(this)); } - private class coordDialogListener implements View.OnClickListener { + private class CoordDialogListener implements View.OnClickListener { @Override public void onClick(View arg0) { @@ -290,9 +291,9 @@ public class cgeopoint extends AbstractActivity { if (latButton.getText().length() > 0 && lonButton.getText().length() > 0) { gp = new Geopoint(latButton.getText().toString() + " " + lonButton.getText().toString()); } - cgeocoords coordsDialog = new cgeocoords(cgeopoint.this, null, gp, app.currentGeo()); + CoordinatesInputDialog coordsDialog = new CoordinatesInputDialog(NavigateAnyPointActivity.this, null, gp, app.currentGeo()); coordsDialog.setCancelable(true); - coordsDialog.setOnCoordinateUpdate(new cgeocoords.CoordinateUpdate() { + coordsDialog.setOnCoordinateUpdate(new CoordinatesInputDialog.CoordinateUpdate() { @Override public void update(Geopoint gp) { latButton.setText(gp.format(GeopointFormatter.Format.LAT_DECMINUTE)); @@ -304,13 +305,13 @@ public class cgeopoint extends AbstractActivity { } } - private class changeDistanceUnit implements OnItemSelectedListener { + private static class ChangeDistanceUnit implements OnItemSelectedListener { - private changeDistanceUnit(cgeopoint unitView) { + private ChangeDistanceUnit(NavigateAnyPointActivity unitView) { this.unitView = unitView; } - private cgeopoint unitView; + private NavigateAnyPointActivity unitView; @Override public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, @@ -393,7 +394,7 @@ public class cgeopoint extends AbstractActivity { getHistoryOfSearchedLocations().add(0, loc); // Save location - app.saveSearchedDestination(loc); + cgData.saveSearchedDestination(loc); // Ensure to remove the footer historyListView.removeFooterView(getEmptyHistoryFooter()); @@ -405,7 +406,7 @@ public class cgeopoint extends AbstractActivity { getHistoryOfSearchedLocations().remove(destination); // Save - app.removeSearchedDestinations(destination); + cgData.removeSearchedDestination(destination); if (getHistoryOfSearchedLocations().isEmpty()) { if (historyListView.getFooterViewsCount() == 0) { @@ -424,7 +425,7 @@ public class cgeopoint extends AbstractActivity { getHistoryOfSearchedLocations().clear(); // Save - app.clearSearchedDestinations(); + cgData.clearSearchedDestinations(); if (historyListView.getFooterViewsCount() == 0) { historyListView.addFooterView(getEmptyHistoryFooter()); @@ -469,7 +470,7 @@ public class cgeopoint extends AbstractActivity { } }; - private class currentListener implements View.OnClickListener { + private class CurrentListener implements View.OnClickListener { @Override public void onClick(View arg0) { @@ -487,8 +488,6 @@ public class cgeopoint extends AbstractActivity { } private Geopoint getDestination() { - Geopoint result; - Geopoint coords; String bearingText = ((EditText) findViewById(R.id.bearing)).getText().toString(); // combine distance from EditText and distanceUnit saved from Spinner @@ -502,6 +501,7 @@ public class cgeopoint extends AbstractActivity { return null; } + Geopoint coords; if (StringUtils.isNotBlank(latText) && StringUtils.isNotBlank(lonText)) { try { coords = new Geopoint(latText, lonText); @@ -518,6 +518,7 @@ public class cgeopoint extends AbstractActivity { coords = app.currentGeo().getCoords(); } + Geopoint result; if (StringUtils.isNotBlank(bearingText) && StringUtils.isNotBlank(distanceText)) { // bearing & distance double bearing; diff --git a/main/src/cgeo/geocaching/SearchActivity.java b/main/src/cgeo/geocaching/SearchActivity.java index 6194013..231555c 100644 --- a/main/src/cgeo/geocaching/SearchActivity.java +++ b/main/src/cgeo/geocaching/SearchActivity.java @@ -7,6 +7,7 @@ import cgeo.geocaching.connector.capability.ISearchByGeocode; import cgeo.geocaching.connector.gc.GCConstants; 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; @@ -28,9 +29,9 @@ import android.widget.AutoCompleteTextView; import android.widget.Button; import android.widget.EditText; -public class SearchActivity extends AbstractActivity { +import java.util.Locale; - private static final String EXTRAS_KEYWORDSEARCH = "keywordsearch"; +public class SearchActivity extends AbstractActivity { private static final int MENU_SEARCH_OWN_CACHES = 1; private EditText latEdit = null; @@ -48,7 +49,7 @@ public class SearchActivity extends AbstractActivity { Intent intent = getIntent(); if (Intent.ACTION_SEARCH.equals(intent.getAction())) { final String query = intent.getStringExtra(SearchManager.QUERY); - final boolean keywordSearch = intent.getBooleanExtra(EXTRAS_KEYWORDSEARCH, true); + final boolean keywordSearch = intent.getBooleanExtra(Intents.EXTRA_KEYWORD_SEARCH, true); if (instantSearch(query, keywordSearch)) { setResult(RESULT_OK); @@ -109,7 +110,7 @@ public class SearchActivity extends AbstractActivity { final IConnector connector = ConnectorFactory.getConnector(geocode); if (connector instanceof ISearchByGeocode) { final Intent cachesIntent = new Intent(this, CacheDetailActivity.class); - cachesIntent.putExtra("geocode", geocode.toUpperCase()); + cachesIntent.putExtra(Intents.EXTRA_GEOCODE, geocode.toUpperCase(Locale.US)); startActivity(cachesIntent); return true; } @@ -117,8 +118,8 @@ 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 Intent trackablesIntent = new Intent(this, cgeotrackable.class); - trackablesIntent.putExtra("geocode", trackable.toUpperCase()); + final Intent trackablesIntent = new Intent(this, TrackableActivity.class); + trackablesIntent.putExtra(Intents.EXTRA_GEOCODE, trackable.toUpperCase(Locale.US)); startActivity(trackablesIntent); return true; } @@ -134,11 +135,11 @@ public class SearchActivity extends AbstractActivity { private void init() { Settings.getLogin(); - findViewById(R.id.buttonLatitude).setOnClickListener(new findByCoordsAction()); - findViewById(R.id.buttonLongitude).setOnClickListener(new findByCoordsAction()); + findViewById(R.id.buttonLatitude).setOnClickListener(new FindByCoordsAction()); + findViewById(R.id.buttonLongitude).setOnClickListener(new FindByCoordsAction()); final Button findByCoords = (Button) findViewById(R.id.search_coordinates); - findByCoords.setOnClickListener(new findByCoordsListener()); + findByCoords.setOnClickListener(new FindByCoordsListener()); EditUtils.setActionListener((EditText) findViewById(R.id.address), new Runnable() { @@ -149,7 +150,7 @@ public class SearchActivity extends AbstractActivity { }); final Button findByAddress = (Button) findViewById(R.id.search_address); - findByAddress.setOnClickListener(new findByAddressListener()); + findByAddress.setOnClickListener(new FindByAddressListener()); final AutoCompleteTextView geocodeEdit = (AutoCompleteTextView) findViewById(R.id.geocode); EditUtils.setActionListener(geocodeEdit, new Runnable() { @@ -159,10 +160,10 @@ public class SearchActivity extends AbstractActivity { findByGeocodeFn(); } }); - addHistoryEntries(geocodeEdit, app.geocodesInCache()); + addHistoryEntries(geocodeEdit, cgData.getRecentGeocodesForSearch()); final Button displayByGeocode = (Button) findViewById(R.id.display_geocode); - displayByGeocode.setOnClickListener(new findByGeocodeListener()); + displayByGeocode.setOnClickListener(new FindByGeocodeListener()); EditUtils.setActionListener((EditText) findViewById(R.id.keyword), new Runnable() { @@ -173,7 +174,7 @@ public class SearchActivity extends AbstractActivity { }); final Button findByKeyword = (Button) findViewById(R.id.search_keyword); - findByKeyword.setOnClickListener(new findByKeywordListener()); + findByKeyword.setOnClickListener(new FindByKeywordListener()); EditUtils.setActionListener((EditText) findViewById(R.id.username), new Runnable() { @@ -184,7 +185,7 @@ public class SearchActivity extends AbstractActivity { }); final Button findByUserName = (Button) findViewById(R.id.search_username); - findByUserName.setOnClickListener(new findByUsernameListener()); + findByUserName.setOnClickListener(new FindByUsernameListener()); EditUtils.setActionListener((EditText) findViewById(R.id.owner), new Runnable() { @@ -211,12 +212,12 @@ public class SearchActivity extends AbstractActivity { findTrackableFn(); } }); - addHistoryEntries(trackable, app.getTrackableCodes()); + addHistoryEntries(trackable, cgData.getTrackableCodes()); disableSuggestions(trackable); final Button displayTrackable = (Button) findViewById(R.id.display_trackable); - displayTrackable.setOnClickListener(new findTrackableListener()); + displayTrackable.setOnClickListener(new FindTrackableListener()); } private void addHistoryEntries(final AutoCompleteTextView textView, final String[] entries) { @@ -251,13 +252,13 @@ public class SearchActivity extends AbstractActivity { } }; - private class findByCoordsAction implements OnClickListener { + private class FindByCoordsAction implements OnClickListener { @Override public void onClick(View arg0) { - cgeocoords coordsDialog = new cgeocoords(SearchActivity.this, null, null, app.currentGeo()); + CoordinatesInputDialog coordsDialog = new CoordinatesInputDialog(SearchActivity.this, null, null, app.currentGeo()); coordsDialog.setCancelable(true); - coordsDialog.setOnCoordinateUpdate(new cgeocoords.CoordinateUpdate() { + coordsDialog.setOnCoordinateUpdate(new CoordinatesInputDialog.CoordinateUpdate() { @Override public void update(Geopoint gp) { ((Button) findViewById(R.id.buttonLatitude)).setText(gp.format(GeopointFormatter.Format.LAT_DECMINUTE)); @@ -268,7 +269,7 @@ public class SearchActivity extends AbstractActivity { } } - private class findByCoordsListener implements View.OnClickListener { + private class FindByCoordsListener implements View.OnClickListener { @Override public void onClick(View arg0) { @@ -297,7 +298,7 @@ public class SearchActivity extends AbstractActivity { } } - private class findByKeywordListener implements View.OnClickListener { + private class FindByKeywordListener implements View.OnClickListener { @Override public void onClick(View arg0) { @@ -317,7 +318,7 @@ public class SearchActivity extends AbstractActivity { cgeocaches.startActivityKeyword(this, keyText); } - private class findByAddressListener implements View.OnClickListener { + private class FindByAddressListener implements View.OnClickListener { @Override public void onClick(View arg0) { findByAddressFn(); @@ -332,12 +333,12 @@ public class SearchActivity extends AbstractActivity { return; } - final Intent addressesIntent = new Intent(this, AdressListActivity.class); - addressesIntent.putExtra("keyword", addText); + final Intent addressesIntent = new Intent(this, AddressListActivity.class); + addressesIntent.putExtra(Intents.EXTRA_KEYWORD, addText); startActivity(addressesIntent); } - private class findByUsernameListener implements View.OnClickListener { + private class FindByUsernameListener implements View.OnClickListener { @Override public void onClick(View arg0) { @@ -371,7 +372,7 @@ public class SearchActivity extends AbstractActivity { cgeocaches.startActivityOwner(this, usernameText); } - private class findByGeocodeListener implements View.OnClickListener { + private class FindByGeocodeListener implements View.OnClickListener { @Override public void onClick(View arg0) { @@ -390,7 +391,7 @@ public class SearchActivity extends AbstractActivity { CacheDetailActivity.startActivity(this, geocodeText); } - private class findTrackableListener implements View.OnClickListener { + private class FindTrackableListener implements View.OnClickListener { @Override public void onClick(View arg0) { @@ -406,8 +407,8 @@ public class SearchActivity extends AbstractActivity { return; } - final Intent trackablesIntent = new Intent(this, cgeotrackable.class); - trackablesIntent.putExtra("geocode", trackableText.toUpperCase()); + final Intent trackablesIntent = new Intent(this, TrackableActivity.class); + trackablesIntent.putExtra(Intents.EXTRA_GEOCODE, trackableText.toUpperCase(Locale.US)); startActivity(trackablesIntent); } @@ -430,7 +431,7 @@ public class SearchActivity extends AbstractActivity { final Intent searchIntent = new Intent(fromActivity, SearchActivity.class); searchIntent.setAction(Intent.ACTION_SEARCH). putExtra(SearchManager.QUERY, scan). - putExtra(SearchActivity.EXTRAS_KEYWORDSEARCH, false); + putExtra(Intents.EXTRA_KEYWORD_SEARCH, false); fromActivity.startActivityForResult(searchIntent, cgeo.SEARCH_REQUEST_CODE); } } diff --git a/main/src/cgeo/geocaching/SearchResult.java b/main/src/cgeo/geocaching/SearchResult.java index e21717d..b0540f2 100644 --- a/main/src/cgeo/geocaching/SearchResult.java +++ b/main/src/cgeo/geocaching/SearchResult.java @@ -8,6 +8,7 @@ import cgeo.geocaching.enumerations.LoadFlags.SaveFlag; import cgeo.geocaching.enumerations.StatusCode; import cgeo.geocaching.gcvote.GCVote; +import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import android.os.Parcel; @@ -40,38 +41,49 @@ public class SearchResult implements Parcelable { } }; + /** + * Build a new empty search result. + */ public SearchResult() { - this((Set<String>) null); + this(new HashSet<String>()); } - public SearchResult(SearchResult searchResult) { - if (searchResult != null) { - this.geocodes = new HashSet<String>(searchResult.geocodes); - this.error = searchResult.error; - this.url = searchResult.url; - this.viewstates = searchResult.viewstates; - this.setTotal(searchResult.getTotal()); - } else { - this.geocodes = new HashSet<String>(); - } + /** + * Copy a search result, for example to apply different filters on it. + * + * @param searchResult the original search result, which cannot be null + */ + public SearchResult(final SearchResult searchResult) { + geocodes = new HashSet<String>(searchResult.geocodes); + error = searchResult.error; + url = searchResult.url; + viewstates = searchResult.viewstates; + setTotal(searchResult.getTotal()); } - public SearchResult(final Set<String> geocodes, final int total) { - if (geocodes == null) { - this.geocodes = new HashSet<String>(); - } else { - this.geocodes = new HashSet<String>(geocodes.size()); - this.geocodes.addAll(geocodes); - } + /** + * Build a search result from an existing collection of geocodes. + * + * @param geocodes a non-null collection of geocodes + * @param total the total number of geocodes (FIXME: what is the meaning of this number wrt to geocodes.size()?) + */ + public SearchResult(final Collection<String> geocodes, final int total) { + this.geocodes = new HashSet<String>(geocodes.size()); + this.geocodes.addAll(geocodes); this.setTotal(total); } + /** + * Build a search result from an existing collection of geocodes. + * + * @param geocodes a non-null set of geocodes + */ public SearchResult(final Set<String> geocodes) { - this(geocodes, geocodes == null ? 0 : geocodes.size()); + this(geocodes, geocodes.size()); } public SearchResult(final Parcel in) { - ArrayList<String> list = new ArrayList<String>(); + final ArrayList<String> list = new ArrayList<String>(); in.readStringList(list); geocodes = new HashSet<String>(list); error = (StatusCode) in.readSerializable(); @@ -84,14 +96,24 @@ public class SearchResult implements Parcelable { setTotal(in.readInt()); } - public SearchResult(cgCache cache) { - this(); - addCache(cache); + /** + * Build a search result designating a single cache. + * + * @param cache the cache to include + */ + + public SearchResult(final Geocache cache) { + this(Collections.singletonList(cache)); } - public SearchResult(Collection<cgCache> caches) { + /** + * Build a search result from a collection of caches. + * + * @param caches the non-null collection of caches to include + */ + public SearchResult(final Collection<Geocache> caches) { this(); - for (cgCache cache : caches) { + for (final Geocache cache : caches) { addCache(cache); } } @@ -148,7 +170,7 @@ public class SearchResult implements Parcelable { return; } - this.viewstates = viewstates; + System.arraycopy(viewstates, 0, this.viewstates, 0, viewstates.length); } public int getTotal() { @@ -169,14 +191,13 @@ public class SearchResult implements Parcelable { SearchResult result = new SearchResult(this); result.geocodes.clear(); - final ArrayList<cgCache> cachesForVote = new ArrayList<cgCache>(); - - final Set<cgCache> caches = cgeoapplication.getInstance().loadCaches(geocodes, LoadFlags.LOAD_CACHE_OR_DB); - for (cgCache cache : caches) { + final ArrayList<Geocache> cachesForVote = new ArrayList<Geocache>(); + final Set<Geocache> caches = cgData.loadCaches(geocodes, LoadFlags.LOAD_CACHE_OR_DB); + for (Geocache cache : caches) { // Is there any reason to exclude the cache from the list? final boolean excludeCache = (excludeDisabled && cache.isDisabled()) || - (excludeMine && (cache.isOwn() || cache.isFound())) || - (cacheType != CacheType.ALL && cacheType != cache.getType()); + (excludeMine && (cache.isOwner() || cache.isFound())) || + (!cacheType.contains(cache)); if (!excludeCache) { result.addCache(cache); cachesForVote.add(cache); @@ -186,15 +207,12 @@ public class SearchResult implements Parcelable { return result; } - public cgCache getFirstCacheFromResult(final EnumSet<LoadFlag> loadFlags) { - if (geocodes != null && geocodes.size() >= 1) { - return cgeoapplication.getInstance().loadCache((String) geocodes.toArray()[0], loadFlags); - } - return null; + public Geocache getFirstCacheFromResult(final EnumSet<LoadFlag> loadFlags) { + return CollectionUtils.isNotEmpty(geocodes) ? cgData.loadCache(geocodes.iterator().next(), loadFlags) : null; } - public Set<cgCache> getCachesFromSearchResult(final EnumSet<LoadFlag> loadFlags) { - return cgeoapplication.getInstance().loadCaches(geocodes, loadFlags); + public Set<Geocache> getCachesFromSearchResult(final EnumSet<LoadFlag> loadFlags) { + return cgData.loadCaches(geocodes, loadFlags); } /** Add the geocode to the search. No cache is loaded into the CacheCache */ @@ -211,13 +229,22 @@ public class SearchResult implements Parcelable { } /** Add the cache geocode to the search and store the cache in the CacheCache */ - public boolean addCache(final cgCache cache) { + public boolean addCache(final Geocache cache) { addGeocode(cache.getGeocode()); - return cgeoapplication.getInstance().saveCache(cache, EnumSet.of(SaveFlag.SAVE_CACHE)); + return cgData.saveCache(cache, EnumSet.of(SaveFlag.SAVE_CACHE)); } public boolean isEmpty() { return geocodes.isEmpty(); } + public boolean hasUnsavedCaches() { + for (final String geocode : getGeocodes()) { + if (!cgData.isOffline(geocode, null)) { + return true; + } + } + return false; + } + } diff --git a/main/src/cgeo/geocaching/cgSelectMapfile.java b/main/src/cgeo/geocaching/SelectMapfileActivity.java index 3757c4f..9557f3e 100644 --- a/main/src/cgeo/geocaching/cgSelectMapfile.java +++ b/main/src/cgeo/geocaching/SelectMapfileActivity.java @@ -1,6 +1,6 @@ package cgeo.geocaching; -import cgeo.geocaching.files.FileList; +import cgeo.geocaching.files.AbstractFileListActivity; import cgeo.geocaching.files.IFileSelectionView; import cgeo.geocaching.files.LocalStorage; import cgeo.geocaching.ui.FileSelectionListAdapter; @@ -13,9 +13,9 @@ import java.io.File; import java.util.ArrayList; import java.util.List; -public class cgSelectMapfile extends FileList<FileSelectionListAdapter> implements IFileSelectionView { +public class SelectMapfileActivity extends AbstractFileListActivity<FileSelectionListAdapter> implements IFileSelectionView { - public cgSelectMapfile() { + public SelectMapfileActivity() { super("map"); } @@ -31,7 +31,7 @@ public class cgSelectMapfile extends FileList<FileSelectionListAdapter> implemen public void close() { Intent intent = new Intent(); - intent.putExtra("mapfile", mapFile); + intent.putExtra(Intents.EXTRA_MAP_FILE, mapFile); setResult(RESULT_OK, intent); diff --git a/main/src/cgeo/geocaching/Settings.java b/main/src/cgeo/geocaching/Settings.java index 850c4f4..0c157e1 100644 --- a/main/src/cgeo/geocaching/Settings.java +++ b/main/src/cgeo/geocaching/Settings.java @@ -75,7 +75,6 @@ public final class Settings { private static final String KEY_AUTO_VISIT_TRACKABLES = "trackautovisit"; private static final String KEY_AUTO_INSERT_SIGNATURE = "sigautoinsert"; private static final String KEY_ALTITUDE_CORRECTION = "altcorrection"; - private static final String KEY_USE_GOOGLE_NAVIGATION = "usegnav"; private static final String KEY_STORE_LOG_IMAGES = "logimages"; private static final String KEY_EXCLUDE_DISABLED = "excludedisabled"; private static final String KEY_EXCLUDE_OWN = "excludemine"; @@ -87,6 +86,7 @@ public final class Settings { private static final String KEY_MEMBER_STATUS = "memberstatus"; private static final String KEY_COORD_INPUT_FORMAT = "coordinputformat"; private static final String KEY_LOG_OFFLINE = "log_offline"; + private static final String KEY_CHOOSE_LIST = "choose_list"; private static final String KEY_LOAD_DIRECTION_IMG = "loaddirectionimg"; private static final String KEY_GC_CUSTOM_DATE = "gccustomdate"; private static final String KEY_SHOW_WAYPOINTS_THRESHOLD = "gcshowwaypointsthreshold"; @@ -110,6 +110,8 @@ 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_OC_ACTIVE = "connectorOCActive"; + private static final String KEY_CONNECTOR_OC_USER = "connectorOCUser"; private final static int unitsMetric = 1; @@ -127,7 +129,7 @@ public final class Settings { Min, Sec; - static coordInputFormatEnum fromInt(int id) { + public static coordInputFormatEnum fromInt(int id) { final coordInputFormatEnum[] values = coordInputFormatEnum.values(); if (id < 0 || id >= values.length) { return Min; @@ -192,7 +194,6 @@ public final class Settings { e.putBoolean(KEY_AUTO_VISIT_TRACKABLES, old.getBoolean(KEY_AUTO_VISIT_TRACKABLES, false)); e.putBoolean(KEY_AUTO_INSERT_SIGNATURE, old.getBoolean(KEY_AUTO_INSERT_SIGNATURE, false)); e.putInt(KEY_ALTITUDE_CORRECTION, old.getInt(KEY_ALTITUDE_CORRECTION, 0)); - e.putBoolean(KEY_USE_GOOGLE_NAVIGATION, 0 != old.getInt(KEY_USE_GOOGLE_NAVIGATION, 1)); e.putBoolean(KEY_STORE_LOG_IMAGES, old.getBoolean(KEY_STORE_LOG_IMAGES, false)); e.putBoolean(KEY_EXCLUDE_DISABLED, 0 != old.getInt(KEY_EXCLUDE_DISABLED, 0)); e.putBoolean(KEY_EXCLUDE_OWN, 0 != old.getInt(KEY_EXCLUDE_OWN, 0)); @@ -204,6 +205,7 @@ public final class Settings { e.putString(KEY_MEMBER_STATUS, old.getString(KEY_MEMBER_STATUS, "")); e.putInt(KEY_COORD_INPUT_FORMAT, old.getInt(KEY_COORD_INPUT_FORMAT, 0)); e.putBoolean(KEY_LOG_OFFLINE, old.getBoolean(KEY_LOG_OFFLINE, false)); + e.putBoolean(KEY_CHOOSE_LIST, old.getBoolean(KEY_CHOOSE_LIST, false)); e.putBoolean(KEY_LOAD_DIRECTION_IMG, old.getBoolean(KEY_LOAD_DIRECTION_IMG, true)); e.putString(KEY_GC_CUSTOM_DATE, old.getString(KEY_GC_CUSTOM_DATE, null)); e.putInt(KEY_SHOW_WAYPOINTS_THRESHOLD, old.getInt(KEY_SHOW_WAYPOINTS_THRESHOLD, 0)); @@ -307,6 +309,42 @@ public final class Settings { }); } + public static boolean isOCConnectorActive() { + return sharedPrefs.getBoolean(KEY_CONNECTOR_OC_ACTIVE, false); + } + + public static boolean setOCConnectorActive(final boolean isActive) { + return editSharedSettings(new PrefRunnable() { + + @Override + public void edit(Editor edit) { + edit.putBoolean(KEY_CONNECTOR_OC_ACTIVE, isActive); + } + }); + } + + public static String getOCConnectorUserName() { + String ocConnectorUser = sharedPrefs.getString(KEY_CONNECTOR_OC_USER, null); + if (StringUtils.isBlank(ocConnectorUser)) { + return StringUtils.EMPTY; + } + return ocConnectorUser; + } + + public static boolean setOCConnectorUserName(final String userName) { + return 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); + } + } + }); + } + public static boolean isGCvoteLogin() { final String preUsername = sharedPrefs.getString(KEY_USERNAME, null); final String prePassword = sharedPrefs.getString(KEY_GCVOTE_PASSWORD, null); @@ -516,6 +554,20 @@ public final class Settings { return sharedPrefs.getBoolean(KEY_LOG_OFFLINE, false); } + static void setChooseList(final boolean choose) { + editSharedSettings(new PrefRunnable() { + + @Override + public void edit(Editor edit) { + edit.putBoolean(KEY_CHOOSE_LIST, choose); + } + }); + } + + public static boolean getChooseList() { + return sharedPrefs.getBoolean(KEY_CHOOSE_LIST, false); + } + static void setLoadDirImg(final boolean value) { editSharedSettings(new PrefRunnable() { @@ -672,20 +724,6 @@ public final class Settings { }); } - public static boolean isUseGoogleNavigation() { - return sharedPrefs.getBoolean(KEY_USE_GOOGLE_NAVIGATION, true); - } - - public static void setUseGoogleNavigation(final boolean useGoogleNavigation) { - editSharedSettings(new PrefRunnable() { - - @Override - public void edit(Editor edit) { - edit.putBoolean(KEY_USE_GOOGLE_NAVIGATION, useGoogleNavigation); - } - }); - } - public static boolean isAutoLoadDescription() { return sharedPrefs.getBoolean(KEY_LOAD_DESCRIPTION, false); } @@ -1331,7 +1369,7 @@ public final class Settings { FileUtils.listDir(result, directory, new ExtensionsBasedFileSelector(new String[] { "xml" }), null); - return result.toArray(new File[] {}); + return result.toArray(new File[result.size()]); } private static class ExtensionsBasedFileSelector extends FileSelector { diff --git a/main/src/cgeo/geocaching/SettingsActivity.java b/main/src/cgeo/geocaching/SettingsActivity.java index 823b52f..0678617 100644 --- a/main/src/cgeo/geocaching/SettingsActivity.java +++ b/main/src/cgeo/geocaching/SettingsActivity.java @@ -5,7 +5,6 @@ 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.enumerations.CacheType; import cgeo.geocaching.enumerations.StatusCode; import cgeo.geocaching.files.SimpleDirChooser; import cgeo.geocaching.maps.MapProviderFactory; @@ -24,6 +23,7 @@ import ch.boye.httpclientandroidlib.HttpResponse; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; +import org.openintents.intents.FileManagerIntents; import android.app.ProgressDialog; import android.content.Context; @@ -84,7 +84,7 @@ public class SettingsActivity extends AbstractActivity { } catch (Exception e) { showToast(res.getString(R.string.err_login_failed)); - Log.e("SettingsActivity.logInHandler: " + e.toString()); + Log.e("SettingsActivity.logInHandler", e); } if (loginDialog != null && loginDialog.isShowing()) { @@ -112,7 +112,7 @@ public class SettingsActivity extends AbstractActivity { } catch (Exception e) { showToast(res.getString(R.string.init_sendToCgeo_register_fail)); - Log.e("SettingsActivity.webHandler: " + e.toString()); + Log.e("SettingsActivity.webHandler", e); } if (webDialog != null && webDialog.isShowing()) { @@ -230,7 +230,7 @@ public class SettingsActivity extends AbstractActivity { } Button logMeIn = (Button) findViewById(R.id.log_me_in); - logMeIn.setOnClickListener(new logIn()); + logMeIn.setOnClickListener(new LoginListener()); TextView legalNote = (TextView) findViewById(R.id.legal_note); legalNote.setClickable(true); @@ -242,6 +242,21 @@ public class SettingsActivity extends AbstractActivity { } }); + // opencaching.de settings + final CheckBox ocCheck = (CheckBox) findViewById(R.id.oc_option); + ocCheck.setChecked(Settings.isOCConnectorActive()); + ocCheck.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + Settings.setOCConnectorActive(ocCheck.isChecked()); + } + }); + EditText ocUserEdit = (EditText) findViewById(R.id.oc_username); + if (ocUserEdit.getText().length() == 0) { + ocUserEdit.setText(Settings.getOCConnectorUserName()); + } + // gcvote settings final ImmutablePair<String, String> gcvoteLogin = Settings.getGCvoteLogin(); if (null != gcvoteLogin && null != gcvoteLogin.right) { @@ -495,24 +510,25 @@ public class SettingsActivity extends AbstractActivity { } }); - final CheckBox gnavButton = (CheckBox) findViewById(R.id.gnav); - gnavButton.setChecked(Settings.isUseGoogleNavigation()); - gnavButton.setOnClickListener(new View.OnClickListener() { + final CheckBox logOffline = (CheckBox) findViewById(R.id.log_offline); + logOffline.setChecked(Settings.getLogOffline()); + logOffline.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - Settings.setUseGoogleNavigation(gnavButton.isChecked()); + Settings.setLogOffline(!Settings.getLogOffline()); + logOffline.setChecked(Settings.getLogOffline()); } }); - final CheckBox logOffline = (CheckBox) findViewById(R.id.log_offline); - logOffline.setChecked(Settings.getLogOffline()); - logOffline.setOnClickListener(new View.OnClickListener() { + final CheckBox chooseList = (CheckBox) findViewById(R.id.choose_list); + chooseList.setChecked(Settings.getChooseList()); + chooseList.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - Settings.setLogOffline(!Settings.getLogOffline()); - logOffline.setChecked(Settings.getLogOffline()); + Settings.setChooseList(!Settings.getChooseList()); + chooseList.setChecked(Settings.getChooseList()); } }); @@ -554,7 +570,7 @@ public class SettingsActivity extends AbstractActivity { } Button webAuth = (Button) findViewById(R.id.sendToCgeo_register); - webAuth.setOnClickListener(new webAuth()); + webAuth.setOnClickListener(new WebAuthListener()); // Map source settings updateMapSourceMenu(); @@ -564,7 +580,7 @@ public class SettingsActivity extends AbstractActivity { @Override public void onClick(View v) { - Intent selectIntent = new Intent(SettingsActivity.this, cgSelectMapfile.class); + Intent selectIntent = new Intent(SettingsActivity.this, SelectMapfileActivity.class); startActivityForResult(selectIntent, SELECT_MAPFILE_REQUEST); } }); @@ -577,9 +593,7 @@ public class SettingsActivity extends AbstractActivity { @Override public void onClick(View v) { - Intent dirChooser = new Intent(SettingsActivity.this, SimpleDirChooser.class); - dirChooser.putExtra(SimpleDirChooser.START_DIR, Settings.getCustomRenderThemeBaseFolder()); - startActivityForResult(dirChooser, SELECT_THEMEFOLDER_REQUEST); + selectDirectory(Settings.getCustomRenderThemeBaseFolder(), SELECT_THEMEFOLDER_REQUEST); } }); @@ -591,9 +605,7 @@ public class SettingsActivity extends AbstractActivity { @Override public void onClick(View v) { - Intent dirChooser = new Intent(SettingsActivity.this, SimpleDirChooser.class); - dirChooser.putExtra(SimpleDirChooser.START_DIR, Settings.getGpxExportDir()); - startActivityForResult(dirChooser, SELECT_GPX_EXPORT_REQUEST); + selectDirectory(Settings.getGpxExportDir(), SELECT_GPX_EXPORT_REQUEST); } }); @@ -605,9 +617,7 @@ public class SettingsActivity extends AbstractActivity { @Override public void onClick(View v) { - Intent dirChooser = new Intent(SettingsActivity.this, SimpleDirChooser.class); - dirChooser.putExtra(SimpleDirChooser.START_DIR, Settings.getGpxImportDir()); - startActivityForResult(dirChooser, SELECT_GPX_IMPORT_REQUEST); + selectDirectory(Settings.getGpxImportDir(), SELECT_GPX_IMPORT_REQUEST); } }); @@ -748,7 +758,7 @@ public class SettingsActivity extends AbstractActivity { mapSourceSelector.setAdapter(adapter); final int index = MapProviderFactory.getMapSources().indexOf(Settings.getMapSource()); mapSourceSelector.setSelection(index); - mapSourceSelector.setOnItemSelectedListener(new cgeoChangeMapSource()); + mapSourceSelector.setOnItemSelectedListener(new ChangeMapSourceListener()); initMapDirectoryEdittext(false); } @@ -775,7 +785,7 @@ public class SettingsActivity extends AbstractActivity { */ public void backup(View view) { // avoid overwriting an existing backup with an empty database (can happen directly after reinstalling the app) - if (app.getAllStoredCachesCount(true, CacheType.ALL) == 0) { + if (cgData.getAllCachesCount() == 0) { helpDialog(res.getString(R.string.init_backup), res.getString(R.string.init_backup_unnecessary)); return; } @@ -784,7 +794,7 @@ public class SettingsActivity extends AbstractActivity { new Thread() { @Override public void run() { - final String backupFileName = app.backupDatabase(); + final String backupFileName = cgData.backupDatabase(); runOnUiThread(new Runnable() { @Override public void run() { @@ -800,7 +810,7 @@ public class SettingsActivity extends AbstractActivity { private void refreshBackupLabel() { TextView lastBackup = (TextView) findViewById(R.id.backup_last); - File lastBackupFile = cgeoapplication.isRestoreFile(); + File lastBackupFile = cgData.getRestoreFile(); if (lastBackupFile != null) { lastBackup.setText(res.getString(R.string.init_backup_last) + " " + Formatter.formatTime(lastBackupFile.lastModified()) + ", " + Formatter.formatDate(lastBackupFile.lastModified())); } else { @@ -829,6 +839,7 @@ 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); @@ -842,6 +853,7 @@ 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()); @@ -849,7 +861,7 @@ public class SettingsActivity extends AbstractActivity { Settings.setGpxImportDir(importNew); Settings.setGpxExportDir(exportNew); - return status1 && status2 && status3 && status4 && status5 && status6; + return status1 && status2 && status3 && status4 && status5 && status6 && status7; } /** @@ -870,7 +882,7 @@ public class SettingsActivity extends AbstractActivity { } } - private static class cgeoChangeMapSource implements OnItemSelectedListener { + private static class ChangeMapSourceListener implements OnItemSelectedListener { @Override public void onItemSelected(AdapterView<?> arg0, View arg1, int position, @@ -884,7 +896,7 @@ public class SettingsActivity extends AbstractActivity { } } - private class logIn implements View.OnClickListener { + private class LoginListener implements View.OnClickListener { @Override public void onClick(View arg0) { @@ -918,7 +930,7 @@ public class SettingsActivity extends AbstractActivity { } } - private class webAuth implements View.OnClickListener { + private class WebAuthListener implements View.OnClickListener { @Override public void onClick(View arg0) { @@ -951,7 +963,7 @@ public class SettingsActivity extends AbstractActivity { try { pin = Integer.parseInt(strings[1].trim()); } catch (Exception e) { - Log.e("webDialog: " + e.toString()); + Log.e("webDialog", e); } String code = strings[0]; Settings.setWebNameCode(nam, code); @@ -966,58 +978,81 @@ public class SettingsActivity extends AbstractActivity { @Override protected void onActivityResult(int requestCode, int resultCode, final Intent data) { super.onActivityResult(requestCode, resultCode, data); + if (resultCode != RESULT_OK) { + return; + } - if (requestCode == SELECT_MAPFILE_REQUEST) { - if (resultCode == RESULT_OK) { - if (data.hasExtra("mapfile")) { - final String mapFile = data.getStringExtra("mapfile"); + switch (requestCode) { + case SELECT_MAPFILE_REQUEST: + if (data.hasExtra(Intents.EXTRA_MAP_FILE)) { + final String mapFile = data.getStringExtra(Intents.EXTRA_MAP_FILE); Settings.setMapFile(mapFile); if (!Settings.isValidMapFile(Settings.getMapFile())) { showToast(res.getString(R.string.warn_invalid_mapfile)); } } - } - updateMapSourceMenu(); - initMapDirectoryEdittext(true); - } - if (requestCode == SELECT_GPX_EXPORT_REQUEST) { - checkDirectory(resultCode, data, R.id.gpx_exportdir, new RunnableWithArgument<String>() { + updateMapSourceMenu(); + initMapDirectoryEdittext(true); + break; + case SELECT_GPX_EXPORT_REQUEST: + checkDirectory(resultCode, data, R.id.gpx_exportdir, new RunnableWithArgument<String>() { - @Override - public void run(String directory) { - Settings.setGpxExportDir(directory); - } - }); - } - if (requestCode == SELECT_GPX_IMPORT_REQUEST) { - checkDirectory(resultCode, data, R.id.gpx_importdir, new RunnableWithArgument<String>() { + @Override + public void run(String directory) { + Settings.setGpxExportDir(directory); + } + }); + break; + case SELECT_GPX_IMPORT_REQUEST: + checkDirectory(resultCode, data, R.id.gpx_importdir, new RunnableWithArgument<String>() { - @Override - public void run(String directory) { - Settings.setGpxImportDir(directory); - } - }); - } - if (requestCode == SELECT_THEMEFOLDER_REQUEST) { - checkDirectory(resultCode, data, R.id.themefolder, new RunnableWithArgument<String>() { + @Override + public void run(String directory) { + Settings.setGpxImportDir(directory); + } + }); + break; + case SELECT_THEMEFOLDER_REQUEST: + checkDirectory(resultCode, data, R.id.themefolder, new RunnableWithArgument<String>() { - @Override - public void run(String directory) { - Settings.setCustomRenderThemeBaseFolder(directory); - } - }); + @Override + public void run(String directory) { + Settings.setCustomRenderThemeBaseFolder(directory); + } + }); + break; + default: + throw new IllegalArgumentException(); } } private void checkDirectory(int resultCode, Intent data, int textField, RunnableWithArgument<String> runnableSetDir) { - if (resultCode == RESULT_OK) { - if (data.hasExtra(SimpleDirChooser.EXTRA_CHOSEN_DIR)) { - final String directory = data.getStringExtra(SimpleDirChooser.EXTRA_CHOSEN_DIR); - runnableSetDir.run(directory); - EditText directoryText = (EditText) findViewById(textField); - directoryText.setText(directory); - directoryText.requestFocus(); + if (resultCode != RESULT_OK) { + return; + } + final String directory = new File(data.getData().getPath()).getAbsolutePath(); + if (StringUtils.isNotBlank(directory)) { + runnableSetDir.run(directory); + EditText directoryText = (EditText) findViewById(textField); + directoryText.setText(directory); + directoryText.requestFocus(); + } + } + + private void selectDirectory(String startDirectory, int directoryKind) { + try { + final Intent dirChooser = new Intent(FileManagerIntents.ACTION_PICK_DIRECTORY); + if (StringUtils.isNotBlank(startDirectory)) { + dirChooser.setData(Uri.fromFile(new File(startDirectory))); } + dirChooser.putExtra(FileManagerIntents.EXTRA_TITLE, res.getString(R.string.simple_dir_chooser_title)); + dirChooser.putExtra(FileManagerIntents.EXTRA_BUTTON_TEXT, res.getString(android.R.string.ok)); + startActivityForResult(dirChooser, directoryKind); + } catch (android.content.ActivityNotFoundException ex) { + // OI file manager not available + final Intent dirChooser = new Intent(this, SimpleDirChooser.class); + dirChooser.putExtra(Intents.EXTRA_START_DIR, startDirectory); + startActivityForResult(dirChooser, directoryKind); } } diff --git a/main/src/cgeo/geocaching/StaticMapsActivity.java b/main/src/cgeo/geocaching/StaticMapsActivity.java index 05a18f2..d7cef65 100644 --- a/main/src/cgeo/geocaching/StaticMapsActivity.java +++ b/main/src/cgeo/geocaching/StaticMapsActivity.java @@ -10,7 +10,6 @@ import android.app.ProgressDialog; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.os.Bundle; import android.os.Handler; import android.os.Message; @@ -36,7 +35,6 @@ public class StaticMapsActivity extends AbstractActivity { private LayoutInflater inflater = null; private ProgressDialog waitDialog = null; private LinearLayout smapsView = null; - private BitmapFactory factory = null; private final Handler loadMapsHandler = new Handler() { @Override @@ -61,7 +59,7 @@ public class StaticMapsActivity extends AbstractActivity { showStaticMaps(); } } catch (Exception e) { - Log.e("StaticMapsActivity.loadMapsHandler: " + e.toString()); + Log.e("StaticMapsActivity.loadMapsHandler", e); } } }; @@ -131,10 +129,6 @@ public class StaticMapsActivity extends AbstractActivity { @Override public void run() { try { - if (factory == null) { - factory = new BitmapFactory(); - } - // try downloading 2 times for (int trials = 0; trials < 2; trials++) { for (int level = 1; level <= 5; level++) { @@ -151,7 +145,7 @@ public class StaticMapsActivity extends AbstractActivity { } } } catch (Exception e) { - Log.e("StaticMapsActivity.LoadMapsThread.run: " + e.toString()); + Log.e("StaticMapsActivity.LoadMapsThread.run", e); } } if (!maps.isEmpty()) { @@ -161,7 +155,7 @@ public class StaticMapsActivity extends AbstractActivity { loadMapsHandler.sendMessage(Message.obtain()); } catch (Exception e) { - Log.e("StaticMapsActivity.LoadMapsThread.run: " + e.toString()); + Log.e("StaticMapsActivity.LoadMapsThread.run", e); } } } @@ -183,23 +177,23 @@ public class StaticMapsActivity extends AbstractActivity { } private boolean downloadStaticMaps() { - final cgCache cache = app.loadCache(geocode, LoadFlags.LOAD_CACHE_OR_DB); + final Geocache cache = cgData.loadCache(geocode, LoadFlags.LOAD_CACHE_OR_DB); if (waypoint_id == null) { showToast(res.getString(R.string.info_storing_static_maps)); - StaticMapsProvider.storeCacheStaticMap(cache, this, true); + StaticMapsProvider.storeCacheStaticMap(cache, true); return cache.hasStaticMap(); } - final cgWaypoint waypoint = cache.getWaypointById(waypoint_id); + final Waypoint waypoint = cache.getWaypointById(waypoint_id); if (waypoint != null) { showToast(res.getString(R.string.info_storing_static_maps)); - StaticMapsProvider.storeWaypointStaticMap(cache, this, waypoint, true); + StaticMapsProvider.storeWaypointStaticMap(cache, waypoint, true); return StaticMapsProvider.hasStaticMapForWaypoint(geocode, waypoint_id); } showToast(res.getString(R.string.err_detail_not_load_map_static)); return false; } - public static void startActivity(final Context activity, final String geocode, final boolean download, final cgWaypoint waypoint) { + 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); diff --git a/main/src/cgeo/geocaching/StaticMapsProvider.java b/main/src/cgeo/geocaching/StaticMapsProvider.java index 87a04fa..6feacc2 100644 --- a/main/src/cgeo/geocaching/StaticMapsProvider.java +++ b/main/src/cgeo/geocaching/StaticMapsProvider.java @@ -1,5 +1,6 @@ package cgeo.geocaching; +import cgeo.geocaching.compatibility.Compatibility; import cgeo.geocaching.concurrent.BlockingThreadPool; import cgeo.geocaching.files.LocalStorage; import cgeo.geocaching.geopoint.GeopointFormatter.Format; @@ -12,10 +13,10 @@ import ch.boye.httpclientandroidlib.HttpResponse; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; -import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.Point; import android.util.DisplayMetrics; import android.view.Display; import android.view.WindowManager; @@ -61,30 +62,25 @@ public class StaticMapsProvider { } final HttpResponse httpResponse = Network.getRequest(GOOGLE_STATICMAP_URL, params); - if (httpResponse != null) { - if (httpResponse.getStatusLine().getStatusCode() == 200) { - final File file = getMapFile(geocode, prefix, true); - if (LocalStorage.saveEntityToFile(httpResponse, file)) { - // Delete image if it has no contents - final long fileSize = file.length(); - if (fileSize < MIN_MAP_IMAGE_BYTES) { - file.delete(); - } - } - } else { - Log.d("StaticMapsProvider.downloadMap: httpResponseCode = " + httpResponse.getStatusLine().getStatusCode()); - } - } else { + if (httpResponse == null) { Log.e("StaticMapsProvider.downloadMap: httpResponse is null"); + return; + } + if (httpResponse.getStatusLine().getStatusCode() != 200) { + Log.d("StaticMapsProvider.downloadMap: httpResponseCode = " + httpResponse.getStatusLine().getStatusCode()); + return; + } + final File file = getMapFile(geocode, prefix, true); + if (LocalStorage.saveEntityToFile(httpResponse, file)) { + // Delete image if it has no contents + final long fileSize = file.length(); + if (fileSize < MIN_MAP_IMAGE_BYTES) { + file.delete(); + } } } - public static void downloadMaps(cgCache cache) { - final Display display = ((WindowManager) cgeoapplication.getInstance().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); - downloadMaps(cache, display); - } - - private static void downloadMaps(cgCache cache, Display display) { + public static void downloadMaps(Geocache cache) { if (cache == null) { Log.e("downloadMaps - missing input parameter cache"); return; @@ -92,7 +88,7 @@ public class StaticMapsProvider { if ((!Settings.isStoreOfflineMaps() && !Settings.isStoreOfflineWpMaps()) || StringUtils.isBlank(cache.getGeocode())) { return; } - int edge = guessMaxDisplaySide(display); + int edge = guessMaxDisplaySide(); if (Settings.isStoreOfflineMaps() && cache.getCoords() != null) { storeCachePreviewMap(cache); @@ -103,18 +99,18 @@ public class StaticMapsProvider { if (Settings.isStoreOfflineWpMaps() && CollectionUtils.isNotEmpty(cache.getWaypoints())) { // remove all waypoint static map files due to origin cache waypoint id changed on saveCache LocalStorage.deleteFilesWithPrefix(cache.getGeocode(), MAP_FILENAME_PREFIX + WAYPOINT_PREFIX); - for (cgWaypoint waypoint : cache.getWaypoints()) { + for (Waypoint waypoint : cache.getWaypoints()) { storeWaypointStaticMap(cache.getGeocode(), edge, waypoint, false); } } } - public static void storeWaypointStaticMap(cgCache cache, Activity activity, cgWaypoint waypoint, boolean waitForResult) { - int edge = StaticMapsProvider.guessMaxDisplaySide(activity); + public static void storeWaypointStaticMap(Geocache cache, Waypoint waypoint, boolean waitForResult) { + int edge = StaticMapsProvider.guessMaxDisplaySide(); storeWaypointStaticMap(cache.getGeocode(), edge, waypoint, waitForResult); } - private static void storeWaypointStaticMap(final String geocode, int edge, cgWaypoint waypoint, final boolean waitForResult) { + private static void storeWaypointStaticMap(final String geocode, int edge, Waypoint waypoint, final boolean waitForResult) { if (geocode == null) { Log.e("storeWaypointStaticMap - missing input parameter geocode"); return; @@ -132,15 +128,15 @@ public class StaticMapsProvider { downloadMaps(geocode, wpMarkerUrl, WAYPOINT_PREFIX + waypoint.getId() + '_', wpLatlonMap, edge, null, waitForResult); } - public static void storeCacheStaticMap(cgCache cache, Activity activity, final boolean waitForResult) { - int edge = guessMaxDisplaySide(activity); + public static void storeCacheStaticMap(Geocache cache, final boolean waitForResult) { + int edge = guessMaxDisplaySide(); storeCacheStaticMap(cache, edge, waitForResult); } - private static void storeCacheStaticMap(final cgCache cache, final int edge, final boolean waitForResult) { + private static void storeCacheStaticMap(final Geocache cache, final int edge, final boolean waitForResult) { final String latlonMap = cache.getCoords().format(Format.LAT_LON_DECDEGREE_COMMA); final Parameters waypoints = new Parameters(); - for (final cgWaypoint waypoint : cache.getWaypoints()) { + for (final Waypoint waypoint : cache.getWaypoints()) { if (waypoint.getCoords() == null) { continue; } @@ -152,34 +148,31 @@ public class StaticMapsProvider { downloadMaps(cache.getGeocode(), cacheMarkerUrl, "", latlonMap, edge, waypoints, waitForResult); } - public static void storeCachePreviewMap(final cgCache cache) { + 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 String markerUrl = MARKERS_URL + "my_location_mdpi.png"; final Display display = ((WindowManager) cgeoapplication.getInstance().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); DisplayMetrics metrics = new DisplayMetrics(); display.getMetrics(metrics); final int width = metrics.widthPixels; final int height = (int) (110 * metrics.density); + final String markerUrl = MARKERS_URL + "my_location_mdpi.png"; downloadMap(cache.getGeocode(), 15, ROADMAP, markerUrl, PREFIX_PREVIEW, "shadow:false|", latlonMap, width, height, null); } - private static int guessMaxDisplaySide(Display display) { - final int maxWidth = display.getWidth() - 25; - final int maxHeight = display.getHeight() - 25; + 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; } - private static int guessMaxDisplaySide(Activity activity) { - return guessMaxDisplaySide(((WindowManager) activity.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay()); - } - private static void downloadMaps(final String geocode, final String markerUrl, final String prefix, final String latlonMap, final int edge, final Parameters waypoints, boolean waitForResult) { if (waitForResult) { @@ -195,12 +188,12 @@ public class StaticMapsProvider { try { pool.add(currentTask, 20, TimeUnit.SECONDS); } catch (InterruptedException e) { - Log.e("StaticMapsProvider.downloadMaps error adding task: " + e.toString()); + Log.e("StaticMapsProvider.downloadMaps error adding task", e); } } } - private static String getCacheMarkerUrl(final cgCache cache) { + private static String getCacheMarkerUrl(final Geocache cache) { StringBuilder url = new StringBuilder(MARKERS_URL); url.append("marker_cache_").append(cache.getType().id); if (cache.isFound()) { @@ -212,7 +205,7 @@ public class StaticMapsProvider { return url.toString(); } - private static String getWpMarkerUrl(final cgWaypoint waypoint) { + private static String getWpMarkerUrl(final Waypoint waypoint) { String type = waypoint.getWaypointType() != null ? waypoint.getWaypointType().id : null; return MARKERS_URL + "marker_waypoint_" + type + ".png"; } @@ -225,7 +218,7 @@ public class StaticMapsProvider { try { StaticMapsProvider.getMapFile(geocode, WAYPOINT_PREFIX + wp_id + '_' + level, false).delete(); } catch (Exception e) { - Log.e("StaticMapsProvider.removeWpStaticMaps: " + e.toString()); + Log.e("StaticMapsProvider.removeWpStaticMaps", e); } } } @@ -236,7 +229,7 @@ public class StaticMapsProvider { * @param cache * @return <code>true</code> if at least one mapfile exists; <code>false</code> otherwise */ - public static boolean hasStaticMap(final cgCache cache) { + public static boolean hasStaticMap(final Geocache cache) { if (cache == null) { return false; } diff --git a/main/src/cgeo/geocaching/StatusFragment.java b/main/src/cgeo/geocaching/StatusFragment.java new file mode 100644 index 0000000..5a9a5b4 --- /dev/null +++ b/main/src/cgeo/geocaching/StatusFragment.java @@ -0,0 +1,110 @@ +package cgeo.geocaching; + +import cgeo.geocaching.network.StatusUpdater.Status; +import cgeo.geocaching.utils.IObserver; +import cgeo.geocaching.utils.Log; + +import android.content.Intent; +import android.content.res.Resources; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +public class StatusFragment extends Fragment { + + private ViewGroup status; + private ImageView statusIcon; + private TextView statusMessage; + + final private StatusHandler statusHandler = new StatusHandler(); + + @Override + public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) { + super.onCreateView(inflater, container, savedInstanceState); + status = (ViewGroup) inflater.inflate(R.layout.status, container, false); + statusIcon = (ImageView) status.findViewById(R.id.status_icon); + statusMessage = (TextView) status.findViewById(R.id.status_message); + return status; + } + + @Override + public void onResume() { + super.onResume(); + cgeoapplication.getInstance().getStatusUpdater().addObserver(statusHandler); + } + + @Override + public void onPause() { + cgeoapplication.getInstance().getStatusUpdater().deleteObserver(statusHandler); + super.onPause(); + } + + private class StatusHandler extends Handler implements IObserver<Status> { + + @Override + public void update(final Status data) { + obtainMessage(0, data).sendToTarget(); + } + + @Override + public void handleMessage(final Message msg) { + final Status data = (Status) msg.obj; + updateDisplay(data != null && data.message != null ? data : null); + } + + private void updateDisplay(final Status data) { + + if (data == null) { + status.setVisibility(View.INVISIBLE); + return; + } + + final Resources res = getResources(); + final String packageName = getActivity().getPackageName(); + + if (data.icon != null) { + final int iconId = res.getIdentifier(data.icon, "drawable", packageName); + if (iconId != 0) { + statusIcon.setImageResource(iconId); + statusIcon.setVisibility(View.VISIBLE); + } else { + Log.w("StatusHandler: could not find icon corresponding to @drawable/" + data.icon); + statusIcon.setVisibility(View.GONE); + } + } else { + statusIcon.setVisibility(View.GONE); + } + + String message = data.message; + if (data.messageId != null) { + final int messageId = res.getIdentifier(data.messageId, "string", packageName); + if (messageId != 0) { + message = res.getString(messageId); + } + } + + statusMessage.setText(message); + status.setVisibility(View.VISIBLE); + + if (data.url != null) { + status.setOnClickListener(new OnClickListener() { + @Override + public void onClick(final View v) { + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(data.url))); + } + }); + } else { + status.setClickable(false); + } + } + + } +} diff --git a/main/src/cgeo/geocaching/StoredList.java b/main/src/cgeo/geocaching/StoredList.java index cdff1cb..5a6f132 100644 --- a/main/src/cgeo/geocaching/StoredList.java +++ b/main/src/cgeo/geocaching/StoredList.java @@ -1,6 +1,6 @@ package cgeo.geocaching; -import cgeo.geocaching.activity.IAbstractActivity; +import cgeo.geocaching.activity.ActivityMixin; import cgeo.geocaching.utils.RunnableWithArgument; import org.apache.commons.lang3.StringUtils; @@ -34,28 +34,54 @@ public class StoredList { return title + " [" + count + "]"; } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + id; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof StoredList)) { + return false; + } + return id == ((StoredList) obj).id; + } + public static class UserInterface { - private final IAbstractActivity activity; + private final Activity activity; private final cgeoapplication app; private final Resources res; - public UserInterface(final IAbstractActivity activity) { + public UserInterface(final Activity activity) { this.activity = activity; app = cgeoapplication.getInstance(); res = app.getResources(); } public void promptForListSelection(final int titleId, final RunnableWithArgument<Integer> runAfterwards) { - promptForListSelection(titleId, runAfterwards, false); + promptForListSelection(titleId, runAfterwards, false, -1); } - public void promptForListSelection(final int titleId, final RunnableWithArgument<Integer> runAfterwards, final boolean onlyMoveTargets) { - final List<StoredList> lists = app.getLists(); + public void promptForListSelection(final int titleId, final RunnableWithArgument<Integer> runAfterwards, final boolean onlyMoveTargets, final int exceptListId) { + final List<StoredList> lists = cgData.getLists(); if (lists == null) { return; } + if (exceptListId > StoredList.TEMPORARY_LIST_ID) { + StoredList exceptList = cgData.getList(exceptListId); + if (exceptList != null) { + lists.remove(exceptList); + } + } + final List<CharSequence> listsTitle = new ArrayList<CharSequence>(); for (StoredList list : lists) { listsTitle.add(list.getTitleAndCount()); @@ -67,7 +93,7 @@ public class StoredList { final CharSequence[] items = new CharSequence[listsTitle.size()]; - AlertDialog.Builder builder = new AlertDialog.Builder((Activity) activity); + AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setTitle(res.getString(titleId)); builder.setItems(listsTitle.toArray(items), new DialogInterface.OnClickListener() { @Override @@ -94,23 +120,23 @@ public class StoredList { @Override public void run(final String listName) { - final int newId = app.createList(listName); + final int newId = cgData.createList(listName); if (newId >= cgData.customListIdOffset) { - activity.showToast(res.getString(R.string.list_dialog_create_ok)); + ActivityMixin.showToast(activity, res.getString(R.string.list_dialog_create_ok)); if (runAfterwards != null) { runAfterwards.run(newId); } } else { - activity.showToast(res.getString(R.string.list_dialog_create_err)); + ActivityMixin.showToast(activity, res.getString(R.string.list_dialog_create_err)); } } }); } private void handleListNameInput(final String defaultValue, int dialogTitle, int buttonTitle, final RunnableWithArgument<String> runnable) { - final AlertDialog.Builder alert = new AlertDialog.Builder((Activity) activity); - final View view = ((Activity) activity).getLayoutInflater().inflate(R.layout.list_create_dialog, null); + final AlertDialog.Builder alert = new AlertDialog.Builder(activity); + final View view = activity.getLayoutInflater().inflate(R.layout.list_create_dialog, null); final EditText input = (EditText) view.findViewById(R.id.text); input.setText(defaultValue); @@ -137,12 +163,12 @@ public class StoredList { } public void promptForListRename(final int listId, final Runnable runAfterRename) { - final StoredList list = app.getList(listId); + final StoredList list = cgData.getList(listId); handleListNameInput(list.title, R.string.list_dialog_rename_title, R.string.list_dialog_rename, new RunnableWithArgument<String>() { @Override public void run(final String listName) { - app.renameList(listId, listName); + cgData.renameList(listId, listName); if (runAfterRename != null) { runAfterRename.run(); } diff --git a/main/src/cgeo/geocaching/cgTrackable.java b/main/src/cgeo/geocaching/Trackable.java index 7ed3424..f777351 100644 --- a/main/src/cgeo/geocaching/cgTrackable.java +++ b/main/src/cgeo/geocaching/Trackable.java @@ -11,7 +11,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; -public class cgTrackable implements ILogable { +public class Trackable implements ILogable { static final public int SPOTTED_UNSET = 0; static final public int SPOTTED_CACHE = 1; static final public int SPOTTED_USER = 2; @@ -38,17 +38,17 @@ public class cgTrackable implements ILogable { private String trackingcode = null; public String getUrl() { - if (StringUtils.startsWithIgnoreCase(geocode, "GK")) { - String hex = geocode.substring(3); + 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("cgTrackable.getUrl", e); + Log.e("Trackable.getUrl", e); return null; } } - return "http://www.geocaching.com//track/details.aspx?tracker=" + geocode.toUpperCase(); + return "http://www.geocaching.com//track/details.aspx?tracker=" + geocode; } public String getGuid() { @@ -65,7 +65,7 @@ public class cgTrackable implements ILogable { } public void setGeocode(String geocode) { - this.geocode = geocode; + this.geocode = StringUtils.upperCase(geocode); } public String getIconUrl() { @@ -208,7 +208,7 @@ public class cgTrackable implements ILogable { } public boolean isLoggable() { - return !StringUtils.startsWithIgnoreCase(geocode, "GK"); + return !StringUtils.startsWithIgnoreCase(getGeocode(), "GK"); } public String getTrackingcode() { diff --git a/main/src/cgeo/geocaching/TrackableActivity.java b/main/src/cgeo/geocaching/TrackableActivity.java new file mode 100644 index 0000000..fea4521 --- /dev/null +++ b/main/src/cgeo/geocaching/TrackableActivity.java @@ -0,0 +1,681 @@ +package cgeo.geocaching; + +import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.activity.AbstractViewPagerActivity; +import cgeo.geocaching.connector.gc.GCParser; +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.CacheDetailsCreator; +import cgeo.geocaching.ui.Formatter; +import cgeo.geocaching.utils.BaseUtils; +import cgeo.geocaching.utils.Log; +import cgeo.geocaching.utils.UnknownTagsHandler; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; + +import android.app.ProgressDialog; +import android.content.Intent; +import android.graphics.drawable.BitmapDrawable; +import android.net.Uri; +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; +import android.view.MenuItem; +import android.view.View; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ListView; +import android.widget.RelativeLayout; +import android.widget.ScrollView; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivity.Page> { + + public enum Page { + DETAILS(R.string.detail), + LOGS(R.string.cache_logs); + + private final int resId; + + Page(final int resId) { + 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; + private String guid = null; + private String id = null; + private String contextMenuUser = null; + private LayoutInflater inflater = null; + private ProgressDialog waitDialog = null; + private Handler loadTrackableHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + if (trackable == null) { + if (waitDialog != null) { + waitDialog.dismiss(); + } + + if (StringUtils.isNotBlank(geocode)) { + showToast(res.getString(R.string.err_tb_find) + " " + geocode + "."); + } else { + showToast(res.getString(R.string.err_tb_find_that)); + } + + finish(); + return; + } + + try { + inflater = getLayoutInflater(); + geocode = trackable.getGeocode(); + + if (StringUtils.isNotBlank(trackable.getName())) { + setTitle(Html.fromHtml(trackable.getName()).toString()); + } else { + setTitle(trackable.getName()); + } + + invalidateOptionsMenuCompatible(); + reinitializeViewPager(); + + } catch (Exception e) { + Log.e("TrackableActivity.loadTrackableHandler: ", e); + } + + if (waitDialog != null) { + waitDialog.dismiss(); + } + } + }; + + public TrackableActivity() { + super("c:geo-trackable-details"); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setTheme(); + setContentView(R.layout.trackable_activity); + setTitle(res.getString(R.string.trackable)); + + // get parameters + Bundle extras = getIntent().getExtras(); + Uri uri = getIntent().getData(); + + // try to get data from extras + if (extras != null) { + geocode = extras.getString(Intents.EXTRA_GEOCODE); + name = extras.getString(Intents.EXTRA_NAME); + guid = extras.getString(Intents.EXTRA_GUID); + id = extras.getString(Intents.EXTRA_ID); + } + + // try to get data from URI + if (geocode == null && guid == null && id == null && uri != null) { + String uriHost = uri.getHost().toLowerCase(Locale.US); + if (uriHost.contains("geocaching.com")) { + geocode = uri.getQueryParameter("tracker"); + guid = uri.getQueryParameter("guid"); + id = uri.getQueryParameter("id"); + + if (StringUtils.isNotBlank(geocode)) { + geocode = geocode.toUpperCase(Locale.US); + guid = null; + id = null; + } else if (StringUtils.isNotBlank(guid)) { + geocode = null; + guid = guid.toLowerCase(Locale.US); + id = null; + } else if (StringUtils.isNotBlank(id)) { + geocode = null; + guid = null; + id = id.toLowerCase(Locale.US); + } else { + showToast(res.getString(R.string.err_tb_details_open)); + finish(); + return; + } + } else if (uriHost.contains("coord.info")) { + String uriPath = uri.getPath().toLowerCase(Locale.US); + if (uriPath != null && uriPath.startsWith("/tb")) { + geocode = uriPath.substring(1).toUpperCase(Locale.US); + guid = null; + id = null; + } else { + showToast(res.getString(R.string.err_tb_details_open)); + finish(); + return; + } + } + } + + // no given data + if (geocode == null && guid == null && id == null) { + showToast(res.getString(R.string.err_tb_display)); + finish(); + return; + } + + String message; + if (StringUtils.isNotBlank(name)) { + message = Html.fromHtml(name).toString(); + } else if (StringUtils.isNotBlank(geocode)) { + message = geocode; + } else { + message = res.getString(R.string.trackable); + } + 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); + thread.start(); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo info) { + 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); + + 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))) { + contextMenuUser = trackable.getSpottedName(); + } + } + + menu.setHeaderTitle(res.getString(R.string.user_menu_title) + " " + contextMenuUser); + menu.add(viewId, 1, 0, res.getString(R.string.user_menu_view_hidden)); + menu.add(viewId, 2, 0, res.getString(R.string.user_menu_view_found)); + menu.add(viewId, 3, 0, res.getString(R.string.user_menu_open_browser)); + menu.add(viewId, 4, 0, res.getString(R.string.user_menu_send_message)); + } + + @Override + public boolean onContextItemSelected(final MenuItem item) { + switch (item.getItemId()) { + case 1: + cgeocaches.startActivityOwner(this, contextMenuUser); + return true; + case 2: + cgeocaches.startActivityUserName(this, contextMenuUser); + return true; + case 3: + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.geocaching.com/profile/?u=" + Network.encode(contextMenuUser)))); + return true; + case 4: + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.geocaching.com/email/?u=" + Network.encode(contextMenuUser)))); + return true; + default: + return false; + } + } + + @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 + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case MENU_LOG_TOUCH: + LogTrackableActivity.startActivity(this, trackable); + return true; + case MENU_BROWSER_TRACKABLE: + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(trackable.getUrl()))); + return true; + default: + return false; + } + } + + @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())); + } + return super.onPrepareOptionsMenu(menu); + } + + private class LoadTrackableThread extends Thread { + final private Handler handler; + final private String geocode; + final private String guid; + final private String id; + + public LoadTrackableThread(Handler handlerIn, String geocodeIn, String guidIn, String idIn) { + handler = handlerIn; + geocode = geocodeIn; + guid = guidIn; + id = idIn; + } + + @Override + public void run() { + trackable = cgData.loadTrackable(geocode); + + if ((trackable == null || trackable.isLoggable()) && !StringUtils.startsWithIgnoreCase(geocode, "GK")) { + trackable = GCParser.searchTrackable(geocode, guid, id); + } + handler.sendMessage(Message.obtain()); + } + } + + private class UserActionsListener implements View.OnClickListener { + + @Override + public void onClick(View view) { + if (view == null) { + return; + } + + try { + registerForContextMenu(view); + openContextMenu(view); + } catch (Exception e) { + Log.e("TrackableActivity.UserActionsListener.onClick ", e); + } + } + } + + private class TrackableIconThread extends Thread { + final private String url; + final private Handler handler; + + public TrackableIconThread(String urlIn, Handler handlerIn) { + url = urlIn; + handler = handlerIn; + } + + @Override + public void run() { + if (url == null || handler == null) { + return; + } + + try { + HtmlImage imgGetter = new HtmlImage(trackable.getGeocode(), false, 0, false); + + BitmapDrawable image = imgGetter.getDrawable(url); + Message message = handler.obtainMessage(0, image); + handler.sendMessage(message); + } catch (Exception e) { + Log.e("TrackableActivity.TrackableIconThread.run: ", e); + } + } + } + + private static class TrackableIconHandler extends Handler { + final private TextView view; + + public TrackableIconHandler(TextView viewIn) { + view = viewIn; + } + + @Override + public void handleMessage(Message message) { + final BitmapDrawable image = (BitmapDrawable) message.obj; + if (image != null && view != null) { + image.setBounds(0, 0, view.getHeight(), view.getHeight()); + view.setCompoundDrawables(image, null, null, null); + } + } + } + + public static void startActivity(final AbstractActivity fromContext, + final String guid, final String geocode, final String name) { + final Intent trackableIntent = new Intent(fromContext, TrackableActivity.class); + trackableIntent.putExtra(Intents.EXTRA_GUID, guid); + trackableIntent.putExtra(Intents.EXTRA_GEOCODE, geocode); + trackableIntent.putExtra(Intents.EXTRA_NAME, name); + fromContext.startActivity(trackableIntent); + } + + @Override + protected PageViewCreator createViewCreator(Page page) { + switch (page) { + case DETAILS: + return new DetailsViewCreator(); + case LOGS: + return new LogsViewCreator(); + default: + throw new IllegalArgumentException(); + } + } + + @Override + protected String getTitle(Page page) { + return res.getString(page.resId); + } + + @Override + protected Pair<List<? extends Page>, Integer> getOrderedPages() { + List<Page> pages = new ArrayList<TrackableActivity.Page>(); + pages.add(Page.DETAILS); + if (!trackable.getLogs().isEmpty()) { + pages.add(Page.LOGS); + } + return new ImmutablePair<List<? extends Page>, Integer>(pages, 0); + } + + 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()) { + @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); + } + LogViewHolder holder = (LogViewHolder) rowView.getTag(); + if (null == holder) { + holder = new LogViewHolder(rowView); + rowView.setTag(holder); + } + + final LogEntry log = getItem(position); + fillViewHolder(holder, log); + return rowView; + } + }); + } + return view; + } + + protected void fillViewHolder(LogViewHolder holder, LogEntry log) { + if (log.date > 0) { + holder.added.setText(Formatter.formatShortDate(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); + } else { + holder.location.setText(Html.fromHtml(log.cacheName)); + final String cacheGuid = log.cacheGuid; + final String cacheName = log.cacheName; + holder.location.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View arg0) { + CacheDetailActivity.startActivityGuid(TrackableActivity.this, cacheGuid, Html.fromHtml(cacheName).toString()); + } + }); + } + + TextView logView = holder.log; + logView.setMovementMethod(LinkMovementMethod.getInstance()); + + String logText = log.log; + if (BaseUtils.containsHtml(logText)) { + logText = log.getDisplayText(); + logView.setText(Html.fromHtml(logText, new HtmlImage(null, false, StoredList.TEMPORARY_LIST_ID, false), null), TextView.BufferType.SPANNABLE); + } + else { + logView.setText(logText); + } + + ImageView statusMarker = holder.marker; + // colored marker + int marker = log.type.markerId; + if (marker != 0) { + statusMarker.setVisibility(View.VISIBLE); + statusMarker.setImageResource(marker); + } + else { + statusMarker.setVisibility(View.GONE); + } + + // add LogImages + LinearLayout logLayout = holder.logImages; + + if (log.hasLogImages()) { + + final ArrayList<Image> logImages = new ArrayList<Image>(log.getLogImages()); + + final View.OnClickListener listener = new View.OnClickListener() { + @Override + public void onClick(View v) { + ImagesActivity.startActivityLogImages(TrackableActivity.this, trackable.getGeocode(), logImages); + } + }; + + 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); + } + + holder.author.setOnClickListener(new UserActionsListener()); + } + + } + + public class DetailsViewCreator extends AbstractCachingPageViewCreator<ScrollView> { + + @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)); + + // action bar icon + if (StringUtils.isNotBlank(trackable.getIconUrl())) { + final TrackableIconHandler iconHandler = new TrackableIconHandler(((TextView) findViewById(R.id.actionbar_title))); + final TrackableIconThread iconThread = new TrackableIconThread(trackable.getIconUrl(), iconHandler); + iconThread.start(); + } + + // trackable name + details.add(R.string.trackable_name, StringUtils.isNotBlank(trackable.getName()) ? Html.fromHtml(trackable.getName()).toString() : res.getString(R.string.trackable_unknown)); + + // trackable type + String tbType; + if (StringUtils.isNotBlank(trackable.getType())) { + tbType = Html.fromHtml(trackable.getType()).toString(); + } else { + tbType = res.getString(R.string.trackable_unknown); + } + details.add(R.string.trackable_type, tbType); + + // trackable geocode + details.add(R.string.trackable_code, trackable.getGeocode()); + + // trackable owner + 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()); + } + + // trackable spotted + if (StringUtils.isNotBlank(trackable.getSpottedName()) || + trackable.getSpottedType() == Trackable.SPOTTED_UNKNOWN || + trackable.getSpottedType() == Trackable.SPOTTED_OWNER) { + boolean showTimeSpan = true; + StringBuilder text; + + if (trackable.getSpottedType() == Trackable.SPOTTED_CACHE) { + text = new StringBuilder(res.getString(R.string.trackable_spotted_in_cache) + ' ' + Html.fromHtml(trackable.getSpottedName()).toString()); + } else if (trackable.getSpottedType() == Trackable.SPOTTED_USER) { + text = new StringBuilder(res.getString(R.string.trackable_spotted_at_user) + ' ' + Html.fromHtml(trackable.getSpottedName()).toString()); + } else if (trackable.getSpottedType() == Trackable.SPOTTED_UNKNOWN) { + text = new StringBuilder(res.getString(R.string.trackable_spotted_unknown_location)); + } else if (trackable.getSpottedType() == Trackable.SPOTTED_OWNER) { + text = new StringBuilder(res.getString(R.string.trackable_spotted_owner)); + } else { + text = new StringBuilder("N/A"); + showTimeSpan = false; + } + + // days since last spotting + if (showTimeSpan && trackable.getLogs() != null) { + for (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(')'); + break; + } + } + } + + final TextView spotted = details.add(R.string.trackable_spotted, text.toString()); + spotted.setClickable(true); + if (Trackable.SPOTTED_CACHE == trackable.getSpottedType()) { + spotted.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View arg0) { + CacheDetailActivity.startActivityGuid(TrackableActivity.this, trackable.getSpottedGuid(), trackable.getSpottedName()); + } + }); + } else if (Trackable.SPOTTED_USER == trackable.getSpottedType()) { + spotted.setOnClickListener(new UserActionsListener()); + } + } + + // trackable origin + if (StringUtils.isNotBlank(trackable.getOrigin())) { + TextView origin = details.add(R.string.trackable_origin, ""); + origin.setText(Html.fromHtml(trackable.getOrigin()), TextView.BufferType.SPANNABLE); + } + + // trackable released + if (trackable.getReleased() != null) { + details.add(R.string.trackable_released, Formatter.formatDate(trackable.getReleased().getTime())); + } + + // trackable distance + if (trackable.getDistance() >= 0) { + details.add(R.string.trackable_distance, Units.getDistanceFromKilometers(trackable.getDistance())); + } + + // 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()); + } + + // 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()); + } + + // trackable image + if (StringUtils.isNotBlank(trackable.getImage())) { + view.findViewById(R.id.image_box).setVisibility(View.VISIBLE); + LinearLayout imgView = (LinearLayout) view.findViewById(R.id.image); + + final ImageView trackableImage = (ImageView) inflater.inflate(R.layout.trackable_image, null); + + trackableImage.setImageResource(R.drawable.image_not_loaded); + trackableImage.setClickable(true); + trackableImage.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View arg0) { + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(trackable.getImage()))); + } + }); + + // try to load image + final Handler handler = new Handler() { + + @Override + public void handleMessage(Message message) { + BitmapDrawable image = (BitmapDrawable) message.obj; + if (image != null) { + trackableImage.setImageDrawable((BitmapDrawable) message.obj); + } + } + }; + + new Thread() { + + @Override + public void run() { + try { + HtmlImage imgGetter = new HtmlImage(geocode, true, 0, false); + + BitmapDrawable image = imgGetter.getDrawable(trackable.getImage()); + Message message = handler.obtainMessage(0, image); + handler.sendMessage(message); + } catch (Exception e) { + Log.e("cgeospoilers.onCreate.onClick.run: ", e); + } + } + }.start(); + + imgView.addView(trackableImage); + } + return view; + } + + } + +} diff --git a/main/src/cgeo/geocaching/VisitCacheActivity.java b/main/src/cgeo/geocaching/VisitCacheActivity.java index 443ef3a..25d36df 100644 --- a/main/src/cgeo/geocaching/VisitCacheActivity.java +++ b/main/src/cgeo/geocaching/VisitCacheActivity.java @@ -7,26 +7,33 @@ import cgeo.geocaching.enumerations.LogType; import cgeo.geocaching.enumerations.LogTypeTrackable; import cgeo.geocaching.enumerations.StatusCode; import cgeo.geocaching.gcvote.GCVote; -import cgeo.geocaching.network.Network; +import cgeo.geocaching.loaders.UrlLoader; import cgeo.geocaching.network.Parameters; import cgeo.geocaching.twitter.Twitter; -import cgeo.geocaching.ui.DateDialog; import cgeo.geocaching.ui.Formatter; +import cgeo.geocaching.ui.dialog.DateDialog; 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.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.content.res.Configuration; +import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; -import android.view.ContextMenu; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.Loader; +import android.util.SparseArray; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; @@ -42,134 +49,158 @@ import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; +import java.util.Locale; -public class VisitCacheActivity extends AbstractLoggingActivity implements DateDialog.DateDialogParent { - static final String EXTRAS_FOUND = "found"; - static final String EXTRAS_TEXT = "text"; +public class VisitCacheActivity extends AbstractLoggingActivity implements DateDialog.DateDialogParent, LoaderManager.LoaderCallbacks<String> { static final String EXTRAS_GEOCODE = "geocode"; static final String EXTRAS_ID = "id"; private static final int SUBMENU_VOTE = 3; + private static final String SAVED_STATE_RATING = "cgeo.geocaching.saved_state_rating"; + private static final String SAVED_STATE_TYPE = "cgeo.geocaching.saved_state_type"; + private static final String SAVED_STATE_DATE = "cgeo.geocaching.saved_state_date"; + 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 int SELECT_IMAGE = 101; private LayoutInflater inflater = null; - private cgCache cache = null; + private Geocache cache = null; private ProgressDialog waitDialog = null; private String cacheid = null; private String geocode = null; private String text = null; - private boolean alreadyFound = false; private List<LogType> possibleLogTypes = new ArrayList<LogType>(); private String[] viewstates = null; - private boolean gettingViewstate = true; private List<TrackableLog> trackables = null; - private Calendar date = Calendar.getInstance(); - private LogType typeSelected = LogType.UNKNOWN; - private int attempts = 0; private Button postButton = null; - private Button clearButton = null; private CheckBox tweetCheck = null; private LinearLayout tweetBox = null; - private double rating = 0.0; private boolean tbChanged = false; + private SparseArray<TrackableLog> actionButtons; - // handlers - private final Handler loadDataHandler = new Handler() { + // Data to be saved while reconfiguring + private double rating; + private LogType typeSelected; + private Calendar date; + private String imageCaption; + private String imageDescription; + private Uri imageUri; - @Override - public void handleMessage(Message msg) { - if (!possibleLogTypes.contains(typeSelected)) { - typeSelected = possibleLogTypes.get(0); - setType(typeSelected); + @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)); + } - showToast(res.getString(R.string.info_log_type_changed)); - } + @Override + public void onLoaderReset(final Loader<String> loader) { + // Nothing to do + } - if (Login.isEmpty(viewstates)) { - if (attempts < 2) { - new LoadDataThread().start(); - } else { - showToast(res.getString(R.string.err_log_load_data)); - showProgress(false); - } - return; + @Override + public void onLoadFinished(final Loader<String> loader, final String page) { + if (page == null) { + showToast(res.getString(R.string.err_log_load_data)); + showProgress(false); + return; + } + + viewstates = Login.getViewstates(page); + trackables = GCParser.parseTrackableLog(page); + possibleLogTypes = GCParser.parseTypes(page); + possibleLogTypes.remove(LogType.UPDATE_COORDINATES); + + if (!possibleLogTypes.contains(typeSelected)) { + typeSelected = possibleLogTypes.get(0); + setType(typeSelected); + + showToast(res.getString(R.string.info_log_type_changed)); + } + + enablePostButton(true); + + initializeTrackablesAction(); + updateTrackablesList(); + + showProgress(false); + } + + private void initializeTrackablesAction() { + if (Settings.isTrackableAutoVisit()) { + for (TrackableLog trackable : trackables) { + trackable.action = LogTypeTrackable.VISITED; + tbChanged = true; } + } + } - gettingViewstate = false; // we're done, user can post log + private void updateTrackablesList() { + if (CollectionUtils.isEmpty(trackables)) { + return; + } + if (inflater == null) { + inflater = getLayoutInflater(); + } + actionButtons = new SparseArray<TrackableLog>(); - enablePostButton(true); + final LinearLayout inventoryView = (LinearLayout) findViewById(R.id.inventory); + inventoryView.removeAllViews(); - // add trackables - if (CollectionUtils.isNotEmpty(trackables)) { - if (inflater == null) { - inflater = getLayoutInflater(); - } + for (TrackableLog tb : trackables) { + LinearLayout inventoryItem = (LinearLayout) inflater.inflate(R.layout.visit_trackable, null); - final LinearLayout inventoryView = (LinearLayout) findViewById(R.id.inventory); - inventoryView.removeAllViews(); + ((TextView) inventoryItem.findViewById(R.id.trackcode)).setText(tb.trackCode); + ((TextView) inventoryItem.findViewById(R.id.name)).setText(tb.name); + final TextView actionButton = (TextView) inventoryItem.findViewById(R.id.action); + actionButton.setId(tb.id); + actionButtons.put(actionButton.getId(), tb); + actionButton.setText(res.getString(tb.action.resourceId) + " ▼"); + actionButton.setOnClickListener(new View.OnClickListener() { - for (TrackableLog tb : trackables) { - LinearLayout inventoryItem = (LinearLayout) inflater.inflate(R.layout.visit_trackable, null); - - ((TextView) inventoryItem.findViewById(R.id.trackcode)).setText(tb.trackCode); - ((TextView) inventoryItem.findViewById(R.id.name)).setText(tb.name); - ((TextView) inventoryItem.findViewById(R.id.action)) - .setText(res.getString(Settings.isTrackableAutoVisit() - ? LogTypeTrackable.VISITED.resourceId - : LogTypeTrackable.DO_NOTHING.resourceId) - + " ▼"); - - inventoryItem.setId(tb.id); - final String tbCode = tb.trackCode; - inventoryItem.setClickable(true); - registerForContextMenu(inventoryItem); - inventoryItem.findViewById(R.id.info).setOnClickListener(new View.OnClickListener() { - - @Override - public void onClick(View view) { - final Intent trackablesIntent = new Intent(VisitCacheActivity.this, cgeotrackable.class); - trackablesIntent.putExtra(EXTRAS_GEOCODE, tbCode); - startActivity(trackablesIntent); - } - }); - inventoryItem.findViewById(R.id.action).setOnClickListener(new View.OnClickListener() { - - @Override - public void onClick(View view) { - openContextMenu(view); - } - }); - - inventoryView.addView(inventoryItem); - - if (Settings.isTrackableAutoVisit()) { - tb.action = LogTypeTrackable.VISITED; - tbChanged = true; - } + @Override + public void onClick(View view) { + selectTrackableAction(view); } + }); + + final String tbCode = tb.trackCode; + inventoryItem.setClickable(true); + inventoryItem.findViewById(R.id.info).setOnClickListener(new View.OnClickListener() { - if (inventoryView.getChildCount() > 0) { - findViewById(R.id.inventory_box).setVisibility(View.VISIBLE); + @Override + public void onClick(View view) { + final Intent trackablesIntent = new Intent(VisitCacheActivity.this, TrackableActivity.class); + trackablesIntent.putExtra(Intents.EXTRA_GEOCODE, tbCode); + startActivity(trackablesIntent); } - if (inventoryView.getChildCount() > 1) { - final LinearLayout inventoryChangeAllView = (LinearLayout) findViewById(R.id.inventory_changeall); + }); - final Button changeButton = (Button) inventoryChangeAllView.findViewById(R.id.changebutton); - registerForContextMenu(changeButton); - changeButton.setOnClickListener(new View.OnClickListener() { + inventoryView.addView(inventoryItem); + } + + if (inventoryView.getChildCount() > 0) { + findViewById(R.id.inventory_box).setVisibility(View.VISIBLE); + } + if (inventoryView.getChildCount() > 1) { + final LinearLayout inventoryChangeAllView = (LinearLayout) findViewById(R.id.inventory_changeall); - @Override - public void onClick(View view) { - openContextMenu(view); - } - }); + final Button changeButton = (Button) inventoryChangeAllView.findViewById(R.id.changebutton); + changeButton.setOnClickListener(new View.OnClickListener() { - inventoryChangeAllView.setVisibility(View.VISIBLE); + @Override + public void onClick(View view) { + selectAllTrackablesAction(); } - } + }); - showProgress(false); + inventoryChangeAllView.setVisibility(View.VISIBLE); } - }; + } private void enablePostButton(boolean enabled) { postButton.setEnabled(enabled); @@ -184,20 +215,20 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD } private void updatePostButtonText() { - if (postButton.isEnabled()) { - if (typeSelected == LogType.FOUND_IT && Settings.isGCvoteLogin()) { - if (rating == 0) { - postButton.setText(res.getString(R.string.log_post_no_rate)); - } else { - postButton.setText(res.getString(R.string.log_post_rate) + " " + ratingTextValue(rating) + "*"); - } - } else { - postButton.setText(res.getString(R.string.log_post)); - } + postButton.setText(getPostButtonText()); + } + + private String getPostButtonText() { + if (!postButton.isEnabled()) { + return res.getString(R.string.log_post_not_possible); } - else { - postButton.setText(res.getString(R.string.log_post_not_possible)); + if (typeSelected != LogType.FOUND_IT || !Settings.isGCvoteLogin()) { + return res.getString(R.string.log_post); } + if (rating == 0) { + return res.getString(R.string.log_post_no_rate); + } + return res.getString(R.string.log_post_rate) + " " + ratingTextValue(rating) + "*"; } private final Handler postLogHandler = new Handler() { @@ -233,44 +264,158 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD } @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setTheme(); setContentView(R.layout.visit); setTitle(res.getString(R.string.log_new_log)); - // get parameters + // Get parameters from intent and basic cache information from database final Bundle extras = getIntent().getExtras(); if (extras != null) { cacheid = extras.getString(EXTRAS_ID); geocode = extras.getString(EXTRAS_GEOCODE); - text = extras.getString(EXTRAS_TEXT); - alreadyFound = extras.getBoolean(EXTRAS_FOUND); } if ((StringUtils.isBlank(cacheid)) && StringUtils.isNotBlank(geocode)) { - cacheid = app.getCacheid(geocode); + cacheid = cgData.getCacheidForGeocode(geocode); } if (StringUtils.isBlank(geocode) && StringUtils.isNotBlank(cacheid)) { - geocode = app.getGeocode(cacheid); + geocode = cgData.getGeocodeForGuid(cacheid); } - cache = cgeoapplication.getInstance().loadCache(geocode, LoadFlags.LOAD_CACHE_OR_DB); + cache = cgData.loadCache(geocode, LoadFlags.LOAD_CACHE_OR_DB); + possibleLogTypes = cache.getPossibleLogTypes(); if (StringUtils.isNotBlank(cache.getName())) { setTitle(res.getString(R.string.log_new_log) + ": " + cache.getName()); } else { - setTitle(res.getString(R.string.log_new_log) + ": " + cache.getGeocode().toUpperCase()); + setTitle(res.getString(R.string.log_new_log) + ": " + cache.getGeocode()); + } + + // Get ids for later use + postButton = (Button) findViewById(R.id.post); + tweetBox = (LinearLayout) findViewById(R.id.tweet_box); + tweetCheck = (CheckBox) findViewById(R.id.tweet); + + // initialize with default values + setDefaultValues(); + + // Restore previous state + if (savedInstanceState != null) { + rating = savedInstanceState.getDouble(SAVED_STATE_RATING); + typeSelected = LogType.getById(savedInstanceState.getInt(SAVED_STATE_TYPE)); + date.setTimeInMillis(savedInstanceState.getLong(SAVED_STATE_DATE)); + imageCaption = savedInstanceState.getString(SAVED_STATE_IMAGE_CAPTION); + imageDescription = savedInstanceState.getString(SAVED_STATE_IMAGE_DESCRIPTION); + imageUri = Uri.parse(savedInstanceState.getString(SAVED_STATE_IMAGE_URI)); + } else { + // If log had been previously saved, load it now, otherwise initialize signature as needed + final LogEntry log = cgData.loadLogOffline(geocode); + if (log != null) { + typeSelected = log.type; + date.setTime(new Date(log.date)); + text = log.log; + } else if (StringUtils.isNotBlank(Settings.getSignature()) + && Settings.isAutoInsertSignature() + && StringUtils.isBlank(currentLogText())) { + insertIntoLog(LogTemplateProvider.applyTemplates(Settings.getSignature(), new LogContext(cache)), false); + } } + updatePostButtonText(); + setImageButtonText(); + enablePostButton(false); - init(); + final Button typeButton = (Button) findViewById(R.id.type); + typeButton.setText(typeSelected.getL10n()); + typeButton.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View view) { + selectLogType(); + } + }); + + final Button dateButton = (Button) findViewById(R.id.date); + setDate(date); + dateButton.setOnClickListener(new DateListener()); + + final EditText logView = (EditText) findViewById(R.id.log); + if (StringUtils.isBlank(currentLogText()) && StringUtils.isNotBlank(text)) { + logView.setText(text); + } + + tweetCheck.setChecked(true); + + final Button imageButton = (Button) findViewById(R.id.image_btn); + imageButton.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View view) { + selectImage(); + } + }); + + final Button saveButton = (Button) findViewById(R.id.save); + saveButton.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + saveLog(true); + } + }); + + final Button clearButton = (Button) findViewById(R.id.clear); + clearButton.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + clearLog(); + } + }); + + getSupportLoaderManager().initLoader(0, null, this); } - @Override - public void onResume() { - super.onResume(); + private void setDefaultValues() { + date = Calendar.getInstance(); + rating = 0.0; + if (cache.isEventCache()) { + if (cache.hasOwnLog(LogType.WILL_ATTEND)) { + typeSelected = LogType.ATTENDED; + } + else { + typeSelected = LogType.WILL_ATTEND; + } + } + else { + if (cache.isFound()) { + typeSelected = LogType.NOTE; + } else { + typeSelected = LogType.FOUND_IT; + } + } + text = null; + imageCaption = ""; + imageDescription = ""; + imageUri = Uri.EMPTY; + } + + private void clearLog() { + cgData.clearLogOffline(geocode); + + setDefaultValues(); + + setType(typeSelected); + setDate(date); + + final EditText logView = (EditText) findViewById(R.id.log); + logView.setText(StringUtils.EMPTY); + + setImageButtonText(); + showToast(res.getString(R.string.info_log_cleared)); } @Override @@ -280,13 +425,6 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD } @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - - init(); - } - - @Override public boolean onCreateOptionsMenu(final Menu menu) { super.onCreateOptionsMenu(menu); @@ -335,184 +473,18 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD } private static String ratingTextValue(final double rating) { - return String.format("%.1f", rating); + return String.format(Locale.getDefault(), "%.1f", rating); } @Override - public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo info) { - super.onCreateContextMenu(menu, view, info); - final int viewId = view.getId(); - - if (viewId == R.id.type) { - for (final LogType typeOne : possibleLogTypes) { - menu.add(viewId, typeOne.id, 0, typeOne.getL10n()); - Log.w("Adding " + typeOne + " " + typeOne.getL10n()); - } - } else if (viewId == R.id.changebutton) { - final int textId = findViewById(viewId).getId(); - - menu.setHeaderTitle(res.getString(R.string.log_tb_changeall)); - for (LogTypeTrackable logType : LogTypeTrackable.values()) { - menu.add(textId, logType.id, 0, res.getString(logType.resourceId)); - } - } else { - final int realViewId = findViewById(viewId).getId(); - - for (final TrackableLog tb : trackables) { - if (tb.id == realViewId) { - menu.setHeaderTitle(tb.name); - } - } - for (LogTypeTrackable logType : LogTypeTrackable.values()) { - menu.add(realViewId, logType.id, 0, res.getString(logType.resourceId)); - } - } - } - - @Override - public boolean onContextItemSelected(MenuItem item) { - final int group = item.getGroupId(); - final int id = item.getItemId(); - - if (group == R.id.type) { - setType(LogType.getById(id)); - return true; - } - - if (group == R.id.changebutton) { - try { - final LogTypeTrackable logType = LogTypeTrackable.findById(id); - if (logType != null) { - final LinearLayout inventView = (LinearLayout) findViewById(R.id.inventory); - for (int count = 0; count < inventView.getChildCount(); count++) { - final LinearLayout tbView = (LinearLayout) inventView.getChildAt(count); - if (tbView == null) { - return false; - } - - final TextView tbText = (TextView) tbView.findViewById(R.id.action); - if (tbText == null) { - return false; - } - tbText.setText(res.getString(logType.resourceId) + " ▼"); - } - for (TrackableLog tb : trackables) { - tb.action = logType; - } - tbChanged = true; - return true; - } - } catch (Exception e) { - Log.e("cgeovisit.onContextItemSelected: " + e.toString()); - } - } else { - try { - final LogTypeTrackable logType = LogTypeTrackable.findById(id); - if (logType != null) { - final LinearLayout tbView = (LinearLayout) findViewById(group); - if (tbView == null) { - return false; - } - - final TextView tbText = (TextView) tbView.findViewById(R.id.action); - if (tbText == null) { - return false; - } - - for (TrackableLog tb : trackables) { - if (tb.id == group) { - tbChanged = true; - - tb.action = logType; - tbText.setText(res.getString(logType.resourceId) + " ▼"); - - Log.i("Trackable " + tb.trackCode + " (" + tb.name + ") has new action: #" + id); - } - } - - return true; - } - } catch (Exception e) { - Log.e("cgeovisit.onContextItemSelected: " + e.toString()); - } - } - - return false; - } - - public void init() { - postButton = (Button) findViewById(R.id.post); - tweetBox = (LinearLayout) findViewById(R.id.tweet_box); - tweetCheck = (CheckBox) findViewById(R.id.tweet); - clearButton = (Button) findViewById(R.id.clear); - final Button saveButton = (Button) findViewById(R.id.save); - - possibleLogTypes = cache.getPossibleLogTypes(); - - final LogEntry log = app.loadLogOffline(geocode); - if (log != null) { - typeSelected = log.type; - date.setTime(new Date(log.date)); - text = log.log; - updatePostButtonText(); - } else if (StringUtils.isNotBlank(Settings.getSignature()) - && Settings.isAutoInsertSignature() - && StringUtils.isBlank(((EditText) findViewById(R.id.log)).getText())) { - insertIntoLog(LogTemplateProvider.applyTemplates(Settings.getSignature(), new LogContext(cache)), false); - } - - if (!possibleLogTypes.contains(typeSelected)) { - if (alreadyFound) { - typeSelected = LogType.NOTE; - } else { - typeSelected = possibleLogTypes.get(0); - } - setType(typeSelected); - } - - final Button typeButton = (Button) findViewById(R.id.type); - registerForContextMenu(typeButton); - typeButton.setText(typeSelected.getL10n()); - typeButton.setOnClickListener(new View.OnClickListener() { - - @Override - public void onClick(View view) { - openContextMenu(view); - } - }); - - final Button dateButton = (Button) findViewById(R.id.date); - setDate(date); - dateButton.setOnClickListener(new DateListener()); - - final EditText logView = (EditText) findViewById(R.id.log); - if (StringUtils.isBlank(logView.getText()) && StringUtils.isNotBlank(text)) { - logView.setText(text); - } - - tweetCheck.setChecked(true); - - final ActivityState lastState = (ActivityState) getLastNonConfigurationInstance(); - if (lastState != null) { - lastState.restore(this); - } - - if (Login.isEmpty(viewstates)) { - enablePostButton(false); - new LoadDataThread().start(); - } else { - enablePostButton(true); - } - - saveButton.setOnClickListener(new View.OnClickListener() { - - @Override - public void onClick(View v) { - saveLog(true); - } - }); - - clearButton.setOnClickListener(new ClearListener()); + protected void onSaveInstanceState(final Bundle outState) { + super.onSaveInstanceState(outState); + outState.putDouble(SAVED_STATE_RATING, rating); + outState.putInt(SAVED_STATE_TYPE, typeSelected.id); + outState.putLong(SAVED_STATE_DATE, date.getTimeInMillis()); + outState.putString(SAVED_STATE_IMAGE_URI, imageUri.getPath()); + outState.putString(SAVED_STATE_IMAGE_CAPTION, imageCaption); + outState.putString(SAVED_STATE_IMAGE_DESCRIPTION, imageDescription); } @Override @@ -554,102 +526,14 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD } private class PostListener implements View.OnClickListener { - @Override public void onClick(View arg0) { - if (!gettingViewstate) { - waitDialog = ProgressDialog.show(VisitCacheActivity.this, null, res.getString(R.string.log_saving), true); - waitDialog.setCancelable(true); + 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 String log = ((EditText) findViewById(R.id.log)).getText().toString(); - final Thread thread = new PostLogThread(postLogHandler, log); - thread.start(); - } else { - showToast(res.getString(R.string.err_log_load_data_still)); - } - } - } - - private class ClearListener implements View.OnClickListener { - - @Override - public void onClick(View arg0) { - //TODO: unify this method and the code in init() - app.clearLogOffline(geocode); - - if (alreadyFound) { - typeSelected = LogType.NOTE; - } else { - typeSelected = possibleLogTypes.get(0); - } - date.setTime(new Date()); - text = null; - - setType(typeSelected); - - final Button dateButton = (Button) findViewById(R.id.date); - dateButton.setOnClickListener(new DateListener()); - setDate(date); - - final EditText logView = (EditText) findViewById(R.id.log); - logView.setText(""); - - clearButton.setOnClickListener(new ClearListener()); - - showToast(res.getString(R.string.info_log_cleared)); - } - } - - private class LoadDataThread extends Thread { - - public LoadDataThread() { - super("Load data for logging"); - if (cacheid == null) { - showToast(res.getString(R.string.err_detail_cache_forgot_visit)); - - finish(); - return; - } - if (!Settings.isLogin()) { // allow offline logging - showToast(res.getString(R.string.err_login)); - } - } - - @Override - public void run() { - if (!Settings.isLogin()) { - // enable only offline logging, don't get the current state of the cache - return; - } - final Parameters params = new Parameters(); - - gettingViewstate = true; - attempts++; - - try { - if (StringUtils.isNotBlank(cacheid)) { - params.put("ID", cacheid); - } else { - loadDataHandler.sendEmptyMessage(0); - return; - } - - final String page = Network.getResponseData(Network.getRequest("http://www.geocaching.com/seek/log.aspx", params)); - - viewstates = Login.getViewstates(page); - trackables = GCParser.parseTrackableLog(page); - - final List<LogType> typesPre = GCParser.parseTypes(page); - if (CollectionUtils.isNotEmpty(typesPre)) { - possibleLogTypes.clear(); - possibleLogTypes.addAll(typesPre); - possibleLogTypes.remove(LogType.UPDATE_COORDINATES); - } - } catch (Exception e) { - Log.e("cgeovisit.loadData.run: " + e.toString()); - } - - loadDataHandler.sendEmptyMessage(0); + final Thread thread = new PostLogThread(postLogHandler, currentLogText()); + thread.start(); } } @@ -672,40 +556,50 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD } public StatusCode postLogFn(String log) { + + StatusCode result = StatusCode.LOG_POST_ERROR; + try { - final StatusCode status = GCParser.postLog(geocode, cacheid, viewstates, typeSelected, + + 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); - if (status == StatusCode.NO_ERROR) { + result = logResult.left; + + if (logResult.left == StatusCode.NO_ERROR) { final LogEntry logNow = new LogEntry(date, typeSelected, log); - cache.getLogs().prepend(logNow); + cache.getLogs().add(0, logNow); - if (typeSelected == LogType.FOUND_IT) { + if (typeSelected == LogType.FOUND_IT || typeSelected == LogType.ATTENDED) { cache.setFound(true); } - app.updateCache(cache); + cgData.saveChangedCache(cache); } - if (status == StatusCode.NO_ERROR) { - app.clearLogOffline(geocode); + if (logResult.left == StatusCode.NO_ERROR) { + cgData.clearLogOffline(geocode); } - if (status == StatusCode.NO_ERROR && typeSelected == LogType.FOUND_IT && Settings.isUseTwitter() + if (logResult.left == StatusCode.NO_ERROR && typeSelected == LogType.FOUND_IT && Settings.isUseTwitter() && Settings.isTwitterLoginValid() && tweetCheck.isChecked() && tweetBox.getVisibility() == View.VISIBLE) { Twitter.postTweetCache(geocode); } - if (status == StatusCode.NO_ERROR && typeSelected == LogType.FOUND_IT && Settings.isGCvoteLogin()) { + if (logResult.left == StatusCode.NO_ERROR && typeSelected == LogType.FOUND_IT && Settings.isGCvoteLogin()) { GCVote.setRating(cache, rating); } - return status; + if (logResult.left == StatusCode.NO_ERROR && StringUtils.isNotBlank(imageUri.getPath())) { + result = GCParser.uploadLogImage(logResult.right, imageCaption, imageDescription, imageUri); + } + + return result; } catch (Exception e) { - Log.e("cgeovisit.postLogFn: " + e.toString()); + Log.e("cgeovisit.postLogFn", e); } return StatusCode.LOG_POST_ERROR; @@ -717,7 +611,7 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD // Do not erase the saved log if the user has removed all the characters // without using "Clear". This may be a manipulation mistake, and erasing // again will be easy using "Clear" while retyping the text may not be. - if (force || (log.length() > 0 && !StringUtils.equals(log, text))) { + if (force || (StringUtils.isNotEmpty(log) && !StringUtils.equals(log, text))) { cache.logOffline(this, log, date, typeSelected); } text = log; @@ -727,40 +621,106 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD return ((EditText) findViewById(R.id.log)).getText().toString(); } - private class ActivityState { - private final String[] viewstates; - private final List<TrackableLog> trackables; - private final int attempts; - private final List<LogType> possibleLogTypes; - private final LogType typeSelected; - private final double rating; + @Override + protected LogContext getLogContext() { + return new LogContext(cache); + } - public ActivityState() { - this.viewstates = VisitCacheActivity.this.viewstates; - this.trackables = VisitCacheActivity.this.trackables; - this.attempts = VisitCacheActivity.this.attempts; - this.possibleLogTypes = VisitCacheActivity.this.possibleLogTypes; - this.typeSelected = VisitCacheActivity.this.typeSelected; - this.rating = VisitCacheActivity.this.rating; + private void selectAllTrackablesAction() { + Builder alert = new AlertDialog.Builder(this); + alert.setTitle(res.getString(R.string.log_tb_changeall)); + String[] tbLogTypes = getTBLogTypes(); + alert.setItems(tbLogTypes, new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int position) { + final LogTypeTrackable logType = LogTypeTrackable.values()[position]; + for (TrackableLog tb : trackables) { + tb.action = logType; + } + tbChanged = true; + updateTrackablesList(); + dialog.dismiss(); + } + }); + alert.create().show(); + } + + private String[] getTBLogTypes() { + final LogTypeTrackable[] logTypeValues = LogTypeTrackable.values(); + String[] logTypes = new String[logTypeValues.length]; + for (int i = 0; i < logTypes.length; i++) { + logTypes[i] = res.getString(logTypeValues[i].resourceId); } + return logTypes; + } - public void restore(final VisitCacheActivity activity) { - activity.viewstates = this.viewstates; - activity.trackables = this.trackables; - activity.attempts = this.attempts; - activity.possibleLogTypes = this.possibleLogTypes; - activity.typeSelected = this.typeSelected; - activity.rating = this.rating; + private void selectLogType() { + Builder alert = new AlertDialog.Builder(this); + String[] choices = new String[possibleLogTypes.size()]; + for (int i = 0; i < choices.length; i++) { + choices[i] = possibleLogTypes.get(i).getL10n(); } + alert.setSingleChoiceItems(choices, possibleLogTypes.indexOf(typeSelected), new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int position) { + setType(possibleLogTypes.get(position)); + dialog.dismiss(); + } + }); + alert.create().show(); } - @Override - public Object onRetainNonConfigurationInstance() { - return new ActivityState(); + private void selectTrackableAction(View view) { + final int realViewId = view.getId(); + Builder alert = new AlertDialog.Builder(this); + final TrackableLog trackableLog = actionButtons.get(realViewId); + alert.setTitle(trackableLog.name); + String[] tbLogTypes = getTBLogTypes(); + alert.setItems(tbLogTypes, new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int position) { + final LogTypeTrackable logType = LogTypeTrackable.values()[position]; + tbChanged = true; + trackableLog.action = logType; + Log.i("Trackable " + trackableLog.trackCode + " (" + trackableLog.name + ") has new action: #" + logType); + updateTrackablesList(); + dialog.dismiss(); + } + }); + alert.create().show(); + } + + private void selectImage() { + Intent selectImageIntent = new Intent(this, ImageSelectActivity.class); + selectImageIntent.putExtra(ImageSelectActivity.EXTRAS_CAPTION, imageCaption); + selectImageIntent.putExtra(ImageSelectActivity.EXTRAS_DESCRIPTION, imageDescription); + selectImageIntent.putExtra(ImageSelectActivity.EXTRAS_URI_AS_STRING, imageUri.toString()); + + startActivityForResult(selectImageIntent, SELECT_IMAGE); } @Override - protected LogContext getLogContext() { - return new LogContext(cache); + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == SELECT_IMAGE) { + if (resultCode == RESULT_OK) { + imageCaption = data.getStringExtra(ImageSelectActivity.EXTRAS_CAPTION); + imageDescription = data.getStringExtra(ImageSelectActivity.EXTRAS_DESCRIPTION); + imageUri = Uri.parse(data.getStringExtra(ImageSelectActivity.EXTRAS_URI_AS_STRING)); + } else if (resultCode != RESULT_CANCELED) { + // Image capture failed, advise user + showToast(getResources().getString(R.string.err_select_logimage_failed)); + } + setImageButtonText(); + + } + } + + private void setImageButtonText() { + final Button imageButton = (Button) findViewById(R.id.image_btn); + imageButton.setText(StringUtils.isNotBlank(imageUri.getPath()) ? + res.getString(R.string.log_image_edit) : res.getString(R.string.log_image_attach)); } } diff --git a/main/src/cgeo/geocaching/cgWaypoint.java b/main/src/cgeo/geocaching/Waypoint.java index 0e21c08..4b014a6 100644 --- a/main/src/cgeo/geocaching/cgWaypoint.java +++ b/main/src/cgeo/geocaching/Waypoint.java @@ -6,15 +6,19 @@ import cgeo.geocaching.geopoint.Geopoint; import org.apache.commons.lang3.StringUtils; import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; import android.widget.TextView; +import java.util.HashMap; import java.util.List; +import java.util.Map; -public class cgWaypoint implements IWaypoint, Comparable<cgWaypoint> { +public class Waypoint implements IWaypoint, Comparable<Waypoint> { public static final String PREFIX_OWN = "OWN"; private static final int ORDER_UNDEFINED = -2; - private int id = 0; + private int id = -1; private String geocode = "geocode"; private WaypointType waypointType = WaypointType.WAYPOINT; private String prefix = ""; @@ -25,6 +29,14 @@ public class cgWaypoint implements IWaypoint, Comparable<cgWaypoint> { private String note = ""; private int cachedOrder = ORDER_UNDEFINED; private boolean own = false; + private boolean visited = false; + // preliminary default for mdpi screens + private static int VISITED_INSET = 7; + + public static void initializeScale() { + // Calculate visited inset based on screen density + VISITED_INSET = (int) (6.6f * cgeoapplication.getInstance().getResources().getDisplayMetrics().density + 0.5f); + } /** * require name and type for every waypoint @@ -32,7 +44,7 @@ public class cgWaypoint implements IWaypoint, Comparable<cgWaypoint> { * @param name * @param type */ - public cgWaypoint(final String name, final WaypointType type, final boolean own) { + public Waypoint(final String name, final WaypointType type, final boolean own) { this.name = name; this.waypointType = type; this.own = own; @@ -43,17 +55,29 @@ public class cgWaypoint implements IWaypoint, Comparable<cgWaypoint> { * * @param other */ - public cgWaypoint(final cgWaypoint other) { + public Waypoint(final Waypoint other) { merge(other); this.waypointType = other.waypointType; - id = 0; + id = -1; } public void setIcon(final Resources res, final TextView nameView) { - nameView.setCompoundDrawablesWithIntrinsicBounds(res.getDrawable(waypointType.markerId), null, null, null); + Drawable icon; + if (visited) { + LayerDrawable ld = new LayerDrawable(new Drawable[] { + res.getDrawable(waypointType.markerId), + res.getDrawable(R.drawable.tick) }); + ld.setLayerInset(0, 0, 0, VISITED_INSET, VISITED_INSET); + ld.setLayerInset(1, VISITED_INSET, VISITED_INSET, 0, 0); + icon = ld; + } else { + icon = res.getDrawable(waypointType.markerId); + } + final Drawable fIcon = icon; + nameView.setCompoundDrawablesWithIntrinsicBounds(fIcon, null, null, null); } - public void merge(final cgWaypoint old) { + public void merge(final Waypoint old) { if (StringUtils.isBlank(prefix)) { setPrefix(old.prefix); } @@ -77,31 +101,26 @@ public class cgWaypoint implements IWaypoint, Comparable<cgWaypoint> { note = old.note; } } + if (id < 0) { + id = old.id; + } + visited = old.visited; } - public static void mergeWayPoints(List<cgWaypoint> newPoints, - List<cgWaypoint> oldPoints, boolean forceMerge) { - // copy user modified details of the waypoints - if (newPoints != null && oldPoints != null) { - for (cgWaypoint old : oldPoints) { - if (old != null) { - boolean merged = false; - if (old.name != null && old.name.length() > 0) { - for (cgWaypoint waypoint : newPoints) { - if (waypoint != null && waypoint.name != null) { - if (old.name.equalsIgnoreCase(waypoint.name)) { - waypoint.merge(old); - merged = true; - break; - } - } - } - } - // user added waypoints should also be in the new list - if (!merged && (old.isUserDefined() || forceMerge)) { - newPoints.add(old); - } - } + public static void mergeWayPoints(final List<Waypoint> newPoints, final List<Waypoint> oldPoints, final boolean forceMerge) { + // Build a map of new waypoints for faster subsequent lookups + final Map<String, Waypoint> newPrefixes = new HashMap<String, Waypoint>(newPoints.size()); + for (final Waypoint waypoint : newPoints) { + newPrefixes.put(waypoint.getPrefix(), waypoint); + } + + // Copy user modified details of the old waypoints over the new ones + for (final Waypoint oldWaypoint : oldPoints) { + final String prefix = oldWaypoint.getPrefix(); + if (newPrefixes.containsKey(prefix)) { + newPrefixes.get(prefix).merge(oldWaypoint); + } else if (oldWaypoint.isUserDefined() || forceMerge) { + newPoints.add(oldWaypoint); } } } @@ -142,7 +161,7 @@ public class cgWaypoint implements IWaypoint, Comparable<cgWaypoint> { } @Override - public int compareTo(cgWaypoint other) { + public int compareTo(Waypoint other) { return order() - other.order(); } @@ -156,7 +175,7 @@ public class cgWaypoint implements IWaypoint, Comparable<cgWaypoint> { } public String getUrl() { - return "http://www.geocaching.com//seek/cache_details.aspx?wp=" + geocode.toUpperCase(); + return "http://www.geocaching.com//seek/cache_details.aspx?wp=" + geocode; } @Override @@ -174,7 +193,7 @@ public class cgWaypoint implements IWaypoint, Comparable<cgWaypoint> { } public void setGeocode(String geocode) { - this.geocode = geocode; + this.geocode = StringUtils.upperCase(geocode); } @Override @@ -242,4 +261,12 @@ public class cgWaypoint implements IWaypoint, Comparable<cgWaypoint> { public String getCoordType() { return "waypoint"; } + + public void setVisited(boolean visited) { + this.visited = visited; + } + + public boolean isVisited() { + return visited; + } } diff --git a/main/src/cgeo/geocaching/WaypointPopup.java b/main/src/cgeo/geocaching/WaypointPopup.java index 7fbfe10..766d43d 100644 --- a/main/src/cgeo/geocaching/WaypointPopup.java +++ b/main/src/cgeo/geocaching/WaypointPopup.java @@ -17,9 +17,8 @@ import android.widget.LinearLayout; import android.widget.TextView; public class WaypointPopup extends AbstractPopupActivity { - private static final String EXTRA_WAYPOINT_ID = "waypoint_id"; private int waypointId = 0; - private cgWaypoint waypoint = null; + private Waypoint waypoint = null; public WaypointPopup() { super("c:geo-waypoint-info", R.layout.waypoint_popup); @@ -31,19 +30,19 @@ public class WaypointPopup extends AbstractPopupActivity { // get parameters final Bundle extras = getIntent().getExtras(); if (extras != null) { - waypointId = extras.getInt(EXTRA_WAYPOINT_ID); + waypointId = extras.getInt(Intents.EXTRA_WAYPOINT_ID); } } @Override protected void init() { super.init(); - waypoint = app.loadWaypoint(waypointId); + waypoint = cgData.loadWaypoint(waypointId); try { if (StringUtils.isNotBlank(waypoint.getName())) { setTitle(waypoint.getName()); } else { - setTitle(waypoint.getGeocode().toUpperCase()); + setTitle(waypoint.getGeocode()); } // actionbar icon @@ -53,7 +52,7 @@ public class WaypointPopup extends AbstractPopupActivity { details = new CacheDetailsCreator(this, (LinearLayout) findViewById(R.id.waypoint_details_list)); //Waypoint geocode - details.add(R.string.cache_geocode, waypoint.getPrefix().toUpperCase() + waypoint.getGeocode().toUpperCase().substring(2)); + details.add(R.string.cache_geocode, waypoint.getPrefix() + waypoint.getGeocode().substring(2)); // Edit Button final Button buttonEdit = (Button) findViewById(R.id.edit); @@ -73,7 +72,7 @@ public class WaypointPopup extends AbstractPopupActivity { addCacheDetails(); } catch (Exception e) { - Log.e("cgeopopup.init: " + e.toString()); + Log.e("cgeopopup.init", e); } } @@ -83,7 +82,7 @@ public class WaypointPopup extends AbstractPopupActivity { } /** - * Tries to navigate to the {@link cgCache} of this activity. + * Tries to navigate to the {@link Geocache} of this activity. */ @Override protected void startDefaultNavigation2() { @@ -97,8 +96,8 @@ public class WaypointPopup extends AbstractPopupActivity { public static void startActivity(final Context context, final int waypointId, final String geocode) { final Intent popupIntent = new Intent(context, WaypointPopup.class); - popupIntent.putExtra(EXTRA_WAYPOINT_ID, waypointId); - popupIntent.putExtra(EXTRA_GEOCODE, geocode); + popupIntent.putExtra(Intents.EXTRA_WAYPOINT_ID, waypointId); + popupIntent.putExtra(Intents.EXTRA_GEOCODE, geocode); context.startActivity(popupIntent); } diff --git a/main/src/cgeo/geocaching/activity/AbstractActivity.java b/main/src/cgeo/geocaching/activity/AbstractActivity.java index a648486..557665e 100644 --- a/main/src/cgeo/geocaching/activity/AbstractActivity.java +++ b/main/src/cgeo/geocaching/activity/AbstractActivity.java @@ -5,16 +5,16 @@ import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.compatibility.Compatibility; import cgeo.geocaching.network.Cookies; -import android.app.Activity; import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.os.Bundle; +import android.support.v4.app.FragmentActivity; import android.view.View; import android.widget.EditText; -public abstract class AbstractActivity extends Activity implements IAbstractActivity { +public abstract class AbstractActivity extends FragmentActivity implements IAbstractActivity { - private String helpTopic; + final private String helpTopic; protected cgeoapplication app = null; protected Resources res = null; diff --git a/main/src/cgeo/geocaching/activity/AbstractListActivity.java b/main/src/cgeo/geocaching/activity/AbstractListActivity.java index dc9c0fe..f96a769 100644 --- a/main/src/cgeo/geocaching/activity/AbstractListActivity.java +++ b/main/src/cgeo/geocaching/activity/AbstractListActivity.java @@ -3,13 +3,13 @@ package cgeo.geocaching.activity; import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.compatibility.Compatibility; -import android.app.ListActivity; import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.os.Bundle; +import android.support.v4.app.FragmentListActivity; import android.view.View; -public abstract class AbstractListActivity extends ListActivity implements +public abstract class AbstractListActivity extends FragmentListActivity implements IAbstractActivity { private String helpTopic; diff --git a/main/src/cgeo/geocaching/activity/AbstractViewPagerActivity.java b/main/src/cgeo/geocaching/activity/AbstractViewPagerActivity.java new file mode 100644 index 0000000..366a59d --- /dev/null +++ b/main/src/cgeo/geocaching/activity/AbstractViewPagerActivity.java @@ -0,0 +1,276 @@ +package cgeo.geocaching.activity; + +import cgeo.geocaching.R; +import cgeo.geocaching.utils.Log; + +import com.viewpagerindicator.TitlePageIndicator; +import com.viewpagerindicator.TitleProvider; + +import org.apache.commons.lang3.tuple.Pair; + +import android.app.Activity; +import android.os.Parcelable; +import android.support.v4.view.PagerAdapter; +import android.support.v4.view.ViewPager; +import android.support.v4.view.ViewPager.OnPageChangeListener; +import android.view.View; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Abstract activity with the ability to manage pages in a view pager. + * + * @param <Page> + * Enum listing all available pages of this activity. The pages available at a certain point of time are + * defined by overriding {@link #getOrderedPages()}. + */ +public abstract class AbstractViewPagerActivity<Page extends Enum<Page>> extends AbstractActivity { + + protected AbstractViewPagerActivity(String helpTopic) { + super(helpTopic); + } + + /** + * A {@link List} of all available pages. + * + * TODO Move to adapter + */ + private final List<Page> pageOrder = new ArrayList<Page>(); + + /** + * Instances of all {@link PageViewCreator}. + */ + private final Map<Page, PageViewCreator> viewCreators = new HashMap<Page, PageViewCreator>(); + + /** + * The {@link ViewPager} for this activity. + */ + private ViewPager viewPager; + + /** + * The {@link ViewPagerAdapter} for this activity. + */ + private ViewPagerAdapter viewPagerAdapter; + + /** + * The {@link TitlePageIndicator} for this activity. + */ + private TitlePageIndicator titleIndicator; + + public interface PageViewCreator { + /** + * Returns a validated view. + * + * @return + */ + public View getDispatchedView(); + + /** + * Returns a (maybe cached) view. + * + * @return + */ + public View getView(); + + /** + * Handles changed data-sets. + */ + public void notifyDataSetChanged(); + } + + /** + * Page selection interface for the view pager. + * + */ + protected interface OnPageSelectedListener { + public void onPageSelected(int position); + } + + /** + * The ViewPagerAdapter for scrolling through pages of the CacheDetailActivity. + */ + private class ViewPagerAdapter extends PagerAdapter implements TitleProvider { + + @Override + public void destroyItem(View container, int position, Object object) { + ((ViewPager) container).removeView((View) object); + } + + @Override + public void finishUpdate(View container) { + } + + @Override + public int getCount() { + return pageOrder.size(); + } + + @Override + public Object instantiateItem(View container, int position) { + final Page page = pageOrder.get(position); + + PageViewCreator creator = viewCreators.get(page); + + if (null == creator && null != page) { + creator = AbstractViewPagerActivity.this.createViewCreator(page); + viewCreators.put(page, creator); + } + + View view = null; + + try { + if (null != creator) { + // Result from getView() is maybe cached, but it should be valid because the + // creator should be informed about data-changes with notifyDataSetChanged() + view = creator.getView(); + ((ViewPager) container).addView(view, 0); + } + } catch (Exception e) { + Log.e("ViewPagerAdapter.instantiateItem ", e); + } + + return view; + } + + @Override + public boolean isViewFromObject(View view, Object object) { + return view == object; + } + + @Override + public void restoreState(Parcelable arg0, ClassLoader arg1) { + } + + @Override + public Parcelable saveState() { + return null; + } + + @Override + public void startUpdate(View arg0) { + } + + @Override + public int getItemPosition(Object object) { + // We are doing the caching. So pretend that the view is gone. + // The ViewPager will get it back in instantiateItem() + return POSITION_NONE; + } + + @Override + public String getTitle(int position) { + final Page page = pageOrder.get(position); + if (null == page) { + return ""; + } + return AbstractViewPagerActivity.this.getTitle(page); + } + + } + + /** + * Create the view pager. Call this from the {@link Activity#onCreate} implementation. + * + * @param startPageIndex + * index of the page shown first + * @param pageSelectedListener + * page selection listener or <code>null</code> + */ + protected final void createViewPager(int startPageIndex, final OnPageSelectedListener pageSelectedListener) { + // initialize ViewPager + viewPager = (ViewPager) findViewById(R.id.viewpager); + viewPagerAdapter = new ViewPagerAdapter(); + viewPager.setAdapter(viewPagerAdapter); + + titleIndicator = (TitlePageIndicator) findViewById(R.id.pager_indicator); + titleIndicator.setViewPager(viewPager); + if (pageSelectedListener != null) { + titleIndicator.setOnPageChangeListener(new OnPageChangeListener() { + @Override + public void onPageSelected(int position) { + pageSelectedListener.onPageSelected(position); + } + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + } + + @Override + public void onPageScrollStateChanged(int state) { + } + }); + } + + // switch to entry page (last used or 2) + if (viewPagerAdapter.getCount() < startPageIndex) { + for (int i = 0; i <= startPageIndex; i++) { + // we can't switch to a page that is out of bounds, so we add null-pages + pageOrder.add(null); + } + } + viewPager.setCurrentItem(startPageIndex, false); + } + + /** + * create the view creator for the given page + * + * @return new view creator + */ + protected abstract PageViewCreator createViewCreator(Page page); + + /** + * get the title for the given page + */ + protected abstract String getTitle(Page page); + + protected final void reinitializeViewPager() { + // notify all creators that the data has changed + for (PageViewCreator creator : viewCreators.values()) { + creator.notifyDataSetChanged(); + } + + pageOrder.clear(); + final Pair<List<? extends Page>, Integer> pagesAndIndex = getOrderedPages(); + pageOrder.addAll(pagesAndIndex.getLeft()); + + // switch to details page, if we're out of bounds + final int defaultPage = pagesAndIndex.getRight(); + if (getCurrentItem() < 0 || getCurrentItem() >= 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(); + } + + /** + * @return the currently available list of ordered pages, together with the index of the default page + */ + protected abstract Pair<List<? extends Page>, Integer> getOrderedPages(); + + public final Page getPage(int position) { + return pageOrder.get(position); + } + + protected final int getPageIndex(Page page) { + return pageOrder.indexOf(page); + } + + protected final PageViewCreator getViewCreator(Page page) { + return viewCreators.get(page); + } + + protected final boolean isCurrentPage(Page page) { + return getCurrentItem() == getPageIndex(page); + } + + protected int getCurrentItem() { + return viewPager.getCurrentItem(); + } +} diff --git a/main/src/cgeo/geocaching/activity/ActivityMixin.java b/main/src/cgeo/geocaching/activity/ActivityMixin.java index 8900593..de2dade 100644 --- a/main/src/cgeo/geocaching/activity/ActivityMixin.java +++ b/main/src/cgeo/geocaching/activity/ActivityMixin.java @@ -79,6 +79,22 @@ public final class ActivityMixin { } } + public static int getTheme() { + if (Settings.isLightSkin()) { + return R.style.light; + } + + return R.style.dark; + } + + public static int getDialogTheme() { + if (Settings.isLightSkin()) { + return R.style.popup_light; + } + + return R.style.popup_dark; + } + public static void showToast(final Activity activity, final String text) { if (StringUtils.isNotBlank(text)) { Toast toast = Toast.makeText(activity, text, Toast.LENGTH_LONG); diff --git a/main/src/cgeo/geocaching/activity/FilteredActivity.java b/main/src/cgeo/geocaching/activity/FilteredActivity.java new file mode 100644 index 0000000..0370d63 --- /dev/null +++ b/main/src/cgeo/geocaching/activity/FilteredActivity.java @@ -0,0 +1,11 @@ +package cgeo.geocaching.activity; + +import android.view.View; + +public interface FilteredActivity { + /** + * called from the filter bar view + */ + public void showFilterMenu(final View view); + +} diff --git a/main/src/cgeo/geocaching/activity/Progress.java b/main/src/cgeo/geocaching/activity/Progress.java index 1aa1942..34e7623 100644 --- a/main/src/cgeo/geocaching/activity/Progress.java +++ b/main/src/cgeo/geocaching/activity/Progress.java @@ -1,6 +1,6 @@ package cgeo.geocaching.activity; -import cgeo.geocaching.ui.CustomProgressDialog; +import cgeo.geocaching.ui.dialog.CustomProgressDialog; import android.app.ProgressDialog; import android.content.Context; diff --git a/main/src/cgeo/geocaching/apps/AbstractApp.java b/main/src/cgeo/geocaching/apps/AbstractApp.java index 678b98c..c95e8b4 100644 --- a/main/src/cgeo/geocaching/apps/AbstractApp.java +++ b/main/src/cgeo/geocaching/apps/AbstractApp.java @@ -1,6 +1,6 @@ package cgeo.geocaching.apps; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; import cgeo.geocaching.cgeo; import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.utils.ProcessUtils; @@ -56,7 +56,7 @@ public abstract class AbstractApp implements App { } @Override - public boolean isEnabled(cgCache cache) { + public boolean isEnabled(Geocache cache) { return cache != null; } } diff --git a/main/src/cgeo/geocaching/apps/AbstractLocusApp.java b/main/src/cgeo/geocaching/apps/AbstractLocusApp.java index ac6fc1c..53620e4 100644 --- a/main/src/cgeo/geocaching/apps/AbstractLocusApp.java +++ b/main/src/cgeo/geocaching/apps/AbstractLocusApp.java @@ -1,8 +1,8 @@ package cgeo.geocaching.apps; +import cgeo.geocaching.Geocache; import cgeo.geocaching.R; -import cgeo.geocaching.cgCache; -import cgeo.geocaching.cgWaypoint; +import cgeo.geocaching.Waypoint; import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.enumerations.CacheSize; import cgeo.geocaching.enumerations.CacheType; @@ -22,6 +22,7 @@ import android.location.Location; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.List; +import java.util.Locale; /** * for the Locus API: @@ -30,7 +31,7 @@ import java.util.List; */ public abstract class AbstractLocusApp extends AbstractApp { private static final String INTENT = Intent.ACTION_VIEW; - private static final SimpleDateFormat ISO8601DATE = new SimpleDateFormat("yyyy-MM-dd'T'"); + private static final SimpleDateFormat ISO8601DATE = new SimpleDateFormat("yyyy-MM-dd'T'", Locale.US); protected AbstractLocusApp() { super(getString(R.string.caches_map_locus), INTENT); @@ -47,11 +48,11 @@ public abstract class AbstractLocusApp extends AbstractApp { /** * Display a list of caches / waypoints in Locus - * + * * @param objectsToShow * which caches/waypoints to show * @param withCacheWaypoints - * wether to give waypoints of caches to Locus or not + * Whether to give waypoints of caches to Locus or not * @param activity */ protected static boolean showInLocus(final List<?> objectsToShow, final boolean withCacheWaypoints, final boolean export, @@ -65,10 +66,10 @@ public abstract class AbstractLocusApp extends AbstractApp { for (Object o : objectsToShow) { Point p = null; // get icon and Point - if (o instanceof cgCache) { - p = getCachePoint((cgCache) o, withCacheWaypoints, withCacheDetails); - } else if (o instanceof cgWaypoint) { - p = getWaypointPoint((cgWaypoint) o); + if (o instanceof Geocache) { + p = getCachePoint((Geocache) o, withCacheWaypoints, withCacheDetails); + } else if (o instanceof Waypoint) { + p = getWaypointPoint((Waypoint) o); } if (p != null) { pd.addPoint(p); @@ -85,7 +86,7 @@ public abstract class AbstractLocusApp extends AbstractApp { final ArrayList<PointsData> data = new ArrayList<PointsData>(); data.add(pd); DisplayData.sendDataCursor(activity, data, - "content://" + LocusDataStorageProvider.class.getCanonicalName().toLowerCase(), + "content://" + LocusDataStorageProvider.class.getCanonicalName().toLowerCase(Locale.US), export); } @@ -103,7 +104,7 @@ public abstract class AbstractLocusApp extends AbstractApp { * should be false for all if more then 200 Caches are transferred * @return null, when the <code>Point</code> could not be constructed */ - private static Point getCachePoint(cgCache cache, boolean withWaypoints, boolean withCacheDetails) { + private static Point getCachePoint(Geocache cache, boolean withWaypoints, boolean withCacheDetails) { if (cache == null || cache.getCoords() == null) { return null; } @@ -145,7 +146,7 @@ public abstract class AbstractLocusApp extends AbstractApp { if (withWaypoints && cache.hasWaypoints()) { pg.waypoints = new ArrayList<PointGeocachingDataWaypoint>(); - for (cgWaypoint waypoint : cache.getWaypoints()) { + for (Waypoint waypoint : cache.getWaypoints()) { if (waypoint == null || waypoint.getCoords() == null) { continue; } @@ -181,7 +182,7 @@ public abstract class AbstractLocusApp extends AbstractApp { * @param waypoint * @return null, when the <code>Point</code> could not be constructed */ - private static Point getWaypointPoint(cgWaypoint waypoint) { + private static Point getWaypointPoint(Waypoint waypoint) { if (waypoint == null || waypoint.getCoords() == null) { return null; } diff --git a/main/src/cgeo/geocaching/apps/App.java b/main/src/cgeo/geocaching/apps/App.java index 9d6d371..bc99526 100644 --- a/main/src/cgeo/geocaching/apps/App.java +++ b/main/src/cgeo/geocaching/apps/App.java @@ -1,6 +1,6 @@ package cgeo.geocaching.apps; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; public interface App { public boolean isInstalled(); @@ -17,5 +17,5 @@ public interface App { * @param cache * @return */ - boolean isEnabled(final cgCache cache); + boolean isEnabled(final Geocache cache); } diff --git a/main/src/cgeo/geocaching/apps/LocusDataStorageProvider.java b/main/src/cgeo/geocaching/apps/LocusDataStorageProvider.java index 6897f95..03954f5 100644 --- a/main/src/cgeo/geocaching/apps/LocusDataStorageProvider.java +++ b/main/src/cgeo/geocaching/apps/LocusDataStorageProvider.java @@ -4,16 +4,12 @@ import menion.android.locus.addon.publiclib.geoData.PointsData; import menion.android.locus.addon.publiclib.utils.DataCursor; import menion.android.locus.addon.publiclib.utils.DataStorage; -import org.apache.commons.collections.CollectionUtils; - import android.content.ContentProvider; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import android.os.Parcel; -import java.util.ArrayList; - /** * code provided by menion - developer of Locus */ @@ -23,20 +19,14 @@ public class LocusDataStorageProvider extends ContentProvider { public Cursor query(Uri aUri, String[] aProjection, String aSelection, String[] aSelectionArgs, String aSortOrder) { - DataCursor cursor = new DataCursor(new String[] { "data" }); - - ArrayList<PointsData> data = DataStorage.getData(); - if (CollectionUtils.isEmpty(data)) { - return cursor; - } + final DataCursor cursor = new DataCursor(new String[] { "data" }); - for (int i = 0; i < data.size(); i++) { - // get byte array - Parcel par = Parcel.obtain(); - data.get(i).writeToParcel(par, 0); - byte[] byteData = par.marshall(); - // add to row - cursor.addRow(new Object[] { byteData }); + for (final PointsData item : DataStorage.getData()) { + final Parcel par = Parcel.obtain(); + item.writeToParcel(par, 0); + // add byte array to row + cursor.addRow(new Object[] { par.marshall() }); + par.recycle(); } // data filled to cursor, clear reference to prevent some memory issue DataStorage.clearData(); diff --git a/main/src/cgeo/geocaching/apps/cache/AbstractGeneralApp.java b/main/src/cgeo/geocaching/apps/cache/AbstractGeneralApp.java index 61528ea..fd7d4b5 100644 --- a/main/src/cgeo/geocaching/apps/cache/AbstractGeneralApp.java +++ b/main/src/cgeo/geocaching/apps/cache/AbstractGeneralApp.java @@ -1,25 +1,24 @@ package cgeo.geocaching.apps.cache; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; import cgeo.geocaching.apps.AbstractApp; +import cgeo.geocaching.apps.cache.navi.CacheNavigationApp; import android.app.Activity; import android.content.Intent; -abstract class AbstractGeneralApp extends AbstractApp implements GeneralApp { +abstract class AbstractGeneralApp extends AbstractApp implements CacheNavigationApp { protected AbstractGeneralApp(String name, String packageName) { super(name, null, packageName); } @Override - public boolean invoke(Activity activity, cgCache cache) { + public void navigate(Activity activity, Geocache cache) { final Intent intent = getLaunchIntent(); if (intent != null) { intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); activity.startActivity(intent); - return true; } - return false; } } diff --git a/main/src/cgeo/geocaching/apps/cache/CacheBeaconApp.java b/main/src/cgeo/geocaching/apps/cache/CacheBeaconApp.java index 6e7cdca..9cfafb4 100644 --- a/main/src/cgeo/geocaching/apps/cache/CacheBeaconApp.java +++ b/main/src/cgeo/geocaching/apps/cache/CacheBeaconApp.java @@ -1,21 +1,18 @@ package cgeo.geocaching.apps.cache; +import cgeo.geocaching.Geocache; import cgeo.geocaching.R; -import cgeo.geocaching.cgCache; import cgeo.geocaching.enumerations.CacheAttribute; public class CacheBeaconApp extends AbstractGeneralApp { - protected CacheBeaconApp() { + public CacheBeaconApp() { super(getString(R.string.cache_menu_cachebeacon), "de.fun2code.android.cachebeacon"); } @Override - public boolean isEnabled(cgCache cache) { - if (cache == null) { - return false; - } - return cache.hasAttribute(CacheAttribute.WIRELESS_BEACON, true); + public boolean isEnabled(Geocache cache) { + return cache.hasAttribute(CacheAttribute.WIRELESSBEACON, true); } } diff --git a/main/src/cgeo/geocaching/apps/cache/GccApp.java b/main/src/cgeo/geocaching/apps/cache/GccApp.java index 9000d9e..b129b45 100644 --- a/main/src/cgeo/geocaching/apps/cache/GccApp.java +++ b/main/src/cgeo/geocaching/apps/cache/GccApp.java @@ -2,8 +2,8 @@ package cgeo.geocaching.apps.cache; import cgeo.geocaching.R; -class GccApp extends AbstractGeneralApp { - GccApp() { +public class GccApp extends AbstractGeneralApp { + public GccApp() { super(getString(R.string.cache_menu_gcc), "eisbehr.gcc"); } } diff --git a/main/src/cgeo/geocaching/apps/cache/GeneralApp.java b/main/src/cgeo/geocaching/apps/cache/GeneralApp.java deleted file mode 100644 index cdbedd5..0000000 --- a/main/src/cgeo/geocaching/apps/cache/GeneralApp.java +++ /dev/null @@ -1,12 +0,0 @@ -package cgeo.geocaching.apps.cache; - -import cgeo.geocaching.cgCache; -import cgeo.geocaching.apps.App; - -import android.app.Activity; - -interface GeneralApp extends App { - - public boolean invoke(Activity activity, cgCache cache); - -} diff --git a/main/src/cgeo/geocaching/apps/cache/GeneralAppsFactory.java b/main/src/cgeo/geocaching/apps/cache/GeneralAppsFactory.java deleted file mode 100644 index 57eb957..0000000 --- a/main/src/cgeo/geocaching/apps/cache/GeneralAppsFactory.java +++ /dev/null @@ -1,48 +0,0 @@ -package cgeo.geocaching.apps.cache; - -import cgeo.geocaching.cgCache; -import cgeo.geocaching.apps.AbstractAppFactory; -import cgeo.geocaching.utils.Log; - -import org.apache.commons.lang3.ArrayUtils; - -import android.app.Activity; -import android.view.Menu; -import android.view.MenuItem; - -public final class GeneralAppsFactory extends AbstractAppFactory { - private static GeneralApp[] apps = new GeneralApp[] {}; - - private static GeneralApp[] getGeneralApps() { - if (ArrayUtils.isEmpty(apps)) { - apps = new GeneralApp[] { - new CacheBeaconApp(), - new GccApp(), - new WhereYouGoApp() - }; - } - return apps; - } - - public static void addMenuItems(Menu menu, cgCache cache) { - for (GeneralApp app : getGeneralApps()) { - if (app.isInstalled() && app.isEnabled(cache)) { - menu.add(0, app.getId(), 0, app.getName()); - } - } - } - - public static boolean onMenuItemSelected(final MenuItem item, Activity activity, cgCache cache) { - final GeneralApp app = (GeneralApp) getAppFromMenuItem(item, apps); - if (app == null) { - return false; - } - try { - app.invoke(activity, cache); - } catch (Exception e) { - Log.e("GeneralAppsFactory.onMenuItemSelected: " + e.toString()); - } - return true; - } - -} diff --git a/main/src/cgeo/geocaching/apps/cache/WhereYouGoApp.java b/main/src/cgeo/geocaching/apps/cache/WhereYouGoApp.java index 8c16eaf..39e1963 100644 --- a/main/src/cgeo/geocaching/apps/cache/WhereYouGoApp.java +++ b/main/src/cgeo/geocaching/apps/cache/WhereYouGoApp.java @@ -1,16 +1,16 @@ package cgeo.geocaching.apps.cache; import cgeo.geocaching.R; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; import cgeo.geocaching.enumerations.CacheType; -class WhereYouGoApp extends AbstractGeneralApp { - WhereYouGoApp() { +public class WhereYouGoApp extends AbstractGeneralApp { + public WhereYouGoApp() { super(getString(R.string.cache_menu_whereyougo), "menion.android.whereyougo"); } @Override - public boolean isEnabled(cgCache cache) { - return cache != null && cache.getType() == CacheType.WHERIGO; + public boolean isEnabled(Geocache cache) { + return cache.getType() == CacheType.WHERIGO; } } diff --git a/main/src/cgeo/geocaching/apps/cache/navi/AbstractNavigationApp.java b/main/src/cgeo/geocaching/apps/cache/navi/AbstractNavigationApp.java deleted file mode 100644 index 27cb47c..0000000 --- a/main/src/cgeo/geocaching/apps/cache/navi/AbstractNavigationApp.java +++ /dev/null @@ -1,26 +0,0 @@ -package cgeo.geocaching.apps.cache.navi; - -import cgeo.geocaching.cgWaypoint; -import cgeo.geocaching.apps.AbstractApp; -import cgeo.geocaching.geopoint.Geopoint; - -abstract class AbstractNavigationApp extends AbstractApp implements NavigationApp { - - protected AbstractNavigationApp(String name, String intent, String packageName) { - super(name, intent, packageName); - } - - protected AbstractNavigationApp(String name, String intent) { - super(name, intent); - } - - @Override - public boolean isEnabled(cgWaypoint waypoint) { - return waypoint != null; - } - - @Override - public boolean isEnabled(Geopoint geopoint) { - return geopoint != null; - } -} diff --git a/main/src/cgeo/geocaching/apps/cache/navi/AbstractPointNavigationApp.java b/main/src/cgeo/geocaching/apps/cache/navi/AbstractPointNavigationApp.java index ca8c7db..a3ea57e 100644 --- a/main/src/cgeo/geocaching/apps/cache/navi/AbstractPointNavigationApp.java +++ b/main/src/cgeo/geocaching/apps/cache/navi/AbstractPointNavigationApp.java @@ -1,7 +1,7 @@ package cgeo.geocaching.apps.cache.navi; -import cgeo.geocaching.cgCache; -import cgeo.geocaching.cgWaypoint; +import cgeo.geocaching.Geocache; +import cgeo.geocaching.Waypoint; import cgeo.geocaching.apps.AbstractApp; import android.app.Activity; @@ -20,22 +20,22 @@ abstract class AbstractPointNavigationApp extends AbstractApp implements CacheNa } @Override - public void navigate(Activity activity, cgCache cache) { + public void navigate(Activity activity, Geocache cache) { navigate(activity, cache.getCoords()); } @Override - public void navigate(Activity activity, cgWaypoint waypoint) { + public void navigate(Activity activity, Waypoint waypoint) { navigate(activity, waypoint.getCoords()); } @Override - public boolean isEnabled(cgCache cache) { + public boolean isEnabled(Geocache cache) { return cache.getCoords() != null; } @Override - public boolean isEnabled(cgWaypoint waypoint) { + public boolean isEnabled(Waypoint waypoint) { return waypoint.getCoords() != null; } } diff --git a/main/src/cgeo/geocaching/apps/cache/navi/AbstractStaticMapsApp.java b/main/src/cgeo/geocaching/apps/cache/navi/AbstractStaticMapsApp.java index f27b53c..d089e82 100644 --- a/main/src/cgeo/geocaching/apps/cache/navi/AbstractStaticMapsApp.java +++ b/main/src/cgeo/geocaching/apps/cache/navi/AbstractStaticMapsApp.java @@ -4,9 +4,9 @@ import cgeo.geocaching.ILogable; import cgeo.geocaching.R; import cgeo.geocaching.StaticMapsActivity; import cgeo.geocaching.StaticMapsProvider; -import cgeo.geocaching.cgCache; -import cgeo.geocaching.cgWaypoint; -import cgeo.geocaching.cgeoapplication; +import cgeo.geocaching.Waypoint; +import cgeo.geocaching.Geocache; +import cgeo.geocaching.cgData; import cgeo.geocaching.activity.ActivityMixin; import cgeo.geocaching.apps.AbstractApp; @@ -15,7 +15,7 @@ import org.apache.commons.lang3.StringUtils; import android.app.Activity; abstract class AbstractStaticMapsApp extends AbstractApp implements CacheNavigationApp, WaypointNavigationApp { - public AbstractStaticMapsApp(String name) { + protected AbstractStaticMapsApp(String name) { super(name, null); } @@ -29,18 +29,19 @@ abstract class AbstractStaticMapsApp extends AbstractApp implements CacheNavigat return false; } - protected static boolean hasStaticMap(cgWaypoint waypoint) { - if (waypoint==null) + protected static boolean hasStaticMap(Waypoint waypoint) { + if (waypoint==null) { return false; + } String geocode = waypoint.getGeocode(); int id = waypoint.getId(); - if (StringUtils.isNotEmpty(geocode) && cgeoapplication.getInstance().isOffline(geocode, null)) { + if (StringUtils.isNotEmpty(geocode) && cgData.isOffline(geocode, null)) { return StaticMapsProvider.hasStaticMapForWaypoint(geocode, id); } return false; } - protected static boolean invokeStaticMaps(final Activity activity, final cgCache cache, final cgWaypoint waypoint, final boolean download) { + protected static boolean invokeStaticMaps(final Activity activity, final Geocache cache, final Waypoint waypoint, final boolean download) { final ILogable logable = cache != null && cache.getListId() != 0 ? cache : waypoint; // If the cache is not stored for offline, cache seems to be null and waypoint may be null too if (logable==null || logable.getGeocode()==null ) { diff --git a/main/src/cgeo/geocaching/apps/cache/navi/CacheNavigationApp.java b/main/src/cgeo/geocaching/apps/cache/navi/CacheNavigationApp.java index e47150f..d47bdc0 100644 --- a/main/src/cgeo/geocaching/apps/cache/navi/CacheNavigationApp.java +++ b/main/src/cgeo/geocaching/apps/cache/navi/CacheNavigationApp.java @@ -1,6 +1,6 @@ package cgeo.geocaching.apps.cache.navi; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; import cgeo.geocaching.apps.App; import android.app.Activity; @@ -10,8 +10,8 @@ import android.app.Activity; * */ public interface CacheNavigationApp extends App { - void navigate(final Activity activity, final cgCache cache); + void navigate(final Activity activity, final Geocache cache); @Override - boolean isEnabled(final cgCache cache); + boolean isEnabled(final Geocache cache); }
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/apps/cache/navi/CompassApp.java b/main/src/cgeo/geocaching/apps/cache/navi/CompassApp.java index 5275d53..4811916 100644 --- a/main/src/cgeo/geocaching/apps/cache/navi/CompassApp.java +++ b/main/src/cgeo/geocaching/apps/cache/navi/CompassApp.java @@ -1,16 +1,15 @@ package cgeo.geocaching.apps.cache.navi; +import cgeo.geocaching.Geocache; import cgeo.geocaching.R; -import cgeo.geocaching.cgCache; -import cgeo.geocaching.cgWaypoint; +import cgeo.geocaching.Waypoint; import cgeo.geocaching.cgeonavigate; -import cgeo.geocaching.apps.AbstractApp; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.ui.Formatter; import android.app.Activity; -class CompassApp extends AbstractApp implements CacheNavigationApp, WaypointNavigationApp, GeopointNavigationApp { +class CompassApp extends AbstractPointNavigationApp { CompassApp() { super(getString(R.string.compass_title), null); @@ -27,25 +26,15 @@ class CompassApp extends AbstractApp implements CacheNavigationApp, WaypointNavi } @Override - public void navigate(Activity activity, cgWaypoint waypoint) { + public void navigate(Activity activity, Waypoint waypoint) { cgeonavigate.startActivity(activity, waypoint.getPrefix() + "/" + waypoint.getLookup(), waypoint.getName(), waypoint.getCoords(), null, waypoint.getWaypointType().getL10n()); } @Override - public boolean isEnabled(cgWaypoint waypoint) { - return waypoint.getCoords() != null; - } - - @Override - public void navigate(Activity activity, cgCache cache) { + public void navigate(Activity activity, Geocache cache) { cgeonavigate.startActivity(activity, cache.getGeocode(), cache.getName(), cache.getCoords(), null, Formatter.formatCacheInfoShort(cache)); } - @Override - public boolean isEnabled(cgCache cache) { - return cache.getGeocode() != null; - } - }
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/apps/cache/navi/DownloadStaticMapsApp.java b/main/src/cgeo/geocaching/apps/cache/navi/DownloadStaticMapsApp.java index faf3c36..bc422d4 100644 --- a/main/src/cgeo/geocaching/apps/cache/navi/DownloadStaticMapsApp.java +++ b/main/src/cgeo/geocaching/apps/cache/navi/DownloadStaticMapsApp.java @@ -1,8 +1,8 @@ package cgeo.geocaching.apps.cache.navi; +import cgeo.geocaching.Geocache; import cgeo.geocaching.R; -import cgeo.geocaching.cgCache; -import cgeo.geocaching.cgWaypoint; +import cgeo.geocaching.Waypoint; import android.app.Activity; @@ -13,22 +13,22 @@ class DownloadStaticMapsApp extends AbstractStaticMapsApp { } @Override - public boolean isEnabled(cgCache cache) { + public boolean isEnabled(Geocache cache) { return !cache.hasStaticMap(); } @Override - public boolean isEnabled(cgWaypoint waypoint) { + public boolean isEnabled(Waypoint waypoint) { return !hasStaticMap(waypoint); } @Override - public void navigate(Activity activity, cgCache cache) { + public void navigate(Activity activity, Geocache cache) { invokeStaticMaps(activity, cache, null, true); } @Override - public void navigate(Activity activity, cgWaypoint waypoint) { + public void navigate(Activity activity, Waypoint waypoint) { invokeStaticMaps(activity, null, waypoint, true); } } diff --git a/main/src/cgeo/geocaching/apps/cache/navi/GoogleMapsDirectionApp.java b/main/src/cgeo/geocaching/apps/cache/navi/GoogleMapsDirectionApp.java new file mode 100644 index 0000000..db4fc1c --- /dev/null +++ b/main/src/cgeo/geocaching/apps/cache/navi/GoogleMapsDirectionApp.java @@ -0,0 +1,48 @@ +package cgeo.geocaching.apps.cache.navi; + +import cgeo.geocaching.IGeoData; +import cgeo.geocaching.R; +import cgeo.geocaching.cgeoapplication; +import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.maps.MapProviderFactory; +import cgeo.geocaching.utils.Log; + +import android.app.Activity; +import android.content.Intent; +import android.net.Uri; + +public class GoogleMapsDirectionApp extends AbstractPointNavigationApp { + + protected GoogleMapsDirectionApp() { + super(getString(R.string.cache_menu_maps_directions), null); + } + + @Override + public boolean isInstalled() { + return MapProviderFactory.isGoogleMapsInstalled(); + } + + @Override + public void navigate(Activity activity, Geopoint coords) { + try { + IGeoData geo = cgeoapplication.getInstance().currentGeo(); + final Geopoint coordsNow = geo == null ? null : geo.getCoords(); + + if (coordsNow != null) { + activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri + .parse("http://maps.google.com/maps?f=d&saddr=" + + coordsNow.getLatitude() + "," + coordsNow.getLongitude() + "&daddr=" + + coords.getLatitude() + "," + coords.getLongitude()))); + } else { + activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri + .parse("http://maps.google.com/maps?f=d&daddr=" + + coords.getLatitude() + "," + coords.getLongitude()))); + } + + } catch (Exception e) { + Log.i("GoogleMapsDirection: application not available."); + } + + } + +} diff --git a/main/src/cgeo/geocaching/apps/cache/navi/GoogleNavigationApp.java b/main/src/cgeo/geocaching/apps/cache/navi/GoogleNavigationApp.java index 7258e11..f1616ad 100644 --- a/main/src/cgeo/geocaching/apps/cache/navi/GoogleNavigationApp.java +++ b/main/src/cgeo/geocaching/apps/cache/navi/GoogleNavigationApp.java @@ -1,10 +1,6 @@ package cgeo.geocaching.apps.cache.navi; -import cgeo.geocaching.IGeoData; import cgeo.geocaching.R; -import cgeo.geocaching.Settings; -import cgeo.geocaching.cgeoapplication; -import cgeo.geocaching.activity.ActivityMixin; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.utils.Log; @@ -12,10 +8,13 @@ import android.app.Activity; import android.content.Intent; import android.net.Uri; -class GoogleNavigationApp extends AbstractPointNavigationApp { +abstract class GoogleNavigationApp extends AbstractPointNavigationApp { - GoogleNavigationApp() { - super(getString(R.string.cache_menu_tbt), null); + private final String mode; + + protected GoogleNavigationApp(final int nameResourceId, final String mode) { + super(getString(nameResourceId), null); + this.mode = mode; } @Override @@ -23,49 +22,27 @@ class GoogleNavigationApp extends AbstractPointNavigationApp { return true; } - private static boolean navigateToCoordinates(Activity activity, final Geopoint coords) { - IGeoData geo = cgeoapplication.getInstance().currentGeo(); - final Geopoint coordsNow = geo == null ? null : geo.getCoords(); - - // Google Navigation - if (Settings.isUseGoogleNavigation()) { - try { - activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri - .parse("google.navigation:ll=" + coords.getLatitude() + "," - + coords.getLongitude()))); - - return true; - } catch (Exception e) { - // nothing - } - } - - // Google Maps Directions + @Override + public void navigate(Activity activity, Geopoint coords) { try { - if (coordsNow != null) { - activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri - .parse("http://maps.google.com/maps?f=d&saddr=" - + coordsNow.getLatitude() + "," + coordsNow.getLongitude() + "&daddr=" - + coords.getLatitude() + "," + coords.getLongitude()))); - } else { - activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri - .parse("http://maps.google.com/maps?f=d&daddr=" - + coords.getLatitude() + "," + coords.getLongitude()))); - } + activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri + .parse("google.navigation:ll=" + coords.getLatitude() + "," + + coords.getLongitude() + mode))); - return true; } catch (Exception e) { - // nothing + Log.i("cgBase.runNavigation: No navigation application available."); } + } - Log.i("cgBase.runNavigation: No navigation application available."); - return false; + static class GoogleNavigationWalkingApp extends GoogleNavigationApp { + GoogleNavigationWalkingApp() { + super(R.string.cache_menu_navigation_walk, "&mode=w"); + } } - @Override - public void navigate(Activity activity, Geopoint coords) { - if (!navigateToCoordinates(activity, coords)) { - ActivityMixin.showToast(activity, getString(R.string.err_navigation_no)); + static class GoogleNavigationDrivingApp extends GoogleNavigationApp { + GoogleNavigationDrivingApp() { + super(R.string.cache_menu_navigation_drive, "&mode=d"); } } }
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/apps/cache/navi/InternalMap.java b/main/src/cgeo/geocaching/apps/cache/navi/InternalMap.java index 8185f40..cdf14f0 100644 --- a/main/src/cgeo/geocaching/apps/cache/navi/InternalMap.java +++ b/main/src/cgeo/geocaching/apps/cache/navi/InternalMap.java @@ -1,16 +1,15 @@ package cgeo.geocaching.apps.cache.navi; +import cgeo.geocaching.Geocache; import cgeo.geocaching.R; -import cgeo.geocaching.cgCache; -import cgeo.geocaching.cgWaypoint; -import cgeo.geocaching.apps.AbstractApp; +import cgeo.geocaching.Waypoint; import cgeo.geocaching.enumerations.WaypointType; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.maps.CGeoMap; import android.app.Activity; -class InternalMap extends AbstractApp implements CacheNavigationApp, WaypointNavigationApp, GeopointNavigationApp { +class InternalMap extends AbstractPointNavigationApp { InternalMap() { super(getString(R.string.cache_menu_map), null); @@ -27,22 +26,13 @@ class InternalMap extends AbstractApp implements CacheNavigationApp, WaypointNav } @Override - public void navigate(Activity activity, cgWaypoint waypoint) { + public void navigate(Activity activity, Waypoint waypoint) { CGeoMap.startActivityCoords(activity, waypoint.getCoords(), waypoint.getWaypointType(), waypoint.getName()); } @Override - public boolean isEnabled(cgWaypoint waypoint) { - return waypoint.getCoords() != null; - } - - @Override - public void navigate(Activity activity, cgCache cache) { + public void navigate(Activity activity, Geocache cache) { CGeoMap.startActivityGeoCode(activity, cache.getGeocode()); } - @Override - public boolean isEnabled(cgCache cache) { - return cache.getCoords() != null; - } } diff --git a/main/src/cgeo/geocaching/apps/cache/navi/LocusApp.java b/main/src/cgeo/geocaching/apps/cache/navi/LocusApp.java index a20f2ce..8b64ac8 100644 --- a/main/src/cgeo/geocaching/apps/cache/navi/LocusApp.java +++ b/main/src/cgeo/geocaching/apps/cache/navi/LocusApp.java @@ -1,7 +1,7 @@ package cgeo.geocaching.apps.cache.navi; -import cgeo.geocaching.cgCache; -import cgeo.geocaching.cgWaypoint; +import cgeo.geocaching.Geocache; +import cgeo.geocaching.Waypoint; import cgeo.geocaching.apps.AbstractLocusApp; import android.app.Activity; @@ -11,22 +11,27 @@ import java.util.Collections; class LocusApp extends AbstractLocusApp implements CacheNavigationApp, WaypointNavigationApp { @Override - public boolean isEnabled(cgWaypoint waypoint) { + public boolean isEnabled(Waypoint waypoint) { return waypoint.getCoords() != null; } + @Override + public boolean isEnabled(Geocache cache) { + return cache.getCoords() != null; + } + /** * Show a single cache with waypoints or a single waypoint in Locus. * This method constructs a list of cache and waypoints only. * */ @Override - public void navigate(Activity activity, cgWaypoint waypoint) { + public void navigate(Activity activity, Waypoint waypoint) { showInLocus(Collections.singletonList(waypoint), true, false, activity); } @Override - public void navigate(Activity activity, cgCache cache) { + public void navigate(Activity activity, Geocache cache) { showInLocus(Collections.singletonList(cache), true, false, activity); } } diff --git a/main/src/cgeo/geocaching/apps/cache/navi/NavigationApp.java b/main/src/cgeo/geocaching/apps/cache/navi/NavigationApp.java deleted file mode 100644 index 52d16cf..0000000 --- a/main/src/cgeo/geocaching/apps/cache/navi/NavigationApp.java +++ /dev/null @@ -1,18 +0,0 @@ -package cgeo.geocaching.apps.cache.navi; - -import cgeo.geocaching.cgCache; -import cgeo.geocaching.cgWaypoint; -import cgeo.geocaching.apps.App; -import cgeo.geocaching.geopoint.Geopoint; - -import android.app.Activity; - -public interface NavigationApp extends App { - public boolean invoke(final Activity activity, - final cgCache cache, final cgWaypoint waypoint, - final Geopoint coords); - - boolean isEnabled(final cgWaypoint waypoint); - - boolean isEnabled(final Geopoint geopoint); -} diff --git a/main/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java b/main/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java index 57a71bb..5545936 100644 --- a/main/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java +++ b/main/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java @@ -1,13 +1,18 @@ package cgeo.geocaching.apps.cache.navi; +import cgeo.geocaching.Geocache; import cgeo.geocaching.R; import cgeo.geocaching.Settings; -import cgeo.geocaching.cgCache; -import cgeo.geocaching.cgWaypoint; +import cgeo.geocaching.Waypoint; import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.activity.ActivityMixin; import cgeo.geocaching.apps.AbstractAppFactory; 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.GoogleNavigationDrivingApp; +import cgeo.geocaching.apps.cache.navi.GoogleNavigationApp.GoogleNavigationWalkingApp; import cgeo.geocaching.geopoint.Geopoint; import android.app.Activity; @@ -40,7 +45,7 @@ public final class NavigationAppFactory extends AbstractAppFactory { /** Google Maps */ GOOGLE_MAPS(new GoogleMapsApp(), 6), /** Google Navigation */ - GOOGLE_NAVIGATION(new GoogleNavigationApp(), 7), + GOOGLE_NAVIGATION(new GoogleNavigationDrivingApp(), 7), /** Google Streetview */ GOOGLE_STREETVIEW(new StreetviewApp(), 8), /** The external OruxMaps app */ @@ -48,7 +53,19 @@ public final class NavigationAppFactory extends AbstractAppFactory { /** The external navigon app */ NAVIGON(new NavigonApp(), 10), /** The external Sygic app */ - SYGIC(new SygicNavigationApp(), 11); + SYGIC(new SygicNavigationApp(), 11), + /** + * Google Navigation in walking mode + */ + GOOGLE_NAVIGATION_WALK(new GoogleNavigationWalkingApp(), 12), + /** + * Google Maps Directions + */ + GOOGLE_MAPS_DIRECTIONS(new GoogleMapsDirectionApp(), 13), + + CACHE_BEACON(new CacheBeaconApp(), 14), + GCC(new GccApp(), 15), + WHERE_YOU_GO(new WhereYouGoApp(), 16); NavigationAppsEnum(App app, int id) { this.app = app; @@ -79,7 +96,7 @@ public final class NavigationAppFactory extends AbstractAppFactory { * Default way to handle selection of navigation tool.<br /> * A dialog is created for tool selection and the selected tool is started afterwards. * <p /> - * Delegates to {@link #showNavigationMenu(Activity, cgCache, cgWaypoint, Geopoint, boolean, boolean)} with + * Delegates to {@link #showNavigationMenu(Activity, cgeo.geocaching.Geocache, cgeo.geocaching.Waypoint, Geopoint, boolean, boolean)} with * <code>showInternalMap = true</code> and <code>showDefaultNavigation = false</code> * * @param activity @@ -88,7 +105,7 @@ public final class NavigationAppFactory extends AbstractAppFactory { * @param destination */ public static void showNavigationMenu(final Activity activity, - final cgCache cache, final cgWaypoint waypoint, final Geopoint destination) { + final Geocache cache, final Waypoint waypoint, final Geopoint destination) { showNavigationMenu(activity, cache, waypoint, destination, true, false); } @@ -108,14 +125,13 @@ public final class NavigationAppFactory extends AbstractAppFactory { * @param showDefaultNavigation * should be <code>false</code> by default * - * @see #showNavigationMenu(Activity, cgCache, cgWaypoint, Geopoint) + * @see #showNavigationMenu(Activity, cgeo.geocaching.Geocache, cgeo.geocaching.Waypoint, Geopoint) */ public static void showNavigationMenu(final Activity activity, - final cgCache cache, final cgWaypoint waypoint, final Geopoint destination, + final Geocache cache, final Waypoint waypoint, final Geopoint destination, final boolean showInternalMap, final boolean showDefaultNavigation) { final AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setTitle(R.string.cache_menu_navigate); - builder.setIcon(R.drawable.ic_menu_mapmode); final List<NavigationAppsEnum> items = new ArrayList<NavigationAppFactory.NavigationAppsEnum>(); final int defaultNavigationTool = Settings.getDefaultNavigationTool(); for (NavigationAppsEnum navApp : getInstalledNavigationApps()) { @@ -146,17 +162,15 @@ public final class NavigationAppFactory extends AbstractAppFactory { @Override public void onClick(DialogInterface dialog, int item) { NavigationAppsEnum selectedItem = adapter.getItem(item); + App app = selectedItem.app; if (cache != null) { - CacheNavigationApp cacheApp = (CacheNavigationApp) selectedItem.app; - cacheApp.navigate(activity, cache); + navigateCache(activity, cache, app); } else if (waypoint != null) { - WaypointNavigationApp waypointApp = (WaypointNavigationApp) selectedItem.app; - waypointApp.navigate(activity, waypoint); + navigateWaypoint(activity, waypoint, app); } else { - GeopointNavigationApp geopointApp = (GeopointNavigationApp) selectedItem.app; - geopointApp.navigate(activity, destination); + navigateGeopoint(activity, destination, app); } } }); @@ -202,15 +216,15 @@ public final class NavigationAppFactory extends AbstractAppFactory { /** * Adds the installed navigation tools to the given menu. - * Use {@link #onMenuItemSelected(MenuItem, Activity, cgCache)} on + * Use {@link #onMenuItemSelected(MenuItem, Activity, cgeo.geocaching.Geocache)} on * selection event to start the selected navigation tool. * - * <b>Only use this way if {@link #showNavigationMenu(Activity, cgCache, cgWaypoint, Geopoint, boolean, boolean)} is + * <b>Only use this way if {@link #showNavigationMenu(Activity, cgeo.geocaching.Geocache, cgeo.geocaching.Waypoint, Geopoint, boolean, boolean)} is * not suitable for the given usecase.</b> * * @param menu */ - public static void addMenuItems(final Menu menu, final cgCache cache) { + public static void addMenuItems(final Menu menu, final Geocache cache) { for (NavigationAppsEnum navApp : getInstalledNavigationApps()) { if (navApp.app instanceof CacheNavigationApp) { CacheNavigationApp cacheApp = (CacheNavigationApp) navApp.app; @@ -221,7 +235,7 @@ public final class NavigationAppFactory extends AbstractAppFactory { } } - public static void addMenuItems(final Menu menu, final cgWaypoint waypoint) { + public static void addMenuItems(final Menu menu, final Waypoint waypoint) { for (NavigationAppsEnum navApp : getInstalledNavigationApps()) { if (navApp.app instanceof WaypointNavigationApp) { WaypointNavigationApp waypointApp = (WaypointNavigationApp) navApp.app; @@ -233,33 +247,33 @@ public final class NavigationAppFactory extends AbstractAppFactory { } /** - * Handles menu selections for menu entries created with {@link #addMenuItems(Menu, cgCache)}. + * Handles menu selections for menu entries created with {@link #addMenuItems(Menu, cgeo.geocaching.Geocache)}. * * @param item * @param activity * @param cache * @return */ - public static boolean onMenuItemSelected(final MenuItem item, Activity activity, cgCache cache) { + public static boolean onMenuItemSelected(final MenuItem item, Activity activity, Geocache cache) { final App menuItem = getAppFromMenuItem(item); navigateCache(activity, cache, menuItem); return menuItem != null; } - private static void navigateCache(Activity activity, cgCache cache, App app) { + private static void navigateCache(Activity activity, Geocache cache, App app) { if (app instanceof CacheNavigationApp) { CacheNavigationApp cacheApp = (CacheNavigationApp) app; cacheApp.navigate(activity, cache); } } - public static boolean onMenuItemSelected(final MenuItem item, Activity activity, cgWaypoint waypoint) { + public static boolean onMenuItemSelected(final MenuItem item, Activity activity, Waypoint waypoint) { final App menuItem = getAppFromMenuItem(item); navigateWaypoint(activity, waypoint, menuItem); return menuItem != null; } - private static void navigateWaypoint(Activity activity, cgWaypoint waypoint, App app) { + private static void navigateWaypoint(Activity activity, Waypoint waypoint, App app) { if (app instanceof WaypointNavigationApp) { WaypointNavigationApp waypointApp = (WaypointNavigationApp) app; waypointApp.navigate(activity, waypoint); @@ -291,7 +305,7 @@ public final class NavigationAppFactory extends AbstractAppFactory { * @param activity * @param cache */ - public static void startDefaultNavigationApplication(int defaultNavigation, Activity activity, cgCache cache) { + public static void startDefaultNavigationApplication(int defaultNavigation, Activity activity, Geocache cache) { if (cache == null || cache.getCoords() == null) { ActivityMixin.showToast(activity, cgeoapplication.getInstance().getString(R.string.err_location_unknown)); return; @@ -313,7 +327,7 @@ public final class NavigationAppFactory extends AbstractAppFactory { * @param activity * @param waypoint */ - public static void startDefaultNavigationApplication(int defaultNavigation, Activity activity, cgWaypoint waypoint) { + public static void startDefaultNavigationApplication(int defaultNavigation, Activity activity, Waypoint waypoint) { if (waypoint == null || waypoint.getCoords() == null) { ActivityMixin.showToast(activity, cgeoapplication.getInstance().getString(R.string.err_location_unknown)); return; diff --git a/main/src/cgeo/geocaching/apps/cache/navi/RMapsApp.java b/main/src/cgeo/geocaching/apps/cache/navi/RMapsApp.java index 5170212..b203aeb 100644 --- a/main/src/cgeo/geocaching/apps/cache/navi/RMapsApp.java +++ b/main/src/cgeo/geocaching/apps/cache/navi/RMapsApp.java @@ -1,8 +1,8 @@ package cgeo.geocaching.apps.cache.navi; +import cgeo.geocaching.Geocache; import cgeo.geocaching.R; -import cgeo.geocaching.cgCache; -import cgeo.geocaching.cgWaypoint; +import cgeo.geocaching.Waypoint; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.GeopointFormatter.Format; @@ -20,7 +20,7 @@ class RMapsApp extends AbstractPointNavigationApp { } @Override - public void navigate(Activity activity, cgWaypoint waypoint) { + public void navigate(Activity activity, Waypoint waypoint) { navigate(activity, waypoint.getCoords(), waypoint.getLookup(), waypoint.getName()); } @@ -33,7 +33,7 @@ class RMapsApp extends AbstractPointNavigationApp { } @Override - public void navigate(Activity activity, cgCache cache) { + public void navigate(Activity activity, Geocache cache) { navigate(activity, cache.getCoords(), cache.getGeocode(), cache.getName()); } diff --git a/main/src/cgeo/geocaching/apps/cache/navi/StaticMapApp.java b/main/src/cgeo/geocaching/apps/cache/navi/StaticMapApp.java index eb01f23..1dd57a3 100644 --- a/main/src/cgeo/geocaching/apps/cache/navi/StaticMapApp.java +++ b/main/src/cgeo/geocaching/apps/cache/navi/StaticMapApp.java @@ -1,8 +1,8 @@ package cgeo.geocaching.apps.cache.navi; import cgeo.geocaching.R; -import cgeo.geocaching.cgCache; -import cgeo.geocaching.cgWaypoint; +import cgeo.geocaching.Waypoint; +import cgeo.geocaching.Geocache; import android.app.Activity; @@ -13,22 +13,22 @@ class StaticMapApp extends AbstractStaticMapsApp { } @Override - public boolean isEnabled(cgCache cache) { + public boolean isEnabled(Geocache cache) { return cache.hasStaticMap(); } @Override - public boolean isEnabled(cgWaypoint waypoint) { + public boolean isEnabled(Waypoint waypoint) { return hasStaticMap(waypoint); } @Override - public void navigate(Activity activity, cgCache cache) { + public void navigate(Activity activity, Geocache cache) { invokeStaticMaps(activity, cache, null, false); } @Override - public void navigate(Activity activity, cgWaypoint waypoint) { + public void navigate(Activity activity, Waypoint waypoint) { invokeStaticMaps(activity, null, waypoint, false); } } diff --git a/main/src/cgeo/geocaching/apps/cache/navi/WaypointNavigationApp.java b/main/src/cgeo/geocaching/apps/cache/navi/WaypointNavigationApp.java index 7d3a706..c26ec3e 100644 --- a/main/src/cgeo/geocaching/apps/cache/navi/WaypointNavigationApp.java +++ b/main/src/cgeo/geocaching/apps/cache/navi/WaypointNavigationApp.java @@ -1,6 +1,6 @@ package cgeo.geocaching.apps.cache.navi; -import cgeo.geocaching.cgWaypoint; +import cgeo.geocaching.Waypoint; import android.app.Activity; @@ -9,7 +9,7 @@ import android.app.Activity; * */ public interface WaypointNavigationApp { - void navigate(final Activity activity, final cgWaypoint waypoint); + void navigate(final Activity activity, final Waypoint waypoint); - boolean isEnabled(final cgWaypoint waypoint); + boolean isEnabled(final Waypoint waypoint); } diff --git a/main/src/cgeo/geocaching/apps/cachelist/CacheListApp.java b/main/src/cgeo/geocaching/apps/cachelist/CacheListApp.java index 519b394..ac5809e 100644 --- a/main/src/cgeo/geocaching/apps/cachelist/CacheListApp.java +++ b/main/src/cgeo/geocaching/apps/cachelist/CacheListApp.java @@ -1,7 +1,7 @@ package cgeo.geocaching.apps.cachelist; import cgeo.geocaching.SearchResult; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; import cgeo.geocaching.apps.App; import android.app.Activity; @@ -10,7 +10,7 @@ import java.util.List; interface CacheListApp extends App { - boolean invoke(final List<cgCache> caches, + boolean invoke(final List<Geocache> caches, final Activity activity, final SearchResult search); } diff --git a/main/src/cgeo/geocaching/apps/cachelist/CacheListAppFactory.java b/main/src/cgeo/geocaching/apps/cachelist/CacheListAppFactory.java index fbfb9fc..b747eee 100644 --- a/main/src/cgeo/geocaching/apps/cachelist/CacheListAppFactory.java +++ b/main/src/cgeo/geocaching/apps/cachelist/CacheListAppFactory.java @@ -2,7 +2,7 @@ package cgeo.geocaching.apps.cachelist; import cgeo.geocaching.R; import cgeo.geocaching.SearchResult; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; import cgeo.geocaching.activity.IAbstractActivity; import cgeo.geocaching.apps.AbstractAppFactory; import cgeo.geocaching.utils.Log; @@ -55,7 +55,7 @@ public final class CacheListAppFactory extends AbstractAppFactory { } } - public static boolean onMenuItemSelected(final MenuItem item, final List<cgCache> caches, final IAbstractActivity activity, + public static boolean onMenuItemSelected(final MenuItem item, final List<Geocache> caches, final IAbstractActivity activity, final SearchResult search) { final CacheListApp app = (CacheListApp) getAppFromMenuItem(item, LazyHolder.apps); if (app != null) { @@ -64,7 +64,7 @@ public final class CacheListAppFactory extends AbstractAppFactory { activity.invalidateOptionsMenuCompatible(); return result; } catch (Exception e) { - Log.e("CacheListAppFactory.onMenuItemSelected: " + e.toString()); + Log.e("CacheListAppFactory.onMenuItemSelected", e); } } return false; diff --git a/main/src/cgeo/geocaching/apps/cachelist/InternalCacheListMap.java b/main/src/cgeo/geocaching/apps/cachelist/InternalCacheListMap.java index d6e1fed..38fb499 100644 --- a/main/src/cgeo/geocaching/apps/cachelist/InternalCacheListMap.java +++ b/main/src/cgeo/geocaching/apps/cachelist/InternalCacheListMap.java @@ -2,7 +2,7 @@ package cgeo.geocaching.apps.cachelist; import cgeo.geocaching.R; import cgeo.geocaching.SearchResult; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; import cgeo.geocaching.apps.AbstractApp; import cgeo.geocaching.maps.CGeoMap; @@ -22,7 +22,7 @@ class InternalCacheListMap extends AbstractApp implements CacheListApp { } @Override - public boolean invoke(List<cgCache> caches, Activity activity, final SearchResult search) { + public boolean invoke(List<Geocache> caches, Activity activity, final SearchResult search) { CGeoMap.startActivitySearch(activity, search, null); return true; } diff --git a/main/src/cgeo/geocaching/apps/cachelist/LocusCacheListApp.java b/main/src/cgeo/geocaching/apps/cachelist/LocusCacheListApp.java index 65760c7..cd0289a 100644 --- a/main/src/cgeo/geocaching/apps/cachelist/LocusCacheListApp.java +++ b/main/src/cgeo/geocaching/apps/cachelist/LocusCacheListApp.java @@ -2,7 +2,7 @@ package cgeo.geocaching.apps.cachelist; import cgeo.geocaching.R; import cgeo.geocaching.SearchResult; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; import cgeo.geocaching.apps.AbstractLocusApp; import org.apache.commons.collections.CollectionUtils; @@ -27,7 +27,7 @@ class LocusCacheListApp extends AbstractLocusApp implements CacheListApp { * @see AbstractLocusApp#showInLocus */ @Override - public boolean invoke(List<cgCache> cacheList, Activity activity, final SearchResult search) { + public boolean invoke(List<Geocache> cacheList, Activity activity, final SearchResult search) { if (CollectionUtils.isEmpty(cacheList)) { return false; } diff --git a/main/src/cgeo/geocaching/cgData.java b/main/src/cgeo/geocaching/cgData.java index 94eb3fd..770a28f 100644 --- a/main/src/cgeo/geocaching/cgData.java +++ b/main/src/cgeo/geocaching/cgData.java @@ -23,7 +23,6 @@ import android.content.ContextWrapper; import android.content.res.Resources; import android.database.Cursor; import android.database.DatabaseUtils; -import android.database.DatabaseUtils.InsertHelper; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.database.sqlite.SQLiteDoneException; @@ -48,6 +47,10 @@ import java.util.regex.Pattern; public class cgData { + private cgData() { + // utility class + } + public enum StorageLocation { HEAP, CACHE, @@ -56,14 +59,21 @@ public class cgData { /** The list of fields needed for mapping. */ private static final String[] CACHE_COLUMNS = new String[] { - "_id", "updated", "reason", "detailed", "detailedupdate", "visiteddate", "geocode", "cacheid", "guid", "type", "name", "own", "owner", "owner_real", "hidden", "hint", "size", - "difficulty", "distance", "direction", "terrain", "latlon", "location", "latitude", "longitude", "elevation", "shortdesc", - "favourite_cnt", "rating", "votes", "myvote", "disabled", "archived", "members", "found", "favourite", "inventorycoins", "inventorytags", - "inventoryunknown", "onWatchlist", "personal_note", "reliable_latlon", "coordsChanged", "finalDefined" - // reason is replaced by listId in cgCache + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 + "updated", "reason", "detailed", "detailedupdate", "visiteddate", "geocode", "cacheid", "guid", "type", "name", "owner", "owner_real", "hidden", "hint", "size", + // 15 16 17 18 19 20 21 22 23 + "difficulty", "direction", "distance", "terrain", "latlon", "location", "elevation", "personal_note", "shortdesc", + // 24 25 26 27 28 29 30 31 32 + "favourite_cnt", "rating", "votes", "myvote", "disabled", "archived", "members", "found", "favourite", + // 33 34 35 36 37 38 39 40 41 42 + "inventoryunknown", "onWatchlist", "reliable_latlon", "coordsChanged", "latitude", "longitude", "finalDefined", "_id", "inventorycoins", "inventorytags" + // reason is replaced by listId in Geocache }; + + //TODO: remove "latlon" field from cache table + /** The list of fields needed for mapping. */ - private static final String[] WAYPOINT_COLUMNS = new String[] { "_id", "geocode", "updated", "type", "prefix", "lookup", "name", "latlon", "latitude", "longitude", "note", "own" }; + private static final String[] WAYPOINT_COLUMNS = new String[] { "_id", "geocode", "updated", "type", "prefix", "lookup", "name", "latlon", "latitude", "longitude", "note", "own", "visited" }; /** Number of days (as ms) after temporarily saved caches are deleted */ private final static long DAYS_AFTER_CACHE_IS_DELETED = 3 * 24 * 60 * 60 * 1000; @@ -72,9 +82,9 @@ public class cgData { * holds the column indexes of the cache table to avoid lookups */ private static int[] cacheColumnIndex; - private CacheCache cacheCache = new CacheCache(); - private SQLiteDatabase database = null; - private static final int dbVersion = 64; + private static CacheCache cacheCache = new CacheCache(); + private static SQLiteDatabase database = null; + private static final int dbVersion = 66; public static final int customListIdOffset = 10; private static final String dbName = "data"; private static final String dbTableCaches = "cg_caches"; @@ -101,7 +111,6 @@ public class cgData { + "guid text, " + "type text, " + "name text, " - + "own integer not null default 0, " + "owner text, " + "owner_real text, " + "hidden long, " @@ -151,9 +160,6 @@ public class cgData { + "updated long not null, " // date of save + "attribute text " + "); "; - private final static int ATTRIBUTES_GEOCODE = 2; - private final static int ATTRIBUTES_UPDATED = 3; - private final static int ATTRIBUTES_ATTRIBUTE = 4; private static final String dbCreateWaypoints = "" + "create table " + dbTableWaypoints + " (" @@ -168,7 +174,8 @@ public class cgData { + "latitude double, " + "longitude double, " + "note text, " - + "own integer default 0" + + "own integer default 0, " + + "visited integer default 0" + "); "; private static final String dbCreateSpoilers = "" + "create table " + dbTableSpoilers + " (" @@ -191,14 +198,6 @@ public class cgData { + "found integer not null default 0, " + "friend integer " + "); "; - private final static int LOGS_GEOCODE = 2; - private final static int LOGS_UPDATED = 3; - private final static int LOGS_TYPE = 4; - private final static int LOGS_AUTHOR = 5; - private final static int LOGS_LOG = 6; - private final static int LOGS_DATE = 7; - private final static int LOGS_FOUND = 8; - private final static int LOGS_FRIEND = 9; private static final String dbCreateLogCount = "" + "create table " + dbTableLogCount + " (" @@ -246,10 +245,10 @@ public class cgData { + "longitude double " + "); "; - private HashMap<String, SQLiteStatement> statements = new HashMap<String, SQLiteStatement>(); private static boolean newlyCreatedDatabase = false; + private static boolean databaseCleaned = false; - public synchronized void init() { + public synchronized static void init() { if (database != null) { return; } @@ -262,35 +261,28 @@ public class cgData { } } - public void closeDb() { + public static void closeDb() { if (database == null) { return; } cacheCache.removeAllFromCache(); - clearPreparedStatements(); + PreparedStatements.clearPreparedStatements(); database.close(); database = null; } - private void clearPreparedStatements() { - for (SQLiteStatement statement : statements.values()) { - statement.close(); - } - statements.clear(); - } - - private static File backupFile() { + private static File getBackupFile() { return new File(LocalStorage.getStorage(), "cgeo.sqlite"); } - public String backupDatabase() { + public static String backupDatabase() { if (!LocalStorage.isExternalStorageAvailable()) { Log.w("Database wasn't backed up: no external memory"); return null; } - final File target = backupFile(); + final File target = getBackupFile(); closeDb(); final boolean backupDone = LocalStorage.copy(databasePath(), target); init(); @@ -304,7 +296,7 @@ public class cgData { return target.getPath(); } - public boolean moveDatabase() { + public static boolean moveDatabase() { if (!LocalStorage.isExternalStorageAvailable()) { Log.w("Database was not moved: external memory not available"); return false; @@ -340,18 +332,18 @@ public class cgData { return databasePath(Settings.isDbOnSDCard()); } - public static File isRestoreFile() { - final File fileSourceFile = backupFile(); + public static File getRestoreFile() { + final File fileSourceFile = getBackupFile(); return fileSourceFile.exists() ? fileSourceFile : null; } - public boolean restoreDatabase() { + public static boolean restoreDatabase() { if (!LocalStorage.isExternalStorageAvailable()) { Log.w("Database wasn't restored: no external memory"); return false; } - final File sourceFile = backupFile(); + final File sourceFile = getBackupFile(); closeDb(); final boolean restoreDone = LocalStorage.copy(sourceFile, databasePath()); init(); @@ -475,7 +467,7 @@ public class cgData { try { db.execSQL(dbCreateLogImages); } catch (Exception e) { - Log.e("Failed to upgrade to ver. 54: " + e.toString()); + Log.e("Failed to upgrade to ver. 54", e); } } @@ -484,7 +476,7 @@ public class cgData { try { db.execSQL("alter table " + dbTableCaches + " add column personal_note text"); } catch (Exception e) { - Log.e("Failed to upgrade to ver. 55: " + e.toString()); + Log.e("Failed to upgrade to ver. 55", e); } } @@ -496,7 +488,7 @@ public class cgData { "lower(attribute) where attribute like \"%_yes\" " + "or attribute like \"%_no\""); } catch (Exception e) { - Log.e("Failed to upgrade to ver. 56: " + e.toString()); + Log.e("Failed to upgrade to ver. 56", e); } } @@ -511,7 +503,7 @@ public class cgData { db.execSQL("drop index in_f"); createIndices(db); } catch (Exception e) { - Log.e("Failed to upgrade to ver. 57: " + e.toString()); + Log.e("Failed to upgrade to ver. 57", e); } } @@ -629,7 +621,7 @@ public class cgData { db.execSQL("alter table " + dbTableLogs + " add column friend integer"); db.execSQL("alter table " + dbTableCaches + " add column coordsChanged integer default 0"); } catch (Exception e) { - Log.e("Failed to upgrade to ver. 61: " + e.toString()); + Log.e("Failed to upgrade to ver. 61", e); } } @@ -640,7 +632,7 @@ public class cgData { db.execSQL("alter table " + dbTableWaypoints + " add column own integer default 0"); db.execSQL("update " + dbTableWaypoints + " set own = 1 where type = 'own'"); } catch (Exception e) { - Log.e("Failed to upgrade to ver. 62: " + e.toString()); + Log.e("Failed to upgrade to ver. 62", e); } } @@ -648,7 +640,7 @@ public class cgData { try { removeDoubleUnderscoreMapFiles(); } catch (Exception e) { - Log.e("Failed to upgrade to ver. 63: " + e.toString()); + Log.e("Failed to upgrade to ver. 63", e); } } @@ -663,6 +655,24 @@ public class cgData { Log.e("Failed to upgrade to ver. 64", e); } } + + if (oldVersion < 65) { + try { + // Set all waypoints where name is Original coordinates to type ORIGINAL + db.execSQL("update " + dbTableWaypoints + " set type='original', own=0 where name='Original Coordinates'"); + } catch (Exception e) { + Log.e("Failed to upgrade to ver. 65:", e); + } + } + // Introduces visited feature on waypoints + if (oldVersion < 66) { + try { + db.execSQL("alter table " + dbTableWaypoints + " add column visited integer default 0"); + } catch (Exception e) { + Log.e("Failed to upgrade to ver. 66", e); + + } + } } db.setTransactionSuccessful(); @@ -692,7 +702,7 @@ public class cgData { // to NPE traces. final int staleHistorySearches = db.delete(dbTableSearchDestionationHistory, "date is null", null); if (staleHistorySearches > 0) { - Log.w(String.format("cgData.dbHelper.onOpen: removed %d bad search history entries", staleHistorySearches)); + Log.w(String.format(Locale.getDefault(), "cgData.dbHelper.onOpen: removed %d bad search history entries", staleHistorySearches)); } } @@ -705,13 +715,15 @@ public class cgData { final FilenameFilter filter = new FilenameFilter() { @Override public boolean accept(File dir, String filename) { - return (filename.startsWith("map_") && filename.contains("__")); + return filename.startsWith("map_") && filename.contains("__"); } }; - for (File dir : geocodeDirs) { - File[] wrongFiles = dir.listFiles(filter); - for (File wrongFile : wrongFiles) { - wrongFile.delete(); + for (final File dir : geocodeDirs) { + final File[] wrongFiles = dir.listFiles(filter); + if (wrongFiles != null) { + for (final File wrongFile : wrongFiles) { + wrongFile.delete(); + } } } } @@ -776,15 +788,12 @@ public class cgData { db.execSQL("drop table if exists " + dbTableTrackables); } - public String[] allDetailedThere() { + public static String[] getRecentGeocodesForSearch() { init(); - Cursor cursor = null; - List<String> list = new ArrayList<String>(); - try { long timestamp = System.currentTimeMillis() - DAYS_AFTER_CACHE_IS_DELETED; - cursor = database.query( + final Cursor cursor = database.query( dbTableCaches, new String[]{"geocode"}, "(detailed = 1 and detailedupdate > ?) or reason > 0", @@ -794,43 +803,23 @@ public class cgData { "detailedupdate desc", "100"); - if (cursor != null) { - int index; - - if (cursor.getCount() > 0) { - cursor.moveToFirst(); - index = cursor.getColumnIndex("geocode"); - - do { - list.add(cursor.getString(index)); - } while (cursor.moveToNext()); - } else { - cursor.close(); - return null; - } - } - } catch (Exception e) { - Log.e("cgData.allDetailedThere: " + e.toString()); - } - - if (cursor != null) { - cursor.close(); + return getFirstColumn(cursor); + } catch (final Exception e) { + Log.e("cgData.allDetailedThere", e); + return new String[0]; } - - return list.toArray(new String[list.size()]); } - public boolean isThere(String geocode, String guid, boolean detailed, boolean checkTime) { + public static boolean isThere(String geocode, String guid, boolean detailed, boolean checkTime) { init(); - Cursor cursor = null; - - int cnt = 0; long dataUpdated = 0; long dataDetailedUpdate = 0; int dataDetailed = 0; try { + Cursor cursor; + if (StringUtils.isNotBlank(geocode)) { cursor = database.query( dbTableCaches, @@ -855,55 +844,38 @@ public class cgData { return false; } - if (cursor != null) { - int index; - cnt = cursor.getCount(); - - if (cnt > 0) { - cursor.moveToFirst(); - - index = cursor.getColumnIndex("updated"); - dataUpdated = cursor.getLong(index); - index = cursor.getColumnIndex("detailedupdate"); - dataDetailedUpdate = cursor.getLong(index); - index = cursor.getColumnIndex("detailed"); - dataDetailed = cursor.getInt(index); - } + if (cursor.moveToFirst()) { + dataDetailed = cursor.getInt(0); + dataDetailedUpdate = cursor.getLong(1); + dataUpdated = cursor.getLong(2); } - } catch (Exception e) { - Log.e("cgData.isThere: " + e.toString()); - } - if (cursor != null) { cursor.close(); + } catch (final Exception e) { + Log.e("cgData.isThere", e); } - if (cnt > 0) { - if (detailed && dataDetailed == 0) { - // we want details, but these are not stored - return false; - } - - if (checkTime && detailed && dataDetailedUpdate < (System.currentTimeMillis() - DAYS_AFTER_CACHE_IS_DELETED)) { - // we want to check time for detailed cache, but data are older than 3 hours - return false; - } + if (detailed && dataDetailed == 0) { + // we want details, but these are not stored + return false; + } - if (checkTime && !detailed && dataUpdated < (System.currentTimeMillis() - DAYS_AFTER_CACHE_IS_DELETED)) { - // we want to check time for short cache, but data are older than 3 hours - return false; - } + if (checkTime && detailed && dataDetailedUpdate < (System.currentTimeMillis() - DAYS_AFTER_CACHE_IS_DELETED)) { + // we want to check time for detailed cache, but data are older than 3 hours + return false; + } - // we have some cache - return true; + if (checkTime && !detailed && dataUpdated < (System.currentTimeMillis() - DAYS_AFTER_CACHE_IS_DELETED)) { + // we want to check time for short cache, but data are older than 3 hours + return false; } - // we have no such cache stored in cache - return false; + // we have some cache + return true; } /** is cache stored in one of the lists (not only temporary) */ - public boolean isOffline(String geocode, String guid) { + public static boolean isOffline(String geocode, String guid) { if (StringUtils.isBlank(geocode) && StringUtils.isBlank(guid)) { return false; } @@ -913,11 +885,11 @@ public class cgData { final SQLiteStatement listId; final String value; if (StringUtils.isNotBlank(geocode)) { - listId = getStatementListIdFromGeocode(); + listId = PreparedStatements.getListIdOfGeocode(); value = geocode; } else { - listId = getStatementListIdFromGuid(); + listId = PreparedStatements.getListIdOfGuid(); value = guid; } synchronized (listId) { @@ -933,14 +905,14 @@ public class cgData { return false; } - public String getGeocodeForGuid(String guid) { + public static String getGeocodeForGuid(String guid) { if (StringUtils.isBlank(guid)) { return null; } init(); try { - final SQLiteStatement description = getStatementGeocode(); + final SQLiteStatement description = PreparedStatements.getGeocodeOfGuid(); synchronized (description) { description.bindString(1, guid); return description.simpleQueryForString(); @@ -954,14 +926,14 @@ public class cgData { return null; } - public String getCacheidForGeocode(String geocode) { + public static String getCacheidForGeocode(String geocode) { if (StringUtils.isBlank(geocode)) { return null; } init(); try { - final SQLiteStatement description = getStatementCacheId(); + final SQLiteStatement description = PreparedStatements.getCacheIdOfGeocode(); synchronized (description) { description.bindString(1, geocode); return description.simpleQueryForString(); @@ -984,7 +956,7 @@ public class cgData { * * @return true = cache saved successfully to the CacheCache/DB */ - public boolean saveCache(cgCache cache, EnumSet<LoadFlags.SaveFlag> saveFlags) { + public static boolean saveCache(Geocache cache, EnumSet<LoadFlags.SaveFlag> saveFlags) { if (cache == null) { throw new IllegalArgumentException("cache must not be null"); } @@ -1028,7 +1000,6 @@ public class cgData { values.put("guid", cache.getGuid()); values.put("type", cache.getType().id); values.put("name", cache.getName()); - values.put("own", cache.isOwn() ? 1 : 0); values.put("owner", cache.getOwnerDisplayName()); values.put("owner_real", cache.getOwnerUserId()); if (cache.getHiddenDate() == null) { @@ -1040,14 +1011,13 @@ public class cgData { values.put("size", cache.getSize() == null ? "" : cache.getSize().id); values.put("difficulty", cache.getDifficulty()); values.put("terrain", cache.getTerrain()); - values.put("latlon", cache.getLatlon()); values.put("location", cache.getLocation()); values.put("distance", cache.getDistance()); values.put("direction", cache.getDirection()); putCoords(values, cache.getCoords()); values.put("reliable_latlon", cache.isReliableLatLon() ? 1 : 0); values.put("elevation", cache.getElevation()); - values.put("shortdesc", cache.getShortdesc()); + values.put("shortdesc", cache.getShortDescription()); values.put("personal_note", cache.getPersonalNote()); values.put("description", cache.getDescription()); values.put("favourite_cnt", cache.getFavoritePoints()); @@ -1064,12 +1034,12 @@ public class cgData { values.put("coordsChanged", cache.hasUserModifiedCoords() ? 1 : 0); values.put("finalDefined", cache.hasFinalDefined() ? 1 : 0); - boolean result = false; init(); //try to update record else insert fresh.. database.beginTransaction(); + boolean result = false; try { saveAttributesWithoutTransaction(cache); saveOriginalWaypointsWithoutTransaction(cache); @@ -1087,7 +1057,7 @@ public class cgData { database.setTransactionSuccessful(); result = true; } catch (Exception e) { - // nothing + Log.e("SaveCache", e); } finally { database.endTransaction(); } @@ -1095,25 +1065,21 @@ public class cgData { return result; } - private void saveAttributesWithoutTransaction(final cgCache cache) { + private static void saveAttributesWithoutTransaction(final Geocache cache) { String geocode = cache.getGeocode(); database.delete(dbTableAttributes, "geocode = ?", new String[]{geocode}); - if (cache.getAttributes().isNotEmpty()) { - - InsertHelper helper = new InsertHelper(database, dbTableAttributes); - long timeStamp = System.currentTimeMillis(); - - for (String attribute : cache.getAttributes()) { - helper.prepareForInsert(); - - helper.bind(ATTRIBUTES_GEOCODE, geocode); - helper.bind(ATTRIBUTES_UPDATED, timeStamp); - helper.bind(ATTRIBUTES_ATTRIBUTE, attribute); + if (cache.getAttributes().isEmpty()) { + return; + } + SQLiteStatement statement = PreparedStatements.getInsertAttribute(); + final long timestamp = System.currentTimeMillis(); + for (String attribute : cache.getAttributes()) { + statement.bindString(1, geocode); + statement.bindLong(2, timestamp); + statement.bindString(3, attribute); - helper.execute(); - } - helper.close(); + statement.executeInsert(); } } @@ -1123,16 +1089,14 @@ public class cgData { * @param destination * a destination to save */ - public void saveSearchedDestination(final Destination destination) { + public static void saveSearchedDestination(final Destination destination) { init(); database.beginTransaction(); try { - ContentValues values = new ContentValues(); - values.put("date", destination.getDate()); - putCoords(values, destination.getCoords()); - database.insert(dbTableSearchDestionationHistory, null, values); + SQLiteStatement insertDestination = PreparedStatements.getInsertSearchDestination(destination); + insertDestination.executeInsert(); database.setTransactionSuccessful(); } catch (Exception e) { Log.e("Updating searchedDestinations db failed", e); @@ -1141,11 +1105,11 @@ public class cgData { } } - public boolean saveWaypoints(final cgCache cache) { - boolean result = false; + public static boolean saveWaypoints(final Geocache cache) { init(); database.beginTransaction(); + boolean result = false; try { saveOriginalWaypointsWithoutTransaction(cache); database.setTransactionSuccessful(); @@ -1158,15 +1122,14 @@ public class cgData { return result; } - private void saveOriginalWaypointsWithoutTransaction(final cgCache cache) { + private static void saveOriginalWaypointsWithoutTransaction(final Geocache cache) { String geocode = cache.getGeocode(); - database.delete(dbTableWaypoints, "geocode = ? and type <> ? and own = 0", new String[]{geocode, "own"}); - List<cgWaypoint> waypoints = cache.getWaypoints(); + List<Waypoint> waypoints = cache.getWaypoints(); if (CollectionUtils.isNotEmpty(waypoints)) { ContentValues values = new ContentValues(); long timeStamp = System.currentTimeMillis(); - for (cgWaypoint oneWaypoint : waypoints) { + for (Waypoint oneWaypoint : waypoints) { if (oneWaypoint.isUserDefined()) { continue; } @@ -1182,9 +1145,13 @@ public class cgData { putCoords(values, oneWaypoint.getCoords()); values.put("note", oneWaypoint.getNote()); values.put("own", oneWaypoint.isUserDefined() ? 1 : 0); - - final long rowId = database.insert(dbTableWaypoints, null, values); - oneWaypoint.setId((int) rowId); + values.put("visited", oneWaypoint.isVisited() ? 1 : 0); + if (oneWaypoint.getId() < 0) { + final long rowId = database.insert(dbTableWaypoints, null, values); + oneWaypoint.setId((int) rowId); + } else { + database.update(dbTableWaypoints, values, "_id = ?", new String[] { Integer.toString(oneWaypoint.getId(), 10) }); + } } } } @@ -1221,15 +1188,15 @@ public class cgData { return new Geopoint(cursor.getDouble(indexLat), cursor.getDouble(indexLon)); } - public boolean saveWaypoint(int id, String geocode, cgWaypoint waypoint) { + private static boolean saveWaypointInternal(int id, String geocode, Waypoint waypoint) { if ((StringUtils.isBlank(geocode) && id <= 0) || waypoint == null) { return false; } init(); - boolean ok = false; database.beginTransaction(); + boolean ok = false; try { ContentValues values = new ContentValues(); values.put("geocode", geocode); @@ -1242,7 +1209,7 @@ public class cgData { putCoords(values, waypoint.getCoords()); values.put("note", waypoint.getNote()); values.put("own", waypoint.isUserDefined() ? 1 : 0); - + values.put("visited", waypoint.isVisited() ? 1 : 0); if (id <= 0) { final long rowId = database.insert(dbTableWaypoints, null, values); waypoint.setId((int) rowId); @@ -1259,7 +1226,7 @@ public class cgData { return ok; } - public boolean deleteWaypoint(int id) { + public static boolean deleteWaypoint(int id) { if (id == 0) { return false; } @@ -1269,28 +1236,32 @@ public class cgData { return database.delete(dbTableWaypoints, "_id = " + id, null) > 0; } - private void saveSpoilersWithoutTransaction(final cgCache cache) { + private static void saveSpoilersWithoutTransaction(final Geocache cache) { String geocode = cache.getGeocode(); database.delete(dbTableSpoilers, "geocode = ?", new String[]{geocode}); - List<cgImage> spoilers = cache.getSpoilers(); + List<Image> spoilers = cache.getSpoilers(); if (CollectionUtils.isNotEmpty(spoilers)) { - ContentValues values = new ContentValues(); - long timeStamp = System.currentTimeMillis(); - for (cgImage spoiler : spoilers) { - values.clear(); - values.put("geocode", geocode); - values.put("updated", timeStamp); - values.put("url", spoiler.getUrl()); - values.put("title", spoiler.getTitle()); - values.put("description", spoiler.getDescription()); - - database.insert(dbTableSpoilers, null, values); + SQLiteStatement insertSpoiler = PreparedStatements.getInsertSpoiler(); + final long timestamp = System.currentTimeMillis(); + for (Image spoiler : spoilers) { + insertSpoiler.bindString(1, geocode); + insertSpoiler.bindLong(2, timestamp); + insertSpoiler.bindString(3, spoiler.getUrl()); + insertSpoiler.bindString(4, spoiler.getTitle()); + final String description = spoiler.getDescription(); + if (description != null) { + insertSpoiler.bindString(5, description); + } + else { + insertSpoiler.bindNull(5); + } + insertSpoiler.executeInsert(); } } } - private void saveLogsWithoutTransaction(final String geocode, final Iterable<LogEntry> logs) { + private static void saveLogsWithoutTransaction(final String geocode, final Iterable<LogEntry> logs) { // TODO delete logimages referring these logs database.delete(dbTableLogs, "geocode = ?", new String[]{geocode}); @@ -1298,59 +1269,51 @@ public class cgData { return; } - InsertHelper helper = new InsertHelper(database, dbTableLogs); - long timeStamp = System.currentTimeMillis(); + SQLiteStatement insertLog = PreparedStatements.getInsertLog(); + final long timestamp = System.currentTimeMillis(); for (LogEntry log : logs) { - helper.prepareForInsert(); - - helper.bind(LOGS_GEOCODE, geocode); - helper.bind(LOGS_UPDATED, timeStamp); - helper.bind(LOGS_TYPE, log.type.id); - helper.bind(LOGS_AUTHOR, log.author); - helper.bind(LOGS_LOG, log.log); - helper.bind(LOGS_DATE, log.date); - helper.bind(LOGS_FOUND, log.found); - helper.bind(LOGS_FRIEND, log.friend); - - long log_id = helper.execute(); - + insertLog.bindString(1, geocode); + insertLog.bindLong(2, timestamp); + insertLog.bindLong(3, log.type.id); + insertLog.bindString(4, log.author); + insertLog.bindString(5, log.log); + insertLog.bindLong(6, log.date); + insertLog.bindLong(7, log.found); + insertLog.bindLong(8, log.friend ? 1 : 0); + long logId = insertLog.executeInsert(); if (log.hasLogImages()) { - ContentValues values = new ContentValues(); - for (cgImage img : log.getLogImages()) { - values.clear(); - values.put("log_id", log_id); - values.put("title", img.getTitle()); - values.put("url", img.getUrl()); - database.insert(dbTableLogImages, null, values); + SQLiteStatement insertImage = PreparedStatements.getInsertLogImage(); + for (Image img : log.getLogImages()) { + insertImage.bindLong(1, logId); + insertImage.bindString(2, img.getTitle()); + insertImage.bindString(3, img.getUrl()); + insertImage.executeInsert(); } } } - helper.close(); } - private void saveLogCountsWithoutTransaction(final cgCache cache) { + private static void saveLogCountsWithoutTransaction(final Geocache cache) { String geocode = cache.getGeocode(); database.delete(dbTableLogCount, "geocode = ?", new String[]{geocode}); Map<LogType, Integer> logCounts = cache.getLogCounts(); if (MapUtils.isNotEmpty(logCounts)) { - ContentValues values = new ContentValues(); - Set<Entry<LogType, Integer>> logCountsItems = logCounts.entrySet(); - long timeStamp = System.currentTimeMillis(); + SQLiteStatement insertLogCounts = PreparedStatements.getInsertLogCounts(); + final long timestamp = System.currentTimeMillis(); for (Entry<LogType, Integer> pair : logCountsItems) { - values.clear(); - values.put("geocode", geocode); - values.put("updated", timeStamp); - values.put("type", pair.getKey().id); - values.put("count", pair.getValue()); + insertLogCounts.bindString(1, geocode); + insertLogCounts.bindLong(2, timestamp); + insertLogCounts.bindLong(3, pair.getKey().id); + insertLogCounts.bindLong(4, pair.getValue()); - database.insert(dbTableLogCount, null, values); + insertLogCounts.executeInsert(); } } } - public boolean saveTrackable(final cgTrackable trackable) { + public static boolean saveTrackable(final Trackable trackable) { init(); database.beginTransaction(); @@ -1364,7 +1327,7 @@ public class cgData { return true; } - private void saveInventoryWithoutTransaction(final String geocode, final List<cgTrackable> trackables) { + private static void saveInventoryWithoutTransaction(final String geocode, final List<Trackable> trackables) { if (geocode != null) { database.delete(dbTableTrackables, "geocode = ?", new String[]{geocode}); } @@ -1372,7 +1335,7 @@ public class cgData { if (CollectionUtils.isNotEmpty(trackables)) { ContentValues values = new ContentValues(); long timeStamp = System.currentTimeMillis(); - for (cgTrackable trackable : trackables) { + for (Trackable trackable : trackables) { final String tbCode = trackable.getGeocode(); if (StringUtils.isNotBlank(tbCode)) { database.delete(dbTableTrackables, "tbcode = ?", new String[] { tbCode }); @@ -1401,12 +1364,12 @@ public class cgData { } } - public Viewport getBounds(final Set<String> geocodes) { + public static Viewport getBounds(final Set<String> geocodes) { if (CollectionUtils.isEmpty(geocodes)) { return null; } - final Set<cgCache> caches = loadCaches(geocodes, LoadFlags.LOAD_CACHE_OR_DB); + final Set<Geocache> caches = loadCaches(geocodes, LoadFlags.LOAD_CACHE_OR_DB); return Viewport.containing(caches); } @@ -1417,12 +1380,12 @@ public class cgData { * The Geocode GCXXXX * @return the loaded cache (if found). Can be null */ - public cgCache loadCache(final String geocode, final EnumSet<LoadFlag> loadFlags) { + public static Geocache loadCache(final String geocode, final EnumSet<LoadFlag> loadFlags) { if (StringUtils.isBlank(geocode)) { throw new IllegalArgumentException("geocode must not be empty"); } - final Set<cgCache> caches = loadCaches(Collections.singleton(geocode), loadFlags); + final Set<Geocache> caches = loadCaches(Collections.singleton(geocode), loadFlags); return caches.isEmpty() ? null : caches.iterator().next(); } @@ -1432,17 +1395,17 @@ public class cgData { * @param geocodes * @return Set of loaded caches. Never null. */ - public Set<cgCache> loadCaches(final Set<String> geocodes, final EnumSet<LoadFlag> loadFlags) { + public static Set<Geocache> loadCaches(final Set<String> geocodes, final EnumSet<LoadFlag> loadFlags) { if (CollectionUtils.isEmpty(geocodes)) { - return new HashSet<cgCache>(); + return new HashSet<Geocache>(); } - Set<cgCache> result = new HashSet<cgCache>(); + Set<Geocache> result = new HashSet<Geocache>(); Set<String> remaining = new HashSet<String>(geocodes); if (loadFlags.contains(LoadFlag.LOAD_CACHE_BEFORE)) { for (String geocode : new HashSet<String>(remaining)) { - cgCache cache = cacheCache.getCacheFromCache(geocode); + Geocache cache = cacheCache.getCacheFromCache(geocode); if (cache != null) { result.add(cache); remaining.remove(cache.getGeocode()); @@ -1458,16 +1421,16 @@ public class cgData { loadFlags.contains(LoadFlag.LOAD_INVENTORY) || loadFlags.contains(LoadFlag.LOAD_OFFLINE_LOG)) { - final Set<cgCache> cachesFromDB = loadCachesFromGeocodes(remaining, loadFlags); + final Set<Geocache> cachesFromDB = loadCachesFromGeocodes(remaining, loadFlags); result.addAll(cachesFromDB); - for (final cgCache cache : cachesFromDB) { + for (final Geocache cache : cachesFromDB) { remaining.remove(cache.getGeocode()); } } if (loadFlags.contains(LoadFlag.LOAD_CACHE_AFTER)) { for (String geocode : new HashSet<String>(remaining)) { - cgCache cache = cacheCache.getCacheFromCache(geocode); + Geocache cache = cacheCache.getCacheFromCache(geocode); if (cache != null) { result.add(cache); remaining.remove(cache.getGeocode()); @@ -1476,7 +1439,7 @@ public class cgData { } if (remaining.size() >= 1) { - Log.e("cgData.loadCaches(" + remaining.toString() + ") failed"); + Log.i("cgData.loadCaches(" + remaining.toString() + ") failed"); } return result; } @@ -1488,14 +1451,12 @@ public class cgData { * @param loadFlags * @return Set of loaded caches. Never null. */ - private Set<cgCache> loadCachesFromGeocodes(final Set<String> geocodes, final EnumSet<LoadFlag> loadFlags) { + private static Set<Geocache> loadCachesFromGeocodes(final Set<String> geocodes, final EnumSet<LoadFlag> loadFlags) { if (CollectionUtils.isEmpty(geocodes)) { return Collections.emptySet(); } - - Log.d("cgData.loadCachesFromGeocodes(" + geocodes.toString() + ") from DB"); - + // do not log the entire collection of geo codes to the debug log. This can be more than 100 KB of text for large lists! init(); final StringBuilder query = new StringBuilder("SELECT "); @@ -1516,29 +1477,25 @@ public class cgData { Cursor cursor = database.rawQuery(query.toString(), null); try { - if (!cursor.moveToFirst()) { - return Collections.emptySet(); - } - - final Set<cgCache> caches = new HashSet<cgCache>(); + final Set<Geocache> caches = new HashSet<Geocache>(); int logIndex = -1; - do { - //Extracted Method = LOADDBMINIMAL - cgCache cache = cgData.createCacheFromDatabaseContent(cursor); + + while (cursor.moveToNext()) { + Geocache cache = cgData.createCacheFromDatabaseContent(cursor); if (loadFlags.contains(LoadFlag.LOAD_ATTRIBUTES)) { cache.setAttributes(loadAttributes(cache.getGeocode())); } if (loadFlags.contains(LoadFlag.LOAD_WAYPOINTS)) { - final List<cgWaypoint> waypoints = loadWaypoints(cache.getGeocode()); + final List<Waypoint> waypoints = loadWaypoints(cache.getGeocode()); if (CollectionUtils.isNotEmpty(waypoints)) { cache.setWaypoints(waypoints, false); } } if (loadFlags.contains(LoadFlag.LOAD_SPOILERS)) { - final List<cgImage> spoilers = loadSpoilers(cache.getGeocode()); + final List<Image> spoilers = loadSpoilers(cache.getGeocode()); cache.setSpoilers(spoilers); } @@ -1552,10 +1509,10 @@ public class cgData { } if (loadFlags.contains(LoadFlag.LOAD_INVENTORY)) { - final List<cgTrackable> inventory = loadInventory(cache.getGeocode()); + final List<Trackable> inventory = loadInventory(cache.getGeocode()); if (CollectionUtils.isNotEmpty(inventory)) { if (cache.getInventory() == null) { - cache.setInventory(new ArrayList<cgTrackable>()); + cache.setInventory(new ArrayList<Trackable>()); } else { cache.getInventory().clear(); } @@ -1573,7 +1530,7 @@ public class cgData { cacheCache.putCacheInCache(cache); caches.add(cache); - } while (cursor.moveToNext()); + } return caches; } finally { cursor.close(); @@ -1599,53 +1556,14 @@ public class cgData { * @param cursor * @return Cache from DB */ - private static cgCache createCacheFromDatabaseContent(Cursor cursor) { - int index; - cgCache cache = new cgCache(); + private static Geocache createCacheFromDatabaseContent(Cursor cursor) { + Geocache cache = new Geocache(); if (cacheColumnIndex == null) { - int[] local_cci = new int[41]; // use a local variable to avoid having the not yet fully initialized array be visible to other threads - local_cci[0] = cursor.getColumnIndex("updated"); - local_cci[1] = cursor.getColumnIndex("reason"); - local_cci[2] = cursor.getColumnIndex("detailed"); - local_cci[3] = cursor.getColumnIndex("detailedupdate"); - local_cci[4] = cursor.getColumnIndex("visiteddate"); - local_cci[5] = cursor.getColumnIndex("geocode"); - local_cci[6] = cursor.getColumnIndex("cacheid"); - local_cci[7] = cursor.getColumnIndex("guid"); - local_cci[8] = cursor.getColumnIndex("type"); - local_cci[9] = cursor.getColumnIndex("name"); - local_cci[10] = cursor.getColumnIndex("own"); - local_cci[11] = cursor.getColumnIndex("owner"); - local_cci[12] = cursor.getColumnIndex("owner_real"); - local_cci[13] = cursor.getColumnIndex("hidden"); - local_cci[14] = cursor.getColumnIndex("hint"); - local_cci[15] = cursor.getColumnIndex("size"); - local_cci[16] = cursor.getColumnIndex("difficulty"); - local_cci[17] = cursor.getColumnIndex("direction"); - local_cci[18] = cursor.getColumnIndex("distance"); - local_cci[19] = cursor.getColumnIndex("terrain"); - local_cci[20] = cursor.getColumnIndex("latlon"); - local_cci[21] = cursor.getColumnIndex("location"); - local_cci[22] = cursor.getColumnIndex("elevation"); - local_cci[23] = cursor.getColumnIndex("personal_note"); - local_cci[24] = cursor.getColumnIndex("shortdesc"); - local_cci[25] = cursor.getColumnIndex("favourite_cnt"); - local_cci[26] = cursor.getColumnIndex("rating"); - local_cci[27] = cursor.getColumnIndex("votes"); - local_cci[28] = cursor.getColumnIndex("myvote"); - local_cci[29] = cursor.getColumnIndex("disabled"); - local_cci[30] = cursor.getColumnIndex("archived"); - local_cci[31] = cursor.getColumnIndex("members"); - local_cci[32] = cursor.getColumnIndex("found"); - local_cci[33] = cursor.getColumnIndex("favourite"); - local_cci[34] = cursor.getColumnIndex("inventoryunknown"); - local_cci[35] = cursor.getColumnIndex("onWatchlist"); - local_cci[36] = cursor.getColumnIndex("reliable_latlon"); - local_cci[37] = cursor.getColumnIndex("coordsChanged"); - local_cci[38] = cursor.getColumnIndex("latitude"); - local_cci[39] = cursor.getColumnIndex("longitude"); - local_cci[40] = cursor.getColumnIndex("finalDefined"); + final int[] local_cci = new int[CACHE_COLUMNS.length]; // use a local variable to avoid having the not yet fully initialized array be visible to other threads + for (int i = 0; i < CACHE_COLUMNS.length; i++) { + local_cci[i] = cursor.getColumnIndex(CACHE_COLUMNS[i]); + } cacheColumnIndex = local_cci; } @@ -1659,71 +1577,69 @@ public class cgData { cache.setGuid(cursor.getString(cacheColumnIndex[7])); cache.setType(CacheType.getById(cursor.getString(cacheColumnIndex[8]))); cache.setName(cursor.getString(cacheColumnIndex[9])); - cache.setOwn(cursor.getInt(cacheColumnIndex[10]) == 1); - cache.setOwnerDisplayName(cursor.getString(cacheColumnIndex[11])); - cache.setOwnerUserId(cursor.getString(cacheColumnIndex[12])); - long dateValue = cursor.getLong(cacheColumnIndex[13]); + cache.setOwnerDisplayName(cursor.getString(cacheColumnIndex[10])); + cache.setOwnerUserId(cursor.getString(cacheColumnIndex[11])); + long dateValue = cursor.getLong(cacheColumnIndex[12]); if (dateValue != 0) { cache.setHidden(new Date(dateValue)); } - cache.setHint(cursor.getString(cacheColumnIndex[14])); - cache.setSize(CacheSize.getById(cursor.getString(cacheColumnIndex[15]))); - cache.setDifficulty(cursor.getFloat(cacheColumnIndex[16])); - index = cacheColumnIndex[17]; + // do not set cache.hint + cache.setSize(CacheSize.getById(cursor.getString(cacheColumnIndex[14]))); + cache.setDifficulty(cursor.getFloat(cacheColumnIndex[15])); + int index = cacheColumnIndex[16]; if (cursor.isNull(index)) { cache.setDirection(null); } else { cache.setDirection(cursor.getFloat(index)); } - index = cacheColumnIndex[18]; + index = cacheColumnIndex[17]; if (cursor.isNull(index)) { cache.setDistance(null); } else { cache.setDistance(cursor.getFloat(index)); } - cache.setTerrain(cursor.getFloat(cacheColumnIndex[19])); - cache.setLatlon(cursor.getString(cacheColumnIndex[20])); - cache.setLocation(cursor.getString(cacheColumnIndex[21])); - cache.setCoords(getCoords(cursor, cacheColumnIndex[38], cacheColumnIndex[39])); - index = cacheColumnIndex[22]; + cache.setTerrain(cursor.getFloat(cacheColumnIndex[18])); + // do not set cache.location + cache.setCoords(getCoords(cursor, cacheColumnIndex[37], cacheColumnIndex[38])); + index = cacheColumnIndex[21]; if (cursor.isNull(index)) { cache.setElevation(null); } else { cache.setElevation(cursor.getDouble(index)); } - cache.setPersonalNote(cursor.getString(cacheColumnIndex[23])); - cache.setShortdesc(cursor.getString(cacheColumnIndex[24])); - // do not set cache.description ! - cache.setFavoritePoints(cursor.getInt(cacheColumnIndex[25])); - cache.setRating(cursor.getFloat(cacheColumnIndex[26])); - cache.setVotes(cursor.getInt(cacheColumnIndex[27])); - cache.setMyVote(cursor.getFloat(cacheColumnIndex[28])); - cache.setDisabled(cursor.getInt(cacheColumnIndex[29]) == 1); - cache.setArchived(cursor.getInt(cacheColumnIndex[30]) == 1); - cache.setPremiumMembersOnly(cursor.getInt(cacheColumnIndex[31]) == 1); - cache.setFound(cursor.getInt(cacheColumnIndex[32]) == 1); - cache.setFavorite(cursor.getInt(cacheColumnIndex[33]) == 1); - cache.setInventoryItems(cursor.getInt(cacheColumnIndex[34])); - cache.setOnWatchlist(cursor.getInt(cacheColumnIndex[35]) == 1); - cache.setReliableLatLon(cursor.getInt(cacheColumnIndex[36]) > 0); - cache.setUserModifiedCoords(cursor.getInt(cacheColumnIndex[37]) > 0); - cache.setFinalDefined(cursor.getInt(cacheColumnIndex[40]) > 0); + cache.setPersonalNote(cursor.getString(cacheColumnIndex[22])); + // do not set cache.shortdesc + // do not set cache.description + cache.setFavoritePoints(cursor.getInt(cacheColumnIndex[24])); + cache.setRating(cursor.getFloat(cacheColumnIndex[25])); + cache.setVotes(cursor.getInt(cacheColumnIndex[26])); + cache.setMyVote(cursor.getFloat(cacheColumnIndex[27])); + cache.setDisabled(cursor.getInt(cacheColumnIndex[28]) == 1); + cache.setArchived(cursor.getInt(cacheColumnIndex[29]) == 1); + cache.setPremiumMembersOnly(cursor.getInt(cacheColumnIndex[30]) == 1); + cache.setFound(cursor.getInt(cacheColumnIndex[31]) == 1); + cache.setFavorite(cursor.getInt(cacheColumnIndex[32]) == 1); + cache.setInventoryItems(cursor.getInt(cacheColumnIndex[33])); + cache.setOnWatchlist(cursor.getInt(cacheColumnIndex[34]) == 1); + cache.setReliableLatLon(cursor.getInt(cacheColumnIndex[35]) > 0); + cache.setUserModifiedCoords(cursor.getInt(cacheColumnIndex[36]) > 0); + cache.setFinalDefined(cursor.getInt(cacheColumnIndex[39]) > 0); Log.d("Loading " + cache.toString() + " (" + cache.getListId() + ") from DB"); return cache; } - public List<String> loadAttributes(String geocode) { + public static List<String> loadAttributes(String geocode) { if (StringUtils.isBlank(geocode)) { return null; } init(); - ArrayList<String> attributes = new ArrayList<String>(); + final ArrayList<String> attributes = new ArrayList<String>(); - Cursor cursor = database.query( + final Cursor cursor = database.query( dbTableAttributes, new String[]{"attribute"}, "geocode = ?", @@ -1733,32 +1649,23 @@ public class cgData { null, "100"); - if (cursor != null && cursor.getCount() > 0) { - cursor.moveToFirst(); - int index = cursor.getColumnIndex("attribute"); - - do { - attributes.add(cursor.getString(index)); - } while (cursor.moveToNext()); + while (cursor.moveToNext()) { + attributes.add(cursor.getString(0)); } - if (cursor != null) { - cursor.close(); - } + cursor.close(); return attributes; } - public cgWaypoint loadWaypoint(int id) { + public static Waypoint loadWaypoint(int id) { if (id == 0) { return null; } init(); - cgWaypoint waypoint = null; - - Cursor cursor = database.query( + final Cursor cursor = database.query( dbTableWaypoints, WAYPOINT_COLUMNS, "_id = ?", @@ -1770,29 +1677,23 @@ public class cgData { Log.d("cgData.loadWaypoint(" + id + ")"); - if (cursor != null && cursor.getCount() > 0) { - cursor.moveToFirst(); + final Waypoint waypoint = cursor.moveToFirst() ? createWaypointFromDatabaseContent(cursor) : null; - waypoint = createWaypointFromDatabaseContent(cursor); - } - - if (cursor != null) { - cursor.close(); - } + cursor.close(); return waypoint; } - public List<cgWaypoint> loadWaypoints(final String geocode) { + public static List<Waypoint> loadWaypoints(final String geocode) { if (StringUtils.isBlank(geocode)) { return null; } init(); - List<cgWaypoint> waypoints = new ArrayList<cgWaypoint>(); + final List<Waypoint> waypoints = new ArrayList<Waypoint>(); - Cursor cursor = database.query( + final Cursor cursor = database.query( dbTableWaypoints, WAYPOINT_COLUMNS, "geocode = ?", @@ -1802,30 +1703,21 @@ public class cgData { "_id", "100"); - if (cursor != null && cursor.getCount() > 0) { - cursor.moveToFirst(); - - do { - - cgWaypoint waypoint = createWaypointFromDatabaseContent(cursor); - - waypoints.add(waypoint); - } while (cursor.moveToNext()); + while (cursor.moveToNext()) { + waypoints.add(createWaypointFromDatabaseContent(cursor)); } - if (cursor != null) { - cursor.close(); - } + cursor.close(); return waypoints; } - private static cgWaypoint createWaypointFromDatabaseContent(Cursor cursor) { - String name = cursor.getString(cursor.getColumnIndex("name")); - WaypointType type = WaypointType.findById(cursor.getString(cursor.getColumnIndex("type"))); - boolean own = cursor.getInt(cursor.getColumnIndex("own")) != 0; - cgWaypoint waypoint = new cgWaypoint(name, type, own); - + private static Waypoint createWaypointFromDatabaseContent(final Cursor cursor) { + final String name = cursor.getString(cursor.getColumnIndex("name")); + final WaypointType type = WaypointType.findById(cursor.getString(cursor.getColumnIndex("type"))); + final boolean own = cursor.getInt(cursor.getColumnIndex("own")) != 0; + final Waypoint waypoint = new Waypoint(name, type, own); + waypoint.setVisited(cursor.getInt(cursor.getColumnIndex("visited")) != 0); waypoint.setId(cursor.getInt(cursor.getColumnIndex("_id"))); waypoint.setGeocode(cursor.getString(cursor.getColumnIndex("geocode"))); waypoint.setPrefix(cursor.getString(cursor.getColumnIndex("prefix"))); @@ -1837,16 +1729,16 @@ public class cgData { return waypoint; } - private List<cgImage> loadSpoilers(String geocode) { + private static List<Image> loadSpoilers(final String geocode) { if (StringUtils.isBlank(geocode)) { return null; } init(); - List<cgImage> spoilers = new ArrayList<cgImage>(); + final List<Image> spoilers = new ArrayList<Image>(); - Cursor cursor = database.query( + final Cursor cursor = database.query( dbTableSpoilers, new String[]{"url", "title", "description"}, "geocode = ?", @@ -1856,22 +1748,11 @@ public class cgData { null, "100"); - if (cursor != null && cursor.getCount() > 0) { - cursor.moveToFirst(); - int indexUrl = cursor.getColumnIndex("url"); - int indexTitle = cursor.getColumnIndex("title"); - int indexDescription = cursor.getColumnIndex("description"); - - do { - cgImage spoiler = new cgImage(cursor.getString(indexUrl), cursor.getString(indexTitle), cursor.getString(indexDescription)); - - spoilers.add(spoiler); - } while (cursor.moveToNext()); + while (cursor.moveToNext()) { + spoilers.add(new Image(cursor.getString(0), cursor.getString(1), cursor.getString(2))); } - if (cursor != null) { - cursor.close(); - } + cursor.close(); return spoilers; } @@ -1882,10 +1763,10 @@ public class cgData { * * @return A list of previously entered destinations or an empty list. */ - public List<Destination> loadHistoryOfSearchedLocations() { + public static List<Destination> loadHistoryOfSearchedLocations() { init(); - Cursor cursor = database.query(dbTableSearchDestionationHistory, + final Cursor cursor = database.query(dbTableSearchDestionationHistory, new String[]{"_id", "date", "latitude", "longitude"}, null, null, @@ -1896,36 +1777,25 @@ public class cgData { final List<Destination> destinations = new LinkedList<Destination>(); - if (cursor != null && cursor.getCount() > 0) { - cursor.moveToFirst(); - int indexId = cursor.getColumnIndex("_id"); - int indexDate = cursor.getColumnIndex("date"); - int indexLatitude = cursor.getColumnIndex("latitude"); - int indexLongitude = cursor.getColumnIndex("longitude"); - - do { - final Destination dest = new Destination(cursor.getLong(indexId), cursor.getLong(indexDate), getCoords(cursor, indexLatitude, indexLongitude)); + while (cursor.moveToNext()) { + final Destination dest = new Destination(cursor.getLong(0), cursor.getLong(1), getCoords(cursor, 2, 3)); - // If coordinates are non-existent or invalid, do not consider - // this point. - if (dest.getCoords() != null) { - destinations.add(dest); - } - } while (cursor.moveToNext()); + // If coordinates are non-existent or invalid, do not consider this point. + if (dest.getCoords() != null) { + destinations.add(dest); + } } - if (cursor != null) { - cursor.close(); - } + cursor.close(); return destinations; } - public boolean clearSearchedDestinations() { - boolean success = true; + public static boolean clearSearchedDestinations() { init(); database.beginTransaction(); + boolean success = true; try { database.delete(dbTableSearchDestionationHistory, null, null); database.setTransactionSuccessful(); @@ -1939,7 +1809,7 @@ public class cgData { return success; } - public List<LogEntry> loadLogs(String geocode) { + public static List<LogEntry> loadLogs(String geocode) { List<LogEntry> logs = new ArrayList<LogEntry>(); if (StringUtils.isBlank(geocode)) { @@ -1948,60 +1818,45 @@ public class cgData { init(); - Cursor cursor = database.rawQuery( - "SELECT cg_logs._id as cg_logs_id, type, author, log, date, found, friend, " + dbTableLogImages + "._id as cg_logImages_id, log_id, title, url FROM " - + dbTableLogs + " LEFT OUTER JOIN " + dbTableLogImages + final Cursor cursor = database.rawQuery( + /* 0 1 2 3 4 5 6 7 8 9 10 */ + "SELECT cg_logs._id as cg_logs_id, type, author, log, date, found, friend, " + dbTableLogImages + "._id as cg_logImages_id, log_id, title, url" + + " FROM " + dbTableLogs + " LEFT OUTER JOIN " + dbTableLogImages + " ON ( cg_logs._id = log_id ) WHERE geocode = ? ORDER BY date desc, cg_logs._id asc", new String[]{geocode}); - if (cursor != null && cursor.getCount() > 0) { - LogEntry log = null; - int indexLogsId = cursor.getColumnIndex("cg_logs_id"); - int indexType = cursor.getColumnIndex("type"); - int indexAuthor = cursor.getColumnIndex("author"); - int indexLog = cursor.getColumnIndex("log"); - int indexDate = cursor.getColumnIndex("date"); - int indexFound = cursor.getColumnIndex("found"); - int indexFriend = cursor.getColumnIndex("friend"); - int indexLogImagesId = cursor.getColumnIndex("cg_logImages_id"); - int indexTitle = cursor.getColumnIndex("title"); - int indexUrl = cursor.getColumnIndex("url"); - while (cursor.moveToNext() && logs.size() < 100) { - if (log == null || log.id != cursor.getInt(indexLogsId)) { - log = new LogEntry( - cursor.getString(indexAuthor), - cursor.getLong(indexDate), - LogType.getById(cursor.getInt(indexType)), - cursor.getString(indexLog)); - log.id = cursor.getInt(indexLogsId); - log.found = cursor.getInt(indexFound); - log.friend = cursor.getInt(indexFriend) == 1; - logs.add(log); - } - if (!cursor.isNull(indexLogImagesId)) { - String title = cursor.getString(indexTitle); - String url = cursor.getString(indexUrl); - log.addLogImage(new cgImage(url, title)); - } + LogEntry log = null; + while (cursor.moveToNext() && logs.size() < 100) { + if (log == null || log.id != cursor.getInt(0)) { + log = new LogEntry( + cursor.getString(2), + cursor.getLong(4), + LogType.getById(cursor.getInt(1)), + cursor.getString(3)); + log.id = cursor.getInt(0); + log.found = cursor.getInt(5); + log.friend = cursor.getInt(6) == 1; + logs.add(log); + } + if (!cursor.isNull(7)) { + log.addLogImage(new Image(cursor.getString(10), cursor.getString(9))); } } - if (cursor != null) { - cursor.close(); - } + cursor.close(); return logs; } - public Map<LogType, Integer> loadLogCounts(String geocode) { + public static Map<LogType, Integer> loadLogCounts(String geocode) { if (StringUtils.isBlank(geocode)) { return null; } init(); - Map<LogType, Integer> logCounts = new HashMap<LogType, Integer>(); + final Map<LogType, Integer> logCounts = new HashMap<LogType, Integer>(); - Cursor cursor = database.query( + final Cursor cursor = database.query( dbTableLogCount, new String[]{"type", "count"}, "geocode = ?", @@ -2011,36 +1866,25 @@ public class cgData { null, "100"); - if (cursor != null && cursor.getCount() > 0) { - cursor.moveToFirst(); - int indexType = cursor.getColumnIndex("type"); - int indexCount = cursor.getColumnIndex("count"); - - do { - LogType type = LogType.getById(cursor.getInt(indexType)); - Integer count = cursor.getInt(indexCount); - - logCounts.put(type, count); - } while (cursor.moveToNext()); + while (cursor.moveToNext()) { + logCounts.put(LogType.getById(cursor.getInt(0)), cursor.getInt(1)); } - if (cursor != null) { - cursor.close(); - } + cursor.close(); return logCounts; } - private List<cgTrackable> loadInventory(String geocode) { + private static List<Trackable> loadInventory(String geocode) { if (StringUtils.isBlank(geocode)) { return null; } init(); - List<cgTrackable> trackables = new ArrayList<cgTrackable>(); + final List<Trackable> trackables = new ArrayList<Trackable>(); - Cursor cursor = database.query( + final Cursor cursor = database.query( dbTableTrackables, new String[]{"_id", "updated", "tbcode", "guid", "title", "owner", "released", "goal", "description"}, "geocode = ?", @@ -2050,33 +1894,23 @@ public class cgData { "title COLLATE NOCASE ASC", "100"); - if (cursor != null && cursor.getCount() > 0) { - cursor.moveToFirst(); - - do { - cgTrackable trackable = createTrackableFromDatabaseContent(cursor); - - trackables.add(trackable); - } while (cursor.moveToNext()); + while (cursor.moveToNext()) { + trackables.add(createTrackableFromDatabaseContent(cursor)); } - if (cursor != null) { - cursor.close(); - } + cursor.close(); return trackables; } - public cgTrackable loadTrackable(String geocode) { + public static Trackable loadTrackable(final String geocode) { if (StringUtils.isBlank(geocode)) { return null; } init(); - cgTrackable trackable = new cgTrackable(); - - Cursor cursor = database.query( + final Cursor cursor = database.query( dbTableTrackables, new String[]{"updated", "tbcode", "guid", "title", "owner", "released", "goal", "description"}, "tbcode = ?", @@ -2086,30 +1920,25 @@ public class cgData { null, "1"); - if (cursor != null && cursor.getCount() > 0) { - cursor.moveToFirst(); - trackable = createTrackableFromDatabaseContent(cursor); - } + final Trackable trackable = cursor.moveToFirst() ? createTrackableFromDatabaseContent(cursor) : null; - if (cursor != null) { - cursor.close(); - } + cursor.close(); return trackable; } - private cgTrackable createTrackableFromDatabaseContent(Cursor cursor) { - cgTrackable trackable = new cgTrackable(); + private static Trackable createTrackableFromDatabaseContent(final Cursor cursor) { + final Trackable trackable = new Trackable(); trackable.setGeocode(cursor.getString(cursor.getColumnIndex("tbcode"))); trackable.setGuid(cursor.getString(cursor.getColumnIndex("guid"))); trackable.setName(cursor.getString(cursor.getColumnIndex("title"))); trackable.setOwner(cursor.getString(cursor.getColumnIndex("owner"))); - String released = cursor.getString(cursor.getColumnIndex("released")); + final String released = cursor.getString(cursor.getColumnIndex("released")); if (released != null) { try { long releaseMilliSeconds = Long.parseLong(released); trackable.setReleased(new Date(releaseMilliSeconds)); - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { Log.e("createTrackableFromDatabaseContent", e); } } @@ -2120,103 +1949,99 @@ public class cgData { } /** - * Number of caches stored. The number is shown on the starting activity of c:geo + * Number of caches stored for a given type and/or list * - * @param detailedOnly * @param cacheType * @param list * @return */ - public int getAllStoredCachesCount(final boolean detailedOnly, final CacheType cacheType, final int list) { + public static int getAllStoredCachesCount(final CacheType cacheType, final int list) { if (cacheType == null) { throw new IllegalArgumentException("cacheType must not be null"); } - init(); - - String listSql; - String listSqlW; - if (list == 0) { - listSql = " where reason >= 1"; - listSqlW = " and reason >= 1"; - } else if (list >= 1) { - listSql = " where reason = " + list; - listSqlW = " and reason = " + list; - } else { - return 0; + if (list <= 0) { + throw new IllegalArgumentException("list must be > 0"); } + init(); - int count = 0; try { - String sql; - if (!detailedOnly) { - if (cacheType == CacheType.ALL) { - sql = "select count(_id) from " + dbTableCaches + listSql; - } else { - sql = "select count(_id) from " + dbTableCaches + " where type = " + DatabaseUtils.sqlEscapeString(cacheType.id) + listSqlW; - } + StringBuilder sql = new StringBuilder("select count(_id) from " + dbTableCaches + " where detailed = 1"); + String typeKey; + int reasonIndex; + if (cacheType != CacheType.ALL) { + sql.append(" and type = ?"); + typeKey = cacheType.id; + reasonIndex = 2; + } + else { + typeKey = "all_types"; + reasonIndex = 1; + } + String listKey; + if (list == StoredList.ALL_LIST_ID) { + sql.append(" and reason > 0"); + listKey = "all_list"; } else { - if (cacheType == CacheType.ALL) { - sql = "select count(_id) from " + dbTableCaches + " where detailed = 1" + listSqlW; - } else { - sql = "select count(_id) from " + dbTableCaches + " where detailed = 1 and type = " + DatabaseUtils.sqlEscapeString(cacheType.id) + listSqlW; - } + sql.append(" and reason = ?"); + listKey = "list"; + } + + String key = "CountCaches_" + typeKey + "_" + listKey; + + SQLiteStatement compiledStmnt = PreparedStatements.getStatement(key, sql.toString()); + if (cacheType != CacheType.ALL) { + compiledStmnt.bindString(1, cacheType.id); + } + if (list != StoredList.ALL_LIST_ID) { + compiledStmnt.bindLong(reasonIndex, list); } - SQLiteStatement compiledStmnt = database.compileStatement(sql); - count = (int) compiledStmnt.simpleQueryForLong(); - compiledStmnt.close(); + return (int) compiledStmnt.simpleQueryForLong(); } catch (Exception e) { - Log.e("cgData.loadAllStoredCachesCount: " + e.toString()); + Log.e("cgData.loadAllStoredCachesCount", e); } - return count; + return 0; } - public int getAllHistoricCachesCount() { + public static int getAllHistoryCachesCount() { init(); - int count = 0; - try { - SQLiteStatement sqlCount = database.compileStatement("select count(_id) from " + dbTableCaches + " where visiteddate > 0"); - count = (int) sqlCount.simpleQueryForLong(); - sqlCount.close(); + return (int) PreparedStatements.getCountHistoryCaches().simpleQueryForLong(); } catch (Exception e) { - Log.e("cgData.getAllHistoricCachesCount: " + e.toString()); + Log.e("cgData.getAllHistoricCachesCount", e); } - return count; + return 0; } /** * Return a batch of stored geocodes. * - * @param detailedOnly * @param coords * the current coordinates to sort by distance, or null to sort by geocode * @param cacheType * @param listId - * @return + * @return a non-null set of geocodes */ - public Set<String> loadBatchOfStoredGeocodes(final boolean detailedOnly, final Geopoint coords, final CacheType cacheType, final int listId) { + private static Set<String> loadBatchOfStoredGeocodes(final Geopoint coords, final CacheType cacheType, final int listId) { if (cacheType == null) { throw new IllegalArgumentException("cacheType must not be null"); } init(); - Set<String> geocodes = new HashSet<String>(); - - StringBuilder specifySql = new StringBuilder(); + final Set<String> geocodes = new HashSet<String>(); - specifySql.append("reason "); - specifySql.append(listId != StoredList.ALL_LIST_ID ? "=" + Math.max(listId, 1) : ">= " + StoredList.STANDARD_LIST_ID); + final StringBuilder selection = new StringBuilder(); - if (detailedOnly) { - specifySql.append(" and detailed = 1 "); - } + selection.append("reason "); + selection.append(listId != StoredList.ALL_LIST_ID ? "=" + Math.max(listId, 1) : ">= " + StoredList.STANDARD_LIST_ID); + selection.append(" and detailed = 1 "); + String[] selectionArgs = null; if (cacheType != CacheType.ALL) { - specifySql.append(" and type = "); - specifySql.append(DatabaseUtils.sqlEscapeString(cacheType.id)); + selection.append(" and type = ?"); + selectionArgs = new String[] { String.valueOf(cacheType.id) }; } try { @@ -2226,8 +2051,8 @@ public class cgData { dbTableCaches, new String[]{"geocode", "(abs(latitude-" + String.format((Locale) null, "%.6f", coords.getLatitude()) + ") + abs(longitude-" + String.format((Locale) null, "%.6f", coords.getLongitude()) + ")) as dif"}, - specifySql.toString(), - null, + selection.toString(), + selectionArgs, null, null, "dif", @@ -2236,86 +2061,69 @@ public class cgData { cursor = database.query( dbTableCaches, new String[]{"geocode"}, - specifySql.toString(), - null, + selection.toString(), + selectionArgs, null, null, "geocode"); } - if (cursor.moveToFirst()) { - final int index = cursor.getColumnIndex("geocode"); - - do { - geocodes.add(cursor.getString(index)); - } while (cursor.moveToNext()); + while (cursor.moveToNext()) { + geocodes.add(cursor.getString(0)); } cursor.close(); - - } catch (Exception e) { - Log.e("cgData.loadBatchOfStoredGeocodes: " + e.toString()); + } catch (final Exception e) { + Log.e("cgData.loadBatchOfStoredGeocodes", e); } return geocodes; } - public Set<String> loadBatchOfHistoricGeocodes(final boolean detailedOnly, final CacheType cacheType) { + private static Set<String> loadBatchOfHistoricGeocodes(final boolean detailedOnly, final CacheType cacheType) { init(); - Set<String> geocodes = new HashSet<String>(); + final Set<String> geocodes = new HashSet<String>(); - StringBuilder specifySql = new StringBuilder(); - specifySql.append("visiteddate > 0"); + final StringBuilder selection = new StringBuilder("visiteddate > 0"); if (detailedOnly) { - specifySql.append(" and detailed = 1"); + selection.append(" and detailed = 1"); } + String[] selectionArgs = null; if (cacheType != CacheType.ALL) { - specifySql.append(" and type = "); - specifySql.append(DatabaseUtils.sqlEscapeString(cacheType.id)); + selection.append(" and type = ?"); + selectionArgs = new String[] { String.valueOf(cacheType.id) }; } try { - Cursor cursor = database.query( + final Cursor cursor = database.query( dbTableCaches, new String[]{"geocode"}, - specifySql.toString(), - null, + selection.toString(), + selectionArgs, null, null, "visiteddate", null); - - if (cursor != null) { - if (cursor.getCount() > 0) { - cursor.moveToFirst(); - int index = cursor.getColumnIndex("geocode"); - - do { - geocodes.add(cursor.getString(index)); - } while (cursor.moveToNext()); - } else { - cursor.close(); - return null; - } - - cursor.close(); + while (cursor.moveToNext()) { + geocodes.add(cursor.getString(0)); } + cursor.close(); } catch (Exception e) { - Log.e("cgData.loadBatchOfHistoricGeocodes: " + e.toString()); + Log.e("cgData.loadBatchOfHistoricGeocodes", e); } return geocodes; } /** Retrieve all stored caches from DB */ - public Set<String> loadCachedInViewport(final Viewport viewport, final CacheType cacheType) { + public static SearchResult loadCachedInViewport(final Viewport viewport, final CacheType cacheType) { return loadInViewport(false, viewport, cacheType); } /** Retrieve stored caches from DB with listId >= 1 */ - public Set<String> loadStoredInViewport(final Viewport viewport, final CacheType cacheType) { + public static SearchResult loadStoredInViewport(final Viewport viewport, final CacheType cacheType) { return loadInViewport(true, viewport, cacheType); } @@ -2331,7 +2139,7 @@ public class cgData { * @param cacheType * @return Set with geocodes */ - private Set<String> loadInViewport(final boolean stored, final Viewport viewport, final CacheType cacheType) { + private static SearchResult loadInViewport(final boolean stored, final Viewport viewport, final CacheType cacheType) { init(); final Set<String> geocodes = new HashSet<String>(); @@ -2342,47 +2150,45 @@ public class cgData { } // viewport limitation - final StringBuilder where = new StringBuilder(buildCoordinateWhere(dbTableCaches, viewport)); + final StringBuilder selection = new StringBuilder(buildCoordinateWhere(dbTableCaches, viewport)); // cacheType limitation + String[] selectionArgs = null; if (cacheType != CacheType.ALL) { - where.append(" and type = "); - where.append(DatabaseUtils.sqlEscapeString(cacheType.id)); + selection.append(" and type = ?"); + selectionArgs = new String[] { String.valueOf(cacheType.id) }; } // offline caches only if (stored) { - where.append(" and reason >= " + StoredList.STANDARD_LIST_ID); + selection.append(" and reason >= " + StoredList.STANDARD_LIST_ID); } try { final Cursor cursor = database.query( dbTableCaches, new String[]{"geocode"}, - where.toString(), - null, + selection.toString(), + selectionArgs, null, null, null, "500"); - if (cursor.moveToFirst()) { - final int index = cursor.getColumnIndex("geocode"); - - do { - geocodes.add(cursor.getString(index)); - } while (cursor.moveToNext()); + while (cursor.moveToNext()) { + geocodes.add(cursor.getString(0)); } + cursor.close(); - } catch (Exception e) { - Log.e("cgData.loadInViewport: " + e.toString()); + } catch (final Exception e) { + Log.e("cgData.loadInViewport", e); } - return geocodes; + return new SearchResult(geocodes); } /** delete caches from the DB store 3 days or more before */ - public void clean() { + public static void clean() { clean(false); } @@ -2392,15 +2198,17 @@ public class cgData { * @param more * true = all caches false = caches stored 3 days or more before */ - public void clean(boolean more) { + public static void clean(final boolean more) { + if (databaseCleaned) { + return; + } + init(); Log.d("Database clean: started"); - Cursor cursor; - Set<String> geocodes = new HashSet<String>(); - try { + Cursor cursor; if (more) { cursor = database.query( dbTableCaches, @@ -2425,44 +2233,31 @@ public class cgData { null); } - if (cursor != null) { - if (cursor.getCount() > 0) { - cursor.moveToFirst(); - final int index = cursor.getColumnIndex("geocode"); - - do { - geocodes.add(cursor.getString(index)); - } while (cursor.moveToNext()); - } - - cursor.close(); + Set<String> geocodes = new HashSet<String>(); + while (cursor.moveToNext()) { + geocodes.add(cursor.getString(0)); } - final int size = geocodes.size(); - if (size > 0) { - Log.d("Database clean: removing " + size + " geocaches from listId=0"); + cursor.close(); + if (!geocodes.isEmpty()) { + Log.d("Database clean: removing " + geocodes.size() + " geocaches from listId=0"); removeCaches(geocodes, LoadFlags.REMOVE_ALL); } - - final SQLiteStatement countSql = database.compileStatement("select count(_id) from " + dbTableCaches + " where reason = 0"); - final int count = (int) countSql.simpleQueryForLong(); - countSql.close(); - Log.d("Database clean: " + count + " geocaches remaining for listId=0"); - - } catch (Exception e) { - Log.w("cgData.clean: " + e.toString()); + } catch (final Exception e) { + Log.w("cgData.clean", e); } Log.d("Database clean: finished"); + databaseCleaned = true; } - public void removeAllFromCache() { + public static void removeAllFromCache() { // clean up CacheCache cacheCache.removeAllFromCache(); } - public void removeCache(final String geocode, EnumSet<LoadFlags.RemoveFlag> removeFlags) { + public static void removeCache(final String geocode, EnumSet<LoadFlags.RemoveFlag> removeFlags) { removeCaches(Collections.singleton(geocode), removeFlags); } @@ -2472,7 +2267,7 @@ public class cgData { * @param geocodes * list of geocodes to drop from cache */ - public void removeCaches(final Set<String> geocodes, EnumSet<LoadFlags.RemoveFlag> removeFlags) { + public static void removeCaches(final Set<String> geocodes, EnumSet<LoadFlags.RemoveFlag> removeFlags) { if (CollectionUtils.isEmpty(geocodes)) { return; } @@ -2519,7 +2314,7 @@ public class cgData { } } - public boolean saveLogOffline(String geocode, Date date, LogType type, String log) { + public static boolean saveLogOffline(String geocode, Date date, LogType type, String log) { if (StringUtils.isBlank(geocode)) { Log.e("cgData.saveLogOffline: cannot log a blank geocode"); return false; @@ -2546,16 +2341,15 @@ public class cgData { return id != -1; } - public LogEntry loadLogOffline(String geocode) { + public static LogEntry loadLogOffline(String geocode) { if (StringUtils.isBlank(geocode)) { return null; } init(); - LogEntry log = null; - Cursor cursor = database.query( + final Cursor cursor = database.query( dbTableLogsOffline, new String[]{"_id", "type", "log", "date"}, "geocode = ?", @@ -2565,24 +2359,20 @@ public class cgData { "_id desc", "1"); - if (cursor != null && cursor.getCount() > 0) { - cursor.moveToFirst(); - - log = new LogEntry( - cursor.getLong(cursor.getColumnIndex("date")), - LogType.getById(cursor.getInt(cursor.getColumnIndex("type"))), - cursor.getString(cursor.getColumnIndex("log"))); - log.id = cursor.getInt(cursor.getColumnIndex("_id")); + LogEntry log = null; + if (cursor.moveToFirst()) { + log = new LogEntry(cursor.getLong(3), + LogType.getById(cursor.getInt(1)), + cursor.getString(2)); + log.id = cursor.getInt(0); } - if (cursor != null) { - cursor.close(); - } + cursor.close(); return log; } - public void clearLogOffline(String geocode) { + public static void clearLogOffline(String geocode) { if (StringUtils.isBlank(geocode)) { return; } @@ -2592,35 +2382,30 @@ public class cgData { database.delete(dbTableLogsOffline, "geocode = ?", new String[]{geocode}); } - private SQLiteStatement getStatementLogCount() { - return getStatement("LogCountFromGeocode", "SELECT count(_id) FROM " + dbTableLogsOffline + " WHERE geocode = ?"); - } - - private synchronized SQLiteStatement getStatement(final String key, final String query) { - SQLiteStatement statement = statements.get(key); - if (statement == null) { - statement = database.compileStatement(query); - statements.put(key, statement); + public static void clearLogsOffline(List<Geocache> caches) { + if (CollectionUtils.isEmpty(caches)) { + return; } - return statement; - } - private SQLiteStatement getStatementCountStandardList() { - return getStatement("CountStandardList", "SELECT count(_id) FROM " + dbTableCaches + " WHERE reason = " + StoredList.STANDARD_LIST_ID); - } + init(); - private SQLiteStatement getStatementCountAllLists() { - return getStatement("CountAllLists", "SELECT count(_id) FROM " + dbTableCaches + " WHERE reason >= " + StoredList.STANDARD_LIST_ID); + Set<String> geocodes = new HashSet<String>(caches.size()); + for (Geocache cache : caches) { + geocodes.add(cache.getGeocode()); + cache.setLogOffline(false); + } + + database.execSQL(String.format("DELETE FROM %s where %s", dbTableLogsOffline, whereGeocodeIn(geocodes))); } - public boolean hasLogOffline(final String geocode) { + public static boolean hasLogOffline(final String geocode) { if (StringUtils.isBlank(geocode)) { return false; } init(); try { - final SQLiteStatement logCount = getStatementLogCount(); + final SQLiteStatement logCount = PreparedStatements.getLogCountOfGeocode(); synchronized (logCount) { logCount.bindString(1, geocode); return logCount.simpleQueryForLong() > 0; @@ -2632,7 +2417,7 @@ public class cgData { return false; } - public void setVisitDate(List<String> geocodes, long visitedDate) { + private static void setVisitDate(List<String> geocodes, long visitedDate) { if (geocodes.isEmpty()) { return; } @@ -2641,11 +2426,12 @@ public class cgData { database.beginTransaction(); try { - ContentValues values = new ContentValues(); - values.put("visiteddate", visitedDate); + SQLiteStatement setVisit = PreparedStatements.getUpdateVisitDate(); for (String geocode : geocodes) { - database.update(dbTableCaches, values, "geocode = ?", new String[]{geocode}); + setVisit.bindLong(1, visitedDate); + setVisit.bindString(2, geocode); + setVisit.execute(); } database.setTransactionSuccessful(); } finally { @@ -2653,11 +2439,12 @@ public class cgData { } } - public List<StoredList> getLists(Resources res) { + public static List<StoredList> getLists() { init(); - List<StoredList> lists = new ArrayList<StoredList>(); - lists.add(new StoredList(StoredList.STANDARD_LIST_ID, res.getString(R.string.list_inbox), (int) getStatementCountStandardList().simpleQueryForLong())); + final Resources res = cgeoapplication.getInstance().getResources(); + final List<StoredList> lists = new ArrayList<StoredList>(); + lists.add(new StoredList(StoredList.STANDARD_LIST_ID, res.getString(R.string.list_inbox), (int) PreparedStatements.getCountCachesOnStandardList().simpleQueryForLong())); try { String query = "SELECT l._id as _id, l.title as title, COUNT(c._id) as count" + @@ -2666,47 +2453,38 @@ public class cgData { " GROUP BY l._id" + " ORDER BY l.title COLLATE NOCASE ASC"; - Cursor cursor = database.rawQuery(query, null); + final Cursor cursor = database.rawQuery(query, null); ArrayList<StoredList> storedLists = getListsFromCursor(cursor); lists.addAll(storedLists); - - } catch (Exception e) { - Log.e("cgData.readLists: " + e.toString()); + cursor.close(); + } catch (final Exception e) { + Log.e("cgData.readLists", e); } return lists; } - private static ArrayList<StoredList> getListsFromCursor(Cursor cursor) { - ArrayList<StoredList> result = new ArrayList<StoredList>(); - if (cursor != null) { - if (cursor.getCount() > 0) { - cursor.moveToFirst(); - int indexId = cursor.getColumnIndex("_id"); - int indexTitle = cursor.getColumnIndex("title"); - int indexCount = cursor.getColumnIndex("count"); - do { - int count = 0; - if (indexCount >= 0) { - count = cursor.getInt(indexCount); - } - StoredList list = new StoredList(cursor.getInt(indexId) + customListIdOffset, cursor.getString(indexTitle), count); - result.add(list); - } while (cursor.moveToNext()); - } - - cursor.close(); + private static ArrayList<StoredList> getListsFromCursor(final Cursor cursor) { + final int indexId = cursor.getColumnIndex("_id"); + final int indexTitle = cursor.getColumnIndex("title"); + final int indexCount = cursor.getColumnIndex("count"); + final ArrayList<StoredList> result = new ArrayList<StoredList>(); + while (cursor.moveToNext()) { + final int count = indexCount != -1 ? cursor.getInt(indexCount) : 0; + final StoredList list = new StoredList(cursor.getInt(indexId) + customListIdOffset, cursor.getString(indexTitle), count); + result.add(list); } + cursor.close(); return result; } - public StoredList getList(int id, Resources res) { + public static StoredList getList(int id) { init(); if (id >= customListIdOffset) { Cursor cursor = database.query( dbTableLists, new String[]{"_id", "title"}, - "_id = " + (id - customListIdOffset), - null, + "_id = ? ", + new String[] { String.valueOf(id - customListIdOffset) }, null, null, null); @@ -2716,18 +2494,23 @@ public class cgData { } } + Resources res = cgeoapplication.getInstance().getResources(); if (id == StoredList.ALL_LIST_ID) { - return new StoredList(StoredList.ALL_LIST_ID, res.getString(R.string.list_all_lists), (int) getStatementCountAllLists().simpleQueryForLong()); + return new StoredList(StoredList.ALL_LIST_ID, res.getString(R.string.list_all_lists), getAllCachesCount()); } // fall back to standard list in case of invalid list id if (id == StoredList.STANDARD_LIST_ID || id >= customListIdOffset) { - return new StoredList(StoredList.STANDARD_LIST_ID, res.getString(R.string.list_inbox), (int) getStatementCountStandardList().simpleQueryForLong()); + return new StoredList(StoredList.STANDARD_LIST_ID, res.getString(R.string.list_inbox), (int) PreparedStatements.getCountCachesOnStandardList().simpleQueryForLong()); } return null; } + public static int getAllCachesCount() { + return (int) PreparedStatements.getCountAllCaches().simpleQueryForLong(); + } + /** * Create a new list * @@ -2735,7 +2518,7 @@ public class cgData { * Name * @return new listId */ - public int createList(String name) { + public static int createList(String name) { int id = -1; if (StringUtils.isBlank(name)) { return id; @@ -2765,15 +2548,15 @@ public class cgData { * New name of list * @return Number of lists changed */ - public int renameList(final int listId, final String name) { + public static int renameList(final int listId, final String name) { if (StringUtils.isBlank(name) || StoredList.STANDARD_LIST_ID == listId) { return 0; } init(); - int count = 0; database.beginTransaction(); + int count = 0; try { ContentValues values = new ContentValues(); values.put("title", name); @@ -2794,23 +2577,23 @@ public class cgData { * @param listId * @return true if the list got deleted, false else */ - public boolean removeList(int listId) { - boolean status = false; + public static boolean removeList(int listId) { if (listId < customListIdOffset) { - return status; + return false; } init(); database.beginTransaction(); + boolean status = false; try { int cnt = database.delete(dbTableLists, "_id = " + (listId - customListIdOffset), null); if (cnt > 0) { // move caches from deleted list to standard list - ContentValues values = new ContentValues(); - values.put("reason", StoredList.STANDARD_LIST_ID); - database.update(dbTableCaches, values, "reason = " + listId, null); + SQLiteStatement moveToStandard = PreparedStatements.getMoveToStandardList(); + moveToStandard.bindLong(1, listId); + moveToStandard.execute(); status = true; } @@ -2823,7 +2606,11 @@ public class cgData { return status; } - public void moveToList(final List<cgCache> caches, final int listId) { + public static void moveToList(final Geocache cache, final int listId) { + moveToList(Collections.singletonList(cache), listId); + } + + public static void moveToList(final List<Geocache> caches, final int listId) { if (listId == StoredList.ALL_LIST_ID) { return; } @@ -2832,13 +2619,14 @@ public class cgData { } init(); - final ContentValues values = new ContentValues(); - values.put("reason", listId); + SQLiteStatement move = PreparedStatements.getMoveToList(); database.beginTransaction(); try { - for (cgCache cache : caches) { - database.update(dbTableCaches, values, "geocode = ?", new String[]{cache.getGeocode()}); + for (Geocache cache : caches) { + move.bindLong(1, listId); + move.bindString(2, cache.getGeocode()); + move.execute(); cache.setListId(listId); } database.setTransactionSuccessful(); @@ -2847,72 +2635,74 @@ public class cgData { } } - public synchronized boolean status() { + public static boolean isInitialized() { return database != null; - } - public boolean removeSearchedDestination(Destination destination) { - boolean success = true; + public static boolean removeSearchedDestination(Destination destination) { if (destination == null) { - success = false; - } else { - init(); - - database.beginTransaction(); - try { - database.delete(dbTableSearchDestionationHistory, "_id = " + destination.getId(), null); - database.setTransactionSuccessful(); - } catch (Exception e) { - Log.e("Unable to remove searched destination", e); - success = false; - } finally { - database.endTransaction(); - } + return false; } + init(); - return success; - } - - private SQLiteStatement getStatementDescription() { - return getStatement("descriptionFromGeocode", "SELECT description FROM " + dbTableCaches + " WHERE geocode = ?"); - } - - private SQLiteStatement getStatementListIdFromGeocode() { - return getStatement("listFromGeocode", "SELECT reason FROM " + dbTableCaches + " WHERE geocode = ?"); - } + database.beginTransaction(); + boolean result = false; + try { + database.delete(dbTableSearchDestionationHistory, "_id = " + destination.getId(), null); + database.setTransactionSuccessful(); + result = true; + } catch (Exception e) { + Log.e("Unable to remove searched destination", e); + } finally { + database.endTransaction(); + } - private SQLiteStatement getStatementListIdFromGuid() { - return getStatement("listFromGeocode", "SELECT reason FROM " + dbTableCaches + " WHERE guid = ?"); + return result; } - private SQLiteStatement getStatementCacheId() { - return getStatement("cacheIdFromGeocode", "SELECT cacheid FROM " + dbTableCaches + " WHERE geocode = ?"); - } + /** + * Load the lazily initialized fields of a cache and return them as partial cache (all other fields unset). + * + * @param geocode + * @return + */ + public static Geocache loadCacheTexts(final String geocode) { + final Geocache partial = new Geocache(); - private SQLiteStatement getStatementGeocode() { - return getStatement("geocodeFromGuid", "SELECT geocode FROM " + dbTableCaches + " WHERE guid = ?"); - } + // in case of database issues, we still need to return a result to avoid endless loops + partial.setDescription(StringUtils.EMPTY); + partial.setShortDescription(StringUtils.EMPTY); + partial.setHint(StringUtils.EMPTY); + partial.setLocation(StringUtils.EMPTY); - public String getCacheDescription(String geocode) { - if (StringUtils.isBlank(geocode)) { - return null; - } init(); try { - final SQLiteStatement description = getStatementDescription(); - synchronized (description) { - description.bindString(1, geocode); - return description.simpleQueryForString(); + final Cursor cursor = database.query( + dbTableCaches, + new String[] { "description", "shortdesc", "hint", "location" }, + "geocode = ?", + new String[] { geocode }, + null, + null, + null, + "1"); + + if (cursor.moveToFirst()) { + partial.setDescription(StringUtils.defaultString(cursor.getString(0))); + partial.setShortDescription(StringUtils.defaultString(cursor.getString(1))); + partial.setHint(StringUtils.defaultString(cursor.getString(2))); + partial.setLocation(StringUtils.defaultString(cursor.getString(3))); } + + cursor.close(); } catch (SQLiteDoneException e) { // Do nothing, it only means we have no information on the cache } catch (Exception e) { Log.e("cgData.getCacheDescription", e); } - return null; + return partial; } /** @@ -2931,14 +2721,14 @@ public class cgData { newlyCreatedDatabase = false; } - private static String whereGeocodeIn(Set<String> geocodes) { + private static StringBuilder whereGeocodeIn(Set<String> geocodes) { final StringBuilder where = new StringBuilder(); - if (geocodes != null && geocodes.size() > 0) { + if (geocodes != null && !geocodes.isEmpty()) { StringBuilder all = new StringBuilder(); for (String geocode : geocodes) { if (all.length() > 0) { - all.append(", "); + all.append(','); } all.append(DatabaseUtils.sqlEscapeString(geocode)); } @@ -2946,7 +2736,7 @@ public class cgData { where.append("geocode in (").append(all).append(')'); } - return where.toString(); + return where; } /** @@ -2958,16 +2748,16 @@ public class cgData { * @return */ - public Set<cgWaypoint> loadWaypoints(final Viewport viewport, boolean excludeMine, boolean excludeDisabled, CacheType type) { + public static Set<Waypoint> loadWaypoints(final Viewport viewport, boolean excludeMine, boolean excludeDisabled, CacheType type) { final StringBuilder where = new StringBuilder(buildCoordinateWhere(dbTableWaypoints, viewport)); if (excludeMine) { - where.append(" and ").append(dbTableCaches).append(".own == 0 and ").append(dbTableCaches).append(".found == 0"); + where.append(" and ").append(dbTableCaches).append(".found == 0"); } if (excludeDisabled) { where.append(" and ").append(dbTableCaches).append(".disabled == 0"); } if (type != CacheType.ALL) { - where.append(" and ").append(dbTableCaches).append(".type == '").append(type.id).append("'"); + where.append(" and ").append(dbTableCaches).append(".type == '").append(type.id).append('\''); } init(); @@ -2977,23 +2767,16 @@ public class cgData { } query.append(" FROM ").append(dbTableWaypoints).append(", ").append(dbTableCaches).append(" WHERE ").append(dbTableWaypoints).append(".geocode == ").append(dbTableCaches).append(".geocode and ").append(where); + final Set<Waypoint> waypoints = new HashSet<Waypoint>(); final Cursor cursor = database.rawQuery(query.toString(), null); - try { - if (!cursor.moveToFirst()) { - return Collections.emptySet(); - } - - final Set<cgWaypoint> waypoints = new HashSet<cgWaypoint>(); - do { - waypoints.add(createWaypointFromDatabaseContent(cursor)); - } while (cursor.moveToNext()); - return waypoints; - } finally { - cursor.close(); + while (cursor.moveToNext()) { + waypoints.add(createWaypointFromDatabaseContent(cursor)); } + cursor.close(); + return waypoints; } - public String[] getTrackableCodes() { + public static String[] getTrackableCodes() { init(); final Cursor cursor = database.query( @@ -3005,21 +2788,164 @@ public class cgData { null, "updated DESC", "100"); + return getFirstColumn(cursor); + } + + /** + * Extract the first column of the cursor rows and close the cursor. + * + * @param cursor a database cursor + * @return the first column of each row + */ + private static String[] getFirstColumn(final Cursor cursor) { + final String[] result = new String[cursor.getCount()]; + for (int i = 0; cursor.moveToNext(); i++) { + result[i] = cursor.getString(0); + } + cursor.close(); + return result; + } + + public static boolean saveChangedCache(Geocache cache) { + return cgData.saveCache(cache, cache.getStorageLocation().contains(StorageLocation.DATABASE) ? LoadFlags.SAVE_ALL : EnumSet.of(SaveFlag.SAVE_CACHE)); + } + + private static class PreparedStatements { + + private static HashMap<String, SQLiteStatement> statements = new HashMap<String, SQLiteStatement>(); + + public static SQLiteStatement getMoveToStandardList() { + return getStatement("MoveToStandardList", "UPDATE " + dbTableCaches + " SET reason = " + StoredList.STANDARD_LIST_ID + " WHERE reason = ?"); + } + + public static SQLiteStatement getMoveToList() { + return getStatement("MoveToList", "UPDATE " + dbTableCaches + " SET reason = ? WHERE geocode = ?"); + } + + public static SQLiteStatement getUpdateVisitDate() { + return getStatement("UpdateVisitDate", "UPDATE " + dbTableCaches + " SET visiteddate = ? WHERE geocode = ?"); + } + + public static SQLiteStatement getInsertLogImage() { + return getStatement("InsertLogImage", "INSERT INTO " + dbTableLogImages + " (log_id, title, url) VALUES (?, ?, ?)"); + } + + public static SQLiteStatement getInsertLogCounts() { + return getStatement("InsertLogCounts", "INSERT INTO " + dbTableLogCount + " (geocode, updated, type, count) VALUES (?, ?, ?, ?)"); + } + + public static SQLiteStatement getInsertSpoiler() { + return getStatement("InsertSpoiler", "INSERT INTO " + dbTableSpoilers + " (geocode, updated, url, title, description) VALUES (?, ?, ?, ?, ?)"); + } - final ArrayList<String> list = new ArrayList<String>(); - if (cursor != null) { - if (cursor.getCount() > 0) { - cursor.moveToFirst(); + public static SQLiteStatement getInsertSearchDestination(Destination destination) { + final SQLiteStatement statement = getStatement("InsertSearch", "INSERT INTO " + dbTableSearchDestionationHistory + " (date, latitude, longitude) VALUES (?, ?, ?)"); + statement.bindLong(1, destination.getDate()); + final Geopoint coords = destination.getCoords(); + statement.bindDouble(2, coords.getLatitude()); + statement.bindDouble(3, coords.getLongitude()); + return statement; + } - final int index = cursor.getColumnIndex("tbcode"); - do { - list.add(cursor.getString(index)); - } while (cursor.moveToNext()); + private static void clearPreparedStatements() { + for (SQLiteStatement statement : statements.values()) { + statement.close(); } - cursor.close(); + statements.clear(); + } + + private static synchronized SQLiteStatement getStatement(final String key, final String query) { + SQLiteStatement statement = statements.get(key); + if (statement == null) { + init(); + statement = database.compileStatement(query); + statements.put(key, statement); + } + return statement; + } + + public static SQLiteStatement getCountHistoryCaches() { + return getStatement("HistoryCount", "select count(_id) from " + dbTableCaches + " where visiteddate > 0"); + } + + private static SQLiteStatement getLogCountOfGeocode() { + return getStatement("LogCountFromGeocode", "SELECT count(_id) FROM " + cgData.dbTableLogsOffline + " WHERE geocode = ?"); + } + + private static SQLiteStatement getCountCachesOnStandardList() { + return getStatement("CountStandardList", "SELECT count(_id) FROM " + dbTableCaches + " WHERE reason = " + StoredList.STANDARD_LIST_ID); + } + + private static SQLiteStatement getCountAllCaches() { + return getStatement("CountAllLists", "SELECT count(_id) FROM " + dbTableCaches + " WHERE reason >= " + StoredList.STANDARD_LIST_ID); + } + + private static SQLiteStatement getInsertLog() { + return getStatement("InsertLog", "INSERT INTO " + dbTableLogs + " (geocode, updated, type, author, log, date, found, friend) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"); + } + + private static SQLiteStatement getInsertAttribute() { + return getStatement("InsertAttribute", "INSERT INTO " + dbTableAttributes + " (geocode, updated, attribute) VALUES (?, ?, ?)"); + } + + private static SQLiteStatement getListIdOfGeocode() { + return getStatement("listFromGeocode", "SELECT reason FROM " + dbTableCaches + " WHERE geocode = ?"); + } + + private static SQLiteStatement getListIdOfGuid() { + return getStatement("listFromGeocode", "SELECT reason FROM " + dbTableCaches + " WHERE guid = ?"); + } + + private static SQLiteStatement getCacheIdOfGeocode() { + return getStatement("cacheIdFromGeocode", "SELECT cacheid FROM " + dbTableCaches + " WHERE geocode = ?"); } - return list.toArray(new String[list.size()]); + private static SQLiteStatement getGeocodeOfGuid() { + return getStatement("geocodeFromGuid", "SELECT geocode FROM " + dbTableCaches + " WHERE guid = ?"); + } + + } + + public static void saveVisitDate(final String geocode) { + setVisitDate(Collections.singletonList(geocode), System.currentTimeMillis()); + } + + public static void markDropped(List<Geocache> caches) { + moveToList(caches, StoredList.TEMPORARY_LIST_ID); + } + + public static Viewport getBounds(String geocode) { + if (geocode == null) { + return null; + } + + return cgData.getBounds(Collections.singleton(geocode)); + } + + public static void clearVisitDate(List<Geocache> caches) { + ArrayList<String> geocodes = new ArrayList<String>(caches.size()); + for (Geocache cache : caches) { + geocodes.add(cache.getGeocode()); + } + setVisitDate(geocodes, 0); + } + + public static SearchResult getBatchOfStoredCaches(Geopoint coords, CacheType cacheType, int listId) { + final Set<String> geocodes = cgData.loadBatchOfStoredGeocodes(coords, cacheType, listId); + return new SearchResult(geocodes, cgData.getAllStoredCachesCount(cacheType, listId)); + } + + public static SearchResult getHistoryOfCaches(boolean detailedOnly, CacheType cacheType) { + final Set<String> geocodes = cgData.loadBatchOfHistoricGeocodes(detailedOnly, cacheType); + return new SearchResult(geocodes, cgData.getAllHistoryCachesCount()); + } + + public static boolean saveWaypoint(int id, String geocode, Waypoint waypoint) { + if (cgData.saveWaypointInternal(id, geocode, waypoint)) { + cgData.removeCache(geocode, EnumSet.of(RemoveFlag.REMOVE_CACHE)); + return true; + } + return false; } } diff --git a/main/src/cgeo/geocaching/cgeo.java b/main/src/cgeo/geocaching/cgeo.java index c80ec6f..5680ff3 100644 --- a/main/src/cgeo/geocaching/cgeo.java +++ b/main/src/cgeo/geocaching/cgeo.java @@ -8,17 +8,17 @@ import cgeo.geocaching.enumerations.StatusCode; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.Units; import cgeo.geocaching.maps.CGeoMap; -import cgeo.geocaching.network.StatusUpdater.Status; import cgeo.geocaching.ui.Formatter; import cgeo.geocaching.utils.GeoDirHandler; -import cgeo.geocaching.utils.IObserver; import cgeo.geocaching.utils.Log; +import cgeo.geocaching.utils.RunnableWithArgument; import cgeo.geocaching.utils.Version; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import android.app.AlertDialog; +import android.app.AlertDialog.Builder; import android.app.SearchManager; import android.content.DialogInterface; import android.content.Intent; @@ -27,36 +27,28 @@ import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.location.Address; import android.location.Geocoder; -import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; -import android.view.ContextMenu; import android.view.Menu; +import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.Locale; public class cgeo extends AbstractActivity { private static final String SCAN_INTENT = "com.google.zxing.client.android.SCAN"; - private static final int MENU_ABOUT = 0; - private static final int MENU_HELPERS = 1; - private static final int MENU_SETTINGS = 2; - private static final int MENU_HISTORY = 3; - private static final int MENU_SCAN = 4; private static final int SCAN_REQUEST_CODE = 1; - private static final int MENU_OPEN_LIST = 100; - public static final int SEARCH_REQUEST_CODE = 2; private int version = 0; @@ -172,74 +164,11 @@ 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.toString()); + Log.w("cgeo.firstLoginHander", e); } } }; - private class StatusHandler extends Handler implements IObserver<Status> { - - @Override - public void update(final Status data) { - obtainMessage(0, data).sendToTarget(); - } - - @Override - public void handleMessage(final Message msg) { - final Status data = (Status) msg.obj; - updateDisplay(data != null && data.message != null ? data : null); - } - - private void updateDisplay(final Status data) { - final ViewGroup status = (ViewGroup) findViewById(R.id.status); - final ImageView statusIcon = (ImageView) findViewById(R.id.status_icon); - final TextView statusMessage = (TextView) findViewById(R.id.status_message); - - if (data == null) { - status.setVisibility(View.GONE); - return; - } - - if (data.icon != null) { - final int iconId = res.getIdentifier(data.icon, "drawable", getPackageName()); - if (iconId != 0) { - statusIcon.setImageResource(iconId); - statusIcon.setVisibility(View.VISIBLE); - } else { - Log.e("StatusHandler: could not find icon corresponding to @drawable/" + data.icon); - statusIcon.setVisibility(View.GONE); - } - } else { - statusIcon.setVisibility(View.GONE); - } - - String message = data.message; - if (data.messageId != null) { - final int messageId = res.getIdentifier(data.messageId, "string", getPackageName()); - if (messageId != 0) { - message = res.getString(messageId); - } - } - - statusMessage.setText(message); - status.setVisibility(View.VISIBLE); - - if (data.url != null) { - status.setOnClickListener(new OnClickListener() { - @Override - public void onClick(final View v) { - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(data.url))); - } - }); - } else { - status.setClickable(false); - } - } - - } - - private StatusHandler statusHandler = new StatusHandler(); - public cgeo() { super("c:geo-main-screen"); } @@ -248,6 +177,12 @@ public class cgeo extends AbstractActivity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + if ((getIntent().getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) { + // If we had been open already, start from the last used activity. + finish(); + return; + } + setContentView(R.layout.main); setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); // type to search @@ -288,7 +223,6 @@ public class cgeo extends AbstractActivity { @Override public void onResume() { super.onResume(); - app.getStatusUpdater().addObserver(statusHandler); locationUpdater.startGeo(); satellitesHandler.startGeo(); updateUserInfoHandler.sendEmptyMessage(-1); @@ -312,7 +246,6 @@ public class cgeo extends AbstractActivity { @Override public void onPause() { initialized = false; - app.getStatusUpdater().deleteObserver(statusHandler); locationUpdater.stopGeo(); satellitesHandler.stopGeo(); super.onPause(); @@ -320,18 +253,15 @@ public class cgeo extends AbstractActivity { @Override public boolean onCreateOptionsMenu(Menu menu) { - menu.add(0, MENU_SETTINGS, 0, res.getString(R.string.menu_settings)).setIcon(R.drawable.ic_menu_preferences); - menu.add(0, MENU_HISTORY, 0, res.getString(R.string.menu_history)).setIcon(R.drawable.ic_menu_recent_history); - menu.add(0, MENU_HELPERS, 0, res.getString(R.string.menu_helpers)).setIcon(R.drawable.ic_menu_shopping); - menu.add(0, MENU_SCAN, 0, res.getString(R.string.menu_scan_geo)).setIcon(R.drawable.ic_menu_barcode); - menu.add(0, MENU_ABOUT, 0, res.getString(R.string.menu_about)).setIcon(R.drawable.ic_menu_info_details); + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.main_options, menu); return true; } @Override public boolean onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); - MenuItem item = menu.findItem(MENU_SCAN); + MenuItem item = menu.findItem(R.id.menu_scan); if (item != null) { item.setEnabled(isIntentAvailable(SCAN_INTENT)); } @@ -350,26 +280,25 @@ public class cgeo extends AbstractActivity { public boolean onOptionsItemSelected(MenuItem item) { final int id = item.getItemId(); switch (id) { - case MENU_ABOUT: + case R.id.menu_about: showAbout(null); return true; - case MENU_HELPERS: + case R.id.menu_helpers: startActivity(new Intent(this, UsefulAppsActivity.class)); return true; - case MENU_SETTINGS: + case R.id.menu_settings: startActivity(new Intent(this, SettingsActivity.class)); return true; - case MENU_HISTORY: + case R.id.menu_history: cgeocaches.startActivityHistory(this); return true; - case MENU_SCAN: + case R.id.menu_scan: startScannerApplication(); return true; default: - break; + return super.onOptionsItemSelected(item); } - return false; } private void startScannerApplication() { @@ -408,90 +337,6 @@ public class cgeo extends AbstractActivity { } } - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, v, menuInfo); - - // context menu for offline button - if (v.getId() == R.id.search_offline) { - menu.setHeaderTitle(res.getString(R.string.list_title)); - for (final StoredList list : app.getLists()) { - menu.add(Menu.NONE, MENU_OPEN_LIST + list.id, Menu.NONE, list.getTitleAndCount()); - } - return; - } - - // standard context menu - menu.setHeaderTitle(res.getString(R.string.menu_filter)); - - //first add the most used types - menu.add(1, 0, 0, CacheType.ALL.getL10n()); - menu.add(1, 1, 0, CacheType.TRADITIONAL.getL10n()); - menu.add(1, 2, 0, CacheType.MULTI.getL10n()); - menu.add(1, 3, 0, CacheType.MYSTERY.getL10n()); - - // then add all other cache types sorted alphabetically - List<String> sorted = new ArrayList<String>(); - for (CacheType ct : CacheType.values()) { - if (ct == CacheType.ALL || - ct == CacheType.TRADITIONAL || - ct == CacheType.MULTI || - ct == CacheType.MYSTERY) { - continue; - } - sorted.add(ct.getL10n()); - } - Collections.sort(sorted); - for (String choice : sorted) { - menu.add(1, menu.size(), 0, choice); - } - - // mark current filter as checked - menu.setGroupCheckable(1, true, true); - boolean foundItem = false; - int itemCount = menu.size(); - String typeTitle = Settings.getCacheType().getL10n(); - for (int i = 0; i < itemCount; i++) { - if (menu.getItem(i).getTitle().equals(typeTitle)) { - menu.getItem(i).setChecked(true); - foundItem = true; - break; - } - } - if (!foundItem) { - menu.getItem(0).setChecked(true); - } - } - - @Override - public boolean onContextItemSelected(final MenuItem item) { - final int id = item.getItemId(); - if (id < 0) { - return false; - } - - if (id == 0) { - Settings.setCacheType(CacheType.ALL); - setFilterTitle(); - } else if (id > MENU_OPEN_LIST) { - Settings.saveLastList(id - MENU_OPEN_LIST); - cgeocaches.startActivityOffline(this); - } else { - final String itemTitle = item.getTitle().toString(); - CacheType cacheType = CacheType.ALL; - for (final CacheType ct : CacheType.values()) { - if (ct.getL10n().equalsIgnoreCase(itemTitle)) { - cacheType = ct; - break; - } - } - Settings.setCacheType(cacheType); - setFilterTitle(); - } - - return true; - } - private void setFilterTitle() { if (filterTitle == null) { filterTitle = (TextView) findViewById(R.id.filter_button_title); @@ -510,7 +355,7 @@ public class cgeo extends AbstractActivity { Settings.getLogin(); if (app.firstRun) { - (new firstLogin()).start(); + (new FirstLoginThread()).start(); } final View findOnMap = findViewById(R.id.map); @@ -530,7 +375,22 @@ public class cgeo extends AbstractActivity { cgeoFindByOffline(v); } }); - registerForContextMenu(findByOffline); + findByOffline.setOnLongClickListener(new View.OnLongClickListener() { + + @Override + public boolean onLongClick(View v) { + new StoredList.UserInterface(cgeo.this).promptForListSelection(R.string.list_title, new RunnableWithArgument<Integer>() { + + @Override + public void run(Integer selectedListId) { + Settings.saveLastList(selectedListId); + cgeocaches.startActivityOffline(cgeo.this); + } + }); + return true; + } + }); + findByOffline.setLongClickable(true); final View advanced = findViewById(R.id.advanced_button); advanced.setClickable(true); @@ -552,11 +412,18 @@ public class cgeo extends AbstractActivity { final View filter = findViewById(R.id.filter_button); filter.setClickable(true); - registerForContextMenu(filter); filter.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - openContextMenu(v); + selectGlobalTypeFilter(); + } + }); + filter.setOnLongClickListener(new View.OnLongClickListener() { + + @Override + public boolean onLongClick(View v) { + selectGlobalTypeFilter(); + return true; } }); @@ -564,15 +431,65 @@ public class cgeo extends AbstractActivity { setFilterTitle(); checkRestore(); - (new cleanDatabase()).start(); + (new CleanDatabaseThread()).start(); + } + + protected void selectGlobalTypeFilter() { + final List<CacheType> cacheTypes = new ArrayList<CacheType>(); + + //first add the most used types + cacheTypes.add(CacheType.ALL); + cacheTypes.add(CacheType.TRADITIONAL); + cacheTypes.add(CacheType.MULTI); + cacheTypes.add(CacheType.MYSTERY); + + // then add all other cache types sorted alphabetically + List<CacheType> sorted = new ArrayList<CacheType>(); + sorted.addAll(Arrays.asList(CacheType.values())); + sorted.removeAll(cacheTypes); + + Collections.sort(sorted, new Comparator<CacheType>() { + + @Override + public int compare(CacheType left, CacheType right) { + return left.getL10n().compareToIgnoreCase(right.getL10n()); + } + }); + + cacheTypes.addAll(sorted); + + int checkedItem = cacheTypes.indexOf(Settings.getCacheType()); + if (checkedItem < 0) { + checkedItem = 0; + } + + String[] items = new String[cacheTypes.size()]; + for (int i = 0; i < cacheTypes.size(); i++) { + items[i] = cacheTypes.get(i).getL10n(); + } + + Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.menu_filter); + builder.setSingleChoiceItems(items, checkedItem, new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int position) { + CacheType cacheType = cacheTypes.get(position); + Settings.setCacheType(cacheType); + setFilterTitle(); + dialog.dismiss(); + } + + }); + builder.create().show(); } - private void updateCacheCounter() { + void updateCacheCounter() { (new CountBubbleUpdateThread()).start(); } private void checkRestore() { - if (!cgData.isNewlyCreatedDatebase() || null == cgData.isRestoreFile()) { + if (!cgData.isNewlyCreatedDatebase() || null == cgData.getRestoreFile()) { return; } new AlertDialog.Builder(this) @@ -585,7 +502,6 @@ public class cgeo extends AbstractActivity { dialog.dismiss(); cgData.resetNewlyCreatedDatabase(); app.restoreDatabase(cgeo.this); - updateCacheCounter(); } }) .setNegativeButton(getString(android.R.string.no), new DialogInterface.OnClickListener() { @@ -708,7 +624,7 @@ public class cgeo extends AbstractActivity { */ public void cgeoPoint(View v) { findViewById(R.id.any_button).setPressed(true); - startActivity(new Intent(this, cgeopoint.class)); + startActivity(new Intent(this, NavigateAnyPointActivity.class)); } /** @@ -747,7 +663,7 @@ public class cgeo extends AbstractActivity { countBubble.setVisibility(View.VISIBLE); } } catch (Exception e) { - Log.w("cgeo.countBubbleHander: " + e.toString()); + Log.w("cgeo.countBubbleHander", e); } } }; @@ -759,7 +675,7 @@ public class cgeo extends AbstractActivity { } int checks = 0; - while (!app.storageStatus()) { + while (!cgData.isInitialized()) { try { wait(500); checks++; @@ -772,13 +688,13 @@ public class cgeo extends AbstractActivity { } } - countBubbleCnt = app.getAllStoredCachesCount(true, CacheType.ALL); + countBubbleCnt = cgData.getAllCachesCount(); countBubbleHandler.sendEmptyMessage(0); } } - private class cleanDatabase extends Thread { + private class CleanDatabaseThread extends Thread { @Override public void run() { @@ -797,7 +713,7 @@ public class cgeo extends AbstractActivity { } cleanupRunning = true; - app.cleanDatabase(more); + cgData.clean(more); cleanupRunning = false; if (version > 0) { @@ -806,7 +722,7 @@ public class cgeo extends AbstractActivity { } } - private class firstLogin extends Thread { + private class FirstLoginThread extends Thread { @Override public void run() { diff --git a/main/src/cgeo/geocaching/cgeoapplication.java b/main/src/cgeo/geocaching/cgeoapplication.java index 9e221cd..a1fd7d1 100644 --- a/main/src/cgeo/geocaching/cgeoapplication.java +++ b/main/src/cgeo/geocaching/cgeoapplication.java @@ -1,21 +1,10 @@ package cgeo.geocaching; -import cgeo.geocaching.cgData.StorageLocation; import cgeo.geocaching.activity.ActivityMixin; -import cgeo.geocaching.enumerations.CacheType; -import cgeo.geocaching.enumerations.LoadFlags; -import cgeo.geocaching.enumerations.LoadFlags.LoadFlag; -import cgeo.geocaching.enumerations.LoadFlags.RemoveFlag; -import cgeo.geocaching.enumerations.LoadFlags.SaveFlag; -import cgeo.geocaching.enumerations.LogType; -import cgeo.geocaching.geopoint.Geopoint; -import cgeo.geocaching.geopoint.Viewport; import cgeo.geocaching.network.StatusUpdater; import cgeo.geocaching.utils.IObserver; import cgeo.geocaching.utils.Log; -import org.apache.commons.lang3.StringUtils; - import android.app.Activity; import android.app.Application; import android.app.ProgressDialog; @@ -23,27 +12,17 @@ import android.content.res.Resources; import android.os.Handler; import android.os.Message; -import java.io.File; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.EnumSet; -import java.util.List; -import java.util.Map; -import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; public class cgeoapplication extends Application { - final private cgData storage = new cgData(); private volatile GeoDataProvider geo; private volatile DirectionProvider dir; public boolean firstRun = true; // c:geo is just launched public boolean showLoginToast = true; //login toast shown just once. - private boolean databaseCleaned = false; // database was cleaned private boolean liveMapHintShown = false; // livemap hint has been shown final private StatusUpdater statusUpdater = new StatusUpdater(); - private static cgeoapplication instance = null; + private static cgeoapplication instance; public cgeoapplication() { instance = this; @@ -56,34 +35,28 @@ public class cgeoapplication extends Application { @Override public void onCreate() { new Thread(statusUpdater).start(); + // Initialize densitiy related waypoint data + Waypoint.initializeScale(); } @Override public void onLowMemory() { Log.i("Cleaning applications cache."); - removeAllFromCache(); - } - - public void removeAllFromCache() { - storage.removeAllFromCache(); + cgData.removeAllFromCache(); } @Override public void onTerminate() { Log.d("Terminating c:geo…"); - storage.clean(); - storage.closeDb(); + cgData.clean(); + cgData.closeDb(); super.onTerminate(); } - public String backupDatabase() { - return storage.backupDatabase(); - } - /** - * Move the database to/from external storage in a new thread, + * Move the database to/from external cgdata in a new thread, * showing a progress window * * @param fromActivity @@ -105,18 +78,13 @@ public class cgeoapplication extends Application { @Override public void run() { - atomic.set(storage.moveDatabase()); + atomic.set(cgData.moveDatabase()); handler.sendMessage(handler.obtainMessage()); } }; moveThread.start(); } - - public static File isRestoreFile() { - return cgData.isRestoreFile(); - } - /** * restore the database in a new thread, showing a progress window * @@ -135,12 +103,15 @@ 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(); + } } }; @Override public void run() { - atomic.set(storage.restoreDatabase()); + atomic.set(cgData.restoreDatabase()); handler.sendMessage(handler.obtainMessage()); } }; @@ -203,280 +174,6 @@ public class cgeoapplication extends Application { return statusUpdater; } - public boolean storageStatus() { - return storage.status(); - } - - public void cleanDatabase(boolean more) { - if (databaseCleaned) { - return; - } - - storage.clean(more); - databaseCleaned = true; - } - - /** {@link cgData#isThere(String, String, boolean, boolean)} */ - public boolean isThere(String geocode, String guid, boolean detailed, boolean checkTime) { - return storage.isThere(geocode, guid, detailed, checkTime); - } - - /** {@link cgData#isOffline(String, String)} */ - public boolean isOffline(String geocode, String guid) { - return storage.isOffline(geocode, guid); - } - - /** {@link cgData#getGeocodeForGuid(String)} */ - public String getGeocode(String guid) { - return storage.getGeocodeForGuid(guid); - } - - /** {@link cgData#getCacheidForGeocode(String)} */ - public String getCacheid(String geocode) { - return storage.getCacheidForGeocode(geocode); - } - - public boolean hasUnsavedCaches(final SearchResult search) { - if (search == null) { - return false; - } - - for (final String geocode : search.getGeocodes()) { - if (!isOffline(geocode, null)) { - return true; - } - } - return false; - } - - public cgTrackable getTrackableByGeocode(String geocode) { - if (StringUtils.isBlank(geocode)) { - return null; - } - - return storage.loadTrackable(geocode); - } - - /** {@link cgData#allDetailedThere()} */ - public String[] geocodesInCache() { - return storage.allDetailedThere(); - } - - public Viewport getBounds(String geocode) { - if (geocode == null) { - return null; - } - - return getBounds(Collections.singleton(geocode)); - } - - /** {@link cgData#getBounds(Set)} */ - public Viewport getBounds(final Set<String> geocodes) { - return storage.getBounds(geocodes); - } - - /** {@link cgData#loadBatchOfStoredGeocodes(boolean, Geopoint, CacheType, int)} */ - public SearchResult getBatchOfStoredCaches(final boolean detailedOnly, final Geopoint coords, final CacheType cacheType, final int listId) { - final Set<String> geocodes = storage.loadBatchOfStoredGeocodes(detailedOnly, coords, cacheType, listId); - return new SearchResult(geocodes, getAllStoredCachesCount(true, cacheType, listId)); - } - - /** {@link cgData#loadHistoryOfSearchedLocations()} */ - public List<Destination> getHistoryOfSearchedLocations() { - return storage.loadHistoryOfSearchedLocations(); - } - - public SearchResult getHistoryOfCaches(final boolean detailedOnly, final CacheType cacheType) { - final Set<String> geocodes = storage.loadBatchOfHistoricGeocodes(detailedOnly, cacheType); - return new SearchResult(geocodes, getAllHistoricCachesCount()); - } - - /** {@link cgData#loadCachedInViewport(Viewport, CacheType)} */ - public SearchResult getCachedInViewport(final Viewport viewport, final CacheType cacheType) { - final Set<String> geocodes = storage.loadCachedInViewport(viewport, cacheType); - return new SearchResult(geocodes); - } - - /** {@link cgData#loadStoredInViewport(Viewport, CacheType)} */ - public SearchResult getStoredInViewport(final Viewport viewport, final CacheType cacheType) { - final Set<String> geocodes = storage.loadStoredInViewport(viewport, cacheType); - return new SearchResult(geocodes); - } - - /** {@link cgData#getAllStoredCachesCount(boolean, CacheType, int)} */ - public int getAllStoredCachesCount(final boolean detailedOnly, final CacheType cacheType) { - return storage.getAllStoredCachesCount(detailedOnly, cacheType, 0); - } - - /** {@link cgData#getAllStoredCachesCount(boolean, CacheType, int)} */ - public int getAllStoredCachesCount(final boolean detailedOnly, final CacheType cacheType, final Integer list) { - return storage.getAllStoredCachesCount(detailedOnly, cacheType, list); - } - - /** {@link cgData#getAllHistoricCachesCount()} */ - public int getAllHistoricCachesCount() { - return storage.getAllHistoricCachesCount(); - } - - /** {@link cgData#moveToList(List, int)} */ - public void markStored(List<cgCache> caches, int listId) { - storage.moveToList(caches, listId); - } - - /** {@link cgData#moveToList(List, int)} */ - public void markDropped(List<cgCache> caches) { - storage.moveToList(caches, StoredList.TEMPORARY_LIST_ID); - } - - /** {@link cgData#clearSearchedDestinations()} */ - public boolean clearSearchedDestinations() { - return storage.clearSearchedDestinations(); - } - - /** {@link cgData#saveSearchedDestination(Destination)} */ - public void saveSearchedDestination(Destination destination) { - storage.saveSearchedDestination(destination); - } - - /** {@link cgData#saveWaypoints(cgCache)} */ - public boolean saveWaypoints(final cgCache cache) { - return storage.saveWaypoints(cache); - } - - public boolean saveWaypoint(int id, String geocode, cgWaypoint waypoint) { - if (storage.saveWaypoint(id, geocode, waypoint)) { - this.removeCache(geocode, EnumSet.of(RemoveFlag.REMOVE_CACHE)); - return true; - } - return false; - } - - /** {@link cgData#deleteWaypoint(int)} */ - public boolean deleteWaypoint(int id) { - return storage.deleteWaypoint(id); - } - - public boolean saveTrackable(cgTrackable trackable) { - return storage.saveTrackable(trackable); - } - - /** {@link cgData#loadLogCounts(String)} */ - public Map<LogType, Integer> loadLogCounts(String geocode) { - return storage.loadLogCounts(geocode); - } - - /** {@link cgData#loadWaypoint(int)} */ - public cgWaypoint loadWaypoint(int id) { - return storage.loadWaypoint(id); - } - - /** {@link cgData#saveLogOffline(String, Date, LogType, String)} */ - public boolean saveLogOffline(String geocode, Date date, LogType logtype, String log) { - return storage.saveLogOffline(geocode, date, logtype, log); - } - - /** {@link cgData#loadLogOffline(String)} */ - public LogEntry loadLogOffline(String geocode) { - return storage.loadLogOffline(geocode); - } - - /** {@link cgData#clearLogOffline(String)} */ - public void clearLogOffline(String geocode) { - storage.clearLogOffline(geocode); - } - - /** {@link cgData#setVisitDate(List, long)} */ - public void saveVisitDate(String geocode) { - storage.setVisitDate(Collections.singletonList(geocode), System.currentTimeMillis()); - } - - /** {@link cgData#setVisitDate(List, long)} */ - public void clearVisitDate(List<cgCache> caches) { - ArrayList<String> geocodes = new ArrayList<String>(caches.size()); - for (cgCache cache : caches) { - geocodes.add(cache.getGeocode()); - } - storage.setVisitDate(geocodes, 0); - } - - /** {@link cgData#getLists(Resources)} */ - public List<StoredList> getLists() { - return storage.getLists(getResources()); - } - - /** {@link cgData#getList(int, Resources)} */ - public StoredList getList(int id) { - return storage.getList(id, getResources()); - } - - /** {@link cgData#createList(String)} */ - public int createList(String title) { - return storage.createList(title); - } - - /** {@link cgData#renameList(int, String)} */ - public int renameList(final int listId, final String title) { - return storage.renameList(listId, title); - } - - /** {@link cgData#removeList(int)} */ - public boolean removeList(int id) { - return storage.removeList(id); - } - - /** {@link cgData#removeSearchedDestination(Destination)} */ - public boolean removeSearchedDestinations(Destination destination) { - return storage.removeSearchedDestination(destination); - } - - /** {@link cgData#moveToList(List, int)} */ - public void moveToList(List<cgCache> caches, int listId) { - storage.moveToList(caches, listId); - } - - /** {@link cgData#getCacheDescription(String)} */ - public String getCacheDescription(String geocode) { - return storage.getCacheDescription(geocode); - } - - /** {@link cgData#loadCaches} */ - public cgCache loadCache(final String geocode, final EnumSet<LoadFlag> loadFlags) { - return storage.loadCache(geocode, loadFlags); - } - - /** {@link cgData#loadCaches} */ - public Set<cgCache> loadCaches(final Set<String> geocodes, final EnumSet<LoadFlag> loadFlags) { - return storage.loadCaches(geocodes, loadFlags); - } - - /** - * Update a cache in the DB or in the CacheCace depending on it's storage location - * - * {@link cgData#saveCache} - */ - public boolean updateCache(cgCache cache) { - return saveCache(cache, cache.getStorageLocation().contains(StorageLocation.DATABASE) ? LoadFlags.SAVE_ALL : EnumSet.of(SaveFlag.SAVE_CACHE)); - } - - /** {@link cgData#saveCache} */ - public boolean saveCache(cgCache cache, EnumSet<LoadFlags.SaveFlag> saveFlags) { - return storage.saveCache(cache, saveFlags); - } - - /** {@link cgData#removeCache} */ - public void removeCache(String geocode, EnumSet<LoadFlags.RemoveFlag> removeFlags) { - storage.removeCache(geocode, removeFlags); - } - - /** {@link cgData#removeCaches} */ - public void removeCaches(final Set<String> geocodes, EnumSet<LoadFlags.RemoveFlag> removeFlags) { - storage.removeCaches(geocodes, removeFlags); - } - - public Set<cgWaypoint> getWaypointsInViewport(final Viewport viewport, boolean excludeMine, boolean excludeDisabled, CacheType type) { - return storage.loadWaypoints(viewport, excludeMine, excludeDisabled, type); - } - public boolean isLiveMapHintShown() { return liveMapHintShown; } @@ -485,20 +182,4 @@ public class cgeoapplication extends Application { liveMapHintShown = true; } - public String[] getTrackableCodes() { - return storage.getTrackableCodes(); - } - - public List<LogEntry> loadLogs(final String geocode) { - return storage.loadLogs(geocode); - } - - public List<String> loadAttributes(final String geocode) { - return storage.loadAttributes(geocode); - } - - public List<cgWaypoint> loadWaypoints(final String geocode) { - return storage.loadWaypoints(geocode); - } - } diff --git a/main/src/cgeo/geocaching/cgeocaches.java b/main/src/cgeo/geocaching/cgeocaches.java index 688af99..f7a67ba 100644 --- a/main/src/cgeo/geocaching/cgeocaches.java +++ b/main/src/cgeo/geocaching/cgeocaches.java @@ -3,9 +3,12 @@ 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; import cgeo.geocaching.apps.cachelist.CacheListAppFactory; +import cgeo.geocaching.connector.ConnectorFactory; +import cgeo.geocaching.connector.capability.ISearchByCenter; import cgeo.geocaching.connector.gc.AbstractSearchThread; import cgeo.geocaching.connector.gc.GCParser; import cgeo.geocaching.connector.gc.SearchHandler; @@ -28,6 +31,8 @@ import cgeo.geocaching.sorting.EventDateComparator; import cgeo.geocaching.sorting.VisitComparator; import cgeo.geocaching.ui.CacheListAdapter; import cgeo.geocaching.ui.LoggingUI; +import cgeo.geocaching.ui.WeakReferenceHandler; +import cgeo.geocaching.utils.DateUtils; import cgeo.geocaching.utils.GeoDirHandler; import cgeo.geocaching.utils.Log; import cgeo.geocaching.utils.RunnableWithArgument; @@ -60,18 +65,13 @@ import android.widget.TextView; import java.util.ArrayList; import java.util.Collections; +import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; -public class cgeocaches extends AbstractListActivity { +public class cgeocaches extends AbstractListActivity implements FilteredActivity { - private static final String EXTRAS_USERNAME = "username"; - private static final String EXTRAS_ADDRESS = "address"; - private static final String EXTRAS_SEARCH = "search"; - private static final String EXTRAS_KEYWORD = "keyword"; - private static final String EXTRAS_LIST_TYPE = "type"; - private static final String EXTRAS_COORDS = "coords"; private static final int MAX_LIST_ITEMS = 1000; private static final int MENU_REFRESH_STORED = 2; private static final int MENU_CACHE_DETAILS = 4; @@ -98,6 +98,8 @@ public class cgeocaches extends AbstractListActivity { private static final int MENU_NAVIGATION = 69; private static final int MENU_STORE_CACHE = 73; private static final int MENU_FILTER = 74; + private static final int MENU_DELETE_EVENTS = 75; + private static final int MENU_CLEAR_OFFLINE_LOGS = 76; private static final int MSG_DONE = -1; private static final int MSG_RESTART_GEO_AND_DIR = -2; @@ -107,7 +109,7 @@ public class cgeocaches extends AbstractListActivity { private Geopoint coords = null; private SearchResult search = null; /** The list of shown caches shared with Adapter. Don't manipulate outside of main thread only with Handler */ - private final List<cgCache> cacheList = new ArrayList<cgCache>(); + private final List<Geocache> cacheList = new ArrayList<Geocache>(); private CacheListAdapter adapter = null; private LayoutInflater inflater = null; private View listFooter = null; @@ -124,11 +126,7 @@ public class cgeocaches extends AbstractListActivity { @Override public void updateGeoData(final IGeoData geo) { - if (adapter == null) { - return; - } - - if (geo.getCoords() != null) { + if (geo.getCoords() != null) { adapter.setActualCoordinates(geo.getCoords()); } if (!Settings.isUseCompass() || geo.getSpeed() > 5) { // use GPS when speed is higher than 18 km/h @@ -138,7 +136,7 @@ public class cgeocaches extends AbstractListActivity { @Override public void updateDirection(final float direction) { - if (adapter == null || !Settings.isLiveList()) { + if (!Settings.isLiveList()) { return; } @@ -156,57 +154,42 @@ public class cgeocaches extends AbstractListActivity { */ private MenuItem navigationMenu; - private Handler loadCachesHandler = new Handler() { - - @Override - public void handleMessage(Message msg) { - try { - setAdapter(); - - updateTitle(); - - setDateComparatorForEventList(); - - showFooterMoreCaches(); + public void handleCachesLoaded() { + try { + setAdapter(); - if (search != null && search.getError() == StatusCode.UNAPPROVED_LICENSE) { - AlertDialog.Builder dialog = new AlertDialog.Builder(cgeocaches.this); - dialog.setTitle(res.getString(R.string.license)); - dialog.setMessage(res.getString(R.string.err_license)); - dialog.setCancelable(true); - dialog.setNegativeButton(res.getString(R.string.license_dismiss), new DialogInterface.OnClickListener() { + updateTitle(); - @Override - public void onClick(DialogInterface dialog, int id) { - Cookies.clearCookies(); - dialog.cancel(); - } - }); - dialog.setPositiveButton(res.getString(R.string.license_show), new DialogInterface.OnClickListener() { + setDateComparatorForEventList(); - @Override - public void onClick(DialogInterface dialog, int id) { - Cookies.clearCookies(); - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.geocaching.com/software/agreement.aspx?ID=0"))); - } - }); + showFooterMoreCaches(); - 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) + '.'); + if (search != null && search.getError() == StatusCode.UNAPPROVED_LICENSE) { + 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); + dialog.setNegativeButton(res.getString(R.string.license_dismiss), new DialogInterface.OnClickListener() { - hideLoading(); - showProgress(false); + @Override + public void onClick(DialogInterface dialog, int id) { + Cookies.clearCookies(); + dialog.cancel(); + } + }); + dialog.setPositiveButton(res.getString(R.string.license_show), new DialogInterface.OnClickListener() { - finish(); - return; - } + @Override + public void onClick(DialogInterface dialog, int id) { + Cookies.clearCookies(); + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.geocaching.com/software/agreement.aspx?ID=0"))); + } + }); - setAdapterCurrentCoordinates(false); - } catch (Exception e) { - showToast(res.getString(R.string.err_detail_cache_find_any)); - Log.e("cgeocaches.loadCachesHandler: " + e.toString()); + 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) + '.'); hideLoading(); showProgress(false); @@ -215,29 +198,52 @@ public class cgeocaches extends AbstractListActivity { return; } - try { - hideLoading(); - showProgress(false); - } catch (Exception e2) { - Log.e("cgeocaches.loadCachesHandler.2: " + e2.toString()); - } + setAdapterCurrentCoordinates(false); + } catch (Exception e) { + showToast(res.getString(R.string.err_detail_cache_find_any)); + Log.e("cgeocaches.loadCachesHandler", e); - if (adapter != null) { - adapter.setSelectMode(false); + hideLoading(); + showProgress(false); + + finish(); + return; + } + + try { + hideLoading(); + showProgress(false); + } catch (Exception e2) { + Log.e("cgeocaches.loadCachesHandler.2", e2); + } + + adapter.setSelectMode(false); + } + + private Handler loadCachesHandler = new LoadCachesHandler(this); + + private static class LoadCachesHandler extends WeakReferenceHandler<cgeocaches> { + + protected LoadCachesHandler(cgeocaches activity) { + super(activity); + } + + @Override + public void handleMessage(Message msg) { + final cgeocaches activity = getActivity(); + if (activity == null) { + return; } + activity.handleCachesLoaded(); } - }; + } + private Handler loadNextPageHandler = new Handler() { @Override public void handleMessage(Message msg) { try { - if (search != null) { - replaceCacheListFromSearch(); - if (adapter != null) { - adapter.reFilter(); - } - } + replaceCacheListFromSearch(); setAdapter(); updateTitle(); @@ -258,33 +264,45 @@ public class cgeocaches extends AbstractListActivity { setAdapterCurrentCoordinates(false); } catch (Exception e) { showToast(res.getString(R.string.err_detail_cache_find_next)); - Log.e("cgeocaches.loadNextPageHandler: " + e.toString()); + Log.e("cgeocaches.loadNextPageHandler", e); } hideLoading(); showProgress(false); - if (adapter != null) { - adapter.setSelectMode(false); - } + adapter.setSelectMode(false); } }; - private Set<cgCache> cachesFromSearchResult; /** - * Loads the caches and fills the cachelist + * Loads the caches and fills the {@link #cacheList} according to {@link #search} content. + * + * If {@link #search} is <code>null</code>, this does nothing. */ private void replaceCacheListFromSearch() { - if (search!=null) { - cachesFromSearchResult = search.getCachesFromSearchResult(LoadFlags.LOAD_CACHE_OR_DB); + if (search != null) { + runOnUiThread(new Runnable() { + @Override + public void run() { + cacheList.clear(); + + // The database search was moved into the UI call intentionally. If this is done before the runOnUIThread, + // then we have 2 sets of caches in memory. This can lead to OOM for huge cache lists. + final Set<Geocache> cachesFromSearchResult = search.getCachesFromSearchResult(LoadFlags.LOAD_CACHE_OR_DB); + + cacheList.addAll(cachesFromSearchResult); + adapter.reFilter(); + updateTitle(); + showFooterMoreCaches(); + } + }); } - refreshCacheListHandler.sendEmptyMessage(0); } protected void updateTitle() { ArrayList<Integer> numbers = new ArrayList<Integer>(); - if (adapter != null && adapter.isFiltered()) { + if (adapter.isFiltered()) { numbers.add(adapter.getCount()); } if (search != null) { @@ -307,9 +325,7 @@ public class cgeocaches extends AbstractListActivity { if (msg.what > -1) { cacheList.get(msg.what).setStatusChecked(false); - if (adapter != null) { - adapter.notifyDataSetChanged(); - } + adapter.notifyDataSetChanged(); int secondsElapsed = (int) ((System.currentTimeMillis() - detailProgressTime) / 1000); int minutesRemaining = ((detailTotal - detailProgress) * secondsElapsed / ((detailProgress > 0) ? detailProgress : 1) / 60); @@ -328,7 +344,7 @@ public class cgeocaches extends AbstractListActivity { startGeoAndDir(); } else { if (search != null) { - final Set<cgCache> cacheListTmp = search.getCachesFromSearchResult(LoadFlags.LOAD_CACHE_OR_DB); + final Set<Geocache> cacheListTmp = search.getCachesFromSearchResult(LoadFlags.LOAD_CACHE_OR_DB); if (CollectionUtils.isNotEmpty(cacheListTmp)) { cacheList.clear(); cacheList.addAll(cacheListTmp); @@ -353,9 +369,7 @@ public class cgeocaches extends AbstractListActivity { public void handleMessage(Message msg) { setAdapter(); - if (adapter != null) { - adapter.notifyDataSetChanged(); - } + adapter.notifyDataSetChanged(); if (msg.what == 0) { //no caches progress.setMessage(res.getString(R.string.web_import_waiting)); @@ -377,9 +391,7 @@ public class cgeocaches extends AbstractListActivity { threadWeb.kill(); } } else { - if (adapter != null) { - adapter.setSelectMode(false); - } + adapter.setSelectMode(false); replaceCacheListFromSearch(); @@ -392,9 +404,7 @@ public class cgeocaches extends AbstractListActivity { @Override public void handleMessage(Message msg) { if (msg.what != MSG_CANCEL) { - if (adapter != null) { - adapter.setSelectMode(false); - } + adapter.setSelectMode(false); refreshCurrentList(); @@ -413,9 +423,7 @@ public class cgeocaches extends AbstractListActivity { if (msg.what > -1) { progress.setProgress(detailProgress); } else { - if (adapter != null) { - adapter.setSelectMode(false); - } + adapter.setSelectMode(false); // reload history list (new LoadByHistoryThread()).start(); @@ -424,6 +432,21 @@ public class cgeocaches extends AbstractListActivity { } } }; + 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() { @Override @@ -446,9 +469,9 @@ public class cgeocaches extends AbstractListActivity { // get parameters Bundle extras = getIntent().getExtras(); if (extras != null) { - Object typeObject = extras.get(EXTRAS_LIST_TYPE); + Object typeObject = extras.get(Intents.EXTRA_LIST_TYPE); type = (typeObject instanceof CacheListType) ? (CacheListType) typeObject : CacheListType.OFFLINE; - coords = (Geopoint) extras.getParcelable(EXTRAS_COORDS); + coords = (Geopoint) extras.getParcelable(Intents.EXTRAS_COORDS); } else { extras = new Bundle(); @@ -471,7 +494,7 @@ public class cgeocaches extends AbstractListActivity { Thread threadPure; AbstractSearchThread thread; - final String username = extras.getString(EXTRAS_USERNAME); + final String username = extras.getString(Intents.EXTRA_USERNAME); switch (type) { case OFFLINE: listId = Settings.getLastList(); @@ -479,7 +502,7 @@ public class cgeocaches extends AbstractListActivity { listId = StoredList.STANDARD_LIST_ID; title = res.getString(R.string.stored_caches_button); } else { - final StoredList list = app.getList(listId); + final StoredList list = cgData.getList(listId); // list.id may be different if listId was not valid listId = list.id; title = list.title; @@ -523,7 +546,7 @@ public class cgeocaches extends AbstractListActivity { thread.start(); break; case KEYWORD: - final String keyword = extras.getString(EXTRAS_KEYWORD); + final String keyword = extras.getString(Intents.EXTRA_KEYWORD); title = keyword; setTitle(title); showProgress(true); @@ -534,7 +557,7 @@ public class cgeocaches extends AbstractListActivity { thread.start(); break; case ADDRESS: - final String address = extras.getString(EXTRAS_ADDRESS); + final String address = extras.getString(Intents.EXTRA_ADDRESS); if (StringUtils.isNotBlank(address)) { title = address; } else { @@ -577,7 +600,7 @@ public class cgeocaches extends AbstractListActivity { title = res.getString(R.string.map_map); setTitle(title); showProgress(true); - search = (SearchResult) extras.get(EXTRAS_SEARCH); + search = (SearchResult) extras.get(Intents.EXTRA_SEARCH); replaceCacheListFromSearch(); loadCachesHandler.sendMessage(Message.obtain()); break; @@ -630,10 +653,8 @@ public class cgeocaches extends AbstractListActivity { startGeoAndDir(); - if (adapter != null) { - adapter.setSelectMode(false); - setAdapterCurrentCoordinates(true); - } + adapter.setSelectMode(false); + setAdapterCurrentCoordinates(true); if (loadCachesHandler != null && search != null) { replaceCacheListFromSearch(); @@ -642,7 +663,7 @@ public class cgeocaches extends AbstractListActivity { // refresh standard list if it has changed (new caches downloaded) if (type == CacheListType.OFFLINE && listId >= StoredList.STANDARD_LIST_ID && search != null) { - SearchResult newSearch = cgeoapplication.getInstance().getBatchOfStoredCaches(true, coords, Settings.getCacheType(), listId); + SearchResult newSearch = cgData.getBatchOfStoredCaches(coords, Settings.getCacheType(), listId); if (newSearch != null && newSearch.getTotal() != search.getTotal()) { refreshCurrentList(); } @@ -660,15 +681,6 @@ public class cgeocaches extends AbstractListActivity { } @Override - public void onDestroy() { - if (adapter != null) { - adapter = null; - } - - super.onDestroy(); - } - - @Override public void onPause() { removeGeoAndDir(); @@ -691,6 +703,8 @@ public class cgeocaches extends AbstractListActivity { 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 subMenu.add(0, MENU_MOVE_TO_LIST, 0, res.getString(R.string.cache_menu_move_list)); + subMenu.add(0, MENU_DELETE_EVENTS, 0, res.getString(R.string.caches_delete_events)); + subMenu.add(0, MENU_CLEAR_OFFLINE_LOGS, 0, res.getString(R.string.caches_clear_offlinelogs)); //TODO: add submenu/AlertDialog and use R.string.gpx_import_title subMenu.add(0, MENU_IMPORT_GPX, 0, res.getString(R.string.gpx_import_title)); @@ -733,7 +747,7 @@ public class cgeocaches extends AbstractListActivity { super.onPrepareOptionsMenu(menu); try { - if (adapter != null && adapter.isSelectMode()) { + if (adapter.isSelectMode()) { menu.findItem(MENU_SWITCH_SELECT_MODE).setTitle(res.getString(R.string.caches_select_mode_exit)) .setIcon(R.drawable.ic_menu_clear_playlist); menu.findItem(MENU_INVERT_SELECTION).setVisible(true); @@ -753,9 +767,11 @@ public class cgeocaches extends AbstractListActivity { setVisible(menu, MENU_REFRESH_STORED, !isEmpty && (isConcrete || type != CacheListType.OFFLINE)); setVisible(menu, MENU_DROP_CACHES, !isEmpty); setVisible(menu, MENU_DROP_CACHES_AND_LIST, isConcrete && !isEmpty); + setVisible(menu, MENU_DELETE_EVENTS, isConcrete && !isEmpty && containsEvents()); setVisible(menu, MENU_MOVE_TO_LIST, !isEmpty); 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); @@ -784,14 +800,14 @@ public class cgeocaches extends AbstractListActivity { item.setVisible(isNonDefaultList); } - final boolean multipleLists = app.getLists().size() >= 2; + final boolean multipleLists = cgData.getLists().size() >= 2; item = menu.findItem(MENU_SWITCH_LIST); if (item != null) { item.setVisible(multipleLists); } item = menu.findItem(MENU_MOVE_TO_LIST); if (item != null) { - item.setVisible(multipleLists && !isEmpty); + item.setVisible(!isEmpty); } setMenuItemLabel(menu, MENU_REMOVE_FROM_HISTORY, R.string.cache_remove_from_history, R.string.cache_clear_history); @@ -803,6 +819,24 @@ public class cgeocaches extends AbstractListActivity { return true; } + private boolean containsEvents() { + for (Geocache cache : adapter.getCheckedOrAllCaches()) { + if (cache.isEventCache()) { + return true; + } + } + return false; + } + + private boolean containsOfflineLogs() { + for (Geocache cache : adapter.getCheckedOrAllCaches()) { + if (cache.isLogOffline()) { + return true; + } + } + return false; + } + private void setMenuItemLabel(final Menu menu, final int menuId, final int resIdSelection, final int resId) { final MenuItem menuItem = menu.findItem(menuId); if (menuItem == null) { @@ -821,13 +855,11 @@ public class cgeocaches extends AbstractListActivity { int itemId = item.getItemId(); switch (itemId) { case MENU_SWITCH_SELECT_MODE: - if (adapter != null) { - adapter.switchSelectMode(); - } + adapter.switchSelectMode(); invalidateOptionsMenuCompatible(); return true; case MENU_REFRESH_STORED: - refreshStored(); + refreshStored(adapter.getCheckedOrAllCaches()); invalidateOptionsMenuCompatible(); return true; case MENU_DROP_CACHES: @@ -854,9 +886,7 @@ public class cgeocaches extends AbstractListActivity { renameList(); return false; case MENU_INVERT_SELECTION: - if (adapter != null) { - adapter.invertSelection(); - } + adapter.invertSelection(); invalidateOptionsMenuCompatible(); return false; case MENU_SWITCH_LIST: @@ -864,20 +894,7 @@ public class cgeocaches extends AbstractListActivity { invalidateOptionsMenuCompatible(); return false; case MENU_FILTER: - new FilterUserInterface(this).selectFilter(new RunnableWithArgument<IFilter>() { - @Override - public void run(IFilter selectedFilter) { - if (selectedFilter != null) { - setFilter(selectedFilter); - } - else { - // clear filter - if (adapter != null) { - setFilter(null); - } - } - } - }); + showFilterMenu(null); return true; case MENU_SORT: new ComparatorUserInterface(this).selectComparator(adapter.getCacheComparator(), new RunnableWithArgument<CacheComparator>() { @@ -901,36 +918,76 @@ public class cgeocaches extends AbstractListActivity { moveCachesToOtherList(); invalidateOptionsMenuCompatible(); return true; + case MENU_DELETE_EVENTS: + deletePastEvents(); + invalidateOptionsMenuCompatible(); + return true; + case MENU_CLEAR_OFFLINE_LOGS: + clearOfflineLogs(); + invalidateOptionsMenuCompatible(); + return true; + default: + return CacheListAppFactory.onMenuItemSelected(item, cacheList, this, search); } + } - return CacheListAppFactory.onMenuItemSelected(item, cacheList, this, search); + 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()) { + if (cache.isEventCache()) { + final Date eventDate = cache.getHiddenDate(); + if (DateUtils.daysSince(eventDate.getTime()) > 0) { + deletion.add(cache); + } + } + } + new DropDetailsThread(dropDetailsHandler, deletion).start(); + } + + public void clearOfflineLogs() { + progress.show(this, null, res.getString(R.string.caches_clear_offlinelogs_progress), true, clearOfflineLogsHandler.obtainMessage(MSG_CANCEL)); + new ClearOfflineLogsThread(clearOfflineLogsHandler).start(); + } + + /** + * called from the filter bar view + */ + @Override + public void showFilterMenu(final View view) { + new FilterUserInterface(this).selectFilter(new RunnableWithArgument<IFilter>() { + @Override + public void run(IFilter selectedFilter) { + if (selectedFilter != null) { + setFilter(selectedFilter); + } + else { + // clear filter + setFilter(null); + } + } + }); } private void setComparator(final CacheComparator comparator) { - if (adapter != null) { - adapter.setComparator(comparator); - } + adapter.setComparator(comparator); } @Override public void onCreateContextMenu(final ContextMenu menu, final View view, final ContextMenu.ContextMenuInfo info) { super.onCreateContextMenu(menu, view, info); - if (adapter == null) { - return; - } - AdapterContextMenuInfo adapterInfo = null; try { adapterInfo = (AdapterContextMenuInfo) info; } catch (Exception e) { - Log.w("cgeocaches.onCreateContextMenu: " + e.toString()); + Log.w("cgeocaches.onCreateContextMenu", e); } if (adapterInfo == null || adapterInfo.position >= adapter.getCount()) { return; } - final cgCache cache = adapter.getItem(adapterInfo.position); + final Geocache cache = adapter.getItem(adapterInfo.position); menu.setHeaderTitle(StringUtils.defaultIfBlank(cache.getName(), cache.getGeocode())); @@ -944,10 +1001,7 @@ public class cgeocaches extends AbstractListActivity { } if (cache.isOffline()) { menu.add(0, MENU_DROP_CACHE, 0, res.getString(R.string.cache_offline_drop)); - final List<StoredList> cacheLists = app.getLists(); - if (cacheLists.size() > 1) { - menu.add(0, MENU_MOVE_TO_LIST, 0, res.getString(R.string.cache_menu_move_list)); - } + 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)); } else { @@ -960,12 +1014,12 @@ public class cgeocaches extends AbstractListActivity { @Override public void run(Integer newListId) { - app.moveToList(adapter.getCheckedOrAllCaches(), newListId); + cgData.moveToList(adapter.getCheckedOrAllCaches(), newListId); adapter.setSelectMode(false); refreshCurrentList(); } - }, true); + }, true, listId); } @Override @@ -983,10 +1037,10 @@ public class cgeocaches extends AbstractListActivity { try { adapterInfo = (AdapterContextMenuInfo) info; } catch (Exception e) { - Log.w("cgeocaches.onContextItemSelected: " + e.toString()); + Log.w("cgeocaches.onContextItemSelected", e); } - final cgCache cache = adapterInfo != null ? getCacheFromAdapter(adapterInfo) : null; + final Geocache cache = adapterInfo != null ? getCacheFromAdapter(adapterInfo) : null; // just in case the list got resorted while we are executing this code if (cache == null) { @@ -1003,8 +1057,8 @@ public class cgeocaches extends AbstractListActivity { break; case MENU_CACHE_DETAILS: final Intent cachesIntent = new Intent(this, CacheDetailActivity.class); - cachesIntent.putExtra("geocode", cache.getGeocode().toUpperCase()); - cachesIntent.putExtra("name", cache.getName()); + cachesIntent.putExtra(Intents.EXTRA_GEOCODE, cache.getGeocode()); + cachesIntent.putExtra(Intents.EXTRA_NAME, cache.getName()); startActivity(cachesIntent); break; case MENU_DROP_CACHE: @@ -1021,15 +1075,14 @@ public class cgeocaches extends AbstractListActivity { @Override public void run(Integer newListId) { - app.moveToList(Collections.singletonList(cache), newListId); + cgData.moveToList(Collections.singletonList(cache), newListId); adapter.setSelectMode(false); refreshCurrentList(); } - }, true); + }, true, listId); break; case MENU_STORE_CACHE: - //FIXME: this must use the same handler like in the CacheDetailActivity. Will be done by moving the handler into the store method. - cache.store(null); + refreshStored(Collections.singletonList(cache)); break; case MENU_EXPORT: ExportFactory.showExportMenu(Collections.singletonList(cache), this); @@ -1052,8 +1105,8 @@ public class cgeocaches extends AbstractListActivity { * an adapterInfo * @return the pointed cache */ - private cgCache getCacheFromAdapter(final AdapterContextMenuInfo adapterInfo) { - final cgCache cache = adapter.getItem(adapterInfo.position); + private Geocache getCacheFromAdapter(final AdapterContextMenuInfo adapterInfo) { + final Geocache cache = adapter.getItem(adapterInfo.position); if (cache.getGeocode().equalsIgnoreCase(contextMenuGeocode)) { return cache; } @@ -1062,24 +1115,19 @@ public class cgeocaches extends AbstractListActivity { } private boolean setFilter(IFilter filter) { - if (adapter != null) { - adapter.setFilter(filter); - prepareFilterBar(); - updateTitle(); - invalidateOptionsMenuCompatible(); - return true; - } - return false; + adapter.setFilter(filter); + prepareFilterBar(); + updateTitle(); + invalidateOptionsMenuCompatible(); + return true; } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { - if (adapter != null) { - if (adapter.isSelectMode()) { - adapter.setSelectMode(false); - return true; - } + if (adapter.isSelectMode()) { + adapter.setSelectMode(false); + return true; } } return super.onKeyDown(keyCode, event); @@ -1157,7 +1205,7 @@ public class cgeocaches extends AbstractListActivity { } private void importGpx() { - cgeogpxes.startSubActivity(this, listId); + GpxFileListActivity.startSubActivity(this, listId); } @Override @@ -1166,8 +1214,27 @@ public class cgeocaches extends AbstractListActivity { refreshCurrentList(); } - public void refreshStored() { - detailTotal = adapter.getCheckedOrAllCount(); + public void refreshStored(final List<Geocache> caches) { + detailTotal = caches.size(); + if (detailTotal == 0) { + return; + } + + if (Settings.getChooseList() && type != CacheListType.OFFLINE) { + // let user select list to store cache in + new StoredList.UserInterface(this).promptForListSelection(R.string.list_title, + new RunnableWithArgument<Integer>() { + @Override + public void run(final Integer selectedListId) { + refreshStored(caches, selectedListId); + } + }, true, StoredList.TEMPORARY_LIST_ID); + } else { + refreshStored(caches, this.listId); + } + } + + private void refreshStored(final List<Geocache> caches, final int storeListId) { detailProgress = 0; showProgress(false); @@ -1185,7 +1252,7 @@ public class cgeocaches extends AbstractListActivity { detailProgressTime = System.currentTimeMillis(); - threadDetails = new LoadDetailsThread(loadDetailsHandler, listId); + threadDetails = new LoadDetailsThread(loadDetailsHandler, caches, storeListId); threadDetails.start(); } @@ -1239,7 +1306,7 @@ public class cgeocaches extends AbstractListActivity { dialog.setCancelable(true); dialog.setTitle(res.getString(R.string.caches_drop_stored)); - if (adapter != null && adapter.getCheckedCount() > 0) { + if (adapter.getCheckedCount() > 0) { dialog.setMessage(res.getString(R.string.caches_drop_selected_ask)); } else { dialog.setMessage(res.getString(R.string.caches_drop_all_ask)); @@ -1269,7 +1336,7 @@ public class cgeocaches extends AbstractListActivity { public void dropSelected() { progress.show(this, null, res.getString(R.string.caches_drop_progress), true, dropDetailsHandler.obtainMessage(MSG_CANCEL)); - new DropDetailsThread(dropDetailsHandler).start(); + new DropDetailsThread(dropDetailsHandler, adapter.getCheckedOrAllCaches()).start(); } private class LoadByOfflineThread extends Thread { @@ -1283,7 +1350,7 @@ public class cgeocaches extends AbstractListActivity { @Override public void run() { - search = cgeoapplication.getInstance().getBatchOfStoredCaches(true, coords, Settings.getCacheType(), listId); + search = cgData.getBatchOfStoredCaches(coords, Settings.getCacheType(), listId); replaceCacheListFromSearch(); loadCachesHandler.sendMessage(Message.obtain()); } @@ -1292,7 +1359,7 @@ public class cgeocaches extends AbstractListActivity { private class LoadByHistoryThread extends Thread { @Override public void run() { - search = cgeoapplication.getInstance().getHistoryOfCaches(true, coords != null ? Settings.getCacheType() : CacheType.ALL); + search = cgData.getHistoryOfCaches(true, coords != null ? Settings.getCacheType() : CacheType.ALL); replaceCacheListFromSearch(); loadCachesHandler.sendMessage(Message.obtain()); } @@ -1319,7 +1386,17 @@ public class cgeocaches extends AbstractListActivity { @Override public void runSearch() { + search = GCParser.searchByCoords(coords, Settings.getCacheType(), Settings.isShowCaptcha()); + + for (ISearchByCenter centerConn : ConnectorFactory.getSearchByCenterConnectors()) { + if (centerConn.isActivated()) { + SearchResult temp = centerConn.searchByCenter(coords); + if (temp != null) { + search.addGeocodes(temp.getGeocodes()); + } + } + } replaceCacheListFromSearch(); } } @@ -1394,11 +1471,11 @@ public class cgeocaches extends AbstractListActivity { final private int listIdLD; private volatile boolean needToStop = false; private long last = 0L; - final private List<cgCache> selected; + final private List<Geocache> caches; - public LoadDetailsThread(Handler handlerIn, int listId) { + public LoadDetailsThread(Handler handlerIn, List<Geocache> caches, int listId) { handler = handlerIn; - selected = adapter.getCheckedOrAllCaches(); + this.caches = caches; // in case of online lists, set the list id to the standard list this.listIdLD = Math.max(listId, StoredList.STANDARD_LIST_ID); @@ -1412,8 +1489,8 @@ public class cgeocaches extends AbstractListActivity { public void run() { removeGeoAndDir(); - final List<cgCache> cachesWithStaticMaps = new ArrayList<cgCache>(selected.size()); - for (cgCache cache : selected) { + final List<Geocache> cachesWithStaticMaps = new ArrayList<Geocache>(this.caches.size()); + for (Geocache cache : this.caches) { if (Settings.isStoreOfflineMaps() && cache.hasStaticMap()) { cachesWithStaticMaps.add(cache); continue; @@ -1425,7 +1502,7 @@ public class cgeocaches extends AbstractListActivity { } } - for (cgCache cache : cachesWithStaticMaps) { + for (Geocache cache : cachesWithStaticMaps) { if (!refreshCache(cache)) { break; } @@ -1443,7 +1520,7 @@ public class cgeocaches extends AbstractListActivity { * @return * <code>false</code> if the storing was interrupted, <code>true</code> otherwise */ - private boolean refreshCache(cgCache cache) { + private boolean refreshCache(Geocache cache) { try { if (needToStop) { throw new InterruptedException("Stopped storing process."); @@ -1458,7 +1535,7 @@ public class cgeocaches extends AbstractListActivity { Log.i("Waiting for next cache " + delay + " ms"); } catch (Exception e) { - Log.e("cgeocaches.LoadDetailsThread.sleep: " + e.toString()); + Log.e("cgeocaches.LoadDetailsThread.sleep", e); } } @@ -1476,7 +1553,7 @@ public class cgeocaches extends AbstractListActivity { Log.i(e.getMessage()); return false; } catch (Exception e) { - Log.e("cgeocaches.LoadDetailsThread: " + e.toString()); + Log.e("cgeocaches.LoadDetailsThread", e); } last = System.currentTimeMillis(); @@ -1501,15 +1578,14 @@ public class cgeocaches extends AbstractListActivity { @Override public void run() { - int ret = MSG_DONE; removeGeoAndDir(); int delay = -1; int times = 0; - while (!needToStop && times < 3 * 60 / 5) // maximum: 3 minutes, every 5 seconds - { + int ret = MSG_DONE; + while (!needToStop && times < 3 * 60 / 5) { // maximum: 3 minutes, every 5 seconds //download new code String deviceCode = Settings.getWebDeviceCode(); if (deviceCode == null) { @@ -1525,7 +1601,7 @@ public class cgeocaches extends AbstractListActivity { handler.sendMessage(handler.obtainMessage(1, response)); yield(); - cgCache.storeCache(null, response, listIdLFW, false, null); + Geocache.storeCache(null, response, listIdLFW, false, null); handler.sendMessage(handler.obtainMessage(2, response)); yield(); @@ -1549,8 +1625,7 @@ public class cgeocaches extends AbstractListActivity { try { yield(); - if (delay == 0) - { + if (delay == 0) { sleep(5000); //No caches 5s times++; } else { @@ -1558,7 +1633,7 @@ public class cgeocaches extends AbstractListActivity { times = 0; } } catch (InterruptedException e) { - Log.e("cgeocaches.LoadFromWebThread.sleep: " + e.toString()); + Log.e("cgeocaches.LoadFromWebThread.sleep", e); } } @@ -1571,17 +1646,17 @@ public class cgeocaches extends AbstractListActivity { private class DropDetailsThread extends Thread { final private Handler handler; - final private List<cgCache> selected; + final private List<Geocache> selected; - public DropDetailsThread(Handler handlerIn) { + public DropDetailsThread(Handler handlerIn, List<Geocache> selectedIn) { handler = handlerIn; - selected = adapter.getCheckedOrAllCaches(); + selected = selectedIn; } @Override public void run() { removeGeoAndDir(); - app.markDropped(selected); + cgData.markDropped(selected); handler.sendEmptyMessage(MSG_DONE); startGeoAndDir(); @@ -1591,7 +1666,7 @@ public class cgeocaches extends AbstractListActivity { private class RemoveFromHistoryThread extends Thread { final private Handler handler; - final private List<cgCache> selected; + final private List<Geocache> selected; public RemoveFromHistoryThread(Handler handlerIn) { handler = handlerIn; @@ -1600,7 +1675,24 @@ public class cgeocaches extends AbstractListActivity { @Override public void run() { - app.clearVisitDate(selected); + cgData.clearVisitDate(selected); + handler.sendEmptyMessage(MSG_DONE); + } + } + + private class ClearOfflineLogsThread extends Thread { + + final private Handler handler; + final private List<Geocache> selected; + + public ClearOfflineLogsThread(Handler handlerIn) { + handler = handlerIn; + selected = adapter.getCheckedOrAllCaches(); + } + + @Override + public void run() { + cgData.clearLogsOffline(selected); handler.sendEmptyMessage(MSG_DONE); } } @@ -1650,7 +1742,7 @@ public class cgeocaches extends AbstractListActivity { return; } - StoredList list = app.getList(id); + StoredList list = cgData.getList(id); if (list == null) { return; } @@ -1692,33 +1784,12 @@ public class cgeocaches extends AbstractListActivity { @Override public void run() { - final List<cgCache> caches = adapter.getCheckedCaches(); - app.moveToList(caches, listId); + final List<Geocache> caches = adapter.getCheckedCaches(); + cgData.moveToList(caches, listId); handler.sendEmptyMessage(listId); } } - /** - * Handler to refresh the current list of caches. This list is shared with the Adapter and therefore must be updated - * in the UI-Thread - */ - - private Handler refreshCacheListHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - cacheList.clear(); - if (cachesFromSearchResult != null) { - cacheList.addAll(cachesFromSearchResult); - } - if (adapter != null) { - adapter.reFilter(); - } - updateTitle(); - showFooterMoreCaches(); - } - }; - - private void renameList() { new StoredList.UserInterface(this).promptForListRename(listId, new Runnable() { @@ -1730,7 +1801,7 @@ public class cgeocaches extends AbstractListActivity { } private void removeListInternal() { - if (app.removeList(listId)) { + if (cgData.removeList(listId)) { showToast(res.getString(R.string.list_dialog_remove_ok)); switchListById(StoredList.STANDARD_LIST_ID); } else { @@ -1783,18 +1854,14 @@ public class cgeocaches extends AbstractListActivity { return; } - SearchResult searchToUse = search; - // apply filter settings (if there's a filter) - if (adapter != null) { - Set<String> geocodes = new HashSet<String>(); - for (cgCache cache : adapter.getFilteredList()) { - geocodes.add(cache.getGeocode()); - } - searchToUse = new SearchResult(geocodes); + Set<String> geocodes = new HashSet<String>(); + for (Geocache cache : adapter.getFilteredList()) { + geocodes.add(cache.getGeocode()); } - int count = searchToUse.getCount(); + final SearchResult searchToUse = new SearchResult(geocodes); + final int count = searchToUse.getCount(); String mapTitle = title; if (count > 0) { mapTitle = title + " [" + count + "]"; @@ -1823,7 +1890,7 @@ public class cgeocaches extends AbstractListActivity { public static void startActivityOffline(final Context context) { final Intent cachesIntent = new Intent(context, cgeocaches.class); - cachesIntent.putExtra(EXTRAS_LIST_TYPE, CacheListType.OFFLINE); + cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.OFFLINE); context.startActivity(cachesIntent); } @@ -1832,8 +1899,8 @@ public class cgeocaches extends AbstractListActivity { return; } final Intent cachesIntent = new Intent(context, cgeocaches.class); - cachesIntent.putExtra(EXTRAS_LIST_TYPE, CacheListType.OWNER); - cachesIntent.putExtra(EXTRAS_USERNAME, userName); + cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.OWNER); + cachesIntent.putExtra(Intents.EXTRA_USERNAME, userName); context.startActivity(cachesIntent); } @@ -1850,8 +1917,8 @@ public class cgeocaches extends AbstractListActivity { return; } final Intent cachesIntent = new Intent(context, cgeocaches.class); - cachesIntent.putExtra(EXTRAS_LIST_TYPE, CacheListType.USERNAME); - cachesIntent.putExtra(EXTRAS_USERNAME, userName); + cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.USERNAME); + cachesIntent.putExtra(Intents.EXTRA_USERNAME, userName); context.startActivity(cachesIntent); } @@ -1879,7 +1946,7 @@ public class cgeocaches extends AbstractListActivity { private void setDateComparatorForEventList() { if (CollectionUtils.isNotEmpty(cacheList)) { boolean eventsOnly = true; - for (cgCache cache : cacheList) { + for (Geocache cache : cacheList) { if (!cache.isEventCache()) { eventsOnly = false; break; @@ -1902,22 +1969,22 @@ public class cgeocaches extends AbstractListActivity { return; } final Intent cachesIntent = new Intent(context, cgeocaches.class); - cachesIntent.putExtra(EXTRAS_LIST_TYPE, CacheListType.NEAREST); - cachesIntent.putExtra(EXTRAS_COORDS, coordsNow); + cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.NEAREST); + cachesIntent.putExtra(Intents.EXTRAS_COORDS, coordsNow); context.startActivity(cachesIntent); } public static void startActivityHistory(Context context) { final Intent cachesIntent = new Intent(context, cgeocaches.class); - cachesIntent.putExtra(EXTRAS_LIST_TYPE, CacheListType.HISTORY); + cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.HISTORY); context.startActivity(cachesIntent); } public static void startActivityAddress(final Context context, final Geopoint coords, final String address) { final Intent addressIntent = new Intent(context, cgeocaches.class); - addressIntent.putExtra(EXTRAS_LIST_TYPE, CacheListType.ADDRESS); - addressIntent.putExtra(EXTRAS_COORDS, coords); - addressIntent.putExtra(EXTRAS_ADDRESS, address); + addressIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.ADDRESS); + addressIntent.putExtra(Intents.EXTRAS_COORDS, coords); + addressIntent.putExtra(Intents.EXTRA_ADDRESS, address); context.startActivity(addressIntent); } @@ -1926,8 +1993,8 @@ public class cgeocaches extends AbstractListActivity { return; } final Intent cachesIntent = new Intent(context, cgeocaches.class); - cachesIntent.putExtra(EXTRAS_LIST_TYPE, CacheListType.COORDINATE); - cachesIntent.putExtra(EXTRAS_COORDS, coords); + cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.COORDINATE); + cachesIntent.putExtra(Intents.EXTRAS_COORDS, coords); context.startActivity(cachesIntent); } @@ -1945,15 +2012,15 @@ public class cgeocaches extends AbstractListActivity { return; } final Intent cachesIntent = new Intent(context, cgeocaches.class); - cachesIntent.putExtra(EXTRAS_LIST_TYPE, CacheListType.KEYWORD); - cachesIntent.putExtra(EXTRAS_KEYWORD, keyword); + cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.KEYWORD); + cachesIntent.putExtra(Intents.EXTRA_KEYWORD, keyword); context.startActivity(cachesIntent); } public static void startActivityMap(final Context context, final SearchResult search) { final Intent cachesIntent = new Intent(context, cgeocaches.class); - cachesIntent.putExtra(EXTRAS_LIST_TYPE, CacheListType.MAP); - cachesIntent.putExtra(EXTRAS_SEARCH, search); + cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.MAP); + cachesIntent.putExtra(Intents.EXTRA_SEARCH, search); context.startActivity(cachesIntent); } } diff --git a/main/src/cgeo/geocaching/cgeonavigate.java b/main/src/cgeo/geocaching/cgeonavigate.java index 64b7cd8..17c2e20 100644 --- a/main/src/cgeo/geocaching/cgeonavigate.java +++ b/main/src/cgeo/geocaching/cgeonavigate.java @@ -61,8 +61,7 @@ public class cgeonavigate extends AbstractActivity { // get parameters Bundle extras = getIntent().getExtras(); if (extras != null) { - final String geocode = extras.getString(EXTRAS_GEOCODE); - title = geocode; + title = extras.getString(EXTRAS_GEOCODE); final String name = extras.getString(EXTRAS_NAME); dstCoords = (Geopoint) extras.getParcelable(EXTRAS_COORDS); info = extras.getString(EXTRAS_CACHE_INFO); @@ -75,7 +74,7 @@ public class cgeonavigate extends AbstractActivity { } } } else { - Intent pointIntent = new Intent(this, cgeopoint.class); + Intent pointIntent = new Intent(this, NavigateAnyPointActivity.class); startActivity(pointIntent); finish(); @@ -157,7 +156,7 @@ public class cgeonavigate extends AbstractActivity { geoDirHandler.startDir(); } } else if (id == 2) { - Intent pointIntent = new Intent(this, cgeopoint.class); + Intent pointIntent = new Intent(this, NavigateAnyPointActivity.class); startActivity(pointIntent); finish(); @@ -293,7 +292,7 @@ public class cgeonavigate extends AbstractActivity { final Intent navigateIntent = new Intent(context, cgeonavigate.class); navigateIntent.putExtra(EXTRAS_COORDS, coords); - navigateIntent.putExtra(EXTRAS_GEOCODE, geocode.toUpperCase()); + navigateIntent.putExtra(EXTRAS_GEOCODE, geocode); if (null != displayedName) { navigateIntent.putExtra(EXTRAS_NAME, displayedName); } diff --git a/main/src/cgeo/geocaching/cgeotrackable.java b/main/src/cgeo/geocaching/cgeotrackable.java deleted file mode 100644 index 405581d..0000000 --- a/main/src/cgeo/geocaching/cgeotrackable.java +++ /dev/null @@ -1,575 +0,0 @@ -package cgeo.geocaching; - -import cgeo.geocaching.activity.AbstractActivity; -import cgeo.geocaching.connector.gc.GCParser; -import cgeo.geocaching.enumerations.LogType; -import cgeo.geocaching.geopoint.Units; -import cgeo.geocaching.network.HtmlImage; -import cgeo.geocaching.ui.AnchorAwareLinkMovementMethod; -import cgeo.geocaching.ui.CacheDetailsCreator; -import cgeo.geocaching.ui.Formatter; -import cgeo.geocaching.utils.BaseUtils; -import cgeo.geocaching.utils.Log; -import cgeo.geocaching.utils.UnknownTagsHandler; - -import org.apache.commons.lang3.StringUtils; - -import android.app.ProgressDialog; -import android.content.Intent; -import android.graphics.drawable.BitmapDrawable; -import android.net.Uri; -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; -import android.view.MenuItem; -import android.view.View; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.RelativeLayout; -import android.widget.TextView; - -import java.net.URLEncoder; -import java.util.ArrayList; -import java.util.Arrays; - -public class cgeotrackable extends AbstractActivity { - private static final int MENU_LOG_TOUCH = 1; - private static final int MENU_BROWSER_TRACKABLE = 2; - private cgTrackable trackable = null; - private String geocode = null; - private String name = null; - private String guid = null; - private String id = null; - private String contextMenuUser = null; - private LayoutInflater inflater = null; - private ProgressDialog waitDialog = null; - private Handler loadTrackableHandler = new Handler() { - - @Override - public void handleMessage(Message msg) { - if (trackable == null) { - if (waitDialog != null) { - waitDialog.dismiss(); - } - - if (StringUtils.isNotBlank(geocode)) { - showToast(res.getString(R.string.err_tb_find) + " " + geocode + "."); - } else { - showToast(res.getString(R.string.err_tb_find_that)); - } - - finish(); - return; - } - - try { - inflater = getLayoutInflater(); - geocode = trackable.getGeocode().toUpperCase(); - - if (StringUtils.isNotBlank(trackable.getName())) { - setTitle(Html.fromHtml(trackable.getName()).toString()); - } else { - setTitle(trackable.getName().toUpperCase()); - } - - findViewById(R.id.details_list_box).setVisibility(View.VISIBLE); - final CacheDetailsCreator details = new CacheDetailsCreator(cgeotrackable.this, (LinearLayout) findViewById(R.id.details_list)); - - // action bar icon - if (StringUtils.isNotBlank(trackable.getIconUrl())) { - final TrackableIconHandler iconHandler = new TrackableIconHandler(((TextView) findViewById(R.id.actionbar_title))); - final TrackableIconThread iconThread = new TrackableIconThread(trackable.getIconUrl(), iconHandler); - iconThread.start(); - } - - // trackable name - details.add(R.string.trackable_name, StringUtils.isNotBlank(trackable.getName()) ? Html.fromHtml(trackable.getName()).toString() : res.getString(R.string.trackable_unknown)); - - // trackable type - String tbType; - if (StringUtils.isNotBlank(trackable.getType())) { - tbType = Html.fromHtml(trackable.getType()).toString(); - } else { - tbType = res.getString(R.string.trackable_unknown); - } - details.add(R.string.trackable_type, tbType); - - // trackable geocode - details.add(R.string.trackable_code, trackable.getGeocode().toUpperCase()); - - // trackable owner - 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()); - } - - // trackable spotted - if (StringUtils.isNotBlank(trackable.getSpottedName()) || - trackable.getSpottedType() == cgTrackable.SPOTTED_UNKNOWN || - trackable.getSpottedType() == cgTrackable.SPOTTED_OWNER - ) { - boolean showTimeSpan = true; - StringBuilder text; - - if (trackable.getSpottedType() == cgTrackable.SPOTTED_CACHE) { - text = new StringBuilder(res.getString(R.string.trackable_spotted_in_cache) + ' ' + Html.fromHtml(trackable.getSpottedName()).toString()); - } else if (trackable.getSpottedType() == cgTrackable.SPOTTED_USER) { - text = new StringBuilder(res.getString(R.string.trackable_spotted_at_user) + ' ' + Html.fromHtml(trackable.getSpottedName()).toString()); - } else if (trackable.getSpottedType() == cgTrackable.SPOTTED_UNKNOWN) { - text = new StringBuilder(res.getString(R.string.trackable_spotted_unknown_location)); - } else if (trackable.getSpottedType() == cgTrackable.SPOTTED_OWNER) { - text = new StringBuilder(res.getString(R.string.trackable_spotted_owner)); - } else { - text = new StringBuilder("N/A"); - showTimeSpan = false; - } - - // days since last spotting - if (showTimeSpan && trackable.getLogs() != null) { - for (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(')'); - break; - } - } - } - - final TextView spotted = details.add(R.string.trackable_spotted, text.toString()); - spotted.setClickable(true); - if (cgTrackable.SPOTTED_CACHE == trackable.getSpottedType()) { - spotted.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View arg0) { - CacheDetailActivity.startActivityGuid(cgeotrackable.this, trackable.getSpottedGuid(), trackable.getSpottedName()); - } - }); - } else if (cgTrackable.SPOTTED_USER == trackable.getSpottedType()) { - spotted.setOnClickListener(new UserActionsListener()); - } - } - - // trackable origin - if (StringUtils.isNotBlank(trackable.getOrigin())) { - TextView origin = details.add(R.string.trackable_origin, ""); - origin.setText(Html.fromHtml(trackable.getOrigin()), TextView.BufferType.SPANNABLE); - } - - // trackable released - if (trackable.getReleased() != null) { - details.add(R.string.trackable_released, Formatter.formatDate(trackable.getReleased().getTime())); - } - - // trackable distance - if (trackable.getDistance() >= 0) { - details.add(R.string.trackable_distance, Units.getDistanceFromKilometers(trackable.getDistance())); - } - - // trackable goal - if (StringUtils.isNotBlank(trackable.getGoal())) { - findViewById(R.id.goal_box).setVisibility(View.VISIBLE); - TextView descView = (TextView) 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(AnchorAwareLinkMovementMethod.getInstance()); - } - - // trackable details - if (StringUtils.isNotBlank(trackable.getDetails())) { - findViewById(R.id.details_box).setVisibility(View.VISIBLE); - TextView descView = (TextView) 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(AnchorAwareLinkMovementMethod.getInstance()); - } - - // trackable image - if (StringUtils.isNotBlank(trackable.getImage())) { - findViewById(R.id.image_box).setVisibility(View.VISIBLE); - LinearLayout imgView = (LinearLayout) findViewById(R.id.image); - - final ImageView trackableImage = (ImageView) inflater.inflate(R.layout.trackable_image, null); - - trackableImage.setImageResource(R.drawable.image_not_loaded); - trackableImage.setClickable(true); - trackableImage.setOnClickListener(new View.OnClickListener() { - - @Override - public void onClick(View arg0) { - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(trackable.getImage()))); - } - }); - - // try to load image - final Handler handler = new Handler() { - - @Override - public void handleMessage(Message message) { - BitmapDrawable image = (BitmapDrawable) message.obj; - if (image != null) { - trackableImage.setImageDrawable((BitmapDrawable) message.obj); - } - } - }; - - new Thread() { - - @Override - public void run() { - BitmapDrawable image; - try { - HtmlImage imgGetter = new HtmlImage(geocode, true, 0, false); - - image = imgGetter.getDrawable(trackable.getImage()); - Message message = handler.obtainMessage(0, image); - handler.sendMessage(message); - } catch (Exception e) { - Log.e("cgeospoilers.onCreate.onClick.run: " + e.toString()); - } - } - }.start(); - - imgView.addView(trackableImage); - } - } catch (Exception e) { - Log.e("cgeotrackable.loadTrackableHandler: " + e.toString() + Arrays.toString(e.getStackTrace())); - } - - displayLogs(); - - if (waitDialog != null) { - waitDialog.dismiss(); - } - } - }; - - public cgeotrackable() { - super("c:geo-trackable-details"); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setTheme(); - setContentView(R.layout.trackable_detail); - setTitle(res.getString(R.string.trackable)); - - // get parameters - Bundle extras = getIntent().getExtras(); - Uri uri = getIntent().getData(); - - // try to get data from extras - if (extras != null) { - geocode = extras.getString("geocode"); - name = extras.getString("name"); - guid = extras.getString("guid"); - id = extras.getString("id"); - } - - // try to get data from URI - if (geocode == null && guid == null && id == null && uri != null) { - String uriHost = uri.getHost().toLowerCase(); - if (uriHost.contains("geocaching.com")) { - geocode = uri.getQueryParameter("tracker"); - guid = uri.getQueryParameter("guid"); - id = uri.getQueryParameter("id"); - - if (StringUtils.isNotBlank(geocode)) { - geocode = geocode.toUpperCase(); - guid = null; - id = null; - } else if (StringUtils.isNotBlank(guid)) { - geocode = null; - guid = guid.toLowerCase(); - id = null; - } else if (StringUtils.isNotBlank(id)) { - geocode = null; - guid = null; - id = id.toLowerCase(); - } else { - showToast(res.getString(R.string.err_tb_details_open)); - finish(); - return; - } - } else if (uriHost.contains("coord.info")) { - String uriPath = uri.getPath().toLowerCase(); - if (uriPath != null && uriPath.startsWith("/tb")) { - geocode = uriPath.substring(1).toUpperCase(); - guid = null; - id = null; - } else { - showToast(res.getString(R.string.err_tb_details_open)); - finish(); - return; - } - } - } - - // no given data - if (geocode == null && guid == null && id == null) { - showToast(res.getString(R.string.err_tb_display)); - finish(); - return; - } - - String message; - if (StringUtils.isNotBlank(name)) { - message = Html.fromHtml(name).toString(); - } else if (StringUtils.isNotBlank(geocode)) { - message = geocode.toUpperCase(); - } else { - message = res.getString(R.string.trackable); - } - waitDialog = ProgressDialog.show(this, message, res.getString(R.string.trackable_details_loading), true, true); - - LoadTrackableThread thread = new LoadTrackableThread(loadTrackableHandler, geocode, guid, id); - thread.start(); - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo info) { - 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); - - 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))) { - contextMenuUser = trackable.getSpottedName(); - } - } - - menu.setHeaderTitle(res.getString(R.string.user_menu_title) + " " + contextMenuUser); - menu.add(viewId, 1, 0, res.getString(R.string.user_menu_view_hidden)); - menu.add(viewId, 2, 0, res.getString(R.string.user_menu_view_found)); - menu.add(viewId, 3, 0, res.getString(R.string.user_menu_open_browser)); - } - - @Override - public boolean onContextItemSelected(final MenuItem item) { - switch (item.getItemId()) { - case 1: - cgeocaches.startActivityOwner(this, contextMenuUser); - return true; - case 2: - cgeocaches.startActivityUserName(this, contextMenuUser); - return true; - case 3: - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.geocaching.com/profile/?u=" + URLEncoder.encode(contextMenuUser)))); - return true; - default: - return false; - } - } - - @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 - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case MENU_LOG_TOUCH: - LogTrackableActivity.startActivity(this, trackable); - return true; - case MENU_BROWSER_TRACKABLE: - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(trackable.getUrl()))); - return true; - } - return false; - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - menu.findItem(MENU_LOG_TOUCH).setEnabled(StringUtils.isNotBlank(geocode) && trackable.isLoggable()); - menu.findItem(MENU_BROWSER_TRACKABLE).setEnabled(StringUtils.isNotBlank(trackable.getUrl())); - return super.onPrepareOptionsMenu(menu); - } - - private class LoadTrackableThread extends Thread { - final private Handler handler; - final private String geocode; - final private String guid; - final private String id; - - public LoadTrackableThread(Handler handlerIn, String geocodeIn, String guidIn, String idIn) { - handler = handlerIn; - geocode = geocodeIn; - guid = guidIn; - id = idIn; - } - - @Override - public void run() { - // for non TB trackables, we should just use what we have in the database - trackable = cgeoapplication.getInstance().getTrackableByGeocode(geocode); - - if ((trackable == null || trackable.isLoggable()) && !StringUtils.startsWithIgnoreCase(geocode, "GK")) { - trackable = GCParser.searchTrackable(geocode, guid, id); - } - handler.sendMessage(Message.obtain()); - } - } - - private void displayLogs() { - // trackable logs - LinearLayout listView = (LinearLayout) findViewById(R.id.log_list); - listView.removeAllViews(); - - RelativeLayout rowView; - - if (trackable != null && trackable.getLogs() != null) { - for (LogEntry log : trackable.getLogs()) { - rowView = (RelativeLayout) inflater.inflate(R.layout.trackable_logs_item, null); - - if (log.date > 0) { - ((TextView) rowView.findViewById(R.id.added)).setText(Formatter.formatShortDate(log.date)); - } - - ((TextView) rowView.findViewById(R.id.type)).setText(log.type.getL10n()); - ((TextView) rowView.findViewById(R.id.author)).setText(Html.fromHtml(log.author), TextView.BufferType.SPANNABLE); - - if (StringUtils.isBlank(log.cacheName)) { - rowView.findViewById(R.id.location).setVisibility(View.GONE); - } else { - ((TextView) rowView.findViewById(R.id.location)).setText(Html.fromHtml(log.cacheName)); - final String cacheGuid = log.cacheGuid; - final String cacheName = log.cacheName; - rowView.findViewById(R.id.location).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View arg0) { - CacheDetailActivity.startActivityGuid(cgeotrackable.this, cacheGuid, Html.fromHtml(cacheName).toString()); - } - }); - } - - TextView logView = (TextView) rowView.findViewById(R.id.log); - logView.setMovementMethod(LinkMovementMethod.getInstance()); - - String logText = log.log; - if (BaseUtils.containsHtml(logText)) { - logText = log.getDisplayText(); - logView.setText(Html.fromHtml(logText, new HtmlImage(null, false, StoredList.TEMPORARY_LIST_ID, false), null), TextView.BufferType.SPANNABLE); - } - else { - logView.setText(logText); - } - - // add LogImages - LinearLayout logLayout = (LinearLayout) rowView.findViewById(R.id.log_layout); - - if (log.hasLogImages()) { - - final ArrayList<cgImage> logImages = new ArrayList<cgImage>(log.getLogImages()); - - final View.OnClickListener listener = new View.OnClickListener() { - @Override - public void onClick(View v) { - ImagesActivity.startActivityLogImages(cgeotrackable.this, trackable.getGeocode(), logImages); - } - }; - - 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); - } - - rowView.findViewById(R.id.author).setOnClickListener(new UserActionsListener()); - listView.addView(rowView); - } - - if (trackable.getLogs().size() > 0) { - findViewById(R.id.log_box).setVisibility(View.VISIBLE); - } - } - } - - private class UserActionsListener implements View.OnClickListener { - - @Override - public void onClick(View view) { - if (view == null) { - return; - } - - try { - registerForContextMenu(view); - openContextMenu(view); - } catch (Exception e) { - Log.e("cgeotrackable.UserActionsListener.onClick ", e); - } - } - } - - private class TrackableIconThread extends Thread { - final private String url; - final private Handler handler; - - public TrackableIconThread(String urlIn, Handler handlerIn) { - url = urlIn; - handler = handlerIn; - } - - @Override - public void run() { - if (url == null || handler == null) { - return; - } - - BitmapDrawable image; - try { - HtmlImage imgGetter = new HtmlImage(trackable.getGeocode(), false, 0, false); - - image = imgGetter.getDrawable(url); - Message message = handler.obtainMessage(0, image); - handler.sendMessage(message); - } catch (Exception e) { - Log.e("cgeotrackable.TrackableIconThread.run: " + e.toString()); - } - } - } - - private static class TrackableIconHandler extends Handler { - final private TextView view; - - public TrackableIconHandler(TextView viewIn) { - view = viewIn; - } - - @Override - public void handleMessage(Message message) { - final BitmapDrawable image = (BitmapDrawable) message.obj; - if (image != null && view != null) { - image.setBounds(0, 0, view.getHeight(), view.getHeight()); - view.setCompoundDrawables(image, null, null, null); - } - } - } - - public static void startActivity(final AbstractActivity fromContext, - final String guid, final String geocode, final String name) { - final Intent trackableIntent = new Intent(fromContext, cgeotrackable.class); - trackableIntent.putExtra("guid", guid); - trackableIntent.putExtra("geocode", geocode); - trackableIntent.putExtra("name", name); - fromContext.startActivity(trackableIntent); - } -} diff --git a/main/src/cgeo/geocaching/compatibility/AndroidLevel11Dummy.java b/main/src/cgeo/geocaching/compatibility/AndroidLevel11Emulation.java index 9c2bb8c..6a23ed5 100644 --- a/main/src/cgeo/geocaching/compatibility/AndroidLevel11Dummy.java +++ b/main/src/cgeo/geocaching/compatibility/AndroidLevel11Emulation.java @@ -3,9 +3,9 @@ package cgeo.geocaching.compatibility; import android.app.Activity; /** - * dummy class which has no functionality in the level 11 API + * implement level 11 API using older methods */ -public class AndroidLevel11Dummy implements AndroidLevel11Interface { +public class AndroidLevel11Emulation implements AndroidLevel11Interface { @Override public void invalidateOptionsMenu(final Activity activity) { diff --git a/main/src/cgeo/geocaching/compatibility/AndroidLevel13.java b/main/src/cgeo/geocaching/compatibility/AndroidLevel13.java new file mode 100644 index 0000000..4eac205 --- /dev/null +++ b/main/src/cgeo/geocaching/compatibility/AndroidLevel13.java @@ -0,0 +1,31 @@ +package cgeo.geocaching.compatibility; + +import cgeo.geocaching.cgeoapplication; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Point; +import android.view.WindowManager; + +@TargetApi(value = 13) +public class AndroidLevel13 implements AndroidLevel13Interface { + + @Override + public int getDisplayWidth() { + return getDisplaySize().x; + } + + @Override + public Point getDisplaySize() { + Point dimensions = new Point(); + ((WindowManager) cgeoapplication.getInstance().getSystemService(Context.WINDOW_SERVICE)) + .getDefaultDisplay().getSize(dimensions); + return dimensions; + } + + @Override + public int getDisplayHeight() { + return getDisplaySize().y; + } + +} diff --git a/main/src/cgeo/geocaching/compatibility/AndroidLevel13Emulation.java b/main/src/cgeo/geocaching/compatibility/AndroidLevel13Emulation.java new file mode 100644 index 0000000..2257d83 --- /dev/null +++ b/main/src/cgeo/geocaching/compatibility/AndroidLevel13Emulation.java @@ -0,0 +1,33 @@ +package cgeo.geocaching.compatibility; + +import cgeo.geocaching.cgeoapplication; + +import android.content.Context; +import android.graphics.Point; +import android.view.Display; +import android.view.WindowManager; + +@SuppressWarnings("deprecation") +public class AndroidLevel13Emulation implements AndroidLevel13Interface { + + @Override + public int getDisplayWidth() { + return getDisplay().getWidth(); + } + + @Override + public int getDisplayHeight() { + return getDisplay().getHeight(); + } + + @Override + public Point getDisplaySize() { + final Display display = getDisplay(); + return new Point(display.getWidth(), display.getHeight()); + } + + private static Display getDisplay() { + return ((WindowManager) cgeoapplication.getInstance().getSystemService(Context.WINDOW_SERVICE)) + .getDefaultDisplay(); + } +} diff --git a/main/src/cgeo/geocaching/compatibility/AndroidLevel13Interface.java b/main/src/cgeo/geocaching/compatibility/AndroidLevel13Interface.java new file mode 100644 index 0000000..f4e1975 --- /dev/null +++ b/main/src/cgeo/geocaching/compatibility/AndroidLevel13Interface.java @@ -0,0 +1,11 @@ +package cgeo.geocaching.compatibility; + +import android.graphics.Point; + +public interface AndroidLevel13Interface { + int getDisplayWidth(); + + int getDisplayHeight(); + + Point getDisplaySize(); +} diff --git a/main/src/cgeo/geocaching/compatibility/AndroidLevel8.java b/main/src/cgeo/geocaching/compatibility/AndroidLevel8.java index ea5a795..e250934 100644 --- a/main/src/cgeo/geocaching/compatibility/AndroidLevel8.java +++ b/main/src/cgeo/geocaching/compatibility/AndroidLevel8.java @@ -5,7 +5,11 @@ import cgeo.geocaching.utils.Log; import android.annotation.TargetApi; import android.app.Activity; import android.app.backup.BackupManager; +import android.os.Environment; import android.view.Display; +import android.view.Surface; + +import java.io.File; @TargetApi(8) public class AndroidLevel8 implements AndroidLevel8Interface { @@ -21,4 +25,29 @@ public class AndroidLevel8 implements AndroidLevel8Interface { Log.i("Requesting settings backup with settings manager"); BackupManager.dataChanged(name); } + + @Override + public int getRotationOffset(final Activity activity) { + try { + final int rotation = getRotation(activity); + if (rotation == Surface.ROTATION_90) { + return 90; + } else if (rotation == Surface.ROTATION_180) { + return 180; + } else if (rotation == Surface.ROTATION_270) { + return 270; + } + } catch (final Exception e) { + // This should never happen: IllegalArgumentException, IllegalAccessException or InvocationTargetException + Log.e("Cannot call getRotation()", e); + } + + return 0; + } + + @Override + public File getExternalPictureDir() { + return Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_PICTURES); + } } diff --git a/main/src/cgeo/geocaching/compatibility/AndroidLevel8Dummy.java b/main/src/cgeo/geocaching/compatibility/AndroidLevel8Dummy.java deleted file mode 100644 index d0ab911..0000000 --- a/main/src/cgeo/geocaching/compatibility/AndroidLevel8Dummy.java +++ /dev/null @@ -1,16 +0,0 @@ -package cgeo.geocaching.compatibility; - -import android.app.Activity; - -public class AndroidLevel8Dummy implements AndroidLevel8Interface { - - @Override - public int getRotation(final Activity activity) { - return 0; - } - - @Override - public void dataChanged(final String name) { - // do nothing - } -} diff --git a/main/src/cgeo/geocaching/compatibility/AndroidLevel8Emulation.java b/main/src/cgeo/geocaching/compatibility/AndroidLevel8Emulation.java new file mode 100644 index 0000000..996c527 --- /dev/null +++ b/main/src/cgeo/geocaching/compatibility/AndroidLevel8Emulation.java @@ -0,0 +1,39 @@ +package cgeo.geocaching.compatibility; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.res.Configuration; +import android.os.Environment; +import android.view.Display; + +import java.io.File; + +@TargetApi(value = 7) +public class AndroidLevel8Emulation implements AndroidLevel8Interface { + + @Override + public int getRotation(final Activity activity) { + return 0; + } + + @Override + public void dataChanged(final String name) { + // do nothing + } + + @Override + public int getRotationOffset(Activity activity) { + final Display display = activity.getWindowManager().getDefaultDisplay(); + final int rotation = display.getOrientation(); + if (rotation == Configuration.ORIENTATION_LANDSCAPE) { + return 90; + } + return 0; + } + + @Override + public File getExternalPictureDir() { + // Use externalStorage/Pictures as default + return new File(Environment.getExternalStorageDirectory(), "Pictures"); + } +} diff --git a/main/src/cgeo/geocaching/compatibility/AndroidLevel8Interface.java b/main/src/cgeo/geocaching/compatibility/AndroidLevel8Interface.java index b1c4f81..75998aa 100644 --- a/main/src/cgeo/geocaching/compatibility/AndroidLevel8Interface.java +++ b/main/src/cgeo/geocaching/compatibility/AndroidLevel8Interface.java @@ -2,8 +2,13 @@ package cgeo.geocaching.compatibility; import android.app.Activity; +import java.io.File; + public interface AndroidLevel8Interface { public int getRotation(final Activity activity); public void dataChanged(final String name); + public int getRotationOffset(final Activity activity); + + public File getExternalPictureDir(); }
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/compatibility/Compatibility.java b/main/src/cgeo/geocaching/compatibility/Compatibility.java index 0821655..d846bda 100644 --- a/main/src/cgeo/geocaching/compatibility/Compatibility.java +++ b/main/src/cgeo/geocaching/compatibility/Compatibility.java @@ -8,13 +8,13 @@ import org.apache.commons.lang3.reflect.MethodUtils; import android.app.Activity; import android.content.Intent; -import android.content.res.Configuration; +import android.graphics.Point; import android.os.Build; import android.text.InputType; -import android.view.Display; -import android.view.Surface; import android.widget.EditText; +import java.io.File; + public final class Compatibility { private final static int sdkVersion = Build.VERSION.SDK_INT; @@ -23,19 +23,26 @@ public final class Compatibility { private final static AndroidLevel8Interface level8; private final static AndroidLevel11Interface level11; + private final static AndroidLevel13Interface level13; static { if (isLevel8) { level8 = new AndroidLevel8(); } else { - level8 = new AndroidLevel8Dummy(); + level8 = new AndroidLevel8Emulation(); } if (sdkVersion >= 11) { level11 = new AndroidLevel11(); } else { - level11 = new AndroidLevel11Dummy(); + level11 = new AndroidLevel11Emulation(); + } + if (sdkVersion >= 13) { + level13 = new AndroidLevel13(); + } + else { + level13 = new AndroidLevel13Emulation(); } } @@ -47,29 +54,7 @@ public final class Compatibility { * @return the adjusted direction, in the [0, 360[ range */ public static float getDirectionNow(final float directionNowPre, final Activity activity) { - float offset = 0; - if (isLevel8) { - try { - final int rotation = level8.getRotation(activity); - if (rotation == Surface.ROTATION_90) { - offset = 90; - } else if (rotation == Surface.ROTATION_180) { - offset = 180; - } else if (rotation == Surface.ROTATION_270) { - offset = 270; - } - } catch (final Exception e) { - // This should never happen: IllegalArgumentException, IllegalAccessException or InvocationTargetException - Log.e("Cannot call getRotation()", e); - } - } else { - final Display display = activity.getWindowManager().getDefaultDisplay(); - final int rotation = display.getOrientation(); - if (rotation == Configuration.ORIENTATION_LANDSCAPE) { - offset = 90; - } - } - return AngleUtils.normalize(directionNowPre + offset); + return AngleUtils.normalize(directionNowPre + level8.getRotationOffset(activity)); } public static void dataChanged(final String name) { @@ -113,4 +98,20 @@ public final class Compatibility { level11.invalidateOptionsMenu(activity); } + public static int getDisplayWidth() { + return level13.getDisplayWidth(); + } + + public static int getDisplayHeight() { + return level13.getDisplayHeight(); + } + + public static Point getDisplaySize() { + return level13.getDisplaySize(); + } + + public static File getExternalPictureDir() { + return level8.getExternalPictureDir(); + } + } diff --git a/main/src/cgeo/geocaching/connector/AbstractConnector.java b/main/src/cgeo/geocaching/connector/AbstractConnector.java index 112d6cf..1eb8fbb 100644 --- a/main/src/cgeo/geocaching/connector/AbstractConnector.java +++ b/main/src/cgeo/geocaching/connector/AbstractConnector.java @@ -1,8 +1,8 @@ package cgeo.geocaching.connector; -import cgeo.geocaching.SearchResult; -import cgeo.geocaching.cgCache; -import cgeo.geocaching.geopoint.Viewport; +import cgeo.geocaching.Geocache; +import cgeo.geocaching.enumerations.CacheRealm; +import cgeo.geocaching.geopoint.Geopoint; import org.apache.commons.lang3.StringUtils; @@ -19,6 +19,31 @@ public abstract class AbstractConnector implements IConnector { } @Override + public boolean supportsOwnCoordinates() { + return false; + } + + /** + * Uploading modified coordinates to website + * + * @param cache + * @param wpt + * @return success + */ + @Override + public boolean uploadModifiedCoordinates(Geocache cache, Geopoint wpt) { + throw new UnsupportedOperationException(); + } + + /** + * {@link IConnector} + */ + @Override + public boolean deleteModifiedCoordinates(Geocache cache) { + throw new UnsupportedOperationException(); + } + + @Override public boolean supportsFavoritePoints() { return false; } @@ -29,7 +54,7 @@ public abstract class AbstractConnector implements IConnector { } @Override - public String getLicenseText(final cgCache cache) { + public String getLicenseText(final Geocache cache) { return null; } @@ -38,11 +63,6 @@ public abstract class AbstractConnector implements IConnector { return false; } - @Override - public SearchResult searchByViewport(Viewport viewport, String tokens[]) { - return null; - } - protected static boolean isNumericId(final String string) { try { return Integer.parseInt(string) > 0; @@ -81,4 +101,20 @@ public abstract class AbstractConnector implements IConnector { } abstract protected String getCacheUrlPrefix(); + + /** + * {@link IConnector} + */ + @Override + public CacheRealm getCacheRealm() { + return CacheRealm.OTHER; + } + + /** + * {@link IConnector} + */ + @Override + public boolean isActivated() { + return false; + } } diff --git a/main/src/cgeo/geocaching/connector/ConnectorFactory.java b/main/src/cgeo/geocaching/connector/ConnectorFactory.java index bc4dcc0..561bae2 100644 --- a/main/src/cgeo/geocaching/connector/ConnectorFactory.java +++ b/main/src/cgeo/geocaching/connector/ConnectorFactory.java @@ -2,26 +2,32 @@ package cgeo.geocaching.connector; import cgeo.geocaching.ICache; import cgeo.geocaching.SearchResult; -import cgeo.geocaching.cgTrackable; +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.OCConnector; +import cgeo.geocaching.connector.oc.OCXMLApiConnector; import cgeo.geocaching.connector.ox.OXConnector; import cgeo.geocaching.geopoint.Viewport; import org.apache.commons.lang3.StringUtils; +import java.util.ArrayList; +import java.util.List; + public final class ConnectorFactory { private static final UnknownConnector UNKNOWN_CONNECTOR = new UnknownConnector(); private static final IConnector[] connectors = new IConnector[] { GCConnector.getInstance(), - new OCConnector("OpenCaching.DE", "www.opencaching.de", "OC"), + new OCXMLApiConnector("OpenCaching.DE", "www.opencaching.de", "OC"), new OCConnector("OpenCaching.CZ", "www.opencaching.cz", "OZ"), new OCApiConnector("OpenCaching.CO.UK", "www.opencaching.org.uk", "OK", "arU4okouc4GEjMniE2fq"), 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.no", "OS"), + 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"), @@ -31,10 +37,37 @@ public final class ConnectorFactory { UNKNOWN_CONNECTOR // the unknown connector MUST be the last one }; + private static final ISearchByViewPort[] searchByViewPortConns; + + private static final ISearchByCenter[] searchByCenterConns; + + static { + List<ISearchByViewPort> vpConns = new ArrayList<ISearchByViewPort>(); + for (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) { + // GCConnector is handled specially, omit it here! + if (conn instanceof ISearchByCenter && !(conn instanceof GCConnector)) { + centerConns.add((ISearchByCenter) conn); + } + } + searchByCenterConns = centerConns.toArray(new ISearchByCenter[centerConns.size()]); + } + public static IConnector[] getConnectors() { return connectors; } + public static ISearchByCenter[] getSearchByCenterConnectors() { + return searchByCenterConns; + } + public static boolean canHandle(final String geocode) { if (isInvalidGeocode(geocode)) { return false; @@ -51,7 +84,7 @@ public final class ConnectorFactory { return getConnector(cache.getGeocode()); } - public static IConnector getConnector(cgTrackable trackable) { + public static IConnector getConnector(Trackable trackable) { return getConnector(trackable.getGeocode()); } @@ -74,11 +107,19 @@ public final class ConnectorFactory { return StringUtils.isBlank(geocode) || !Character.isLetterOrDigit(geocode.charAt(0)); } - /** @see IConnector#searchByViewport */ + /** @see ISearchByViewPort#searchByViewport */ public static SearchResult searchByViewport(final Viewport viewport, final String[] tokens) { - // We have only connector capable of doing a 'searchByViewport()' - // If there is a second connector the information has to be collected from all collectors - return GCConnector.getInstance().searchByViewport(viewport, tokens); + + SearchResult result = new SearchResult(); + for (ISearchByViewPort vpconn : searchByViewPortConns) { + if (vpconn.isActivated()) { + SearchResult temp = vpconn.searchByViewport(viewport, tokens); + if (temp != null) { + result.addGeocodes(temp.getGeocodes()); + } + } + } + return result; } public static String getGeocodeFromURL(final String url) { diff --git a/main/src/cgeo/geocaching/connector/GeocachingAustraliaConnector.java b/main/src/cgeo/geocaching/connector/GeocachingAustraliaConnector.java index 30d1a4b..ac2fb37 100644 --- a/main/src/cgeo/geocaching/connector/GeocachingAustraliaConnector.java +++ b/main/src/cgeo/geocaching/connector/GeocachingAustraliaConnector.java @@ -1,6 +1,7 @@ package cgeo.geocaching.connector; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; +import cgeo.geocaching.ICache; import org.apache.commons.lang3.StringUtils; @@ -12,7 +13,7 @@ public class GeocachingAustraliaConnector extends AbstractConnector { } @Override - public String getCacheUrl(final cgCache cache) { + public String getCacheUrl(final Geocache cache) { return getCacheUrlPrefix() + cache.getGeocode(); } @@ -22,6 +23,11 @@ public class GeocachingAustraliaConnector extends AbstractConnector { } @Override + public boolean isOwner(final ICache cache) { + return false; + } + + @Override public boolean canHandle(final String geocode) { return (StringUtils.startsWithIgnoreCase(geocode, "GA") || StringUtils.startsWithIgnoreCase(geocode, "TP")) && isNumericId(geocode.substring(2)); } diff --git a/main/src/cgeo/geocaching/connector/GeopeitusConnector.java b/main/src/cgeo/geocaching/connector/GeopeitusConnector.java index 6ef91db..500f752 100644 --- a/main/src/cgeo/geocaching/connector/GeopeitusConnector.java +++ b/main/src/cgeo/geocaching/connector/GeopeitusConnector.java @@ -1,6 +1,7 @@ package cgeo.geocaching.connector; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; +import cgeo.geocaching.ICache; import org.apache.commons.lang3.StringUtils; @@ -12,7 +13,7 @@ public class GeopeitusConnector extends AbstractConnector { } @Override - public String getCacheUrl(final cgCache cache) { + public String getCacheUrl(final Geocache cache) { return getCacheUrlPrefix() + StringUtils.stripStart(cache.getGeocode().substring(2), "0"); } @@ -22,6 +23,11 @@ public class GeopeitusConnector extends AbstractConnector { } @Override + public boolean isOwner(final ICache cache) { + return false; + } + + @Override public boolean canHandle(String geocode) { return StringUtils.startsWith(geocode, "GE") && isNumericId(geocode.substring(2)); } diff --git a/main/src/cgeo/geocaching/connector/IConnector.java b/main/src/cgeo/geocaching/connector/IConnector.java index d6ac2ff..da626f2 100644 --- a/main/src/cgeo/geocaching/connector/IConnector.java +++ b/main/src/cgeo/geocaching/connector/IConnector.java @@ -1,8 +1,9 @@ package cgeo.geocaching.connector; -import cgeo.geocaching.SearchResult; -import cgeo.geocaching.cgCache; -import cgeo.geocaching.geopoint.Viewport; +import cgeo.geocaching.ICache; +import cgeo.geocaching.Geocache; +import cgeo.geocaching.enumerations.CacheRealm; +import cgeo.geocaching.geopoint.Geopoint; public interface IConnector { /** @@ -26,7 +27,7 @@ public interface IConnector { * @param cache * @return */ - public String getCacheUrl(final cgCache cache); + public String getCacheUrl(final Geocache cache); /** * enable/disable watchlist controls in cache details @@ -62,7 +63,7 @@ public interface IConnector { * @param cache * @return */ - public String getLicenseText(final cgCache cache); + public String getLicenseText(final Geocache cache); /** * enable/disable user actions in cache details @@ -72,15 +73,6 @@ public interface IConnector { public boolean supportsUserActions(); /** - * Search caches by viewport. - * - * @param viewport - * @param tokens - * @return - */ - public SearchResult searchByViewport(final Viewport viewport, final String[] tokens); - - /** * return true if this is a ZIP file containing a GPX file * * @param fileName @@ -106,10 +98,57 @@ public interface IConnector { /** * extract a geocode from the given URL, if this connector can handle that URL somehow - * + * * @param url * @return */ public String getGeocodeFromUrl(final String url); + /** + * enable/disable uploading modified coordinates to website + * + * @return true, when uploading is possible + */ + public boolean supportsOwnCoordinates(); + + /** + * Uploading modified coordinates to website + * + * @param cache + * @param wpt + * @return success + */ + public boolean uploadModifiedCoordinates(Geocache cache, Geopoint wpt); + + /** + * Reseting of modified coordinates on website to details + * + * @param cache + * @return success + */ + public boolean deleteModifiedCoordinates(Geocache cache); + + /** + * The CacheRealm this cache belongs to + * + * @return + */ + public CacheRealm getCacheRealm(); + + /** + * Return true if this connector is activated for online + * interaction (download details, do searches, ...) + * + * @return + */ + + public boolean isActivated(); + + /** + * Check if the current user is the owner of the given cache. + * + * @param cache a cache that this connector must be able to handle + * @return <code>true</code> if the current user is the cache owner, <code>false</code> otherwise + */ + public boolean isOwner(final ICache cache); } diff --git a/main/src/cgeo/geocaching/connector/UnknownConnector.java b/main/src/cgeo/geocaching/connector/UnknownConnector.java index 991d31c..b6fc29a 100644 --- a/main/src/cgeo/geocaching/connector/UnknownConnector.java +++ b/main/src/cgeo/geocaching/connector/UnknownConnector.java @@ -1,6 +1,7 @@ package cgeo.geocaching.connector; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.ICache; +import cgeo.geocaching.Geocache; import org.apache.commons.lang3.StringUtils; @@ -12,7 +13,7 @@ public class UnknownConnector extends AbstractConnector { } @Override - public String getCacheUrl(cgCache cache) { + public String getCacheUrl(Geocache cache) { return null; // we have no url for these caches } @@ -22,6 +23,11 @@ public class UnknownConnector extends AbstractConnector { } @Override + public boolean isOwner(final ICache cache) { + return false; + } + + @Override public boolean canHandle(final String geocode) { return StringUtils.isNotBlank(geocode); } diff --git a/main/src/cgeo/geocaching/connector/capability/ISearchByCenter.java b/main/src/cgeo/geocaching/connector/capability/ISearchByCenter.java index 62645c2..3fdd61f 100644 --- a/main/src/cgeo/geocaching/connector/capability/ISearchByCenter.java +++ b/main/src/cgeo/geocaching/connector/capability/ISearchByCenter.java @@ -10,4 +10,5 @@ import cgeo.geocaching.geopoint.Geopoint; public interface ISearchByCenter { public SearchResult searchByCenter(final Geopoint center); + public boolean isActivated(); } diff --git a/main/src/cgeo/geocaching/connector/capability/ISearchByViewPort.java b/main/src/cgeo/geocaching/connector/capability/ISearchByViewPort.java index 316cf00..f1bd2ce 100644 --- a/main/src/cgeo/geocaching/connector/capability/ISearchByViewPort.java +++ b/main/src/cgeo/geocaching/connector/capability/ISearchByViewPort.java @@ -5,4 +5,6 @@ import cgeo.geocaching.geopoint.Viewport; public interface ISearchByViewPort { public SearchResult searchByViewport(final Viewport viewport, final String[] tokens); + + public boolean isActivated(); } diff --git a/main/src/cgeo/geocaching/connector/gc/AbstractSearchThread.java b/main/src/cgeo/geocaching/connector/gc/AbstractSearchThread.java index d0e45f6..f19064d 100644 --- a/main/src/cgeo/geocaching/connector/gc/AbstractSearchThread.java +++ b/main/src/cgeo/geocaching/connector/gc/AbstractSearchThread.java @@ -12,7 +12,7 @@ abstract public class AbstractSearchThread extends Thread { private final Handler handler; private static AbstractSearchThread currentInstance; - public AbstractSearchThread(final Handler handler) { + protected AbstractSearchThread(final Handler handler) { this.handler = handler; } diff --git a/main/src/cgeo/geocaching/connector/gc/GCConnector.java b/main/src/cgeo/geocaching/connector/gc/GCConnector.java index 197d4e6..2a38bd9 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCConnector.java +++ b/main/src/cgeo/geocaching/connector/gc/GCConnector.java @@ -1,12 +1,16 @@ package cgeo.geocaching.connector.gc; +import cgeo.geocaching.Geocache; +import cgeo.geocaching.ICache; import cgeo.geocaching.R; import cgeo.geocaching.SearchResult; -import cgeo.geocaching.cgCache; -import cgeo.geocaching.cgeoapplication; +import cgeo.geocaching.Settings; +import cgeo.geocaching.cgData; import cgeo.geocaching.connector.AbstractConnector; 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; @@ -18,21 +22,24 @@ import org.apache.commons.lang3.StringUtils; import java.util.regex.Pattern; -public class GCConnector extends AbstractConnector implements ISearchByGeocode, ISearchByCenter { +public class GCConnector extends AbstractConnector implements ISearchByGeocode, ISearchByCenter, ISearchByViewPort { private static final String HTTP_COORD_INFO = "http://coord.info/"; - private static GCConnector instance; private static final Pattern gpxZipFilePattern = Pattern.compile("\\d{7,}(_.+)?\\.zip", Pattern.CASE_INSENSITIVE); private GCConnector() { // singleton } + /** + * initialization on demand holder pattern + */ + private static class Holder { + private static final GCConnector INSTANCE = new GCConnector(); + } + public static GCConnector getInstance() { - if (instance == null) { - instance = new GCConnector(); - } - return instance; + return Holder.INSTANCE; } @Override @@ -44,12 +51,17 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, } @Override - public String getCacheUrl(cgCache cache) { + public String getCacheUrl(Geocache cache) { // it would also be possible to use "http://www.geocaching.com/seek/cache_details.aspx?wp=" + cache.getGeocode(); return "http://www.geocaching.com//seek/cache_details.aspx?wp=" + cache.getGeocode(); } @Override + public boolean supportsOwnCoordinates() { + return true; + } + + @Override public boolean supportsWatchList() { return true; } @@ -83,11 +95,10 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, if (StringUtils.isEmpty(page)) { final SearchResult search = new SearchResult(); - if (cgeoapplication.getInstance().isThere(geocode, guid, true, false)) { + if (cgData.isThere(geocode, guid, true, false)) { if (StringUtils.isBlank(geocode) && StringUtils.isNotBlank(guid)) { Log.i("Loading old cache from cache."); - - search.addGeocode(cgeoapplication.getInstance().getGeocode(guid)); + search.addGeocode(cgData.getGeocodeForGuid(guid)); } else { search.addGeocode(geocode); } @@ -103,7 +114,7 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, final SearchResult searchResult = GCParser.parseCache(page, handler); if (searchResult == null || CollectionUtils.isEmpty(searchResult.getGeocodes())) { - Log.e("GCConnector.searchByGeocode: No cache parsed"); + Log.w("GCConnector.searchByGeocode: No cache parsed"); return searchResult; } @@ -126,39 +137,81 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, return cacheHasReliableLatLon; } - public static boolean addToWatchlist(cgCache cache) { + @Override + public boolean isOwner(final ICache cache) { + return StringUtils.equalsIgnoreCase(cache.getOwnerUserId(), Settings.getUsername()); + + } + + public static boolean addToWatchlist(Geocache cache) { final boolean added = GCParser.addToWatchlist(cache); if (added) { - cgeoapplication.getInstance().updateCache(cache); + cgData.saveChangedCache(cache); } return added; } - public static boolean removeFromWatchlist(cgCache cache) { + public static boolean removeFromWatchlist(Geocache cache) { final boolean removed = GCParser.removeFromWatchlist(cache); if (removed) { - cgeoapplication.getInstance().updateCache(cache); + cgData.saveChangedCache(cache); } return removed; } - public static boolean addToFavorites(cgCache cache) { + /** + * Add a cache to the favorites list. + * + * This must not be called from the UI thread. + * + * @param cache the cache to add + * @return <code>true</code> if the cache was sucessfully added, <code>false</code> otherwise + */ + + public static boolean addToFavorites(Geocache cache) { final boolean added = GCParser.addToFavorites(cache); if (added) { - cgeoapplication.getInstance().updateCache(cache); + cgData.saveChangedCache(cache); } return added; } - public static boolean removeFromFavorites(cgCache cache) { + /** + * Remove a cache from the favorites list. + * + * This must not be called from the UI thread. + * + * @param cache the cache to add + * @return <code>true</code> if the cache was sucessfully added, <code>false</code> otherwise + */ + + public static boolean removeFromFavorites(Geocache cache) { final boolean removed = GCParser.removeFromFavorites(cache); if (removed) { - cgeoapplication.getInstance().updateCache(cache); + cgData.saveChangedCache(cache); } return removed; } @Override + public boolean uploadModifiedCoordinates(Geocache cache, Geopoint wpt) { + final boolean uploaded = GCParser.uploadModifiedCoordinates(cache, wpt); + if (uploaded) { + cgData.saveChangedCache(cache); + } + return uploaded; + } + + @Override + public boolean deleteModifiedCoordinates(Geocache cache) { + final boolean deleted = GCParser.deleteModifiedCoordinates(cache); + if (deleted) { + cgData.saveChangedCache(cache); + } + return deleted; + } + + @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; @@ -173,4 +226,14 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, protected String getCacheUrlPrefix() { return HTTP_COORD_INFO; } + + @Override + public CacheRealm getCacheRealm() { + return CacheRealm.GC; + } + + @Override + public boolean isActivated() { + return true; + } } diff --git a/main/src/cgeo/geocaching/connector/gc/GCConstants.java b/main/src/cgeo/geocaching/connector/gc/GCConstants.java index a339b9a..4d27617 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCConstants.java +++ b/main/src/cgeo/geocaching/connector/gc/GCConstants.java @@ -1,5 +1,6 @@ package cgeo.geocaching.connector.gc; +import java.util.Locale; import java.util.regex.Pattern; /** @@ -49,7 +50,7 @@ public final class GCConstants { public final static Pattern PATTERN_FAVORITE = Pattern.compile("<div id=\"pnlFavoriteCache\">"); // without 'class="hideMe"' inside the tag ! public final static Pattern PATTERN_FAVORITECOUNT = Pattern.compile("<a id=\"uxFavContainerLink\"[^>]+>[^<]*<div[^<]*<span class=\"favorite-value\">\\D*([0-9]+?)</span>"); public final static Pattern PATTERN_COUNTLOGS = Pattern.compile("<span id=\"ctl00_ContentBody_lblFindCounts\"><p(.+?)</p></span>"); - public final static Pattern PATTERN_LOGBOOK = Pattern.compile("initalLogs = (\\{.+\\});"); + public final static Pattern PATTERN_LOGBOOK = Pattern.compile("initalLogs = (\\{.+\\});"); // The "inital" typo really comes from gc.com site /** Two groups ! */ public final static Pattern PATTERN_COUNTLOG = Pattern.compile("<img src=\"/images/logtypes/([0-9]+)\\.png\"[^>]+> (\\d*[,.]?\\d+)"); public static final Pattern PATTERN_PREMIUMMEMBERS = Pattern.compile("<p class=\"Warning NoBottomSpacing\">This is a Premium Member Only cache.</p>"); @@ -103,7 +104,7 @@ public final class GCConstants { public final static Pattern PATTERN_TRACKABLE_ICON = Pattern.compile("<img id=\"ctl00_ContentBody_BugTypeImage\" class=\"TravelBugHeaderIcon\" src=\"([^\"]+)\"[^>]*>"); public final static Pattern PATTERN_TRACKABLE_TYPE = Pattern.compile("<img id=\"ctl00_ContentBody_BugTypeImage\" class=\"TravelBugHeaderIcon\" src=\"[^\"]+\" alt=\"([^\"]+)\"[^>]*>"); public final static Pattern PATTERN_TRACKABLE_DISTANCE = Pattern.compile("<h4[^>]*\\W*Tracking History \\(([0-9.,]+(km|mi))[^\\)]*\\)"); - public final static Pattern PATTERN_TRACKABLE_LOG = Pattern.compile("<tr class=\"Data BorderTop .+?src=\"/images/logtypes/([^.]+)\\.png[^>]+> ([^<]+)</td>.+?guid.+?>([^<]+)</a>.+?(?:guid=([^\"]+)\">(<span[^>]+>)?([^<]+)</.+?)?<td colspan=\"4\">(.+?)(?:<ul.+?ul>)?\\s*</td>\\s*</tr>"); + public final static Pattern PATTERN_TRACKABLE_LOG = Pattern.compile("<tr class=\"Data BorderTop .+?src=\"/images/logtypes/([^.]+)\\.png[^>]+> ([^<]+)</td>.+?guid.+?>([^<]+)</a>.+?(?:guid=([^\"]+)\">(<span[^>]+>)?([^<]+)</.+?)?<td colspan=\"4\">\\s*<div>(.*?)</div>\\s*(?:<ul.+?ul>)?\\s*</td>\\s*</tr>"); public final static Pattern PATTERN_TRACKABLE_LOG_IMAGES = Pattern.compile("<li><a href=\"([^\"]+)\".+?LogImgTitle.+?>([^<]+)</"); /** @@ -148,6 +149,7 @@ 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_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*'([^']+)'"); @@ -162,10 +164,13 @@ public final class GCConstants { public final static Pattern PATTERN_USERSESSION = Pattern.compile("UserSession\\('([^']+)'"); public final static Pattern PATTERN_SESSIONTOKEN = Pattern.compile("sessionToken:'([^']+)'"); + public final static Pattern PATTERN_LOG_IMAGE_UPLOAD = Pattern.compile("/seek/upload\\.aspx\\?LID=(\\d+)", Pattern.CASE_INSENSITIVE); + public final static String STRING_PREMIUMONLY_2 = "Sorry, the owner of this listing has made it viewable to Premium Members only."; public final static String STRING_PREMIUMONLY_1 = "has chosen to make this cache listing visible to Premium Members only."; - public final static String STRING_UNPUBLISHED_OWNER = "Cache is Unpublished"; + public final static String STRING_UNPUBLISHED_OWNER = "cache has not been published yet"; public final static String STRING_UNPUBLISHED_OTHER = "you cannot view this cache listing until it has been published"; + public final static String STRING_UNPUBLISHED_FROM_SEARCH = "UnpublishedCacheSearchWidget"; public final static String STRING_UNKNOWN_ERROR = "An Error Has Occurred"; public final static String STRING_DISABLED = "<li>This cache is temporarily unavailable."; public final static String STRING_ARCHIVED = "<li>This cache has been archived,"; @@ -185,14 +190,14 @@ public final class GCConstants { * see http://support.groundspeak.com/index.php?pg=kb.printer.friendly&id=1#p221 */ public static long gccodeToGCId(final String gccode) { - long gcid = 0; long base = GC_BASE31; - String geocodeWO = gccode.substring(2).toUpperCase(); + 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; } + long gcid = 0; for (int p = 0; p < geocodeWO.length(); p++) { gcid = base * gcid + SEQUENCE_GCID.indexOf(geocodeWO.charAt(p)); } diff --git a/main/src/cgeo/geocaching/connector/gc/GCMap.java b/main/src/cgeo/geocaching/connector/gc/GCMap.java index 681a1d7..2e1dff0 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCMap.java +++ b/main/src/cgeo/geocaching/connector/gc/GCMap.java @@ -2,7 +2,8 @@ package cgeo.geocaching.connector.gc; import cgeo.geocaching.SearchResult; import cgeo.geocaching.Settings; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; +import cgeo.geocaching.cgData; import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.enumerations.CacheSize; import cgeo.geocaching.enumerations.CacheType; @@ -40,10 +41,10 @@ public class GCMap { final SearchResult result = new SearchResult(); final String geocodeList = StringUtils.join(geocodes.toArray(), "|"); - final String referer = GCConstants.URL_LIVE_MAP_DETAILS; try { final Parameters params = new Parameters("i", geocodeList, "_", String.valueOf(System.currentTimeMillis())); + final String referer = GCConstants.URL_LIVE_MAP_DETAILS; final String data = StringUtils.defaultString(Tile.requestMapInfo(referer, params, referer)); // Example JSON information @@ -67,7 +68,7 @@ public class GCMap { } for (int j = 0; j < dataArray.length(); j++) { - final cgCache cache = new cgCache(); + final Geocache cache = new Geocache(); JSONObject dataObject = dataArray.getJSONObject(j); cache.setName(dataObject.getString("name")); @@ -166,6 +167,8 @@ public class GCMap { // iterate over the data and construct all caches in this tile Map<String, List<UTFGridPosition>> positions = new HashMap<String, List<UTFGridPosition>>(); // JSON id as key + Map<String, List<UTFGridPosition>> singlePositions = new HashMap<String, List<UTFGridPosition>>(); // JSON id as key + for (int i = 1; i < keys.length(); i++) { // index 0 is empty String key = keys.getString(i); if (StringUtils.isNotBlank(key)) { @@ -177,12 +180,20 @@ public class GCMap { nameCache.put(id, cacheInfo.getString("n")); List<UTFGridPosition> listOfPositions = positions.get(id); + List<UTFGridPosition> singleListOfPositions = singlePositions.get(id); + if (listOfPositions == null) { listOfPositions = new ArrayList<UTFGridPosition>(); positions.put(id, listOfPositions); + singleListOfPositions = new ArrayList<UTFGridPosition>(); + singlePositions.put(id, singleListOfPositions); } listOfPositions.add(pos); + if (dataForKey.length() == 1) { + singleListOfPositions.add(pos); + } + } } } @@ -191,27 +202,31 @@ public class GCMap { String id = entry.getKey(); List<UTFGridPosition> pos = entry.getValue(); UTFGridPosition xy = UTFGrid.getPositionInGrid(pos); - cgCache cache = new cgCache(); + Geocache cache = new Geocache(); cache.setDetailed(false); cache.setReliableLatLon(false); cache.setGeocode(id); cache.setName(nameCache.get(id)); cache.setZoomlevel(tile.getZoomlevel()); cache.setCoords(tile.getCoord(xy)); - if (strategy.flags.contains(StrategyFlag.PARSE_TILES) && positions.size() < 64 && bitmap != null) { - // don't parse if there are too many caches. The decoding would return too much wrong results - IconDecoder.parseMapPNG(cache, bitmap, xy, tile.getZoomlevel()); + if (strategy.flags.contains(StrategyFlag.PARSE_TILES) && bitmap != null) { + for (UTFGridPosition singlePos : singlePositions.get(id)) { + if (IconDecoder.parseMapPNG(cache, bitmap, singlePos, tile.getZoomlevel())) { + break; // cache parsed + } + } } else { cache.setType(CacheType.UNKNOWN); } + boolean exclude = false; - if (Settings.isExcludeMyCaches() && (cache.isFound() || cache.isOwn())) { // workaround for BM + if (Settings.isExcludeMyCaches() && (cache.isFound() || cache.isOwner())) { // workaround for BM exclude = true; } if (Settings.isExcludeDisabledCaches() && cache.isDisabled()) { exclude = true; } - if (Settings.getCacheType() != CacheType.ALL && Settings.getCacheType() != cache.getType() && cache.getType() != CacheType.UNKNOWN) { // workaround for BM + if (!Settings.getCacheType().contains(cache) && cache.getType() != CacheType.UNKNOWN) { // workaround for BM exclude = true; } if (!exclude) { @@ -313,7 +328,7 @@ public class GCMap { String data = Tile.requestMapInfo(GCConstants.URL_MAP_INFO, params, GCConstants.URL_LIVE_MAP); if (StringUtils.isEmpty(data)) { - Log.e("GCBase.searchByViewport: No data from server for tile (" + tile.getX() + "/" + tile.getY() + ")"); + Log.w("GCBase.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())) { @@ -341,7 +356,7 @@ public class GCMap { if (search != null && !search.isEmpty()) { final Set<String> geocodes = search.getGeocodes(); if (Settings.isPremiumMember()) { - lastSearchViewport = cgeoapplication.getInstance().getBounds(geocodes); + lastSearchViewport = cgData.getBounds(geocodes); } else { lastSearchViewport = new Viewport(center, center); } diff --git a/main/src/cgeo/geocaching/connector/gc/GCParser.java b/main/src/cgeo/geocaching/connector/gc/GCParser.java index 158a201..9707e06 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCParser.java +++ b/main/src/cgeo/geocaching/connector/gc/GCParser.java @@ -1,14 +1,15 @@ package cgeo.geocaching.connector.gc; +import cgeo.geocaching.Geocache; +import cgeo.geocaching.Image; import cgeo.geocaching.LogEntry; import cgeo.geocaching.R; import cgeo.geocaching.SearchResult; import cgeo.geocaching.Settings; +import cgeo.geocaching.Trackable; import cgeo.geocaching.TrackableLog; -import cgeo.geocaching.cgCache; -import cgeo.geocaching.cgImage; -import cgeo.geocaching.cgTrackable; -import cgeo.geocaching.cgWaypoint; +import cgeo.geocaching.Waypoint; +import cgeo.geocaching.cgData; import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.enumerations.CacheSize; import cgeo.geocaching.enumerations.CacheType; @@ -28,8 +29,9 @@ import cgeo.geocaching.network.Parameters; import cgeo.geocaching.ui.DirectionImage; import cgeo.geocaching.utils.BaseUtils; import cgeo.geocaching.utils.CancellableHandler; -import cgeo.geocaching.utils.LazyInitializedList; +import cgeo.geocaching.utils.HtmlUtils; import cgeo.geocaching.utils.Log; +import cgeo.geocaching.utils.MatcherWrapper; import ch.boye.httpclientandroidlib.HttpResponse; @@ -37,18 +39,15 @@ import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringEscapeUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.net.Uri; import android.text.Html; -import android.text.Spannable; -import android.text.Spanned; -import android.text.style.ForegroundColorSpan; -import android.text.style.StrikethroughSpan; -import java.net.URLDecoder; +import java.io.File; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -58,7 +57,6 @@ import java.util.EnumSet; import java.util.List; import java.util.Locale; import java.util.Set; -import java.util.regex.Matcher; public abstract class GCParser { private final static SimpleDateFormat dateTbIn1 = new SimpleDateFormat("EEEEE, dd MMMMM yyyy", Locale.ENGLISH); // Saturday, 28 March 2009 @@ -71,8 +69,6 @@ public abstract class GCParser { } final List<String> cids = new ArrayList<String>(); - String recaptchaChallenge = null; - String recaptchaText = null; String page = pageContent; final SearchResult searchResult = new SearchResult(); @@ -81,6 +77,7 @@ public abstract class GCParser { // recaptcha AbstractSearchThread thread = AbstractSearchThread.getCurrentInstance(); + String recaptchaChallenge = null; if (showCaptcha) { String recaptchaJsParam = BaseUtils.getMatch(page, GCConstants.PATTERN_SEARCH_RECAPTCHA, false, null); @@ -124,7 +121,7 @@ public abstract class GCParser { final int rows_count = rows.length; for (int z = 1; z < rows_count; z++) { - final cgCache cache = new cgCache(); + final Geocache cache = new Geocache(); String row = rows[z]; // check for cache type presence @@ -133,7 +130,7 @@ public abstract class GCParser { } try { - final Matcher matcherGuidAndDisabled = GCConstants.PATTERN_SEARCH_GUIDANDDISABLED.matcher(row); + final MatcherWrapper matcherGuidAndDisabled = new MatcherWrapper(GCConstants.PATTERN_SEARCH_GUIDANDDISABLED, row); while (matcherGuidAndDisabled.find()) { if (matcherGuidAndDisabled.groupCount() > 0) { @@ -163,29 +160,32 @@ public abstract class GCParser { continue; } - String inventoryPre = null; - - cache.setGeocode(BaseUtils.getMatch(row, GCConstants.PATTERN_SEARCH_GEOCODE, true, 1, cache.getGeocode(), true).toUpperCase()); + cache.setGeocode(BaseUtils.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 direction - image if (Settings.getLoadDirImg()) { - cache.setDirectionImg(URLDecoder.decode(BaseUtils.getMatch(row, GCConstants.PATTERN_SEARCH_DIRECTION, true, 1, cache.getDirectionImg(), true))); + cache.setDirectionImg(Network.decode(BaseUtils.getMatch(row, GCConstants.PATTERN_SEARCH_DIRECTION, true, 1, cache.getDirectionImg(), true))); } // cache inventory - final Matcher matcherTbs = GCConstants.PATTERN_SEARCH_TRACKABLES.matcher(row); + final MatcherWrapper matcherTbs = new MatcherWrapper(GCConstants.PATTERN_SEARCH_TRACKABLES, row); + String inventoryPre = null; while (matcherTbs.find()) { if (matcherTbs.groupCount() > 0) { - cache.setInventoryItems(Integer.parseInt(matcherTbs.group(1))); + try { + cache.setInventoryItems(Integer.parseInt(matcherTbs.group(1))); + } catch (NumberFormatException e) { + Log.e("Error parsing trackables count", e); + } inventoryPre = matcherTbs.group(2); } } if (StringUtils.isNotBlank(inventoryPre)) { - final Matcher matcherTbsInside = GCConstants.PATTERN_SEARCH_TRACKABLESINSIDE.matcher(inventoryPre); + final MatcherWrapper matcherTbsInside = new MatcherWrapper(GCConstants.PATTERN_SEARCH_TRACKABLESINSIDE, inventoryPre); while (matcherTbsInside.find()) { if (matcherTbsInside.groupCount() == 2 && matcherTbsInside.group(2) != null && @@ -202,9 +202,6 @@ public abstract class GCParser { // found it cache.setFound(row.contains("/images/icons/16/found.png")); - // own it - cache.setOwn(row.contains("/images/icons/16/placed.png")); - // id String result = BaseUtils.getMatch(row, GCConstants.PATTERN_SEARCH_ID, null); if (null != result) { @@ -222,16 +219,6 @@ public abstract class GCParser { Log.w("GCParser.parseSearch: Failed to parse favourite count"); } - if (cache.getNameSp() == null) { - cache.setNameSp((new Spannable.Factory()).newSpannable(cache.getName())); - if (cache.isDisabled() || cache.isArchived()) { // strike - cache.getNameSp().setSpan(new StrikethroughSpan(), 0, cache.getNameSp().toString().length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - if (cache.isArchived()) { - cache.getNameSp().setSpan(new ForegroundColorSpan(cgeoapplication.getInstance().getResources().getColor(R.color.archived_cache_color)), 0, cache.getNameSp().toString().length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - } - searchResult.addCache(cache); } @@ -245,6 +232,7 @@ public abstract class GCParser { Log.w("GCParser.parseSearch: Failed to parse cache count"); } + String recaptchaText = null; if (thread != null && recaptchaChallenge != null) { if (thread.getText() == null) { thread.waitForUser(); @@ -253,7 +241,7 @@ public abstract class GCParser { recaptchaText = thread.getText(); } - if (cids.size() > 0 && (Settings.isPremiumMember() || showCaptcha) && (recaptchaChallenge == null || StringUtils.isNotBlank(recaptchaText))) { + if (!cids.isEmpty() && (Settings.isPremiumMember() || showCaptcha) && (recaptchaChallenge == null || StringUtils.isNotBlank(recaptchaText))) { Log.i("Trying to get .loc for " + cids.size() + " caches"); try { @@ -295,14 +283,14 @@ public abstract class GCParser { LocParser.parseLoc(searchResult, coordinates); } catch (Exception e) { - Log.e("GCParser.parseSearch.CIDs: " + e.toString()); + Log.e("GCParser.parseSearch.CIDs", e); } } // get direction images if (Settings.getLoadDirImg()) { - final Set<cgCache> caches = searchResult.getCachesFromSearchResult(LoadFlags.LOAD_CACHE_OR_DB); - for (cgCache cache : caches) { + final Set<Geocache> caches = searchResult.getCachesFromSearchResult(LoadFlags.LOAD_CACHE_OR_DB); + for (Geocache cache : caches) { if (cache.getCoords() == null && StringUtils.isNotEmpty(cache.getDirectionImg())) { DirectionImage.getDrawable(cache.getGeocode(), cache.getDirectionImg()); } @@ -315,18 +303,16 @@ public abstract class GCParser { static SearchResult parseCache(final String page, final CancellableHandler handler) { final SearchResult searchResult = parseCacheFromText(page, handler); if (searchResult != null && !searchResult.getGeocodes().isEmpty()) { - final cgCache cache = searchResult.getFirstCacheFromResult(LoadFlags.LOAD_CACHE_OR_DB); + final Geocache cache = searchResult.getFirstCacheFromResult(LoadFlags.LOAD_CACHE_OR_DB); getExtraOnlineInfo(cache, page, handler); - cache.setUpdated(System.currentTimeMillis()); - cache.setDetailedUpdate(cache.getUpdated()); - cache.setDetailed(true); + cache.setDetailedUpdatedNow(); if (CancellableHandler.isCancelled(handler)) { return null; } // save full detailed caches CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_cache); - cgeoapplication.getInstance().saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); + cgData.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); // update progress message so user knows we're still working. This is more of a place holder than // actual indication of what the program is doing @@ -345,7 +331,7 @@ public abstract class GCParser { final SearchResult searchResult = new SearchResult(); - if (page.contains(GCConstants.STRING_UNPUBLISHED_OTHER) || page.contains(GCConstants.STRING_UNPUBLISHED_OWNER)) { + if (page.contains(GCConstants.STRING_UNPUBLISHED_OTHER) || page.contains(GCConstants.STRING_UNPUBLISHED_OWNER) || page.contains(GCConstants.STRING_UNPUBLISHED_FROM_SEARCH)) { searchResult.setError(StatusCode.UNPUBLISHED_CACHE); return searchResult; } @@ -361,7 +347,7 @@ public abstract class GCParser { return searchResult; } - final cgCache cache = new cgCache(); + final Geocache cache = new Geocache(); cache.setDisabled(page.contains(GCConstants.STRING_DISABLED)); cache.setArchived(page.contains(GCConstants.STRING_ARCHIVED)); @@ -383,9 +369,9 @@ public abstract class GCParser { cache.setName(cacheName); // owner real name - cache.setOwnerUserId(URLDecoder.decode(BaseUtils.getMatch(page, GCConstants.PATTERN_OWNER_USERID, true, cache.getOwnerUserId()))); + cache.setOwnerUserId(Network.decode(BaseUtils.getMatch(page, GCConstants.PATTERN_OWNER_USERID, true, cache.getOwnerUserId()))); - cache.setOwn(StringUtils.equalsIgnoreCase(cache.getOwnerUserId(), Settings.getUsername())); + cache.setUserModifiedCoords(false); String tableInside = page; @@ -401,13 +387,21 @@ public abstract class GCParser { // cache terrain String result = BaseUtils.getMatch(tableInside, GCConstants.PATTERN_TERRAIN, true, null); if (result != null) { - cache.setTerrain(Float.parseFloat(StringUtils.replaceChars(result, '_', '.'))); + try { + cache.setTerrain(Float.parseFloat(StringUtils.replaceChars(result, '_', '.'))); + } catch (NumberFormatException e) { + Log.e("Error parsing terrain value", e); + } } // cache difficulty result = BaseUtils.getMatch(tableInside, GCConstants.PATTERN_DIFFICULTY, true, null); if (result != null) { - cache.setDifficulty(Float.parseFloat(StringUtils.replaceChars(result, '_', '.'))); + try { + cache.setDifficulty(Float.parseFloat(StringUtils.replaceChars(result, '_', '.'))); + } catch (NumberFormatException e) { + Log.e("Error parsing difficulty value", e); + } } // owner @@ -432,10 +426,14 @@ public abstract class GCParser { } // favourite - cache.setFavoritePoints(Integer.parseInt(BaseUtils.getMatch(tableInside, GCConstants.PATTERN_FAVORITECOUNT, true, "0"))); + try { + cache.setFavoritePoints(Integer.parseInt(BaseUtils.getMatch(tableInside, GCConstants.PATTERN_FAVORITECOUNT, true, "0"))); + } catch (NumberFormatException e) { + Log.e("Error parsing favourite count", e); + } // cache size - cache.setSize(CacheSize.getById(BaseUtils.getMatch(tableInside, GCConstants.PATTERN_SIZE, true, CacheSize.NOT_CHOSEN.id).toLowerCase())); + cache.setSize(CacheSize.getById(BaseUtils.getMatch(tableInside, GCConstants.PATTERN_SIZE, true, CacheSize.NOT_CHOSEN.id))); } // cache found @@ -459,18 +457,18 @@ public abstract class GCParser { cache.setOnWatchlist(BaseUtils.matches(page, GCConstants.PATTERN_WATCHLIST)); // latitude and longitude. Can only be retrieved if user is logged in - cache.setLatlon(BaseUtils.getMatch(page, GCConstants.PATTERN_LATLON, true, cache.getLatlon())); - if (StringUtils.isNotEmpty(cache.getLatlon())) { + String latlon = BaseUtils.getMatch(page, GCConstants.PATTERN_LATLON, true, ""); + if (StringUtils.isNotEmpty(latlon)) { try { - cache.setCoords(new Geopoint(cache.getLatlon())); + cache.setCoords(new Geopoint(latlon)); cache.setReliableLatLon(true); } catch (Geopoint.GeopointException e) { - Log.w("GCParser.parseCache: Failed to parse cache coordinates: " + e.toString()); + Log.w("GCParser.parseCache: Failed to parse cache coordinates", e); } } // cache location - cache.setLocation(BaseUtils.getMatch(page, GCConstants.PATTERN_LOCATION, true, cache.getLocation())); + cache.setLocation(BaseUtils.getMatch(page, GCConstants.PATTERN_LOCATION, true, "")); // cache hint String result = BaseUtils.getMatch(page, GCConstants.PATTERN_HINT, false, null); @@ -488,7 +486,7 @@ public abstract class GCParser { cache.setPersonalNote(BaseUtils.getMatch(page, GCConstants.PATTERN_PERSONALNOTE, true, cache.getPersonalNote())); // cache short description - cache.setShortdesc(BaseUtils.getMatch(page, GCConstants.PATTERN_SHORTDESC, true, cache.getShortdesc())); + cache.setShortDescription(BaseUtils.getMatch(page, GCConstants.PATTERN_SHORTDESC, true, "")); // cache description cache.setDescription(BaseUtils.getMatch(page, GCConstants.PATTERN_DESC, true, "")); @@ -497,21 +495,21 @@ public abstract class GCParser { try { final String attributesPre = BaseUtils.getMatch(page, GCConstants.PATTERN_ATTRIBUTES, true, null); if (null != attributesPre) { - final Matcher matcherAttributesInside = GCConstants.PATTERN_ATTRIBUTESINSIDE.matcher(attributesPre); + final MatcherWrapper matcherAttributesInside = new MatcherWrapper(GCConstants.PATTERN_ATTRIBUTESINSIDE, attributesPre); final ArrayList<String> attributes = new ArrayList<String>(); while (matcherAttributesInside.find()) { if (matcherAttributesInside.groupCount() > 1 && !matcherAttributesInside.group(2).equalsIgnoreCase("blank")) { // by default, use the tooltip of the attribute - String attribute = matcherAttributesInside.group(2).toLowerCase(); + String attribute = matcherAttributesInside.group(2).toLowerCase(Locale.US); // if the image name can be recognized, use the image name as attribute - String imageName = matcherAttributesInside.group(1).trim(); - if (imageName.length() > 0) { + final String imageName = matcherAttributesInside.group(1).trim(); + if (StringUtils.isNotEmpty(imageName)) { int start = imageName.lastIndexOf('/'); int end = imageName.lastIndexOf('.'); if (start >= 0 && end >= 0) { - attribute = imageName.substring(start + 1, end).replace('-', '_').toLowerCase(); + attribute = imageName.substring(start + 1, end).replace('-', '_').toLowerCase(Locale.US); } } attributes.add(attribute); @@ -531,26 +529,22 @@ public abstract class GCParser { } CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_spoilers); - final Matcher matcherSpoilersInside = GCConstants.PATTERN_SPOILER_IMAGE.matcher(page); + final MatcherWrapper matcherSpoilersInside = new MatcherWrapper(GCConstants.PATTERN_SPOILER_IMAGE, page); 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 - // - // Create a new string to avoid referencing the whole page though the matcher (@see BaseUtils.getMatch()) - String url = new String(matcherSpoilersInside.group(1).replace("/display", "")); + String url = matcherSpoilersInside.group(1).replace("/display", ""); String title = null; if (matcherSpoilersInside.group(3) != null) { - // Create a new string to avoid referencing the whole page though the matcher (@see BaseUtils.getMatch()) - title = new String(matcherSpoilersInside.group(3)); + title = matcherSpoilersInside.group(3); } String description = null; if (matcherSpoilersInside.group(4) != null) { - // Create a new string to avoid referencing the whole page though the matcher (@see BaseUtils.getMatch()) - description = new String(matcherSpoilersInside.group(4)); + description = matcherSpoilersInside.group(4); } - cache.addSpoiler(new cgImage(url, title, description)); + cache.addSpoiler(new Image(url, title, description)); } } catch (Exception e) { // failed to parse cache spoilers @@ -561,25 +555,23 @@ public abstract class GCParser { try { cache.setInventoryItems(0); - final Matcher matcherInventory = GCConstants.PATTERN_INVENTORY.matcher(page); + final MatcherWrapper matcherInventory = new MatcherWrapper(GCConstants.PATTERN_INVENTORY, page); if (matcherInventory.find()) { if (cache.getInventory() == null) { - cache.setInventory(new ArrayList<cgTrackable>()); + cache.setInventory(new ArrayList<Trackable>()); } if (matcherInventory.groupCount() > 1) { final String inventoryPre = matcherInventory.group(2); if (StringUtils.isNotBlank(inventoryPre)) { - final Matcher matcherInventoryInside = GCConstants.PATTERN_INVENTORYINSIDE.matcher(inventoryPre); + final MatcherWrapper matcherInventoryInside = new MatcherWrapper(GCConstants.PATTERN_INVENTORYINSIDE, inventoryPre); while (matcherInventoryInside.find()) { if (matcherInventoryInside.groupCount() > 0) { - final cgTrackable inventoryItem = new cgTrackable(); - // Create a new string to avoid referencing the whole page though the matcher (@see BaseUtils.getMatch()) - inventoryItem.setGuid(new String(matcherInventoryInside.group(1))); - // Create a new string to avoid referencing the whole page though the matcher (@see BaseUtils.getMatch()) - inventoryItem.setName(new String(matcherInventoryInside.group(2))); + final Trackable inventoryItem = new Trackable(); + inventoryItem.setGuid(matcherInventoryInside.group(1)); + inventoryItem.setName(matcherInventoryInside.group(2)); cache.getInventory().add(inventoryItem); cache.setInventoryItems(cache.getInventoryItems() + 1); @@ -594,14 +586,12 @@ public abstract class GCParser { } // cache logs counts - try - { + try { final String countlogs = BaseUtils.getMatch(page, GCConstants.PATTERN_COUNTLOGS, true, null); if (null != countlogs) { - final Matcher matcherLog = GCConstants.PATTERN_COUNTLOG.matcher(countlogs); + final MatcherWrapper matcherLog = new MatcherWrapper(GCConstants.PATTERN_COUNTLOG, countlogs); - while (matcherLog.find()) - { + while (matcherLog.find()) { String typeStr = matcherLog.group(1); String countStr = matcherLog.group(2).replaceAll("[.,]", ""); @@ -612,21 +602,20 @@ public abstract class GCParser { } } } - } catch (Exception e) - { + } catch (Exception e) { // failed to parse logs Log.w("GCParser.parseCache: Failed to parse cache log count"); } // waypoints - reset collection - cache.setWaypoints(Collections.<cgWaypoint> emptyList(), false); + cache.setWaypoints(Collections.<Waypoint> emptyList(), false); // 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); if (null != originalCoords) { - final cgWaypoint waypoint = new cgWaypoint(cgeoapplication.getInstance().getString(R.string.cache_coordinates_original), WaypointType.WAYPOINT, false); + final Waypoint waypoint = new Waypoint(cgeoapplication.getInstance().getString(R.string.cache_coordinates_original), WaypointType.ORIGINAL, false); waypoint.setCoords(new Geopoint(originalCoords)); cache.addOrChangeWaypoint(waypoint, false); cache.setUserModifiedCoords(true); @@ -634,10 +623,7 @@ public abstract class GCParser { } catch (Geopoint.GeopointException e) { } - int wpBegin; - int wpEnd; - - wpBegin = page.indexOf("<table class=\"Table\" id=\"ctl00_ContentBody_Waypoints\">"); + int wpBegin = page.indexOf("<table class=\"Table\" id=\"ctl00_ContentBody_Waypoints\">"); if (wpBegin != -1) { // parse waypoints if (CancellableHandler.isCancelled(handler)) { return null; @@ -646,7 +632,7 @@ public abstract class GCParser { String wpList = page.substring(wpBegin); - wpEnd = wpList.indexOf("</p>"); + int wpEnd = wpList.indexOf("</p>"); if (wpEnd > -1 && wpEnd <= wpList.length()) { wpList = wpList.substring(0, wpEnd); } @@ -663,9 +649,8 @@ public abstract class GCParser { final String[] wpItems = wpList.split("<tr"); - String[] wp; for (int j = 1; j < wpItems.length; j++) { - wp = wpItems[j].split("<td"); + String[] wp = wpItems[j].split("<td"); // waypoint name // res is null during the unit tests @@ -674,7 +659,7 @@ public abstract class GCParser { // waypoint type final String resulttype = BaseUtils.getMatch(wp[3], GCConstants.PATTERN_WPTYPE, null); - final cgWaypoint waypoint = new cgWaypoint(name, WaypointType.findById(resulttype), false); + 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)); @@ -683,7 +668,7 @@ public abstract class GCParser { waypoint.setLookup(BaseUtils.getMatch(wp[5], GCConstants.PATTERN_WPPREFIXORLOOKUPORLATLON, true, 2, waypoint.getLookup(), false)); // waypoint latitude and logitude - String latlon = Html.fromHtml(BaseUtils.getMatch(wp[7], GCConstants.PATTERN_WPPREFIXORLOOKUPORLATLON, false, 2, "", false)).toString().trim(); + latlon = Html.fromHtml(BaseUtils.getMatch(wp[7], GCConstants.PATTERN_WPPREFIXORLOOKUPORLATLON, false, 2, "", false)).toString().trim(); if (!StringUtils.startsWith(latlon, "???")) { waypoint.setLatlon(latlon); waypoint.setCoords(new Geopoint(latlon)); @@ -756,12 +741,12 @@ public abstract class GCParser { final SearchResult searchResult = parseSearch(url, page, showCaptcha); if (searchResult == null || CollectionUtils.isEmpty(searchResult.getGeocodes())) { - Log.e("GCParser.searchByNextPage: No cache parsed"); + Log.w("GCParser.searchByNextPage: No cache parsed"); return search; } // search results don't need to be filtered so load GCVote ratings here - GCVote.loadRatings(new ArrayList<cgCache>(searchResult.getCachesFromSearchResult(LoadFlags.LOAD_CACHE_OR_DB))); + GCVote.loadRatings(new ArrayList<Geocache>(searchResult.getCachesFromSearchResult(LoadFlags.LOAD_CACHE_OR_DB))); // save to application search.setError(searchResult.getError()); @@ -896,13 +881,13 @@ public abstract class GCParser { return null; } - public static cgTrackable searchTrackable(final String geocode, final String guid, final String id) { + public static Trackable searchTrackable(final String geocode, final String guid, final String id) { if (StringUtils.isBlank(geocode) && StringUtils.isBlank(guid) && StringUtils.isBlank(id)) { Log.w("GCParser.searchTrackable: No geocode nor guid nor id given"); return null; } - cgTrackable trackable = new cgTrackable(); + Trackable trackable = new Trackable(); final Parameters params = new Parameters(); if (StringUtils.isNotBlank(geocode)) { @@ -923,43 +908,27 @@ public abstract class GCParser { trackable = parseTrackable(page, geocode); if (trackable == null) { - Log.e("GCParser.searchTrackable: No trackable parsed"); + Log.w("GCParser.searchTrackable: No trackable parsed"); return null; } return trackable; } - public static StatusCode postLog(final String geocode, final String cacheid, final String[] viewstates, + public static ImmutablePair<StatusCode, String> postLog(final String geocode, final String cacheid, final String[] viewstates, final LogType logType, final int year, final int month, final int day, final String log, final List<TrackableLog> trackables) { if (Login.isEmpty(viewstates)) { Log.e("GCParser.postLog: No viewstate given"); - return StatusCode.LOG_POST_ERROR; + return new ImmutablePair<StatusCode, String>(StatusCode.LOG_POST_ERROR, ""); } if (StringUtils.isBlank(log)) { Log.e("GCParser.postLog: No log text given"); - return StatusCode.NO_LOG_TEXT; - } - - // fix log (non-Latin characters converted to HTML entities) - final int logLen = log.length(); - final StringBuilder logUpdated = new StringBuilder(); - - for (int i = 0; i < logLen; i++) { - char c = log.charAt(i); - - if (c > 300) { - logUpdated.append("&#"); - logUpdated.append(Integer.toString(c)); - logUpdated.append(';'); - } else { - logUpdated.append(c); - } + return new ImmutablePair<StatusCode, String>(StatusCode.NO_LOG_TEXT, ""); } - final String logInfo = logUpdated.toString().replace("\n", "\r\n").trim(); // windows' eol and remove leading and trailing whitespaces + final String logInfo = HtmlUtils.convertNonLatinCharactersToHTML(log).replace("\n", "\r\n").trim(); // windows' eol and remove leading and trailing whitespaces if (trackables != null) { Log.i("Trying to post log for cache #" + cacheid + " - action: " + logType + "; date: " + year + "." + month + "." + day + ", log: " + logInfo + "; trackables: " + trackables.size()); @@ -999,12 +968,12 @@ public abstract class GCParser { String page = Login.postRequestLogged(uri, params); if (!Login.getLoginStatus(page)) { Log.e("GCParser.postLogTrackable: Can not log in geocaching"); - return StatusCode.NOT_LOGGED_IN; + return new ImmutablePair<StatusCode, String>(StatusCode.NOT_LOGGED_IN, ""); } // maintenance, archived needs to be confirmed - final Matcher matcher = GCConstants.PATTERN_MAINTENANCE.matcher(page); + final MatcherWrapper matcher = new MatcherWrapper(GCConstants.PATTERN_MAINTENANCE, page); try { if (matcher.find() && matcher.groupCount() > 0) { @@ -1012,7 +981,7 @@ public abstract class GCParser { if (Login.isEmpty(viewstatesConfirm)) { Log.e("GCParser.postLog: No viewstate for confirm log"); - return StatusCode.LOG_POST_ERROR; + return new ImmutablePair<StatusCode, String>(StatusCode.LOG_POST_ERROR, ""); } params.clear(); @@ -1048,17 +1017,17 @@ public abstract class GCParser { page = Network.getResponseData(Network.postRequest(uri, params)); } } catch (Exception e) { - Log.e("GCParser.postLog.confim: " + e.toString()); + Log.e("GCParser.postLog.confim", e); } try { - final Matcher matcherOk = GCConstants.PATTERN_OK1.matcher(page); + final MatcherWrapper matcherOk = new MatcherWrapper(GCConstants.PATTERN_OK1, page); if (matcherOk.find()) { Log.i("Log successfully posted to cache #" + cacheid); if (geocode != null) { - cgeoapplication.getInstance().saveVisitDate(geocode); + cgData.saveVisitDate(geocode); } Login.getLoginStatus(page); @@ -1066,14 +1035,71 @@ public abstract class GCParser { if (Login.getActualCachesFound() >= 0) { Login.setActualCachesFound(Login.getActualCachesFound() + 1); } - return StatusCode.NO_ERROR; + + final String logID = BaseUtils.getMatch(page, GCConstants.PATTERN_LOG_IMAGE_UPLOAD, ""); + + return new ImmutablePair<StatusCode, String>(StatusCode.NO_ERROR, logID); } } catch (Exception e) { - Log.e("GCParser.postLog.check: " + e.toString()); + Log.e("GCParser.postLog.check", e); } Log.e("GCParser.postLog: Failed to post log because of unknown error"); - return StatusCode.LOG_POST_ERROR; + return new ImmutablePair<StatusCode, String>(StatusCode.LOG_POST_ERROR, ""); + } + + /** + * Upload an image to a log that has already been posted + * + * @param logId + * the ID of the log to upload the image to. Found on page returned when log is uploaded + * @param caption + * of the image; max 50 chars + * @param description + * of the image; max 250 chars + * @param imageUri + * 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)); + + 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[] viewstates = Login.getViewstates(page); + + final Parameters uploadParams = new Parameters( + "__EVENTTARGET", "", + "__EVENTARGUMENT", "", + "ctl00$ContentBody$ImageUploadControl1$uxFileCaption", HtmlUtils.convertNonLatinCharactersToHTML(caption), + "ctl00$ContentBody$ImageUploadControl1$uxFileDesc", HtmlUtils.convertNonLatinCharactersToHTML(description), + "ctl00$ContentBody$ImageUploadControl1$uxUpload", "Upload"); + Login.putViewstates(uploadParams, viewstates); + + 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); + + if (matcherOK.find()) { + Log.i("Logimage successfully uploaded."); + + return StatusCode.NO_ERROR; + } + Log.e("GCParser.uploadLogIMage: Failed to upload image because of unknown error"); + + return StatusCode.LOGIMAGE_POST_ERROR; } public static StatusCode postLogTrackable(final String tbid, final String trackingCode, final String[] viewstates, @@ -1090,7 +1116,7 @@ public abstract class GCParser { Log.i("Trying to post log for trackable #" + trackingCode + " - action: " + logType + "; date: " + year + "." + month + "." + day + ", log: " + log); - final String logInfo = log.replace("\n", "\r\n"); // windows' eol + final String logInfo = HtmlUtils.convertNonLatinCharactersToHTML(log).replace("\n", "\r\n"); // windows' eol final Calendar currentDate = Calendar.getInstance(); final Parameters params = new Parameters( @@ -1122,13 +1148,13 @@ public abstract class GCParser { try { - final Matcher matcherOk = GCConstants.PATTERN_OK2.matcher(page); + final MatcherWrapper matcherOk = new MatcherWrapper(GCConstants.PATTERN_OK2, page); if (matcherOk.find()) { Log.i("Log successfully posted to trackable #" + trackingCode); return StatusCode.NO_ERROR; } } catch (Exception e) { - Log.e("GCParser.postLogTrackable.check: " + e.toString()); + Log.e("GCParser.postLogTrackable.check", e); } Log.e("GCParser.postLogTrackable: Failed to post log because of unknown error"); @@ -1142,7 +1168,7 @@ public abstract class GCParser { * the cache to add * @return <code>false</code> if an error occurred, <code>true</code> otherwise */ - static boolean addToWatchlist(final cgCache cache) { + 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); @@ -1168,7 +1194,7 @@ public abstract class GCParser { * the cache to remove * @return <code>false</code> if an error occurred, <code>true</code> otherwise */ - static boolean removeFromWatchlist(final cgCache cache) { + static boolean removeFromWatchlist(final Geocache cache) { final String uri = "http://www.geocaching.com/my/watchlist.aspx?ds=1&action=rem&id=" + cache.getCacheId(); String page = Login.postRequestLogged(uri, null); @@ -1211,15 +1237,17 @@ public abstract class GCParser { /** * Adds the cache to the favorites of the user. * + * This must not be called from the UI thread. + * * @param cache * the cache to add * @return <code>false</code> if an error occurred, <code>true</code> otherwise */ - static boolean addToFavorites(final cgCache cache) { + static boolean addToFavorites(final Geocache cache) { return changeFavorite(cache, true); } - private static boolean changeFavorite(final cgCache cache, final boolean add) { + 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, ""); if (StringUtils.isEmpty(userToken)) { @@ -1241,26 +1269,26 @@ public abstract class GCParser { } /** - * Removes the cache from the Favorites + * Removes the cache from the favorites. + * + * This must not be called from the UI thread. * * @param cache * the cache to remove * @return <code>false</code> if an error occurred, <code>true</code> otherwise */ - static boolean removeFromFavorites(final cgCache cache) { + static boolean removeFromFavorites(final Geocache cache) { return changeFavorite(cache, false); } /** - * Parse a trackable HTML description into a cgTrackable object + * Parse a trackable HTML description into a Trackable object * * @param page * the HTML page to parse, already processed through {@link BaseUtils#replaceWhitespace} - * @param app - * if not null, the application to use to save the trackable * @return the parsed trackable, or null if none could be parsed */ - static cgTrackable parseTrackable(final String page, final String possibleTrackingcode) { + static Trackable parseTrackable(final String page, final String possibleTrackingcode) { if (StringUtils.isBlank(page)) { Log.e("GCParser.parseTrackable: No page given"); return null; @@ -1270,10 +1298,10 @@ public abstract class GCParser { return null; } - final cgTrackable trackable = new cgTrackable(); + final Trackable trackable = new Trackable(); // trackable geocode - trackable.setGeocode(BaseUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_GEOCODE, true, trackable.getGeocode()).toUpperCase()); + trackable.setGeocode(BaseUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_GEOCODE, true, trackable.getGeocode())); // trackable id trackable.setGuid(BaseUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_GUID, true, trackable.getGuid())); @@ -1291,7 +1319,7 @@ public abstract class GCParser { // trackable owner name try { - final Matcher matcherOwner = GCConstants.PATTERN_TRACKABLE_OWNER.matcher(page); + final MatcherWrapper matcherOwner = new MatcherWrapper(GCConstants.PATTERN_TRACKABLE_OWNER, page); if (matcherOwner.find() && matcherOwner.groupCount() > 0) { trackable.setOwnerGuid(matcherOwner.group(1)); trackable.setOwner(matcherOwner.group(2).trim()); @@ -1306,26 +1334,26 @@ public abstract class GCParser { // trackable spotted try { - final Matcher matcherSpottedCache = GCConstants.PATTERN_TRACKABLE_SPOTTEDCACHE.matcher(page); + final MatcherWrapper matcherSpottedCache = new MatcherWrapper(GCConstants.PATTERN_TRACKABLE_SPOTTEDCACHE, page); if (matcherSpottedCache.find() && matcherSpottedCache.groupCount() > 0) { trackable.setSpottedGuid(matcherSpottedCache.group(1)); trackable.setSpottedName(matcherSpottedCache.group(2).trim()); - trackable.setSpottedType(cgTrackable.SPOTTED_CACHE); + trackable.setSpottedType(Trackable.SPOTTED_CACHE); } - final Matcher matcherSpottedUser = GCConstants.PATTERN_TRACKABLE_SPOTTEDUSER.matcher(page); + final MatcherWrapper matcherSpottedUser = new MatcherWrapper(GCConstants.PATTERN_TRACKABLE_SPOTTEDUSER, page); if (matcherSpottedUser.find() && matcherSpottedUser.groupCount() > 0) { trackable.setSpottedGuid(matcherSpottedUser.group(1)); trackable.setSpottedName(matcherSpottedUser.group(2).trim()); - trackable.setSpottedType(cgTrackable.SPOTTED_USER); + trackable.setSpottedType(Trackable.SPOTTED_USER); } if (BaseUtils.matches(page, GCConstants.PATTERN_TRACKABLE_SPOTTEDUNKNOWN)) { - trackable.setSpottedType(cgTrackable.SPOTTED_UNKNOWN); + trackable.setSpottedType(Trackable.SPOTTED_UNKNOWN); } if (BaseUtils.matches(page, GCConstants.PATTERN_TRACKABLE_SPOTTEDOWNER)) { - trackable.setSpottedType(cgTrackable.SPOTTED_OWNER); + trackable.setSpottedType(Trackable.SPOTTED_OWNER); } } catch (Exception e) { // failed to parse trackable last known place @@ -1356,7 +1384,7 @@ public abstract class GCParser { // trackable details & image try { - final Matcher matcherDetailsImage = GCConstants.PATTERN_TRACKABLE_DETAILSIMAGE.matcher(page); + final MatcherWrapper matcherDetailsImage = new MatcherWrapper(GCConstants.PATTERN_TRACKABLE_DETAILSIMAGE, page); if (matcherDetailsImage.find() && matcherDetailsImage.groupCount() > 0) { final String image = StringUtils.trim(matcherDetailsImage.group(3)); final String details = StringUtils.trim(matcherDetailsImage.group(4)); @@ -1375,7 +1403,7 @@ public abstract class GCParser { // trackable logs try { - final Matcher matcherLogs = GCConstants.PATTERN_TRACKABLE_LOG.matcher(page); + final MatcherWrapper matcherLogs = new MatcherWrapper(GCConstants.PATTERN_TRACKABLE_LOG, page); /* * 1. Type (image) * 2. Date @@ -1404,13 +1432,14 @@ public abstract class GCParser { } // Apply the pattern for images in a trackable log entry against each full log (group(0)) - final Matcher matcherLogImages = GCConstants.PATTERN_TRACKABLE_LOG_IMAGES.matcher(matcherLogs.group(0)); + final String logEntry = matcherLogs.group(0); + final MatcherWrapper matcherLogImages = new MatcherWrapper(GCConstants.PATTERN_TRACKABLE_LOG_IMAGES, logEntry); /* * 1. Image URL * 2. Image title */ while (matcherLogImages.find()) { - final cgImage logImage = new cgImage(matcherLogImages.group(1), matcherLogImages.group(2)); + final Image logImage = new Image(matcherLogImages.group(1), matcherLogImages.group(2)); logDone.addLogImage(logImage); } @@ -1418,7 +1447,7 @@ public abstract class GCParser { } } catch (Exception e) { // failed to parse logs - Log.w("GCParser.parseCache: Failed to parse cache logs" + e.toString()); + Log.w("GCParser.parseCache: Failed to parse cache logs", e); } // tracking code @@ -1427,7 +1456,7 @@ public abstract class GCParser { } if (cgeoapplication.getInstance() != null) { - cgeoapplication.getInstance().saveTrackable(trackable); + cgData.saveTrackable(trackable); } return trackable; @@ -1450,11 +1479,11 @@ public abstract class GCParser { * @param friends * retrieve friend logs */ - private static List<LogEntry> loadLogsFromDetails(final String page, final cgCache cache, final boolean friends, final boolean getDataFromPage) { + private static List<LogEntry> loadLogsFromDetails(final String page, final Geocache cache, final boolean friends, final boolean getDataFromPage) { String rawResponse; if (!getDataFromPage) { - final Matcher userTokenMatcher = GCConstants.PATTERN_USERTOKEN.matcher(page); + final MatcherWrapper userTokenMatcher = new MatcherWrapper(GCConstants.PATTERN_USERTOKEN, page); if (!userTokenMatcher.find()) { Log.e("GCParser.loadLogsFromDetails: unable to extract userToken"); return null; @@ -1532,7 +1561,7 @@ public abstract class GCParser { final JSONObject image = images.getJSONObject(i); String url = "http://img.geocaching.com/cache/log/" + image.getString("FileName"); String title = image.getString("Name"); - final cgImage logImage = new cgImage(url, title); + final Image logImage = new Image(url, title); logDone.addLogImage(logImage); } @@ -1553,7 +1582,7 @@ public abstract class GCParser { final List<LogType> types = new ArrayList<LogType>(); - final Matcher typeBoxMatcher = GCConstants.PATTERN_TYPEBOX.matcher(page); + final MatcherWrapper typeBoxMatcher = new MatcherWrapper(GCConstants.PATTERN_TYPEBOX, page); String typesText = null; if (typeBoxMatcher.find()) { if (typeBoxMatcher.groupCount() > 0) { @@ -1563,13 +1592,16 @@ public abstract class GCParser { if (typesText != null) { - final Matcher typeMatcher = GCConstants.PATTERN_TYPE2.matcher(typesText); + final MatcherWrapper typeMatcher = new MatcherWrapper(GCConstants.PATTERN_TYPE2, typesText); while (typeMatcher.find()) { if (typeMatcher.groupCount() > 1) { - final int type = Integer.parseInt(typeMatcher.group(2)); - - if (type > 0) { - types.add(LogType.getById(type)); + try { + int type = Integer.parseInt(typeMatcher.group(2)); + if (type > 0) { + types.add(LogType.getById(type)); + } + } catch (NumberFormatException e) { + Log.e("Error parsing log types", e); } } } @@ -1598,7 +1630,7 @@ public abstract class GCParser { final List<TrackableLog> trackableLogs = new ArrayList<TrackableLog>(); - final Matcher trackableMatcher = GCConstants.PATTERN_TRACKABLE.matcher(page); + final MatcherWrapper trackableMatcher = new MatcherWrapper(GCConstants.PATTERN_TRACKABLE, page); while (trackableMatcher.find()) { if (trackableMatcher.groupCount() > 0) { @@ -1634,7 +1666,7 @@ public abstract class GCParser { params.put("tx", cacheType.guid); } - private static void getExtraOnlineInfo(final cgCache cache, final String page, final CancellableHandler handler) { + private static void getExtraOnlineInfo(final Geocache cache, final String page, final CancellableHandler handler) { if (CancellableHandler.isCancelled(handler)) { return; } @@ -1642,7 +1674,7 @@ public abstract class GCParser { //cache.setLogs(loadLogsFromDetails(page, cache, false)); if (Settings.isFriendLogsWanted()) { CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_logs); - LazyInitializedList<LogEntry> allLogs = cache.getLogs(); + List<LogEntry> allLogs = cache.getLogs(); List<LogEntry> friendLogs = loadLogsFromDetails(page, cache, true, false); if (friendLogs != null) { for (LogEntry log : friendLogs) { @@ -1679,4 +1711,48 @@ public abstract class GCParser { } } + public static boolean uploadModifiedCoordinates(Geocache cache, Geopoint wpt) { + return editModifiedCoordinates(cache, wpt); + } + + public static boolean deleteModifiedCoordinates(Geocache cache) { + return editModifiedCoordinates(cache, null); + } + + 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, ""); + if (StringUtils.isEmpty(userToken)) { + return false; + } + + try { + JSONObject jo; + if (wpt != null) { + jo = new JSONObject().put("dto", (new JSONObject().put("ut", userToken) + .put("data", new JSONObject() + .put("lat", wpt.getLatitudeE6() / 1E6) + .put("lng", wpt.getLongitudeE6() / 1E6)))); + } else { + jo = new JSONObject().put("dto", (new JSONObject().put("ut", userToken))); + } + + 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); + Log.i("Sending to " + uriPrefix + uriSuffix + " :" + jo.toString()); + + if (response != null && response.getStatusLine().getStatusCode() == 200) { + Log.i("GCParser.editModifiedCoordinates - edited on GC.com"); + return true; + } + + } catch (JSONException e) { + Log.e("Unknown exception with json wrap code", e); + } + Log.e("GCParser.deleteModifiedCoordinates - cannot delete modified coords"); + return false; + } + } diff --git a/main/src/cgeo/geocaching/connector/gc/GCSmiliesProvider.java b/main/src/cgeo/geocaching/connector/gc/GCSmiliesProvider.java index 1083a89..eba9301 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCSmiliesProvider.java +++ b/main/src/cgeo/geocaching/connector/gc/GCSmiliesProvider.java @@ -26,7 +26,7 @@ public class GCSmiliesProvider { public final String text; - private Smiley(final String text) { + Smiley(final String text) { this.text = text; } diff --git a/main/src/cgeo/geocaching/connector/gc/IconDecoder.java b/main/src/cgeo/geocaching/connector/gc/IconDecoder.java index 50c0a6a..d3a2959 100644 --- a/main/src/cgeo/geocaching/connector/gc/IconDecoder.java +++ b/main/src/cgeo/geocaching/connector/gc/IconDecoder.java @@ -1,6 +1,7 @@ package cgeo.geocaching.connector.gc; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; +import cgeo.geocaching.Settings; import cgeo.geocaching.enumerations.CacheType; import android.graphics.Bitmap; @@ -10,153 +11,495 @@ import android.graphics.Bitmap; * */ public abstract class IconDecoder { + private static final int CT_TRADITIONAL = 0; + private static final int CT_MULTI = 1; + private static final int CT_MYSTERY = 2; + private static final int CT_EVENT = 3; + private static final int CT_EARTH = 4; + private static final int CT_FOUND = 5; + private static final int CT_OWN = 6; + private static final int CT_MEGAEVENT = 7; + private static final int CT_CITO = 8; + private static final int CT_WEBCAM = 9; + private static final int CT_WHEREIGO = 10; + private static final int CT_VIRTUAL = 11; + private static final int CT_LETTERBOX = 12; - public static void parseMapPNG(final cgCache cache, Bitmap bitmap, UTFGridPosition xy, int zoomlevel) { - if (zoomlevel >= 14) { - parseMapPNG14(cache, bitmap, xy); - } else { - parseMapPNG13(cache, bitmap, xy); + public static boolean parseMapPNG(final Geocache cache, Bitmap bitmap, UTFGridPosition xy, int zoomlevel) { + final int topX = xy.getX() * 4; + final int topY = xy.getY() * 4; + final int bitmapWidth = bitmap.getWidth(); + final int bitmapHeight = bitmap.getHeight(); + + if ((topX < 0) || (topY < 0) || (topX + 4 > bitmapWidth) || (topY + 4 > bitmapHeight)) { + return false; //out of image position } - } - private static final int[] OFFSET_X = new int[] { 0, -1, -1, 0, 1, 1, 1, 0, -1, -2, -2, -2, -2, -1, 0, 1, 2, 2, 2, 2, 2, 1, 0, -1, -2 }; - private static final int[] OFFSET_Y = new int[] { 0, 0, 1, 1, 1, 0, -1, -1, -1, -1, 0, 1, 2, 2, 2, 2, 2, 1, 0, -1, -2, -2, -2, -2, -2 }; + int numberOfDetections = 7; //for level 12 and 13; + if (zoomlevel < 12) { + numberOfDetections = 5; + } + if (zoomlevel > 13) { + numberOfDetections = 13; + } - /** - * The icon decoder walks a spiral around the center pixel position of the cache - * and searches for characteristic colors. - * - * @param cache - * @param bitmap - * @param xy - */ - private static void parseMapPNG13(final cgCache cache, Bitmap bitmap, UTFGridPosition xy) { - final int xCenter = xy.getX() * 4 + 2; - final int yCenter = xy.getY() * 4 + 2; - final int bitmapWidth = bitmap.getWidth(); - final int bitmapHeight = bitmap.getHeight(); + int[] pngType = new int[numberOfDetections]; + for (int x = topX; x < topX + 4; x++) { + for (int y = topY; y < topY + 4; y++) { + int color = bitmap.getPixel(x, y); - int countMulti = 0; - int countFound = 0; + if ((color >>> 24) != 255) { + continue; //transparent pixels (or semi_transparent) are only shadows of border + } - for (int i = 0; i < OFFSET_X.length; i++) { + int r = (color & 0xFF0000) >> 16; + int g = (color & 0xFF00) >> 8; + int b = color & 0xFF; - // assert that we are still in the tile - final int x = xCenter + OFFSET_X[i]; - if (x < 0 || x >= bitmapWidth) { - continue; - } + if (isPixelDuplicated(r, g, b, zoomlevel)) { + continue; + } - final int y = yCenter + OFFSET_Y[i]; - if (y < 0 || y >= bitmapHeight) { - continue; + int type; + if (zoomlevel < 12) { + type = getCacheTypeFromPixel11(r, g, b); + } else { + if (zoomlevel > 13) { + type = getCacheTypeFromPixel14(r, g, b); + } else { + type = getCacheTypeFromPixel13(r, g, b); + } + } + pngType[type]++; } + } - int color = bitmap.getPixel(x, y) & 0x00FFFFFF; + int type = -1; + int count = 0; - // transparent pixels are not interesting - if (color == 0) { - continue; + for (int x = 0; x < pngType.length; x++) { + if (pngType[x] > count) { + count = pngType[x]; + type = x; } + } - int red = (color & 0xFF0000) >> 16; - int green = (color & 0xFF00) >> 8; - int blue = color & 0xFF; + if (count > 1) { // 2 pixels need to detect same type and we say good to go + switch (type) { + case CT_TRADITIONAL: + cache.setType(CacheType.TRADITIONAL); + return true; + case CT_MULTI: + cache.setType(CacheType.MULTI); + return true; + case CT_MYSTERY: + cache.setType(CacheType.MYSTERY); + return true; + case CT_EVENT: + cache.setType(CacheType.EVENT); + return true; + case CT_EARTH: + cache.setType(CacheType.EARTH); + return true; + case CT_FOUND: + cache.setFound(true); + return true; + case CT_OWN: + cache.setOwnerUserId(Settings.getUsername()); + return true; + case CT_MEGAEVENT: + cache.setType(CacheType.MEGA_EVENT); + return true; + case CT_CITO: + cache.setType(CacheType.CITO); + return true; + case CT_WEBCAM: + cache.setType(CacheType.WEBCAM); + return true; + case CT_WHEREIGO: + cache.setType(CacheType.WHERIGO); + return true; + case CT_VIRTUAL: + cache.setType(CacheType.VIRTUAL); + return true; + case CT_LETTERBOX: + cache.setType(CacheType.LETTERBOX); + return true; + } + } + return false; + } - // these are quite sure, so one pixel is enough for matching - if (green > 0x80 && green > red && green > blue) { - cache.setType(CacheType.TRADITIONAL); - return; + /** + * A method that returns true if pixel color appears on more then one cache type and shall be excluded from parsing + * + * @param r + * red value + * @param g + * green value + * @param b + * blue value + * @param zoomlevel + * zoom level of map + * @return true if parsing should not be performed + */ + private static boolean isPixelDuplicated(int r, int g, int b, int zoomlevel) { + if (zoomlevel < 12) { + if (((r == g) && (g == b)) || ((r == 233) && (g == 233) && (b == 234))) { + return true; } - if (blue > 0x80 && blue > red && blue > green) { - cache.setType(CacheType.MYSTERY); - return; + return false; + } + if (zoomlevel > 13) { + if ((r == g) && (g == b)) { + if ((r == 119) || (r == 231) || (r == 5) || (r == 230) || (r == 244) || (r == 93) || (r == 238) || (r == 73) || (r == 9) || (r == 225) || (r == 162) || (r == 153) || (r == 32) || + (r == 50) || (r == 20) || (r == 232) || (r == 224) || (r == 192) || (r == 248) || (r == 152) || (r == 128) || (r == 176) || (r == 184) || (r == 200)) { + return false; + } + return true; } - if (red > 0x90 && blue < 0x10 && green < 0x10) { - cache.setType(CacheType.EVENT); - return; + if ((r == 44) && (b == 44) && (g == 17) || + (r == 228) && (b == 228) && (g == 255) || + (r == 236) && (b == 236) && (g == 255) || + (r == 252) && (b == 225) && (g == 83) || + (r == 252) && (b == 221) && (g == 81) || + (r == 252) && (b == 216) && (g == 79) || + (r == 252) && (b == 211) && (g == 77) || + (r == 251) && (b == 206) && (g == 75) || + (r == 251) && (b == 201) && (g == 73) || + (r == 251) && (b == 196) && (g == 71) || + (r == 251) && (b == 191) && (g == 69) || + (r == 243) && (b == 153) && (g == 36)) { + return true; } + return false; + } + //zoom level 12, 13 + if ((r == 95) && (g == 95) && (b == 95)) { + return true; + } + return false; + } - // next two are hard to distinguish, therefore we sample all pixels of the spiral - if (red > 0xFA && green > 0xD0) { - countMulti++; + /** + * This method returns detected type from specific pixel from geocaching.com live map. + * It was constructed based on classification tree made by Orange (http://orange.biolab.si/) + * Input file was made from every non-transparent pixel of every possible "middle" cache icon from GC map + * + * @param r + * Red component of pixel (from 0 - 255) + * @param g + * Green component of pixel (from 0 - 255) + * @param b + * Blue component of pixel (from 0 - 255) + * @return Value from 0 to 6 representing detected type or state of the cache. + */ + private static int getCacheTypeFromPixel13(int r, int g, int b) { + if (b < 130) { + if (r < 41) { + return CT_MYSTERY; + } + if (g < 74) { + return CT_EVENT; + } + if (r < 130) { + return CT_TRADITIONAL; } - if (red < 0xF3 && red > 0xa0 && green > 0x20 && blue < 0x80) { - countFound++; + if (b < 31) { + return CT_MULTI; } + if (b < 101) { + if (g < 99) { + return r < 178 ? CT_FOUND : CT_EVENT; + } + if (b < 58) { + if (g < 174) { + return CT_FOUND; + } + if (r < 224) { + return CT_OWN; + } + if (b < 49) { + return g < 210 ? CT_FOUND : CT_OWN; + } + if (g < 205) { + return g < 202 ? CT_FOUND : CT_OWN; + } + return CT_FOUND; + } + if (r < 255) { + return CT_FOUND; + } + return g < 236 ? CT_MULTI : CT_FOUND; + } + return g < 182 ? CT_EVENT : CT_MULTI; } - - // now check whether we are sure about found/multi - if (countFound > countMulti && countFound >= 2) { - cache.setFound(true); + if (r < 136) { + return CT_MYSTERY; } - if (countMulti > countFound && countMulti >= 5) { - cache.setType(CacheType.MULTI); + if (b < 168) { + return g < 174 ? CT_EARTH : CT_TRADITIONAL; } + return CT_EARTH; } - // Pixel colors in tile - private final static int COLOR_BORDER_GRAY = 0x5F5F5F; - private final static int COLOR_TRADITIONAL = 0x316013; - private final static int COLOR_MYSTERY = 0x243C97; - private final static int COLOR_MULTI = 0xFFDE19; - private final static int COLOR_FOUND = 0xFF0000; - - // Offset inside cache icon - private final static int POSX_TRADI = 7; - private final static int POSY_TRADI = -12; - private final static int POSX_MULTI = 5; // for orange 8 - private final static int POSY_MULTI = -9; // for orange 10 - private final static int POSX_MYSTERY = 5; - private final static int POSY_MYSTERY = -13; - private final static int POSX_FOUND = 9; - private final static int POSY_FOUND = -6; - /** - * For level 14 find the borders of the icons and then use a single pixel and color to match. + * This method returns detected type from specific pixel from geocaching.com live map level 14 or higher. + * It was constructed based on classification tree made by Orange (http://orange.biolab.si/) + * Input file was made from every non-transparent pixel of every possible "full" cache icon from GC map * - * @param cache - * @param bitmap - * @param xy + * @param r + * Red component of pixel (from 0 - 255) + * @param g + * Green component of pixel (from 0 - 255) + * @param b + * Blue component of pixel (from 0 - 255) + * @return Value from 0 to 6 representing detected type or state of the cache. */ - private static void parseMapPNG14(cgCache cache, Bitmap bitmap, UTFGridPosition xy) { - int x = xy.getX() * 4 + 2; - int y = xy.getY() * 4 + 2; - - // search for left border - int countX = 0; - while ((bitmap.getPixel(x, y) & 0x00FFFFFF) != COLOR_BORDER_GRAY) { - if (--x < 0 || ++countX > 20) { - return; + private static int getCacheTypeFromPixel14(int r, int g, int b) { + if (b < 128) { + if (r < 214) { + if (b < 37) { + if (g < 50) { + if (b < 19) { + if (g < 16) { + if (b < 4) { + return CT_FOUND; + } + return r < 8 ? CT_VIRTUAL : CT_WEBCAM; + } + return CT_FOUND; + } + return CT_WEBCAM; + } + if (b < 24) { + if (b < 18) { + return CT_EARTH; + } + return r < 127 ? CT_TRADITIONAL : CT_EARTH; + } + return CT_FOUND; + } + if (r < 142) { + if (r < 63) { + if (r < 26) { + return CT_CITO; + } + return r < 51 ? CT_WEBCAM : CT_CITO; + } + return g < 107 ? CT_WEBCAM : CT_MULTI; + } + if (g < 138) { + return r < 178 ? CT_MEGAEVENT : CT_EVENT; + } + return b < 71 ? CT_FOUND : CT_EARTH; + } + if (b < 77) { + if (g < 166) { + if (r < 238) { + return g < 120 ? CT_MULTI : CT_OWN; + } + if (b < 57) { + if (r < 254) { + if (b < 39) { + if (r < 239) { + return CT_OWN; + } + if (b < 36) { + if (g < 150) { + if (b < 24) { + return b < 22 ? CT_FOUND : CT_OWN; + } + if (g < 138) { + return b < 25 ? CT_FOUND : CT_OWN; + } + return CT_FOUND; + } + return CT_OWN; + } + if (b < 38) { + if (b < 37) { + if (g < 153) { + return r < 242 ? CT_OWN : CT_FOUND; + } + return CT_OWN; + } + return CT_FOUND; + } + return CT_OWN; + } + if (g < 148) { + return CT_OWN; + } + if (r < 244) { + return CT_FOUND; + } + if (b < 45) { + if (b < 42) { + return CT_FOUND; + } + if (g < 162) { + return r < 245 ? CT_OWN : CT_FOUND; + } + return CT_OWN; + } + return CT_FOUND; + } + return g < 3 ? CT_FOUND : CT_VIRTUAL; + } + return CT_OWN; + } + if (b < 51) { + if (r < 251) { + return CT_OWN; + } + return g < 208 ? CT_EARTH : CT_MULTI; + } + if (b < 63) { + if (r < 247) { + return CT_FOUND; + } + if (r < 250) { + if (g < 169) { + return CT_FOUND; + } + if (g < 192) { + if (b < 54) { + return CT_OWN; + } + if (r < 248) { + return g < 180 ? CT_FOUND : CT_OWN; + } + return CT_OWN; + } + return g < 193 ? CT_FOUND : CT_OWN; + } + return CT_FOUND; + } + return CT_FOUND; + } + if (g < 177) { + return CT_OWN; } + if (r < 239) { + return CT_FOUND; + } + if (g < 207) { + return CT_OWN; + } + return r < 254 ? CT_FOUND : CT_EARTH; } - // search for bottom border - int countY = 0; - while ((bitmap.getPixel(x, y) & 0x00FFFFFF) != 0x000000) { - if (++y >= Tile.TILE_SIZE || ++countY > 20) { - return; + if (r < 203) { + if (b < 218) { + if (g < 158) { + if (g < 71) { + return CT_MYSTERY; + } + return r < 153 ? CT_WHEREIGO : CT_WEBCAM; + } + if (b < 167) { + return r < 157 ? CT_TRADITIONAL : CT_WEBCAM; + } + return CT_WHEREIGO; + } + if (g < 199) { + if (r < 142) { + return CT_LETTERBOX; + } + return r < 175 ? CT_CITO : CT_LETTERBOX; } + if (g < 207) { + return r < 167 ? CT_MEGAEVENT : CT_CITO; + } + return CT_EARTH; } - - try { - if ((bitmap.getPixel(x + POSX_TRADI, y + POSY_TRADI) & 0x00FFFFFF) == COLOR_TRADITIONAL) { - cache.setType(CacheType.TRADITIONAL); - return; + if (b < 224) { + if (g < 235) { + if (b < 163) { + if (r < 249) { + return b < 133 ? CT_FOUND : CT_OWN; + } + return CT_FOUND; + } + if (r < 235) { + if (r < 213) { + if (r < 207) { + return CT_FOUND; + } + if (g < 206) { + return CT_OWN; + } + return g < 207 ? CT_FOUND : CT_OWN; + } + return g < 194 ? CT_OWN : CT_FOUND; + } + if (g < 230) { + return CT_OWN; + } + return b < 205 ? CT_FOUND : CT_OWN; } - if ((bitmap.getPixel(x + POSX_MYSTERY, y + POSY_MYSTERY) & 0x00FFFFFF) == COLOR_MYSTERY) { - cache.setType(CacheType.MYSTERY); - return; + if (r < 238) { + return CT_CITO; } - if ((bitmap.getPixel(x + POSX_MULTI, y + POSY_MULTI) & 0x00FFFFFF) == COLOR_MULTI) { - cache.setType(CacheType.MULTI); - return; + return b < 170 ? CT_EVENT : CT_FOUND; + } + if (r < 251) { + if (r < 210) { + return CT_MYSTERY; } - if ((bitmap.getPixel(x + POSX_FOUND, y + POSY_FOUND) & 0x00FFFFFF) == COLOR_FOUND) { - cache.setFound(true); + if (b < 252) { + if (r < 243) { + if (r < 225) { + return CT_WHEREIGO; + } + if (b < 232) { + if (g < 228) { + return CT_WEBCAM; + } + return r < 231 ? CT_VIRTUAL : CT_TRADITIONAL; + } + if (r < 236) { + return CT_WHEREIGO; + } + return r < 240 ? CT_WEBCAM : CT_WHEREIGO; + } + if (g < 247) { + return r < 245 ? CT_WEBCAM : CT_FOUND; + } + return CT_WHEREIGO; } - } catch (IllegalArgumentException e) { - // intentionally left blank + return CT_LETTERBOX; } + if (r < 255) { + return CT_OWN; + } + return g < 254 ? CT_FOUND : CT_OWN; + } + /** + * This method returns detected type from specific pixel from geocaching.com live map level 11 or lower. + * It was constructed based on classification tree made by Orange (http://orange.biolab.si/) + * Input file was made from every non-transparent pixel of every possible "full" cache icon from GC map + * + * @param r + * Red component of pixel (from 0 - 255) + * @param g + * Green component of pixel (from 0 - 255) + * @param b + * Blue component of pixel (from 0 - 255) + * @return Value from 0 to 4 representing detected type or state of the cache. + */ + private static int getCacheTypeFromPixel11(int r, int g, int b) { + if (g < 136) { + if (r < 90) { + return g < 111 ? CT_MYSTERY : CT_TRADITIONAL; + } + return b < 176 ? CT_EVENT : CT_MYSTERY; + } + if (r < 197) { + return CT_TRADITIONAL; + } + return b < 155 ? CT_MULTI : CT_EARTH; } + } diff --git a/main/src/cgeo/geocaching/connector/gc/Login.java b/main/src/cgeo/geocaching/connector/gc/Login.java index 8bb9866..f3ef290 100644 --- a/main/src/cgeo/geocaching/connector/gc/Login.java +++ b/main/src/cgeo/geocaching/connector/gc/Login.java @@ -10,6 +10,7 @@ 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 ch.boye.httpclientandroidlib.HttpResponse; @@ -26,7 +27,6 @@ import java.util.Date; import java.util.HashMap; import java.util.Locale; import java.util.Map; -import java.util.regex.Matcher; public abstract class Login { @@ -133,7 +133,7 @@ public abstract class Login { } if (loginData.contains("You must validate your account before you can log in.")) { - Log.i("Failued to log in Geocaching.com as " + login.left + " because account needs to be validated first"); + Log.i("Failed to log in Geocaching.com as " + login.left + " because account needs to be validated first"); return StatusCode.UNVALIDATED_ACCOUNT; } @@ -208,7 +208,13 @@ public abstract class Login { setActualLoginStatus(BaseUtils.matches(page, GCConstants.PATTERN_LOGIN_NAME)); if (isActualLoginStatus()) { setActualUserName(BaseUtils.getMatch(page, GCConstants.PATTERN_LOGIN_NAME, true, "???")); - setActualCachesFound(Integer.parseInt(BaseUtils.getMatch(page, GCConstants.PATTERN_CACHES_FOUND, true, "0").replaceAll("[,.]", ""))); + int cachesCount = 0; + try { + cachesCount = Integer.parseInt(BaseUtils.getMatch(page, GCConstants.PATTERN_CACHES_FOUND, true, "0").replaceAll("[,.]", "")); + } catch (NumberFormatException e) { + Log.e("getLoginStatus: bad cache count", e); + } + setActualCachesFound(cachesCount); Settings.setMemberStatus(BaseUtils.getMatch(page, GCConstants.PATTERN_MEMBER_STATUS, true, null)); if ( page.contains(GCConstants.MEMBER_STATUS_RENEW) ) { Settings.setMemberStatus(GCConstants.MEMBER_STATUS_PM); @@ -354,7 +360,7 @@ public abstract class Login { } int count = 1; - final Matcher matcherViewstateCount = GCConstants.PATTERN_VIEWSTATEFIELDCOUNT.matcher(page); + final MatcherWrapper matcherViewstateCount = new MatcherWrapper(GCConstants.PATTERN_VIEWSTATEFIELDCOUNT, page); if (matcherViewstateCount.find()) { try { count = Integer.parseInt(matcherViewstateCount.group(1)); @@ -366,14 +372,13 @@ public abstract class Login { String[] viewstates = new String[count]; // Get the viewstates - int no; - final Matcher matcherViewstates = GCConstants.PATTERN_VIEWSTATES.matcher(page); + final MatcherWrapper matcherViewstates = new MatcherWrapper(GCConstants.PATTERN_VIEWSTATES, page); while (matcherViewstates.find()) { String sno = matcherViewstates.group(1); // number of viewstate - if (sno.length() == 0) { + int no; + if (StringUtils.isEmpty(sno)) { no = 0; - } - else { + } else { try { no = Integer.parseInt(sno); } catch (NumberFormatException e) { diff --git a/main/src/cgeo/geocaching/connector/gc/SearchHandler.java b/main/src/cgeo/geocaching/connector/gc/SearchHandler.java index 201280a..2351554 100644 --- a/main/src/cgeo/geocaching/connector/gc/SearchHandler.java +++ b/main/src/cgeo/geocaching/connector/gc/SearchHandler.java @@ -57,7 +57,7 @@ public class SearchHandler extends Handler { imageView = (ImageView) view.findViewById(R.id.image); - (new getCaptcha(new URL("http://www.google.com/recaptcha/api/image?c=" + recaptchaThread.getChallenge()))).start(); + (new GetCaptchaThread(new URL("http://www.google.com/recaptcha/api/image?c=" + recaptchaThread.getChallenge()))).start(); dlg.setTitle(res.getString(R.string.caches_recaptcha_title)); dlg.setView(view); @@ -79,10 +79,10 @@ public class SearchHandler extends Handler { } } - private class getCaptcha extends Thread { + private class GetCaptchaThread extends Thread { private URL uri = null; - public getCaptcha(URL uriIn) { + public GetCaptchaThread(URL uriIn) { uri = uriIn; } diff --git a/main/src/cgeo/geocaching/connector/gc/Tile.java b/main/src/cgeo/geocaching/connector/gc/Tile.java index 5404446..0e5ffe7 100644 --- a/main/src/cgeo/geocaching/connector/gc/Tile.java +++ b/main/src/cgeo/geocaching/connector/gc/Tile.java @@ -17,6 +17,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; +import java.util.Locale; import java.util.Set; /** @@ -52,11 +53,15 @@ public class Tile { private final Viewport viewPort; public Tile(Geopoint origin, int zoomlevel) { + this(calcX(origin, clippedZoomlevel(zoomlevel)), calcY(origin, clippedZoomlevel(zoomlevel)), clippedZoomlevel(zoomlevel)); + } + + private Tile(int tileX, int tileY, int zoomlevel) { - this.zoomlevel = Math.max(Math.min(zoomlevel, ZOOMLEVEL_MAX), ZOOMLEVEL_MIN); + this.zoomlevel = clippedZoomlevel(zoomlevel); - tileX = calcX(origin); - tileY = calcY(origin); + this.tileX = tileX; + this.tileY = tileY; viewPort = new Viewport(getCoord(new UTFGridPosition(0, 0)), getCoord(new UTFGridPosition(63, 63))); } @@ -65,28 +70,34 @@ public class Tile { return zoomlevel; } + private static int clippedZoomlevel(int zoomlevel) { + return Math.max(Math.min(zoomlevel, ZOOMLEVEL_MAX), ZOOMLEVEL_MIN); + } + /** * Calculate the tile for a Geopoint based on the Spherical Mercator. * * @see <a * href="http://developers.cloudmade.com/projects/tiles/examples/convert-coordinates-to-tile-numbers">Cloudmade</a> */ - private int calcX(final Geopoint origin) { - return (int) ((origin.getLongitude() + 180.0) / 360.0 * NUMBER_OF_TILES[this.zoomlevel]); + private static int calcX(final Geopoint origin, final int zoomlevel) { + // The cut of the fractional part instead of rounding to the nearest integer is intentional and part of the algorithm + return (int) ((origin.getLongitude() + 180.0) / 360.0 * NUMBER_OF_TILES[zoomlevel]); } /** * Calculate the tile for a Geopoint based on the Spherical Mercator. * */ - private int calcY(final Geopoint origin) { + private static int calcY(final Geopoint origin, final int zoomlevel) { // double latRad = Math.toRadians(origin.getLatitude()); // return (int) ((1 - (Math.log(Math.tan(latRad) + (1 / Math.cos(latRad))) / Math.PI)) / 2 * numberOfTiles); // Optimization from Bing double sinLatRad = Math.sin(Math.toRadians(origin.getLatitude())); - return (int) ((0.5 - Math.log((1 + sinLatRad) / (1 - sinLatRad)) / (4 * Math.PI)) * NUMBER_OF_TILES[this.zoomlevel]); + // The cut of the fractional part instead of rounding to the nearest integer is intentional and part of the algorithm + return (int) ((0.5 - Math.log((1 + sinLatRad) / (1 - sinLatRad)) / (4 * Math.PI)) * NUMBER_OF_TILES[zoomlevel]); } public int getX() { @@ -99,7 +110,7 @@ public class Tile { /** * Calculate latitude/longitude for a given x/y position in this tile. - * + * * @see <a * href="http://developers.cloudmade.com/projects/tiles/examples/convert-coordinates-to-tile-numbers">Cloudmade</a> */ @@ -115,12 +126,17 @@ public class Tile { @Override public String toString() { - return String.format("(%d/%d), zoom=%d", tileX, tileY, zoomlevel); + return String.format(Locale.US, "(%d/%d), zoom=%d", tileX, tileY, zoomlevel); } /** * Calculates the maximum possible zoom level where the supplied points - * are covered by adjacent tiles on the east/west axis. + * are covered by at least by the supplied number of + * adjacent tiles on the east/west axis. + * This criterion can be exactly met for even numbers of tiles + * while it may result in one more tile as requested for odd numbers + * of tiles. + * * The order of the points (left/right) is irrelevant. * * @param left @@ -129,17 +145,17 @@ public class Tile { * Second point * @return */ - public static int calcZoomLon(final Geopoint left, final Geopoint right) { + public static int calcZoomLon(final Geopoint left, final Geopoint right, final int numberOfTiles) { int zoom = (int) Math.floor( - Math.log(360.0 / Math.abs(left.getLongitude() - right.getLongitude())) + Math.log(360.0 * numberOfTiles / (2.0 * Math.abs(left.getLongitude() - right.getLongitude()))) / Math.log(2) ); Tile tileLeft = new Tile(left, zoom); Tile tileRight = new Tile(right, zoom); - if (tileLeft.tileX == tileRight.tileX) { + if (Math.abs(tileLeft.tileX - tileRight.tileX) < (numberOfTiles - 1)) { zoom += 1; } @@ -148,7 +164,12 @@ public class Tile { /** * Calculates the maximum possible zoom level where the supplied points - * are covered by adjacent tiles on the north/south axis. + * are covered by at least by the supplied number of + * adjacent tiles on the north/south axis. + * This criterion can be exactly met for even numbers of tiles + * while it may result in one more tile as requested for odd numbers + * of tiles. + * * The order of the points (bottom/top) is irrelevant. * * @param bottom @@ -157,21 +178,21 @@ public class Tile { * Second point * @return */ - public static int calcZoomLat(final Geopoint bottom, final Geopoint top) { + public static int calcZoomLat(final Geopoint bottom, final Geopoint top, final int numberOfTiles) { int zoom = (int) Math.ceil( - Math.log(2.0 * Math.PI / + Math.log(2.0 * Math.PI * numberOfTiles / ( Math.abs( asinh(tanGrad(bottom.getLatitude())) - asinh(tanGrad(top.getLatitude())) - ) + ) * 2.0) ) / Math.log(2) ); Tile tileBottom = new Tile(bottom, zoom); Tile tileTop = new Tile(top, zoom); - if (Math.abs(tileBottom.tileY - tileTop.tileY) > 1) { + if (Math.abs(tileBottom.tileY - tileTop.tileY) > (numberOfTiles - 1)) { zoom -= 1; } @@ -184,7 +205,7 @@ public class Tile { /** * Calculates the inverted hyperbolic sine - * (after Bronstein, Semendjajew: Taschenbuch der Mathematik + * (after Bronstein, Semendjajew: Taschenbuch der Mathematik) * * @param x * @return @@ -232,19 +253,48 @@ public class Tile { } /** - * Calculate needed tiles for the given viewport + * Calculate needed tiles for the given viewport to cover it with + * max 2x2 tiles * * @param viewport * @return */ protected static Set<Tile> getTilesForViewport(final Viewport viewport) { + return getTilesForViewport(viewport, 2, Tile.ZOOMLEVEL_MIN); + } + + /** + * Calculate needed tiles for the given viewport. + * You can define the minimum number of tiles on the longer axis + * and/or the minimum zoom level. + * + * @param viewport + * @param tilesOnAxis + * @param minZoom + * @return + */ + protected static Set<Tile> getTilesForViewport(final Viewport viewport, final int tilesOnAxis, final int minZoom) { Set<Tile> tiles = new HashSet<Tile>(); - int zoom = Math.min(Tile.calcZoomLon(viewport.bottomLeft, viewport.topRight), - Tile.calcZoomLat(viewport.bottomLeft, viewport.topRight)); - tiles.add(new Tile(viewport.bottomLeft, zoom)); - tiles.add(new Tile(new Geopoint(viewport.getLatitudeMin(), viewport.getLongitudeMax()), zoom)); - tiles.add(new Tile(new Geopoint(viewport.getLatitudeMax(), viewport.getLongitudeMin()), zoom)); - tiles.add(new Tile(viewport.topRight, zoom)); + int zoom = Math.max( + Math.min(Tile.calcZoomLon(viewport.bottomLeft, viewport.topRight, tilesOnAxis), + Tile.calcZoomLat(viewport.bottomLeft, viewport.topRight, tilesOnAxis)), + minZoom); + + Tile tileBottomLeft = new Tile(viewport.bottomLeft, zoom); + Tile tileTopRight = new Tile(viewport.topRight, zoom); + + int xLow = Math.min(tileBottomLeft.getX(), tileTopRight.getX()); + int xHigh = Math.max(tileBottomLeft.getX(), tileTopRight.getX()); + + int yLow = Math.min(tileBottomLeft.getY(), tileTopRight.getY()); + int yHigh = Math.max(tileBottomLeft.getY(), tileTopRight.getY()); + + for (int xNum = xLow; xNum <= xHigh; xNum++) { + for (int yNum = yLow; yNum <= yHigh; yNum++) { + tiles.add(new Tile(xNum, yNum, zoom)); + } + } + return tiles; } diff --git a/main/src/cgeo/geocaching/connector/gc/UTFGrid.java b/main/src/cgeo/geocaching/connector/gc/UTFGrid.java index a4eeff5..6d20eb6 100644 --- a/main/src/cgeo/geocaching/connector/gc/UTFGrid.java +++ b/main/src/cgeo/geocaching/connector/gc/UTFGrid.java @@ -29,7 +29,7 @@ public final class UTFGrid { } /** Calculate from a list of positions (x/y) the coords */ - protected static UTFGridPosition getPositionInGrid(List<UTFGridPosition> positions) { + public static UTFGridPosition getPositionInGrid(List<UTFGridPosition> positions) { int minX = GRID_MAXX; int maxX = 0; int minY = GRID_MAXY; diff --git a/main/src/cgeo/geocaching/connector/gc/UTFGridPosition.java b/main/src/cgeo/geocaching/connector/gc/UTFGridPosition.java index e34d277..5965fff 100644 --- a/main/src/cgeo/geocaching/connector/gc/UTFGridPosition.java +++ b/main/src/cgeo/geocaching/connector/gc/UTFGridPosition.java @@ -1,6 +1,7 @@ package cgeo.geocaching.connector.gc; -import java.util.regex.Matcher; +import cgeo.geocaching.utils.MatcherWrapper; + import java.util.regex.Pattern; @@ -35,7 +36,7 @@ public final class UTFGridPosition { * @return */ static UTFGridPosition fromString(String key) { - final Matcher matcher = UTFGridPosition.PATTERN_JSON_KEY.matcher(key); + final MatcherWrapper matcher = new MatcherWrapper(UTFGridPosition.PATTERN_JSON_KEY, key); try { if (matcher.matches()) { final int x = Integer.parseInt(matcher.group(1)); diff --git a/main/src/cgeo/geocaching/connector/oc/OC11XMLParser.java b/main/src/cgeo/geocaching/connector/oc/OC11XMLParser.java new file mode 100644 index 0000000..621032f --- /dev/null +++ b/main/src/cgeo/geocaching/connector/oc/OC11XMLParser.java @@ -0,0 +1,754 @@ +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 17c961a..69cf8a4 100644 --- a/main/src/cgeo/geocaching/connector/oc/OCApiConnector.java +++ b/main/src/cgeo/geocaching/connector/oc/OCApiConnector.java @@ -1,7 +1,7 @@ package cgeo.geocaching.connector.oc; +import cgeo.geocaching.Geocache; import cgeo.geocaching.SearchResult; -import cgeo.geocaching.cgCache; import cgeo.geocaching.connector.capability.ISearchByGeocode; import cgeo.geocaching.network.Parameters; import cgeo.geocaching.utils.CancellableHandler; @@ -21,17 +21,23 @@ public class OCApiConnector extends OCConnector implements ISearchByGeocode { } @Override - public String getLicenseText(final cgCache cache) { + 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"; } @Override public SearchResult searchByGeocode(final String geocode, final String guid, final CancellableHandler handler) { - final cgCache cache = OkapiClient.getCache(geocode); + final Geocache cache = OkapiClient.getCache(geocode); if (cache == null) { return null; } return new SearchResult(cache); } + + @Override + public boolean isActivated() { + // currently always active, but only for details download + return true; + } } diff --git a/main/src/cgeo/geocaching/connector/oc/OCConnector.java b/main/src/cgeo/geocaching/connector/oc/OCConnector.java index c098d12..62dfb4c 100644 --- a/main/src/cgeo/geocaching/connector/oc/OCConnector.java +++ b/main/src/cgeo/geocaching/connector/oc/OCConnector.java @@ -1,7 +1,9 @@ package cgeo.geocaching.connector.oc; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; +import cgeo.geocaching.ICache; import cgeo.geocaching.connector.AbstractConnector; +import cgeo.geocaching.enumerations.CacheRealm; import java.util.regex.Pattern; @@ -32,7 +34,7 @@ public class OCConnector extends AbstractConnector { } @Override - public String getCacheUrl(cgCache cache) { + public String getCacheUrl(Geocache cache) { return getCacheUrlPrefix() + cache.getGeocode(); } @@ -47,8 +49,18 @@ public class OCConnector extends AbstractConnector { } @Override + public boolean isOwner(final ICache cache) { + return false; + } + + @Override protected String getCacheUrlPrefix() { return "http://" + host + "/viewcache.php?wp="; } + @Override + public CacheRealm getCacheRealm() { + return CacheRealm.OC; + } + } diff --git a/main/src/cgeo/geocaching/connector/oc/OCXMLApiConnector.java b/main/src/cgeo/geocaching/connector/oc/OCXMLApiConnector.java new file mode 100644 index 0000000..43fdcfc --- /dev/null +++ b/main/src/cgeo/geocaching/connector/oc/OCXMLApiConnector.java @@ -0,0 +1,67 @@ +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 new file mode 100644 index 0000000..6767b48 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/oc/OCXMLClient.java @@ -0,0 +1,115 @@ +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 87cc3a1..0673605 100644 --- a/main/src/cgeo/geocaching/connector/oc/OkapiClient.java +++ b/main/src/cgeo/geocaching/connector/oc/OkapiClient.java @@ -1,9 +1,9 @@ package cgeo.geocaching.connector.oc; +import cgeo.geocaching.Geocache; +import cgeo.geocaching.Image; import cgeo.geocaching.LogEntry; -import cgeo.geocaching.cgCache; -import cgeo.geocaching.cgImage; -import cgeo.geocaching.cgeoapplication; +import cgeo.geocaching.cgData; import cgeo.geocaching.connector.ConnectorFactory; import cgeo.geocaching.connector.IConnector; import cgeo.geocaching.enumerations.CacheSize; @@ -68,7 +68,7 @@ final public class OkapiClient { private static final String SERVICE_NEAREST = "/okapi/services/caches/search/nearest"; - public static cgCache getCache(final String geoCode) { + 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); @@ -79,7 +79,7 @@ final public class OkapiClient { return parseCache(data); } - public static List<cgCache> getCachesAround(final Geopoint center, IConnector connector) { + 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); @@ -91,7 +91,7 @@ final public class OkapiClient { return parseCaches(data); } - private static List<cgCache> parseCaches(final JSONObject response) { + private static List<Geocache> parseCaches(final JSONObject response) { try { final JSONArray cachesResponse = response.getJSONArray("results"); if (cachesResponse != null) { @@ -102,9 +102,9 @@ final public class OkapiClient { geocodes.add(geocode); } } - List<cgCache> caches = new ArrayList<cgCache>(geocodes.size()); + List<Geocache> caches = new ArrayList<Geocache>(geocodes.size()); for (String geocode : geocodes) { - cgCache cache = getCache(geocode); + Geocache cache = getCache(geocode); if (cache != null) { caches.add(cache); } @@ -117,8 +117,8 @@ final public class OkapiClient { return null; } - private static cgCache parseCache(final JSONObject response) { - final cgCache cache = new cgCache(); + private static Geocache parseCache(final JSONObject response) { + final Geocache cache = new Geocache(); cache.setReliableLatLon(true); try { cache.setGeocode(response.getString(CACHE_CODE)); @@ -153,13 +153,12 @@ final public class OkapiClient { final JSONArray images = response.getJSONArray(CACHE_IMAGES); if (images != null) { - JSONObject imageResponse; for (int i = 0; i < images.length(); i++) { - imageResponse = images.getJSONObject(i); + 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()); - cache.addSpoiler(new cgImage(url, title)); + cache.addSpoiler(new Image(url, title)); } } } @@ -168,12 +167,9 @@ final public class OkapiClient { cache.setLogs(parseLogs(response.getJSONArray(CACHE_LATEST_LOGS))); cache.setHidden(parseDate(response.getString(CACHE_HIDDEN))); - cache.setUpdated(System.currentTimeMillis()); - cache.setDetailedUpdate(cache.getUpdated()); - cache.setDetailed(true); - + cache.setDetailedUpdatedNow(); // save full detailed caches - cgeoapplication.getInstance().saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); + cgData.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); } catch (JSONException e) { Log.e("OkapiClient.parseCache", e); } @@ -239,7 +235,7 @@ final public class OkapiClient { return null; } - private static void setLocation(final cgCache cache, final String location) { + private static void setLocation(final Geocache cache, final String location) { final String latitude = StringUtils.substringBefore(location, "|"); final String longitude = StringUtils.substringAfter(location, "|"); cache.setCoords(new Geopoint(latitude, longitude)); @@ -301,8 +297,18 @@ final public class OkapiClient { return null; } - final String uri = "http://" + host + service; ((OCApiConnector) connector).addAuthentication(params); + params.add("langpref", getPreferredLanguage()); + + final String uri = "http://" + host + service; return Network.requestJSON(uri, params); } + + private static String getPreferredLanguage() { + final String code = Locale.getDefault().getCountry(); + if (StringUtils.isNotBlank(code)) { + return StringUtils.lowerCase(code) + "|en"; + } + return "en"; + } } diff --git a/main/src/cgeo/geocaching/connector/ox/OXConnector.java b/main/src/cgeo/geocaching/connector/ox/OXConnector.java index 98b1656..eec07e3 100644 --- a/main/src/cgeo/geocaching/connector/ox/OXConnector.java +++ b/main/src/cgeo/geocaching/connector/ox/OXConnector.java @@ -1,8 +1,9 @@ package cgeo.geocaching.connector.ox; +import cgeo.geocaching.Geocache; +import cgeo.geocaching.ICache; import cgeo.geocaching.SearchResult; import cgeo.geocaching.Settings; -import cgeo.geocaching.cgCache; import cgeo.geocaching.connector.AbstractConnector; import cgeo.geocaching.connector.capability.ISearchByCenter; import cgeo.geocaching.connector.capability.ISearchByGeocode; @@ -26,7 +27,7 @@ public class OXConnector extends AbstractConnector implements ISearchByCenter, I } @Override - public String getCacheUrl(cgCache cache) { + public String getCacheUrl(Geocache cache) { return getCacheUrlPrefix() + cache.getGeocode(); } @@ -41,14 +42,19 @@ public class OXConnector extends AbstractConnector implements ISearchByCenter, I } @Override - public String getLicenseText(cgCache cache) { + public String getLicenseText(Geocache cache) { // NOT TO BE TRANSLATED return "<a href=\"" + getCacheUrl(cache) + "\">" + getName() + "</a> data licensed under the Creative Commons BY-SA 3.0 License"; } @Override + public boolean isOwner(final ICache cache) { + return false; + } + + @Override public SearchResult searchByGeocode(String geocode, String guid, CancellableHandler handler) { - final cgCache cache = OpenCachingApi.searchByGeoCode(geocode); + final Geocache cache = OpenCachingApi.searchByGeoCode(geocode); if (cache == null) { return null; } @@ -58,7 +64,7 @@ public class OXConnector extends AbstractConnector implements ISearchByCenter, I @Override public SearchResult searchByCenter(Geopoint center) { - Collection<cgCache> caches = OpenCachingApi.searchByCenter(center); + Collection<Geocache> caches = OpenCachingApi.searchByCenter(center); if (caches == null) { return null; } diff --git a/main/src/cgeo/geocaching/connector/ox/OXGPXParser.java b/main/src/cgeo/geocaching/connector/ox/OXGPXParser.java index 377785a..f40a3e8 100644 --- a/main/src/cgeo/geocaching/connector/ox/OXGPXParser.java +++ b/main/src/cgeo/geocaching/connector/ox/OXGPXParser.java @@ -1,6 +1,6 @@ package cgeo.geocaching.connector.ox; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; import cgeo.geocaching.files.GPX10Parser; public class OXGPXParser extends GPX10Parser { @@ -13,7 +13,7 @@ public class OXGPXParser extends GPX10Parser { } @Override - protected void afterParsing(cgCache cache) { + protected void afterParsing(Geocache cache) { cache.setUpdated(System.currentTimeMillis()); if (isDetailed) { cache.setDetailedUpdate(cache.getUpdated()); diff --git a/main/src/cgeo/geocaching/connector/ox/OpenCachingApi.java b/main/src/cgeo/geocaching/connector/ox/OpenCachingApi.java index 17be540..f25e289 100644 --- a/main/src/cgeo/geocaching/connector/ox/OpenCachingApi.java +++ b/main/src/cgeo/geocaching/connector/ox/OpenCachingApi.java @@ -1,7 +1,7 @@ package cgeo.geocaching.connector.ox; +import cgeo.geocaching.Geocache; import cgeo.geocaching.StoredList; -import cgeo.geocaching.cgCache; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.GeopointFormatter; import cgeo.geocaching.network.Network; @@ -20,25 +20,25 @@ public class OpenCachingApi { private static final String DEV_KEY = CryptUtils.rot13("PtqQnHo9RUTht3Np"); - public static cgCache searchByGeoCode(final String geocode) { + public static Geocache searchByGeoCode(final String geocode) { final HttpResponse response = Network.getRequest("http://www.opencaching.com/api/geocache/" + geocode + ".gpx", new Parameters( "Authorization", DEV_KEY, "log_limit", "30", "hint", "true", "description", "html")); - final Collection<cgCache> caches = importCachesFromResponse(response, true); + final Collection<Geocache> caches = importCachesFromResponse(response, true); if (CollectionUtils.isNotEmpty(caches)) { return caches.iterator().next(); } return null; } - private static Collection<cgCache> importCachesFromResponse(final HttpResponse response, final boolean isDetailed) { + private static Collection<Geocache> importCachesFromResponse(final HttpResponse response, final boolean isDetailed) { if (response == null) { return Collections.emptyList(); } - Collection<cgCache> caches; + Collection<Geocache> caches; try { caches = new OXGPXParser(StoredList.STANDARD_LIST_ID, isDetailed).parse(response.getEntity().getContent(), null); } catch (Exception e) { @@ -48,7 +48,7 @@ public class OpenCachingApi { return caches; } - public static Collection<cgCache> searchByCenter(final Geopoint center) { + public static Collection<Geocache> searchByCenter(final Geopoint center) { final HttpResponse response = Network.getRequest("http://www.opencaching.com/api/geocache/.gpx", new Parameters( "Authorization", DEV_KEY, diff --git a/main/src/cgeo/geocaching/enumerations/CacheAttribute.java b/main/src/cgeo/geocaching/enumerations/CacheAttribute.java index a31b0cc..530869f 100644 --- a/main/src/cgeo/geocaching/enumerations/CacheAttribute.java +++ b/main/src/cgeo/geocaching/enumerations/CacheAttribute.java @@ -5,133 +5,190 @@ import cgeo.geocaching.cgeoapplication; import org.apache.commons.lang3.StringUtils; +import android.util.SparseArray; + import java.util.Collections; import java.util.HashMap; import java.util.Map; - public enum CacheAttribute { - UNKNOWN(0, "unknown", R.drawable.attribute__strikethru, R.string.attribute_unknown_yes, R.string.attribute_unknown_no), - DOGS(1, "dogs", R.drawable.attribute_dogs, R.string.attribute_dogs_yes, R.string.attribute_dogs_no), - FEE(2, "fee", R.drawable.attribute_fee, R.string.attribute_fee_yes, R.string.attribute_fee_no), - RAPPELLING(3, "rappelling", R.drawable.attribute_rappelling, R.string.attribute_rappelling_yes, R.string.attribute_rappelling_no), - BOAT(4, "boat", R.drawable.attribute_boat, R.string.attribute_boat_yes, R.string.attribute_boat_no), - SCUBA(5, "scuba", R.drawable.attribute_scuba, R.string.attribute_scuba_yes, R.string.attribute_scuba_no), - KIDS(6, "kids", R.drawable.attribute_kids, R.string.attribute_kids_yes, R.string.attribute_kids_no), - ONEHOUR(7, "onehour", R.drawable.attribute_onehour, R.string.attribute_onehour_yes, R.string.attribute_onehour_no), - SCENIC(8, "scenic", R.drawable.attribute_scenic, R.string.attribute_scenic_yes, R.string.attribute_scenic_no), - HIKING(9, "hiking", R.drawable.attribute_hiking, R.string.attribute_hiking_yes, R.string.attribute_hiking_no), - CLIMBING(10, "climbing", R.drawable.attribute_climbing, R.string.attribute_climbing_yes, R.string.attribute_climbing_no), - WADING(11, "wading", R.drawable.attribute_wading, R.string.attribute_wading_yes, R.string.attribute_wading_no), - SWIMMING(12, "swimming", R.drawable.attribute_swimming, R.string.attribute_swimming_yes, R.string.attribute_swimming_no), - AVAILABLE(13, "available", R.drawable.attribute_available, R.string.attribute_available_yes, R.string.attribute_available_no), - NIGHT(14, "night", R.drawable.attribute_night, R.string.attribute_night_yes, R.string.attribute_night_no), - WINTER(15, "winter", R.drawable.attribute_winter, R.string.attribute_winter_yes, R.string.attribute_winter_no), - POISONOAK(17, "poisonoak", R.drawable.attribute_poisonoak, R.string.attribute_poisonoak_yes, R.string.attribute_poisonoak_no), - DANGEROUSANIMALS(18, "dangerousanimals", R.drawable.attribute_dangerousanimals, R.string.attribute_dangerousanimals_yes, R.string.attribute_dangerousanimals_no), - TICKS(19, "ticks", R.drawable.attribute_ticks, R.string.attribute_ticks_yes, R.string.attribute_ticks_no), - MINE(29, "mine", R.drawable.attribute_mine, R.string.attribute_mine_yes, R.string.attribute_mine_no), - CLIFF(21, "cliff", R.drawable.attribute_cliff, R.string.attribute_cliff_yes, R.string.attribute_cliff_no), - HUNTING(22, "hunting", R.drawable.attribute_hunting, R.string.attribute_hunting_yes, R.string.attribute_hunting_no), - DANGER(23, "danger", R.drawable.attribute_danger, R.string.attribute_danger_yes, R.string.attribute_danger_no), - WHEELCHAIR(24, "wheelchair", R.drawable.attribute_wheelchair, R.string.attribute_wheelchair_yes, R.string.attribute_wheelchair_no), - PARKING(25, "parking", R.drawable.attribute_parking, R.string.attribute_parking_yes, R.string.attribute_parking_no), - PUBLIC(26, "public", R.drawable.attribute_public, R.string.attribute_public_yes, R.string.attribute_public_no), - WATER(27, "water", R.drawable.attribute_water, R.string.attribute_water_yes, R.string.attribute_water_no), - RESTROOMS(28, "restrooms", R.drawable.attribute_restrooms, R.string.attribute_restrooms_yes, R.string.attribute_restrooms_no), - PHONE(29, "phone", R.drawable.attribute_phone, R.string.attribute_phone_yes, R.string.attribute_phone_no), - PICNIC(30, "picnic", R.drawable.attribute_picnic, R.string.attribute_picnic_yes, R.string.attribute_picnic_no), - CAMPING(31, "camping", R.drawable.attribute_camping, R.string.attribute_camping_yes, R.string.attribute_camping_no), - BICYCLES(32, "bicycles", R.drawable.attribute_bicycles, R.string.attribute_bicycles_yes, R.string.attribute_bicycles_no), - MOTORCYCLES(33, "motorcycles", R.drawable.attribute_motorcycles, R.string.attribute_motorcycles_yes, R.string.attribute_motorcycles_no), - QUADS(34, "quads", R.drawable.attribute_quads, R.string.attribute_quads_yes, R.string.attribute_quads_no), - JEEPS(35, "jeeps", R.drawable.attribute_jeeps, R.string.attribute_jeeps_yes, R.string.attribute_jeeps_no), - SNOWMOBILES(36, "snowmobiles", R.drawable.attribute_snowmobiles, R.string.attribute_snowmobiles_yes, R.string.attribute_snowmobiles_no), - HORSES(37, "horses", R.drawable.attribute_horses, R.string.attribute_horses_yes, R.string.attribute_horses_no), - CAMPFIRES(38, "campfires", R.drawable.attribute_campfires, R.string.attribute_campfires_yes, R.string.attribute_campfires_no), - THORN(39, "thorn", R.drawable.attribute_thorn, R.string.attribute_thorn_yes, R.string.attribute_thorn_no), - STEALTH(40, "stealth", R.drawable.attribute_stealth, R.string.attribute_stealth_yes, R.string.attribute_stealth_no), - STROLLER(41, "stroller", R.drawable.attribute_stroller, R.string.attribute_stroller_yes, R.string.attribute_stroller_no), - FIRSTAID(42, "firstaid", R.drawable.attribute_firstaid, R.string.attribute_firstaid_yes, R.string.attribute_firstaid_no), - COW(43, "cow", R.drawable.attribute_cow, R.string.attribute_cow_yes, R.string.attribute_cow_no), - FLASHLIGHT(44, "flashlight", R.drawable.attribute_flashlight, R.string.attribute_flashlight_yes, R.string.attribute_flashlight_no), - LANDF(45, "landf", R.drawable.attribute_landf, R.string.attribute_landf_yes, R.string.attribute_landf_no), - RV(46, "rv", R.drawable.attribute_rv, R.string.attribute_rv_yes, R.string.attribute_rv_no), - FIELD_PUZZLE(47, "field_puzzle", R.drawable.attribute_field_puzzle, R.string.attribute_field_puzzle_yes, R.string.attribute_field_puzzle_no), - UV(48, "uv", R.drawable.attribute_uv, R.string.attribute_uv_yes, R.string.attribute_uv_no), - SNOWSHOES(49, "snowshoes", R.drawable.attribute_snowshoes, R.string.attribute_snowshoes_yes, R.string.attribute_snowshoes_no), - SKIIS(50, "skiis", R.drawable.attribute_skiis, R.string.attribute_skiis_yes, R.string.attribute_skiis_no), - SPECIAL_TOOLS(51, "s_tool", R.drawable.attribute_s_tool, R.string.attribute_s_tool_yes, R.string.attribute_s_tool_no), - NIGHTCACHE(52, "nightcache", R.drawable.attribute_nightcache, R.string.attribute_nightcache_yes, R.string.attribute_nightcache_no), - PARKNGRAB(53, "parkngrab", R.drawable.attribute_parkngrab, R.string.attribute_parkngrab_yes, R.string.attribute_parkngrab_no), - ABANDONED_BUILDING(54, "abandonedbuilding", R.drawable.attribute_abandonedbuilding, R.string.attribute_abandonedbuilding_yes, R.string.attribute_abandonedbuilding_no), - HIKE_SHORT(55, "hike_short", R.drawable.attribute_hike_short, R.string.attribute_hike_short_yes, R.string.attribute_hike_short_no), - HIKE_MED(56, "hike_med", R.drawable.attribute_hike_med, R.string.attribute_hike_med_yes, R.string.attribute_hike_med_no), - HIKE_LONG(57, "hike_long", R.drawable.attribute_hike_long, R.string.attribute_hike_long_yes, R.string.attribute_hike_long_no), - FUEL(58, "fuel", R.drawable.attribute_fuel, R.string.attribute_fuel_yes, R.string.attribute_fuel_no), - FOOD(59, "food", R.drawable.attribute_food, R.string.attribute_food_yes, R.string.attribute_food_no), - WIRELESS_BEACON(60, "wirelessbeacon", R.drawable.attribute_wirelessbeacon, R.string.attribute_wirelessbeacon_yes, R.string.attribute_wirelessbeacon_no), - PARTNERSHIP(61, "partnership", R.drawable.attribute_partnership, R.string.attribute_partnership_yes, R.string.attribute_partnership_no), - SEASONAL(62, "seasonal", R.drawable.attribute_seasonal, R.string.attribute_seasonal_yes, R.string.attribute_seasonal_no), - TOURIST_OK(63, "touristok", R.drawable.attribute_touristok, R.string.attribute_touristok_yes, R.string.attribute_touristok_no), - TREECLIMBING(64, "treeclimbing", R.drawable.attribute_treeclimbing, R.string.attribute_treeclimbing_yes, R.string.attribute_treeclimbing_no), - FRONTYARD(65, "frontyard", R.drawable.attribute_frontyard, R.string.attribute_frontyard_yes, R.string.attribute_frontyard_no), - TEAMWORK(66, "teamwork", R.drawable.attribute_teamwork, R.string.attribute_teamwork_yes, R.string.attribute_teamwork_no); - - private static final String INTERNAL_PRE = "attribute_"; + // THIS LIST IS GENERATED: don't change anything here but in + // project/attributes/makeEnum.sh + 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), + QUADS(34, -1, "quads", R.drawable.attribute_quads, R.string.attribute_quads_yes, R.string.attribute_quads_no), + JEEPS(35, -1, "jeeps", R.drawable.attribute_jeeps, R.string.attribute_jeeps_yes, R.string.attribute_jeeps_no), + SNOWMOBILES(36, -1, "snowmobiles", R.drawable.attribute_snowmobiles, R.string.attribute_snowmobiles_yes, R.string.attribute_snowmobiles_no), + HORSES(37, -1, "horses", R.drawable.attribute_horses, R.string.attribute_horses_yes, R.string.attribute_horses_no), + CAMPFIRES(38, -1, "campfires", R.drawable.attribute_campfires, R.string.attribute_campfires_yes, R.string.attribute_campfires_no), + RV(46, -1, "rv", R.drawable.attribute_rv, R.string.attribute_rv_yes, R.string.attribute_rv_no), + KIDS(6, 59, "kids", R.drawable.attribute_kids, R.string.attribute_kids_yes, R.string.attribute_kids_no), + 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), + 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), + NIGHT(14, -1, "night", R.drawable.attribute_night, R.string.attribute_night_yes, R.string.attribute_night_no), + WINTER(15, -1, "winter", R.drawable.attribute_winter, R.string.attribute_winter_yes, R.string.attribute_winter_no), + STEALTH(40, -1, "stealth", R.drawable.attribute_stealth, R.string.attribute_stealth_yes, R.string.attribute_stealth_no), + FIRSTAID(42, -1, "firstaid", R.drawable.attribute_firstaid, R.string.attribute_firstaid_yes, R.string.attribute_firstaid_no), + COW(43, -1, "cow", R.drawable.attribute_cow, R.string.attribute_cow_yes, R.string.attribute_cow_no), + FIELD_PUZZLE(47, -1, "field_puzzle", R.drawable.attribute_field_puzzle, R.string.attribute_field_puzzle_yes, R.string.attribute_field_puzzle_no), + NIGHTCACHE(52, 1, "nightcache", R.drawable.attribute_nightcache, R.string.attribute_nightcache_yes, R.string.attribute_nightcache_no), + PARKNGRAB(53, 24, "parkngrab", R.drawable.attribute_parkngrab, R.string.attribute_parkngrab_yes, R.string.attribute_parkngrab_no), + ABANDONEDBUILDING(54, -1, "abandonedbuilding", R.drawable.attribute_abandonedbuilding, R.string.attribute_abandonedbuilding_yes, R.string.attribute_abandonedbuilding_no), + HIKE_SHORT(55, -1, "hike_short", R.drawable.attribute_hike_short, R.string.attribute_hike_short_yes, R.string.attribute_hike_short_no), + HIKE_MED(56, -1, "hike_med", R.drawable.attribute_hike_med, R.string.attribute_hike_med_yes, R.string.attribute_hike_med_no), + HIKE_LONG(57, -1, "hike_long", R.drawable.attribute_hike_long, R.string.attribute_hike_long_yes, R.string.attribute_hike_long_no), + SEASONAL(62, 60, "seasonal", R.drawable.attribute_seasonal, R.string.attribute_seasonal_yes, R.string.attribute_seasonal_no), + TOURISTOK(63, -1, "touristok", R.drawable.attribute_touristok, R.string.attribute_touristok_yes, R.string.attribute_touristok_no), + FRONTYARD(65, -1, "frontyard", R.drawable.attribute_frontyard, R.string.attribute_frontyard_yes, R.string.attribute_frontyard_no), + TEAMWORK(66, -1, "teamwork", R.drawable.attribute_teamwork, R.string.attribute_teamwork_yes, R.string.attribute_teamwork_no), + LANDF(45, -1, "landf", R.drawable.attribute_landf, R.string.attribute_landf_yes, R.string.attribute_landf_no), + PARTNERSHIP(61, -1, "partnership", R.drawable.attribute_partnership, R.string.attribute_partnership_yes, R.string.attribute_partnership_no), + FEE(2, 36, "fee", R.drawable.attribute_fee, R.string.attribute_fee_yes, R.string.attribute_fee_no), + RAPPELLING(3, 49, "rappelling", R.drawable.attribute_rappelling, R.string.attribute_rappelling_yes, R.string.attribute_rappelling_no), + BOAT(4, 52, "boat", R.drawable.attribute_boat, R.string.attribute_boat_yes, R.string.attribute_boat_no), + SCUBA(5, 51, "scuba", R.drawable.attribute_scuba, R.string.attribute_scuba_yes, R.string.attribute_scuba_no), + FLASHLIGHT(44, 48, "flashlight", R.drawable.attribute_flashlight, R.string.attribute_flashlight_yes, R.string.attribute_flashlight_no), + UV(48, -1, "uv", R.drawable.attribute_uv, R.string.attribute_uv_yes, R.string.attribute_uv_no), + SNOWSHOES(49, -1, "snowshoes", R.drawable.attribute_snowshoes, R.string.attribute_snowshoes_yes, R.string.attribute_snowshoes_no), + SKIIS(50, -1, "skiis", R.drawable.attribute_skiis, R.string.attribute_skiis_yes, R.string.attribute_skiis_no), + S_TOOL(51, 46, "s_tool", R.drawable.attribute_s_tool, R.string.attribute_s_tool_yes, R.string.attribute_s_tool_no), + WIRELESSBEACON(60, -1, "wirelessbeacon", R.drawable.attribute_wirelessbeacon, R.string.attribute_wirelessbeacon_yes, R.string.attribute_wirelessbeacon_no), + TREECLIMBING(64, -1, "treeclimbing", R.drawable.attribute_treeclimbing, R.string.attribute_treeclimbing_yes, R.string.attribute_treeclimbing_no), + POISONOAK(17, 16, "poisonoak", R.drawable.attribute_poisonoak, R.string.attribute_poisonoak_yes, R.string.attribute_poisonoak_no), + DANGEROUSANIMALS(18, 17, "dangerousanimals", R.drawable.attribute_dangerousanimals, R.string.attribute_dangerousanimals_yes, R.string.attribute_dangerousanimals_no), + TICKS(19, 14, "ticks", R.drawable.attribute_ticks, R.string.attribute_ticks_yes, R.string.attribute_ticks_no), + MINE(20, 15, "mine", R.drawable.attribute_mine, R.string.attribute_mine_yes, R.string.attribute_mine_no), + CLIFF(21, 11, "cliff", R.drawable.attribute_cliff, R.string.attribute_cliff_yes, R.string.attribute_cliff_no), + HUNTING(22, 12, "hunting", R.drawable.attribute_hunting, R.string.attribute_hunting_yes, R.string.attribute_hunting_no), + DANGER(23, 9, "danger", R.drawable.attribute_danger, R.string.attribute_danger_yes, R.string.attribute_danger_no), + THORN(39, 13, "thorn", R.drawable.attribute_thorn, R.string.attribute_thorn_yes, R.string.attribute_thorn_no), + WHEELCHAIR(24, -1, "wheelchair", R.drawable.attribute_wheelchair, R.string.attribute_wheelchair_yes, R.string.attribute_wheelchair_no), + PARKING(25, 18, "parking", R.drawable.attribute_parking, R.string.attribute_parking_yes, R.string.attribute_parking_no), + PUBLIC(26, 19, "public", R.drawable.attribute_public, R.string.attribute_public_yes, R.string.attribute_public_no), + WATER(27, 20, "water", R.drawable.attribute_water, R.string.attribute_water_yes, R.string.attribute_water_no), + RESTROOMS(28, 21, "restrooms", R.drawable.attribute_restrooms, R.string.attribute_restrooms_yes, R.string.attribute_restrooms_no), + PHONE(29, 22, "phone", R.drawable.attribute_phone, R.string.attribute_phone_yes, R.string.attribute_phone_no), + PICNIC(30, -1, "picnic", R.drawable.attribute_picnic, R.string.attribute_picnic_yes, R.string.attribute_picnic_no), + CAMPING(31, -1, "camping", R.drawable.attribute_camping, R.string.attribute_camping_yes, R.string.attribute_camping_no), + STROLLER(41, -1, "stroller", R.drawable.attribute_stroller, R.string.attribute_stroller_yes, R.string.attribute_stroller_no), + FUEL(58, -1, "fuel", R.drawable.attribute_fuel, R.string.attribute_fuel_yes, R.string.attribute_fuel_no), + FOOD(59, -1, "food", R.drawable.attribute_food, R.string.attribute_food_yes, R.string.attribute_food_no), + OC_ONLY(-1, 6, "oc_only", R.drawable.attribute_oc_only, R.string.attribute_oc_only_yes, R.string.attribute_oc_only_no), + LINK_ONLY(-1, 7, "link_only", R.drawable.attribute_link_only, R.string.attribute_link_only_yes, R.string.attribute_link_only_no), + LETTERBOX(-1, 8, "letterbox", R.drawable.attribute_letterbox, R.string.attribute_letterbox_yes, R.string.attribute_letterbox_no), + RAILWAY(-1, 10, "railway", R.drawable.attribute_railway, R.string.attribute_railway_yes, R.string.attribute_railway_no), + 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), + 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), + INSIDE(-1, 33, "inside", R.drawable.attribute_inside, R.string.attribute_inside_yes, R.string.attribute_inside_no), + IN_WATER(-1, 34, "in_water", R.drawable.attribute_in_water, R.string.attribute_in_water_yes, R.string.attribute_in_water_no), + NO_GPS(-1, 35, "no_gps", R.drawable.attribute_no_gps, R.string.attribute_no_gps_yes, R.string.attribute_no_gps_no), + OVERNIGHT(-1, 37, "overnight", R.drawable.attribute_overnight, R.string.attribute_overnight_yes, R.string.attribute_overnight_no), + SPECIFIC_TIMES(-1, 39, "specific_times", R.drawable.attribute_specific_times, R.string.attribute_specific_times_yes, R.string.attribute_specific_times_no), + DAY(-1, 40, "day", R.drawable.attribute_day, R.string.attribute_day_yes, R.string.attribute_day_no), + TIDE(-1, 41, "tide", R.drawable.attribute_tide, R.string.attribute_tide_yes, R.string.attribute_tide_no), + ALL_SEASONS(-1, 42, "all_seasons", R.drawable.attribute_all_seasons, R.string.attribute_all_seasons_yes, R.string.attribute_all_seasons_no), + BREEDING(-1, 43, "breeding", R.drawable.attribute_breeding, R.string.attribute_breeding_yes, R.string.attribute_breeding_no), + SNOW_PROOF(-1, 44, "snow_proof", R.drawable.attribute_snow_proof, R.string.attribute_snow_proof_yes, R.string.attribute_snow_proof_no), + COMPASS(-1, 47, "compass", R.drawable.attribute_compass, R.string.attribute_compass_yes, R.string.attribute_compass_no), + CAVE(-1, 50, "cave", R.drawable.attribute_cave, R.string.attribute_cave_yes, R.string.attribute_cave_no), + AIRCRAFT(-1, 53, "aircraft", R.drawable.attribute_aircraft, R.string.attribute_aircraft_yes, R.string.attribute_aircraft_no), + INVESTIGATION(-1, 54, "investigation", R.drawable.attribute_investigation, R.string.attribute_investigation_yes, R.string.attribute_investigation_no), + PUZZLE(-1, 55, "puzzle", R.drawable.attribute_puzzle, R.string.attribute_puzzle_yes, R.string.attribute_puzzle_no), + 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 + private static final String INTERNAL_YES = "_yes"; private static final String INTERNAL_NO = "_no"; - public final int id; - public final String gcRawName; + public static final int NO_ID = -1; + + public final int gcid; + public final int ocid; + public final String rawName; public final int drawableId; public final int stringIdYes; public final int stringIdNo; - private CacheAttribute(final int id, final String gcRawName, final int drawableId, final int stringIdYes, final int stringIdNo) { - this.id = id; - this.gcRawName = gcRawName; + CacheAttribute(final int gcid, final int ocid, final String rawName, + final int drawableId, final int stringIdYes, final int stringIdNo) { + this.gcid = gcid; + this.ocid = ocid; + this.rawName = rawName; this.drawableId = drawableId; this.stringIdYes = stringIdYes; this.stringIdNo = stringIdNo; } + /** + * get localized text + * + * @param enabled + * true: for positive text, false: for negative text + * @return the localized text + */ public String getL10n(final boolean enabled) { - return cgeoapplication.getInstance().getResources().getString(enabled ? stringIdYes : stringIdNo); + return cgeoapplication.getInstance().getResources().getString( + enabled ? stringIdYes : stringIdNo); } private final static Map<String, CacheAttribute> FIND_BY_GCRAWNAME; - static { final HashMap<String, CacheAttribute> mapGcRawNames = new HashMap<String, CacheAttribute>(); for (CacheAttribute attr : values()) { - mapGcRawNames.put(attr.gcRawName, attr); + mapGcRawNames.put(attr.rawName, attr); } FIND_BY_GCRAWNAME = Collections.unmodifiableMap(mapGcRawNames); } - public static CacheAttribute getById(final int id) { + private final static SparseArray<CacheAttribute> FIND_BY_GCID = new SparseArray<CacheAttribute>(); + static { for (CacheAttribute attr : values()) { - if (attr.id == id) { - return attr; + if (attr.gcid != NO_ID) { + FIND_BY_GCID.put(attr.gcid, attr); } } - return UNKNOWN; } - public static CacheAttribute getByGcRawName(final String gcRawName) { - final CacheAttribute result = gcRawName != null ? FIND_BY_GCRAWNAME.get(gcRawName) : null; - if (result == null) { - return UNKNOWN; + 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); + } } - return result; + } + + public static CacheAttribute getByRawName(final String rawName) { + return rawName != null ? FIND_BY_GCRAWNAME.get(rawName) : null; + } + + public static CacheAttribute getByGcId(final int gcid) { + return FIND_BY_GCID.get(gcid); + } + + public static CacheAttribute getByOcId(final int ocid) { + return FIND_BY_OCID.get(ocid); } public static String trimAttributeName(String attributeName) { if (null == attributeName) { return ""; } - return attributeName.replace(INTERNAL_PRE, "").replace(INTERNAL_YES, "").replace(INTERNAL_NO, "").trim(); + return attributeName.replace(INTERNAL_YES, "").replace(INTERNAL_NO, "").trim(); } public static boolean isEnabled(final String attributeName) { @@ -139,6 +196,6 @@ public enum CacheAttribute { } public String getAttributeName(final boolean yes) { - return gcRawName + (yes ? INTERNAL_YES : INTERNAL_NO); + return rawName + (yes ? INTERNAL_YES : INTERNAL_NO); } } diff --git a/main/src/cgeo/geocaching/enumerations/CacheRealm.java b/main/src/cgeo/geocaching/enumerations/CacheRealm.java new file mode 100644 index 0000000..5a203ab --- /dev/null +++ b/main/src/cgeo/geocaching/enumerations/CacheRealm.java @@ -0,0 +1,22 @@ +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/CacheSize.java b/main/src/cgeo/geocaching/enumerations/CacheSize.java index 155c9a5..726ebe2 100644 --- a/main/src/cgeo/geocaching/enumerations/CacheSize.java +++ b/main/src/cgeo/geocaching/enumerations/CacheSize.java @@ -5,6 +5,7 @@ import cgeo.geocaching.cgeoapplication; import java.util.Collections; import java.util.HashMap; +import java.util.Locale; import java.util.Map; /** @@ -24,7 +25,7 @@ public enum CacheSize { public final int comparable; private final int stringId; - private CacheSize(String id, int comparable, int stringId) { + CacheSize(String id, int comparable, int stringId) { this.id = id; this.comparable = comparable; this.stringId = stringId; @@ -34,7 +35,7 @@ public enum CacheSize { static { final HashMap<String, CacheSize> mapping = new HashMap<String, CacheSize>(); for (CacheSize cs : values()) { - mapping.put(cs.id.toLowerCase(), cs); + mapping.put(cs.id.toLowerCase(Locale.US), cs); } // add medium as additional string for Regular mapping.put("medium", CacheSize.REGULAR); @@ -51,7 +52,7 @@ public enum CacheSize { return result; } // only if String was not found, normalize it - final CacheSize resultNormalized = CacheSize.FIND_BY_ID.get(id.toLowerCase().trim()); + final CacheSize resultNormalized = CacheSize.FIND_BY_ID.get(id.toLowerCase(Locale.US).trim()); if (resultNormalized != null) { return resultNormalized; } diff --git a/main/src/cgeo/geocaching/enumerations/CacheType.java b/main/src/cgeo/geocaching/enumerations/CacheType.java index 730c989..528d3fa 100644 --- a/main/src/cgeo/geocaching/enumerations/CacheType.java +++ b/main/src/cgeo/geocaching/enumerations/CacheType.java @@ -1,10 +1,12 @@ package cgeo.geocaching.enumerations; +import cgeo.geocaching.ICache; import cgeo.geocaching.R; import cgeo.geocaching.cgeoapplication; import java.util.Collections; import java.util.HashMap; +import java.util.Locale; import java.util.Map; /** @@ -44,7 +46,7 @@ public enum CacheType { private final int stringId; public final int markerId; - private CacheType(String id, String pattern, String guid, int stringId, int markerId) { + CacheType(String id, String pattern, String guid, int stringId, int markerId) { this.id = id; this.pattern = pattern; this.guid = guid; @@ -59,14 +61,14 @@ public enum CacheType { final HashMap<String, CacheType> mappingPattern = new HashMap<String, CacheType>(); for (CacheType ct : values()) { mappingId.put(ct.id, ct); - mappingPattern.put(ct.pattern.toLowerCase(), ct); + mappingPattern.put(ct.pattern.toLowerCase(Locale.US), ct); } FIND_BY_ID = Collections.unmodifiableMap(mappingId); FIND_BY_PATTERN = Collections.unmodifiableMap(mappingPattern); } public static CacheType getById(final String id) { - final CacheType result = (id != null) ? CacheType.FIND_BY_ID.get(id.toLowerCase().trim()) : null; + final CacheType result = (id != null) ? CacheType.FIND_BY_ID.get(id.toLowerCase(Locale.US).trim()) : null; if (result == null) { return UNKNOWN; } @@ -74,7 +76,7 @@ public enum CacheType { } public static CacheType getByPattern(final String pattern) { - final CacheType result = (pattern != null) ? CacheType.FIND_BY_PATTERN.get(pattern.toLowerCase().trim()) : null; + final CacheType result = (pattern != null) ? CacheType.FIND_BY_PATTERN.get(pattern.toLowerCase(Locale.US).trim()) : null; if (result == null) { return UNKNOWN; } @@ -88,4 +90,25 @@ public enum CacheType { public boolean isEvent() { return CacheType.EVENT == this || CacheType.MEGA_EVENT == this || CacheType.CITO == this || CacheType.LOSTANDFOUND == this; } + + @Override + public String toString() { + return getL10n(); + } + + /** + * 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. + */ + public boolean contains(ICache cache) { + if (cache == null) { + return false; + } + if (this == ALL) { + return true; + } + return cache.getType() == this; + } } diff --git a/main/src/cgeo/geocaching/enumerations/LiveMapStrategy.java b/main/src/cgeo/geocaching/enumerations/LiveMapStrategy.java index e9dbc5d..5f5b2c9 100644 --- a/main/src/cgeo/geocaching/enumerations/LiveMapStrategy.java +++ b/main/src/cgeo/geocaching/enumerations/LiveMapStrategy.java @@ -26,7 +26,7 @@ public interface LiveMapStrategy { public final EnumSet<StrategyFlag> flags; private final int stringId; - private Strategy(int id, EnumSet<StrategyFlag> flags, int stringId) { + Strategy(int id, EnumSet<StrategyFlag> flags, int stringId) { this.id = id; this.flags = flags; this.stringId = stringId; diff --git a/main/src/cgeo/geocaching/enumerations/LocationProviderType.java b/main/src/cgeo/geocaching/enumerations/LocationProviderType.java index 36a09bf..f2c79fe 100644 --- a/main/src/cgeo/geocaching/enumerations/LocationProviderType.java +++ b/main/src/cgeo/geocaching/enumerations/LocationProviderType.java @@ -9,7 +9,7 @@ public enum LocationProviderType { public final int resourceId; - private LocationProviderType(final int resourceId) { + LocationProviderType(final int resourceId) { this.resourceId = resourceId; } } diff --git a/main/src/cgeo/geocaching/enumerations/LogType.java b/main/src/cgeo/geocaching/enumerations/LogType.java index 332ef03..71a5146 100644 --- a/main/src/cgeo/geocaching/enumerations/LogType.java +++ b/main/src/cgeo/geocaching/enumerations/LogType.java @@ -5,6 +5,7 @@ import cgeo.geocaching.cgeoapplication; import java.util.Collections; import java.util.HashMap; +import java.util.Locale; import java.util.Map; @@ -14,43 +15,50 @@ import java.util.Map; */ public enum LogType { - FOUND_IT(2, "2", "found it", R.string.log_found), - DIDNT_FIND_IT(3, "3", "didn't find it", R.string.log_dnf), + 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), - ENABLE_LISTING(23, "23", "enable listing", R.string.log_enabled), - ARCHIVE(5, "5", "archive", R.string.log_archived), - UNARCHIVE(12, "12", "unarchive", R.string.log_unarchived), - TEMP_DISABLE_LISTING(22, "22", "temporarily disable listing", R.string.log_disabled), - NEEDS_ARCHIVE(7, "7", "needs archived", R.string.log_needs_archived), + 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), - RETRIEVED_IT(13, "13", "retrieved it", R.string.log_retrieved), - PLACED_IT(14, "14", "placed it", R.string.log_placed), - GRABBED_IT(19, "19", "grabbed it", R.string.log_grabbed), - NEEDS_MAINTENANCE(45, "45", "needs maintenance", R.string.log_maintenance_needed), - OWNER_MAINTENANCE(46, "46", "owner maintenance", R.string.log_maintained), + 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), + 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), - WEBCAM_PHOTO_TAKEN(11, "11", "webcam photo taken", R.string.log_webcam), + 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), - UNKNOWN(0, "unknown", "", R.string.err_unknown); // LogType not init. yet + 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; private final int stringId; + public final int markerId; - private LogType(int id, String iconName, String type, int stringId) { + LogType(int id, String iconName, String type, int stringId, int markerId) { this.id = id; this.iconName = iconName; this.type = 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); } private final static Map<String, LogType> FIND_BY_ICONNAME; @@ -76,7 +84,7 @@ public enum LogType { } public static LogType getByIconName(final String imageType) { - final LogType result = imageType != null ? LogType.FIND_BY_ICONNAME.get(imageType.toLowerCase().trim()) : null; + final LogType result = imageType != null ? LogType.FIND_BY_ICONNAME.get(imageType.toLowerCase(Locale.US).trim()) : null; if (result == null) { return UNKNOWN; } @@ -84,7 +92,7 @@ public enum LogType { } public static LogType getByType(final String type) { - final LogType result = type != null ? LogType.FIND_BY_TYPE.get(type.toLowerCase().trim()) : null; + final LogType result = type != null ? LogType.FIND_BY_TYPE.get(type.toLowerCase(Locale.US).trim()) : null; if (result == null) { return UNKNOWN; } diff --git a/main/src/cgeo/geocaching/enumerations/LogTypeTrackable.java b/main/src/cgeo/geocaching/enumerations/LogTypeTrackable.java index 13b8d03..68a17a5 100644 --- a/main/src/cgeo/geocaching/enumerations/LogTypeTrackable.java +++ b/main/src/cgeo/geocaching/enumerations/LogTypeTrackable.java @@ -11,7 +11,7 @@ public enum LogTypeTrackable { final public String action; final public int resourceId; - private LogTypeTrackable(int id, String action, int resourceId) { + LogTypeTrackable(int id, String action, int resourceId) { this.id = id; this.action = action; this.resourceId = resourceId; diff --git a/main/src/cgeo/geocaching/enumerations/StatusCode.java b/main/src/cgeo/geocaching/enumerations/StatusCode.java index dc62225..102b9e9 100644 --- a/main/src/cgeo/geocaching/enumerations/StatusCode.java +++ b/main/src/cgeo/geocaching/enumerations/StatusCode.java @@ -23,7 +23,8 @@ public enum StatusCode { LOG_POST_ERROR(R.string.err_log_post_failed), NO_LOG_TEXT(R.string.warn_log_text_fill), NO_DATA_FROM_SERVER(R.string.err_log_failed_server), - NOT_LOGGED_IN(R.string.init_login_popup_failed); + NOT_LOGGED_IN(R.string.init_login_popup_failed), + LOGIMAGE_POST_ERROR(R.string.err_logimage_post_failed); final private int error_string; diff --git a/main/src/cgeo/geocaching/enumerations/WaypointType.java b/main/src/cgeo/geocaching/enumerations/WaypointType.java index 52eb318..748c432 100644 --- a/main/src/cgeo/geocaching/enumerations/WaypointType.java +++ b/main/src/cgeo/geocaching/enumerations/WaypointType.java @@ -19,13 +19,14 @@ public enum WaypointType { PUZZLE("puzzle", R.string.wp_puzzle, R.drawable.waypoint_puzzle), STAGE("stage", R.string.wp_stage, R.drawable.waypoint_stage), TRAILHEAD("trailhead", R.string.wp_trailhead, R.drawable.waypoint_trailhead), - WAYPOINT("waypoint", R.string.wp_waypoint, R.drawable.waypoint_waypoint); + WAYPOINT("waypoint", R.string.wp_waypoint, R.drawable.waypoint_waypoint), + ORIGINAL("original", R.string.wp_original, R.drawable.waypoint_waypoint); public final String id; public final int stringId; public final int markerId; - private WaypointType(String id, int stringId, int markerId) { + WaypointType(String id, int stringId, int markerId) { this.id = id; this.stringId = stringId; this.markerId = markerId; @@ -36,13 +37,13 @@ public enum WaypointType { * non public so that <code>null</code> handling can be handled centrally in the enum type itself */ private static final Map<String, WaypointType> FIND_BY_ID; - public static final Set<WaypointType> ALL_TYPES_EXCEPT_OWN = new HashSet<WaypointType>(); + public static final Set<WaypointType> ALL_TYPES_EXCEPT_OWN_AND_ORIGINAL = new HashSet<WaypointType>(); static { final HashMap<String, WaypointType> mapping = new HashMap<String, WaypointType>(); for (WaypointType wt : values()) { mapping.put(wt.id, wt); - if (wt != WaypointType.OWN) { - ALL_TYPES_EXCEPT_OWN.add(wt); + if (wt != WaypointType.OWN && wt != WaypointType.ORIGINAL) { + ALL_TYPES_EXCEPT_OWN_AND_ORIGINAL.add(wt); } } FIND_BY_ID = Collections.unmodifiableMap(mapping); @@ -67,7 +68,6 @@ public enum WaypointType { return cgeoapplication.getInstance().getBaseContext().getResources().getString(stringId); } - @Override public final String toString() { return getL10n(); diff --git a/main/src/cgeo/geocaching/export/Export.java b/main/src/cgeo/geocaching/export/Export.java index 7a2b075..a1a873f 100644 --- a/main/src/cgeo/geocaching/export/Export.java +++ b/main/src/cgeo/geocaching/export/Export.java @@ -1,24 +1,24 @@ package cgeo.geocaching.export; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; import android.app.Activity; import java.util.List; /** - * Represents an exporter to export a {@link List} of {@link cgCache} to various formats. + * Represents an exporter to export a {@link List} of {@link cgeo.geocaching.Geocache} to various formats. */ interface Export { /** - * Export a {@link List} of {@link cgCache} to various formats. + * Export a {@link List} of {@link cgeo.geocaching.Geocache} to various formats. * * @param caches - * The {@link List} of {@link cgCache} to be exported + * The {@link List} of {@link cgeo.geocaching.Geocache} to be exported * @param activity * optional: Some exporters might have an UI which requires an {@link Activity} */ - public void export(List<cgCache> caches, Activity activity); + public void export(List<Geocache> caches, Activity activity); /** * Get the localized name of this exporter. diff --git a/main/src/cgeo/geocaching/export/ExportFactory.java b/main/src/cgeo/geocaching/export/ExportFactory.java index d32a751..a3ecb0c 100644 --- a/main/src/cgeo/geocaching/export/ExportFactory.java +++ b/main/src/cgeo/geocaching/export/ExportFactory.java @@ -1,7 +1,7 @@ package cgeo.geocaching.export; +import cgeo.geocaching.Geocache; import cgeo.geocaching.R; -import cgeo.geocaching.cgCache; import cgeo.geocaching.utils.Log; import android.app.Activity; @@ -34,11 +34,11 @@ public abstract class ExportFactory { * Creates a dialog so that the user can select an exporter. * * @param caches - * The {@link List} of {@link cgCache} to be exported + * The {@link List} of {@link cgeo.geocaching.Geocache} to be exported * @param activity * The {@link Activity} in whose context the dialog should be shown */ - public static void showExportMenu(final List<cgCache> caches, final Activity activity) { + public static void showExportMenu(final List<Geocache> caches, final Activity activity) { final AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setTitle(R.string.export).setIcon(R.drawable.ic_menu_share); diff --git a/main/src/cgeo/geocaching/export/FieldnoteExport.java b/main/src/cgeo/geocaching/export/FieldnoteExport.java index 028ad54..5e1805a 100644 --- a/main/src/cgeo/geocaching/export/FieldnoteExport.java +++ b/main/src/cgeo/geocaching/export/FieldnoteExport.java @@ -1,23 +1,28 @@ package cgeo.geocaching.export; +import cgeo.geocaching.Geocache; import cgeo.geocaching.LogEntry; import cgeo.geocaching.R; -import cgeo.geocaching.cgCache; -import cgeo.geocaching.cgeoapplication; +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.IOUtils; import cgeo.geocaching.utils.Log; +import org.apache.commons.lang3.CharEncoding; import org.apache.commons.lang3.StringUtils; 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; @@ -51,57 +56,54 @@ class FieldnoteExport extends AbstractExport { super(getString(R.string.export_fieldnotes)); } - /** - * A dialog to allow the user to set options for the export. - * - * Currently available options are: upload field notes, only new logs since last export/upload - */ - private class ExportOptionsDialog extends AlertDialog { - public ExportOptionsDialog(final List<cgCache> caches, final Activity activity) { - super(activity); - - View layout = activity.getLayoutInflater().inflate(R.layout.fieldnote_export_dialog, null); - setView(layout); - - final CheckBox uploadOption = (CheckBox) layout.findViewById(R.id.upload); - final CheckBox onlyNewOption = (CheckBox) layout.findViewById(R.id.onlynew); - - uploadOption.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - onlyNewOption.setEnabled(uploadOption.isChecked()); - } - }); - - layout.findViewById(R.id.export).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - dismiss(); - new ExportTask( - caches, - activity, - uploadOption.isChecked(), - onlyNewOption.isChecked()) - .execute((Void) null); - } - }); - } - } - @Override - public void export(final List<cgCache> caches, final Activity activity) { + public void export(final List<Geocache> caches, final Activity activity) { 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); } else { // Show configuration dialog - new ExportOptionsDialog(caches, activity).show(); + getExportOptionsDialog(caches, activity).show(); } } + private Dialog getExportOptionsDialog(final List<Geocache> caches, final Activity activity) { + 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); + builder.setView(layout); + + final CheckBox uploadOption = (CheckBox) layout.findViewById(R.id.upload); + final CheckBox onlyNewOption = (CheckBox) layout.findViewById(R.id.onlynew); + + uploadOption.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + onlyNewOption.setEnabled(uploadOption.isChecked()); + } + }); + + builder.setPositiveButton(R.string.export, new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + new ExportTask( + caches, + activity, + uploadOption.isChecked(), + onlyNewOption.isChecked()) + .execute((Void) null); + } + }); + + return builder.create(); + } + private class ExportTask extends AsyncTask<Void, Integer, Boolean> { - private final List<cgCache> caches; + private final List<Geocache> caches; private final Activity activity; private final boolean upload; private final boolean onlyNew; @@ -114,7 +116,7 @@ class FieldnoteExport extends AbstractExport { * Instantiates and configurates the task for exporting field notes. * * @param caches - * The {@link List} of {@link cgCache} to be exported + * The {@link List} of {@link cgeo.geocaching.Geocache} to be exported * @param activity * optional: Show a progress bar and toasts * @param upload @@ -122,7 +124,7 @@ class FieldnoteExport extends AbstractExport { * @param onlyNew * Upload/export only new logs since last export */ - public ExportTask(final List<cgCache> caches, final Activity activity, final boolean upload, final boolean onlyNew) { + public ExportTask(final List<Geocache> caches, final Activity activity, final boolean upload, final boolean onlyNew) { this.caches = caches; this.activity = activity; this.upload = upload; @@ -139,13 +141,11 @@ class FieldnoteExport extends AbstractExport { @Override protected Boolean doInBackground(Void... params) { final StringBuilder fieldNoteBuffer = new StringBuilder(); - final cgeoapplication app = cgeoapplication.getInstance(); - try { int i = 0; - for (cgCache cache : caches) { + for (Geocache cache : caches) { if (cache.isLogOffline()) { - appendFieldNote(fieldNoteBuffer, cache, app.loadLogOffline(cache.getGeocode())); + appendFieldNote(fieldNoteBuffer, cache, cgData.loadLogOffline(cache.getGeocode())); publishProgress(++i); } } @@ -156,40 +156,30 @@ class FieldnoteExport extends AbstractExport { fieldNoteBuffer.append('\n'); - if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - exportLocation.mkdirs(); + if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + return false; + } - SimpleDateFormat fileNameDateFormat = new SimpleDateFormat("yyyyMMddHHmmss", Locale.US); - exportFile = new File(exportLocation.toString() + '/' + fileNameDateFormat.format(new Date()) + ".txt"); + exportLocation.mkdirs(); - OutputStream os; - Writer fw = null; - try { - os = new FileOutputStream(exportFile); - fw = new OutputStreamWriter(os, "UTF-16"); - fw.write(fieldNoteBuffer.toString()); - } catch (IOException e) { - Log.e("FieldnoteExport.ExportTask export", e); - return false; - } finally { - if (fw != null) { - try { - fw.close(); - } catch (IOException e) { - Log.e("FieldnoteExport.ExportTask export", e); - return false; - } - } - } - } else { + SimpleDateFormat fileNameDateFormat = new SimpleDateFormat("yyyyMMddHHmmss", Locale.US); + exportFile = new File(exportLocation.toString() + '/' + fileNameDateFormat.format(new Date()) + ".txt"); + + Writer fw = null; + try { + OutputStream os = new FileOutputStream(exportFile); + fw = new OutputStreamWriter(os, CharEncoding.UTF_16); + fw.write(fieldNoteBuffer.toString()); + } catch (IOException e) { + Log.e("FieldnoteExport.ExportTask export", e); return false; + } finally { + IOUtils.closeQuietly(fw); } if (upload) { publishProgress(STATUS_UPLOAD); - final String uri = "http://www.geocaching.com/my/uploadfieldnotes.aspx"; - if (!Login.isActualLoginStatus()) { // no need to upload (possibly large file) if we're not logged in final StatusCode loginState = Login.login(); @@ -198,6 +188,7 @@ class FieldnoteExport extends AbstractExport { } } + final String uri = "http://www.geocaching.com/my/uploadfieldnotes.aspx"; String page = Network.getResponseData(Network.getRequest(uri)); if (!Login.getLoginStatus(page)) { @@ -268,7 +259,7 @@ class FieldnoteExport extends AbstractExport { } } - static void appendFieldNote(final StringBuilder fieldNoteBuffer, final cgCache cache, final LogEntry log) { + static void appendFieldNote(final StringBuilder fieldNoteBuffer, final Geocache cache, final LogEntry log) { fieldNoteBuffer.append(cache.getGeocode()) .append(',') .append(fieldNoteDateFormat.format(new Date(log.date))) diff --git a/main/src/cgeo/geocaching/export/GpxExport.java b/main/src/cgeo/geocaching/export/GpxExport.java index 7573db9..74ee072 100644 --- a/main/src/cgeo/geocaching/export/GpxExport.java +++ b/main/src/cgeo/geocaching/export/GpxExport.java @@ -1,11 +1,11 @@ package cgeo.geocaching.export; +import cgeo.geocaching.Geocache; import cgeo.geocaching.LogEntry; import cgeo.geocaching.R; import cgeo.geocaching.Settings; -import cgeo.geocaching.cgCache; -import cgeo.geocaching.cgWaypoint; -import cgeo.geocaching.cgeoapplication; +import cgeo.geocaching.Waypoint; +import cgeo.geocaching.cgData; import cgeo.geocaching.activity.ActivityMixin; import cgeo.geocaching.activity.Progress; import cgeo.geocaching.enumerations.CacheAttribute; @@ -13,26 +13,29 @@ import cgeo.geocaching.enumerations.LoadFlags; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.utils.BaseUtils; import cgeo.geocaching.utils.Log; +import cgeo.geocaching.utils.XmlUtils; -import org.apache.commons.lang3.StringEscapeUtils; 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.util.Xml; +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.io.Writer; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; @@ -41,76 +44,74 @@ 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)); } @Override - public void export(final List<cgCache> caches, final Activity activity) { + public void export(final List<Geocache> caches, final Activity activity) { if (null == activity) { // No activity given, so no user interaction possible. // Start export with default parameters. - new ExportTask(caches, activity).execute((Void) null); + new ExportTask(caches, null).execute((Void) null); } else { // Show configuration dialog - new ExportOptionsDialog(caches, activity).show(); + getExportDialog(caches, activity).show(); } } - /** - * A dialog to allow the user to set options for the export. - * - * Currently available option is: opening of share menu after successful export - */ - private class ExportOptionsDialog extends AlertDialog { - public ExportOptionsDialog(final List<cgCache> caches, final Activity activity) { - super(activity); + private Dialog getExportDialog(final List<Geocache> caches, final Activity activity) { + AlertDialog.Builder builder = new AlertDialog.Builder(activity); - View layout = activity.getLayoutInflater().inflate(R.layout.gpx_export_dialog, null); - setView(layout); + // 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); + builder.setView(layout); - final TextView text = (TextView) layout.findViewById(R.id.info); - text.setText(getString(R.string.export_gpx_info, Settings.getGpxExportDir())); + final TextView text = (TextView) layout.findViewById(R.id.info); + text.setText(getString(R.string.export_gpx_info, Settings.getGpxExportDir())); - final CheckBox shareOption = (CheckBox) layout.findViewById(R.id.share); + final CheckBox shareOption = (CheckBox) layout.findViewById(R.id.share); - shareOption.setChecked(Settings.getShareAfterExport()); + shareOption.setChecked(Settings.getShareAfterExport()); - shareOption.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Settings.setShareAfterExport(shareOption.isChecked()); - } - }); + shareOption.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Settings.setShareAfterExport(shareOption.isChecked()); + } + }); - layout.findViewById(R.id.export).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - dismiss(); - new ExportTask(caches, activity).execute((Void) null); - } - }); - } + builder.setPositiveButton(R.string.export, new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + new ExportTask(caches, activity).execute((Void) null); + } + }); + + return builder.create(); } - private class ExportTask extends AsyncTask<Void, Integer, Boolean> { - private final List<cgCache> caches; + private class ExportTask extends AsyncTask<Void, Integer, File> { + private final List<Geocache> caches; private final Activity activity; private final Progress progress = new Progress(); - private File exportFile; - private Writer gpx; /** * Instantiates and configures the task for exporting field notes. * * @param caches - * The {@link List} of {@link cgCache} to be exported + * The {@link List} of {@link cgeo.geocaching.Geocache} to be exported * @param activity * optional: Show a progress bar and toasts */ - public ExportTask(final List<cgCache> caches, final Activity activity) { + public ExportTask(final List<Geocache> caches, final Activity activity) { this.caches = caches; this.activity = activity; } @@ -124,169 +125,122 @@ class GpxExport extends AbstractExport { } @Override - protected Boolean doInBackground(Void... params) { + protected File doInBackground(Void... params) { // quick check for being able to write the GPX file if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - return false; + return null; } + 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; try { final File exportLocation = new File(Settings.getGpxExportDir()); exportLocation.mkdirs(); - final SimpleDateFormat fileNameDateFormat = new SimpleDateFormat("yyyyMMddHHmmss", Locale.US); - exportFile = new File(Settings.getGpxExportDir() + File.separatorChar + "export_" + fileNameDateFormat.format(new Date()) + ".gpx"); - - gpx = new BufferedWriter(new FileWriter(exportFile)); - - gpx.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); - gpx.write("<gpx version=\"1.0\" creator=\"c:geo - http://www.cgeo.org\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://www.topografix.com/GPX/1/0\" xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd http://www.groundspeak.com/cache/1/0/1 http://www.groundspeak.com/cache/1/0/1/cache.xsd\">"); + final XmlSerializer gpx = Xml.newSerializer(); + 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"); for (int i = 0; i < caches.size(); i++) { - // reload the cache. otherwise logs, attributes and other detailed information is not available - final cgCache cache = cgeoapplication.getInstance().loadCache(caches.get(i).getGeocode(), LoadFlags.LOAD_ALL_DB_ONLY); + final Geocache cache = cgData.loadCache(caches.get(i).getGeocode(), LoadFlags.LOAD_ALL_DB_ONLY); - gpx.write("<wpt "); - gpx.write("lat=\""); - gpx.write(Double.toString(cache.getCoords().getLatitude())); - gpx.write("\" "); - gpx.write("lon=\""); - gpx.write(Double.toString(cache.getCoords().getLongitude())); - gpx.write("\">"); + 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) { - gpx.write("<time>"); - gpx.write(StringEscapeUtils.escapeXml(dateFormatZ.format(hiddenDate))); - gpx.write("</time>"); + XmlUtils.simpleText(gpx, PREFIX_GPX, "time", dateFormatZ.format(hiddenDate)); } - gpx.write("<name>"); - gpx.write(StringEscapeUtils.escapeXml(cache.getGeocode())); - gpx.write("</name>"); - - gpx.write("<desc>"); - gpx.write(StringEscapeUtils.escapeXml(cache.getName())); - gpx.write("</desc>"); - - gpx.write("<url>"); - gpx.write(cache.getUrl()); - gpx.write("</url>"); - - gpx.write("<urlname>"); - gpx.write(StringEscapeUtils.escapeXml(cache.getName())); - gpx.write("</urlname>"); - - gpx.write("<sym>"); - gpx.write(cache.isFound() ? "Geocache Found" : "Geocache"); - gpx.write("</sym>"); - - gpx.write("<type>"); - gpx.write(StringEscapeUtils.escapeXml("Geocache|" + cache.getType().pattern)); - gpx.write("</type>"); - - gpx.write("<groundspeak:cache "); - gpx.write("id=\""); - gpx.write(cache.getCacheId()); - gpx.write("\" available=\""); - gpx.write(!cache.isDisabled() ? "True" : "False"); - gpx.write("\" archived=\""); - gpx.write(cache.isArchived() ? "True" : "False"); - gpx.write("\" "); - gpx.write("xmlns:groundspeak=\"http://www.groundspeak.com/cache/1/0/1\">"); - - gpx.write("<groundspeak:name>"); - gpx.write(StringEscapeUtils.escapeXml(cache.getName())); - gpx.write("</groundspeak:name>"); - - gpx.write("<groundspeak:placed_by>"); - gpx.write(StringEscapeUtils.escapeXml(cache.getOwnerDisplayName())); - gpx.write("</groundspeak:placed_by>"); - - gpx.write("<groundspeak:owner>"); - gpx.write(StringEscapeUtils.escapeXml(cache.getOwnerUserId())); - gpx.write("</groundspeak:owner>"); + 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.write("<groundspeak:type>"); - gpx.write(StringEscapeUtils.escapeXml(cache.getType().pattern)); - gpx.write("</groundspeak:type>"); + gpx.startTag(PREFIX_GROUNDSPEAK, "cache"); + gpx.attribute("", "id", cache.getCacheId()); + gpx.attribute("", "available", !cache.isDisabled() ? "True" : "False"); + gpx.attribute("", "archives", cache.isArchived() ? "True" : "False"); - gpx.write("<groundspeak:container>"); - gpx.write(StringEscapeUtils.escapeXml(cache.getSize().id)); - gpx.write("</groundspeak:container>"); - writeAttributes(cache); + 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()); - gpx.write("<groundspeak:difficulty>"); - gpx.write(Float.toString(cache.getDifficulty())); - gpx.write("</groundspeak:difficulty>"); + writeAttributes(gpx, cache); - gpx.write("<groundspeak:terrain>"); - gpx.write(Float.toString(cache.getTerrain())); - gpx.write("</groundspeak:terrain>"); + 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.write("<groundspeak:country>"); - gpx.write(StringEscapeUtils.escapeXml(cache.getLocation())); - gpx.write("</groundspeak:country>"); + 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"); - gpx.write("<groundspeak:state></groundspeak:state>"); // c:geo cannot manage 2 separate fields, so we export as country + writeLogs(gpx, cache); - gpx.write("<groundspeak:short_description html=\""); - gpx.write(BaseUtils.containsHtml(cache.getShortDescription()) ? "True" : "False"); - gpx.write("\">"); - gpx.write(StringEscapeUtils.escapeXml(cache.getShortDescription())); - gpx.write("</groundspeak:short_description>"); + gpx.endTag(PREFIX_GROUNDSPEAK, "cache"); + gpx.endTag(PREFIX_GPX, "wpt"); - gpx.write("<groundspeak:long_description html=\""); - gpx.write(BaseUtils.containsHtml(cache.getDescription()) ? "True" : "False"); - gpx.write("\">"); - gpx.write(StringEscapeUtils.escapeXml(cache.getDescription())); - gpx.write("</groundspeak:long_description>"); - - gpx.write("<groundspeak:encoded_hints>"); - gpx.write(StringEscapeUtils.escapeXml(cache.getHint())); - gpx.write("</groundspeak:encoded_hints>"); - - writeLogs(cache); - - gpx.write("</groundspeak:cache>"); - - gpx.write("</wpt>"); - - writeWaypoints(cache); + writeWaypoints(gpx, cache); publishProgress(i + 1); } - gpx.write("</gpx>"); - - gpx.close(); - } catch (Exception e) { + gpx.endTag(PREFIX_GPX, "gpx"); + gpx.endDocument(); + } catch (final IOException e) { Log.e("GpxExport.ExportTask export", e); - if (gpx != null) { + if (writer != null) { try { - gpx.close(); - } catch (IOException ee) { + writer.close(); + } catch (IOException e1) { + // Ignore double error } } - // delete partial gpx file on error if (exportFile.exists()) { exportFile.delete(); } - return false; + return null; } - return true; + return exportFile; } - private void writeWaypoints(final cgCache cache) throws IOException { - List<cgWaypoint> waypoints = cache.getWaypoints(); - List<cgWaypoint> ownWaypoints = new ArrayList<cgWaypoint>(waypoints.size()); - List<cgWaypoint> originWaypoints = new ArrayList<cgWaypoint>(waypoints.size()); - for (cgWaypoint wp : cache.getWaypoints()) { + 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 { @@ -294,19 +248,19 @@ class GpxExport extends AbstractExport { } } int maxPrefix = 0; - for (cgWaypoint wp : originWaypoints) { + for (Waypoint wp : originWaypoints) { String prefix = wp.getPrefix(); try { maxPrefix = Math.max(Integer.parseInt(prefix), maxPrefix); } catch (NumberFormatException ex) { Log.e("Unexpected origin waypoint prefix='" + prefix + "'", ex); } - writeCacheWaypoint(wp, prefix); + writeCacheWaypoint(gpx, wp, prefix); } - for (cgWaypoint wp : ownWaypoints) { + for (Waypoint wp : ownWaypoints) { maxPrefix++; String prefix = StringUtils.leftPad(String.valueOf(maxPrefix), 2, '0'); - writeCacheWaypoint(wp, prefix); + writeCacheWaypoint(gpx, wp, prefix); } } @@ -319,103 +273,78 @@ class GpxExport extends AbstractExport { * @param prefix * @throws IOException */ - private void writeCacheWaypoint(final cgWaypoint wp, final String prefix) throws IOException { - gpx.write("<wpt lat=\""); + private void writeCacheWaypoint(final XmlSerializer gpx, final Waypoint wp, final String prefix) throws IOException { + gpx.startTag(PREFIX_GPX, "wpt"); final Geopoint coords = wp.getCoords(); - gpx.write(coords != null ? Double.toString(coords.getLatitude()) : ""); // TODO: check whether is the best way to handle unknown waypoint coordinates - gpx.write("\" lon=\""); - gpx.write(coords != null ? Double.toString(coords.getLongitude()) : ""); - gpx.write("\">"); - - gpx.write("<name>"); - gpx.write(StringEscapeUtils.escapeXml(prefix)); - gpx.write(StringEscapeUtils.escapeXml(wp.getGeocode().substring(2))); - gpx.write("</name>"); - - gpx.write("<cmt>"); - gpx.write(StringEscapeUtils.escapeXml(wp.getNote())); - gpx.write("</cmt>"); - - gpx.write("<desc>"); - gpx.write(StringEscapeUtils.escapeXml(wp.getName())); - gpx.write("</desc>"); - - gpx.write("<sym>"); - gpx.write(StringEscapeUtils.escapeXml(wp.getWaypointType().toString())); //TODO: Correct identifier string - gpx.write("</sym>"); - - gpx.write("<type>Waypoint|"); - gpx.write(StringEscapeUtils.escapeXml(wp.getWaypointType().toString())); //TODO: Correct identifier string - gpx.write("</type>"); - - gpx.write("</wpt>"); + gpx.attribute("", "lat", coords != null ? Double.toString(coords.getLatitude()) : ""); // TODO: check whether is the best way to handle unknown waypoint coordinates + gpx.attribute("", "lon", coords != null ? 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 cgCache cache) throws IOException { + private void writeLogs(final XmlSerializer gpx, final Geocache cache) throws IOException { if (cache.getLogs().isEmpty()) { return; } - gpx.write("<groundspeak:logs>"); + gpx.startTag(PREFIX_GROUNDSPEAK, "logs"); for (LogEntry log : cache.getLogs()) { - gpx.write("<groundspeak:log id=\""); - gpx.write(Integer.toString(log.id)); - gpx.write("\">"); + gpx.startTag(PREFIX_GROUNDSPEAK, "log"); + gpx.attribute("", "id", Integer.toString(log.id)); - gpx.write("<groundspeak:date>"); - gpx.write(StringEscapeUtils.escapeXml(dateFormatZ.format(new Date(log.date)))); - gpx.write("</groundspeak:date>"); + XmlUtils.multipleTexts(gpx, PREFIX_GROUNDSPEAK, + "date", dateFormatZ.format(new Date(log.date)), + "type", log.type.type); - gpx.write("<groundspeak:type>"); - gpx.write(StringEscapeUtils.escapeXml(log.type.type)); - gpx.write("</groundspeak:type>"); + gpx.startTag(PREFIX_GROUNDSPEAK, "finder"); + gpx.attribute("", "id", log.author); + gpx.endTag(PREFIX_GROUNDSPEAK, "finder"); - gpx.write("<groundspeak:finder id=\"\">"); - gpx.write(StringEscapeUtils.escapeXml(log.author)); - gpx.write("</groundspeak:finder>"); + gpx.startTag(PREFIX_GROUNDSPEAK, "text"); + gpx.attribute("", "encoded", "False"); + gpx.text(log.log); + gpx.endTag(PREFIX_GROUNDSPEAK, "text"); - gpx.write("<groundspeak:text encoded=\"False\">"); - gpx.write(StringEscapeUtils.escapeXml(log.log)); - gpx.write("</groundspeak:text>"); - - gpx.write("</groundspeak:log>"); + gpx.endTag(PREFIX_GROUNDSPEAK, "log"); } - gpx.write("</groundspeak:logs>"); + gpx.endTag(PREFIX_GROUNDSPEAK, "logs"); } - private void writeAttributes(final cgCache cache) throws IOException { + 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.write("<groundspeak:attributes>"); + gpx.startTag(PREFIX_GROUNDSPEAK, "attributes"); for (String attribute : cache.getAttributes()) { - final CacheAttribute attr = CacheAttribute.getByGcRawName(CacheAttribute.trimAttributeName(attribute)); + final CacheAttribute attr = CacheAttribute.getByRawName(CacheAttribute.trimAttributeName(attribute)); + if (attr == null) { + continue; + } final boolean enabled = CacheAttribute.isEnabled(attribute); - gpx.write("<groundspeak:attribute id=\""); - gpx.write(Integer.toString(attr.id)); - gpx.write("\" inc=\""); - if (enabled) { - gpx.write('1'); - } else { - gpx.write('0'); - } - gpx.write("\">"); - gpx.write(StringEscapeUtils.escapeXml(attr.getL10n(enabled))); - gpx.write("</groundspeak: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.write("</groundspeak:attributes>"); + gpx.endTag(PREFIX_GROUNDSPEAK, "attributes"); } @Override - protected void onPostExecute(Boolean result) { + protected void onPostExecute(final File exportFile) { if (null != activity) { progress.dismiss(); - if (result) { + if (exportFile != null) { ActivityMixin.showToast(activity, getName() + ' ' + getString(R.string.export_exportedto) + ": " + exportFile.toString()); if (Settings.getShareAfterExport()) { Intent shareIntent = new Intent(); diff --git a/main/src/cgeo/geocaching/files/FileList.java b/main/src/cgeo/geocaching/files/AbstractFileListActivity.java index df95085..5ff0d91 100644 --- a/main/src/cgeo/geocaching/files/FileList.java +++ b/main/src/cgeo/geocaching/files/AbstractFileListActivity.java @@ -1,9 +1,11 @@ package cgeo.geocaching.files; +import cgeo.geocaching.Intents; import cgeo.geocaching.R; import cgeo.geocaching.StoredList; import cgeo.geocaching.activity.AbstractListActivity; import cgeo.geocaching.utils.FileUtils; +import cgeo.geocaching.utils.IOUtils; import cgeo.geocaching.utils.Log; import org.apache.commons.collections.CollectionUtils; @@ -26,14 +28,14 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; -public abstract class FileList<T extends ArrayAdapter<File>> extends AbstractListActivity { +public abstract class AbstractFileListActivity<T extends ArrayAdapter<File>> extends AbstractListActivity { private static final int MSG_SEARCH_WHOLE_SD_CARD = 1; private final List<File> files = new ArrayList<File>(); private T adapter = null; private ProgressDialog waitDialog = null; private SearchFilesThread searchingThread = null; - private int listId = StoredList.STANDARD_LIST_ID; + protected int listId = StoredList.STANDARD_LIST_ID; private String[] extensions; final private Handler changeWaitDialogHandler = new Handler() { @@ -91,7 +93,7 @@ public abstract class FileList<T extends ArrayAdapter<File>> extends AbstractLis Bundle extras = getIntent().getExtras(); if (extras != null) { - listId = extras.getInt("list"); + listId = extras.getInt(Intents.EXTRA_LIST_ID); } if (listId <= StoredList.TEMPORARY_LIST_ID) { listId = StoredList.STANDARD_LIST_ID; @@ -182,7 +184,7 @@ public abstract class FileList<T extends ArrayAdapter<File>> extends AbstractLis Log.w("No external media mounted."); } } catch (Exception e) { - Log.e("cgFileList.loadFiles.run: " + e.toString()); + Log.e("AbstractFileListActivity.loadFiles.run", e); } changeWaitDialogHandler.sendMessage(Message.obtain(changeWaitDialogHandler, 0, "loaded directories")); @@ -242,22 +244,15 @@ public abstract class FileList<T extends ArrayAdapter<File>> extends AbstractLis Log.e("Could not get additional mount points for user content. " + "Proceeding with external storage only (" + extStorage + ")"); } finally { - try { - if (fr != null) { - fr.close(); - } - if (br != null) { - br.close(); - } - } catch (IOException e) { - } + IOUtils.closeQuietly(fr); + IOUtils.closeQuietly(br); } } return storages; } /** - * Check if a filename belongs to the FileList. This implementation checks for file extensions. + * Check if a filename belongs to the AbstractFileListActivity. This implementation checks for file extensions. * Subclasses may override this method to filter out specific files. * * @param filename @@ -272,19 +267,19 @@ public abstract class FileList<T extends ArrayAdapter<File>> extends AbstractLis return false; } - protected FileList(final String extension) { + protected AbstractFileListActivity(final String extension) { setExtensions(new String[] { extension }); } - protected FileList(final String[] extensions) { + protected AbstractFileListActivity(final String[] extensions) { setExtensions(extensions); } private void setExtensions(final String[] extensionsIn) { extensions = extensionsIn; for (int i = 0; i < extensions.length; i++) { - String extension = extensions[i]; - if (extension.length() == 0 || extension.charAt(0) != '.') { + final String extension = extensions[i]; + if (StringUtils.isEmpty(extension) || extension.charAt(0) != '.') { extensions[i] = "." + extension; } } diff --git a/main/src/cgeo/geocaching/files/FileParser.java b/main/src/cgeo/geocaching/files/FileParser.java index 0308d81..50b65a1 100644 --- a/main/src/cgeo/geocaching/files/FileParser.java +++ b/main/src/cgeo/geocaching/files/FileParser.java @@ -1,6 +1,6 @@ package cgeo.geocaching.files; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; import cgeo.geocaching.utils.CancellableHandler; import java.io.BufferedReader; @@ -26,7 +26,7 @@ public abstract class FileParser { * @throws ParserException * if the input stream contains data not matching the file format of the parser */ - public abstract Collection<cgCache> parse(final InputStream stream, final CancellableHandler progressHandler) throws IOException, ParserException; + public abstract Collection<Geocache> parse(final InputStream stream, final CancellableHandler progressHandler) throws IOException, ParserException; /** * Convenience method for parsing a file. @@ -37,7 +37,7 @@ public abstract class FileParser { * @throws IOException * @throws ParserException */ - public Collection<cgCache> parse(final File file, final CancellableHandler progressHandler) throws IOException, ParserException { + public Collection<Geocache> parse(final File file, final CancellableHandler progressHandler) throws IOException, ParserException { FileInputStream fis = new FileInputStream(file); try { return parse(fis, progressHandler); @@ -72,7 +72,7 @@ public abstract class FileParser { } } - protected static void fixCache(cgCache cache) { + protected static void fixCache(Geocache cache) { if (cache.getInventory() != null) { cache.setInventoryItems(cache.getInventory().size()); } else { diff --git a/main/src/cgeo/geocaching/files/GPXImporter.java b/main/src/cgeo/geocaching/files/GPXImporter.java index fb78360..b8dcbb3 100644 --- a/main/src/cgeo/geocaching/files/GPXImporter.java +++ b/main/src/cgeo/geocaching/files/GPXImporter.java @@ -1,11 +1,11 @@ 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.cgCache;
-import cgeo.geocaching.cgeoapplication;
+import cgeo.geocaching.cgData;
import cgeo.geocaching.activity.IAbstractActivity;
import cgeo.geocaching.activity.Progress;
import cgeo.geocaching.enumerations.LoadFlags;
@@ -124,7 +124,7 @@ public class GPXImporter { final Handler importStepHandler;
final CancellableHandler progressHandler;
- public ImportThread(int listId, Handler importStepHandler, CancellableHandler progressHandler) {
+ protected ImportThread(int listId, Handler importStepHandler, CancellableHandler progressHandler) {
this.listId = listId;
this.importStepHandler = importStepHandler;
this.progressHandler = progressHandler;
@@ -132,14 +132,13 @@ public class GPXImporter { @Override
public void run() {
- final Collection<cgCache> caches;
try {
importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_START));
- caches = doImport();
+ final Collection<Geocache> caches = doImport();
Log.i("Imported successfully " + caches.size() + " caches.");
final SearchResult search = new SearchResult();
- for (cgCache cache : caches) {
+ for (Geocache cache : caches) {
search.addCache(cache);
}
@@ -168,13 +167,12 @@ public class GPXImporter { }
}
- protected abstract Collection<cgCache> doImport() throws IOException, ParserException;
+ protected abstract Collection<Geocache> doImport() throws IOException, ParserException;
private boolean importStaticMaps(final SearchResult importedCaches) {
- final cgeoapplication app = cgeoapplication.getInstance();
int storedCacheMaps = 0;
for (String geocode : importedCaches.getGeocodes()) {
- cgCache cache = app.loadCache(geocode, LoadFlags.LOAD_WAYPOINTS);
+ Geocache cache = cgData.loadCache(geocode, LoadFlags.LOAD_WAYPOINTS);
Log.d("GPXImporter.ImportThread.importStaticMaps start downloadMaps for cache " + geocode);
StaticMapsProvider.downloadMaps(cache);
storedCacheMaps++;
@@ -196,7 +194,7 @@ public class GPXImporter { }
@Override
- protected Collection<cgCache> doImport() throws IOException, ParserException {
+ 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);
@@ -206,12 +204,12 @@ public class GPXImporter { static abstract class ImportGpxThread extends ImportThread {
- public ImportGpxThread(int listId, Handler importStepHandler, CancellableHandler progressHandler) {
+ protected ImportGpxThread(int listId, Handler importStepHandler, CancellableHandler progressHandler) {
super(listId, importStepHandler, progressHandler);
}
@Override
- protected Collection<cgCache> doImport() throws IOException, ParserException {
+ protected Collection<Geocache> doImport() throws IOException, ParserException {
try {
// try to parse cache file as GPX 10
return doImport(new GPX10Parser(listId));
@@ -221,7 +219,7 @@ public class GPXImporter { }
}
- protected abstract Collection<cgCache> doImport(GPXParser parser) throws IOException, ParserException;
+ protected abstract Collection<Geocache> doImport(GPXParser parser) throws IOException, ParserException;
}
static class ImportGpxFileThread extends ImportGpxThread {
@@ -233,10 +231,10 @@ public class GPXImporter { }
@Override
- protected Collection<cgCache> doImport(GPXParser parser) throws IOException, ParserException {
+ 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<cgCache> caches = parser.parse(cacheFile, progressHandler);
+ Collection<Geocache> caches = parser.parse(cacheFile, progressHandler);
final String wptsFilename = getWaypointsFileNameForGpxFile(cacheFile);
if (wptsFilename != null) {
@@ -262,7 +260,7 @@ public class GPXImporter { }
@Override
- protected Collection<cgCache> doImport(GPXParser parser) throws IOException, ParserException {
+ 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);
@@ -276,13 +274,13 @@ public class GPXImporter { static abstract class ImportGpxZipThread extends ImportGpxThread {
- public ImportGpxZipThread(int listId, Handler importStepHandler, CancellableHandler progressHandler) {
+ protected ImportGpxZipThread(int listId, Handler importStepHandler, CancellableHandler progressHandler) {
super(listId, importStepHandler, progressHandler);
}
@Override
- protected Collection<cgCache> doImport(GPXParser parser) throws IOException, ParserException {
- Collection<cgCache> caches = Collections.emptySet();
+ 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());
diff --git a/main/src/cgeo/geocaching/files/GPXParser.java b/main/src/cgeo/geocaching/files/GPXParser.java index b9d3f53..5647d14 100644 --- a/main/src/cgeo/geocaching/files/GPXParser.java +++ b/main/src/cgeo/geocaching/files/GPXParser.java @@ -1,11 +1,12 @@ package cgeo.geocaching.files; +import cgeo.geocaching.Geocache; import cgeo.geocaching.LogEntry; import cgeo.geocaching.R; import cgeo.geocaching.StoredList; -import cgeo.geocaching.cgCache; -import cgeo.geocaching.cgTrackable; -import cgeo.geocaching.cgWaypoint; +import cgeo.geocaching.Trackable; +import cgeo.geocaching.Waypoint; +import cgeo.geocaching.cgData; import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.connector.ConnectorFactory; import cgeo.geocaching.enumerations.CacheSize; @@ -18,6 +19,7 @@ import cgeo.geocaching.enumerations.WaypointType; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.utils.CancellableHandler; import cgeo.geocaching.utils.Log; +import cgeo.geocaching.utils.MatcherWrapper; import org.apache.commons.lang3.StringUtils; import org.xml.sax.Attributes; @@ -41,7 +43,6 @@ import java.util.EnumSet; import java.util.HashSet; import java.util.Locale; import java.util.Set; -import java.util.regex.Matcher; import java.util.regex.Pattern; public abstract class GPXParser extends FileParser { @@ -79,8 +80,8 @@ public abstract class GPXParser extends FileParser { final protected String namespace; final private String version; - private cgCache cache; - private cgTrackable trackable = new cgTrackable(); + private Geocache cache; + private Trackable trackable = new Trackable(); private LogEntry log = null; private String type = null; @@ -208,7 +209,7 @@ public abstract class GPXParser extends FileParser { return null; } // cut out baseName - final Matcher m = BASENAME_PATTERN.matcher(stringName); + final MatcherWrapper m = new MatcherWrapper(BASENAME_PATTERN, stringName); if (!m.matches()) { return null; } @@ -234,7 +235,7 @@ public abstract class GPXParser extends FileParser { static Date parseDate(String inputUntrimmed) throws ParseException { String input = inputUntrimmed.trim(); // remove milliseconds to reduce number of needed patterns - final Matcher matcher = PATTERN_MILLISECONDS.matcher(input); + final MatcherWrapper matcher = new MatcherWrapper(PATTERN_MILLISECONDS, input); input = matcher.replaceFirst(""); if (input.contains("Z")) { return formatSimpleZ.parse(input); @@ -247,7 +248,7 @@ public abstract class GPXParser extends FileParser { } @Override - public Collection<cgCache> parse(final InputStream stream, final CancellableHandler progressHandler) throws IOException, ParserException { + public Collection<Geocache> parse(final InputStream stream, final CancellableHandler progressHandler) throws IOException, ParserException { resetCache(); final RootElement root = new RootElement(namespace, "gpx"); final Element waypoint = root.getChild(namespace, "wpt"); @@ -287,7 +288,7 @@ public abstract class GPXParser extends FileParser { // take the name as code, if nothing else is available if (StringUtils.isBlank(cache.getGeocode())) { if (StringUtils.isNotBlank(name)) { - cache.setGeocode(name.trim().toUpperCase()); + cache.setGeocode(name.trim()); } } @@ -311,8 +312,8 @@ public abstract class GPXParser extends FileParser { // finally store the cache in the database result.add(geocode); - cgeoapplication.getInstance().saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); - cgeoapplication.getInstance().removeAllFromCache(); + cgData.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); + cgData.removeAllFromCache(); showProgressMessage(progressHandler, progressStream.getProgress()); } else if (StringUtils.isNotBlank(cache.getName()) && StringUtils.containsIgnoreCase(type, "waypoint")) { @@ -326,12 +327,11 @@ public abstract class GPXParser extends FileParser { fixCache(cache); if (cache.getName().length() > 2) { - final String cacheGeocodeForWaypoint = "GC" + cache.getName().substring(2).toUpperCase(); - + final String cacheGeocodeForWaypoint = "GC" + cache.getName().substring(2).toUpperCase(Locale.US); // lookup cache for waypoint in already parsed caches - final cgCache cacheForWaypoint = cgeoapplication.getInstance().loadCache(cacheGeocodeForWaypoint, LoadFlags.LOAD_CACHE_OR_DB); + final Geocache cacheForWaypoint = cgData.loadCache(cacheGeocodeForWaypoint, LoadFlags.LOAD_CACHE_OR_DB); if (cacheForWaypoint != null) { - final cgWaypoint waypoint = new cgWaypoint(cache.getShortdesc(), convertWaypointSym2Type(sym), false); + final Waypoint waypoint = new Waypoint(cache.getShortDescription(), convertWaypointSym2Type(sym), false); waypoint.setId(-1); waypoint.setGeocode(cacheGeocodeForWaypoint); waypoint.setPrefix(cache.getName().substring(0, 2)); @@ -340,14 +340,14 @@ public abstract class GPXParser extends FileParser { waypoint.setCoords(cache.getCoords()); waypoint.setNote(cache.getDescription()); - final ArrayList<cgWaypoint> mergedWayPoints = new ArrayList<cgWaypoint>(); + final ArrayList<Waypoint> mergedWayPoints = new ArrayList<Waypoint>(); mergedWayPoints.addAll(cacheForWaypoint.getWaypoints()); - final ArrayList<cgWaypoint> newPoints = new ArrayList<cgWaypoint>(); + final ArrayList<Waypoint> newPoints = new ArrayList<Waypoint>(); newPoints.add(waypoint); - cgWaypoint.mergeWayPoints(newPoints, mergedWayPoints, true); + Waypoint.mergeWayPoints(newPoints, mergedWayPoints, true); cacheForWaypoint.setWaypoints(newPoints, false); - cgeoapplication.getInstance().saveCache(cacheForWaypoint, EnumSet.of(SaveFlag.SAVE_DB)); + cgData.saveCache(cacheForWaypoint, EnumSet.of(SaveFlag.SAVE_DB)); showProgressMessage(progressHandler, progressStream.getProgress()); } } @@ -362,7 +362,7 @@ public abstract class GPXParser extends FileParser { try { cache.setHidden(parseDate(body)); } catch (Exception e) { - Log.w("Failed to parse cache date: " + e.toString()); + Log.w("Failed to parse cache date", e); } } }); @@ -388,7 +388,7 @@ public abstract class GPXParser extends FileParser { public void end(String body) { desc = body; - cache.setShortdesc(validate(body)); + cache.setShortDescription(validate(body)); } }); @@ -410,7 +410,7 @@ public abstract class GPXParser extends FileParser { public void end(String body) { final String[] content = body.split("\\|"); if (content.length > 0) { - type = content[0].toLowerCase().trim(); + type = content[0].toLowerCase(Locale.US).trim(); } } }); @@ -420,7 +420,7 @@ public abstract class GPXParser extends FileParser { @Override public void end(final String body) { - sym = body.toLowerCase(); + sym = body.toLowerCase(Locale.US); if (sym.contains("geocache") && sym.contains("found")) { cache.setFound(true); } @@ -432,14 +432,14 @@ public abstract class GPXParser extends FileParser { @Override public void end(String url) { - final Matcher matcher = patternGuid.matcher(url); + final MatcherWrapper matcher = new MatcherWrapper(patternGuid, url); if (matcher.matches()) { final String guid = matcher.group(1); if (StringUtils.isNotBlank(guid)) { cache.setGuid(guid); } } - final Matcher matcherCode = patternUrlGeocode.matcher(url); + final MatcherWrapper matcherCode = new MatcherWrapper(patternUrlGeocode, url); if (matcherCode.matches()) { String geocode = matcherCode.group(1); cache.setGeocode(geocode); @@ -526,7 +526,7 @@ public abstract class GPXParser extends FileParser { @Override public void end(String body) { - cache.setType(CacheType.getByPattern(validate(body.toLowerCase()))); + cache.setType(CacheType.getByPattern(validate(body))); } }); @@ -535,7 +535,7 @@ public abstract class GPXParser extends FileParser { @Override public void end(String body) { - cache.setSize(CacheSize.getById(validate(body.toLowerCase()))); + cache.setSize(CacheSize.getById(validate(body))); } }); @@ -578,7 +578,7 @@ public abstract class GPXParser extends FileParser { try { cache.setDifficulty(Float.parseFloat(body)); } catch (NumberFormatException e) { - Log.w("Failed to parse difficulty: " + e.toString()); + Log.w("Failed to parse difficulty", e); } } }); @@ -591,7 +591,7 @@ public abstract class GPXParser extends FileParser { try { cache.setTerrain(Float.parseFloat(body)); } catch (NumberFormatException e) { - Log.w("Failed to parse terrain: " + e.toString()); + Log.w("Failed to parse terrain", e); } } }); @@ -638,7 +638,7 @@ public abstract class GPXParser extends FileParser { @Override public void end(String shortDesc) { - cache.setShortdesc(validate(shortDesc)); + cache.setShortDescription(validate(shortDesc)); } }); @@ -661,11 +661,11 @@ public abstract class GPXParser extends FileParser { @Override public void start(Attributes attrs) { - trackable = new cgTrackable(); + trackable = new Trackable(); try { if (attrs.getIndex("ref") > -1) { - trackable.setGeocode(attrs.getValue("ref").toUpperCase()); + trackable.setGeocode(attrs.getValue("ref")); } } catch (Exception e) { // nothing @@ -679,7 +679,7 @@ public abstract class GPXParser extends FileParser { public void end() { if (StringUtils.isNotBlank(trackable.getGeocode()) && StringUtils.isNotBlank(trackable.getName())) { if (cache.getInventory() == null) { - cache.setInventory(new ArrayList<cgTrackable>()); + cache.setInventory(new ArrayList<Trackable>()); } cache.getInventory().add(trackable); } @@ -735,7 +735,7 @@ public abstract class GPXParser extends FileParser { try { log.date = parseDate(body).getTime(); } catch (Exception e) { - Log.w("Failed to parse log date: " + e.toString()); + Log.w("Failed to parse log date", e); } } }); @@ -745,7 +745,7 @@ public abstract class GPXParser extends FileParser { @Override public void end(String body) { - final String logType = validate(body).toLowerCase(); + final String logType = validate(body); log.type = LogType.getByType(logType); } }); @@ -772,9 +772,9 @@ public abstract class GPXParser extends FileParser { try { progressStream = new ProgressInputStream(stream); Xml.parse(progressStream, Xml.Encoding.UTF_8, root.getContentHandler()); - return cgeoapplication.getInstance().loadCaches(result, EnumSet.of(LoadFlag.LOAD_DB_MINIMAL)); + return cgData.loadCaches(result, EnumSet.of(LoadFlag.LOAD_DB_MINIMAL)); } catch (SAXException e) { - Log.e("Cannot parse .gpx file as GPX " + version + ": could not parse XML - " + e.toString()); + 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); } } @@ -783,7 +783,7 @@ public abstract class GPXParser extends FileParser { * @param cache * currently imported cache */ - protected void afterParsing(cgCache cache) { + protected void afterParsing(Geocache cache) { // can be overridden by sub classes } @@ -806,17 +806,21 @@ public abstract class GPXParser extends FileParser { static WaypointType convertWaypointSym2Type(final String sym) { if ("parking area".equalsIgnoreCase(sym)) { return WaypointType.PARKING; - } else if ("stages of a multicache".equalsIgnoreCase(sym)) { + } + if ("stages of a multicache".equalsIgnoreCase(sym)) { return WaypointType.STAGE; - } else if ("question to answer".equalsIgnoreCase(sym)) { + } + if ("question to answer".equalsIgnoreCase(sym)) { return WaypointType.PUZZLE; - } else if ("trailhead".equalsIgnoreCase(sym)) { + } + if ("trailhead".equalsIgnoreCase(sym)) { return WaypointType.TRAILHEAD; - } else if ("final location".equalsIgnoreCase(sym)) { + } + if ("final location".equalsIgnoreCase(sym)) { return WaypointType.FINAL; } // this is not fully correct, but lets also look for localized waypoint types - for (WaypointType waypointType : WaypointType.ALL_TYPES_EXCEPT_OWN) { + for (WaypointType waypointType : WaypointType.ALL_TYPES_EXCEPT_OWN_AND_ORIGINAL) { final String localized = waypointType.getL10n(); if (StringUtils.isNotEmpty(localized)) { if (localized.equalsIgnoreCase(sym)) { @@ -832,7 +836,7 @@ public abstract class GPXParser extends FileParser { return; } final String trimmed = input.trim(); - final Matcher matcherGeocode = patternGeocode.matcher(trimmed); + final MatcherWrapper matcherGeocode = new MatcherWrapper(patternGeocode, trimmed); if (matcherGeocode.find()) { final String geocode = matcherGeocode.group(1); // a geocode should not be part of a word @@ -854,7 +858,14 @@ public abstract class GPXParser extends FileParser { desc = null; cmt = null; - cache = new cgCache(this); + cache = new Geocache(this); + + // explicitly set all properties which could lead to database access, if left as null value + cache.setLocation(""); + cache.setDescription(""); + cache.setShortDescription(""); + cache.setHint(""); + for (int i = 0; i < userData.length; i++) { userData[i] = null; } diff --git a/main/src/cgeo/geocaching/files/LocParser.java b/main/src/cgeo/geocaching/files/LocParser.java index b17b203..fe290c3 100644 --- a/main/src/cgeo/geocaching/files/LocParser.java +++ b/main/src/cgeo/geocaching/files/LocParser.java @@ -1,14 +1,15 @@ package cgeo.geocaching.files; +import cgeo.geocaching.Geocache; import cgeo.geocaching.SearchResult; -import cgeo.geocaching.cgCache; -import cgeo.geocaching.cgeoapplication; +import cgeo.geocaching.cgData; import cgeo.geocaching.enumerations.CacheSize; import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.enumerations.LoadFlags; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.utils.CancellableHandler; import cgeo.geocaching.utils.Log; +import cgeo.geocaching.utils.MatcherWrapper; import org.apache.commons.lang3.StringUtils; @@ -22,7 +23,6 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import java.util.regex.Matcher; import java.util.regex.Pattern; public final class LocParser extends FileParser { @@ -56,7 +56,7 @@ public final class LocParser extends FileParser { private int listId; public static void parseLoc(final SearchResult searchResult, final String fileContent) { - final Map<String, cgCache> cidCoords = parseCoordinates(fileContent); + final Map<String, Geocache> cidCoords = parseCoordinates(fileContent); // save found cache coordinates final HashSet<String> contained = new HashSet<String>(); @@ -65,27 +65,27 @@ public final class LocParser extends FileParser { contained.add(geocode); } } - Set<cgCache> caches = cgeoapplication.getInstance().loadCaches(contained, LoadFlags.LOAD_CACHE_OR_DB); - for (cgCache cache : caches) { - cgCache coord = cidCoords.get(cache.getGeocode()); + Set<Geocache> caches = cgData.loadCaches(contained, LoadFlags.LOAD_CACHE_OR_DB); + for (Geocache cache : caches) { + Geocache coord = cidCoords.get(cache.getGeocode()); copyCoordToCache(coord, cache); } } - private static void copyCoordToCache(final cgCache coord, final cgCache cache) { + private static void copyCoordToCache(final Geocache coord, final Geocache cache) { cache.setCoords(coord.getCoords()); cache.setDifficulty(coord.getDifficulty()); cache.setTerrain(coord.getTerrain()); cache.setSize(coord.getSize()); - cache.setGeocode(coord.getGeocode().toUpperCase()); + cache.setGeocode(coord.getGeocode()); cache.setReliableLatLon(true); if (StringUtils.isBlank(cache.getName())) { cache.setName(coord.getName()); } } - static Map<String, cgCache> parseCoordinates(final String fileContent) { - final Map<String, cgCache> coords = new HashMap<String, cgCache>(); + static Map<String, Geocache> parseCoordinates(final String fileContent) { + final Map<String, Geocache> coords = new HashMap<String, Geocache>(); if (StringUtils.isBlank(fileContent)) { return coords; } @@ -95,7 +95,7 @@ public final class LocParser extends FileParser { // parse coordinates for (String pointString : points) { - final cgCache pointCoord = parseCache(pointString); + final Geocache pointCoord = parseCache(pointString); if (StringUtils.isNotBlank(pointCoord.getGeocode())) { coords.put(pointCoord.getGeocode(), pointCoord); } @@ -121,17 +121,17 @@ public final class LocParser extends FileParser { } @Override - public Collection<cgCache> parse(InputStream stream, CancellableHandler progressHandler) throws IOException, ParserException { + public Collection<Geocache> parse(InputStream stream, CancellableHandler progressHandler) throws IOException, ParserException { // TODO: progress reporting happens during reading stream only, not during parsing String streamContent = readStream(stream, progressHandler).toString(); - final Map<String, cgCache> coords = parseCoordinates(streamContent); - final List<cgCache> caches = new ArrayList<cgCache>(); - for (Entry<String, cgCache> entry : coords.entrySet()) { - cgCache coord = entry.getValue(); + final Map<String, Geocache> coords = parseCoordinates(streamContent); + final List<Geocache> caches = new ArrayList<Geocache>(); + for (Entry<String, Geocache> entry : coords.entrySet()) { + Geocache coord = entry.getValue(); if (StringUtils.isBlank(coord.getGeocode()) || StringUtils.isBlank(coord.getName())) { continue; } - cgCache cache = new cgCache(); + Geocache cache = new Geocache(); cache.setReliableLatLon(true); copyCoordToCache(coord, cache); caches.add(cache); @@ -140,20 +140,20 @@ public final class LocParser extends FileParser { cache.setType(CacheType.UNKNOWN); // type is not given in the LOC file cache.setListId(listId); cache.setDetailed(true); + cache.store(null); } Log.i("Caches found in .loc file: " + caches.size()); return caches; } - public static cgCache parseCache(final String pointString) { - final cgCache cache = new cgCache(); - final Matcher matcherGeocode = patternGeocode.matcher(pointString); + public static Geocache parseCache(final String pointString) { + final Geocache cache = new Geocache(); + final MatcherWrapper matcherGeocode = new MatcherWrapper(patternGeocode, pointString); if (matcherGeocode.find()) { - final String geocode = matcherGeocode.group(1).trim().toUpperCase(); - cache.setGeocode(geocode.toUpperCase()); + cache.setGeocode(matcherGeocode.group(1).trim()); } - final Matcher matcherName = patternName.matcher(pointString); + final MatcherWrapper matcherName = new MatcherWrapper(patternName, pointString); if (matcherName.find()) { final String name = matcherName.group(1).trim(); cache.setName(StringUtils.substringBeforeLast(name, " by ").trim()); @@ -161,28 +161,32 @@ public final class LocParser extends FileParser { cache.setName(cache.getGeocode()); } - final Matcher matcherLat = patternLat.matcher(pointString); - final Matcher matcherLon = patternLon.matcher(pointString); + final MatcherWrapper matcherLat = new MatcherWrapper(patternLat, pointString); + final MatcherWrapper matcherLon = new MatcherWrapper(patternLon, pointString); if (matcherLat.find() && matcherLon.find()) { cache.setCoords(parsePoint(matcherLat.group(1).trim(), matcherLon.group(1).trim())); } - final Matcher matcherDifficulty = patternDifficulty.matcher(pointString); - if (matcherDifficulty.find()) { - cache.setDifficulty(Float.parseFloat(matcherDifficulty.group(1).trim())); - } + final MatcherWrapper matcherDifficulty = new MatcherWrapper(patternDifficulty, pointString); + try { + if (matcherDifficulty.find()) { + cache.setDifficulty(Float.parseFloat(matcherDifficulty.group(1).trim())); + } - final Matcher matcherTerrain = patternTerrain.matcher(pointString); - if (matcherTerrain.find()) { - cache.setTerrain(Float.parseFloat(matcherTerrain.group(1).trim())); - } + final MatcherWrapper matcherTerrain = new MatcherWrapper(patternTerrain, pointString); + if (matcherTerrain.find()) { + cache.setTerrain(Float.parseFloat(matcherTerrain.group(1).trim())); + } - final Matcher matcherContainer = patternContainer.matcher(pointString); - if (matcherContainer.find()) { - final int size = Integer.parseInt(matcherContainer.group(1).trim()); - if (size >= 1 && size <= 8) { - cache.setSize(SIZES[size - 1]); + final MatcherWrapper matcherContainer = new MatcherWrapper(patternContainer, pointString); + if (matcherContainer.find()) { + final int size = Integer.parseInt(matcherContainer.group(1).trim()); + if (size >= 1 && size <= 8) { + cache.setSize(SIZES[size - 1]); + } } + } catch (NumberFormatException e) { + Log.e("LocParser.parseCache", e); } return cache; diff --git a/main/src/cgeo/geocaching/files/LocalStorage.java b/main/src/cgeo/geocaching/files/LocalStorage.java index c392564..f59f15c 100644 --- a/main/src/cgeo/geocaching/files/LocalStorage.java +++ b/main/src/cgeo/geocaching/files/LocalStorage.java @@ -305,9 +305,9 @@ public class LocalStorage { } private static boolean copy(final InputStream input, final OutputStream output) { - final byte[] buffer = new byte[4096]; - int length; try { + int length; + final byte[] buffer = new byte[4096]; while ((length = input.read(buffer)) > 0) { output.write(buffer, 0, length); } @@ -363,7 +363,7 @@ public class LocalStorage { Log.w("LocalStorage.deleteFilesPrefix: Can't delete file " + file.getName()); } } catch (Exception e) { - Log.e("LocalStorage.deleteFilesPrefix: " + e.toString()); + Log.e("LocalStorage.deleteFilesPrefix", e); } } } diff --git a/main/src/cgeo/geocaching/files/ParserException.java b/main/src/cgeo/geocaching/files/ParserException.java index 5aa152c..c0076cc 100644 --- a/main/src/cgeo/geocaching/files/ParserException.java +++ b/main/src/cgeo/geocaching/files/ParserException.java @@ -13,10 +13,6 @@ public class ParserException extends Exception { super(detailMessage); } - public ParserException(Throwable throwable) { - super(throwable); - } - public ParserException(String detailMessage, Throwable throwable) { super(detailMessage, throwable); } diff --git a/main/src/cgeo/geocaching/files/SimpleDirChooser.java b/main/src/cgeo/geocaching/files/SimpleDirChooser.java index 404777a..7520e2e 100644 --- a/main/src/cgeo/geocaching/files/SimpleDirChooser.java +++ b/main/src/cgeo/geocaching/files/SimpleDirChooser.java @@ -1,5 +1,6 @@ package cgeo.geocaching.files; +import cgeo.geocaching.Intents; import cgeo.geocaching.R; import cgeo.geocaching.activity.ActivityMixin; @@ -8,6 +9,7 @@ import org.apache.commons.lang3.StringUtils; import android.app.ListActivity; import android.content.Context; import android.content.Intent; +import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.view.LayoutInflater; @@ -18,7 +20,6 @@ import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.CheckBox; import android.widget.EditText; -import android.widget.ListView; import android.widget.TextView; import java.io.File; @@ -31,8 +32,6 @@ import java.util.List; * Dialog for choosing a file or directory. */ public class SimpleDirChooser extends ListActivity { - public static final String EXTRA_CHOSEN_DIR = "chosenDir"; - public static final String START_DIR = "start_dir"; private static final String PARENT_DIR = ".. "; private File currentDir; private FileArrayAdapter adapter; @@ -43,7 +42,7 @@ public class SimpleDirChooser extends ListActivity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); final Bundle extras = getIntent().getExtras(); - currentDir = dirContaining(extras.getString(START_DIR)); + currentDir = dirContaining(extras.getString(Intents.EXTRA_START_DIR)); ActivityMixin.setTheme(this); setContentView(R.layout.simple_dir_chooser); @@ -57,10 +56,8 @@ public class SimpleDirChooser extends ListActivity { okButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - Intent intent = new Intent(); - String chosenDirName = File.separator + adapter.getItem(lastPosition).getName(); - intent.putExtra(EXTRA_CHOSEN_DIR, currentDir.getAbsolutePath() + chosenDirName); - setResult(RESULT_OK, intent); + setResult(RESULT_OK, new Intent() + .setData(Uri.fromFile(new File(currentDir, adapter.getItem(lastPosition).getName())))); finish(); } }); @@ -107,11 +104,6 @@ public class SimpleDirChooser extends ListActivity { this.setListAdapter(adapter); } - @Override - protected void onListItemClick(ListView l, View v, int position, long id) { - super.onListItemClick(l, v, position, id); - } - public class FileArrayAdapter extends ArrayAdapter<Option> { private Context content; @@ -206,7 +198,7 @@ public class SimpleDirChooser extends ListActivity { } } - public class Option implements Comparable<Option> { + public static class Option implements Comparable<Option> { private final String name; private final String path; private boolean checked = false; diff --git a/main/src/cgeo/geocaching/filter/AbstractFilter.java b/main/src/cgeo/geocaching/filter/AbstractFilter.java index 44d6d3f..bc99959 100644 --- a/main/src/cgeo/geocaching/filter/AbstractFilter.java +++ b/main/src/cgeo/geocaching/filter/AbstractFilter.java @@ -1,6 +1,6 @@ package cgeo.geocaching.filter; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; import java.util.ArrayList; import java.util.List; @@ -8,14 +8,14 @@ import java.util.List; abstract class AbstractFilter implements IFilter { private final String name; - public AbstractFilter(String name) { + protected AbstractFilter(String name) { this.name = name; } @Override - public void filter(List<cgCache> list) { - final List<cgCache> itemsToRemove = new ArrayList<cgCache>(); - for (cgCache item : list) { + public void filter(List<Geocache> list) { + final List<Geocache> itemsToRemove = new ArrayList<Geocache>(); + for (Geocache item : list) { if (!accepts(item)) { itemsToRemove.add(item); } diff --git a/main/src/cgeo/geocaching/filter/AbstractRangeFilter.java b/main/src/cgeo/geocaching/filter/AbstractRangeFilter.java index ff3fce5..e84174a 100644 --- a/main/src/cgeo/geocaching/filter/AbstractRangeFilter.java +++ b/main/src/cgeo/geocaching/filter/AbstractRangeFilter.java @@ -8,8 +8,8 @@ abstract class AbstractRangeFilter extends AbstractFilter { protected final float rangeMin; protected final float rangeMax; - public AbstractRangeFilter(int ressourceId, int range) { - super(cgeoapplication.getInstance().getResources().getString(ressourceId) + ' ' + (range == 5 ? '5' : String.valueOf(range) + " + " + String.format("%.1f", range + 0.5))); + protected AbstractRangeFilter(int ressourceId, int range) { + super(cgeoapplication.getInstance().getResources().getString(ressourceId) + ' ' + (range == 5 ? '5' : range + " + " + String.format("%.1f", range + 0.5))); this.rangeMin = range; rangeMax = rangeMin + 1f; } diff --git a/main/src/cgeo/geocaching/filter/AttributeFilter.java b/main/src/cgeo/geocaching/filter/AttributeFilter.java index 2565178..4b6f382 100644 --- a/main/src/cgeo/geocaching/filter/AttributeFilter.java +++ b/main/src/cgeo/geocaching/filter/AttributeFilter.java @@ -1,7 +1,8 @@ package cgeo.geocaching.filter; import cgeo.geocaching.R; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; +import cgeo.geocaching.cgData; import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.enumerations.LoadFlags.LoadFlag; @@ -33,8 +34,8 @@ class AttributeFilter extends AbstractFilter { } @Override - public boolean accepts(final cgCache cache) { - cgCache fullCache = cgeoapplication.getInstance().loadCache(cache.getGeocode(), EnumSet.of(LoadFlag.LOAD_ATTRIBUTES)); + public boolean accepts(final Geocache cache) { + Geocache fullCache = cgData.loadCache(cache.getGeocode(), EnumSet.of(LoadFlag.LOAD_ATTRIBUTES)); if (fullCache == null) { fullCache = cache; } diff --git a/main/src/cgeo/geocaching/filter/DifficultyFilter.java b/main/src/cgeo/geocaching/filter/DifficultyFilter.java index 368c20f..c0ec61a 100644 --- a/main/src/cgeo/geocaching/filter/DifficultyFilter.java +++ b/main/src/cgeo/geocaching/filter/DifficultyFilter.java @@ -1,7 +1,7 @@ package cgeo.geocaching.filter; +import cgeo.geocaching.Geocache; import cgeo.geocaching.R; -import cgeo.geocaching.cgCache; import java.util.ArrayList; @@ -12,7 +12,7 @@ class DifficultyFilter extends AbstractRangeFilter { } @Override - public boolean accepts(cgCache cache) { + public boolean accepts(Geocache cache) { return rangeMin <= cache.getDifficulty() && cache.getDifficulty() < rangeMax; } diff --git a/main/src/cgeo/geocaching/filter/FilterUserInterface.java b/main/src/cgeo/geocaching/filter/FilterUserInterface.java index 230bc91..be63a08 100644 --- a/main/src/cgeo/geocaching/filter/FilterUserInterface.java +++ b/main/src/cgeo/geocaching/filter/FilterUserInterface.java @@ -53,6 +53,7 @@ public final class FilterUserInterface { register(R.string.cache_status, StateFilter.Factory.class); register(R.string.caches_filter_track, TrackablesFilter.class); register(R.string.caches_filter_modified, ModifiedFilter.class); + register(R.string.caches_filter_origin, OriginFilter.Factory.class); // sort by localized names Collections.sort(registry, new Comparator<FactoryEntry>() { @@ -73,7 +74,7 @@ public final class FilterUserInterface { public void selectFilter(final RunnableWithArgument<IFilter> runAfterwards) { final AlertDialog.Builder builder = new AlertDialog.Builder(activity); - builder.setTitle(R.string.caches_filter); + builder.setTitle(R.string.caches_filter_title); final ArrayAdapter<FactoryEntry> adapter = new ArrayAdapter<FactoryEntry>(activity, android.R.layout.select_dialog_item, registry); diff --git a/main/src/cgeo/geocaching/filter/IFilter.java b/main/src/cgeo/geocaching/filter/IFilter.java index abc2d50..4a428f8 100644 --- a/main/src/cgeo/geocaching/filter/IFilter.java +++ b/main/src/cgeo/geocaching/filter/IFilter.java @@ -1,6 +1,6 @@ package cgeo.geocaching.filter; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; import java.util.List; @@ -12,7 +12,7 @@ public interface IFilter { * @param cache * @return true if the filter accepts the cache, false otherwise */ - public abstract boolean accepts(final cgCache cache); + public abstract boolean accepts(final Geocache cache); - public void filter(final List<cgCache> list); + public void filter(final List<Geocache> list); }
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/filter/ModifiedFilter.java b/main/src/cgeo/geocaching/filter/ModifiedFilter.java index f74bb4d..f3e57de 100644 --- a/main/src/cgeo/geocaching/filter/ModifiedFilter.java +++ b/main/src/cgeo/geocaching/filter/ModifiedFilter.java @@ -1,7 +1,7 @@ package cgeo.geocaching.filter; +import cgeo.geocaching.Geocache; import cgeo.geocaching.R; -import cgeo.geocaching.cgCache; import cgeo.geocaching.cgeoapplication; class ModifiedFilter extends AbstractFilter implements IFilterFactory { @@ -11,7 +11,7 @@ class ModifiedFilter extends AbstractFilter implements IFilterFactory { } @Override - public boolean accepts(final cgCache cache) { + public boolean accepts(final Geocache cache) { // modified on GC return cache.hasUserModifiedCoords() || cache.hasFinalDefined(); } diff --git a/main/src/cgeo/geocaching/filter/OriginFilter.java b/main/src/cgeo/geocaching/filter/OriginFilter.java new file mode 100644 index 0000000..a880092 --- /dev/null +++ b/main/src/cgeo/geocaching/filter/OriginFilter.java @@ -0,0 +1,47 @@ +package cgeo.geocaching.filter; + +import cgeo.geocaching.Geocache; +import cgeo.geocaching.connector.ConnectorFactory; +import cgeo.geocaching.connector.IConnector; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; + +public class OriginFilter extends AbstractFilter { + + private final IConnector connector; + + public OriginFilter(IConnector connector) { + super(connector.getName()); + this.connector = connector; + } + + @Override + public boolean accepts(Geocache cache) { + return ConnectorFactory.getConnector(cache) == connector; + } + + public static final class Factory implements IFilterFactory { + + @Override + public IFilter[] getFilters() { + final ArrayList<OriginFilter> filters = new ArrayList<OriginFilter>(); + for (IConnector connector : ConnectorFactory.getConnectors()) { + filters.add(new OriginFilter(connector)); + } + + // sort connectors by name + Collections.sort(filters, new Comparator<OriginFilter>() { + + @Override + public int compare(OriginFilter lhs, OriginFilter rhs) { + return lhs.getName().compareToIgnoreCase(rhs.getName()); + } + }); + + return filters.toArray(new OriginFilter[filters.size()]); + } + + } +} diff --git a/main/src/cgeo/geocaching/filter/SizeFilter.java b/main/src/cgeo/geocaching/filter/SizeFilter.java index b08c2ae..7a34c83 100644 --- a/main/src/cgeo/geocaching/filter/SizeFilter.java +++ b/main/src/cgeo/geocaching/filter/SizeFilter.java @@ -1,6 +1,6 @@ package cgeo.geocaching.filter; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; import cgeo.geocaching.enumerations.CacheSize; import java.util.ArrayList; @@ -14,7 +14,7 @@ class SizeFilter extends AbstractFilter { } @Override - public boolean accepts(cgCache cache) { + public boolean accepts(Geocache cache) { return cacheSize == cache.getSize(); } diff --git a/main/src/cgeo/geocaching/filter/StateFilter.java b/main/src/cgeo/geocaching/filter/StateFilter.java index f51329a..0df47c1 100644 --- a/main/src/cgeo/geocaching/filter/StateFilter.java +++ b/main/src/cgeo/geocaching/filter/StateFilter.java @@ -1,7 +1,7 @@ package cgeo.geocaching.filter; +import cgeo.geocaching.Geocache; import cgeo.geocaching.R; -import cgeo.geocaching.cgCache; import cgeo.geocaching.cgeoapplication; import android.content.res.Resources; @@ -25,7 +25,7 @@ abstract class StateFilter extends AbstractFilter { } @Override - public boolean accepts(cgCache cache) { + public boolean accepts(Geocache cache) { return cache.isFound(); } @@ -37,7 +37,7 @@ abstract class StateFilter extends AbstractFilter { } @Override - public boolean accepts(cgCache cache) { + public boolean accepts(Geocache cache) { return cache.isArchived(); } } @@ -48,7 +48,7 @@ abstract class StateFilter extends AbstractFilter { } @Override - public boolean accepts(cgCache cache) { + public boolean accepts(Geocache cache) { return cache.isDisabled(); } } @@ -59,7 +59,7 @@ abstract class StateFilter extends AbstractFilter { } @Override - public boolean accepts(cgCache cache) { + public boolean accepts(Geocache cache) { return cache.isPremiumMembersOnly(); } } @@ -70,7 +70,7 @@ abstract class StateFilter extends AbstractFilter { } @Override - public boolean accepts(cgCache cache) { + public boolean accepts(Geocache cache) { return !cache.isPremiumMembersOnly(); } } @@ -81,7 +81,7 @@ abstract class StateFilter extends AbstractFilter { } @Override - public boolean accepts(cgCache cache) { + public boolean accepts(Geocache cache) { return cache.isLogOffline(); } } diff --git a/main/src/cgeo/geocaching/filter/TerrainFilter.java b/main/src/cgeo/geocaching/filter/TerrainFilter.java index 5cee87e..f7703d5 100644 --- a/main/src/cgeo/geocaching/filter/TerrainFilter.java +++ b/main/src/cgeo/geocaching/filter/TerrainFilter.java @@ -2,7 +2,7 @@ package cgeo.geocaching.filter; import cgeo.geocaching.R; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; import java.util.ArrayList; @@ -13,7 +13,7 @@ class TerrainFilter extends AbstractRangeFilter { } @Override - public boolean accepts(cgCache cache) { + public boolean accepts(Geocache cache) { return rangeMin <= cache.getTerrain() && cache.getTerrain() < rangeMax; } diff --git a/main/src/cgeo/geocaching/filter/TrackablesFilter.java b/main/src/cgeo/geocaching/filter/TrackablesFilter.java index 90def5b..3225daa 100644 --- a/main/src/cgeo/geocaching/filter/TrackablesFilter.java +++ b/main/src/cgeo/geocaching/filter/TrackablesFilter.java @@ -1,7 +1,7 @@ package cgeo.geocaching.filter; import cgeo.geocaching.R; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; import cgeo.geocaching.cgeoapplication; class TrackablesFilter extends AbstractFilter implements IFilterFactory { @@ -10,7 +10,7 @@ class TrackablesFilter extends AbstractFilter implements IFilterFactory { } @Override - public boolean accepts(cgCache cache) { + public boolean accepts(Geocache cache) { return cache.hasTrackables(); } diff --git a/main/src/cgeo/geocaching/filter/TypeFilter.java b/main/src/cgeo/geocaching/filter/TypeFilter.java index 05b97e0..eeab552 100644 --- a/main/src/cgeo/geocaching/filter/TypeFilter.java +++ b/main/src/cgeo/geocaching/filter/TypeFilter.java @@ -1,6 +1,6 @@ package cgeo.geocaching.filter; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; import cgeo.geocaching.enumerations.CacheType; import java.util.ArrayList; @@ -14,7 +14,7 @@ class TypeFilter extends AbstractFilter { } @Override - public boolean accepts(final cgCache cache) { + public boolean accepts(final Geocache cache) { return cacheType == cache.getType(); } diff --git a/main/src/cgeo/geocaching/gcvote/GCVote.java b/main/src/cgeo/geocaching/gcvote/GCVote.java index 5a00009..a053f31 100644 --- a/main/src/cgeo/geocaching/gcvote/GCVote.java +++ b/main/src/cgeo/geocaching/gcvote/GCVote.java @@ -1,21 +1,22 @@ package cgeo.geocaching.gcvote; +import cgeo.geocaching.Geocache; import cgeo.geocaching.Settings; -import cgeo.geocaching.cgCache; import cgeo.geocaching.network.Network; import cgeo.geocaching.network.Parameters; import cgeo.geocaching.utils.LeastRecentlyUsedMap; import cgeo.geocaching.utils.Log; +import cgeo.geocaching.utils.MatcherWrapper; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.regex.Matcher; import java.util.regex.Pattern; public final class GCVote { @@ -38,31 +39,16 @@ public final class GCVote { * @return */ public static GCVoteRating getRating(String guid, String geocode) { - List<String> guids = null; - List<String> geocodes = null; - - if (StringUtils.isNotBlank(guid)) { - - GCVoteRating rating = ratingsCache.get(guid); - if (rating != null) { - return rating; - } - guids = new ArrayList<String>(); - guids.add(guid); - } else if (StringUtils.isNotBlank(geocode)) { - geocodes = new ArrayList<String>(); - geocodes.add(geocode); - } else { - return null; + if (StringUtils.isNotBlank(guid) && ratingsCache.containsKey(guid)) { + return ratingsCache.get(guid); } - final Map<String, GCVoteRating> ratings = getRating(guids, geocodes); - - if (MapUtils.isEmpty(ratings)) { - return null; - } + final Map<String, GCVoteRating> ratings = getRating(singletonOrNull(guid), singletonOrNull(geocode)); + return MapUtils.isNotEmpty(ratings) ? ratings.values().iterator().next() : null; + } - return ratings.values().iterator().next(); + private static List<String> singletonOrNull(final String item) { + return StringUtils.isNotBlank(item) ? Collections.singletonList(item) : null; } /** @@ -98,17 +84,16 @@ public final class GCVote { return null; } - String voteData; - final Matcher matcherVoteElement = patternVoteElement.matcher(page); + final MatcherWrapper matcherVoteElement = new MatcherWrapper(patternVoteElement, page); while (matcherVoteElement.find()) { - voteData = matcherVoteElement.group(1); + String voteData = matcherVoteElement.group(1); if (voteData == null) { continue; } String guid = null; try { - final Matcher matcherGuid = patternGuid.matcher(voteData); + final MatcherWrapper matcherGuid = new MatcherWrapper(patternGuid, voteData); if (matcherGuid.find()) { if (matcherGuid.groupCount() > 0) { guid = matcherGuid.group(1); @@ -123,7 +108,7 @@ public final class GCVote { boolean loggedIn = false; try { - final Matcher matcherLoggedIn = patternLogIn.matcher(page); + final MatcherWrapper matcherLoggedIn = new MatcherWrapper(patternLogIn, page); if (matcherLoggedIn.find()) { if (matcherLoggedIn.groupCount() > 0) { if (matcherLoggedIn.group(1).equalsIgnoreCase("true")) { @@ -137,7 +122,7 @@ public final class GCVote { float rating = 0; try { - final Matcher matcherRating = patternRating.matcher(voteData); + final MatcherWrapper matcherRating = new MatcherWrapper(patternRating, voteData); if (matcherRating.find()) { rating = Float.parseFloat(matcherRating.group(1)); } @@ -150,7 +135,7 @@ public final class GCVote { int votes = -1; try { - final Matcher matcherVotes = patternVotes.matcher(voteData); + final MatcherWrapper matcherVotes = new MatcherWrapper(patternVotes, voteData); if (matcherVotes.find()) { votes = Integer.parseInt(matcherVotes.group(1)); } @@ -164,7 +149,7 @@ public final class GCVote { float myVote = 0; if (loggedIn) { try { - final Matcher matcherVote = patternVote.matcher(voteData); + final MatcherWrapper matcherVote = new MatcherWrapper(patternVote, voteData); if (matcherVote.find()) { myVote = Float.parseFloat(matcherVote.group(1)); } @@ -180,7 +165,7 @@ public final class GCVote { } } } catch (Exception e) { - Log.e("GCVote.getRating: " + e.toString()); + Log.e("GCVote.getRating", e); } return ratings; @@ -193,7 +178,7 @@ public final class GCVote { * @param vote * @return */ - public static boolean setRating(cgCache cache, double vote) { + public static boolean setRating(Geocache cache, double vote) { if (!cache.supportsGCVote()) { return false; } @@ -222,13 +207,13 @@ public final class GCVote { return result.trim().equalsIgnoreCase("ok"); } - public static void loadRatings(ArrayList<cgCache> caches) { + public static void loadRatings(ArrayList<Geocache> caches) { if (!Settings.isRatingWanted()) { return; } final ArrayList<String> guids = new ArrayList<String>(caches.size()); - for (final cgCache cache : caches) { + for (final Geocache cache : caches) { String guid = cache.getGuid(); if (StringUtils.isNotBlank(guid)) { guids.add(guid); @@ -244,7 +229,7 @@ public final class GCVote { if (MapUtils.isNotEmpty(ratings)) { // save found cache coordinates - for (cgCache cache : caches) { + for (Geocache cache : caches) { if (ratings.containsKey(cache.getGuid())) { GCVoteRating rating = ratings.get(cache.getGuid()); @@ -255,7 +240,7 @@ public final class GCVote { } } } catch (Exception e) { - Log.e("GCvote.loadRatings: " + e.toString()); + Log.e("GCvote.loadRatings", e); } } } diff --git a/main/src/cgeo/geocaching/geopoint/DistanceParser.java b/main/src/cgeo/geocaching/geopoint/DistanceParser.java index 5f02895..5a840a8 100644 --- a/main/src/cgeo/geocaching/geopoint/DistanceParser.java +++ b/main/src/cgeo/geocaching/geopoint/DistanceParser.java @@ -1,6 +1,10 @@ package cgeo.geocaching.geopoint; -import java.util.regex.Matcher; +import cgeo.geocaching.utils.MatcherWrapper; + +import org.apache.commons.lang3.StringUtils; + +import java.util.Locale; import java.util.regex.Pattern; public final class DistanceParser { @@ -18,17 +22,17 @@ public final class DistanceParser { * @throws NumberFormatException * if the given number is invalid */ - public static float parseDistance(String distanceText, final boolean metricUnit) { - final Matcher matcher = pattern.matcher(distanceText); + public static float parseDistance(String distanceText, final boolean metricUnit) throws NumberFormatException { + final MatcherWrapper matcher = new MatcherWrapper(pattern, distanceText); if (!matcher.find()) { throw new NumberFormatException(distanceText); } final float value = Float.parseFloat(matcher.group(1).replace(',', '.')); - final String unit = matcher.group(2).toLowerCase(); + final String unit = matcher.group(2).toLowerCase(Locale.US); - if (unit.equals("m") || (unit.length() == 0 && metricUnit)) { + if (unit.equals("m") || (StringUtils.isEmpty(unit) && metricUnit)) { return value / 1000; } if (unit.equals("km")) { diff --git a/main/src/cgeo/geocaching/geopoint/Geopoint.java b/main/src/cgeo/geocaching/geopoint/Geopoint.java index 00dec7f..c03a2bc 100644 --- a/main/src/cgeo/geocaching/geopoint/Geopoint.java +++ b/main/src/cgeo/geocaching/geopoint/Geopoint.java @@ -23,6 +23,8 @@ public final class Geopoint implements ICoordinates, Parcelable { public static final double rad2deg = 180 / Math.PI; public static final float erad = 6371.0f; + public static final Geopoint ZERO = new Geopoint(0.0, 0.0); + private final double latitude; private final double longitude; @@ -34,8 +36,7 @@ public final class Geopoint implements ICoordinates, Parcelable { * @param lon * longitude */ - public Geopoint(final double lat, final double lon) - { + public Geopoint(final double lat, final double lon) { latitude = lat; longitude = lon; } @@ -140,7 +141,7 @@ public final class Geopoint implements ICoordinates, Parcelable { * @param lonSecFrac */ public Geopoint(final String latDir, final String latDeg, final String latMin, final String latSec, final String latSecFrac, - final String lonDir, final String lonDeg, final String lonMin, final String lonSec, final String lonSecFrac) { + final String lonDir, final String lonDeg, final String lonMin, final String lonSec, final String lonSecFrac) { this(latDir + " " + latDeg + " " + latMin + " " + latSec + "." + addZeros(latSecFrac, 3), lonDir + " " + lonDeg + " " + lonMin + " " + lonSec + "." + addZeros(lonSecFrac, 3)); } @@ -150,8 +151,7 @@ public final class Geopoint implements ICoordinates, Parcelable { * * @return latitude */ - public double getLatitude() - { + public double getLatitude() { return latitude; } @@ -160,8 +160,7 @@ public final class Geopoint implements ICoordinates, Parcelable { * * @return latitude */ - public int getLatitudeE6() - { + public int getLatitudeE6() { return (int) Math.round(latitude * 1E6); } @@ -170,8 +169,7 @@ public final class Geopoint implements ICoordinates, Parcelable { * * @return longitude */ - public double getLongitude() - { + public double getLongitude() { return longitude; } @@ -180,8 +178,7 @@ public final class Geopoint implements ICoordinates, Parcelable { * * @return longitude */ - public int getLongitudeE6() - { + public int getLongitudeE6() { return (int) Math.round(longitude * 1E6); } @@ -207,8 +204,7 @@ public final class Geopoint implements ICoordinates, Parcelable { * @throws GeopointException * if there is an error in distance calculation */ - public float distanceTo(final ICoordinates point) - { + public float distanceTo(final ICoordinates point) { return pathTo(point.getCoords())[0] / 1000; } @@ -219,8 +215,7 @@ public final class Geopoint implements ICoordinates, Parcelable { * target * @return bearing in degree, in the [0,360[ range */ - public float bearingTo(final ICoordinates point) - { + public float bearingTo(final ICoordinates point) { // Android library returns a bearing in the [-180;180] range final float bearing = pathTo(point.getCoords())[1]; return bearing < 0 ? bearing + 360 : bearing; @@ -235,8 +230,7 @@ public final class Geopoint implements ICoordinates, Parcelable { * distance in km * @return the projected geopoint */ - public Geopoint project(final double bearing, final double distance) - { + public Geopoint project(final double bearing, final double distance) { final double rlat1 = latitude * deg2rad; final double rlon1 = longitude * deg2rad; final double rbearing = bearing * deg2rad; @@ -253,7 +247,7 @@ public final class Geopoint implements ICoordinates, Parcelable { if (this == obj) { return true; } - if (obj == null || !(obj instanceof Geopoint)) { + if (!(obj instanceof Geopoint)) { return false; } final Geopoint gp = (Geopoint) obj; @@ -274,8 +268,7 @@ public final class Geopoint implements ICoordinates, Parcelable { * tolerance in km * @return true if similar, false otherwise */ - public boolean isEqualTo(Geopoint gp, double tolerance) - { + public boolean isEqualTo(Geopoint gp, double tolerance) { return null != gp && distanceTo(gp) <= tolerance; } @@ -287,8 +280,7 @@ public final class Geopoint implements ICoordinates, Parcelable { * @see GeopointFormatter * @return formatted coordinates */ - public String format(GeopointFormatter.Format format) - { + public String format(GeopointFormatter.Format format) { return GeopointFormatter.format(format, this); } @@ -299,30 +291,23 @@ public final class Geopoint implements ICoordinates, Parcelable { * @return formatted coordinates */ @Override - public String toString() - { + public String toString() { return format(GeopointFormatter.Format.LAT_LON_DECMINUTE); } - abstract public static class GeopointException - extends RuntimeException - { + abstract public static class GeopointException extends NumberFormatException { private static final long serialVersionUID = 1L; - public GeopointException(String msg) - { + protected GeopointException(String msg) { super(msg); } } - public static class ParseException - extends GeopointException - { + public static class ParseException extends GeopointException { private static final long serialVersionUID = 1L; public final int resource; - public ParseException(final String msg, final GeopointParser.LatLon faulty) - { + public ParseException(final String msg, final GeopointParser.LatLon faulty) { super(msg); resource = faulty == GeopointParser.LatLon.LAT ? R.string.err_parse_lat : R.string.err_parse_lon; } @@ -350,7 +335,7 @@ public final class Geopoint implements ICoordinates, Parcelable { return result.getDouble("elevation"); } } catch (Exception e) { - Log.w("cgBase.getElevation: " + e.toString()); + Log.w("cgBase.getElevation", e); } return null; diff --git a/main/src/cgeo/geocaching/geopoint/GeopointFormatter.java b/main/src/cgeo/geocaching/geopoint/GeopointFormatter.java index c706e77..ba0a4d5 100644 --- a/main/src/cgeo/geocaching/geopoint/GeopointFormatter.java +++ b/main/src/cgeo/geocaching/geopoint/GeopointFormatter.java @@ -5,12 +5,11 @@ import java.util.Locale; /** * Formatting of Geopoint. */ -public class GeopointFormatter -{ +public class GeopointFormatter { /** * Predefined formats. */ - public static enum Format { + public enum Format { /** Example: "10,123456 -0,123456" */ LAT_LON_DECDEGREE, @@ -54,20 +53,19 @@ public class GeopointFormatter * one of the predefined formats * @return the formatted coordinates */ - public static String format(final Format format, final Geopoint gp) - { + public static String format(final Format format, final Geopoint gp) { final double latSigned = gp.getLatitude(); final double lonSigned = gp.getLongitude(); switch (format) { case LAT_LON_DECDEGREE: - return String.format("%.6f %.6f", latSigned, lonSigned); + return String.format(Locale.getDefault(), "%.6f %.6f", latSigned, lonSigned); case LAT_LON_DECDEGREE_COMMA: return String.format((Locale) null, "%.6f,%.6f", latSigned, lonSigned); case LAT_LON_DECMINUTE: - return String.format("%c %02d° %06.3f · %c %03d° %06.3f", + return String.format(Locale.getDefault(), "%c %02d° %06.3f · %c %03d° %06.3f", gp.getLatDir(), gp.getLatDeg(), gp.getLatMinRaw(), gp.getLonDir(), gp.getLonDeg(), gp.getLonMinRaw()); case LAT_LON_DECMINUTE_RAW: @@ -75,7 +73,7 @@ public class GeopointFormatter gp.getLatDir(), gp.getLatDeg(), gp.getLatMinRaw(), gp.getLonDir(), gp.getLonDeg(), gp.getLonMinRaw()); case LAT_LON_DECSECOND: - return String.format("%c %02d° %02d' %06.3f\" · %c %03d° %02d' %06.3f\"", + return String.format(Locale.getDefault(), "%c %02d° %02d' %06.3f\" · %c %03d° %02d' %06.3f\"", gp.getLatDir(), gp.getLatDeg(), gp.getLatMin(), gp.getLatSecRaw(), gp.getLonDir(), gp.getLonDeg(), gp.getLonMin(), gp.getLonSecRaw()); @@ -83,23 +81,22 @@ public class GeopointFormatter return String.format((Locale) null, "%.6f", latSigned); case LAT_DECMINUTE: - return String.format("%c %02d° %06.3f", gp.getLatDir(), gp.getLatDeg(), gp.getLatMinRaw()); + return String.format(Locale.getDefault(), "%c %02d° %06.3f", gp.getLatDir(), gp.getLatDeg(), gp.getLatMinRaw()); case LAT_DECMINUTE_RAW: - return String.format("%c %02d %06.3f", gp.getLatDir(), gp.getLatDeg(), gp.getLatMinRaw()); + return String.format(Locale.getDefault(), "%c %02d %06.3f", gp.getLatDir(), gp.getLatDeg(), gp.getLatMinRaw()); case LON_DECDEGREE_RAW: return String.format((Locale) null, "%.6f", lonSigned); case LON_DECMINUTE: - return String.format("%c %03d° %06.3f", gp.getLonDir(), gp.getLonDeg(), gp.getLonMinRaw()); + return String.format(Locale.getDefault(), "%c %03d° %06.3f", gp.getLonDir(), gp.getLonDeg(), gp.getLonMinRaw()); case LON_DECMINUTE_RAW: - return String.format("%c %03d %06.3f", gp.getLonDir(), gp.getLonDeg(), gp.getLonMinRaw()); + return String.format(Locale.getDefault(), "%c %03d %06.3f", gp.getLonDir(), gp.getLonDeg(), gp.getLonMinRaw()); + default: + throw new IllegalArgumentException(); } - - // Keep the compiler happy even though it cannot happen - return null; } } diff --git a/main/src/cgeo/geocaching/geopoint/GeopointParser.java b/main/src/cgeo/geocaching/geopoint/GeopointParser.java index 7604b9d..ba86e70 100644 --- a/main/src/cgeo/geocaching/geopoint/GeopointParser.java +++ b/main/src/cgeo/geocaching/geopoint/GeopointParser.java @@ -1,15 +1,17 @@ package cgeo.geocaching.geopoint; +import cgeo.geocaching.utils.MatcherWrapper; + import org.apache.commons.lang3.StringUtils; -import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Parse coordinates. */ class GeopointParser { + private static class ResultWrapper { final double result; final int matcherPos; @@ -22,12 +24,13 @@ class GeopointParser { } } - // ( 1 ) ( 2 ) ( 3 ) ( 4 ) ( 5 ) - private static final Pattern patternLat = Pattern.compile("\\b([NS])\\s*(\\d+)°?(?:\\s*(\\d+)(?:[.,](\\d+)|'?\\s*(\\d+(?:[.,]\\d+)?)(?:''|\")?)?)?", Pattern.CASE_INSENSITIVE); - private static final Pattern patternLon = Pattern.compile("\\b([WE])\\s*(\\d+)°?(?:\\s*(\\d+)(?:[.,](\\d+)|'?\\s*(\\d+(?:[.,]\\d+)?)(?:''|\")?)?)?", Pattern.CASE_INSENSITIVE); + // ( 1 ) ( 2 ) ( 3 ) ( 4 ) ( 5 ) + private static final Pattern PATTERN_LAT = Pattern.compile("\\b([NS])\\s*(\\d+)°?(?:\\s*(\\d+)(?:[.,](\\d+)|'?\\s*(\\d+(?:[.,]\\d+)?)(?:''|\")?)?)?", Pattern.CASE_INSENSITIVE); + private static final Pattern PATTERN_LON = Pattern.compile("\\b([WE])\\s*(\\d+)°?(?:\\s*(\\d+)(?:[.,](\\d+)|'?\\s*(\\d+(?:[.,]\\d+)?)(?:''|\")?)?)?", Pattern.CASE_INSENSITIVE); - enum LatLon - { + private static final Pattern PATTERN_BAD_BLANK = Pattern.compile("(\\d)[,.] (\\d{2,})"); + + enum LatLon { LAT, LON } @@ -52,8 +55,7 @@ class GeopointParser { * @throws Geopoint.ParseException * if lat or lon could not be parsed */ - public static Geopoint parse(final String text) - { + public static Geopoint parse(final String text) { final ResultWrapper latitudeWrapper = parseHelper(text, LatLon.LAT); final double lat = latitudeWrapper.result; // cut away the latitude part when parsing the longitude @@ -89,8 +91,7 @@ class GeopointParser { * @throws Geopoint.ParseException * if lat or lon could not be parsed */ - public static Geopoint parse(final String latitude, final String longitude) - { + public static Geopoint parse(final String latitude, final String longitude) { final double lat = parseLatitude(latitude); final double lon = parseLongitude(longitude); @@ -101,11 +102,13 @@ class GeopointParser { * (non JavaDoc) * Helper for coordinates-parsing. */ - private static ResultWrapper parseHelper(final String text, final LatLon latlon) - { + private static ResultWrapper parseHelper(final String text, final LatLon latlon) { + + MatcherWrapper matcher = new MatcherWrapper(PATTERN_BAD_BLANK, text); + String replaceSpaceAfterComma = matcher.replaceAll("$1.$2"); - final Pattern pattern = LatLon.LAT == latlon ? patternLat : patternLon; - final Matcher matcher = pattern.matcher(text); + final Pattern pattern = LatLon.LAT == latlon ? PATTERN_LAT : PATTERN_LON; + matcher = new MatcherWrapper(pattern, replaceSpaceAfterComma); if (matcher.find()) { final double sign = matcher.group(1).equalsIgnoreCase("S") || matcher.group(1).equalsIgnoreCase("W") ? -1.0 : 1.0; @@ -153,8 +156,7 @@ class GeopointParser { * @throws Geopoint.ParseException * if latitude could not be parsed */ - public static double parseLatitude(final String text) - { + public static double parseLatitude(final String text) { return parseHelper(text, LatLon.LAT).result; } @@ -168,8 +170,7 @@ class GeopointParser { * @throws Geopoint.ParseException * if longitude could not be parsed */ - public static double parseLongitude(final String text) - { + public static double parseLongitude(final String text) { return parseHelper(text, LatLon.LON).result; } } diff --git a/main/src/cgeo/geocaching/geopoint/Viewport.java b/main/src/cgeo/geocaching/geopoint/Viewport.java index 97ee21d..4aca538 100644 --- a/main/src/cgeo/geocaching/geopoint/Viewport.java +++ b/main/src/cgeo/geocaching/geopoint/Viewport.java @@ -164,7 +164,10 @@ public class Viewport { @Override public boolean equals(final Object other) { - if (other == null || !(other instanceof Viewport)) { + if (this == other) { + return true; + } + if (!(other instanceof Viewport)) { return false; } final Viewport vp = (Viewport) other; diff --git a/main/src/cgeo/geocaching/loaders/UrlLoader.java b/main/src/cgeo/geocaching/loaders/UrlLoader.java new file mode 100644 index 0000000..abafd5f --- /dev/null +++ b/main/src/cgeo/geocaching/loaders/UrlLoader.java @@ -0,0 +1,35 @@ +package cgeo.geocaching.loaders; + +import cgeo.geocaching.network.Network; +import cgeo.geocaching.network.Parameters; +import cgeo.geocaching.utils.Log; + +import android.content.Context; +import android.support.v4.content.AsyncTaskLoader; + +public class UrlLoader extends AsyncTaskLoader<String> { + + final private String url; + final private Parameters params; + + public UrlLoader(final Context context, final String url, final Parameters params) { + super(context); + this.url = url; + this.params = params; + } + + @Override + protected void onStartLoading() { + forceLoad(); + } + + @Override + public String loadInBackground() { + try { + return Network.getResponseData(Network.getRequest(url, params)); + } catch (final Exception e) { + Log.w("cgeovisit.UrlLoader.loadInBackground", e); + return null; + } + } +} diff --git a/main/src/cgeo/geocaching/maps/AbstractMapSource.java b/main/src/cgeo/geocaching/maps/AbstractMapSource.java index 90a61d2..1d219d3 100644 --- a/main/src/cgeo/geocaching/maps/AbstractMapSource.java +++ b/main/src/cgeo/geocaching/maps/AbstractMapSource.java @@ -9,7 +9,7 @@ public abstract class AbstractMapSource implements MapSource { private final MapProvider mapProvider; private final String id; - public AbstractMapSource(final String id, final MapProvider mapProvider, final String name) { + protected AbstractMapSource(final String id, final MapProvider mapProvider, final String name) { this.id = id; this.mapProvider = mapProvider; this.name = name; diff --git a/main/src/cgeo/geocaching/maps/CGeoMap.java b/main/src/cgeo/geocaching/maps/CGeoMap.java index e48ca70..4b8d1a0 100644 --- a/main/src/cgeo/geocaching/maps/CGeoMap.java +++ b/main/src/cgeo/geocaching/maps/CGeoMap.java @@ -1,15 +1,14 @@ package cgeo.geocaching.maps; import cgeo.geocaching.DirectionProvider; +import cgeo.geocaching.Geocache; import cgeo.geocaching.IGeoData; -import cgeo.geocaching.IWaypoint; -import cgeo.geocaching.LiveMapInfo; import cgeo.geocaching.R; import cgeo.geocaching.SearchResult; import cgeo.geocaching.Settings; import cgeo.geocaching.StoredList; -import cgeo.geocaching.cgCache; -import cgeo.geocaching.cgWaypoint; +import cgeo.geocaching.Waypoint; +import cgeo.geocaching.cgData; import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.cgeocaches; import cgeo.geocaching.activity.ActivityMixin; @@ -31,11 +30,13 @@ import cgeo.geocaching.maps.interfaces.MapProvider; import cgeo.geocaching.maps.interfaces.MapSource; import cgeo.geocaching.maps.interfaces.MapViewImpl; import cgeo.geocaching.maps.interfaces.OnMapDragListener; +import cgeo.geocaching.ui.dialog.LiveMapInfoDialogBuilder; import cgeo.geocaching.utils.AngleUtils; import cgeo.geocaching.utils.CancellableHandler; import cgeo.geocaching.utils.GeoDirHandler; import cgeo.geocaching.utils.LeastRecentlyUsedSet; import cgeo.geocaching.utils.Log; +import cgeo.geocaching.utils.RunnableWithArgument; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; @@ -84,7 +85,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 behaviour of the map */ public enum MapMode { /** Live Map */ LIVE, @@ -95,6 +96,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto /** Map with a list of caches (no reload on move) */ LIST } + /** Handler Messages */ private static final int HIDE_PROGRESS = 0; private static final int SHOW_PROGRESS = 1; @@ -177,9 +179,9 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto /** Count of caches currently visible */ private int cachesCnt = 0; /** List of caches in the viewport */ - private LeastRecentlyUsedSet<cgCache> caches = null; + private LeastRecentlyUsedSet<Geocache> caches = null; /** List of waypoints in the viewport */ - private final LeastRecentlyUsedSet<cgWaypoint> waypoints = new LeastRecentlyUsedSet<cgWaypoint>(MAX_CACHES); + private final LeastRecentlyUsedSet<Waypoint> waypoints = new LeastRecentlyUsedSet<Waypoint>(MAX_CACHES); // storing for offline private ProgressDialog waitDialog = null; private int detailTotal = 0; @@ -188,7 +190,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto // views private ImageSwitcher myLocSwitch = null; - /**Controls the map behaviour*/ + /** Controls the map behaviour */ private MapMode mapMode = null; /** Live mode enabled for map. **/ private boolean isLiveEnabled; @@ -226,7 +228,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } countVisibleCaches(); - if (caches != null && caches.size() > 0 && !mapTitle.contains("[")) { + if (caches != null && !caches.isEmpty() && !mapTitle.contains("[")) { title.append(" [").append(cachesCnt); if (cachesCnt != caches.size()) { title.append('/').append(caches.size()); @@ -288,7 +290,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto waitDialog.setMessage(res.getString(R.string.caches_downloading) + " " + res.getString(R.string.caches_eta_ltm)); } else { int minsRemaining = secondsRemaining / 60; - waitDialog.setMessage(res.getString(R.string.caches_downloading) + " " + minsRemaining + " " + res.getQuantityString(R.plurals.caches_eta_mins,minsRemaining)); + waitDialog.setMessage(res.getString(R.string.caches_downloading) + " " + minsRemaining + " " + res.getQuantityString(R.plurals.caches_eta_mins, minsRemaining)); } } } else if (msg.what == FINISHED_LOADING_DETAILS) { @@ -335,13 +337,13 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } protected void countVisibleCaches() { - final List<cgCache> protectedCaches = caches.getAsList(); + final List<Geocache> protectedCaches = caches.getAsList(); int count = 0; - if (protectedCaches.size() > 0) { + if (!protectedCaches.isEmpty()) { final Viewport viewport = mapView.getViewport(); - for (final cgCache cache : protectedCaches) { + for (final Geocache cache : protectedCaches) { if (cache != null && cache.getCoords() != null) { if (viewport.contains(cache)) { count++; @@ -368,8 +370,8 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto activity = this.getActivity(); app = (cgeoapplication) activity.getApplication(); - int countBubbleCnt = app.getAllStoredCachesCount(true, CacheType.ALL); - caches = new LeastRecentlyUsedSet<cgCache>(MAX_CACHES + countBubbleCnt); + int countBubbleCnt = cgData.getAllCachesCount(); + caches = new LeastRecentlyUsedSet<Geocache>(MAX_CACHES + countBubbleCnt); final MapProvider mapProvider = Settings.getMapProvider(); mapItemFactory = mapProvider.getMapItemFactory(); @@ -469,9 +471,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto prepareFilterBar(); if (!app.isLiveMapHintShown() && !Settings.getHideLiveMapHint()) { - Intent hintIntent = new Intent(activity, LiveMapInfo.class); - activity.startActivity(hintIntent); - app.setLiveMapHintShown(); + LiveMapInfoDialogBuilder.create(activity).show(); } } @@ -494,10 +494,11 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto if (!CollectionUtils.isEmpty(dirtyCaches)) { for (String geocode : dirtyCaches) { - cgCache cache = app.loadCache(geocode, LoadFlags.LOAD_WAYPOINTS); + Geocache cache = cgData.loadCache(geocode, LoadFlags.LOAD_WAYPOINTS); if (cache != null) { - // remove to update the cache + // new collection type needs to remove first caches.remove(cache); + // re-add to update the freshness caches.add(cache); } } @@ -579,9 +580,8 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } } - MenuItem item; try { - item = menu.findItem(MENU_TRAIL_MODE); // show trail + MenuItem item = menu.findItem(MENU_TRAIL_MODE); if (Settings.isMapTrail()) { item.setTitle(res.getString(R.string.map_trail_hide)); } else { @@ -596,7 +596,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } final Set<String> geocodesInViewport = getGeocodesForCachesInViewport(); - menu.findItem(MENU_STORE_CACHES).setEnabled(!isLoading() && CollectionUtils.isNotEmpty(geocodesInViewport) && app.hasUnsavedCaches(new SearchResult(geocodesInViewport))); + menu.findItem(MENU_STORE_CACHES).setEnabled(!isLoading() && CollectionUtils.isNotEmpty(geocodesInViewport) && new SearchResult(geocodesInViewport).hasUnsavedCaches()); item = menu.findItem(MENU_CIRCLE_MODE); // show circles if (overlayCaches != null && overlayCaches.getCircles()) { @@ -612,7 +612,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto menu.findItem(SUBMENU_STRATEGY).setEnabled(isLiveEnabled); } catch (Exception e) { - Log.e("cgeomap.onPrepareOptionsMenu: " + e); + Log.e("cgeomap.onPrepareOptionsMenu", e); } return true; @@ -642,7 +642,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto final List<String> geocodes = new ArrayList<String>(); for (final String geocode : geocodesInViewport) { - if (!app.isOffline(geocode, null)) { + if (!cgData.isOffline(geocode, null)) { geocodes.add(geocode); } } @@ -656,42 +656,18 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto return true; } - final LoadDetailsHandler loadDetailsHandler = new LoadDetailsHandler(); - - waitDialog = new ProgressDialog(activity); - waitDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); - waitDialog.setCancelable(true); - waitDialog.setCancelMessage(loadDetailsHandler.cancelMessage()); - waitDialog.setMax(detailTotal); - waitDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { - - @Override - public void onCancel(DialogInterface arg0) { - try { - if (loadDetailsThread != null) { - loadDetailsThread.stopIt(); - } - - geoDirUpdate.startDir(); - } catch (Exception e) { - Log.e("cgeocaches.onPrepareOptionsMenu.onCancel: " + e.toString()); - } - } - }); - - float etaTime = detailTotal * 7.0f / 60.0f; - int roundedEta = Math.round(etaTime); - if (etaTime < 0.4) { - waitDialog.setMessage(res.getString(R.string.caches_downloading) + " " + res.getString(R.string.caches_eta_ltm)); - } else { - waitDialog.setMessage(res.getString(R.string.caches_downloading) + " " + roundedEta + " " + res.getQuantityString(R.plurals.caches_eta_mins, roundedEta)); + if (Settings.getChooseList()) { + // let user select list to store cache in + new StoredList.UserInterface(activity).promptForListSelection(R.string.list_title, + new RunnableWithArgument<Integer>() { + @Override + public void run(final Integer selectedListId) { + storeCaches(geocodes, selectedListId); + } + }, true, StoredList.TEMPORARY_LIST_ID); + } else { + storeCaches(geocodes, StoredList.STANDARD_LIST_ID); } - waitDialog.show(); - - detailProgressTime = System.currentTimeMillis(); - - loadDetailsThread = new LoadDetails(loadDetailsHandler, geocodes); - loadDetailsThread.start(); } return true; case MENU_CIRCLE_MODE: @@ -752,9 +728,9 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto currentTheme = currentThemeFile.getName(); } - int currentItem = 0; List<String> names = new ArrayList<String>(); names.add(res.getString(R.string.map_theme_builtin)); + int currentItem = 0; for (File file : themeFiles) { if (currentTheme.equalsIgnoreCase(file.getName())) { currentItem = names.size(); @@ -768,7 +744,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto builder.setTitle(R.string.map_theme_select); - builder.setSingleChoiceItems(names.toArray(new String[] {}), selectedItem, + builder.setSingleChoiceItems(names.toArray(new String[names.size()]), selectedItem, new DialogInterface.OnClickListener() { @Override @@ -792,15 +768,15 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } /** - * @return a Set of geocodes corresponding to the caches that are shown on screen. + * @return a non-null Set of geocodes corresponding to the caches that are shown on screen. */ private Set<String> getGeocodesForCachesInViewport() { final Set<String> geocodes = new HashSet<String>(); - final List<cgCache> cachesProtected = caches.getAsList(); + final List<Geocache> cachesProtected = caches.getAsList(); final Viewport viewport = mapView.getViewport(); - for (final cgCache cache : cachesProtected) { + for (final Geocache cache : cachesProtected) { if (viewport.contains(cache)) { geocodes.add(cache.getGeocode()); } @@ -1053,22 +1029,11 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto // check if map moved or zoomed //TODO Portree Use Rectangle inside with bigger search window. That will stop reloading on every move - boolean moved = false; - - if (liveChanged) { - moved = true; - } else if (isLiveEnabled && !downloaded) { - moved = true; - } else if (viewport == null) { - moved = true; - } else if (zoomNow != zoom) { - moved = true; - } else if (mapMoved(viewport, viewportNow) && (cachesCnt <= 0 || CollectionUtils.isEmpty(caches) || !viewport.includes(viewportNow))) { - moved = true; - } + final boolean moved = liveChanged || (isLiveEnabled && !downloaded) || (viewport == null) || zoomNow != zoom || + (mapMoved(viewport, viewportNow) && (cachesCnt <= 0 || CollectionUtils.isEmpty(caches) || !viewport.includes(viewportNow))); // update title on any change - if (moved || zoomNow != zoom || !viewportNow.equals(viewport)) { + if (moved || !viewportNow.equals(viewport)) { displayHandler.sendEmptyMessage(UPDATE_TITLE); } zoom = zoomNow; @@ -1088,7 +1053,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto yield(); } catch (Exception e) { - Log.w("cgeomap.LoadTimer.run: " + e.toString()); + Log.w("cgeomap.LoadTimer.run", e); } } } @@ -1120,59 +1085,60 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto SearchResult searchResult; if (mapMode == MapMode.LIVE) { - if (isLiveEnabled) { - searchResult = new SearchResult(); - } else { - searchResult = new SearchResult(app.getStoredInViewport(viewport, Settings.getCacheType())); - } + searchResult = isLiveEnabled ? new SearchResult() : new SearchResult(cgData.loadStoredInViewport(viewport, Settings.getCacheType())); } else { // map started from another activity - searchResult = new SearchResult(searchIntent); + searchResult = searchIntent != null ? new SearchResult(searchIntent) : new SearchResult(); if (geocodeIntent != null) { searchResult.addGeocode(geocodeIntent); } } // live mode search result if (isLiveEnabled) { - SearchResult liveResult = new SearchResult(app.getCachedInViewport(viewport, Settings.getCacheType())); + SearchResult liveResult = new SearchResult(cgData.loadCachedInViewport(viewport, Settings.getCacheType())); searchResult.addGeocodes(liveResult.getGeocodes()); } downloaded = true; - Set<cgCache> cachesFromSearchResult = searchResult.getCachesFromSearchResult(LoadFlags.LOAD_WAYPOINTS); - // to update the caches they have to be removed first + Set<Geocache> cachesFromSearchResult = searchResult.getCachesFromSearchResult(LoadFlags.LOAD_WAYPOINTS); + // update the caches + // new collection type needs to remove first caches.removeAll(cachesFromSearchResult); caches.addAll(cachesFromSearchResult); + final boolean excludeMine = Settings.isExcludeMyCaches(); + final boolean excludeDisabled = Settings.isExcludeDisabledCaches(); if (mapMode == MapMode.LIVE) { - final boolean excludeMine = Settings.isExcludeMyCaches(); - final boolean excludeDisabled = Settings.isExcludeDisabledCaches(); - - final List<cgCache> tempList = caches.getAsList(); + final List<Geocache> tempList = caches.getAsList(); - for (cgCache cache : tempList) { - if ((cache.isFound() && excludeMine) || (cache.isOwn() && excludeMine) || (cache.isDisabled() && excludeDisabled)) { + for (Geocache cache : tempList) { + if ((cache.isFound() && excludeMine) || (cache.isOwner() && excludeMine) || (cache.isDisabled() && excludeDisabled)) { caches.remove(cache); } } } countVisibleCaches(); if (cachesCnt < Settings.getWayPointsThreshold() || geocodeIntent != null) { + // we don't want to see any stale waypoints waypoints.clear(); - if (isLiveEnabled || mapMode == MapMode.COORDS) { + if (isLiveEnabled || mapMode == MapMode.LIVE + || mapMode == MapMode.COORDS) { //All visible waypoints CacheType type = Settings.getCacheType(); - Set<cgWaypoint> waypointsInViewport = app.getWaypointsInViewport(viewport, Settings.isExcludeMyCaches(), Settings.isExcludeDisabledCaches(), type); + Set<Waypoint> waypointsInViewport = cgData.loadWaypoints(viewport, excludeMine, excludeDisabled, type); waypoints.addAll(waypointsInViewport); } - else - { + else { //All waypoints from the viewed caches - for (cgCache c : caches.getAsList()) { + for (Geocache c : caches.getAsList()) { waypoints.addAll(c.getWaypoints()); } } } + else { + // we don't want to see any stale waypoints when above threshold + waypoints.clear(); + } //render displayExecutor.execute(new DisplayRunnable(viewport)); @@ -1229,8 +1195,9 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } while (count < 2); if (searchResult != null) { - Set<cgCache> result = searchResult.getCachesFromSearchResult(LoadFlags.LOAD_CACHE_OR_DB); - // to update the caches they have to be removed first + Set<Geocache> result = searchResult.getCachesFromSearchResult(LoadFlags.LOAD_CACHE_OR_DB); + // update the caches + // new collection type needs to remove first caches.removeAll(result); caches.addAll(result); lastSearchResult = searchResult; @@ -1266,29 +1233,29 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } // display caches - final List<cgCache> cachesToDisplay = caches.getAsList(); - final List<cgWaypoint> waypointsToDisplay = new ArrayList<cgWaypoint>(waypoints); + final List<Geocache> cachesToDisplay = caches.getAsList(); + final List<Waypoint> waypointsToDisplay = new ArrayList<Waypoint>(waypoints); final List<CachesOverlayItemImpl> itemsToDisplay = new ArrayList<CachesOverlayItemImpl>(); if (!cachesToDisplay.isEmpty()) { // Only show waypoints for single view or setting // when less than showWaypointsthreshold Caches shown if (mapMode == MapMode.SINGLE || (cachesCnt < Settings.getWayPointsThreshold())) { - for (cgWaypoint waypoint : waypointsToDisplay) { + for (Waypoint waypoint : waypointsToDisplay) { if (waypoint == null || waypoint.getCoords() == null) { continue; } - itemsToDisplay.add(getItem(waypoint, null, waypoint)); + itemsToDisplay.add(getWaypointItem(waypoint)); } } - for (cgCache cache : cachesToDisplay) { + for (Geocache cache : cachesToDisplay) { if (cache == null || cache.getCoords() == null) { continue; } - itemsToDisplay.add(getItem(cache, cache, null)); + itemsToDisplay.add(getCacheItem(cache)); } overlayCaches.updateItems(itemsToDisplay); @@ -1309,7 +1276,6 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } } - /** * Thread to display one point. Started on opening if in single mode. */ @@ -1322,10 +1288,10 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } if (coordsIntent != null) { - final cgWaypoint waypoint = new cgWaypoint("some place", waypointTypeIntent != null ? waypointTypeIntent : WaypointType.WAYPOINT, false); + final Waypoint waypoint = new Waypoint("some place", waypointTypeIntent != null ? waypointTypeIntent : WaypointType.WAYPOINT, false); waypoint.setCoords(coordsIntent); - final CachesOverlayItemImpl item = getItem(waypoint, null, waypoint); + final CachesOverlayItemImpl item = getWaypointItem(waypoint); overlayCaches.updateItems(item); displayHandler.sendEmptyMessage(INVALIDATE_MAP); @@ -1342,7 +1308,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto final protected Viewport viewport; - public DoRunnable(final Viewport viewport) { + protected DoRunnable(final Viewport viewport) { this.viewport = viewport; } @@ -1364,6 +1330,51 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } /** + * store caches, invoked by "store offline" menu item + * + * @param listId + * the list to store the caches in + */ + private void storeCaches(List<String> geocodes, int listId) { + final LoadDetailsHandler loadDetailsHandler = new LoadDetailsHandler(); + + waitDialog = new ProgressDialog(activity); + waitDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + waitDialog.setCancelable(true); + waitDialog.setCancelMessage(loadDetailsHandler.cancelMessage()); + waitDialog.setMax(detailTotal); + waitDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { + + @Override + public void onCancel(DialogInterface arg0) { + try { + if (loadDetailsThread != null) { + loadDetailsThread.stopIt(); + } + + geoDirUpdate.startDir(); + } catch (Exception e) { + Log.e("cgeocaches.onPrepareOptionsMenu.onCancel", e); + } + } + }); + + float etaTime = detailTotal * 7.0f / 60.0f; + int roundedEta = Math.round(etaTime); + if (etaTime < 0.4) { + waitDialog.setMessage(res.getString(R.string.caches_downloading) + " " + res.getString(R.string.caches_eta_ltm)); + } else { + waitDialog.setMessage(res.getString(R.string.caches_downloading) + " " + roundedEta + " " + res.getQuantityString(R.plurals.caches_eta_mins, roundedEta)); + } + waitDialog.show(); + + detailProgressTime = System.currentTimeMillis(); + + loadDetailsThread = new LoadDetails(loadDetailsHandler, geocodes, listId); + loadDetailsThread.start(); + } + + /** * Thread to store the caches in the viewport. Started by Activity. */ @@ -1371,11 +1382,13 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto final private CancellableHandler handler; final private List<String> geocodes; + final private int listId; private long last = 0L; - public LoadDetails(final CancellableHandler handler, final List<String> geocodes) { + public LoadDetails(final CancellableHandler handler, final List<String> geocodes, final int listId) { this.handler = handler; this.geocodes = geocodes; + this.listId = listId; } public void stopIt() { @@ -1396,7 +1409,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto break; } - if (!app.isOffline(geocode, null)) { + if (!cgData.isOffline(geocode, null)) { if ((System.currentTimeMillis() - last) < 1500) { try { int delay = 1000 + (int) (Math.random() * 1000.0) - (int) (System.currentTimeMillis() - last); @@ -1416,10 +1429,10 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto break; } - cgCache.storeCache(null, geocode, StoredList.STANDARD_LIST_ID, false, handler); + Geocache.storeCache(null, geocode, listId, false, handler); } } catch (Exception e) { - Log.e("cgeocaches.LoadDetails.run: " + e.toString()); + Log.e("cgeocaches.LoadDetails.run", e); } finally { // one more cache over detailProgress++; @@ -1486,9 +1499,9 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto Viewport viewport = null; if (geocodeCenter != null) { - viewport = app.getBounds(geocodeCenter); + viewport = cgData.getBounds(geocodeCenter); } else if (searchCenter != null) { - viewport = app.getBounds(searchCenter.getGeocodes()); + viewport = cgData.getBounds(searchCenter.getGeocodes()); } if (viewport == null) { @@ -1621,116 +1634,101 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto dirtyCaches.add(geocode); } - /** - * Returns a OverlayItem represented by an icon - * - * @param coord - * The coords - * @param cache - * Cache - * @param waypoint - * Waypoint. Mutally exclusive with cache - * @return - */ - private CachesOverlayItemImpl getItem(final IWaypoint coord, final cgCache cache, final cgWaypoint waypoint) { - if (cache != null) { - final CachesOverlayItemImpl item = mapItemFactory.getCachesOverlayItem(coord, cache.getType()); - - final int hashcode = new HashCodeBuilder() - .append(cache.isReliableLatLon()) - .append(cache.getType().id) - .append(cache.isDisabled() || cache.isArchived()) - .append(cache.isOwn()) - .append(cache.isFound()) - .append(cache.hasUserModifiedCoords()) - .append(cache.getPersonalNote()) - .append(cache.isLogOffline()) - .append(cache.getListId() > 0) - .toHashCode(); - - final LayerDrawable ldFromCache = overlaysCache.get(hashcode); - if (ldFromCache != null) { - item.setMarker(ldFromCache); - return item; - } - - // 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() ? R.drawable.marker_disabled : R.drawable.marker); - layers.add(marker); - final int resolution = marker.getIntrinsicWidth() > 40 ? 1 : 0; - // reliable or not - if (!cache.isReliableLatLon()) { - insets.add(INSET_RELIABLE[resolution]); - layers.add(getResources().getDrawable(R.drawable.marker_notreliable)); - } - // cache type - layers.add(getResources().getDrawable(cache.getType().markerId)); - insets.add(INSET_TYPE[resolution]); - // own - if (cache.isOwn()) { - layers.add(getResources().getDrawable(R.drawable.marker_own)); - insets.add(INSET_OWN[resolution]); - // if not, checked if stored - } else if (cache.getListId() > 0) { - layers.add(getResources().getDrawable(R.drawable.marker_stored)); - insets.add(INSET_OWN[resolution]); - } - // found - if (cache.isFound()) { - layers.add(getResources().getDrawable(R.drawable.marker_found)); - insets.add(INSET_FOUND[resolution]); - // if not, perhaps logged offline - } else if (cache.isLogOffline()) { - layers.add(getResources().getDrawable(R.drawable.marker_found_offline)); - insets.add(INSET_FOUND[resolution]); - } - // user modified coords - if (cache.hasUserModifiedCoords()) { - layers.add(getResources().getDrawable(R.drawable.marker_usermodifiedcoords)); - insets.add(INSET_USERMODIFIEDCOORDS[resolution]); - } - // personal note - if (cache.getPersonalNote() != null) { - layers.add(getResources().getDrawable(R.drawable.marker_personalnote)); - insets.add(INSET_PERSONALNOTE[resolution]); - } - - final LayerDrawable ld = new LayerDrawable(layers.toArray(new Drawable[layers.size()])); - - int index = 1; - for (final int[] inset : insets) { - ld.setLayerInset(index++, inset[0], inset[1], inset[2], inset[3]); - } - - overlaysCache.put(hashcode, ld); - - item.setMarker(ld); + private CachesOverlayItemImpl getCacheItem(final Geocache cache) { + final CachesOverlayItemImpl item = mapItemFactory.getCachesOverlayItem(cache, cache.getType()); + + final int hashcode = new HashCodeBuilder() + .append(cache.isReliableLatLon()) + .append(cache.getType().id) + .append(cache.isDisabled() || cache.isArchived()) + .append(cache.getCacheRealm().id) + .append(cache.isOwner()) + .append(cache.isFound()) + .append(cache.hasUserModifiedCoords()) + .append(cache.getPersonalNote()) + .append(cache.isLogOffline()) + .append(cache.getListId() > 0) + .toHashCode(); + + final LayerDrawable ldFromCache = overlaysCache.get(hashcode); + if (ldFromCache != null) { + item.setMarker(ldFromCache); return item; } - if (waypoint != null) { - - final CachesOverlayItemImpl item = mapItemFactory.getCachesOverlayItem(coord, null); - Drawable[] layers = new Drawable[2]; - layers[0] = getResources().getDrawable(R.drawable.marker); - layers[1] = getResources().getDrawable(waypoint.getWaypointType().markerId); + // 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); + layers.add(marker); + final int resolution = marker.getIntrinsicWidth() > 40 ? 1 : 0; + // reliable or not + if (!cache.isReliableLatLon()) { + insets.add(INSET_RELIABLE[resolution]); + layers.add(getResources().getDrawable(R.drawable.marker_notreliable)); + } + // cache type + layers.add(getResources().getDrawable(cache.getType().markerId)); + insets.add(INSET_TYPE[resolution]); + // own + if (cache.isOwner()) { + layers.add(getResources().getDrawable(R.drawable.marker_own)); + insets.add(INSET_OWN[resolution]); + // if not, checked if stored + } else if (cache.getListId() > 0) { + layers.add(getResources().getDrawable(R.drawable.marker_stored)); + insets.add(INSET_OWN[resolution]); + } + // found + if (cache.isFound()) { + layers.add(getResources().getDrawable(R.drawable.marker_found)); + insets.add(INSET_FOUND[resolution]); + // if not, perhaps logged offline + } else if (cache.isLogOffline()) { + layers.add(getResources().getDrawable(R.drawable.marker_found_offline)); + insets.add(INSET_FOUND[resolution]); + } + // user modified coords + if (cache.hasUserModifiedCoords()) { + layers.add(getResources().getDrawable(R.drawable.marker_usermodifiedcoords)); + insets.add(INSET_USERMODIFIEDCOORDS[resolution]); + } + // personal note + if (cache.getPersonalNote() != null) { + layers.add(getResources().getDrawable(R.drawable.marker_personalnote)); + insets.add(INSET_PERSONALNOTE[resolution]); + } + + final LayerDrawable ld = new LayerDrawable(layers.toArray(new Drawable[layers.size()])); + + int index = 1; + for (final int[] inset : insets) { + ld.setLayerInset(index++, inset[0], inset[1], inset[2], inset[3]); + } + + overlaysCache.put(hashcode, ld); + + item.setMarker(ld); + return item; + } - LayerDrawable ld = new LayerDrawable(layers); - if (layers[0].getIntrinsicWidth() > 40) { - ld.setLayerInset(1, 9, 12, 10, 13); - } else { - ld.setLayerInset(1, 9, 12, 8, 12); - } - item.setMarker(ld); - return item; + private CachesOverlayItemImpl getWaypointItem(final Waypoint waypoint) { + final CachesOverlayItemImpl item = mapItemFactory.getCachesOverlayItem(waypoint, null); + Drawable marker = getResources().getDrawable(!waypoint.isVisited() ? R.drawable.marker : R.drawable.marker_transparent); + final Drawable[] layers = new Drawable[] { + marker, + getResources().getDrawable(waypoint.getWaypointType().markerId) + }; + final LayerDrawable ld = new LayerDrawable(layers); + if (layers[0].getIntrinsicWidth() > 40) { + ld.setLayerInset(1, 9, 12, 10, 13); + } else { + ld.setLayerInset(1, 9, 12, 8, 12); } - - return null; - + item.setMarker(ld); + return item; } } diff --git a/main/src/cgeo/geocaching/maps/CachesOverlay.java b/main/src/cgeo/geocaching/maps/CachesOverlay.java index b656900..9bb4cef 100644 --- a/main/src/cgeo/geocaching/maps/CachesOverlay.java +++ b/main/src/cgeo/geocaching/maps/CachesOverlay.java @@ -5,8 +5,8 @@ import cgeo.geocaching.IWaypoint; import cgeo.geocaching.R; import cgeo.geocaching.Settings; import cgeo.geocaching.WaypointPopup; -import cgeo.geocaching.cgCache; -import cgeo.geocaching.cgeoapplication; +import cgeo.geocaching.Geocache; +import cgeo.geocaching.cgData; import cgeo.geocaching.activity.Progress; import cgeo.geocaching.connector.gc.GCMap; import cgeo.geocaching.enumerations.CacheType; @@ -209,10 +209,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); @@ -228,7 +227,7 @@ public class CachesOverlay extends AbstractItemizedOverlay { final IWaypoint coordinate = item.getCoord(); if (StringUtils.isNotBlank(coordinate.getCoordType()) && coordinate.getCoordType().equalsIgnoreCase("cache") && StringUtils.isNotBlank(coordinate.getGeocode())) { - cgCache cache = cgeoapplication.getInstance().loadCache(coordinate.getGeocode(), LoadFlags.LOAD_CACHE_OR_DB); + Geocache cache = cgData.loadCache(coordinate.getGeocode(), LoadFlags.LOAD_CACHE_OR_DB); RequestDetailsThread requestDetailsThread = new RequestDetailsThread(cache); if (!requestDetailsThread.requestRequired()) { // don't show popup if we have enough details @@ -238,7 +237,7 @@ public class CachesOverlay extends AbstractItemizedOverlay { return true; } - if (coordinate.getCoordType() != null && coordinate.getCoordType().equalsIgnoreCase("waypoint") && coordinate.getId() > 0) { + if (coordinate.getCoordType() != null && coordinate.getCoordType().equalsIgnoreCase("waypoint") && coordinate.getId() >= 0) { CGeoMap.markCacheAsDirty(coordinate.getGeocode()); WaypointPopup.startActivity(context, coordinate.getId(), coordinate.getGeocode()); } else { @@ -248,7 +247,7 @@ public class CachesOverlay extends AbstractItemizedOverlay { progress.dismiss(); } catch (Exception e) { - Log.e("cgMapOverlay.onTap: " + e.toString()); + Log.e("cgMapOverlay.onTap", e); if (progress != null) { progress.dismiss(); } @@ -262,7 +261,7 @@ public class CachesOverlay extends AbstractItemizedOverlay { try { return items.get(index); } catch (Exception e) { - Log.e("cgMapOverlay.createItem: " + e.toString()); + Log.e("cgMapOverlay.createItem", e); } return null; @@ -273,7 +272,7 @@ public class CachesOverlay extends AbstractItemizedOverlay { try { return items.size(); } catch (Exception e) { - Log.e("cgMapOverlay.size: " + e.toString()); + Log.e("cgMapOverlay.size", e); } return 0; @@ -281,9 +280,9 @@ public class CachesOverlay extends AbstractItemizedOverlay { private class RequestDetailsThread extends Thread { - private final cgCache cache; + private final Geocache cache; - public RequestDetailsThread(cgCache cache) { + public RequestDetailsThread(Geocache cache) { this.cache = cache; } diff --git a/main/src/cgeo/geocaching/maps/MapProviderFactory.java b/main/src/cgeo/geocaching/maps/MapProviderFactory.java index cb1f87f..483189f 100644 --- a/main/src/cgeo/geocaching/maps/MapProviderFactory.java +++ b/main/src/cgeo/geocaching/maps/MapProviderFactory.java @@ -23,7 +23,7 @@ public class MapProviderFactory { MapsforgeMapProvider.getInstance(); } - private static boolean isGoogleMapsInstalled() { + public static boolean isGoogleMapsInstalled() { boolean googleMaps = true; try { Class.forName("com.google.android.maps.MapActivity"); diff --git a/main/src/cgeo/geocaching/maps/PositionOverlay.java b/main/src/cgeo/geocaching/maps/PositionOverlay.java index 1aa2d3b..fec67ef 100644 --- a/main/src/cgeo/geocaching/maps/PositionOverlay.java +++ b/main/src/cgeo/geocaching/maps/PositionOverlay.java @@ -158,7 +158,6 @@ public class PositionOverlay implements GeneralOverlay { if (Settings.isMapTrail()) { int size = history.size(); if (size > 1) { - int alpha; int alphaCnt = size - 201; if (alphaCnt < 1) { alphaCnt = 1; @@ -172,6 +171,7 @@ public class PositionOverlay implements GeneralOverlay { projection.toPixels(mapItemFactory.getGeoPointBase(new Geopoint(prev)), historyPointP); projection.toPixels(mapItemFactory.getGeoPointBase(new Geopoint(now)), historyPointN); + int alpha; if ((alphaCnt - cnt) > 0) { alpha = 255 / (alphaCnt - cnt); } @@ -211,11 +211,8 @@ public class PositionOverlay implements GeneralOverlay { heightArrowHalf = arrow.getHeight() / 2; } - int marginLeft; - int marginTop; - - marginLeft = center.x - widthArrowHalf; - marginTop = center.y - heightArrowHalf; + int marginLeft = center.x - widthArrowHalf; + int marginTop = center.y - heightArrowHalf; Matrix matrix = new Matrix(); matrix.setRotate(heading, widthArrowHalf, heightArrowHalf); diff --git a/main/src/cgeo/geocaching/maps/google/GoogleMapActivity.java b/main/src/cgeo/geocaching/maps/google/GoogleMapActivity.java index 0377fe9..5649d19 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.activity.FilteredActivity; import cgeo.geocaching.maps.AbstractMap; import cgeo.geocaching.maps.CGeoMap; import cgeo.geocaching.maps.interfaces.MapActivityImpl; @@ -12,7 +13,7 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; -public class GoogleMapActivity extends MapActivity implements MapActivityImpl { +public class GoogleMapActivity extends MapActivity implements MapActivityImpl, FilteredActivity { private AbstractMap mapBase; @@ -127,4 +128,8 @@ public class GoogleMapActivity extends MapActivity implements MapActivityImpl { 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/GoogleMapProvider.java b/main/src/cgeo/geocaching/maps/google/GoogleMapProvider.java index 6973338..1fa38ad 100644 --- a/main/src/cgeo/geocaching/maps/google/GoogleMapProvider.java +++ b/main/src/cgeo/geocaching/maps/google/GoogleMapProvider.java @@ -16,7 +16,6 @@ public final class GoogleMapProvider extends AbstractMapProvider { public static final String GOOGLE_MAP_ID = "GOOGLE_MAP"; public static final String GOOGLE_SATELLITE_ID = "GOOGLE_SATELLITE"; - private static GoogleMapProvider instance; private final MapItemFactory mapItemFactory; @@ -29,15 +28,16 @@ public final class GoogleMapProvider extends AbstractMapProvider { mapItemFactory = new GoogleMapItemFactory(); } + private static class Holder { + private static final GoogleMapProvider INSTANCE = new GoogleMapProvider(); + } + public static GoogleMapProvider getInstance() { - if (instance == null) { - instance = new GoogleMapProvider(); - } - return instance; + return Holder.INSTANCE; } public static boolean isSatelliteSource(final MapSource mapSource) { - return mapSource != null && mapSource instanceof GoogleSatelliteSource; + return mapSource instanceof GoogleSatelliteSource; } @Override @@ -67,7 +67,7 @@ public final class GoogleMapProvider extends AbstractMapProvider { private static abstract class AbstractGoogleMapSource extends AbstractMapSource { - public AbstractGoogleMapSource(final String id, final MapProvider mapProvider, final String name) { + protected AbstractGoogleMapSource(final String id, final MapProvider mapProvider, final String name) { super(id, mapProvider, name); } diff --git a/main/src/cgeo/geocaching/maps/google/GoogleMapView.java b/main/src/cgeo/geocaching/maps/google/GoogleMapView.java index 9c134a4..154c3f3 100644 --- a/main/src/cgeo/geocaching/maps/google/GoogleMapView.java +++ b/main/src/cgeo/geocaching/maps/google/GoogleMapView.java @@ -61,7 +61,7 @@ public class GoogleMapView extends MapView implements MapViewImpl { super.draw(canvas); } catch (Exception e) { - Log.e("GoogleMapView.draw: " + e.toString()); + Log.e("GoogleMapView.draw", e); } } @@ -75,7 +75,7 @@ public class GoogleMapView extends MapView implements MapViewImpl { super.displayZoomControls(takeFocus); } catch (Exception e) { - Log.e("GoogleMapView.displayZoomControls: " + e.toString()); + Log.e("GoogleMapView.displayZoomControls", e); } } diff --git a/main/src/cgeo/geocaching/maps/google/GoogleOverlay.java b/main/src/cgeo/geocaching/maps/google/GoogleOverlay.java index d86eafe..773f9ff 100644 --- a/main/src/cgeo/geocaching/maps/google/GoogleOverlay.java +++ b/main/src/cgeo/geocaching/maps/google/GoogleOverlay.java @@ -27,6 +27,9 @@ public class GoogleOverlay extends Overlay implements OverlayImpl { break; case ScaleOverlay: overlayBase = new ScaleOverlay(activityIn, this); + break; + default: + throw new IllegalArgumentException(); } } diff --git a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapActivity.java b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapActivity.java index 6cb2539..f850402 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.activity.FilteredActivity; import cgeo.geocaching.maps.AbstractMap; import cgeo.geocaching.maps.CGeoMap; import cgeo.geocaching.maps.interfaces.MapActivityImpl; @@ -12,7 +13,7 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; -public class MapsforgeMapActivity extends MapActivity implements MapActivityImpl { +public class MapsforgeMapActivity extends MapActivity implements MapActivityImpl, FilteredActivity { private AbstractMap mapBase; @@ -121,4 +122,9 @@ public class MapsforgeMapActivity extends MapActivity implements MapActivityImpl 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/MapsforgeMapProvider.java b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapProvider.java index ad64807..dc0dbf8 100644 --- a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapProvider.java +++ b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapProvider.java @@ -31,7 +31,6 @@ public final class MapsforgeMapProvider extends AbstractMapProvider { public static final String MAPSFORGE_MAPNIK_ID = "MAPSFORGE_MAPNIK"; private boolean oldMap = false; private MapItemFactory mapItemFactory = new MapsforgeMapItemFactory(); - private static MapsforgeMapProvider instance; private MapsforgeMapProvider() { final Resources resources = cgeoapplication.getInstance().getResources(); @@ -42,11 +41,12 @@ public final class MapsforgeMapProvider extends AbstractMapProvider { updateOfflineMaps(); } + private static final class Holder { + private static final MapsforgeMapProvider INSTANCE = new MapsforgeMapProvider(); + } + public static MapsforgeMapProvider getInstance() { - if (instance == null) { - instance = new MapsforgeMapProvider(); - } - return instance; + return Holder.INSTANCE; } public static List<String> getOfflineMaps() { @@ -65,6 +65,7 @@ public final class MapsforgeMapProvider extends AbstractMapProvider { } } } + Collections.sort(mapFileList, String.CASE_INSENSITIVE_ORDER); return mapFileList; } catch (Exception e) { Log.e("Settings.getOfflineMaps: " + e); diff --git a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapView.java b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapView.java index 1e4a756..083e5bb 100644 --- a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapView.java +++ b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapView.java @@ -59,7 +59,7 @@ public class MapsforgeMapView extends MapView implements MapViewImpl { super.draw(canvas); } catch (Exception e) { - Log.e("MapsforgeMapView.draw: " + e.toString()); + Log.e("MapsforgeMapView.draw", e); } } @@ -264,7 +264,7 @@ public class MapsforgeMapView extends MapView implements MapViewImpl { } } catch (Exception e) { - Log.e("MapsforgeMapView.repaintRequired: " + e.toString()); + Log.e("MapsforgeMapView.repaintRequired", e); } } } diff --git a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeOverlay.java b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeOverlay.java index b6e31a2..dd7fb75 100644 --- a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeOverlay.java +++ b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeOverlay.java @@ -29,6 +29,9 @@ public class MapsforgeOverlay extends Overlay implements OverlayImpl { break; case ScaleOverlay: overlayBase = new ScaleOverlay(activityIn, this); + break; + default: + throw new IllegalArgumentException(); } } diff --git a/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapActivity024.java b/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapActivity024.java index d45db9a..ed8a7bc 100644 --- a/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapActivity024.java +++ b/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapActivity024.java @@ -1,5 +1,6 @@ package cgeo.geocaching.maps.mapsforge.v024; +import cgeo.geocaching.activity.FilteredActivity; import cgeo.geocaching.maps.AbstractMap; import cgeo.geocaching.maps.CGeoMap; import cgeo.geocaching.maps.interfaces.MapActivityImpl; @@ -12,7 +13,7 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; -public class MapsforgeMapActivity024 extends MapActivity implements MapActivityImpl { +public class MapsforgeMapActivity024 extends MapActivity implements MapActivityImpl, FilteredActivity { private AbstractMap mapBase; @@ -121,4 +122,9 @@ public class MapsforgeMapActivity024 extends MapActivity implements MapActivityI 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/MapsforgeMapView024.java b/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapView024.java index d177a47..85d61fe 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.toString()); + Log.e("MapsforgeMapView.draw", e); } } @@ -208,7 +208,7 @@ public class MapsforgeMapView024 extends MapView implements MapViewImpl { } } catch (Exception e) { - Log.e("MapsforgeMapView.repaintRequired: " + e.toString()); + Log.e("MapsforgeMapView.repaintRequired", e); } } } diff --git a/main/src/cgeo/geocaching/network/HtmlImage.java b/main/src/cgeo/geocaching/network/HtmlImage.java index d47afb9..a409750 100644 --- a/main/src/cgeo/geocaching/network/HtmlImage.java +++ b/main/src/cgeo/geocaching/network/HtmlImage.java @@ -3,6 +3,7 @@ package cgeo.geocaching.network; import cgeo.geocaching.R; import cgeo.geocaching.StoredList; import cgeo.geocaching.cgeoapplication; +import cgeo.geocaching.compatibility.Compatibility; import cgeo.geocaching.connector.ConnectorFactory; import cgeo.geocaching.files.LocalStorage; import cgeo.geocaching.utils.ImageHelper; @@ -12,15 +13,13 @@ import ch.boye.httpclientandroidlib.HttpResponse; import org.apache.commons.lang3.StringUtils; -import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.Point; import android.graphics.drawable.BitmapDrawable; import android.net.Uri; import android.text.Html; -import android.view.Display; -import android.view.WindowManager; import java.io.File; import java.io.FileInputStream; @@ -42,7 +41,8 @@ public class HtmlImage implements Html.ImageGetter { "besucherzaehler-homepage.de", "hitwebcounter.com", "kostenloser-counter.eu", - "trendcounter.com" + "trendcounter.com", + "hit-counter-download.com" }; final private String geocode; @@ -66,9 +66,9 @@ public class HtmlImage implements Html.ImageGetter { bfOptions = new BitmapFactory.Options(); bfOptions.inTempStorage = new byte[16 * 1024]; - final Display display = ((WindowManager) cgeoapplication.getInstance().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); - this.maxWidth = display.getWidth() - 25; - this.maxHeight = display.getHeight() - 25; + Point displaySize = Compatibility.getDisplaySize(); + this.maxWidth = displaySize.x - 25; + this.maxHeight = displaySize.y - 25; this.resources = cgeoapplication.getInstance().getResources(); } @@ -161,7 +161,7 @@ public class HtmlImage implements Html.ImageGetter { final File fileSec = LocalStorage.getStorageSecFile(pseudoGeocode, url, true); return loadCachedImage(fileSec, forceKeep); } catch (Exception e) { - Log.w("HtmlImage.getDrawable (reading cache): " + e.toString()); + Log.w("HtmlImage.getDrawable (reading cache)", e); } return null; } diff --git a/main/src/cgeo/geocaching/network/Network.java b/main/src/cgeo/geocaching/network/Network.java index 0bd845f..a4155be 100644 --- a/main/src/cgeo/geocaching/network/Network.java +++ b/main/src/cgeo/geocaching/network/Network.java @@ -22,6 +22,7 @@ import ch.boye.httpclientandroidlib.client.methods.HttpGet; import ch.boye.httpclientandroidlib.client.methods.HttpPost; import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase; import ch.boye.httpclientandroidlib.client.params.ClientPNames; +import ch.boye.httpclientandroidlib.entity.StringEntity; import ch.boye.httpclientandroidlib.entity.mime.MultipartEntity; import ch.boye.httpclientandroidlib.entity.mime.content.FileBody; import ch.boye.httpclientandroidlib.entity.mime.content.StringBody; @@ -34,6 +35,7 @@ import ch.boye.httpclientandroidlib.params.HttpParams; import ch.boye.httpclientandroidlib.protocol.HttpContext; import ch.boye.httpclientandroidlib.util.EntityUtils; +import org.apache.commons.lang3.CharEncoding; import org.apache.commons.lang3.StringUtils; import org.json.JSONException; import org.json.JSONObject; @@ -43,11 +45,13 @@ import android.net.Uri; import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; import java.net.URLEncoder; public abstract class Network { private static final int NB_DOWNLOAD_RETRIES = 4; + /** User agent id */ private final static String PC_USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64; rv:9.0.1) Gecko/20100101 Firefox/9.0.1"; /** Native user agent, taken from a Android 2.2 Nexus **/ @@ -58,7 +62,7 @@ public abstract class Network { private final static HttpParams clientParams = new BasicHttpParams(); static { - Network.clientParams.setParameter(CoreProtocolPNames.HTTP_CONTENT_CHARSET, "UTF-8"); + Network.clientParams.setParameter(CoreProtocolPNames.HTTP_CONTENT_CHARSET, CharEncoding.UTF_8); Network.clientParams.setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 30000); Network.clientParams.setParameter(CoreConnectionPNames.SO_TIMEOUT, 30000); Network.clientParams.setParameter(ClientPNames.HANDLE_REDIRECTS, true); @@ -115,7 +119,6 @@ public abstract class Network { if (contentEncoding != null) { for (final HeaderElement codec : contentEncoding.getElements()) { if (codec.getName().equalsIgnoreCase("gzip")) { - Log.d("Decompressing response"); response.setEntity(new GzipDecompressingEntity(response.getEntity())); return; } @@ -153,6 +156,27 @@ public abstract class Network { } /** + * POST HTTP request with Json POST DATA + * + * @param uri the URI to request + * @param json the json object to add to the POST request + * @return the HTTP response, or null in case of an encoding error params + */ + public static HttpResponse postJsonRequest(final String uri, final JSONObject json) { + HttpPost request = new HttpPost(uri); + request.addHeader("Content-Type", "application/json; charset=utf-8"); + if (json != null) { + try { + request.setEntity(new StringEntity(json.toString())); + } catch (UnsupportedEncodingException e) { + Log.e("postJsonRequest:JSON Entity: UnsupportedEncodingException"); + return null; + } + } + return doRepeatedRequests(request); + } + + /** * Multipart POST HTTP request * * @param uri the URI to request @@ -206,7 +230,7 @@ public abstract class Network { request = new HttpPost(uri); if (params != null) { try { - ((HttpPost) request).setEntity(new UrlEncodedFormEntity(params, "UTF-8")); + ((HttpPost) request).setEntity(new UrlEncodedFormEntity(params, CharEncoding.UTF_8)); } catch (final UnsupportedEncodingException e) { Log.e("request", e); return null; @@ -235,11 +259,8 @@ public abstract class Network { "X-Requested-With", "XMLHttpRequest")) { request.setHeader(header.getName(), header.getValue()); } - if (Settings.getUseNativeUa()) { - request.getParams().setParameter(CoreProtocolPNames.USER_AGENT, Network.NATIVE_USER_AGENT); - } else { - request.getParams().setParameter(CoreProtocolPNames.USER_AGENT, Network.PC_USER_AGENT); - } + request.getParams().setParameter(CoreProtocolPNames.USER_AGENT, + Settings.getUseNativeUa() ? Network.NATIVE_USER_AGENT : Network.PC_USER_AGENT); } /** @@ -270,9 +291,9 @@ public abstract class Network { final String timeSpan = Network.formatTimeSpan(before); final String tries = (i + 1) + "/" + (Network.NB_DOWNLOAD_RETRIES + 1); if (i == Network.NB_DOWNLOAD_RETRIES) { - Log.e("Failure " + tries + timeSpan + reqLogStr, e); + Log.w("Failure " + tries + timeSpan + reqLogStr + " (" + e.toString() + ")"); } else { - Log.e("Failure " + tries + " (" + e.toString() + ")" + timeSpan + "- retrying " + reqLogStr); + Log.w("Failure " + tries + " (" + e.toString() + ")" + timeSpan + "- retrying " + reqLogStr); } } } @@ -362,15 +383,21 @@ public abstract class Network { return response != null && response.getStatusLine().getStatusCode() == 200; } + /** + * Get the result of a GET HTTP request returning a JSON body. + * + * @param uri the base URI of the GET HTTP request + * @param params the query parameters, or <code>null</code> if there are none + * @return a JSON object if the request was successful and the body could be decoded, <code>null</code> otherwise + */ public static JSONObject requestJSON(final String uri, final Parameters params) { final HttpResponse response = request("GET", uri, params, new Parameters("Accept", "application/json, text/javascript, */*; q=0.01"), null); - if (isSuccess(response)) { + final String responseData = Network.getResponseData(response, false); + if (responseData != null) { try { - return new JSONObject(Network.getResponseData(response)); + return new JSONObject(responseData); } catch (final JSONException e) { - Log.e("Network.requestJSON", e); - } catch (final NullPointerException e) { - Log.e("Network.requestJSON", e); + Log.w("Network.requestJSON", e); } } @@ -379,7 +406,7 @@ public abstract class Network { private static String getResponseDataNoError(final HttpResponse response, boolean replaceWhitespace) { try { - String data = EntityUtils.toString(response.getEntity(), "UTF-8"); + String data = EntityUtils.toString(response.getEntity(), CharEncoding.UTF_8); return replaceWhitespace ? BaseUtils.replaceWhitespace(data) : data; } catch (Exception e) { Log.e("getResponseData", e); @@ -387,10 +414,26 @@ public abstract class Network { } } + /** + * Get the body of a HTTP response. + * + * {@link BaseUtils#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 + */ public static String getResponseData(final HttpResponse response) { return Network.getResponseData(response, true); } + /** + * 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)} + * should be called on the body + * @return the body if the response comes from a successful HTTP request, <code>null</code> otherwise + */ public static String getResponseData(final HttpResponse response, boolean replaceWhitespace) { if (!isSuccess(response)) { return null; @@ -399,7 +442,25 @@ public abstract class Network { } public static String rfc3986URLEncode(String text) { - return StringUtils.replace(URLEncoder.encode(text).replace("+", "%20"), "%7E", "~"); + return StringUtils.replace(Network.encode(text).replace("+", "%20"), "%7E", "~"); + } + + public static String decode(final String text) { + try { + return URLDecoder.decode(text, CharEncoding.UTF_8); + } catch (UnsupportedEncodingException e) { + Log.e("Network.decode", e); + } + return null; + } + + public static String encode(final String text) { + try { + return URLEncoder.encode(text, CharEncoding.UTF_8); + } catch (UnsupportedEncodingException e) { + Log.e("Network.encode", e); + } + return null; } } diff --git a/main/src/cgeo/geocaching/network/Parameters.java b/main/src/cgeo/geocaching/network/Parameters.java index 081a926..f035c4a 100644 --- a/main/src/cgeo/geocaching/network/Parameters.java +++ b/main/src/cgeo/geocaching/network/Parameters.java @@ -4,6 +4,8 @@ import ch.boye.httpclientandroidlib.NameValuePair; import ch.boye.httpclientandroidlib.client.utils.URLEncodedUtils; import ch.boye.httpclientandroidlib.message.BasicNameValuePair; +import org.apache.commons.lang3.CharEncoding; + import java.security.InvalidParameterException; import java.util.ArrayList; import java.util.Collections; @@ -24,7 +26,6 @@ public class Parameters extends ArrayList<NameValuePair> { * if the number of key/values is unbalanced */ public Parameters(final String... keyValues) { - super(); put(keyValues); } @@ -66,7 +67,7 @@ public class Parameters extends ArrayList<NameValuePair> { @Override public String toString() { - return URLEncodedUtils.format(this, "UTF-8"); + return URLEncodedUtils.format(this, CharEncoding.UTF_8); } /** @@ -103,4 +104,8 @@ public class Parameters extends ArrayList<NameValuePair> { return params; } + public void add(final String key, final String value) { + put(key, value); + } + } diff --git a/main/src/cgeo/geocaching/network/StatusUpdater.java b/main/src/cgeo/geocaching/network/StatusUpdater.java index 8c3b9dc..1953e1d 100644 --- a/main/src/cgeo/geocaching/network/StatusUpdater.java +++ b/main/src/cgeo/geocaching/network/StatusUpdater.java @@ -31,7 +31,7 @@ public class StatusUpdater extends MemorySubject<StatusUpdater.Status> implement private void requestUpdate() { final JSONObject response = Network.requestJSON("http://status.cgeo.org/api/status.json", - new Parameters("version_code", "" + Version.getVersionCode(cgeoapplication.getInstance()), + new Parameters("version_code", String.valueOf(Version.getVersionCode(cgeoapplication.getInstance())), "version_name", Version.getVersionName(cgeoapplication.getInstance()), "locale", Locale.getDefault().toString())); if (response != null) { diff --git a/main/src/cgeo/geocaching/sorting/AbstractCacheComparator.java b/main/src/cgeo/geocaching/sorting/AbstractCacheComparator.java index 2015375..2dee713 100644 --- a/main/src/cgeo/geocaching/sorting/AbstractCacheComparator.java +++ b/main/src/cgeo/geocaching/sorting/AbstractCacheComparator.java @@ -1,6 +1,6 @@ package cgeo.geocaching.sorting; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; import cgeo.geocaching.utils.Log; @@ -11,7 +11,7 @@ import cgeo.geocaching.utils.Log; public abstract class AbstractCacheComparator implements CacheComparator { @Override - public final int compare(final cgCache cache1, final cgCache cache2) { + public final int compare(final Geocache cache1, final Geocache cache2) { try { // first check that we have all necessary data for the comparison if (!canCompare(cache1, cache2)) { @@ -19,7 +19,7 @@ public abstract class AbstractCacheComparator implements CacheComparator { } return compareCaches(cache1, cache2); } catch (Exception e) { - Log.e("AbstractCacheComparator.compare: " + e.toString()); + Log.e("AbstractCacheComparator.compare", e); } return 0; } @@ -31,7 +31,7 @@ public abstract class AbstractCacheComparator implements CacheComparator { * @param cache2 * @return */ - protected abstract boolean canCompare(final cgCache cache1, final cgCache cache2); + protected abstract boolean canCompare(final Geocache cache1, final Geocache cache2); /** * Compares two caches. Logging and exception handling is implemented outside this method already. @@ -44,5 +44,5 @@ public abstract class AbstractCacheComparator implements CacheComparator { * @return an integer < 0 if cache1 is less than cache2, 0 if they are equal, and > 0 if cache1 is greater than * cache2. */ - protected abstract int compareCaches(final cgCache cache1, final cgCache cache2); + protected abstract int compareCaches(final Geocache cache1, final Geocache cache2); } diff --git a/main/src/cgeo/geocaching/sorting/CacheComparator.java b/main/src/cgeo/geocaching/sorting/CacheComparator.java index c9b5150..7932729 100644 --- a/main/src/cgeo/geocaching/sorting/CacheComparator.java +++ b/main/src/cgeo/geocaching/sorting/CacheComparator.java @@ -2,8 +2,8 @@ package cgeo.geocaching.sorting; import java.util.Comparator; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; -public interface CacheComparator extends Comparator<cgCache> { +public interface CacheComparator extends Comparator<Geocache> { } diff --git a/main/src/cgeo/geocaching/sorting/ComparatorUserInterface.java b/main/src/cgeo/geocaching/sorting/ComparatorUserInterface.java index bc63ef6..4d1a994 100644 --- a/main/src/cgeo/geocaching/sorting/ComparatorUserInterface.java +++ b/main/src/cgeo/geocaching/sorting/ComparatorUserInterface.java @@ -43,7 +43,7 @@ public class ComparatorUserInterface { register(R.string.caches_sort_date_hidden, DateComparator.class); register(R.string.caches_sort_difficulty, DifficultyComparator.class); register(R.string.caches_sort_finds, FindsComparator.class); - register(R.string.caches_sort_gccode, GeocodeComparator.class); + register(R.string.caches_sort_geocode, GeocodeComparator.class); register(R.string.caches_sort_inventory, InventoryComparator.class); register(R.string.caches_sort_name, NameComparator.class); register(R.string.caches_sort_favorites, PopularityComparator.class); diff --git a/main/src/cgeo/geocaching/sorting/DateComparator.java b/main/src/cgeo/geocaching/sorting/DateComparator.java index 3136d47..3464103 100644 --- a/main/src/cgeo/geocaching/sorting/DateComparator.java +++ b/main/src/cgeo/geocaching/sorting/DateComparator.java @@ -1,6 +1,6 @@ package cgeo.geocaching.sorting; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; import cgeo.geocaching.cgeoapplication; import java.util.ArrayList; @@ -12,19 +12,19 @@ import java.util.Date; public class DateComparator extends AbstractCacheComparator { @Override - protected boolean canCompare(cgCache cache1, cgCache cache2) { + protected boolean canCompare(Geocache cache1, Geocache cache2) { return true; } @Override - protected int compareCaches(cgCache cache1, cgCache cache2) { + protected int compareCaches(Geocache cache1, Geocache cache2) { final Date date1 = cache1.getHiddenDate(); final Date date2 = cache2.getHiddenDate(); if (date1 != null && date2 != null) { final int dateDifference = date1.compareTo(date2); // for equal dates, sort by distance if (dateDifference == 0) { - final ArrayList<cgCache> list = new ArrayList<cgCache>(); + final ArrayList<Geocache> list = new ArrayList<Geocache>(); list.add(cache1); list.add(cache2); final DistanceComparator distanceComparator = new DistanceComparator(cgeoapplication.getInstance().currentGeo().getCoords(), list); diff --git a/main/src/cgeo/geocaching/sorting/DifficultyComparator.java b/main/src/cgeo/geocaching/sorting/DifficultyComparator.java index 123bab9..73d12fa 100644 --- a/main/src/cgeo/geocaching/sorting/DifficultyComparator.java +++ b/main/src/cgeo/geocaching/sorting/DifficultyComparator.java @@ -1,6 +1,6 @@ package cgeo.geocaching.sorting; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; /** * sorts caches by difficulty @@ -9,12 +9,12 @@ import cgeo.geocaching.cgCache; public class DifficultyComparator extends AbstractCacheComparator { @Override - protected boolean canCompare(cgCache cache1, cgCache cache2) { + protected boolean canCompare(Geocache cache1, Geocache cache2) { return cache1.getDifficulty() != 0.0 && cache2.getDifficulty() != 0.0; } @Override - protected int compareCaches(final cgCache cache1, final cgCache cache2) { + protected int compareCaches(final Geocache cache1, final Geocache cache2) { return Float.compare(cache1.getDifficulty(), cache2.getDifficulty()); } }
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/sorting/DistanceComparator.java b/main/src/cgeo/geocaching/sorting/DistanceComparator.java index d601433..7b0afbb 100644 --- a/main/src/cgeo/geocaching/sorting/DistanceComparator.java +++ b/main/src/cgeo/geocaching/sorting/DistanceComparator.java @@ -1,6 +1,6 @@ package cgeo.geocaching.sorting; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; import cgeo.geocaching.geopoint.Geopoint; import java.util.List; @@ -11,9 +11,23 @@ import java.util.List; */ public class DistanceComparator extends AbstractCacheComparator { - public DistanceComparator(final Geopoint coords, List<cgCache> list) { - // calculate all distances to avoid duplicate calculations during sorting - for (cgCache cache : list) { + final private Geopoint coords; + final private List<Geocache> list; + private boolean cachedDistances; + + public DistanceComparator(final Geopoint coords, List<Geocache> list) { + this.coords = coords; + this.list = list; + } + + /** + * calculate all distances only once to avoid costly re-calculation of the same distance during sorting + */ + private void calculateAllDistances() { + if (cachedDistances) { + return; + } + for (Geocache cache : list) { if (cache.getCoords() != null) { cache.setDistance(coords.distanceTo(cache.getCoords())); } @@ -21,15 +35,17 @@ public class DistanceComparator extends AbstractCacheComparator { cache.setDistance(null); } } + cachedDistances = true; } @Override - protected boolean canCompare(cgCache cache1, cgCache cache2) { + protected boolean canCompare(Geocache cache1, Geocache cache2) { return true; } @Override - protected int compareCaches(final cgCache cache1, final cgCache cache2) { + protected int compareCaches(final Geocache cache1, final Geocache cache2) { + calculateAllDistances(); if (cache1.getCoords() == null && cache2.getCoords() == null) { return 0; } diff --git a/main/src/cgeo/geocaching/sorting/FindsComparator.java b/main/src/cgeo/geocaching/sorting/FindsComparator.java index 6407b11..ba929b8 100644 --- a/main/src/cgeo/geocaching/sorting/FindsComparator.java +++ b/main/src/cgeo/geocaching/sorting/FindsComparator.java @@ -1,28 +1,26 @@ package cgeo.geocaching.sorting; -import cgeo.geocaching.cgCache; -import cgeo.geocaching.cgeoapplication; +import cgeo.geocaching.Geocache; +import cgeo.geocaching.cgData; import cgeo.geocaching.enumerations.LogType; public class FindsComparator extends AbstractCacheComparator { - private final cgeoapplication app = cgeoapplication.getInstance(); - @Override - protected boolean canCompare(cgCache cache1, cgCache cache2) { + protected boolean canCompare(Geocache cache1, Geocache cache2) { return cache1.getLogCounts() != null && cache2.getLogCounts() != null; } @Override - protected int compareCaches(cgCache cache1, cgCache cache2) { + protected int compareCaches(Geocache cache1, Geocache cache2) { int finds1 = getFindsCount(cache1); int finds2 = getFindsCount(cache2); return finds2 - finds1; } - private int getFindsCount(cgCache cache) { + private static int getFindsCount(Geocache cache) { if (cache.getLogCounts().isEmpty()) { - cache.setLogCounts(app.loadLogCounts(cache.getGeocode())); + cache.setLogCounts(cgData.loadLogCounts(cache.getGeocode())); } Integer logged = cache.getLogCounts().get(LogType.FOUND_IT); if (logged != null) { diff --git a/main/src/cgeo/geocaching/sorting/GeocodeComparator.java b/main/src/cgeo/geocaching/sorting/GeocodeComparator.java index fb93c0e..fff26c6 100644 --- a/main/src/cgeo/geocaching/sorting/GeocodeComparator.java +++ b/main/src/cgeo/geocaching/sorting/GeocodeComparator.java @@ -1,23 +1,23 @@ package cgeo.geocaching.sorting; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; import org.apache.commons.lang3.StringUtils; /** - * sorts caches by GC code, therefore effectively sorting by cache age - * + * sorts caches by geo code, therefore effectively sorting by cache age + * */ public class GeocodeComparator extends AbstractCacheComparator { @Override - protected boolean canCompare(cgCache cache1, cgCache cache2) { + protected boolean canCompare(Geocache cache1, Geocache cache2) { return StringUtils.isNotBlank(cache1.getGeocode()) && StringUtils.isNotBlank(cache2.getGeocode()); } @Override - protected int compareCaches(final cgCache cache1, final cgCache cache2) { + protected int compareCaches(final Geocache cache1, final Geocache cache2) { final int lengthDiff = cache1.getGeocode().length() - cache2.getGeocode().length(); return lengthDiff != 0 ? lengthDiff : cache1.getGeocode().compareToIgnoreCase(cache2.getGeocode()); } diff --git a/main/src/cgeo/geocaching/sorting/InventoryComparator.java b/main/src/cgeo/geocaching/sorting/InventoryComparator.java index fb1360e..73ea2c5 100644 --- a/main/src/cgeo/geocaching/sorting/InventoryComparator.java +++ b/main/src/cgeo/geocaching/sorting/InventoryComparator.java @@ -1,6 +1,6 @@ package cgeo.geocaching.sorting; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; /** * sorts caches by number of items in inventory @@ -8,12 +8,12 @@ import cgeo.geocaching.cgCache; public class InventoryComparator extends AbstractCacheComparator { @Override - protected boolean canCompare(final cgCache cache1, final cgCache cache2) { + protected boolean canCompare(final Geocache cache1, final Geocache cache2) { return true; } @Override - protected int compareCaches(final cgCache cache1, final cgCache cache2) { + protected int compareCaches(final Geocache cache1, final Geocache cache2) { return cache2.getInventoryItems() - cache1.getInventoryItems(); } } diff --git a/main/src/cgeo/geocaching/sorting/InverseComparator.java b/main/src/cgeo/geocaching/sorting/InverseComparator.java new file mode 100644 index 0000000..1dc76e2 --- /dev/null +++ b/main/src/cgeo/geocaching/sorting/InverseComparator.java @@ -0,0 +1,22 @@ +package cgeo.geocaching.sorting; + +import cgeo.geocaching.Geocache; + +/** + * comparator which inverses the sort order of the given other comparator + * + */ +public class InverseComparator implements CacheComparator { + + private final CacheComparator originalComparator; + + public InverseComparator(CacheComparator comparator) { + this.originalComparator = comparator; + } + + @Override + public int compare(Geocache lhs, Geocache rhs) { + return originalComparator.compare(rhs, lhs); + } + +} diff --git a/main/src/cgeo/geocaching/sorting/NameComparator.java b/main/src/cgeo/geocaching/sorting/NameComparator.java index c32bc48..b432ad0 100644 --- a/main/src/cgeo/geocaching/sorting/NameComparator.java +++ b/main/src/cgeo/geocaching/sorting/NameComparator.java @@ -1,6 +1,6 @@ package cgeo.geocaching.sorting; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; import org.apache.commons.lang3.StringUtils; @@ -11,12 +11,12 @@ import org.apache.commons.lang3.StringUtils; public class NameComparator extends AbstractCacheComparator { @Override - protected boolean canCompare(cgCache cache1, cgCache cache2) { + protected boolean canCompare(Geocache cache1, Geocache cache2) { return StringUtils.isNotBlank(cache1.getName()) && StringUtils.isNotBlank(cache2.getName()); } @Override - protected int compareCaches(cgCache cache1, cgCache cache2) { + protected int compareCaches(Geocache cache1, Geocache cache2) { return cache1.getNameForSorting().compareToIgnoreCase(cache2.getNameForSorting()); } } diff --git a/main/src/cgeo/geocaching/sorting/PopularityComparator.java b/main/src/cgeo/geocaching/sorting/PopularityComparator.java index 62ad9a9..e256654 100644 --- a/main/src/cgeo/geocaching/sorting/PopularityComparator.java +++ b/main/src/cgeo/geocaching/sorting/PopularityComparator.java @@ -1,6 +1,6 @@ package cgeo.geocaching.sorting; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; /** * sorts caches by popularity (favorite count) @@ -9,12 +9,12 @@ import cgeo.geocaching.cgCache; public class PopularityComparator extends AbstractCacheComparator { @Override - protected boolean canCompare(final cgCache cache1, final cgCache cache2) { + protected boolean canCompare(final Geocache cache1, final Geocache cache2) { return true; } @Override - protected int compareCaches(final cgCache cache1, final cgCache cache2) { + protected int compareCaches(final Geocache cache1, final Geocache cache2) { return cache2.getFavoritePoints() - cache1.getFavoritePoints(); } } diff --git a/main/src/cgeo/geocaching/sorting/RatingComparator.java b/main/src/cgeo/geocaching/sorting/RatingComparator.java index be071c5..72cf6c8 100644 --- a/main/src/cgeo/geocaching/sorting/RatingComparator.java +++ b/main/src/cgeo/geocaching/sorting/RatingComparator.java @@ -1,6 +1,6 @@ package cgeo.geocaching.sorting; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; /** * sorts caches by gcvote.com rating @@ -9,12 +9,12 @@ import cgeo.geocaching.cgCache; public class RatingComparator extends AbstractCacheComparator { @Override - protected boolean canCompare(final cgCache cache1, final cgCache cache2) { + protected boolean canCompare(final Geocache cache1, final Geocache cache2) { return true; } @Override - protected int compareCaches(final cgCache cache1, final cgCache cache2) { + protected int compareCaches(final Geocache cache1, final Geocache cache2) { final float rating1 = cache1.getRating(); final float rating2 = cache2.getRating(); // Voting can be disabled for caches, then assume an average rating instead diff --git a/main/src/cgeo/geocaching/sorting/SizeComparator.java b/main/src/cgeo/geocaching/sorting/SizeComparator.java index f946522..d128822 100644 --- a/main/src/cgeo/geocaching/sorting/SizeComparator.java +++ b/main/src/cgeo/geocaching/sorting/SizeComparator.java @@ -1,6 +1,6 @@ package cgeo.geocaching.sorting; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; /** * sorts caches by size @@ -9,12 +9,12 @@ import cgeo.geocaching.cgCache; public class SizeComparator extends AbstractCacheComparator { @Override - protected boolean canCompare(cgCache cache1, cgCache cache2) { + protected boolean canCompare(Geocache cache1, Geocache cache2) { return cache1.getSize() != null && cache2.getSize() != null; } @Override - protected int compareCaches(cgCache cache1, cgCache cache2) { + protected int compareCaches(Geocache cache1, Geocache cache2) { return cache2.getSize().comparable - cache1.getSize().comparable; } }
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/sorting/StateComparator.java b/main/src/cgeo/geocaching/sorting/StateComparator.java index bda514f..b99c3c0 100644 --- a/main/src/cgeo/geocaching/sorting/StateComparator.java +++ b/main/src/cgeo/geocaching/sorting/StateComparator.java @@ -1,6 +1,6 @@ package cgeo.geocaching.sorting; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; /** * sort caches by state (normal, disabled, archived) @@ -9,16 +9,16 @@ import cgeo.geocaching.cgCache; public class StateComparator extends AbstractCacheComparator { @Override - protected boolean canCompare(final cgCache cache1, final cgCache cache2) { + protected boolean canCompare(final Geocache cache1, final Geocache cache2) { return true; } @Override - protected int compareCaches(final cgCache cache1, final cgCache cache2) { + protected int compareCaches(final Geocache cache1, final Geocache cache2) { return getState(cache1) - getState(cache2); } - private static int getState(final cgCache cache) { + private static int getState(final Geocache cache) { if (cache.isDisabled()) { return 1; } diff --git a/main/src/cgeo/geocaching/sorting/StorageTimeComparator.java b/main/src/cgeo/geocaching/sorting/StorageTimeComparator.java index 32bef32..78ba742 100644 --- a/main/src/cgeo/geocaching/sorting/StorageTimeComparator.java +++ b/main/src/cgeo/geocaching/sorting/StorageTimeComparator.java @@ -1,16 +1,16 @@ package cgeo.geocaching.sorting; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; public class StorageTimeComparator extends AbstractCacheComparator { @Override - protected boolean canCompare(cgCache cache1, cgCache cache2) { + protected boolean canCompare(Geocache cache1, Geocache cache2) { return true; } @Override - protected int compareCaches(cgCache cache1, cgCache cache2) { + protected int compareCaches(Geocache cache1, Geocache cache2) { if (cache1.getUpdated() < cache2.getUpdated()) { return -1; } diff --git a/main/src/cgeo/geocaching/sorting/TerrainComparator.java b/main/src/cgeo/geocaching/sorting/TerrainComparator.java index 9058a3c..be1e9bb 100644 --- a/main/src/cgeo/geocaching/sorting/TerrainComparator.java +++ b/main/src/cgeo/geocaching/sorting/TerrainComparator.java @@ -1,6 +1,6 @@ package cgeo.geocaching.sorting; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; /** * sorts caches by terrain rating @@ -9,12 +9,12 @@ import cgeo.geocaching.cgCache; public class TerrainComparator extends AbstractCacheComparator { @Override - protected boolean canCompare(final cgCache cache1, final cgCache cache2) { + protected boolean canCompare(final Geocache cache1, final Geocache cache2) { return cache1.getTerrain() != 0.0 && cache2.getTerrain() != 0.0; } @Override - protected int compareCaches(final cgCache cache1, final cgCache cache2) { + protected int compareCaches(final Geocache cache1, final Geocache cache2) { return Float.compare(cache1.getTerrain(), cache2.getTerrain()); } } diff --git a/main/src/cgeo/geocaching/sorting/VisitComparator.java b/main/src/cgeo/geocaching/sorting/VisitComparator.java index 46d8c58..27d3170 100644 --- a/main/src/cgeo/geocaching/sorting/VisitComparator.java +++ b/main/src/cgeo/geocaching/sorting/VisitComparator.java @@ -1,6 +1,6 @@ package cgeo.geocaching.sorting; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; /** * sorts caches by last visited date @@ -9,12 +9,12 @@ import cgeo.geocaching.cgCache; public class VisitComparator extends AbstractCacheComparator { @Override - protected boolean canCompare(final cgCache cache1, final cgCache cache2) { + protected boolean canCompare(final Geocache cache1, final Geocache cache2) { return true; } @Override - protected int compareCaches(final cgCache cache1, final cgCache cache2) { + protected int compareCaches(final Geocache cache1, final Geocache cache2) { return Long.valueOf(cache2.getVisitedDate()).compareTo(cache1.getVisitedDate()); } } diff --git a/main/src/cgeo/geocaching/sorting/VoteComparator.java b/main/src/cgeo/geocaching/sorting/VoteComparator.java index 09d1620..dc0304b 100644 --- a/main/src/cgeo/geocaching/sorting/VoteComparator.java +++ b/main/src/cgeo/geocaching/sorting/VoteComparator.java @@ -1,6 +1,6 @@ package cgeo.geocaching.sorting; -import cgeo.geocaching.cgCache; +import cgeo.geocaching.Geocache; /** * sorts caches by the users own voting (if available at all) @@ -8,12 +8,12 @@ import cgeo.geocaching.cgCache; public class VoteComparator extends AbstractCacheComparator { @Override - protected boolean canCompare(cgCache cache1, cgCache cache2) { + protected boolean canCompare(Geocache cache1, Geocache cache2) { return true; } @Override - protected int compareCaches(cgCache cache1, cgCache cache2) { + protected int compareCaches(Geocache cache1, Geocache cache2) { // if there is no vote available, put that cache at the end of the list return Float.compare(cache2.getMyVote(), cache1.getMyVote()); } diff --git a/main/src/cgeo/geocaching/twitter/Twitter.java b/main/src/cgeo/geocaching/twitter/Twitter.java index c630cec..f30830e 100644 --- a/main/src/cgeo/geocaching/twitter/Twitter.java +++ b/main/src/cgeo/geocaching/twitter/Twitter.java @@ -1,8 +1,9 @@ package cgeo.geocaching.twitter; +import cgeo.geocaching.Geocache; import cgeo.geocaching.Settings; -import cgeo.geocaching.cgCache; -import cgeo.geocaching.cgTrackable; +import cgeo.geocaching.Trackable; +import cgeo.geocaching.cgData; import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.enumerations.LoadFlags; import cgeo.geocaching.geopoint.Geopoint; @@ -42,7 +43,7 @@ public final class Twitter { Log.e("Tweet could not be posted"); } } catch (Exception e) { - Log.e("cgBase.postTweet: " + e.toString()); + Log.e("cgBase.postTweet", e); } } @@ -55,7 +56,7 @@ public final class Twitter { } public static void postTweetCache(String geocode) { - final cgCache cache = cgeoapplication.getInstance().loadCache(geocode, LoadFlags.LOAD_CACHE_OR_DB); + final Geocache cache = cgData.loadCache(geocode, LoadFlags.LOAD_CACHE_OR_DB); String status; final String url = cache.getUrl(); if (url.length() >= 100) { @@ -76,7 +77,7 @@ public final class Twitter { } public static void postTweetTrackable(String geocode) { - final cgTrackable trackable = cgeoapplication.getInstance().getTrackableByGeocode(geocode); + final Trackable trackable = cgData.loadTrackable(geocode); String name = trackable.getName(); if (name.length() > 82) { name = name.substring(0, 81) + '…'; diff --git a/main/src/cgeo/geocaching/twitter/TwitterAuthorizationActivity.java b/main/src/cgeo/geocaching/twitter/TwitterAuthorizationActivity.java index 22ee0fb..3d9f283 100644 --- a/main/src/cgeo/geocaching/twitter/TwitterAuthorizationActivity.java +++ b/main/src/cgeo/geocaching/twitter/TwitterAuthorizationActivity.java @@ -7,6 +7,7 @@ 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 ch.boye.httpclientandroidlib.client.entity.UrlEncodedFormEntity; import ch.boye.httpclientandroidlib.util.EntityUtils; @@ -24,7 +25,6 @@ import android.view.View; import android.widget.Button; import android.widget.EditText; -import java.util.regex.Matcher; import java.util.regex.Pattern; public class TwitterAuthorizationActivity extends AbstractActivity { @@ -45,7 +45,7 @@ public class TwitterAuthorizationActivity extends AbstractActivity { requestTokenDialog.dismiss(); } - startButton.setOnClickListener(new startListener()); + startButton.setOnClickListener(new StartListener()); startButton.setEnabled(true); if (msg.what == 1) { @@ -53,7 +53,7 @@ public class TwitterAuthorizationActivity extends AbstractActivity { pinEntry.setVisibility(View.VISIBLE); pinEntryButton.setVisibility(View.VISIBLE); - pinEntryButton.setOnClickListener(new confirmPINListener()); + pinEntryButton.setOnClickListener(new ConfirmPINListener()); } else { showToast(res.getString(R.string.err_auth_initialize)); startButton.setText(res.getString(R.string.auth_start)); @@ -68,7 +68,7 @@ public class TwitterAuthorizationActivity extends AbstractActivity { changeTokensDialog.dismiss(); } - pinEntryButton.setOnClickListener(new confirmPINListener()); + pinEntryButton.setOnClickListener(new ConfirmPINListener()); pinEntryButton.setEnabled(true); if (msg.what == 1) { @@ -92,7 +92,7 @@ public class TwitterAuthorizationActivity extends AbstractActivity { super.onCreate(savedInstanceState); setTheme(); - setContentView(R.layout.auth); + setContentView(R.layout.twitter_authorization_activity); setTitle(res.getString(R.string.auth_twitter)); init(); @@ -114,7 +114,7 @@ public class TwitterAuthorizationActivity extends AbstractActivity { OAtokenSecret = tempToken.right; startButton.setEnabled(true); - startButton.setOnClickListener(new startListener()); + startButton.setOnClickListener(new StartListener()); if (StringUtils.isBlank(OAtoken) && StringUtils.isBlank(OAtokenSecret)) { // start authorization process @@ -125,29 +125,28 @@ public class TwitterAuthorizationActivity extends AbstractActivity { pinEntry.setVisibility(View.VISIBLE); pinEntryButton.setVisibility(View.VISIBLE); - pinEntryButton.setOnClickListener(new confirmPINListener()); + pinEntryButton.setOnClickListener(new ConfirmPINListener()); } } private void requestToken() { - final String host = "api.twitter.com"; - final String pathRequest = "/oauth/request_token"; - final String pathAuthorize = "/oauth/authorize"; - final String method = "GET"; 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 Matcher paramsMatcher1 = paramsPattern1.matcher(line); + final MatcherWrapper paramsMatcher1 = new MatcherWrapper(paramsPattern1, line); if (paramsMatcher1.find()) { OAtoken = paramsMatcher1.group(1); } - final Matcher paramsMatcher2 = paramsPattern2.matcher(line); + final MatcherWrapper paramsMatcher2 = new MatcherWrapper(paramsPattern2, line); if (paramsMatcher2.find()) { OAtokenSecret = paramsMatcher2.group(1); } @@ -157,43 +156,44 @@ public class TwitterAuthorizationActivity extends AbstractActivity { 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.toString()); + Log.e("TwitterAuthorizationActivity.requestToken(2)", e); } } } } catch (Exception e) { - Log.e("TwitterAuthorizationActivity.requestToken(1): " + e.toString()); + Log.e("TwitterAuthorizationActivity.requestToken(1)", e); } requestTokenHandler.sendEmptyMessage(status); } private void changeToken() { - final String host = "api.twitter.com"; - final String path = "/oauth/access_token"; - final String method = "POST"; 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 = ""; - final Matcher paramsMatcher1 = paramsPattern1.matcher(line); + final MatcherWrapper paramsMatcher1 = new MatcherWrapper(paramsPattern1, line); if (paramsMatcher1.find()) { OAtoken = paramsMatcher1.group(1); } - final Matcher paramsMatcher2 = paramsPattern2.matcher(line); + final MatcherWrapper paramsMatcher2 = new MatcherWrapper(paramsPattern2, line); if (paramsMatcher2.find() && paramsMatcher2.groupCount() > 0) { OAtokenSecret = paramsMatcher2.group(1); } @@ -207,13 +207,13 @@ public class TwitterAuthorizationActivity extends AbstractActivity { status = 1; } } catch (Exception e) { - Log.e("TwitterAuthorizationActivity.changeToken: " + e.toString()); + Log.e("TwitterAuthorizationActivity.changeToken", e); } changeTokensHandler.sendEmptyMessage(status); } - private class startListener implements View.OnClickListener { + private class StartListener implements View.OnClickListener { @Override public void onClick(View arg0) { @@ -238,11 +238,11 @@ public class TwitterAuthorizationActivity extends AbstractActivity { } } - private class confirmPINListener implements View.OnClickListener { + private class ConfirmPINListener implements View.OnClickListener { @Override public void onClick(View arg0) { - if (((EditText) findViewById(R.id.pin)).getText().toString().length() == 0) { + 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; } diff --git a/main/src/cgeo/geocaching/ui/AbstractCachingPageViewCreator.java b/main/src/cgeo/geocaching/ui/AbstractCachingPageViewCreator.java new file mode 100644 index 0000000..333ef11 --- /dev/null +++ b/main/src/cgeo/geocaching/ui/AbstractCachingPageViewCreator.java @@ -0,0 +1,32 @@ +package cgeo.geocaching.ui; + +import cgeo.geocaching.activity.AbstractViewPagerActivity.PageViewCreator; + +import android.view.View; + +/** + * View creator which destroys the created view on every {@link #notifyDataSetChanged()}. + * + * @param <ViewClass> + */ +public abstract class AbstractCachingPageViewCreator<ViewClass extends View> implements PageViewCreator { + + public ViewClass view; + + @Override + public final void notifyDataSetChanged() { + view = null; + } + + @Override + public final View getView() { + if (view == null) { + view = getDispatchedView(); + } + + return view; + } + + @Override + public abstract ViewClass getDispatchedView(); +} diff --git a/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java b/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java index 2a83ddc..e98bd77 100644 --- a/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java +++ b/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java @@ -1,7 +1,7 @@ package cgeo.geocaching.ui; +import cgeo.geocaching.Geocache; import cgeo.geocaching.R; -import cgeo.geocaching.cgCache; import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.Units; @@ -35,7 +35,7 @@ public final class CacheDetailsCreator { } public TextView add(final int nameId, final CharSequence value) { - final RelativeLayout layout = (RelativeLayout) activity.getLayoutInflater().inflate(R.layout.cache_item, null); + final RelativeLayout layout = (RelativeLayout) activity.getLayoutInflater().inflate(R.layout.cache_information_item, null); final TextView nameView = (TextView) layout.findViewById(R.id.name); nameView.setText(res.getString(nameId)); lastValueView = (TextView) layout.findViewById(R.id.value); @@ -49,23 +49,22 @@ public final class CacheDetailsCreator { } public RelativeLayout addStars(final int nameId, final float value) { - final RelativeLayout layout = (RelativeLayout) activity.getLayoutInflater().inflate(R.layout.cache_layout, null); + final RelativeLayout layout = (RelativeLayout) activity.getLayoutInflater().inflate(R.layout.cache_information_item, null); final TextView nameView = (TextView) layout.findViewById(R.id.name); lastValueView = (TextView) layout.findViewById(R.id.value); final LinearLayout layoutStars = (LinearLayout) layout.findViewById(R.id.stars); nameView.setText(activity.getResources().getString(nameId)); lastValueView.setText(String.format("%.1f", value) + ' ' + activity.getResources().getString(R.string.cache_rating_of) + " 5"); - layoutStars.addView(createStarImages(value), 1); + createStarImages(layoutStars, value); + layoutStars.setVisibility(View.VISIBLE); parentView.addView(layout); return layout; } - private LinearLayout createStarImages(final float value) { + private void createStarImages(final ViewGroup starsContainer, final float value) { final LayoutInflater inflater = LayoutInflater.from(activity); - final LinearLayout starsContainer = new LinearLayout(activity); - starsContainer.setOrientation(LinearLayout.HORIZONTAL); for (int i = 0; i < 5; i++) { ImageView star = (ImageView) inflater.inflate(R.layout.star, null); @@ -78,11 +77,9 @@ public final class CacheDetailsCreator { } starsContainer.addView(star); } - - return starsContainer; } - public void addCacheState(cgCache cache) { + public void addCacheState(Geocache cache) { if (cache.isLogOffline() || cache.isArchived() || cache.isDisabled() || cache.isPremiumMembersOnly() || cache.isFound()) { final List<String> states = new ArrayList<String>(5); if (cache.isLogOffline()) { @@ -104,7 +101,7 @@ public final class CacheDetailsCreator { } } - public void addRating(cgCache cache) { + public void addRating(Geocache cache) { if (cache.getRating() > 0) { final RelativeLayout itemLayout = addStars(R.string.cache_rating, cache.getRating()); if (cache.getVotes() > 0) { @@ -115,25 +112,25 @@ public final class CacheDetailsCreator { } } - public void addSize(cgCache cache) { + public void addSize(Geocache cache) { if (null != cache.getSize() && cache.showSize()) { add(R.string.cache_size, cache.getSize().getL10n()); } } - public void addDifficulty(cgCache cache) { + public void addDifficulty(Geocache cache) { if (cache.getDifficulty() > 0) { addStars(R.string.cache_difficulty, cache.getDifficulty()); } } - public void addTerrain(cgCache cache) { + public void addTerrain(Geocache cache) { if (cache.getTerrain() > 0) { addStars(R.string.cache_terrain, cache.getTerrain()); } } - public void addDistance(final cgCache cache, final TextView cacheDistanceView) { + public void addDistance(final Geocache cache, final TextView cacheDistanceView) { Float distance = null; if (cache.getCoords() != null) { final Geopoint currentCoords = cgeoapplication.getInstance().currentGeo().getCoords(); diff --git a/main/src/cgeo/geocaching/ui/CacheListAdapter.java b/main/src/cgeo/geocaching/ui/CacheListAdapter.java index 65d3fbc..41e27a6 100644 --- a/main/src/cgeo/geocaching/ui/CacheListAdapter.java +++ b/main/src/cgeo/geocaching/ui/CacheListAdapter.java @@ -1,10 +1,10 @@ package cgeo.geocaching.ui; import cgeo.geocaching.CacheDetailActivity; +import cgeo.geocaching.Geocache; import cgeo.geocaching.IGeoData; import cgeo.geocaching.R; import cgeo.geocaching.Settings; -import cgeo.geocaching.cgCache; import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.enumerations.CacheListType; import cgeo.geocaching.enumerations.CacheType; @@ -12,6 +12,7 @@ import cgeo.geocaching.filter.IFilter; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.sorting.CacheComparator; import cgeo.geocaching.sorting.DistanceComparator; +import cgeo.geocaching.sorting.InverseComparator; import cgeo.geocaching.sorting.VisitComparator; import cgeo.geocaching.utils.AngleUtils; import cgeo.geocaching.utils.Log; @@ -43,11 +44,12 @@ import android.widget.TextView; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; -public class CacheListAdapter extends ArrayAdapter<cgCache> { +public class CacheListAdapter extends ArrayAdapter<Geocache> { private LayoutInflater inflater = null; private CacheComparator cacheComparator = null; @@ -56,14 +58,15 @@ public class CacheListAdapter extends ArrayAdapter<cgCache> { private long lastSort = 0L; private boolean selectMode = false; private IFilter currentFilter = null; - private List<cgCache> originalList = null; + private List<Geocache> originalList = null; final private Set<CompassMiniView> compasses = new LinkedHashSet<CompassMiniView>(); final private Set<DistanceView> distances = new LinkedHashSet<DistanceView>(); final private CacheListType cacheListType; final private Resources res; /** Resulting list of caches */ - final private List<cgCache> list; + final private List<Geocache> list; + private boolean inverseSort = false; private static final int SWIPE_MIN_DISTANCE = 60; private static final int SWIPE_MAX_OFF_PATH = 100; @@ -104,7 +107,7 @@ public class CacheListAdapter extends ArrayAdapter<cgCache> { ImageView dirImg; } - public CacheListAdapter(final Activity activity, final List<cgCache> list, CacheListType cacheListType) { + public CacheListAdapter(final Activity activity, final List<Geocache> list, CacheListType cacheListType) { super(activity, 0, list); final IGeoData currentGeo = cgeoapplication.getInstance().currentGeo(); if (currentGeo != null) { @@ -149,6 +152,14 @@ public class CacheListAdapter extends ArrayAdapter<cgCache> { * @param comparator */ public void setComparator(final CacheComparator comparator) { + // selecting the same sorting twice will toggle the order + if (cacheComparator != null && comparator != null && cacheComparator.getClass().equals(comparator.getClass())) { + inverseSort = !inverseSort; + } + else { + // always reset the inversion for a new sorting criteria + inverseSort = false; + } cacheComparator = comparator; forceSort(); } @@ -157,7 +168,7 @@ public class CacheListAdapter extends ArrayAdapter<cgCache> { return cacheComparator; } - public cgCache findCacheByGeocode(String geocode) { + public Geocache findCacheByGeocode(String geocode) { for (int i = 0; i < getCount(); i++) { if (getItem(i).getGeocode().equalsIgnoreCase(geocode)) { return getItem(i); @@ -172,7 +183,7 @@ public class CacheListAdapter extends ArrayAdapter<cgCache> { public void reFilter() { if (currentFilter != null) { // Back up the list again - originalList = new ArrayList<cgCache>(list); + originalList = new ArrayList<Geocache>(list); currentFilter.filter(list); } @@ -184,7 +195,7 @@ public class CacheListAdapter extends ArrayAdapter<cgCache> { public void setFilter(final IFilter filter) { // Backup current caches list if it isn't backed up yet if (originalList == null) { - originalList = new ArrayList<cgCache>(list); + originalList = new ArrayList<Geocache>(list); } // If there is already a filter in place, this is a request to change or clear the filter, so we have to @@ -213,7 +224,7 @@ public class CacheListAdapter extends ArrayAdapter<cgCache> { public int getCheckedCount() { int checked = 0; - for (cgCache cache : list) { + for (Geocache cache : list) { if (cache.isStatusChecked()) { checked++; } @@ -225,7 +236,7 @@ public class CacheListAdapter extends ArrayAdapter<cgCache> { this.selectMode = selectMode; if (!selectMode) { - for (final cgCache cache : list) { + for (final Geocache cache : list) { cache.setStatusChecked(false); } } @@ -241,7 +252,7 @@ public class CacheListAdapter extends ArrayAdapter<cgCache> { } public void invertSelection() { - for (cgCache cache : list) { + for (Geocache cache : list) { cache.setStatusChecked(!cache.isStatusChecked()); } notifyDataSetChanged(); @@ -257,7 +268,7 @@ public class CacheListAdapter extends ArrayAdapter<cgCache> { updateSortByDistance(); } else { - Collections.sort(list, cacheComparator); + Collections.sort(list, getPotentialInversion(cacheComparator)); } notifyDataSetChanged(); @@ -291,8 +302,8 @@ public class CacheListAdapter extends ArrayAdapter<cgCache> { if (coords == null) { return; } - final ArrayList<cgCache> oldList = new ArrayList<cgCache>(list); - Collections.sort(list, new DistanceComparator(coords, list)); + final ArrayList<Geocache> oldList = new ArrayList<Geocache>(list); + Collections.sort(list, getPotentialInversion(new DistanceComparator(coords, list))); // avoid an update if the list has not changed due to location update if (list.equals(oldList)) { @@ -302,6 +313,13 @@ public class CacheListAdapter extends ArrayAdapter<cgCache> { lastSort = System.currentTimeMillis(); } + private Comparator<? super Geocache> getPotentialInversion(final CacheComparator comparator) { + if (inverseSort) { + return new InverseComparator(comparator); + } + return comparator; + } + private boolean isSortedByDistance() { return cacheComparator == null || cacheComparator instanceof DistanceComparator; } @@ -328,7 +346,7 @@ public class CacheListAdapter extends ArrayAdapter<cgCache> { return null; } - final cgCache cache = getItem(position); + final Geocache cache = getItem(position); View v = rowView; @@ -390,17 +408,24 @@ public class CacheListAdapter extends ArrayAdapter<cgCache> { holder.logStatusMark.setVisibility(View.GONE); } - if (cache.getNameSp() == null) { - cache.setNameSp((new Spannable.Factory()).newSpannable(cache.getName())); - if (cache.isDisabled() || cache.isArchived()) { // strike - cache.getNameSp().setSpan(new StrikethroughSpan(), 0, cache.getNameSp().toString().length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - if (cache.isArchived()) { - cache.getNameSp().setSpan(new ForegroundColorSpan(res.getColor(R.color.archived_cache_color)), 0, cache.getNameSp().toString().length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + Spannable spannable = null; + if (cache.isDisabled() || cache.isArchived()) { // strike + spannable = Spannable.Factory.getInstance().newSpannable(cache.getName()); + spannable.setSpan(new StrikethroughSpan(), 0, spannable.toString().length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (cache.isArchived()) { // red color + if (spannable == null) { + spannable = Spannable.Factory.getInstance().newSpannable(cache.getName()); } + spannable.setSpan(new ForegroundColorSpan(res.getColor(R.color.archived_cache_color)), 0, spannable.toString().length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } - holder.text.setText(cache.getNameSp(), TextView.BufferType.SPANNABLE); + if (spannable != null) { + holder.text.setText(spannable, TextView.BufferType.SPANNABLE); + } + else { + holder.text.setText(cache.getName()); + } holder.text.setCompoundDrawablesWithIntrinsicBounds(getCacheIcon(cache), null, null, null); if (cache.getInventoryItems() > 0) { @@ -508,7 +533,7 @@ public class CacheListAdapter extends ArrayAdapter<cgCache> { return v; } - private static Drawable getCacheIcon(cgCache cache) { + private static Drawable getCacheIcon(Geocache cache) { int hashCode = getIconHashCode(cache.getType(), cache.hasUserModifiedCoords() || cache.hasFinalDefined()); final Drawable drawable = gcIconDrawables.get(hashCode); if (drawable != null) { @@ -529,9 +554,9 @@ public class CacheListAdapter extends ArrayAdapter<cgCache> { private static class SelectionCheckBoxListener implements View.OnClickListener { - private final cgCache cache; + private final Geocache cache; - public SelectionCheckBoxListener(cgCache cache) { + public SelectionCheckBoxListener(Geocache cache) { this.cache = cache; } @@ -546,9 +571,9 @@ public class CacheListAdapter extends ArrayAdapter<cgCache> { private boolean touch = true; private final GestureDetector gestureDetector; - private final cgCache cache; + private final Geocache cache; - public TouchListener(final cgCache cache) { + public TouchListener(final Geocache cache) { this.cache = cache; final FlingGesture dGesture = new FlingGesture(cache); gestureDetector = new GestureDetector(getContext(), dGesture); @@ -597,9 +622,9 @@ public class CacheListAdapter extends ArrayAdapter<cgCache> { private class FlingGesture extends GestureDetector.SimpleOnGestureListener { - private final cgCache cache; + private final Geocache cache; - public FlingGesture(cgCache cache) { + public FlingGesture(Geocache cache) { this.cache = cache; } @@ -627,20 +652,20 @@ public class CacheListAdapter extends ArrayAdapter<cgCache> { return true; } } catch (Exception e) { - Log.w("CacheListAdapter.FlingGesture.onFling: " + e.toString()); + Log.w("CacheListAdapter.FlingGesture.onFling", e); } return false; } } - public List<cgCache> getFilteredList() { + public List<Geocache> getFilteredList() { return list; } - public List<cgCache> getCheckedCaches() { - final ArrayList<cgCache> result = new ArrayList<cgCache>(); - for (cgCache cache : list) { + public List<Geocache> getCheckedCaches() { + final ArrayList<Geocache> result = new ArrayList<Geocache>(); + for (Geocache cache : list) { if (cache.isStatusChecked()) { result.add(cache); } @@ -648,12 +673,12 @@ public class CacheListAdapter extends ArrayAdapter<cgCache> { return result; } - public List<cgCache> getCheckedOrAllCaches() { - final List<cgCache> result = getCheckedCaches(); + public List<Geocache> getCheckedOrAllCaches() { + final List<Geocache> result = getCheckedCaches(); if (!result.isEmpty()) { return result; } - return new ArrayList<cgCache>(list); + return new ArrayList<Geocache>(list); } public int getCheckedOrAllCount() { diff --git a/main/src/cgeo/geocaching/ui/CompassMiniView.java b/main/src/cgeo/geocaching/ui/CompassMiniView.java index 44fb8e2..da8f69e 100644 --- a/main/src/cgeo/geocaching/ui/CompassMiniView.java +++ b/main/src/cgeo/geocaching/ui/CompassMiniView.java @@ -97,17 +97,17 @@ final public class CompassMiniView extends View { targetCoords = point; } - protected void updateAzimuth(float azimuth) { + public void updateAzimuth(float azimuth) { this.azimuth = azimuth; updateDirection(); } - protected void updateHeading(float heading) { + public void updateHeading(float heading) { this.heading = heading; updateDirection(); } - protected void updateCurrentCoords(final Geopoint currentCoords) { + public void updateCurrentCoords(final Geopoint currentCoords) { if (currentCoords == null || targetCoords == null) { return; } diff --git a/main/src/cgeo/geocaching/ui/CompassView.java b/main/src/cgeo/geocaching/ui/CompassView.java index 048e280..0ef3a43 100644 --- a/main/src/cgeo/geocaching/ui/CompassView.java +++ b/main/src/cgeo/geocaching/ui/CompassView.java @@ -185,16 +185,13 @@ public class CompassView extends View { int canvasCenterX = (compassRoseWidth / 2) + ((getWidth() - compassRoseWidth) / 2); int canvasCenterY = (compassRoseHeight / 2) + ((getHeight() - compassRoseHeight) / 2); - int marginLeftTemp; - int marginTopTemp; - super.onDraw(canvas); canvas.save(); canvas.setDrawFilter(setfil); - marginLeftTemp = (getWidth() - compassUnderlayWidth) / 2; - marginTopTemp = (getHeight() - compassUnderlayHeight) / 2; + int marginLeftTemp = (getWidth() - compassUnderlayWidth) / 2; + int marginTopTemp = (getHeight() - compassUnderlayHeight) / 2; canvas.drawBitmap(compassUnderlay, marginLeftTemp, marginTopTemp, null); diff --git a/main/src/cgeo/geocaching/ui/FileSelectionListAdapter.java b/main/src/cgeo/geocaching/ui/FileSelectionListAdapter.java index 1b5d47f..1db3f21 100644 --- a/main/src/cgeo/geocaching/ui/FileSelectionListAdapter.java +++ b/main/src/cgeo/geocaching/ui/FileSelectionListAdapter.java @@ -61,7 +61,7 @@ public class FileSelectionListAdapter extends ArrayAdapter<File> { holder.filename.setTypeface(holder.filename.getTypeface(), Typeface.NORMAL); } - final touchListener touchLst = new touchListener(file); + final TouchListener touchLst = new TouchListener(file); v.setOnClickListener(touchLst); holder.filepath.setText(file.getParent()); @@ -70,10 +70,10 @@ public class FileSelectionListAdapter extends ArrayAdapter<File> { return v; } - private class touchListener implements View.OnClickListener { + private class TouchListener implements View.OnClickListener { private File file = null; - public touchListener(File fileIn) { + public TouchListener(File fileIn) { file = fileIn; } diff --git a/main/src/cgeo/geocaching/ui/Formatter.java b/main/src/cgeo/geocaching/ui/Formatter.java index 53a7276..412b993 100644 --- a/main/src/cgeo/geocaching/ui/Formatter.java +++ b/main/src/cgeo/geocaching/ui/Formatter.java @@ -1,8 +1,8 @@ package cgeo.geocaching.ui; +import cgeo.geocaching.Geocache; import cgeo.geocaching.R; -import cgeo.geocaching.cgCache; -import cgeo.geocaching.cgWaypoint; +import cgeo.geocaching.Waypoint; import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.enumerations.CacheListType; import cgeo.geocaching.enumerations.CacheSize; @@ -13,6 +13,7 @@ import org.apache.commons.lang3.StringUtils; import android.content.Context; import android.text.format.DateUtils; +import java.text.DateFormat; import java.util.ArrayList; import java.util.List; @@ -70,8 +71,8 @@ public abstract class Formatter { * @return the formatted string */ public static String formatShortDate(long date) { - return DateUtils.formatDateTime(context, date, DateUtils.FORMAT_SHOW_DATE - | DateUtils.FORMAT_NUMERIC_DATE); + DateFormat dateFormat = android.text.format.DateFormat.getDateFormat(context); + return dateFormat.format(date); } /** @@ -108,7 +109,7 @@ public abstract class Formatter { return DateUtils.formatDateTime(context, date, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL); } - public static String formatCacheInfoLong(cgCache cache, CacheListType cacheListType) { + public static String formatCacheInfoLong(Geocache cache, CacheListType cacheListType) { final ArrayList<String> infos = new ArrayList<String>(); if (StringUtils.isNotBlank(cache.getGeocode())) { infos.add(cache.getGeocode()); @@ -125,7 +126,7 @@ public abstract class Formatter { return StringUtils.join(infos, Formatter.SEPARATOR); } - public static String formatCacheInfoShort(cgCache cache) { + public static String formatCacheInfoShort(Geocache cache) { final ArrayList<String> infos = new ArrayList<String>(); if (cache.hasDifficulty()) { infos.add("D " + String.format("%.1f", cache.getDifficulty())); @@ -143,7 +144,7 @@ public abstract class Formatter { return StringUtils.join(infos, Formatter.SEPARATOR); } - public static String formatCacheInfoHistory(cgCache cache) { + public static String formatCacheInfoHistory(Geocache cache) { final ArrayList<String> infos = new ArrayList<String>(3); infos.add(StringUtils.upperCase(cache.getGeocode())); infos.add(Formatter.formatDate(cache.getVisitedDate())); @@ -151,12 +152,13 @@ public abstract class Formatter { return StringUtils.join(infos, Formatter.SEPARATOR); } - public static String formatWaypointInfo(cgWaypoint waypoint) { + public static String formatWaypointInfo(Waypoint waypoint) { final List<String> infos = new ArrayList<String>(3); - if (WaypointType.ALL_TYPES_EXCEPT_OWN.contains(waypoint.getWaypointType())) { - infos.add(waypoint.getWaypointType().getL10n()); + WaypointType waypointType = waypoint.getWaypointType(); + if (waypointType != WaypointType.OWN && waypointType != null) { + infos.add(waypointType.getL10n()); } - if (cgWaypoint.PREFIX_OWN.equalsIgnoreCase(waypoint.getPrefix())) { + if (Waypoint.PREFIX_OWN.equalsIgnoreCase(waypoint.getPrefix())) { infos.add(cgeoapplication.getInstance().getString(R.string.waypoint_custom)); } else { if (StringUtils.isNotBlank(waypoint.getPrefix())) { diff --git a/main/src/cgeo/geocaching/ui/GPXListAdapter.java b/main/src/cgeo/geocaching/ui/GPXListAdapter.java index 45e1f52..9f6c14c 100644 --- a/main/src/cgeo/geocaching/ui/GPXListAdapter.java +++ b/main/src/cgeo/geocaching/ui/GPXListAdapter.java @@ -1,7 +1,7 @@ package cgeo.geocaching.ui; import cgeo.geocaching.R; -import cgeo.geocaching.cgeogpxes; +import cgeo.geocaching.GpxFileListActivity; import cgeo.geocaching.files.GPXImporter; import cgeo.geocaching.utils.Log; @@ -18,7 +18,7 @@ import java.io.File; import java.util.List; public class GPXListAdapter extends ArrayAdapter<File> { - private cgeogpxes activity = null; + private GpxFileListActivity activity = null; private LayoutInflater inflater = null; private static class ViewHolder { @@ -26,7 +26,7 @@ public class GPXListAdapter extends ArrayAdapter<File> { TextView filename; } - public GPXListAdapter(cgeogpxes parentIn, List<File> listIn) { + public GPXListAdapter(GpxFileListActivity parentIn, List<File> listIn) { super(parentIn, 0, listIn); activity = parentIn; diff --git a/main/src/cgeo/geocaching/ui/ImagesList.java b/main/src/cgeo/geocaching/ui/ImagesList.java index 3d6f95c..9464114 100644 --- a/main/src/cgeo/geocaching/ui/ImagesList.java +++ b/main/src/cgeo/geocaching/ui/ImagesList.java @@ -1,14 +1,14 @@ package cgeo.geocaching.ui; +import cgeo.geocaching.Image; import cgeo.geocaching.R; import cgeo.geocaching.StoredList; -import cgeo.geocaching.cgImage; import cgeo.geocaching.files.LocalStorage; import cgeo.geocaching.network.HtmlImage; +import cgeo.geocaching.utils.IOUtils; import cgeo.geocaching.utils.Log; import org.apache.commons.lang3.StringUtils; -import org.mapsforge.core.IOUtils; import android.app.Activity; import android.app.ProgressDialog; @@ -43,7 +43,7 @@ public class ImagesList { private static final int MENU_BROWSER = 202; private BitmapDrawable currentDrawable; - private cgImage currentImage; + private Image currentImage; public enum ImageType { LogImages(R.string.cache_log_images_title, R.string.cache_log_images_loading), @@ -53,7 +53,7 @@ public class ImagesList { private final int titleResId; private final int loadingResId; - private ImageType(final int title, final int loading) { + ImageType(final int title, final int loading) { this.titleResId = title; this.loadingResId = loading; } @@ -73,7 +73,7 @@ public class ImagesList { /** * map image view id to image */ - private final SparseArray<cgImage> images = new SparseArray<cgImage>(); + private final SparseArray<Image> images = new SparseArray<Image>(); private final String geocode; private LinearLayout imagesView; @@ -83,7 +83,7 @@ public class ImagesList { inflater = activity.getLayoutInflater(); } - public void loadImages(final View parentView, final List<cgImage> images, ImageType imageType, final boolean offline) { + public void loadImages(final View parentView, final List<Image> images, ImageType imageType, final boolean offline) { imagesView = (LinearLayout) parentView.findViewById(R.id.spoiler_list); @@ -95,9 +95,8 @@ public class ImagesList { progressDialog.setMax(count); progressDialog.show(); - LinearLayout rowView; - for (final cgImage img : images) { - rowView = (LinearLayout) inflater.inflate(R.layout.cache_image_item, null); + for (final Image img : images) { + LinearLayout rowView = (LinearLayout) inflater.inflate(R.layout.cache_image_item, null); if (StringUtils.isNotBlank(img.getTitle())) { ((TextView) rowView.findViewById(R.id.title)).setText(Html.fromHtml(img.getTitle())); @@ -118,10 +117,10 @@ public class ImagesList { private class AsyncImgLoader extends AsyncTask<Void, Void, BitmapDrawable> { final private LinearLayout view; - final private cgImage img; + final private Image img; final boolean offline; - public AsyncImgLoader(final LinearLayout view, final cgImage img, final boolean offline) { + public AsyncImgLoader(final LinearLayout view, final Image img, final boolean offline) { this.view = view; this.img = img; this.offline = offline; @@ -179,10 +178,6 @@ public class ImagesList { bitmaps.clear(); } - public cgImage getImage(int id) { - return images.get(id); - } - public void onCreateContextMenu(ContextMenu menu, View v) { final Resources res = activity.getResources(); menu.setHeaderTitle(res.getString(R.string.cache_image)); @@ -215,7 +210,7 @@ public class ImagesList { fos = new FileOutputStream(file); image.getBitmap().compress(CompressFormat.JPEG, 100, fos); } catch (Exception e) { - Log.e("ImagesActivity.handleMessage.onClick: " + e.toString()); + Log.e("ImagesActivity.handleMessage.onClick", e); return; } finally { IOUtils.closeQuietly(fos); diff --git a/main/src/cgeo/geocaching/ui/LoggingUI.java b/main/src/cgeo/geocaching/ui/LoggingUI.java index 0e048c3..2615947 100644 --- a/main/src/cgeo/geocaching/ui/LoggingUI.java +++ b/main/src/cgeo/geocaching/ui/LoggingUI.java @@ -1,10 +1,10 @@ package cgeo.geocaching.ui; +import cgeo.geocaching.Geocache; import cgeo.geocaching.LogEntry; import cgeo.geocaching.R; import cgeo.geocaching.Settings; -import cgeo.geocaching.cgCache; -import cgeo.geocaching.cgeoapplication; +import cgeo.geocaching.cgData; import cgeo.geocaching.activity.IAbstractActivity; import cgeo.geocaching.enumerations.LogType; @@ -52,7 +52,7 @@ public class LoggingUI extends AbstractUIFactory { private final int stringId; - private SpecialLogType(final int stringId) { + SpecialLogType(final int stringId) { this.stringId = stringId; } @@ -65,7 +65,7 @@ public class LoggingUI extends AbstractUIFactory { 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 cgCache cache) { + public static void addMenuItems(final Menu menu, final Geocache cache) { if (cache == null) { return; } @@ -80,7 +80,7 @@ public class LoggingUI extends AbstractUIFactory { } } - public static boolean onMenuItemSelected(final MenuItem item, IAbstractActivity activity, cgCache cache) { + public static boolean onMenuItemSelected(final MenuItem item, IAbstractActivity activity, Geocache cache) { switch (item.getItemId()) { case MENU_LOG_VISIT: cache.logVisit(activity); @@ -93,8 +93,8 @@ public class LoggingUI extends AbstractUIFactory { } } - private static void showOfflineMenu(final cgCache cache, final Activity activity) { - final LogEntry currentLog = cgeoapplication.getInstance().loadLogOffline(cache.getGeocode()); + private static void showOfflineMenu(final Geocache cache, final Activity activity) { + final LogEntry currentLog = cgData.loadLogOffline(cache.getGeocode()); final LogType currentLogType = currentLog == null ? null : currentLog.type; final List<LogType> logTypes = cache.getPossibleLogTypes(); @@ -123,7 +123,8 @@ public class LoggingUI extends AbstractUIFactory { break; case CLEAR_LOG: - cgeoapplication.getInstance().clearLogOffline(cache.getGeocode()); + cgData.clearLogOffline(cache.getGeocode()); + break; } } else { cache.logOffline(activity, logTypeEntry.logType); diff --git a/main/src/cgeo/geocaching/ui/WeakReferenceHandler.java b/main/src/cgeo/geocaching/ui/WeakReferenceHandler.java new file mode 100644 index 0000000..4724466 --- /dev/null +++ b/main/src/cgeo/geocaching/ui/WeakReferenceHandler.java @@ -0,0 +1,27 @@ +package cgeo.geocaching.ui; + +import android.app.Activity; +import android.os.Handler; + +import java.lang.ref.WeakReference; + +/** + * Standard handler implementation which uses a weak reference to its activity. This avoids that activities stay in + * memory due to references from the handler to the activity (see Android Lint warning "HandlerLeak") + * + * Create static private subclasses of this handler class in your activity. + * + * @param <ActivityType> + */ +public abstract class WeakReferenceHandler<ActivityType extends Activity> extends Handler { + + private final WeakReference<ActivityType> activityRef; + + protected WeakReferenceHandler(final ActivityType activity) { + this.activityRef = new WeakReference<ActivityType>(activity); + } + + protected ActivityType getActivity() { + return activityRef.get(); + } +} diff --git a/main/src/cgeo/geocaching/cgeocoords.java b/main/src/cgeo/geocaching/ui/dialog/CoordinatesInputDialog.java index 1cf9630..dada8fd 100644 --- a/main/src/cgeo/geocaching/cgeocoords.java +++ b/main/src/cgeo/geocaching/ui/dialog/CoordinatesInputDialog.java @@ -1,5 +1,9 @@ -package cgeo.geocaching; +package cgeo.geocaching.ui.dialog; +import cgeo.geocaching.Geocache; +import cgeo.geocaching.IGeoData; +import cgeo.geocaching.R; +import cgeo.geocaching.Settings; import cgeo.geocaching.Settings.coordInputFormatEnum; import cgeo.geocaching.activity.AbstractActivity; import cgeo.geocaching.activity.ActivityMixin; @@ -24,11 +28,11 @@ import android.widget.EditText; import android.widget.Spinner; import android.widget.TextView; -public class cgeocoords extends Dialog { +public class CoordinatesInputDialog extends Dialog { final private AbstractActivity context; final private IGeoData geo; - final private cgCache cache; + final private Geocache cache; private Geopoint gp; private EditText eLat, eLon; @@ -42,8 +46,8 @@ public class cgeocoords extends Dialog { private coordInputFormatEnum currentFormat = null; - public cgeocoords(final AbstractActivity context, final cgCache cache, final Geopoint gp, final IGeoData geo) { - super(context); + public CoordinatesInputDialog(final AbstractActivity context, final Geocache cache, final Geopoint gp, final IGeoData geo) { + super(context, ActivityMixin.getTheme()); this.context = context; this.geo = geo; this.cache = cache; @@ -378,6 +382,8 @@ public class cgeocoords extends Dialog { break; case Plain: // This case has been handled above + default: + throw new IllegalArgumentException(); } return true; diff --git a/main/src/cgeo/geocaching/ui/CustomProgressDialog.java b/main/src/cgeo/geocaching/ui/dialog/CustomProgressDialog.java index f7772be..c2b722c 100644 --- a/main/src/cgeo/geocaching/ui/CustomProgressDialog.java +++ b/main/src/cgeo/geocaching/ui/dialog/CustomProgressDialog.java @@ -1,5 +1,6 @@ -package cgeo.geocaching.ui; +package cgeo.geocaching.ui.dialog; +import cgeo.geocaching.activity.ActivityMixin; import cgeo.geocaching.utils.Log; import android.app.ProgressDialog; @@ -18,7 +19,7 @@ import java.lang.reflect.Method; public class CustomProgressDialog extends ProgressDialog { public CustomProgressDialog(Context context) { - super(context); + super(context, ActivityMixin.getTheme()); } @Override diff --git a/main/src/cgeo/geocaching/ui/DateDialog.java b/main/src/cgeo/geocaching/ui/dialog/DateDialog.java index 5dfd9b9..a9c579c 100644 --- a/main/src/cgeo/geocaching/ui/DateDialog.java +++ b/main/src/cgeo/geocaching/ui/dialog/DateDialog.java @@ -1,4 +1,4 @@ -package cgeo.geocaching.ui; +package cgeo.geocaching.ui.dialog; import cgeo.geocaching.R; diff --git a/main/src/cgeo/geocaching/ui/EditorDialog.java b/main/src/cgeo/geocaching/ui/dialog/EditorDialog.java index 50b3e27..4db69e5 100644 --- a/main/src/cgeo/geocaching/ui/EditorDialog.java +++ b/main/src/cgeo/geocaching/ui/dialog/EditorDialog.java @@ -1,7 +1,8 @@ -package cgeo.geocaching.ui; +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; @@ -17,7 +18,7 @@ public class EditorDialog extends Dialog { private EditorUpdate editorUpdate; public EditorDialog(CacheDetailActivity cacheDetailActivity, CharSequence editable) { - super(cacheDetailActivity); + super(cacheDetailActivity, ActivityMixin.getTheme()); this.editorText = editable; } @@ -53,7 +54,7 @@ public class EditorDialog extends Dialog { @Override public void show() { super.show(); - getWindow().setLayout(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT); + getWindow().setLayout(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); } } diff --git a/main/src/cgeo/geocaching/ui/dialog/LiveMapInfoDialogBuilder.java b/main/src/cgeo/geocaching/ui/dialog/LiveMapInfoDialogBuilder.java new file mode 100644 index 0000000..862b1a0 --- /dev/null +++ b/main/src/cgeo/geocaching/ui/dialog/LiveMapInfoDialogBuilder.java @@ -0,0 +1,45 @@ +package cgeo.geocaching.ui.dialog; + +import cgeo.geocaching.R; +import cgeo.geocaching.Settings; +import cgeo.geocaching.cgeoapplication; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.view.ContextThemeWrapper; +import android.view.View; +import android.widget.CheckBox; + +public class LiveMapInfoDialogBuilder { + + public static AlertDialog create(Activity activity) { + final AlertDialog.Builder builder = new AlertDialog.Builder(activity); + + // AlertDialog has always dark style, so we have to apply it as well always + final View layout = View.inflate(new ContextThemeWrapper(activity, R.style.dark), R.layout.livemapinfo, null); + builder.setView(layout); + + final CheckBox checkBoxHide = (CheckBox) layout.findViewById(R.id.live_map_hint_hide); + + final int showCount = Settings.getLiveMapHintShowCount(); + if (showCount > 2) { + checkBoxHide.setVisibility(View.VISIBLE); + } + Settings.setLiveMapHintShowCount(showCount + 1); + + builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + cgeoapplication.getInstance().setLiveMapHintShown(); + if (checkBoxHide.getVisibility() == View.VISIBLE && checkBoxHide.isChecked()) { + Settings.setHideLiveHint(true); + } + } + }); + return builder.create(); + } + +} diff --git a/main/src/cgeo/geocaching/utils/AngleUtils.java b/main/src/cgeo/geocaching/utils/AngleUtils.java index e2b4a66..6e59a91 100644 --- a/main/src/cgeo/geocaching/utils/AngleUtils.java +++ b/main/src/cgeo/geocaching/utils/AngleUtils.java @@ -8,9 +8,11 @@ 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 the target angle in degreees + * + * @param from + * the origin angle in degrees + * @param to + * the target angle in degrees * @return a value in degrees, in the [-180, 180[ range */ public static float difference(final float from, final float to) { diff --git a/main/src/cgeo/geocaching/utils/CryptUtils.java b/main/src/cgeo/geocaching/utils/CryptUtils.java index f04327e..df2baa0 100644 --- a/main/src/cgeo/geocaching/utils/CryptUtils.java +++ b/main/src/cgeo/geocaching/utils/CryptUtils.java @@ -46,16 +46,14 @@ public final class CryptUtils { boolean plaintext = false; final int length = text.length(); - int c; - int capitalized; for (int index = 0; index < length; index++) { - c = text.charAt(index); + int c = text.charAt(index); if (c == '[') { plaintext = true; } else if (c == ']') { plaintext = false; } else if (!plaintext) { - capitalized = c & 32; + int capitalized = c & 32; c &= ~capitalized; c = ((c >= 'A') && (c <= 'Z') ? ((c - 'A' + 13) % 26 + 'A') : c) | capitalized; @@ -73,7 +71,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.toString()); + Log.e("cgBase.md5", e); } return hashed; @@ -87,7 +85,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.toString()); + Log.e("cgBase.sha1", e); } return hashed; @@ -102,7 +100,7 @@ public final class CryptUtils { mac.init(secretKeySpec); macBytes = mac.doFinal(text.getBytes()); } catch (Exception e) { - Log.e("cgBase.hashHmac: " + e.toString()); + Log.e("cgBase.hashHmac", e); } return macBytes; @@ -116,16 +114,14 @@ public final class CryptUtils { boolean plaintext = false; final int length = span.length(); - int c; - int capitalized; for (int index = 0; index < length; index++) { - c = span.charAt(index); + int c = span.charAt(index); if (c == '[') { plaintext = true; } else if (c == ']') { plaintext = false; } else if (!plaintext) { - capitalized = c & 32; + int capitalized = c & 32; c &= ~capitalized; c = ((c >= 'A') && (c <= 'Z') ? ((c - 'A' + 13) % 26 + 'A') : c) | capitalized; diff --git a/main/src/cgeo/geocaching/utils/HtmlUtils.java b/main/src/cgeo/geocaching/utils/HtmlUtils.java index a54ba57..30aa19b 100644 --- a/main/src/cgeo/geocaching/utils/HtmlUtils.java +++ b/main/src/cgeo/geocaching/utils/HtmlUtils.java @@ -53,4 +53,29 @@ public class HtmlUtils { return StringUtils.replace(result, "<br />", "\n").trim(); } + /** + * Convert any non-Latin characters into HTML unicode entities + * + * @param input + * String + * @return output String + */ + public static String convertNonLatinCharactersToHTML(final String input) { + final int inputLen = input.length(); + final StringBuilder output = new StringBuilder(); + + for (int i = 0; i < inputLen; i++) { + char c = input.charAt(i); + + if (c > 300) { + output.append("&#"); + output.append(Integer.toString(c)); + output.append(';'); + } else { + output.append(c); + } + } + + return output.toString(); + } } diff --git a/main/src/cgeo/geocaching/utils/IOUtils.java b/main/src/cgeo/geocaching/utils/IOUtils.java new file mode 100644 index 0000000..df90da3 --- /dev/null +++ b/main/src/cgeo/geocaching/utils/IOUtils.java @@ -0,0 +1,22 @@ +package cgeo.geocaching.utils; + +import java.io.Closeable; +import java.io.IOException; + +final public class IOUtils { + + private IOUtils() { + // utility class + } + + public static void closeQuietly(final Closeable closeable) { + if (closeable != null) { + try { + closeable.close(); + } catch (final IOException e) { + Log.w("closeQuietly: unable to close " + closeable, e); + } + } + } + +} diff --git a/main/src/cgeo/geocaching/utils/ImageHelper.java b/main/src/cgeo/geocaching/utils/ImageHelper.java index 4c18b06..98cad64 100644 --- a/main/src/cgeo/geocaching/utils/ImageHelper.java +++ b/main/src/cgeo/geocaching/utils/ImageHelper.java @@ -1,13 +1,12 @@ package cgeo.geocaching.utils; import cgeo.geocaching.cgeoapplication; +import cgeo.geocaching.compatibility.Compatibility; -import android.content.Context; import android.graphics.Bitmap; +import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; -import android.view.Display; -import android.view.WindowManager; public class ImageHelper { @@ -24,9 +23,9 @@ public class ImageHelper { */ public static BitmapDrawable scaleBitmapToFitDisplay(final Bitmap image) { final cgeoapplication app = cgeoapplication.getInstance(); - final Display display = ((WindowManager) app.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); - final int maxWidth = display.getWidth() - 25; - final int maxHeight = display.getHeight() - 25; + Point displaySize = Compatibility.getDisplaySize(); + final int maxWidth = displaySize.x - 25; + final int maxHeight = displaySize.y - 25; Bitmap result = image; int width = image.getWidth(); diff --git a/main/src/cgeo/geocaching/utils/LazyInitializedList.java b/main/src/cgeo/geocaching/utils/LazyInitializedList.java index 25af811..27649e8 100644 --- a/main/src/cgeo/geocaching/utils/LazyInitializedList.java +++ b/main/src/cgeo/geocaching/utils/LazyInitializedList.java @@ -1,103 +1,63 @@ package cgeo.geocaching.utils; +import java.util.AbstractList; import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; import java.util.List; +import java.util.concurrent.Callable; -public abstract class LazyInitializedList<ElementType> implements Iterable<ElementType> { +public abstract class LazyInitializedList<ElementType> extends AbstractList<ElementType> implements Callable<List<ElementType>> { private volatile List<ElementType> list; - private void initializeList() { + private List<ElementType> getList() { if (list == null) { - synchronized (this) { - if (list == null) { - list = loadFromDatabase(); + synchronized(this) { + try { + list = call(); + if (list == null) { + Log.e("LazyInitializedList.getList: null result"); + } + } catch (final Exception e) { + Log.e("LazyInitializedList.getList", e); } } } + return list; } - protected abstract List<ElementType> loadFromDatabase(); - - public void add(final ElementType element) { - initializeList(); - list.add(element); - } - - public void prepend(final ElementType element) { - initializeList(); - list.add(0, element); - } - - public void set(final List<ElementType> elements) { - if (elements != null) { - list = new ArrayList<ElementType>(elements); - } else { - list = new ArrayList<ElementType>(); - } - } - - public void set(LazyInitializedList<ElementType> other) { - if (other != null) { - list = new ArrayList<ElementType>(other.asList()); - } else { - list = new ArrayList<ElementType>(); - } + @Override + public boolean add(final ElementType element) { + return getList().add(element); } - public boolean isEmpty() { - initializeList(); - return list.isEmpty(); + @Override + public ElementType set(final int index, final ElementType element) { + return getList().set(index, element); } + @Override public ElementType remove(final int index) { - initializeList(); - return list.remove(index); + return getList().remove(index); } + @Override public void add(int index, final ElementType element) { - initializeList(); - list.add(index, element); + getList().add(index, element); } + @Override public int size() { - initializeList(); - return list.size(); + return getList().size(); } @Override - public Iterator<ElementType> iterator() { - initializeList(); - return list.iterator(); - } - public ElementType get(final int index) { - initializeList(); - return list.get(index); + return getList().get(index); } - public boolean contains(final ElementType element) { - initializeList(); - return list.contains(element); - } - - public boolean isNotEmpty() { - initializeList(); - return !list.isEmpty(); - } - - /** - * @return an unmodifiable list of the elements - */ - public List<ElementType> asList() { - initializeList(); - return Collections.unmodifiableList(list); + @Override + public void clear() { + list = new ArrayList<ElementType>(); } - public int indexOf(ElementType element) { - initializeList(); - return list.indexOf(element); - } } diff --git a/main/src/cgeo/geocaching/utils/LeastRecentlyUsedMap.java b/main/src/cgeo/geocaching/utils/LeastRecentlyUsedMap.java index f0bc4f5..6122532 100644 --- a/main/src/cgeo/geocaching/utils/LeastRecentlyUsedMap.java +++ b/main/src/cgeo/geocaching/utils/LeastRecentlyUsedMap.java @@ -16,7 +16,7 @@ import java.util.Map; */ public abstract class LeastRecentlyUsedMap<K, V> extends LinkedHashMap<K, V> { - private static enum OperationModes { + private enum OperationModes { LRU_CACHE, BOUNDED } diff --git a/main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java b/main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java index b654fd6..698d38a 100644 --- a/main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java +++ b/main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java @@ -9,7 +9,7 @@ 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. * Unfortunately HashSet does not allow to use a custom Map as its Storage. * Therefore overriding removeEldestEntry() is impossible for a normal LinkedHashSet. @@ -50,7 +50,7 @@ public class LeastRecentlyUsedSet<E> extends AbstractSet<E> /** * Synchronized access to set size * Copy of the HashSet code if size() - * + * * @see HashSet */ @Override @@ -61,7 +61,7 @@ public class LeastRecentlyUsedSet<E> extends AbstractSet<E> /** * Synchronized check of set emptiness * Copy of the HashSet code if isEmpty() - * + * * @see HashSet */ @Override @@ -72,7 +72,7 @@ public class LeastRecentlyUsedSet<E> extends AbstractSet<E> /** * Synchronized check for containment * Copy of the HashSet code if contains() - * + * * @see HashSet */ @Override @@ -83,18 +83,21 @@ public class LeastRecentlyUsedSet<E> extends AbstractSet<E> /** * Synchronized addition of an item * Copy of the HashSet code if add() - * + * * @see HashSet */ @Override public synchronized boolean add(E e) { + if (e == null) { + throw new IllegalArgumentException("LeastRecentlyUsedSet cannot take null element"); + } return map.put(e, PRESENT) == null; } /** * Synchronized removal of a contained item * Copy of the HashSet code if remove() - * + * * @see HashSet */ @Override @@ -117,7 +120,7 @@ public class LeastRecentlyUsedSet<E> extends AbstractSet<E> /** * Synchronized clearing of the set * Copy of the HashSet code if clear() - * + * * @see HashSet */ @Override @@ -128,12 +131,12 @@ public class LeastRecentlyUsedSet<E> extends AbstractSet<E> /** * (synchronized) Clone of the set * Copy of the HashSet code if clone() - * + * * @see HashSet */ @Override @SuppressWarnings("unchecked") - public Object clone() { + public Object clone() throws CloneNotSupportedException { try { synchronized (this) { final LeastRecentlyUsedSet<E> newSet = (LeastRecentlyUsedSet<E>) super.clone(); diff --git a/main/src/cgeo/geocaching/utils/Log.java b/main/src/cgeo/geocaching/utils/Log.java index 6213661..6d57b75 100644 --- a/main/src/cgeo/geocaching/utils/Log.java +++ b/main/src/cgeo/geocaching/utils/Log.java @@ -1,5 +1,11 @@ package cgeo.geocaching.utils; +import android.os.Environment; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; final public class Log { @@ -9,6 +15,7 @@ final public class Log { * the debug flag is cached here so that we don't need to access the settings every time we have to evaluate it */ private static boolean isDebug = true; + private static boolean first = true; public static boolean isDebug() { return isDebug; @@ -74,4 +81,31 @@ final public class Log { public static void e(final String msg, final Throwable t) { android.util.Log.e(TAG, msg, t); } + + /** + * Log the whole content of a string into "/sdcard/cgeo-debug.log". + * <br/> + * Sometimes, the string we want to work on while debugging or developing a new feature is too long to + * be fully stored in Android logcat. This method will log the content of the string in a file named + * "/sdcard/cgeo-debug.log". The file will be reset at every run, and if called several times during a run, + * the contents will be appended to one another. + * <br/> + * <strong>This method should never be called in production.</strong> + * + * @param msg the message to log, or to add to the log if other messages have been stored in the same run + */ + public synchronized static void logToFile(final String msg) { + final File file = new File(Environment.getExternalStorageDirectory(), "cgeo-debug.log"); + if (first) { + first = false; + file.delete(); + } + try { + final Writer writer = new FileWriter(file, true); + writer.write(msg); + writer.close(); + } catch (final IOException e) { + Log.e("logToFile: cannot write to " + file, e); + } + } } diff --git a/main/src/cgeo/geocaching/utils/LogTemplateProvider.java b/main/src/cgeo/geocaching/utils/LogTemplateProvider.java index 7cacd9d..0a8e547 100644 --- a/main/src/cgeo/geocaching/utils/LogTemplateProvider.java +++ b/main/src/cgeo/geocaching/utils/LogTemplateProvider.java @@ -2,8 +2,8 @@ package cgeo.geocaching.utils; import cgeo.geocaching.R; import cgeo.geocaching.Settings; -import cgeo.geocaching.cgCache; -import cgeo.geocaching.cgTrackable; +import cgeo.geocaching.Trackable; +import cgeo.geocaching.Geocache; import cgeo.geocaching.connector.gc.GCConstants; import cgeo.geocaching.connector.gc.Login; import cgeo.geocaching.network.Network; @@ -28,15 +28,15 @@ public class LogTemplateProvider { * */ public static class LogContext { - private cgCache cache; - private cgTrackable trackable; + private Geocache cache; + private Trackable trackable; private boolean offline = false; - public LogContext(final cgCache cache) { + public LogContext(final Geocache cache) { this(cache, false); } - public LogContext(final cgTrackable trackable) { + public LogContext(final Trackable trackable) { this.trackable = trackable; } @@ -44,16 +44,16 @@ public class LogTemplateProvider { this(null, offline); } - public LogContext(final cgCache cache, boolean offline) { + public LogContext(final Geocache cache, boolean offline) { this.cache = cache; this.offline = offline; } - public cgCache getCache() { + public Geocache getCache() { return cache; } - public cgTrackable getTrackable() { + public Trackable getTrackable() { return trackable; } @@ -148,11 +148,11 @@ public class LogTemplateProvider { @Override public String getValue(final LogContext context) { - cgTrackable trackable = context.getTrackable(); + Trackable trackable = context.getTrackable(); if (trackable != null) { return trackable.getOwner(); } - cgCache cache = context.getCache(); + Geocache cache = context.getCache(); if (cache != null) { return cache.getOwnerDisplayName(); } diff --git a/main/src/cgeo/geocaching/utils/MatcherWrapper.java b/main/src/cgeo/geocaching/utils/MatcherWrapper.java new file mode 100644 index 0000000..c3c1663 --- /dev/null +++ b/main/src/cgeo/geocaching/utils/MatcherWrapper.java @@ -0,0 +1,91 @@ +package cgeo.geocaching.utils; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Wrapper around the regex {@link Matcher} class. This implementation optimizes the memory usage of the matched + * Strings. + * + */ +public class MatcherWrapper { + private final Matcher matcher; + + public MatcherWrapper(Pattern pattern, String input) { + this.matcher = pattern.matcher(input); + } + + /** + * see {@link Matcher#find()} + */ + public boolean find() { + return matcher.find(); + } + + /** + * see {@link Matcher#group(int)} + */ + public String group(int index) { + return newString(matcher.group(index)); + } + + /** + * This method explicitly creates a new String instance from an already existing String. This is necessary to avoid + * huge memory leaks in our parser. If we do regular expression matching on large Strings, the returned matches are + * otherwise memory mapped substrings of the huge original String, therefore blocking the garbage collector from + * removing the huge input String. + * <p> + * Do not change this method, even if Findbugs and other tools will report a violation for that line! + * + * @param input + * @return + */ + private static String newString(String input) { + if (input == null) { + return null; + } + return new String(input); // DON'T REMOVE THE "new String" HERE! + } + + /** + * see {@link Matcher#groupCount()} + */ + public int groupCount() { + return matcher.groupCount(); + } + + /** + * see {@link Matcher#group()} + */ + public String group() { + return newString(matcher.group()); + } + + /** + * see {@link Matcher#start()} + */ + public int start() { + return matcher.start(); + } + + /** + * see {@link Matcher#replaceAll(String)} + */ + public String replaceAll(String replacement) { + return newString(matcher.replaceAll(replacement)); + } + + /** + * see {@link Matcher#matches()} + */ + public boolean matches() { + return matcher.matches(); + } + + /** + * see {@link Matcher#replaceFirst(String)} + */ + public String replaceFirst(String replacement) { + return newString(matcher.replaceFirst(replacement)); + } +} diff --git a/main/src/cgeo/geocaching/utils/PeriodicHandler.java b/main/src/cgeo/geocaching/utils/PeriodicHandler.java index 3f6c345..2759580 100644 --- a/main/src/cgeo/geocaching/utils/PeriodicHandler.java +++ b/main/src/cgeo/geocaching/utils/PeriodicHandler.java @@ -26,7 +26,7 @@ abstract public class PeriodicHandler extends Handler { * @param period * The period in milliseconds. */ - public PeriodicHandler(final long period) { + protected PeriodicHandler(final long period) { this.period = period; } @@ -48,6 +48,9 @@ abstract public class PeriodicHandler extends Handler { case ACT: sendEmptyMessageDelayed(ACT, period); act(); + break; + default: + throw new UnsupportedOperationException(); } } diff --git a/main/src/cgeo/geocaching/utils/TranslationUtils.java b/main/src/cgeo/geocaching/utils/TranslationUtils.java index a29c5a7..4d318f0 100644 --- a/main/src/cgeo/geocaching/utils/TranslationUtils.java +++ b/main/src/cgeo/geocaching/utils/TranslationUtils.java @@ -1,12 +1,11 @@ package cgeo.geocaching.utils; import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.network.Network; import android.content.Intent; import android.net.Uri; -import java.net.URLEncoder; - /** * Utilities used for translating */ @@ -30,7 +29,7 @@ public final class TranslationUtils { * @return URI ready to be parsed */ private static String buildTranslationURI(final String toLang, final String text) { - String content = URLEncoder.encode(text); + String content = Network.encode(text); // the app works better without the "+", the website works better with "+", therefore assume using the app if installed if (ProcessUtils.isInstalled(TRANSLATION_APP)) { content = content.replace("+", "%20"); diff --git a/main/src/cgeo/geocaching/utils/XmlUtils.java b/main/src/cgeo/geocaching/utils/XmlUtils.java new file mode 100644 index 0000000..4e08f42 --- /dev/null +++ b/main/src/cgeo/geocaching/utils/XmlUtils.java @@ -0,0 +1,41 @@ +package cgeo.geocaching.utils; + +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; + +public class XmlUtils { + + private XmlUtils() { + // Do not instantiate + } + + /** + * Insert an attribute-less tag with enclosed text in a XML serializer output. + * + * @param serializer an XML serializer + * @param prefix an XML prefix, see {@link XmlSerializer#startTag(String, String)} + * @param tag an XML tag + * @param text some text to insert + * @throws IOException + */ + public static void simpleText(final XmlSerializer serializer, final String prefix, final String tag, final String text) throws IOException { + serializer.startTag(prefix, tag); + serializer.text(text); + serializer.endTag(prefix, tag); + } + + /** + * Insert pairs of attribute-less tags and enclosed texts in a XML serializer output + * + * @param serializer an XML serializer + * @param prefix an XML prefix, see {@link XmlSerializer#startTag(String, String)} shared by all tags + * @param tagAndText an XML tag, the corresponding text, another XML tag, the corresponding text, … + * @throws IOException + */ + public static void multipleTexts(final XmlSerializer serializer, final String prefix, final String... tagAndText) throws IOException { + for (int i = 0; i < tagAndText.length; i += 2) { + simpleText(serializer, prefix, tagAndText[i], tagAndText[i+1]); + } + } +} diff --git a/main/src/com/viewpagerindicator/TitlePageIndicator.java b/main/src/com/viewpagerindicator/TitlePageIndicator.java index a181fed..94ac962 100644 --- a/main/src/com/viewpagerindicator/TitlePageIndicator.java +++ b/main/src/com/viewpagerindicator/TitlePageIndicator.java @@ -65,7 +65,7 @@ public class TitlePageIndicator extends View implements PageIndicator { public final int value; - private IndicatorStyle(int value) { + IndicatorStyle(int value) { this.value = value; } @@ -686,15 +686,13 @@ public class TitlePageIndicator extends View implements PageIndicator { * @return The width of the view, honoring constraints from measureSpec */ private int measureWidth(int measureSpec) { - int result; 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."); } - result = specSize; - return result; + return specSize; } /** diff --git a/main/src/gnu/android/app/appmanualclient/AppManualReaderClient.java b/main/src/gnu/android/app/appmanualclient/AppManualReaderClient.java index 28950b9..af4c03e 100644 --- a/main/src/gnu/android/app/appmanualclient/AppManualReaderClient.java +++ b/main/src/gnu/android/app/appmanualclient/AppManualReaderClient.java @@ -233,9 +233,8 @@ public class AppManualReaderClient { // manuals this doesn't matter, as the user then can choose which // one to use on a single or permanent basis. // - String logTag = "appmanualclient"; - for ( ;; ) { - Uri uri = Uri.parse(URI_SCHEME_APPMANUAL + "://" + manualIdentifier + 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); @@ -300,7 +299,8 @@ public class AppManualReaderClient { | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); } try { - if ( Log.isLoggable(logTag, Log.INFO) ) { + String logTag = "appmanualclient"; + if ( Log.isLoggable(logTag, Log.INFO) ) { Log.i(logTag, "Trying to activate manual: uri=" + uri.toString()); } diff --git a/main/src/org/openintents/intents/FileManagerIntents.java b/main/src/org/openintents/intents/FileManagerIntents.java new file mode 100644 index 0000000..8ff10c8 --- /dev/null +++ b/main/src/org/openintents/intents/FileManagerIntents.java @@ -0,0 +1,127 @@ +/*
+ * 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";
+}
|
