diff options
Diffstat (limited to 'main/src')
139 files changed, 1757 insertions, 1779 deletions
diff --git a/main/src/cgeo/calendar/CalendarAddon.java b/main/src/cgeo/calendar/CalendarAddon.java new file mode 100644 index 0000000..117fb9a --- /dev/null +++ b/main/src/cgeo/calendar/CalendarAddon.java @@ -0,0 +1,58 @@ +package cgeo.calendar; + +import cgeo.geocaching.Geocache; +import cgeo.geocaching.R; +import cgeo.geocaching.geopoint.GeopointFormatter; +import cgeo.geocaching.network.Parameters; +import cgeo.geocaching.ui.dialog.Dialogs; +import cgeo.geocaching.utils.ProcessUtils; + +import org.apache.commons.lang3.StringUtils; + +import android.app.Activity; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.Resources; +import android.net.Uri; + +import java.util.Date; + +public class CalendarAddon { + public static boolean isAvailable() { + return ProcessUtils.isIntentAvailable(ICalendar.INTENT, Uri.parse(ICalendar.URI_SCHEME + "://" + ICalendar.URI_HOST)); + } + + public static void addToCalendarWithIntent(final Activity activity, final Geocache cache) { + final Resources res = activity.getResources(); + if (CalendarAddon.isAvailable()) { + final Date hiddenDate = cache.getHiddenDate(); + final Parameters params = new Parameters( + ICalendar.PARAM_NAME, cache.getName(), + ICalendar.PARAM_NOTE, StringUtils.defaultString(cache.getPersonalNote()), + ICalendar.PARAM_HIDDEN_DATE, hiddenDate != null ? String.valueOf(hiddenDate.getTime()) : StringUtils.EMPTY, + ICalendar.PARAM_URL, StringUtils.defaultString(cache.getUrl()), + ICalendar.PARAM_COORDS, cache.getCoords() == null ? "" : cache.getCoords().format(GeopointFormatter.Format.LAT_LON_DECMINUTE_RAW), + ICalendar.PARAM_LOCATION, StringUtils.defaultString(cache.getLocation()), + ICalendar.PARAM_SHORT_DESC, StringUtils.defaultString(cache.getShortDescription()), + ICalendar.PARAM_START_TIME_MINUTES, StringUtils.defaultString(cache.guessEventTimeMinutes()) + ); + + activity.startActivity(new Intent(ICalendar.INTENT, + Uri.parse(ICalendar.URI_SCHEME + "://" + ICalendar.URI_HOST + "?" + params.toString()))); + } else { + // Inform user the calendar add-on is not installed and let them get it from Google Play + Dialogs.confirmYesNo(activity, R.string.addon_missing_title, new StringBuilder(res.getString(R.string.helper_calendar_missing)) + .append(' ') + .append(res.getString(R.string.addon_download_prompt)) + .toString(), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + final Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(ICalendar.CALENDAR_ADDON_URI)); + activity.startActivity(intent); + } + }); + } + } + +} diff --git a/main/src/cgeo/calendar/ICalendar.java b/main/src/cgeo/calendar/ICalendar.java index 933d248..6ecb6d5 100644 --- a/main/src/cgeo/calendar/ICalendar.java +++ b/main/src/cgeo/calendar/ICalendar.java @@ -14,6 +14,6 @@ public interface ICalendar { static final String PARAM_NOTE = "note"; // personal note static final String PARAM_NAME = "name"; // cache name static final String PARAM_LOCATION = "location"; // cache location, or empty string - static final String PARAM_COORDS = "coords"; // cache coords, or empty string + static final String PARAM_COORDS = "coords"; // cache coordinates, or empty string static final String PARAM_START_TIME_MINUTES = "time"; // time of start } diff --git a/main/src/cgeo/contacts/IContacts.java b/main/src/cgeo/contacts/IContacts.java index d46a5a4..d68b78a 100644 --- a/main/src/cgeo/contacts/IContacts.java +++ b/main/src/cgeo/contacts/IContacts.java @@ -1,8 +1,6 @@ package cgeo.contacts; public interface IContacts { - static final String CALENDAR_ADDON_URI = "market://details?id=cgeo.contacts"; - static final String INTENT = "cgeo.contacts.FIND"; static final String URI_SCHEME = "find"; diff --git a/main/src/cgeo/geocaching/AbstractPopupActivity.java b/main/src/cgeo/geocaching/AbstractPopupActivity.java index 5f24030..38e37da 100644 --- a/main/src/cgeo/geocaching/AbstractPopupActivity.java +++ b/main/src/cgeo/geocaching/AbstractPopupActivity.java @@ -40,7 +40,7 @@ public abstract class AbstractPopupActivity extends AbstractActivity implements private final GeoDirHandler geoUpdate = new GeoDirHandler() { @Override - protected void updateGeoData(final IGeoData geo) { + public void updateGeoData(final IGeoData geo) { try { if (geo.getCoords() != null && cache != null && cache.getCoords() != null) { cacheDistance.setText(Units.getDistanceFromKilometers(geo.getCoords().distanceTo(cache.getCoords()))); diff --git a/main/src/cgeo/geocaching/CacheDetailActivity.java b/main/src/cgeo/geocaching/CacheDetailActivity.java index 4068e38..6962317 100644 --- a/main/src/cgeo/geocaching/CacheDetailActivity.java +++ b/main/src/cgeo/geocaching/CacheDetailActivity.java @@ -3,6 +3,7 @@ package cgeo.geocaching; import butterknife.ButterKnife; import butterknife.InjectView; +import cgeo.calendar.CalendarAddon; import cgeo.geocaching.activity.AbstractActivity; import cgeo.geocaching.activity.AbstractViewPagerActivity; import cgeo.geocaching.activity.Progress; @@ -45,7 +46,6 @@ import cgeo.geocaching.utils.HtmlUtils; import cgeo.geocaching.utils.ImageUtils; import cgeo.geocaching.utils.Log; import cgeo.geocaching.utils.MatcherWrapper; -import cgeo.geocaching.utils.RunnableWithArgument; import cgeo.geocaching.utils.SimpleCancellableHandler; import cgeo.geocaching.utils.SimpleHandler; import cgeo.geocaching.utils.TextUtils; @@ -57,6 +57,14 @@ 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 rx.Observable; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Subscription; +import rx.android.observables.AndroidObservable; +import rx.concurrency.Schedulers; +import rx.subscriptions.Subscriptions; +import rx.util.functions.Action1; import android.R.color; import android.app.AlertDialog; @@ -80,7 +88,6 @@ import android.text.Editable; import android.text.Html; import android.text.Spannable; import android.text.Spanned; -import android.text.format.DateUtils; import android.text.style.ForegroundColorSpan; import android.text.style.StrikethroughSpan; import android.text.style.StyleSpan; @@ -109,7 +116,6 @@ import android.widget.TextView.BufferType; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; -import java.util.Date; import java.util.EnumSet; import java.util.List; import java.util.Locale; @@ -176,24 +182,15 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc // set title in code, as the activity needs a hard coded title due to the intent filters setTitle(res.getString(R.string.cache)); - String geocode = null; - - // 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) { - cache = search.getFirstCacheFromResult(LoadFlags.LOAD_ALL_DB_ONLY); - if (cache != null && cache.getGeocode() != null) { - geocode = cache.getGeocode(); - } - } - // get parameters final Bundle extras = getIntent().getExtras(); final Uri uri = getIntent().getData(); // try to get data from extras String name = null; + String geocode = null; String guid = null; - if (geocode == null && extras != null) { + if (extras != null) { geocode = extras.getString(Intents.EXTRA_GEOCODE); name = extras.getString(Intents.EXTRA_NAME); guid = extras.getString(Intents.EXTRA_GUID); @@ -327,6 +324,14 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } @Override + public void onDestroy() { + if (imagesList != null) { + imagesList.removeAllViews(); + } + super.onDestroy(); + } + + @Override public void onStop() { if (cache != null) { cache.setChangeNotificationHandler(null); @@ -382,6 +387,12 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc clickedItemText = ((TextView) view).getText(); buildDetailsContextMenu(menu, res.getString(R.string.cache_logs), false); break; + case R.id.date: // event date + assert view instanceof TextView; + clickedItemText = ((TextView) view).getText(); + buildDetailsContextMenu(menu, res.getString(R.string.cache_event), true); + menu.findItem(R.id.menu_calendar).setVisible(cache.canBeAddedToCalendar()); + break; case R.id.waypoint: menu.setHeaderTitle(selectedWaypoint.getName() + " (" + res.getString(R.string.waypoint) + ")"); getMenuInflater().inflate(R.menu.waypoint_options, menu); @@ -484,6 +495,9 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc new ResetCoordsThread(cache, handler, selectedWaypoint, true, false, progressDialog).start(); } return true; + case R.id.menu_calendar: + CalendarAddon.addToCalendarWithIntent(this, cache); + return true; default: break; } @@ -542,20 +556,19 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc if (UPDATE_LOAD_PROGRESS_DETAIL == msg.what && msg.obj instanceof String) { updateStatusMsg((String) msg.obj); } else { - CacheDetailActivity activity = ((CacheDetailActivity) activityRef.get()); + final CacheDetailActivity activity = ((CacheDetailActivity) activityRef.get()); if (activity == null) { return; } - SearchResult search = activity.getSearch(); - if (search == null) { + if (activity.search == null) { showToast(R.string.err_dwld_details_failed); dismissProgress(); finishActivity(); return; } - if (search.getError() != null) { - activity.showToast(activity.getResources().getString(R.string.err_dwld_details_failed) + " " + search.getError().getErrorString(activity.getResources()) + "."); + if (activity.search.getError() != null) { + activity.showToast(activity.getResources().getString(R.string.err_dwld_details_failed) + " " + activity.search.getError().getErrorString(activity.getResources()) + "."); dismissProgress(); finishActivity(); return; @@ -972,17 +985,10 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc ownerView.setOnClickListener(new OwnerActionsClickListener(cache)); } - // cache hidden - final Date hiddenDate = cache.getHiddenDate(); - if (hiddenDate != null) { - final long time = hiddenDate.getTime(); - if (time > 0) { - String dateString = Formatter.formatFullDate(time); - if (cache.isEventCache()) { - dateString = DateUtils.formatDateTime(CgeoApplication.getInstance().getBaseContext(), time, DateUtils.FORMAT_SHOW_WEEKDAY) + ", " + dateString; - } - details.add(cache.isEventCache() ? R.string.cache_event : R.string.cache_hidden, dateString); - } + // hidden or event date + final TextView hiddenView = details.addHiddenDate(cache); + if (hiddenView != null) { + registerForContextMenu(hiddenView); } // cache location @@ -1051,9 +1057,9 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc if (Settings.getChooseList()) { // let user select list to store cache in new StoredList.UserInterface(CacheDetailActivity.this).promptForListSelection(R.string.list_title, - new RunnableWithArgument<Integer>() { + new Action1<Integer>() { @Override - public void run(final Integer selectedListId) { + public void call(final Integer selectedListId) { storeCache(selectedListId, new StoreCacheHandler(CacheDetailActivity.this, progress)); } }, true, StoredList.TEMPORARY_LIST_ID); @@ -1302,9 +1308,9 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc @Override public void onClick(View view) { new StoredList.UserInterface(CacheDetailActivity.this).promptForListSelection(R.string.list_title, - new RunnableWithArgument<Integer>() { + new Action1<Integer>() { @Override - public void run(final Integer selectedListId) { + public void call(final Integer selectedListId) { switchListById(selectedListId); } }, true, cache.getListId()); @@ -1368,7 +1374,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc final LinearLayout layout = (LinearLayout) view.findViewById(R.id.favpoint_box); final boolean supportsFavoritePoints = cache.supportsFavoritePoints(); layout.setVisibility(supportsFavoritePoints ? View.VISIBLE : View.GONE); - if (!supportsFavoritePoints || cache.isOwner() || !Settings.isPremiumMember()) { + if (!supportsFavoritePoints || cache.isOwner() || !Settings.isGCPremiumMember()) { return; } final Button buttonAdd = (Button) view.findViewById(R.id.add_to_favpoint); @@ -1470,6 +1476,10 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc protected class DescriptionViewCreator extends AbstractCachingPageViewCreator<ScrollView> { @InjectView(R.id.personalnote) protected TextView personalNoteView; + @InjectView(R.id.shortdesc) protected IndexOutOfBoundsAvoidingTextView shortDescView; + @InjectView(R.id.longdesc) protected IndexOutOfBoundsAvoidingTextView longDescView; + @InjectView(R.id.show_description) protected Button showDesc; + @InjectView(R.id.loading) protected View loadingView; @Override public ScrollView getDispatchedView() { @@ -1483,7 +1493,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc // cache short description if (StringUtils.isNotBlank(cache.getShortDescription())) { - new LoadDescriptionTask(cache.getShortDescription(), view.findViewById(R.id.shortdesc), null, null).execute(); + loadDescription(cache.getShortDescription(), shortDescView, null); } // long description @@ -1491,7 +1501,6 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc if (Settings.isAutoLoadDescription()) { loadLongDescription(); } else { - final Button showDesc = (Button) view.findViewById(R.id.show_description); showDesc.setVisibility(View.VISIBLE); showDesc.setOnClickListener(new View.OnClickListener() { @Override @@ -1605,12 +1614,23 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } private void loadLongDescription() { - final Button showDesc = (Button) view.findViewById(R.id.show_description); showDesc.setVisibility(View.GONE); showDesc.setOnClickListener(null); view.findViewById(R.id.loading).setVisibility(View.VISIBLE); - new LoadDescriptionTask(cache.getDescription(), view.findViewById(R.id.longdesc), view.findViewById(R.id.loading), view.findViewById(R.id.shortdesc)).execute(); + final String longDescription = cache.getDescription(); + loadDescription(longDescription, longDescView, loadingView); + + // Hide the short description, if it is contained somewhere at the start of the long description. + if (shortDescView != null) { + final String shortDescription = cache.getShortDescription(); + if (StringUtils.isNotBlank(shortDescription)) { + final int index = longDescription.indexOf(shortDescription); + if (index >= 0 && index < 200) { + shortDescView.setVisibility(View.GONE); + } + } + } } private void warnPersonalNoteNeedsStoring() { @@ -1641,148 +1661,119 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } - /** - * Loads the description in background. <br /> - * <br /> - * Params: - * <ol> - * <li>description string (String)</li> - * <li>target description view (TextView)</li> - * <li>loading indicator view (View, may be null)</li> - * </ol> - */ - private class LoadDescriptionTask extends AsyncTask<Object, Void, Void> { - private final View loadingIndicatorView; - private final IndexOutOfBoundsAvoidingTextView descriptionView; - private final String descriptionString; - private Spanned description; - private final View shortDescView; + /** + * Load the description in the background. + * @param descriptionString the HTML description as retrieved from the connector + * @param descriptionView the view to fill + * @param loadingIndicatorView the loading indicator view, will be hidden when completed + */ + private void loadDescription(final String descriptionString, final IndexOutOfBoundsAvoidingTextView descriptionView, final View loadingIndicatorView) { + // The producer produces successives (without then with images) versions of the description. + final Observable<Spanned> producer = Observable.create(new OnSubscribeFunc<Spanned>() { + @Override + public Subscription onSubscribe(final Observer<? super Spanned> observer) { + try { + // Fast preview: parse only HTML without loading any images + final HtmlImageCounter imageCounter = new HtmlImageCounter(); + final UnknownTagsHandler unknownTagsHandler = new UnknownTagsHandler(); + Spanned description = Html.fromHtml(descriptionString, imageCounter, unknownTagsHandler); + addWarning(unknownTagsHandler, description); + observer.onNext(description); + + if (imageCounter.getImageCount() > 0) { + // Complete view: parse again with loading images - if necessary ! If there are any images causing problems the user can see at least the preview + description = Html.fromHtml(descriptionString, new HtmlImage(cache.getGeocode(), true, cache.getListId(), false), unknownTagsHandler); + addWarning(unknownTagsHandler, description); + observer.onNext(description); + } - public LoadDescriptionTask(final String description, final View descriptionView, final View loadingIndicatorView, final View shortDescView) { - assert descriptionView instanceof IndexOutOfBoundsAvoidingTextView; - this.descriptionString = description; - this.descriptionView = (IndexOutOfBoundsAvoidingTextView) descriptionView; - this.loadingIndicatorView = loadingIndicatorView; - this.shortDescView = shortDescView; - } + observer.onCompleted(); + } catch (final Exception e) { + Log.e("loadDescription", e); + observer.onError(e); + } + return Subscriptions.empty(); + } - @Override - protected Void doInBackground(Object... params) { - try { - // Fast preview: parse only HTML without loading any images - final HtmlImageCounter imageCounter = new HtmlImageCounter(); - final UnknownTagsHandler unknownTagsHandler = new UnknownTagsHandler(); - description = Html.fromHtml(descriptionString, imageCounter, unknownTagsHandler); - publishProgress(); - - boolean needsRefresh = false; - if (imageCounter.getImageCount() > 0) { - // Complete view: parse again with loading images - if necessary ! If there are any images causing problems the user can see at least the preview - description = Html.fromHtml(descriptionString, new HtmlImage(cache.getGeocode(), true, cache.getListId(), false), unknownTagsHandler); - needsRefresh = true; - } - - // If description has an HTML construct which may be problematic to render, add a note at the end of the long description. - // Technically, it may not be a table, but a pre, which has the same problems as a table, so the message is ok even though - // sometimes technically incorrect. - if (unknownTagsHandler.isProblematicDetected() && descriptionView != null) { + // If description has an HTML construct which may be problematic to render, add a note at the end of the long description. + // Technically, it may not be a table, but a pre, which has the same problems as a table, so the message is ok even though + // sometimes technically incorrect. + private void addWarning(final UnknownTagsHandler unknownTagsHandler, final Spanned description) { + if (unknownTagsHandler.isProblematicDetected()) { final int startPos = description.length(); 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); - needsRefresh = true; - } - - if (needsRefresh) { - publishProgress(); - } - } catch (final Exception e) { - Log.e("LoadDescriptionTask: ", e); - } - return null; - } - - @Override - protected void onProgressUpdate(Void... values) { - if (description == null) { - showToast(res.getString(R.string.err_load_descr_failed)); - return; - } - if (StringUtils.isNotBlank(descriptionString)) { - try { - descriptionView.setText(description, TextView.BufferType.SPANNABLE); - } catch (final Exception e) { - // On 4.1, there is sometimes a crash on measuring the layout: https://code.google.com/p/android/issues/detail?id=35412 - Log.e("Android bug setting text: ", e); - // remove the formatting by converting to a simple string - descriptionView.setText(description.toString()); } - descriptionView.setMovementMethod(AnchorAwareLinkMovementMethod.getInstance()); - fixTextColor(descriptionView, descriptionString); - descriptionView.setVisibility(View.VISIBLE); - registerForContextMenu(descriptionView); - - hideDuplicatedShortDescription(); } - } + }); - /** - * Hide the short description, if it is contained somewhere at the start of the long description. - */ - private void hideDuplicatedShortDescription() { - if (shortDescView != null) { - final String shortDescription = cache.getShortDescription(); - if (StringUtils.isNotBlank(shortDescription)) { - final int index = descriptionString.indexOf(shortDescription); - if (index >= 0 && index < 200) { - shortDescView.setVisibility(View.GONE); + AndroidObservable.fromActivity(this, producer.subscribeOn(Schedulers.threadPoolForIO())) + .subscribe(new Observer<Spanned>() { + @Override + public void onCompleted() { + if (null != loadingIndicatorView) { + loadingIndicatorView.setVisibility(View.GONE); + } } - } - } - } - @Override - protected void onPostExecute(Void result) { - if (null != loadingIndicatorView) { - loadingIndicatorView.setVisibility(View.GONE); - } - } + @Override + public void onError(final Throwable throwable) { + showToast(res.getString(R.string.err_load_descr_failed)); + } - /** - * Handle caches with black font color in dark skin and white font color in light skin - * by changing background color of the view - * - * @param view - * containing the text - * @param text - * to be checked - */ - private void fixTextColor(final TextView view, final String text) { - int backcolor; - if (Settings.isLightSkin()) { - backcolor = color.white; - - for (final Pattern pattern : LIGHT_COLOR_PATTERNS) { - final MatcherWrapper matcher = new MatcherWrapper(pattern, text); - if (matcher.find()) { - view.setBackgroundResource(color.darker_gray); - return; + @Override + public void onNext(final Spanned description) { + if (StringUtils.isNotBlank(descriptionString)) { + try { + descriptionView.setText(description, TextView.BufferType.SPANNABLE); + } catch (final Exception e) { + // On 4.1, there is sometimes a crash on measuring the layout: https://code.google.com/p/android/issues/detail?id=35412 + Log.e("Android bug setting text: ", e); + // remove the formatting by converting to a simple string + descriptionView.setText(description.toString()); + } + descriptionView.setMovementMethod(AnchorAwareLinkMovementMethod.getInstance()); + fixTextColor(descriptionString); + descriptionView.setVisibility(View.VISIBLE); + registerForContextMenu(descriptionView); + } } - } - } else { - backcolor = color.black; - for (final Pattern pattern : DARK_COLOR_PATTERNS) { - final MatcherWrapper matcher = new MatcherWrapper(pattern, text); - if (matcher.find()) { - view.setBackgroundResource(color.darker_gray); - return; + /** + * Handle caches with black font color in dark skin and white font color in light skin + * by changing background color of the view + * + * @param text + * to be checked + */ + private void fixTextColor(final String text) { + int backcolor; + if (Settings.isLightSkin()) { + backcolor = color.white; + + for (final Pattern pattern : LIGHT_COLOR_PATTERNS) { + final MatcherWrapper matcher = new MatcherWrapper(pattern, text); + if (matcher.find()) { + descriptionView.setBackgroundResource(color.darker_gray); + return; + } + } + } else { + backcolor = color.black; + + for (final Pattern pattern : DARK_COLOR_PATTERNS) { + final MatcherWrapper matcher = new MatcherWrapper(pattern, text); + if (matcher.find()) { + descriptionView.setBackgroundResource(color.darker_gray); + return; + } + } + } + descriptionView.setBackgroundResource(backcolor); } - } - } - view.setBackgroundResource(backcolor); - } + }); } private class WaypointsViewCreator extends AbstractCachingPageViewCreator<ListView> { @@ -2255,10 +2246,6 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc return cache; } - public SearchResult getSearch() { - return search; - } - private static class StoreCacheHandler extends SimpleCancellableHandler { public StoreCacheHandler(CacheDetailActivity activity, Progress progress) { diff --git a/main/src/cgeo/geocaching/CacheListActivity.java b/main/src/cgeo/geocaching/CacheListActivity.java index cc8b178..b581411 100644 --- a/main/src/cgeo/geocaching/CacheListActivity.java +++ b/main/src/cgeo/geocaching/CacheListActivity.java @@ -18,11 +18,13 @@ import cgeo.geocaching.files.GPXImporter; import cgeo.geocaching.filter.FilterUserInterface; import cgeo.geocaching.filter.IFilter; import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.list.PseudoList; import cgeo.geocaching.list.StoredList; import cgeo.geocaching.loaders.AbstractSearchLoader; import cgeo.geocaching.loaders.AbstractSearchLoader.CacheListLoaderType; import cgeo.geocaching.loaders.AddressGeocacheListLoader; import cgeo.geocaching.loaders.CoordsGeocacheListLoader; +import cgeo.geocaching.loaders.FinderGeocacheListLoader; import cgeo.geocaching.loaders.HistoryGeocacheListLoader; import cgeo.geocaching.loaders.KeywordGeocacheListLoader; import cgeo.geocaching.loaders.NextPageGeocacheListLoader; @@ -30,7 +32,6 @@ import cgeo.geocaching.loaders.OfflineGeocacheListLoader; import cgeo.geocaching.loaders.OwnerGeocacheListLoader; import cgeo.geocaching.loaders.PocketGeocacheListLoader; import cgeo.geocaching.loaders.RemoveFromHistoryLoader; -import cgeo.geocaching.loaders.FinderGeocacheListLoader; import cgeo.geocaching.maps.CGeoMap; import cgeo.geocaching.network.Cookies; import cgeo.geocaching.network.Network; @@ -43,10 +44,10 @@ import cgeo.geocaching.ui.LoggingUI; import cgeo.geocaching.ui.WeakReferenceHandler; import cgeo.geocaching.ui.dialog.Dialogs; import cgeo.geocaching.utils.AsyncTaskWithProgress; +import cgeo.geocaching.utils.CancellableHandler; import cgeo.geocaching.utils.DateUtils; import cgeo.geocaching.utils.GeoDirHandler; import cgeo.geocaching.utils.Log; -import cgeo.geocaching.utils.RunnableWithArgument; import ch.boye.httpclientandroidlib.HttpResponse; @@ -55,6 +56,8 @@ import org.apache.commons.collections4.ListUtils; import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.NonNull; +import rx.util.functions.Action1; + import android.app.Activity; import android.app.AlertDialog; import android.app.ProgressDialog; @@ -93,7 +96,6 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA private static final int MAX_LIST_ITEMS = 1000; private static final int MSG_DONE = -1; - private static final int MSG_CANCEL = -99; private static final int REQUEST_CODE_IMPORT_GPX = 1; @@ -111,8 +113,6 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA private int detailTotal = 0; private int detailProgress = 0; private long detailProgressTime = 0L; - private LoadDetailsThread threadDetails = null; - private LoadFromWebThread threadWeb = null; private int listId = StoredList.TEMPORARY_LIST_ID; // Only meaningful for the OFFLINE type private final GeoDirHandler geoDirHandler = new GeoDirHandler() { @@ -267,10 +267,10 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } } - private final Handler loadDetailsHandler = new Handler() { + private final CancellableHandler loadDetailsHandler = new CancellableHandler() { @Override - public void handleMessage(Message msg) { + public void handleRegularMessage(Message msg) { setAdapter(); if (msg.what > -1) { @@ -287,10 +287,6 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } else { progress.setMessage(res.getString(R.string.caches_downloading) + " " + minutesRemaining + " " + res.getQuantityString(R.plurals.caches_eta_mins, minutesRemaining)); } - } else if (msg.what == MSG_CANCEL) { - if (threadDetails != null) { - threadDetails.kill(); - } } else { if (search != null) { final Set<Geocache> cacheListTmp = search.getCachesFromSearchResult(LoadFlags.LOAD_CACHE_OR_DB); @@ -304,13 +300,6 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA showProgress(false); progress.dismiss(); - - if (!isPaused()) { - // If the current activity has been paused, then we do not want to fiddle with the - // GPS and direction states. If the activity later gets resumed, its onResume() - // function will take care of turning the GPS back on. - startGeoAndDir(); - } } } }; @@ -318,9 +307,9 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA /** * TODO Possibly parts should be a Thread not a Handler */ - private final Handler downloadFromWebHandler = new Handler() { + private final CancellableHandler downloadFromWebHandler = new CancellableHandler() { @Override - public void handleMessage(Message msg) { + public void handleRegularMessage(Message msg) { setAdapter(); adapter.notifyDataSetChanged(); @@ -340,10 +329,6 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA progress.dismiss(); showToast(res.getString(R.string.sendToCgeo_no_registration)); finish(); - } else if (msg.what == MSG_CANCEL) { - if (threadWeb != null) { - threadWeb.kill(); - } } else { adapter.setSelectMode(false); @@ -353,19 +338,17 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } } }; - private final Handler clearOfflineLogsHandler = new Handler() { + private final CancellableHandler clearOfflineLogsHandler = new CancellableHandler() { @Override - public void handleMessage(Message msg) { - if (msg.what != MSG_CANCEL) { - adapter.setSelectMode(false); + public void handleRegularMessage(Message msg) { + adapter.setSelectMode(false); - refreshCurrentList(); + refreshCurrentList(); - replaceCacheListFromSearch(); + replaceCacheListFromSearch(); - progress.dismiss(); - } + progress.dismiss(); } }; @@ -402,7 +385,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA if (isInvokedFromAttachment()) { type = CacheListType.OFFLINE; if (coords == null) { - coords = new Geopoint(0.0, 0.0); + coords = Geopoint.ZERO; } } @@ -422,7 +405,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA prepareFilterBar(); - currentLoader = (AbstractSearchLoader) getSupportLoaderManager().initLoader(type.ordinal(), extras, this); + currentLoader = (AbstractSearchLoader) getSupportLoaderManager().initLoader(type.getLoaderId(), extras, this); // init if (CollectionUtils.isNotEmpty(cacheList)) { @@ -458,10 +441,10 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } private void importGpxAttachement() { - new StoredList.UserInterface(this).promptForListSelection(R.string.gpx_import_select_list_title, new RunnableWithArgument<Integer>() { + new StoredList.UserInterface(this).promptForListSelection(R.string.gpx_import_select_list_title, new Action1<Integer>() { @Override - public void run(Integer listId) { + public void call(Integer listId) { new GPXImporter(CacheListActivity.this, listId, importGpxAttachementFinishedHandler).importGPX(); switchListById(listId); } @@ -472,12 +455,15 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA public void onResume() { super.onResume(); - startGeoAndDir(); + geoDirHandler.startGeo(); + if (Settings.isLiveMap()) { + geoDirHandler.startDir(); + } adapter.setSelectMode(false); setAdapterCurrentCoordinates(true); - if (loadCachesHandler != null && search != null) { + if (search != null) { replaceCacheListFromSearch(); loadCachesHandler.sendEmptyMessage(0); } @@ -503,8 +489,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA @Override public void onPause() { - removeGeoAndDir(); - + geoDirHandler.stopGeoAndDir(); super.onPause(); } @@ -675,9 +660,9 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA return true; case R.id.menu_sort: final CacheComparator oldComparator = adapter.getCacheComparator(); - new ComparatorUserInterface(this).selectComparator(oldComparator, new RunnableWithArgument<CacheComparator>() { + new ComparatorUserInterface(this).selectComparator(oldComparator, new Action1<CacheComparator>() { @Override - public void run(CacheComparator selectedComparator) { + public void call(CacheComparator selectedComparator) { // selecting the same sorting twice will toggle the order if (selectedComparator != null && oldComparator != null && selectedComparator.getClass().equals(oldComparator.getClass())) { adapter.toggleInverseSort(); @@ -728,7 +713,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } public void clearOfflineLogs() { - progress.show(this, null, res.getString(R.string.caches_clear_offlinelogs_progress), true, clearOfflineLogsHandler.obtainMessage(MSG_CANCEL)); + progress.show(this, null, res.getString(R.string.caches_clear_offlinelogs_progress), true, clearOfflineLogsHandler.cancelMessage()); new ClearOfflineLogsThread(clearOfflineLogsHandler).start(); } @@ -737,13 +722,12 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA */ @Override public void showFilterMenu(final View view) { - new FilterUserInterface(this).selectFilter(new RunnableWithArgument<IFilter>() { + new FilterUserInterface(this).selectFilter(new Action1<IFilter>() { @Override - public void run(IFilter selectedFilter) { + public void call(IFilter selectedFilter) { if (selectedFilter != null) { setFilter(selectedFilter); - } - else { + } else { // clear filter setFilter(null); } @@ -793,10 +777,10 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } private void moveCachesToOtherList() { - new StoredList.UserInterface(this).promptForListSelection(R.string.cache_menu_move_list, new RunnableWithArgument<Integer>() { + new StoredList.UserInterface(this).promptForListSelection(R.string.cache_menu_move_list, new Action1<Integer>() { @Override - public void run(Integer newListId) { + public void call(Integer newListId) { DataStore.moveToList(adapter.getCheckedOrAllCaches(), newListId); adapter.setSelectMode(false); @@ -850,10 +834,10 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA }); break; case R.id.menu_move_to_list: - new StoredList.UserInterface(this).promptForListSelection(R.string.cache_menu_move_list, new RunnableWithArgument<Integer>() { + new StoredList.UserInterface(this).promptForListSelection(R.string.cache_menu_move_list, new Action1<Integer>() { @Override - public void run(Integer newListId) { + public void call(Integer newListId) { DataStore.moveToList(Collections.singletonList(cache), newListId); adapter.setSelectMode(false); refreshCurrentList(); @@ -960,7 +944,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA boolean enableMore = (type != CacheListType.OFFLINE && cacheList.size() < MAX_LIST_ITEMS); if (enableMore && search != null) { final int count = search.getTotalCountGC(); - enableMore = enableMore && count > 0 && cacheList.size() < count; + enableMore = count > 0 && cacheList.size() < count; } if (enableMore) { @@ -973,17 +957,6 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA listFooter.setClickable(enableMore); } - private void startGeoAndDir() { - geoDirHandler.startGeo(); - if (Settings.isLiveMap()) { - geoDirHandler.startDir(); - } - } - - private void removeGeoAndDir() { - geoDirHandler.stopGeoAndDir(); - } - private void importGpx() { GpxFileListActivity.startSubActivity(this, listId); } @@ -1002,7 +975,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA // provided to this method as a parameter. Pull that uri using "resultData.getData()" if (data != null) { final Uri uri = data.getData(); - new GPXImporter(CacheListActivity.this, listId, importGpxAttachementFinishedHandler).importGPX(uri, null, getDisplayName(uri)); + new GPXImporter(this, listId, importGpxAttachementFinishedHandler).importGPX(uri, null, getDisplayName(uri)); } } @@ -1038,9 +1011,9 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA 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>() { + new Action1<Integer>() { @Override - public void run(final Integer selectedListId) { + public void call(final Integer selectedListId) { refreshStored(caches, selectedListId); } }, true, StoredList.TEMPORARY_LIST_ID, newListName); @@ -1062,12 +1035,12 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA message = res.getString(R.string.caches_downloading) + " " + etaTime + " " + res.getQuantityString(R.plurals.caches_eta_mins, etaTime); } - progress.show(this, null, message, ProgressDialog.STYLE_HORIZONTAL, loadDetailsHandler.obtainMessage(MSG_CANCEL)); + progress.show(this, null, message, ProgressDialog.STYLE_HORIZONTAL, loadDetailsHandler.cancelMessage()); progress.setMaxProgressAndReset(detailTotal); detailProgressTime = System.currentTimeMillis(); - threadDetails = new LoadDetailsThread(loadDetailsHandler, caches, storeListId); + final LoadDetailsThread threadDetails = new LoadDetailsThread(loadDetailsHandler, caches, storeListId); threadDetails.start(); } @@ -1091,16 +1064,16 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } final Bundle b = new Bundle(); b.putStringArray(Intents.EXTRA_CACHELIST, geocodes); - getSupportLoaderManager().initLoader(CacheListLoaderType.REMOVE_FROM_HISTORY.ordinal(), b, this); + getSupportLoaderManager().initLoader(CacheListLoaderType.REMOVE_FROM_HISTORY.getLoaderId(), b, this); } public void importWeb() { detailProgress = 0; showProgress(false); - progress.show(this, null, res.getString(R.string.web_import_waiting), true, downloadFromWebHandler.obtainMessage(MSG_CANCEL)); + progress.show(this, null, res.getString(R.string.web_import_waiting), true, downloadFromWebHandler.cancelMessage()); - threadWeb = new LoadFromWebThread(downloadFromWebHandler, listId); + final LoadFromWebThread threadWeb = new LoadFromWebThread(downloadFromWebHandler, listId); threadWeb.start(); } @@ -1127,26 +1100,20 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA private class LoadDetailsThread extends Thread { - final private Handler handler; + final private CancellableHandler handler; final private int listIdLD; - private volatile boolean needToStop = false; final private List<Geocache> caches; - public LoadDetailsThread(Handler handlerIn, List<Geocache> caches, int listId) { - handler = handlerIn; + public LoadDetailsThread(CancellableHandler handler, List<Geocache> caches, int listId) { + this.handler = handler; 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); } - public void kill() { - needToStop = true; - } - @Override public void run() { - removeGeoAndDir(); // First refresh caches that do not yet have static maps to get them a chance to get a copy // before the limit expires, unless we do not want to store offline maps. final List<Geocache> allCaches = Settings.isStoreOfflineMaps() ? @@ -1173,7 +1140,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA */ private boolean refreshCache(Geocache cache) { try { - if (needToStop) { + if (handler.isCancelled()) { throw new InterruptedException("Stopped storing process."); } detailProgress++; @@ -1190,31 +1157,23 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } } - private class LoadFromWebThread extends Thread { + private static class LoadFromWebThread extends Thread { - final private Handler handler; + final private CancellableHandler handler; final private int listIdLFW; - private volatile boolean needToStop = false; - public LoadFromWebThread(Handler handlerIn, int listId) { - handler = handlerIn; + public LoadFromWebThread(CancellableHandler handler, int listId) { + this.handler = handler; listIdLFW = StoredList.getConcreteList(listId); } - public void kill() { - needToStop = true; - } - @Override public void run() { - - removeGeoAndDir(); - int delay = -1; int times = 0; int ret = MSG_DONE; - while (!needToStop && times < 3 * 60 / 5) { // maximum: 3 minutes, every 5 seconds + while (!handler.isCancelled() && times < 3 * 60 / 5) { // maximum: 3 minutes, every 5 seconds //download new code String deviceCode = Settings.getWebDeviceCode(); if (deviceCode == null) { @@ -1238,7 +1197,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA //Server returned RG (registration) and this device no longer registered. Settings.setWebNameCode(null, null); ret = -3; - needToStop = true; + handler.cancel(); break; } else { delay = 0; @@ -1248,7 +1207,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } if (responseFromWeb == null || responseFromWeb.getStatusLine().getStatusCode() != 200) { ret = -2; - needToStop = true; + handler.cancel(); break; } @@ -1267,8 +1226,6 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } handler.sendEmptyMessage(ret); - - startGeoAndDir(); } } @@ -1283,9 +1240,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA @Override protected Void doInBackgroundInternal(Geocache[] caches) { - removeGeoAndDir(); DataStore.markDropped(Arrays.asList(caches)); - startGeoAndDir(); return null; } @@ -1328,7 +1283,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA showFooterLoadingCaches(); listFooter.setOnClickListener(null); - getSupportLoaderManager().restartLoader(CacheListLoaderType.NEXT_PAGE.ordinal(), null, CacheListActivity.this); + getSupportLoaderManager().restartLoader(CacheListLoaderType.NEXT_PAGE.getLoaderId(), null, CacheListActivity.this); } } @@ -1349,11 +1304,11 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } @NonNull - private RunnableWithArgument<Integer> getListSwitchingRunnable() { - return new RunnableWithArgument<Integer>() { + private Action1<Integer> getListSwitchingRunnable() { + return new Action1<Integer>() { @Override - public void run(final Integer selectedListId) { + public void call(final Integer selectedListId) { switchListById(selectedListId); } }; @@ -1364,6 +1319,12 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA return; } + if (id == PseudoList.HISTORY_LIST.id) { + CacheListActivity.startActivityHistory(this); + finish(); + return; + } + final StoredList list = DataStore.getList(id); if (list == null) { return; @@ -1378,7 +1339,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA showFooterLoadingCaches(); DataStore.moveToList(adapter.getCheckedCaches(), listId); - currentLoader = (OfflineGeocacheListLoader) getSupportLoaderManager().initLoader(CacheListType.OFFLINE.ordinal(), new Bundle(), this); + currentLoader = (OfflineGeocacheListLoader) getSupportLoaderManager().initLoader(CacheListType.OFFLINE.getLoaderId(), new Bundle(), this); currentLoader.reset(); ((OfflineGeocacheListLoader) currentLoader).setListId(listId); ((OfflineGeocacheListLoader) currentLoader).setSearchCenter(coords); @@ -1483,12 +1444,12 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA return true; } - public static void startActivityUserName(final Activity context, final String userName) { + public static void startActivityFinder(final Activity context, final String userName) { if (!isValidUsername(context, userName)) { return; } final Intent cachesIntent = new Intent(context, CacheListActivity.class); - cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.USERNAME); + cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.FINDER); cachesIntent.putExtra(Intents.EXTRA_USERNAME, userName); context.startActivity(cachesIntent); } @@ -1606,6 +1567,9 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } else { final StoredList list = DataStore.getList(listId); // list.id may be different if listId was not valid + if (list.id != listId) { + showToast(getString(R.string.list_not_available)); + } listId = list.id; title = list.title; } @@ -1670,8 +1634,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA break; case POCKET: final String guid = extras.getString(Intents.EXTRA_POCKET_GUID); - final String pocket_name = extras.getString(Intents.EXTRA_NAME); - title = pocket_name; + title = extras.getString(Intents.EXTRA_NAME); loader = new PocketGeocacheListLoader(app, guid); break; } diff --git a/main/src/cgeo/geocaching/CacheMenuHandler.java b/main/src/cgeo/geocaching/CacheMenuHandler.java index 84a08f5..ab11765 100644 --- a/main/src/cgeo/geocaching/CacheMenuHandler.java +++ b/main/src/cgeo/geocaching/CacheMenuHandler.java @@ -1,27 +1,17 @@ package cgeo.geocaching; -import cgeo.calendar.ICalendar; +import cgeo.calendar.CalendarAddon; import cgeo.geocaching.apps.cache.navi.NavigationAppFactory; -import cgeo.geocaching.geopoint.GeopointFormatter; -import cgeo.geocaching.network.Parameters; import cgeo.geocaching.ui.AbstractUIFactory; -import cgeo.geocaching.ui.dialog.Dialogs; -import cgeo.geocaching.utils.ProcessUtils; - -import org.apache.commons.lang3.StringUtils; import android.app.Activity; -import android.content.DialogInterface; -import android.content.Intent; -import android.net.Uri; import android.view.Menu; import android.view.MenuItem; -import java.util.Date; - /** - * Shared menu handling for all activities having menu items related to a cache. - * + * Shared menu handling for all activities having menu items related to a cache. <br> + * TODO: replace by a fragment + * */ public class CacheMenuHandler extends AbstractUIFactory { @@ -58,7 +48,7 @@ public class CacheMenuHandler extends AbstractUIFactory { cache.shareCache(activity, res); return true; case R.id.menu_calendar: - addToCalendarWithIntent(activity, cache); + CalendarAddon.addToCalendarWithIntent(activity, cache); return true; default: return false; @@ -80,39 +70,4 @@ public class CacheMenuHandler extends AbstractUIFactory { activity.getMenuInflater().inflate(R.menu.cache_options, menu); onPrepareOptionsMenu(menu, cache); } - - private static void addToCalendarWithIntent(final Activity activity, final Geocache cache) { - final boolean calendarAddOnAvailable = ProcessUtils.isIntentAvailable(ICalendar.INTENT, Uri.parse(ICalendar.URI_SCHEME + "://" + ICalendar.URI_HOST)); - - if (calendarAddOnAvailable) { - final Date hiddenDate = cache.getHiddenDate(); - final Parameters params = new Parameters( - ICalendar.PARAM_NAME, cache.getName(), - ICalendar.PARAM_NOTE, StringUtils.defaultString(cache.getPersonalNote()), - ICalendar.PARAM_HIDDEN_DATE, hiddenDate != null ? String.valueOf(hiddenDate.getTime()) : StringUtils.EMPTY, - ICalendar.PARAM_URL, StringUtils.defaultString(cache.getUrl()), - ICalendar.PARAM_COORDS, cache.getCoords() == null ? "" : cache.getCoords().format(GeopointFormatter.Format.LAT_LON_DECMINUTE_RAW), - ICalendar.PARAM_LOCATION, StringUtils.defaultString(cache.getLocation()), - ICalendar.PARAM_SHORT_DESC, StringUtils.defaultString(cache.getShortDescription()), - ICalendar.PARAM_START_TIME_MINUTES, StringUtils.defaultString(cache.guessEventTimeMinutes()) - ); - - activity.startActivity(new Intent(ICalendar.INTENT, - Uri.parse(ICalendar.URI_SCHEME + "://" + ICalendar.URI_HOST + "?" + params.toString()))); - } else { - // Inform user the calendar add-on is not installed and let them get it from Google Play - Dialogs.confirmYesNo(activity, R.string.addon_missing_title, new StringBuilder(res.getString(R.string.helper_calendar_missing)) - .append(' ') - .append(res.getString(R.string.addon_download_prompt)) - .toString(), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - final Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(ICalendar.CALENDAR_ADDON_URI)); - activity.startActivity(intent); - } - }); - } - } - } diff --git a/main/src/cgeo/geocaching/CachePopup.java b/main/src/cgeo/geocaching/CachePopup.java index 9186497..5d04ab0 100644 --- a/main/src/cgeo/geocaching/CachePopup.java +++ b/main/src/cgeo/geocaching/CachePopup.java @@ -9,9 +9,9 @@ import cgeo.geocaching.settings.Settings; 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; +import rx.util.functions.Action1; import android.content.Context; import android.content.Intent; @@ -110,9 +110,9 @@ public class CachePopup extends AbstractPopupActivity { if (Settings.getChooseList()) { // let user select list to store cache in new StoredList.UserInterface(CachePopup.this).promptForListSelection(R.string.list_title, - new RunnableWithArgument<Integer>() { + new Action1<Integer>() { @Override - public void run(final Integer selectedListId) { + public void call(final Integer selectedListId) { storeCache(selectedListId); } }, true, StoredList.TEMPORARY_LIST_ID); diff --git a/main/src/cgeo/geocaching/CgeoApplication.java b/main/src/cgeo/geocaching/CgeoApplication.java index 2500d10..7bee97e 100644 --- a/main/src/cgeo/geocaching/CgeoApplication.java +++ b/main/src/cgeo/geocaching/CgeoApplication.java @@ -1,10 +1,10 @@ package cgeo.geocaching; -import cgeo.geocaching.network.StatusUpdater; import cgeo.geocaching.ui.dialog.Dialogs; -import cgeo.geocaching.utils.IObserver; import cgeo.geocaching.utils.Log; +import rx.Observable; + import android.app.Activity; import android.app.Application; import android.app.ProgressDialog; @@ -14,12 +14,11 @@ import java.util.concurrent.atomic.AtomicBoolean; public class CgeoApplication extends Application { - private volatile GeoDataProvider geo; - private volatile DirectionProvider dir; + private volatile Observable<IGeoData> geo; + private volatile Observable<Float> dir; private boolean forceRelog = false; // c:geo needs to log into cache providers public boolean showLoginToast = true; //login toast shown just once. - private boolean liveMapHintShown = false; // livemap hint has been shown - final private StatusUpdater statusUpdater = new StatusUpdater(); + private boolean liveMapHintShownInThisSession = false; // livemap hint has been shown private static CgeoApplication instance; public CgeoApplication() { @@ -35,26 +34,11 @@ public class CgeoApplication extends Application { } @Override - public void onCreate() { - new Thread(statusUpdater).start(); - } - - @Override public void onLowMemory() { Log.i("Cleaning applications cache."); DataStore.removeAllFromCache(); } - @Override - public void onTerminate() { - Log.d("Terminating c:geo…"); - - DataStore.clean(); - DataStore.closeDb(); - - super.onTerminate(); - } - /** * Move the database to/from external cgdata in a new thread, * showing a progress window @@ -82,29 +66,11 @@ public class CgeoApplication extends Application { }.start(); } - /** - * Register an observer to receive GeoData information. - * <br/> - * If there is a chance that no observers are registered before this - * method is called, it is necessary to call it from a task implementing - * a looper interface as the data provider will use listeners that - * require a looper thread to run. - * - * @param observer a geodata observer - */ - public void addGeoObserver(final IObserver<? super IGeoData> observer) { - currentGeoObject().addObserver(observer); - } - - public void deleteGeoObserver(final IObserver<? super IGeoData> observer) { - currentGeoObject().deleteObserver(observer); - } - - private GeoDataProvider currentGeoObject() { + public Observable<IGeoData> currentGeoObject() { if (geo == null) { synchronized(this) { if (geo == null) { - geo = new GeoDataProvider(this); + geo = GeoDataProvider.create(this); } } } @@ -112,22 +78,14 @@ public class CgeoApplication extends Application { } public IGeoData currentGeo() { - return currentGeoObject().getMemory(); + return currentGeoObject().first().toBlockingObservable().single(); } - public void addDirectionObserver(final IObserver<? super Float> observer) { - currentDirObject().addObserver(observer); - } - - public void deleteDirectionObserver(final IObserver<? super Float> observer) { - currentDirObject().deleteObserver(observer); - } - - private DirectionProvider currentDirObject() { + public Observable<Float> currentDirObject() { if (dir == null) { synchronized(this) { if (dir == null) { - dir = new DirectionProvider(this); + dir = DirectionProvider.create(this); } } } @@ -135,19 +93,15 @@ public class CgeoApplication extends Application { } public Float currentDirection() { - return currentDirObject().getMemory(); - } - - public StatusUpdater getStatusUpdater() { - return statusUpdater; + return currentDirObject().first().toBlockingObservable().single(); } - public boolean isLiveMapHintShown() { - return liveMapHintShown; + public boolean isLiveMapHintShownInThisSession() { + return liveMapHintShownInThisSession; } - public void setLiveMapHintShown() { - liveMapHintShown = true; + public void setLiveMapHintShownInThisSession() { + liveMapHintShownInThisSession = true; } /** diff --git a/main/src/cgeo/geocaching/CompassActivity.java b/main/src/cgeo/geocaching/CompassActivity.java index 8955afd..6fc2de1 100644 --- a/main/src/cgeo/geocaching/CompassActivity.java +++ b/main/src/cgeo/geocaching/CompassActivity.java @@ -150,12 +150,13 @@ public class CompassActivity extends AbstractActivity { final CgeoApplication app = CgeoApplication.getInstance(); final IGeoData geo = app.currentGeo(); if (geo != null) { - geoDirHandler.update(geo); + geoDirHandler.updateGeoData(geo); } final Float dir = app.currentDirection(); if (dir != null) { - geoDirHandler.update(dir); + geoDirHandler.updateDirection(dir); } + } @Override diff --git a/main/src/cgeo/geocaching/CreateShortcutActivity.java b/main/src/cgeo/geocaching/CreateShortcutActivity.java index 7b91ba1..0b91da0 100644 --- a/main/src/cgeo/geocaching/CreateShortcutActivity.java +++ b/main/src/cgeo/geocaching/CreateShortcutActivity.java @@ -1,8 +1,10 @@ package cgeo.geocaching; import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.list.PseudoList; import cgeo.geocaching.list.StoredList; -import cgeo.geocaching.utils.RunnableWithArgument; + +import rx.util.functions.Action1; import android.content.Intent; import android.content.Intent.ShortcutIconResource; @@ -21,17 +23,17 @@ public class CreateShortcutActivity extends AbstractActivity { } private void promptForShortcut() { - new StoredList.UserInterface(this).promptForListSelection(R.string.create_shortcut, new RunnableWithArgument<Integer>() { + new StoredList.UserInterface(this).promptForListSelection(R.string.create_shortcut, new Action1<Integer>() { @Override - public void run(final Integer listId) { + public void call(final Integer listId) { final Intent shortcut = createShortcut(listId.intValue()); setResult(RESULT_OK, shortcut); // finish activity to return the shortcut finish(); } - }); + }, false, PseudoList.HISTORY_LIST.id); } protected Intent createShortcut(int listId) { diff --git a/main/src/cgeo/geocaching/DataStore.java b/main/src/cgeo/geocaching/DataStore.java index 6da1af8..11c5a9a 100644 --- a/main/src/cgeo/geocaching/DataStore.java +++ b/main/src/cgeo/geocaching/DataStore.java @@ -25,17 +25,20 @@ import org.apache.commons.collections4.MapUtils; import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.NonNull; +import android.app.SearchManager; import android.content.ContentValues; import android.content.Context; import android.content.ContextWrapper; import android.content.res.Resources; import android.database.Cursor; import android.database.DatabaseUtils; +import android.database.MatrixCursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.database.sqlite.SQLiteDoneException; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteStatement; +import android.provider.BaseColumns; import java.io.File; import java.io.FilenameFilter; @@ -811,12 +814,19 @@ public class DataStore { /** * Remove obsolete cache directories in c:geo private storage. + */ + public static void removeObsoleteCacheDirectories() { + removeObsoleteCacheDirectories(database); + } + + /** + * Remove obsolete cache directories in c:geo private storage. * * @param db * the read-write database to use */ private static void removeObsoleteCacheDirectories(final SQLiteDatabase db) { - final Pattern oldFilePattern = Pattern.compile("^[GC|TB|O][A-Z0-9]{4,7}$"); + final Pattern oldFilePattern = Pattern.compile("^[GC|TB|EC|GK|O][A-Z0-9]{4,7}$"); final SQLiteStatement select = db.compileStatement("select count(*) from " + dbTableCaches + " where geocode = ?"); final File[] files = LocalStorage.getStorage().listFiles(); final ArrayList<File> toRemove = new ArrayList<File>(files.length); @@ -868,28 +878,6 @@ public class DataStore { db.execSQL("drop table if exists " + dbTableTrackables); } - public static String[] getRecentGeocodesForSearch() { - init(); - - try { - long timestamp = System.currentTimeMillis() - DAYS_AFTER_CACHE_IS_DELETED; - final Cursor cursor = database.query( - dbTableCaches, - new String[]{"geocode"}, - "(detailed = 1 and detailedupdate > ?) or reason > 0", - new String[]{Long.toString(timestamp)}, - null, - null, - "detailedupdate desc", - "100"); - - return getFirstColumn(cursor); - } catch (final Exception e) { - Log.e("DataStore.allDetailedThere", e); - return new String[0]; - } - } - public static boolean isThere(String geocode, String guid, boolean detailed, boolean checkTime) { init(); @@ -2302,11 +2290,6 @@ public class DataStore { return new SearchResult(geocodes); } - /** delete caches from the DB store 3 days or more before */ - public static void clean() { - clean(false); - } - /** * Remove caches with listId = 0 * @@ -2355,6 +2338,8 @@ public class DataStore { cursor.close(); + geocodes = exceptCachesWithOfflineLog(geocodes); + if (!geocodes.isEmpty()) { Log.d("Database clean: removing " + geocodes.size() + " geocaches from listId=0"); removeCaches(geocodes, LoadFlags.REMOVE_ALL); @@ -2367,6 +2352,33 @@ public class DataStore { databaseCleaned = true; } + /** + * remove all geocodes from the given list of geocodes where an offline log exists + * + * @param geocodes + * @return + */ + private static Set<String> exceptCachesWithOfflineLog(Set<String> geocodes) { + if (geocodes.isEmpty()) { + return geocodes; + } + + init(); + final Cursor cursor = database.query( + dbTableLogsOffline, + new String[] { "geocode" }, + null, + null, + null, + null, + null); + + final List<String> geocodesWithOfflineLog = Arrays.asList(getFirstColumn(cursor)); + + geocodes.removeAll(geocodesWithOfflineLog); + return geocodes; + } + public static void removeAllFromCache() { // clean up CacheCache cacheCache.removeAllFromCache(); @@ -2897,21 +2909,6 @@ public class DataStore { return waypoints; } - public static String[] getTrackableCodes() { - init(); - - final Cursor cursor = database.query( - dbTableTrackables, - new String[] { "tbcode" }, - null, - null, - null, - null, - "updated DESC", - "100"); - return getFirstColumn(cursor); - } - /** * Extract the first column of the cursor rows and close the cursor. * @@ -3098,4 +3095,113 @@ public class DataStore { return missingFromSearch; } + public static Cursor findSuggestions(final String searchTerm) { + // require 3 characters, otherwise there are to many results + if (StringUtils.length(searchTerm) < 3) { + return null; + } + init(); + final MatrixCursor resultCursor = new MatrixCursor(new String[] { + BaseColumns._ID, + SearchManager.SUGGEST_COLUMN_TEXT_1, + SearchManager.SUGGEST_COLUMN_TEXT_2, + SearchManager.SUGGEST_COLUMN_INTENT_ACTION, + SearchManager.SUGGEST_COLUMN_QUERY + }); + try { + final String selectionArg = getSuggestionArgument(searchTerm); + findCaches(resultCursor, selectionArg); + findTrackables(resultCursor, selectionArg); + } catch (final Exception e) { + Log.e("DataStore.loadBatchOfStoredGeocodes", e); + } + return resultCursor; + } + + private static void findCaches(final MatrixCursor resultCursor, final String selectionArg) { + Cursor cursor = database.query( + dbTableCaches, + new String[] { "geocode", "name" }, + "geocode IS NOT NULL AND geocode != '' AND (geocode LIKE ? OR name LIKE ?)", + new String[] { selectionArg, selectionArg }, + null, + null, + "name"); + while (cursor.moveToNext()) { + final String geocode = cursor.getString(0); + resultCursor.addRow(new String[] { + String.valueOf(resultCursor.getCount()), + cursor.getString(1), + geocode, + Intents.ACTION_GEOCACHE, + geocode + }); + } + cursor.close(); + } + + private static String getSuggestionArgument(String input) { + return "%" + StringUtils.trim(input) + "%"; + } + + private static void findTrackables(final MatrixCursor resultCursor, final String selectionArg) { + Cursor cursor = database.query( + dbTableTrackables, + new String[] { "tbcode", "title" }, + "tbcode IS NOT NULL AND tbcode != '' AND (tbcode LIKE ? OR title LIKE ?)", + new String[] { selectionArg, selectionArg }, + null, + null, + "title"); + while (cursor.moveToNext()) { + final String tbcode = cursor.getString(0); + resultCursor.addRow(new String[] { + String.valueOf(resultCursor.getCount()), + cursor.getString(1), + tbcode, + Intents.ACTION_TRACKABLE, + tbcode + }); + } + cursor.close(); + } + + public static String[] getSuggestions(final String table, final String column, final String input) { + Cursor cursor = database.query( + true, + table, + new String[] { column }, + column + " LIKE ?", + new String[] { getSuggestionArgument(input) }, + null, + null, + column, + null); + return getFirstColumn(cursor); + } + + public static String[] getSuggestionsOwnerName(String input) { + return getSuggestions(dbTableCaches, "owner", input); + } + + public static String[] getSuggestionsTrackableCode(String input) { + return getSuggestions(dbTableTrackables, "tbcode", input); + } + + public static String[] getSuggestionsFinderName(String input) { + return getSuggestions(dbTableLogs, "author", input); + } + + public static String[] getSuggestionsGeocode(String input) { + return getSuggestions(dbTableCaches, "geocode", input); + } + + public static String[] getSuggestionsKeyword(String input) { + return getSuggestions(dbTableCaches, "name", input); + } + + public static String[] getSuggestionsAddress(String input) { + return getSuggestions(dbTableCaches, "location", input); + } + } diff --git a/main/src/cgeo/geocaching/DirectionProvider.java b/main/src/cgeo/geocaching/DirectionProvider.java index ae58fed..cc44155 100644 --- a/main/src/cgeo/geocaching/DirectionProvider.java +++ b/main/src/cgeo/geocaching/DirectionProvider.java @@ -1,9 +1,13 @@ package cgeo.geocaching; import cgeo.geocaching.compatibility.Compatibility; -import cgeo.geocaching.utils.MemorySubject; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import rx.Observable; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Subscription; +import rx.observables.ConnectableObservable; +import rx.subjects.BehaviorSubject; import android.app.Activity; import android.content.Context; @@ -12,58 +16,61 @@ import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; -public class DirectionProvider extends MemorySubject<Float> implements SensorEventListener { +public class DirectionProvider implements OnSubscribeFunc<Float> { private final SensorManager sensorManager; + private final BehaviorSubject<Float> subject = BehaviorSubject.create(0.0f); - // Previous values signaled to observers to avoid re-sending the same value when the - // device doesn't change orientation. The orientation is usually given with a 1 degree - // precision by Android, so it is not uncommon to obtain exactly the same value several - // times. - private float previous = -1; - - public DirectionProvider(final Context context) { - sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); - + static public Observable<Float> create(final Context context) { + return new DirectionProvider((SensorManager) context.getSystemService(Context.SENSOR_SERVICE)).worker.refCount(); } - @Override - protected void onFirstObserver() { - @SuppressWarnings("deprecation") - // This will be removed when using a new location service. Until then, it is okay to be used. - final Sensor defaultSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION); - sensorManager.registerListener(this, defaultSensor, SensorManager.SENSOR_DELAY_NORMAL); + private DirectionProvider(final SensorManager sensorManager) { + this.sensorManager = sensorManager; } @Override - protected void onLastObserver() { - sensorManager.unregisterListener(this); + public Subscription onSubscribe(final Observer<? super Float> observer) { + return subject.distinctUntilChanged().subscribe(observer); } - @Override - public void onAccuracyChanged(final Sensor sensor, int accuracy) { - /* - * 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 - */ + private final ConnectableObservable<Float> worker = new ConnectableObservable<Float>(this) { + @Override + public Subscription connect() { + @SuppressWarnings("deprecation") + // This will be removed when using a new location service. Until then, it is okay to be used. + final Sensor defaultSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION); + final SensorEventListener listener = new SensorEventListener() { + @Override + public void onSensorChanged(final SensorEvent event) { + subject.onNext(event.values[0]); + } - //Log.i(Settings.tag, "Compass' accuracy is low (" + accuracy + ")"); - } + @Override + public void onAccuracyChanged(final Sensor sensor, final int accuracy) { + /* + * 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 + */ - @Override - @SuppressFBWarnings("FE_FLOATING_POINT_EQUALITY") - public void onSensorChanged(final SensorEvent event) { - final float direction = event.values[0]; - if (direction != previous) { - notifyObservers(direction); - previous = direction; + //Log.i(Settings.tag, "Compass' accuracy is low (" + accuracy + ")"); + } + }; + + sensorManager.registerListener(listener, defaultSensor, SensorManager.SENSOR_DELAY_NORMAL); + return new Subscription() { + @Override + public void unsubscribe() { + sensorManager.unregisterListener(listener); + } + }; } - } + }; /** * Take the phone rotation (through a given activity) in account and adjust the direction. @@ -72,6 +79,7 @@ public class DirectionProvider extends MemorySubject<Float> implements SensorEve * @param direction the unadjusted direction in degrees, in the [0, 360[ range * @return the adjusted direction in degrees, in the [0, 360[ range */ + public static float getDirectionNow(final Activity activity, final float direction) { return Compatibility.getDirectionNow(direction, activity); } diff --git a/main/src/cgeo/geocaching/EditWaypointActivity.java b/main/src/cgeo/geocaching/EditWaypointActivity.java index 6d0f822..eee7505 100644 --- a/main/src/cgeo/geocaching/EditWaypointActivity.java +++ b/main/src/cgeo/geocaching/EditWaypointActivity.java @@ -110,12 +110,14 @@ public class EditWaypointActivity extends AbstractActivity { buttonLon.setText(waypoint.getCoords().format(GeopointFormatter.Format.LON_DECMINUTE)); } waypointName.setText(Html.fromHtml(StringUtils.trimToEmpty(waypoint.getName())).toString()); + Dialogs.moveCursorToEnd(waypointName); if (TextUtils.containsHtml(waypoint.getNote())) { note.setText(Html.fromHtml(StringUtils.trimToEmpty(waypoint.getNote())).toString()); } else { note.setText(StringUtils.trimToEmpty(waypoint.getNote())); } + Dialogs.moveCursorToEnd(note); } final Geocache cache = DataStore.loadCache(geocode, LoadFlags.LOAD_CACHE_ONLY); setCoordsModificationVisibility(ConnectorFactory.getConnector(geocode), cache); diff --git a/main/src/cgeo/geocaching/GeoDataProvider.java b/main/src/cgeo/geocaching/GeoDataProvider.java index 73aefce..c61b7e7 100644 --- a/main/src/cgeo/geocaching/GeoDataProvider.java +++ b/main/src/cgeo/geocaching/GeoDataProvider.java @@ -3,9 +3,14 @@ package cgeo.geocaching; import cgeo.geocaching.enumerations.LocationProviderType; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.utils.Log; -import cgeo.geocaching.utils.MemorySubject; import org.apache.commons.lang3.StringUtils; +import rx.Observable; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Subscription; +import rx.observables.ConnectableObservable; +import rx.subjects.BehaviorSubject; import android.content.Context; import android.location.GpsSatellite; @@ -15,22 +20,14 @@ import android.location.LocationListener; import android.location.LocationManager; import android.os.Bundle; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.TimeUnit; - -/** - * Provide information about the user location. This class should be instantiated only once per application. - */ -class GeoDataProvider extends MemorySubject<IGeoData> { +class GeoDataProvider implements OnSubscribeFunc<IGeoData> { private static final String LAST_LOCATION_PSEUDO_PROVIDER = "last"; private final LocationManager geoManager; - private final GpsStatus.Listener gpsStatusListener = new GpsStatusListener(); private final LocationData gpsLocation = new LocationData(); private final LocationData netLocation = new LocationData(); - private final Listener networkListener = new Listener(LocationManager.NETWORK_PROVIDER, netLocation); - private final Listener gpsListener = new Listener(LocationManager.GPS_PROVIDER, gpsLocation); - private final Unregisterer unregisterer = new Unregisterer(); + private final BehaviorSubject<IGeoData> subject; + public boolean gpsEnabled = false; public int satellitesVisible = 0; public int satellitesFixed = 0; @@ -111,51 +108,6 @@ class GeoDataProvider extends MemorySubject<IGeoData> { } } - private class Unregisterer extends Thread { - - private boolean unregisterRequested = false; - private final ArrayBlockingQueue<Boolean> queue = new ArrayBlockingQueue<Boolean>(1); - - public void cancelUnregister() { - try { - queue.put(false); - } catch (final InterruptedException e) { - // Do nothing - } - } - - public void lateUnregister() { - try { - queue.put(true); - } catch (final InterruptedException e) { - // Do nothing - } - } - - @Override - public void run() { - try { - while (true) { - if (unregisterRequested) { - final Boolean element = queue.poll(2500, TimeUnit.MILLISECONDS); - if (element == null) { - // Timeout - unregisterListeners(); - unregisterRequested = false; - } else { - unregisterRequested = element; - } - } else { - unregisterRequested = queue.take(); - } - } - } catch (final InterruptedException e) { - // Do nothing - } - } - - } - /** * Build a new geo data provider object. * <p/> @@ -164,10 +116,50 @@ class GeoDataProvider extends MemorySubject<IGeoData> { * * @param context the context used to retrieve the system services */ - GeoDataProvider(final Context context) { + protected GeoDataProvider(final Context context) { geoManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); - unregisterer.start(); + subject = BehaviorSubject.create(findInitialLocation()); + } + + public static Observable<IGeoData> create(final Context context) { + final GeoDataProvider provider = new GeoDataProvider(context); + return provider.worker.refCount(); + } + @Override + public Subscription onSubscribe(final Observer<? super IGeoData> observer) { + return subject.subscribe(observer); + } + + final ConnectableObservable<IGeoData> worker = new ConnectableObservable<IGeoData>(this) { + @Override + public Subscription connect() { + final GpsStatus.Listener gpsStatusListener = new GpsStatusListener(); + geoManager.addGpsStatusListener(gpsStatusListener); + + final Listener networkListener = new Listener(LocationManager.NETWORK_PROVIDER, netLocation); + final Listener gpsListener = new Listener(LocationManager.GPS_PROVIDER, gpsLocation); + + for (final Listener listener : new Listener[] { networkListener, gpsListener }) { + try { + geoManager.requestLocationUpdates(listener.locationProvider, 0, 0, listener); + } catch (final Exception e) { + Log.w("There is no location provider " + listener.locationProvider); + } + } + + return new Subscription() { + @Override + public void unsubscribe() { + geoManager.removeUpdates(networkListener); + geoManager.removeUpdates(gpsListener); + geoManager.removeGpsStatusListener(gpsStatusListener); + } + }; + } + }; + + private IGeoData findInitialLocation() { final Location initialLocation = new Location(LAST_LOCATION_PSEUDO_PROVIDER); try { // Try to find a sensible initial location from the last locations known to Android. @@ -195,45 +187,13 @@ class GeoDataProvider extends MemorySubject<IGeoData> { } // Start with an historical GeoData just in case someone queries it before we get // a chance to get any information. - notifyObservers(new GeoData(initialLocation, false, 0, 0)); + return new GeoData(initialLocation, false, 0, 0); } private static void copyCoords(final Location target, final Location source) { target.setLatitude(source.getLatitude()); target.setLongitude(source.getLongitude()); } - private void registerListeners() { - geoManager.addGpsStatusListener(gpsStatusListener); - - for (final Listener listener : new Listener[] { networkListener, gpsListener }) { - try { - geoManager.requestLocationUpdates(listener.locationProvider, 0, 0, listener); - } catch (final Exception e) { - Log.w("There is no location provider " + listener.locationProvider); - } - } - } - - private synchronized void unregisterListeners() { - // This method must be synchronized because it will be called asynchronously from the Unregisterer thread. - // We check that no observers have been re-added to prevent a race condition. - if (sizeObservers() == 0) { - geoManager.removeUpdates(networkListener); - geoManager.removeUpdates(gpsListener); - geoManager.removeGpsStatusListener(gpsStatusListener); - } - } - - @Override - protected void onFirstObserver() { - unregisterer.cancelUnregister(); - registerListeners(); - } - - @Override - protected void onLastObserver() { - unregisterer.lateUnregister(); - } private class Listener implements LocationListener { private final String locationProvider; @@ -336,7 +296,7 @@ class GeoDataProvider extends MemorySubject<IGeoData> { // We do not necessarily get signalled when satellites go to 0/0. final int visible = gpsLocation.isRecent() ? satellitesVisible : 0; final IGeoData current = new GeoData(locationData.location, gpsEnabled, visible, satellitesFixed); - notifyObservers(current); + subject.onNext(current); } } diff --git a/main/src/cgeo/geocaching/Geocache.java b/main/src/cgeo/geocaching/Geocache.java index 156c4b6..42b8f0a 100644 --- a/main/src/cgeo/geocaching/Geocache.java +++ b/main/src/cgeo/geocaching/Geocache.java @@ -817,13 +817,7 @@ public class Geocache implements ICache, IWaypoint { } public boolean showSize() { - if (size == CacheSize.NOT_CHOSEN) { - return false; - } - if (isEventCache() || isVirtual()) { - return false; - } - return true; + return !(size == CacheSize.NOT_CHOSEN || isEventCache() || isVirtual()); } public long getUpdated() { @@ -1373,17 +1367,6 @@ public class Geocache implements ICache, IWaypoint { } /** - * Retrieve a given waypoint. - * - * @param index - * the index of the waypoint - * @return waypoint or <code>null</code> if index is out of range - */ - public Waypoint getWaypoint(final int index) { - return index >= 0 && index < waypoints.size() ? waypoints.get(index) : null; - } - - /** * Lookup a waypoint by its id. * * @param id @@ -1660,6 +1643,8 @@ public class Geocache implements ICache, IWaypoint { StaticMapsProvider.downloadMaps(cache); + imgGetter.waitForBackgroundLoading(handler); + if (handler != null) { handler.sendMessage(Message.obtain()); } diff --git a/main/src/cgeo/geocaching/ImageSelectActivity.java b/main/src/cgeo/geocaching/ImageSelectActivity.java index 9e0a37d..a9230c9 100644 --- a/main/src/cgeo/geocaching/ImageSelectActivity.java +++ b/main/src/cgeo/geocaching/ImageSelectActivity.java @@ -5,6 +5,7 @@ import butterknife.InjectView; import cgeo.geocaching.activity.AbstractActivity; import cgeo.geocaching.settings.Settings; +import cgeo.geocaching.ui.dialog.Dialogs; import cgeo.geocaching.utils.ImageUtils; import cgeo.geocaching.utils.Log; @@ -103,10 +104,12 @@ public class ImageSelectActivity extends AbstractActivity { if (StringUtils.isNotBlank(imageCaption)) { captionView.setText(imageCaption); + Dialogs.moveCursorToEnd(captionView); } if (StringUtils.isNotBlank(imageDescription)) { descriptionView.setText(imageDescription); + Dialogs.moveCursorToEnd(captionView); } scaleView.setSelection(scaleChoiceIndex); diff --git a/main/src/cgeo/geocaching/Intents.java b/main/src/cgeo/geocaching/Intents.java index d9d9829..5c969a1 100644 --- a/main/src/cgeo/geocaching/Intents.java +++ b/main/src/cgeo/geocaching/Intents.java @@ -29,4 +29,8 @@ public class Intents { public static final String EXTRA_WAYPOINT_ID = PREFIX + "waypoint_id"; public static final String EXTRA_CACHELIST = PREFIX + "cache_list"; public static final String EXTRA_POCKET_GUID = PREFIX + "pocket_guid"; + + private static final String PREFIX_ACTION = "cgeo.geocaching.intent.action."; + public static final String ACTION_GEOCACHE = PREFIX_ACTION + "GEOCACHE"; + public static final String ACTION_TRACKABLE = PREFIX_ACTION + "TRACKABLE"; } diff --git a/main/src/cgeo/geocaching/LogCacheActivity.java b/main/src/cgeo/geocaching/LogCacheActivity.java index f17a008..729ec60 100644 --- a/main/src/cgeo/geocaching/LogCacheActivity.java +++ b/main/src/cgeo/geocaching/LogCacheActivity.java @@ -13,6 +13,7 @@ import cgeo.geocaching.settings.Settings; import cgeo.geocaching.twitter.Twitter; import cgeo.geocaching.ui.Formatter; import cgeo.geocaching.ui.dialog.DateDialog; +import cgeo.geocaching.ui.dialog.Dialogs; import cgeo.geocaching.utils.AsyncTaskWithProgress; import cgeo.geocaching.utils.DateUtils; import cgeo.geocaching.utils.Log; @@ -151,7 +152,7 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia 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.setText(tb.action.getLabel() + " ▼"); actionButton.setOnClickListener(new View.OnClickListener() { @Override @@ -301,6 +302,7 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia final EditText logView = (EditText) findViewById(R.id.log); if (StringUtils.isBlank(currentLogText()) && StringUtils.isNotBlank(text)) { logView.setText(text); + Dialogs.moveCursorToEnd(logView); } tweetCheck.setChecked(true); @@ -632,11 +634,11 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia alert.create().show(); } - private String[] getTBLogTypes() { + private static 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); + logTypes[i] = logTypeValues[i].getLabel(); } return logTypes; } diff --git a/main/src/cgeo/geocaching/LogTrackableActivity.java b/main/src/cgeo/geocaching/LogTrackableActivity.java index 5246fa9..fabe391 100644 --- a/main/src/cgeo/geocaching/LogTrackableActivity.java +++ b/main/src/cgeo/geocaching/LogTrackableActivity.java @@ -3,8 +3,8 @@ package cgeo.geocaching; import butterknife.ButterKnife; import butterknife.InjectView; -import cgeo.geocaching.connector.gc.GCParser; import cgeo.geocaching.connector.gc.GCLogin; +import cgeo.geocaching.connector.gc.GCParser; import cgeo.geocaching.enumerations.LogType; import cgeo.geocaching.enumerations.StatusCode; import cgeo.geocaching.network.Network; @@ -13,6 +13,7 @@ import cgeo.geocaching.settings.Settings; import cgeo.geocaching.twitter.Twitter; import cgeo.geocaching.ui.Formatter; import cgeo.geocaching.ui.dialog.DateDialog; +import cgeo.geocaching.ui.dialog.Dialogs; import cgeo.geocaching.utils.Log; import cgeo.geocaching.utils.LogTemplateProvider.LogContext; @@ -127,6 +128,7 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat if (StringUtils.isNotBlank(extras.getString(Intents.EXTRA_TRACKING_CODE))) { trackingEditText.setText(extras.getString(Intents.EXTRA_TRACKING_CODE)); + Dialogs.moveCursorToEnd(trackingEditText); } } diff --git a/main/src/cgeo/geocaching/MainActivity.java b/main/src/cgeo/geocaching/MainActivity.java index 924c66d..3ffc631 100644 --- a/main/src/cgeo/geocaching/MainActivity.java +++ b/main/src/cgeo/geocaching/MainActivity.java @@ -10,6 +10,7 @@ import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.enumerations.StatusCode; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.Units; +import cgeo.geocaching.list.PseudoList; import cgeo.geocaching.list.StoredList; import cgeo.geocaching.maps.CGeoMap; import cgeo.geocaching.settings.Settings; @@ -19,15 +20,22 @@ import cgeo.geocaching.ui.dialog.Dialogs; import cgeo.geocaching.utils.DatabaseBackupUtils; import cgeo.geocaching.utils.GeoDirHandler; import cgeo.geocaching.utils.Log; -import cgeo.geocaching.utils.RunnableWithArgument; import cgeo.geocaching.utils.Version; import com.google.zxing.integration.android.IntentIntegrator; import com.google.zxing.integration.android.IntentResult; -import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; +import rx.Observable; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Subscription; +import rx.android.observables.AndroidObservable; +import rx.concurrency.Schedulers; +import rx.subscriptions.Subscriptions; +import rx.util.functions.Action1; + import android.app.AlertDialog; import android.app.AlertDialog.Builder; import android.app.SearchManager; @@ -48,7 +56,6 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; -import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -77,8 +84,6 @@ public class MainActivity extends AbstractActivity { private boolean cleanupRunning = false; private int countBubbleCnt = 0; private Geopoint addCoords = null; - private List<Address> addresses = null; - private boolean addressObtaining = false; private boolean initialized = false; private final UpdateLocation locationUpdater = new UpdateLocation(); @@ -104,7 +109,7 @@ public class MainActivity extends AbstractActivity { if (conn.isLoggedIn()) { userInfo.append(conn.getUserName()); if (conn.getCachesFound() >= 0) { - userInfo.append(" (").append(String.valueOf(conn.getCachesFound())).append(')'); + userInfo.append(" (").append(conn.getCachesFound()).append(')'); } userInfo.append(Formatter.SEPARATOR); } @@ -115,40 +120,24 @@ public class MainActivity extends AbstractActivity { } }; - private Handler obtainAddressHandler = new Handler() { - - @Override - public void handleMessage(final Message msg) { - try { - if (CollectionUtils.isNotEmpty(addresses)) { - final Address address = addresses.get(0); - final ArrayList<String> addressParts = new ArrayList<String>(); - - final String countryName = address.getCountryName(); - if (countryName != null) { - addressParts.add(countryName); - } - final String locality = address.getLocality(); - if (locality != null) { - addressParts.add(locality); - } else { - final String adminArea = address.getAdminArea(); - if (adminArea != null) { - addressParts.add(adminArea); - } - } - - addCoords = app.currentGeo().getCoords(); + private static String formatAddress(final Address address) { + final ArrayList<String> addressParts = new ArrayList<String>(); - navLocation.setText(StringUtils.join(addressParts, ", ")); - } - } catch (RuntimeException e) { - // nothing + final String countryName = address.getCountryName(); + if (countryName != null) { + addressParts.add(countryName); + } + final String locality = address.getLocality(); + if (locality != null) { + addressParts.add(locality); + } else { + final String adminArea = address.getAdminArea(); + if (adminArea != null) { + addressParts.add(adminArea); } - - addresses = null; } - }; + return StringUtils.join(addressParts, ", "); + } private class SatellitesHandler extends GeoDirHandler { @@ -285,7 +274,7 @@ public class MainActivity extends AbstractActivity { @Override public boolean onPrepareOptionsMenu(final Menu menu) { super.onPrepareOptionsMenu(menu); - menu.findItem(R.id.menu_pocket_queries).setVisible(Settings.isPremiumMember()); + menu.findItem(R.id.menu_pocket_queries).setVisible(Settings.isGCPremiumMember()); return true; } @@ -309,13 +298,13 @@ public class MainActivity extends AbstractActivity { startScannerApplication(); return true; case R.id.menu_pocket_queries: - if (!Settings.isPremiumMember()) { + if (!Settings.isGCPremiumMember()) { return true; } - new PocketQueryList.UserInterface(MainActivity.this).promptForListSelection(new RunnableWithArgument<PocketQueryList>() { + PocketQueryList.promptForListSelection(this, new Action1<PocketQueryList>() { @Override - public void run(final PocketQueryList pql) { + public void call(final PocketQueryList pql) { CacheListActivity.startActivityPocket(MainActivity.this, pql); } }); @@ -388,14 +377,14 @@ public class MainActivity extends AbstractActivity { @Override public boolean onLongClick(final View v) { - new StoredList.UserInterface(MainActivity.this).promptForListSelection(R.string.list_title, new RunnableWithArgument<Integer>() { + new StoredList.UserInterface(MainActivity.this).promptForListSelection(R.string.list_title, new Action1<Integer>() { @Override - public void run(final Integer selectedListId) { + public void call(final Integer selectedListId) { Settings.saveLastList(selectedListId); CacheListActivity.startActivityOffline(MainActivity.this); } - }); + }, false, PseudoList.HISTORY_LIST.id); return true; } }); @@ -525,52 +514,61 @@ public class MainActivity extends AbstractActivity { @Override public void updateGeoData(final IGeoData geo) { - try { - if (geo.getCoords() != null) { - if (!nearestView.isClickable()) { - nearestView.setFocusable(true); - nearestView.setClickable(true); - nearestView.setOnClickListener(new OnClickListener() { - @Override - public void onClick(final View v) { - cgeoFindNearest(v); - } - }); - nearestView.setBackgroundResource(R.drawable.main_nearby); + if (!nearestView.isClickable()) { + nearestView.setFocusable(true); + nearestView.setClickable(true); + nearestView.setOnClickListener(new OnClickListener() { + @Override + public void onClick(final View v) { + cgeoFindNearest(v); } + }); + nearestView.setBackgroundResource(R.drawable.main_nearby); + } - navType.setText(res.getString(geo.getLocationProvider().resourceId)); + navType.setText(res.getString(geo.getLocationProvider().resourceId)); - if (geo.getAccuracy() >= 0) { - int speed = Math.round(geo.getSpeed()) * 60 * 60 / 1000; - navAccuracy.setText("±" + Units.getDistanceFromMeters(geo.getAccuracy()) + Formatter.SEPARATOR + Units.getSpeed(speed)); - } else { - navAccuracy.setText(null); - } + if (geo.getAccuracy() >= 0) { + int speed = Math.round(geo.getSpeed()) * 60 * 60 / 1000; + navAccuracy.setText("±" + Units.getDistanceFromMeters(geo.getAccuracy()) + Formatter.SEPARATOR + Units.getSpeed(speed)); + } else { + navAccuracy.setText(null); + } - if (Settings.isShowAddress()) { - if (addCoords == null) { - navLocation.setText(res.getString(R.string.loc_no_addr)); - } - if (addCoords == null || (geo.getCoords().distanceTo(addCoords) > 0.5 && !addressObtaining)) { - (new ObtainAddressThread()).start(); + if (Settings.isShowAddress()) { + if (addCoords == null) { + navLocation.setText(R.string.loc_no_addr); + } + if (addCoords == null || (geo.getCoords().distanceTo(addCoords) > 0.5)) { + final Observable<String> address = Observable.create(new OnSubscribeFunc<String>() { + @Override + public Subscription onSubscribe(final Observer<? super String> observer) { + try { + addCoords = geo.getCoords(); + final Geocoder geocoder = new Geocoder(MainActivity.this, Locale.getDefault()); + final Geopoint coords = app.currentGeo().getCoords(); + final List<Address> addresses = geocoder.getFromLocation(coords.getLatitude(), coords.getLongitude(), 1); + if (!addresses.isEmpty()) { + observer.onNext(formatAddress(addresses.get(0))); + } + observer.onCompleted(); + } catch (final Exception e) { + observer.onError(e); + } + return Subscriptions.empty(); } - } else { - navLocation.setText(geo.getCoords().toString()); - } - } else { - if (nearestView.isClickable()) { - nearestView.setFocusable(false); - nearestView.setClickable(false); - nearestView.setOnClickListener(null); - nearestView.setBackgroundResource(R.drawable.main_nearby_disabled); - } - navType.setText(null); - navAccuracy.setText(null); - navLocation.setText(res.getString(R.string.loc_trying)); + }).subscribeOn(Schedulers.threadPoolForIO()); + AndroidObservable.fromActivity(MainActivity.this, address) + .onErrorResumeNext(Observable.from(geo.getCoords().toString())) + .subscribe(new Action1<String>() { + @Override + public void call(final String address) { + navLocation.setText(address); + } + }); } - } catch (RuntimeException e) { - Log.w("Failed to update location."); + } else { + navLocation.setText(geo.getCoords().toString()); } } } @@ -714,35 +712,6 @@ public class MainActivity extends AbstractActivity { } } - private class ObtainAddressThread extends Thread { - - public ObtainAddressThread() { - setPriority(Thread.MIN_PRIORITY); - } - - @Override - public void run() { - if (addressObtaining) { - return; - } - addressObtaining = true; - - try { - final Geocoder geocoder = new Geocoder(MainActivity.this, Locale.getDefault()); - final Geopoint coords = app.currentGeo().getCoords(); - addresses = geocoder.getFromLocation(coords.getLatitude(), coords.getLongitude(), 1); - } catch (final IOException e) { - Log.i("Failed to obtain address"); - } catch (final IllegalArgumentException e) { - Log.w("ObtainAddressThread.run", e); - } - - obtainAddressHandler.sendEmptyMessage(0); - - addressObtaining = false; - } - } - /** * @param view * unused here but needed since this method is referenced from XML layout diff --git a/main/src/cgeo/geocaching/PocketQueryList.java b/main/src/cgeo/geocaching/PocketQueryList.java index 9d1110d..6613441 100644 --- a/main/src/cgeo/geocaching/PocketQueryList.java +++ b/main/src/cgeo/geocaching/PocketQueryList.java @@ -2,15 +2,22 @@ package cgeo.geocaching; import cgeo.geocaching.activity.ActivityMixin; import cgeo.geocaching.connector.gc.GCParser; -import cgeo.geocaching.utils.RunnableWithArgument; + +import org.apache.commons.collections4.CollectionUtils; +import rx.Observable; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Subscription; +import rx.android.observables.AndroidObservable; +import rx.concurrency.Schedulers; +import rx.subscriptions.Subscriptions; +import rx.util.functions.Action1; import android.app.Activity; import android.app.AlertDialog; +import android.app.Dialog; import android.app.ProgressDialog; import android.content.DialogInterface; -import android.content.res.Resources; -import android.os.Handler; -import android.os.Message; import java.util.List; @@ -20,105 +27,63 @@ public final class PocketQueryList { private final int maxCaches; private final String name; - public PocketQueryList(String guid, String name, int maxCaches) { + public PocketQueryList(final String guid, final String name, final int maxCaches) { this.guid = guid; this.name = name; this.maxCaches = maxCaches; } - public static class UserInterface { - - List<PocketQueryList> pocketQueryList = null; - RunnableWithArgument<PocketQueryList> runAfterwards; - - private Handler loadPocketQueryHandler = new Handler() { - - @Override - public void handleMessage(Message msg) { - if ((pocketQueryList == null) || (pocketQueryList.size() == 0)) { - if (waitDialog != null) { - waitDialog.dismiss(); - } - - ActivityMixin.showToast(activity, res.getString(R.string.warn_no_pocket_query_found)); - - return; - } - - if (waitDialog != null) { - waitDialog.dismiss(); - } - - final CharSequence[] items = new CharSequence[pocketQueryList.size()]; - - for (int i = 0; i < pocketQueryList.size(); i++) { - PocketQueryList pq = pocketQueryList.get(i); - items[i] = pq.name; - } + public String getGuid() { + return guid; + } - AlertDialog.Builder builder = new AlertDialog.Builder(activity); - builder.setTitle(res.getString(R.string.search_pocket_select)); - builder.setItems(items, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int itemId) { - final PocketQueryList query = pocketQueryList.get(itemId); - dialogInterface.dismiss(); - runAfterwards.run(query); - } - }); - builder.create().show(); + public int getMaxCaches() { + return maxCaches; + } - } - }; + public String getName() { + return name; + } - private class LoadPocketQueryListThread extends Thread { - final private Handler handler; + public static void promptForListSelection(final Activity activity, final Action1<PocketQueryList> runAfterwards) { + final Dialog waitDialog = ProgressDialog.show(activity, activity.getString(R.string.search_pocket_title), activity.getString(R.string.search_pocket_loading), true, true); - public LoadPocketQueryListThread(Handler handlerIn) { - handler = handlerIn; + AndroidObservable.fromActivity(activity, Observable.create(new OnSubscribeFunc<List<PocketQueryList>>() { + @Override + public Subscription onSubscribe(final Observer<? super List<PocketQueryList>> observer) { + observer.onNext(GCParser.searchPocketQueryList()); + observer.onCompleted(); + return Subscriptions.empty(); } - + }).subscribeOn(Schedulers.threadPoolForIO())).subscribe(new Action1<List<PocketQueryList>>() { @Override - public void run() { - pocketQueryList = GCParser.searchPocketQueryList(); - handler.sendMessage(Message.obtain()); + public void call(final List<PocketQueryList> pocketQueryLists) { + waitDialog.dismiss(); + selectFromPocketQueries(activity, pocketQueryLists, runAfterwards); } + }); + } + private static void selectFromPocketQueries(final Activity activity, final List<PocketQueryList> pocketQueryList, final Action1<PocketQueryList> runAfterwards) { + if (CollectionUtils.isEmpty(pocketQueryList)) { + ActivityMixin.showToast(activity, activity.getString(R.string.warn_no_pocket_query_found)); + return; } - private final Activity activity; - private final CgeoApplication app; - private final Resources res; - private ProgressDialog waitDialog = null; - - public UserInterface(final Activity activity) { - this.activity = activity; - app = CgeoApplication.getInstance(); - res = app.getResources(); - } - - public void promptForListSelection(final RunnableWithArgument<PocketQueryList> runAfterwards) { + final CharSequence[] items = new CharSequence[pocketQueryList.size()]; - this.runAfterwards = runAfterwards; - - waitDialog = ProgressDialog.show(activity, res.getString(R.string.search_pocket_title), res.getString(R.string.search_pocket_loading), true, true); - - LoadPocketQueryListThread thread = new LoadPocketQueryListThread(loadPocketQueryHandler); - thread.start(); + for (int i = 0; i < pocketQueryList.size(); i++) { + items[i] = pocketQueryList.get(i).name; } - - } - - public String getGuid() { - return guid; - } - - public int getMaxCaches() { - return maxCaches; - } - - public String getName() { - return name; + new AlertDialog.Builder(activity) + .setTitle(activity.getString(R.string.search_pocket_select)) + .setItems(items, new DialogInterface.OnClickListener() { + @Override + public void onClick(final DialogInterface dialogInterface, final int itemId) { + dialogInterface.dismiss(); + runAfterwards.call(pocketQueryList.get(itemId)); + } + }).create().show(); } } diff --git a/main/src/cgeo/geocaching/SearchActivity.java b/main/src/cgeo/geocaching/SearchActivity.java index 0648073..7a90a49 100644 --- a/main/src/cgeo/geocaching/SearchActivity.java +++ b/main/src/cgeo/geocaching/SearchActivity.java @@ -10,6 +10,7 @@ import cgeo.geocaching.connector.capability.ISearchByGeocode; import cgeo.geocaching.connector.trackable.TrackableConnector; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.GeopointFormatter; +import cgeo.geocaching.search.AutoCompleteAdapter; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.ui.dialog.CoordinatesInputDialog; import cgeo.geocaching.ui.dialog.Dialogs; @@ -17,6 +18,8 @@ import cgeo.geocaching.utils.EditUtils; import org.apache.commons.lang3.StringUtils; +import rx.util.functions.Func1; + import android.app.Activity; import android.app.SearchManager; import android.content.Intent; @@ -26,10 +29,8 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; -import android.widget.ArrayAdapter; import android.widget.AutoCompleteTextView; import android.widget.Button; -import android.widget.EditText; import java.util.Locale; @@ -39,19 +40,19 @@ public class SearchActivity extends AbstractActivity { @InjectView(R.id.buttonLongitude) protected Button buttonLongitude; @InjectView(R.id.search_coordinates) protected Button buttonSearchCoords; - @InjectView(R.id.address) protected EditText addressEditText; + @InjectView(R.id.address) protected AutoCompleteTextView addressEditText; @InjectView(R.id.search_address) protected Button buttonSearchAddress; @InjectView(R.id.geocode) protected AutoCompleteTextView geocodeEditText; @InjectView(R.id.display_geocode) protected Button buttonSearchGeocode; - @InjectView(R.id.keyword) protected EditText keywordEditText; + @InjectView(R.id.keyword) protected AutoCompleteTextView keywordEditText; @InjectView(R.id.search_keyword) protected Button buttonSearchKeyword; - @InjectView(R.id.finder) protected EditText finderNameEditText; + @InjectView(R.id.finder) protected AutoCompleteTextView finderNameEditText; @InjectView(R.id.search_finder) protected Button buttonSearchFinder; - @InjectView(R.id.owner) protected EditText ownerNameEditText; + @InjectView(R.id.owner) protected AutoCompleteTextView ownerNameEditText; @InjectView(R.id.search_owner) protected Button buttonSearchOwner; @InjectView(R.id.trackable) protected AutoCompleteTextView trackableEditText; @@ -60,9 +61,23 @@ public class SearchActivity extends AbstractActivity { @Override public final void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); + final Intent intent = getIntent(); + + // search suggestion for a cache + if (Intents.ACTION_GEOCACHE.equals(intent.getAction())) { + CacheDetailActivity.startActivity(this, intent.getStringExtra(SearchManager.QUERY)); + finish(); + return; + } + + // search suggestion for a trackable + if (Intents.ACTION_TRACKABLE.equals(intent.getAction())) { + TrackableActivity.startActivity(this, null, intent.getStringExtra(SearchManager.QUERY), null); + finish(); + return; + } // search query - final Intent intent = getIntent(); if (Intent.ACTION_SEARCH.equals(intent.getAction())) { hideKeyboard(); final String query = intent.getStringExtra(SearchManager.QUERY); @@ -173,6 +188,12 @@ public class SearchActivity extends AbstractActivity { public void run() { findByAddressFn(); } + }, new Func1<String, String[]>() { + + @Override + public String[] call(final String input) { + return DataStore.getSuggestionsAddress(input); + } }); setSearchAction(geocodeEditText, buttonSearchGeocode, new Runnable() { @@ -181,8 +202,13 @@ public class SearchActivity extends AbstractActivity { public void run() { findByGeocodeFn(); } + }, new Func1<String, String[]>() { + + @Override + public String[] call(final String input) { + return DataStore.getSuggestionsGeocode(input); + } }); - addHistoryEntries(geocodeEditText, DataStore.getRecentGeocodesForSearch()); setSearchAction(keywordEditText, buttonSearchKeyword, new Runnable() { @@ -190,6 +216,12 @@ public class SearchActivity extends AbstractActivity { public void run() { findByKeywordFn(); } + }, new Func1<String, String[]>() { + + @Override + public String[] call(final String input) { + return DataStore.getSuggestionsKeyword(input); + } }); setSearchAction(finderNameEditText, buttonSearchFinder, new Runnable() { @@ -198,6 +230,12 @@ public class SearchActivity extends AbstractActivity { public void run() { findByFinderFn(); } + }, new Func1<String, String[]>() { + + @Override + public String[] call(final String input) { + return DataStore.getSuggestionsFinderName(input); + } }); setSearchAction(ownerNameEditText, buttonSearchOwner, new Runnable() { @@ -206,6 +244,12 @@ public class SearchActivity extends AbstractActivity { public void run() { findByOwnerFn(); } + }, new Func1<String, String[]>() { + + @Override + public String[] call(final String input) { + return DataStore.getSuggestionsOwnerName(input); + } }); setSearchAction(trackableEditText, buttonSearchTrackable, new Runnable() { @@ -214,12 +258,16 @@ public class SearchActivity extends AbstractActivity { public void run() { findTrackableFn(); } + }, new Func1<String, String[]>() { + + @Override + public String[] call(final String input) { + return DataStore.getSuggestionsTrackableCode(input); + } }); - addHistoryEntries(trackableEditText, DataStore.getTrackableCodes()); - disableSuggestions(trackableEditText); } - private static void setSearchAction(final EditText editText, final Button button, final Runnable runnable) { + private static void setSearchAction(final AutoCompleteTextView editText, final Button button, final Runnable runnable, final Func1<String, String[]> suggestionFunction) { EditUtils.setActionListener(editText, runnable); button.setOnClickListener(new View.OnClickListener() { @Override @@ -227,13 +275,7 @@ public class SearchActivity extends AbstractActivity { runnable.run(); } }); - } - - private void addHistoryEntries(final AutoCompleteTextView textView, final String[] entries) { - if (entries != null) { - final ArrayAdapter<String> historyAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_dropdown_item_1line, entries); - textView.setAdapter(historyAdapter); - } + editText.setAdapter(new AutoCompleteAdapter(editText.getContext(), android.R.layout.simple_dropdown_item_1line, suggestionFunction)); } private class FindByCoordsAction implements OnClickListener { @@ -305,7 +347,7 @@ public class SearchActivity extends AbstractActivity { return; } - CacheListActivity.startActivityUserName(this, usernameText); + CacheListActivity.startActivityFinder(this, usernameText); } private void findByOwnerFn() { diff --git a/main/src/cgeo/geocaching/SearchResult.java b/main/src/cgeo/geocaching/SearchResult.java index 131a01c..46ac38e 100644 --- a/main/src/cgeo/geocaching/SearchResult.java +++ b/main/src/cgeo/geocaching/SearchResult.java @@ -10,6 +10,7 @@ import cgeo.geocaching.gcvote.GCVote; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; +import org.eclipse.jdt.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; @@ -228,6 +229,7 @@ public class SearchResult implements Parcelable { return result; } + @Nullable public Geocache getFirstCacheFromResult(final EnumSet<LoadFlag> loadFlags) { return CollectionUtils.isNotEmpty(geocodes) ? DataStore.loadCache(geocodes.iterator().next(), loadFlags) : null; } diff --git a/main/src/cgeo/geocaching/StaticMapsActivity.java b/main/src/cgeo/geocaching/StaticMapsActivity.java index 7811da5..f19ce29 100644 --- a/main/src/cgeo/geocaching/StaticMapsActivity.java +++ b/main/src/cgeo/geocaching/StaticMapsActivity.java @@ -8,7 +8,6 @@ import com.googlecode.androidannotations.annotations.EActivity; import com.googlecode.androidannotations.annotations.Extra; import com.googlecode.androidannotations.annotations.OptionsItem; import com.googlecode.androidannotations.annotations.OptionsMenu; - import org.apache.commons.collections4.CollectionUtils; import android.app.ProgressDialog; diff --git a/main/src/cgeo/geocaching/StatusFragment.java b/main/src/cgeo/geocaching/StatusFragment.java index 4f70f0e..a4b5973 100644 --- a/main/src/cgeo/geocaching/StatusFragment.java +++ b/main/src/cgeo/geocaching/StatusFragment.java @@ -1,15 +1,17 @@ package cgeo.geocaching; +import cgeo.geocaching.network.StatusUpdater; import cgeo.geocaching.network.StatusUpdater.Status; -import cgeo.geocaching.utils.IObserver; import cgeo.geocaching.utils.Log; +import rx.Subscription; +import rx.android.observables.AndroidObservable; +import rx.util.functions.Action1; + 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; @@ -20,91 +22,68 @@ import android.widget.TextView; public class StatusFragment extends Fragment { - private ViewGroup status; - private ImageView statusIcon; - private TextView statusMessage; - - final private StatusHandler statusHandler = new StatusHandler(); + private Subscription statusSubscription; @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 : Status.defaultStatus()); - } - - private void updateDisplay(final Status data) { - - if (data == null) { - status.setVisibility(View.INVISIBLE); - return; - } - - final Resources res = getResources(); - final String packageName = getActivity().getPackageName(); + final ViewGroup statusGroup = (ViewGroup) inflater.inflate(R.layout.status, container, false); + final ImageView statusIcon = (ImageView) statusGroup.findViewById(R.id.status_icon); + final TextView statusMessage = (TextView) statusGroup.findViewById(R.id.status_message); + statusSubscription = AndroidObservable.fromFragment(this, StatusUpdater.latestStatus).subscribe(new Action1<Status>() { + @Override + public void call(final Status status) { + if (status == null) { + statusGroup.setVisibility(View.INVISIBLE); + return; + } - if (data.icon != null) { - final int iconId = res.getIdentifier(data.icon, "drawable", packageName); - if (iconId != 0) { - statusIcon.setImageResource(iconId); - statusIcon.setVisibility(View.VISIBLE); + final Resources res = getResources(); + final String packageName = getActivity().getPackageName(); + + if (status.icon != null) { + final int iconId = res.getIdentifier(status.icon, "drawable", packageName); + if (iconId != 0) { + statusIcon.setImageResource(iconId); + statusIcon.setVisibility(View.VISIBLE); + } else { + Log.w("StatusHandler: could not find icon corresponding to @drawable/" + status.icon); + statusIcon.setVisibility(View.GONE); + } } 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); + String message = status.message; + if (status.messageId != null) { + final int messageId = res.getIdentifier(status.messageId, "string", packageName); + if (messageId != 0) { + message = res.getString(messageId); + } } - } - statusMessage.setText(message); - status.setVisibility(View.VISIBLE); + statusMessage.setText(message); + statusGroup.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); + if (status.url != null) { + statusGroup.setOnClickListener(new OnClickListener() { + @Override + public void onClick(final View v) { + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(status.url))); + } + }); + } else { + statusGroup.setClickable(false); + } } - } + }); + return statusGroup; + } + @Override + public void onDestroy() { + statusSubscription.unsubscribe(); + super.onDestroy(); } + } diff --git a/main/src/cgeo/geocaching/TrackableActivity.java b/main/src/cgeo/geocaching/TrackableActivity.java index dcfd80a..34f546a 100644 --- a/main/src/cgeo/geocaching/TrackableActivity.java +++ b/main/src/cgeo/geocaching/TrackableActivity.java @@ -130,6 +130,8 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi // try to get data from URI if (geocode == null && guid == null && id == null && uri != null) { + geocode = ConnectorFactory.getTrackableFromURL(uri.toString()); + final String uriHost = uri.getHost().toLowerCase(Locale.US); if (uriHost.contains("geocaching.com")) { geocode = uri.getQueryParameter("tracker"); diff --git a/main/src/cgeo/geocaching/UsefulAppsActivity.java b/main/src/cgeo/geocaching/UsefulAppsActivity.java index c70143f..39c527d 100644 --- a/main/src/cgeo/geocaching/UsefulAppsActivity.java +++ b/main/src/cgeo/geocaching/UsefulAppsActivity.java @@ -62,6 +62,7 @@ public class UsefulAppsActivity extends AbstractActivity { private static final HelperApp[] HELPER_APPS = { new HelperApp(R.string.helper_calendar_title, R.string.helper_calendar_description, R.drawable.cgeo, "cgeo.calendar"), new HelperApp(R.string.helper_sendtocgeo_title, R.string.helper_sendtocgeo_description, R.drawable.cgeo, "http://send2.cgeo.org"), + new HelperApp(R.string.helper_contacts_title, R.string.helper_contacts_description, R.drawable.cgeo, "cgeo.contacts"), new HelperApp(R.string.helper_pocketquery_title, R.string.helper_pocketquery_description, R.drawable.helper_pocketquery, "org.pquery"), new HelperApp(R.string.helper_locus_title, R.string.helper_locus_description, R.drawable.helper_locus, "menion.android.locus"), new HelperApp(R.string.helper_google_translate_title, R.string.helper_google_translate_description, R.drawable.helper_google_translate, "com.google.android.apps.translate"), diff --git a/main/src/cgeo/geocaching/activity/AbstractListActivity.java b/main/src/cgeo/geocaching/activity/AbstractListActivity.java index 2adae7a..a5d5c14 100644 --- a/main/src/cgeo/geocaching/activity/AbstractListActivity.java +++ b/main/src/cgeo/geocaching/activity/AbstractListActivity.java @@ -11,7 +11,6 @@ public abstract class AbstractListActivity extends FragmentListActivity implemen IAbstractActivity { private boolean keepScreenOn = false; - private boolean paused = true; protected CgeoApplication app = null; protected Resources res = null; @@ -85,26 +84,4 @@ public abstract class AbstractListActivity extends FragmentListActivity implemen // initialize action bar title with activity title ActivityMixin.setTitle(this, getTitle()); } - - @Override - public void onResume() { - paused = false; - super.onResume(); - } - - @Override - public void onPause() { - super.onPause(); - paused = true; - } - - /** - * Check if the current activity is paused. This must be called and acted - * upon only from the UI thread. - * - * @return <code>true</code> if the current activity is paused - */ - protected boolean isPaused() { - return paused; - } } diff --git a/main/src/cgeo/geocaching/activity/AbstractViewPagerActivity.java b/main/src/cgeo/geocaching/activity/AbstractViewPagerActivity.java index c7d4507..6e2900d 100644 --- a/main/src/cgeo/geocaching/activity/AbstractViewPagerActivity.java +++ b/main/src/cgeo/geocaching/activity/AbstractViewPagerActivity.java @@ -5,7 +5,6 @@ import cgeo.geocaching.utils.Log; import com.viewpagerindicator.TitlePageIndicator; import com.viewpagerindicator.TitleProvider; - import org.apache.commons.lang3.tuple.Pair; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; diff --git a/main/src/cgeo/geocaching/activity/ActivityMixin.java b/main/src/cgeo/geocaching/activity/ActivityMixin.java index c1a2678..bfd45da 100644 --- a/main/src/cgeo/geocaching/activity/ActivityMixin.java +++ b/main/src/cgeo/geocaching/activity/ActivityMixin.java @@ -125,6 +125,6 @@ public final class ActivityMixin { editText.getText().replace(start, end, completeText); int newCursor = moveCursor ? start + completeText.length() : start; - editText.setSelection(newCursor, newCursor); + editText.setSelection(newCursor); } } diff --git a/main/src/cgeo/geocaching/apps/AbstractLocusApp.java b/main/src/cgeo/geocaching/apps/AbstractLocusApp.java index d6c2fe6..c26a24d 100644 --- a/main/src/cgeo/geocaching/apps/AbstractLocusApp.java +++ b/main/src/cgeo/geocaching/apps/AbstractLocusApp.java @@ -13,7 +13,6 @@ import menion.android.locus.addon.publiclib.geoData.Point; import menion.android.locus.addon.publiclib.geoData.PointGeocachingData; import menion.android.locus.addon.publiclib.geoData.PointGeocachingDataWaypoint; import menion.android.locus.addon.publiclib.geoData.PointsData; - import org.apache.commons.lang3.time.FastDateFormat; import android.app.Activity; diff --git a/main/src/cgeo/geocaching/apps/cachelist/CacheListApp.java b/main/src/cgeo/geocaching/apps/cachelist/CacheListApp.java index ac5809e..40c4d92 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.Geocache; +import cgeo.geocaching.SearchResult; import cgeo.geocaching.apps.App; import android.app.Activity; diff --git a/main/src/cgeo/geocaching/concurrent/PriorityThreadFactory.java b/main/src/cgeo/geocaching/concurrent/PriorityThreadFactory.java index 76379de..0da198b 100644 --- a/main/src/cgeo/geocaching/concurrent/PriorityThreadFactory.java +++ b/main/src/cgeo/geocaching/concurrent/PriorityThreadFactory.java @@ -1,5 +1,7 @@ package cgeo.geocaching.concurrent; +import org.eclipse.jdt.annotation.NonNull; + import java.util.concurrent.ThreadFactory; /** @@ -12,6 +14,7 @@ public class PriorityThreadFactory implements ThreadFactory { this.priority = priority; } + @NonNull @Override public Thread newThread(Runnable r) { Thread result = new Thread(r); diff --git a/main/src/cgeo/geocaching/connector/AbstractConnector.java b/main/src/cgeo/geocaching/connector/AbstractConnector.java index 53a3bcb..ca05439 100644 --- a/main/src/cgeo/geocaching/connector/AbstractConnector.java +++ b/main/src/cgeo/geocaching/connector/AbstractConnector.java @@ -16,10 +16,10 @@ import cgeo.geocaching.connector.capability.ISearchByViewPort; import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.enumerations.LogType; import cgeo.geocaching.geopoint.Geopoint; -import cgeo.geocaching.utils.RunnableWithArgument; import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.NonNull; +import rx.util.functions.Action1; import java.util.ArrayList; import java.util.Collection; @@ -255,21 +255,21 @@ public abstract class AbstractConnector implements IConnector { List<UserAction> actions = getDefaultUserActions(); if (this instanceof ISearchByOwner) { - actions.add(new UserAction(R.string.user_menu_view_hidden, new RunnableWithArgument<UserAction.Context>() { + actions.add(new UserAction(R.string.user_menu_view_hidden, new Action1<Context>() { @Override - public void run(Context context) { + public void call(Context context) { CacheListActivity.startActivityOwner(context.activity, context.userName); } })); } if (this instanceof ISearchByFinder) { - actions.add(new UserAction(R.string.user_menu_view_found, new RunnableWithArgument<UserAction.Context>() { + actions.add(new UserAction(R.string.user_menu_view_found, new Action1<UserAction.Context>() { @Override - public void run(Context context) { - CacheListActivity.startActivityUserName(context.activity, context.userName); + public void call(Context context) { + CacheListActivity.startActivityFinder(context.activity, context.userName); } })); } @@ -283,10 +283,10 @@ public abstract class AbstractConnector implements IConnector { public List<UserAction> getDefaultUserActions() { final ArrayList<UserAction> actions = new ArrayList<UserAction>(); if (ContactsAddon.isAvailable()) { - actions.add(new UserAction(R.string.user_menu_open_contact, new RunnableWithArgument<UserAction.Context>() { + actions.add(new UserAction(R.string.user_menu_open_contact, new Action1<UserAction.Context>() { @Override - public void run(Context context) { + public void call(Context context) { ContactsAddon.openContactCard(context.activity, context.userName); } })); diff --git a/main/src/cgeo/geocaching/connector/ConnectorFactory.java b/main/src/cgeo/geocaching/connector/ConnectorFactory.java index 0081951..68e3142 100644 --- a/main/src/cgeo/geocaching/connector/ConnectorFactory.java +++ b/main/src/cgeo/geocaching/connector/ConnectorFactory.java @@ -27,6 +27,10 @@ import cgeo.geocaching.geopoint.Viewport; import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; +import rx.Observable; +import rx.concurrency.Schedulers; +import rx.util.functions.Func1; +import rx.util.functions.Func2; import java.util.ArrayList; import java.util.Arrays; @@ -176,14 +180,30 @@ public final class ConnectorFactory { } /** @see ISearchByViewPort#searchByViewport */ - public static SearchResult searchByViewport(final @NonNull Viewport viewport, final MapTokens tokens) { - final SearchResult result = new SearchResult(); - for (final ISearchByViewPort connector : searchByViewPortConns) { - if (connector.isActive()) { - result.addSearchResult(connector.searchByViewport(viewport, tokens)); + public static Observable<SearchResult> searchByViewport(final @NonNull Viewport viewport, final MapTokens tokens) { + return Observable.from(searchByViewPortConns).filter(new Func1<ISearchByViewPort, Boolean>() { + @Override + public Boolean call(final ISearchByViewPort connector) { + return connector.isActive(); } - } - return result; + }).parallel(new Func1<Observable<ISearchByViewPort>, Observable<SearchResult>>() { + @Override + public Observable<SearchResult> call(final Observable<ISearchByViewPort> connector) { + return connector.map(new Func1<ISearchByViewPort, SearchResult>() { + @Override + public SearchResult call(final ISearchByViewPort connector) { + return connector.searchByViewport(viewport, tokens); + } + }); + } + }, Schedulers.threadPoolForIO()).reduce(new SearchResult(), new Func2<SearchResult, SearchResult, SearchResult>() { + + @Override + public SearchResult call(final SearchResult result, final SearchResult searchResult) { + result.addSearchResult(searchResult); + return result; + } + }); } public static String getGeocodeFromURL(final String url) { @@ -200,6 +220,12 @@ public final class ConnectorFactory { return TRACKABLE_CONNECTORS; } + /** + * Get the geocode of a trackable from a URL. + * + * @param url + * @return {@code null} if the URL cannot be decoded + */ public static String getTrackableFromURL(final String url) { if (url == null) { return null; diff --git a/main/src/cgeo/geocaching/connector/UserAction.java b/main/src/cgeo/geocaching/connector/UserAction.java index d0c97bb..da03a95 100644 --- a/main/src/cgeo/geocaching/connector/UserAction.java +++ b/main/src/cgeo/geocaching/connector/UserAction.java @@ -1,8 +1,7 @@ package cgeo.geocaching.connector; -import cgeo.geocaching.utils.RunnableWithArgument; - import org.eclipse.jdt.annotation.NonNull; +import rx.util.functions.Action1; import android.app.Activity; @@ -19,14 +18,15 @@ public class UserAction { } public final int displayResourceId; - private final @NonNull RunnableWithArgument<Context> runnable; + private final @NonNull + Action1<Context> runnable; - public UserAction(int displayResourceId, final @NonNull RunnableWithArgument<UserAction.Context> runnable) { + public UserAction(int displayResourceId, final @NonNull Action1<Context> runnable) { this.displayResourceId = displayResourceId; this.runnable = runnable; } public void run(Context context) { - runnable.run(context); + runnable.call(context); } } diff --git a/main/src/cgeo/geocaching/connector/ec/ECApi.java b/main/src/cgeo/geocaching/connector/ec/ECApi.java index 03fce4d..9d786ea 100644 --- a/main/src/cgeo/geocaching/connector/ec/ECApi.java +++ b/main/src/cgeo/geocaching/connector/ec/ECApi.java @@ -17,7 +17,6 @@ import cgeo.geocaching.network.Parameters; import cgeo.geocaching.utils.Log; import ch.boye.httpclientandroidlib.HttpResponse; - import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.FastDateFormat; diff --git a/main/src/cgeo/geocaching/connector/ec/ECConnector.java b/main/src/cgeo/geocaching/connector/ec/ECConnector.java index 6da076b..71716fe 100644 --- a/main/src/cgeo/geocaching/connector/ec/ECConnector.java +++ b/main/src/cgeo/geocaching/connector/ec/ECConnector.java @@ -140,7 +140,7 @@ public class ECConnector extends AbstractConnector implements ISearchByGeocode, // invoke settings activity to insert login details if (status == StatusCode.NO_LOGIN_INFO_STORED && fromActivity != null) { - SettingsActivity.jumpToServicesPage(fromActivity); + SettingsActivity.openForScreen(R.string.preference_screen_ec, fromActivity); } } return status == StatusCode.NO_ERROR; diff --git a/main/src/cgeo/geocaching/connector/ec/ECLogin.java b/main/src/cgeo/geocaching/connector/ec/ECLogin.java index 52bd423..012bdc9 100644 --- a/main/src/cgeo/geocaching/connector/ec/ECLogin.java +++ b/main/src/cgeo/geocaching/connector/ec/ECLogin.java @@ -10,7 +10,6 @@ import cgeo.geocaching.settings.Settings; import cgeo.geocaching.utils.Log; import ch.boye.httpclientandroidlib.HttpResponse; - import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; import org.eclipse.jdt.annotation.Nullable; diff --git a/main/src/cgeo/geocaching/connector/gc/GCConnector.java b/main/src/cgeo/geocaching/connector/gc/GCConnector.java index e946748..a62b1f6 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCConnector.java +++ b/main/src/cgeo/geocaching/connector/gc/GCConnector.java @@ -27,12 +27,12 @@ import cgeo.geocaching.settings.Settings; import cgeo.geocaching.settings.SettingsActivity; import cgeo.geocaching.utils.CancellableHandler; import cgeo.geocaching.utils.Log; -import cgeo.geocaching.utils.RunnableWithArgument; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; +import rx.util.functions.Action1; import android.content.Context; import android.content.Intent; @@ -91,7 +91,7 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, @Override public boolean supportsPersonalNote() { - return Settings.isPremiumMember(); + return Settings.isGCPremiumMember(); } @Override @@ -285,7 +285,22 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, @Override protected String getCacheUrlPrefix() { - return CACHE_URL_SHORT; + return null; // UNUSED + } + + @Override + public String getGeocodeFromUrl(String url) { + // coord.info URLs + String code = StringUtils.substringAfterLast(url, "coord.info/"); + if (code != null && canHandle(code)) { + return code; + } + // expanded geocaching.com URLs + code = StringUtils.substringBetween(url, "/geocache/", "_"); + if (code != null && canHandle(code)) { + return code; + } + return null; } @Override @@ -316,7 +331,7 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, // invoke settings activity to insert login details if (status == StatusCode.NO_LOGIN_INFO_STORED && fromActivity != null) { - SettingsActivity.jumpToServicesPage(fromActivity); + SettingsActivity.openForScreen(R.string.preference_screen_gc, fromActivity); } } return status == StatusCode.NO_ERROR; @@ -379,17 +394,17 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, public @NonNull List<UserAction> getUserActions() { List<UserAction> actions = super.getUserActions(); - actions.add(new UserAction(R.string.user_menu_open_browser, new RunnableWithArgument<UserAction.Context>() { + actions.add(new UserAction(R.string.user_menu_open_browser, new Action1<UserAction.Context>() { @Override - public void run(cgeo.geocaching.connector.UserAction.Context context) { + public void call(cgeo.geocaching.connector.UserAction.Context context) { context.activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.geocaching.com/profile/?u=" + Network.encode(context.userName)))); } })); - actions.add(new UserAction(R.string.user_menu_send_message, new RunnableWithArgument<UserAction.Context>() { + actions.add(new UserAction(R.string.user_menu_send_message, new Action1<UserAction.Context>() { @Override - public void run(cgeo.geocaching.connector.UserAction.Context context) { + public void call(cgeo.geocaching.connector.UserAction.Context context) { context.activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.geocaching.com/email/?u=" + Network.encode(context.userName)))); } })); diff --git a/main/src/cgeo/geocaching/connector/gc/GCLogin.java b/main/src/cgeo/geocaching/connector/gc/GCLogin.java index a7cf6cf..92d488d 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCLogin.java +++ b/main/src/cgeo/geocaching/connector/gc/GCLogin.java @@ -14,7 +14,6 @@ import cgeo.geocaching.utils.MatcherWrapper; import cgeo.geocaching.utils.TextUtils; import ch.boye.httpclientandroidlib.HttpResponse; - import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; @@ -97,7 +96,7 @@ public class GCLogin extends AbstractLogin { } if (getLoginStatus(loginData)) { - Log.i("Already logged in Geocaching.com as " + username + " (" + Settings.getMemberStatus() + ')'); + Log.i("Already logged in Geocaching.com as " + username + " (" + Settings.getGCMemberStatus() + ')'); if (switchToEnglish(loginData) && retry) { return login(false); } @@ -132,7 +131,7 @@ public class GCLogin extends AbstractLogin { assert loginData != null; // Caught above if (getLoginStatus(loginData)) { - Log.i("Successfully logged in Geocaching.com as " + username + " (" + Settings.getMemberStatus() + ')'); + Log.i("Successfully logged in Geocaching.com as " + username + " (" + Settings.getGCMemberStatus() + ')'); if (switchToEnglish(loginData) && retry) { return login(false); @@ -204,9 +203,9 @@ public class GCLogin extends AbstractLogin { Log.e("getLoginStatus: bad cache count", e); } setActualCachesFound(cachesCount); - Settings.setMemberStatus(TextUtils.getMatch(page, GCConstants.PATTERN_MEMBER_STATUS, true, null)); + Settings.setGCMemberStatus(TextUtils.getMatch(page, GCConstants.PATTERN_MEMBER_STATUS, true, null)); if ( page.contains(GCConstants.MEMBER_STATUS_RENEW) ) { - Settings.setMemberStatus(GCConstants.MEMBER_STATUS_PM); + Settings.setGCMemberStatus(GCConstants.MEMBER_STATUS_PM); } return true; } @@ -259,9 +258,9 @@ public class GCLogin extends AbstractLogin { final String responseData = StringUtils.defaultString(Network.getResponseData(Network.getRequest("http://www.geocaching.com/my/"))); final String profile = TextUtils.replaceWhitespace(responseData); - Settings.setMemberStatus(TextUtils.getMatch(profile, GCConstants.PATTERN_MEMBER_STATUS, true, null)); + Settings.setGCMemberStatus(TextUtils.getMatch(profile, GCConstants.PATTERN_MEMBER_STATUS, true, null)); if (profile.contains(GCConstants.MEMBER_STATUS_RENEW)) { - Settings.setMemberStatus(GCConstants.MEMBER_STATUS_PM); + Settings.setGCMemberStatus(GCConstants.MEMBER_STATUS_PM); } setActualCachesFound(Integer.parseInt(TextUtils.getMatch(profile, GCConstants.PATTERN_CACHES_FOUND, true, "-1").replaceAll("[,.]", ""))); diff --git a/main/src/cgeo/geocaching/connector/gc/GCMap.java b/main/src/cgeo/geocaching/connector/gc/GCMap.java index 6c94150..2782b64 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCMap.java +++ b/main/src/cgeo/geocaching/connector/gc/GCMap.java @@ -368,7 +368,7 @@ public class GCMap { } } - if (strategy.flags.contains(StrategyFlag.SEARCH_NEARBY) && Settings.isPremiumMember()) { + if (strategy.flags.contains(StrategyFlag.SEARCH_NEARBY) && Settings.isGCPremiumMember()) { final Geopoint center = viewport.getCenter(); if ((lastSearchViewport == null) || !lastSearchViewport.contains(center)) { //FIXME We don't have a RecaptchaReceiver!? diff --git a/main/src/cgeo/geocaching/connector/gc/GCParser.java b/main/src/cgeo/geocaching/connector/gc/GCParser.java index 62ccb14..70c3b2a 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCParser.java +++ b/main/src/cgeo/geocaching/connector/gc/GCParser.java @@ -36,7 +36,6 @@ import cgeo.geocaching.utils.SynchronizedDateFormat; import cgeo.geocaching.utils.TextUtils; import ch.boye.httpclientandroidlib.HttpResponse; - import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringEscapeUtils; @@ -282,7 +281,7 @@ public abstract class GCParser { recaptchaText = thread.getText(); } - if (!cids.isEmpty() && (Settings.isPremiumMember() || showCaptcha) && ((thread == null || StringUtils.isBlank(thread.getChallenge())) || StringUtils.isNotBlank(recaptchaText))) { + if (!cids.isEmpty() && (Settings.isGCPremiumMember() || showCaptcha) && ((thread == null || StringUtils.isBlank(thread.getChallenge())) || StringUtils.isNotBlank(recaptchaText))) { Log.i("Trying to get .loc for " + cids.size() + " caches"); try { @@ -347,6 +346,9 @@ public abstract class GCParser { // attention: parseCacheFromText already stores implicitly through searchResult.addCache if (searchResult != null && !searchResult.getGeocodes().isEmpty()) { final Geocache cache = searchResult.getFirstCacheFromResult(LoadFlags.LOAD_CACHE_OR_DB); + if (cache == null) { + return null; + } getExtraOnlineInfo(cache, page, handler); // too late: it is already stored through parseCacheFromText cache.setDetailedUpdatedNow(); @@ -741,7 +743,7 @@ public abstract class GCParser { cache.parseWaypointsFromNote(); // logs - cache.setLogs(loadLogsFromDetails(page, cache, false, true)); + cache.setLogs(getLogsFromDetails(page, false)); // last check for necessary cache conditions if (StringUtils.isBlank(cache.getGeocode())) { @@ -1380,8 +1382,7 @@ public abstract class GCParser { } private static boolean changeFavorite(final Geocache cache, final boolean add) { - final String page = requestHtmlPage(cache.getGeocode(), null, "n", "0"); - final String userToken = TextUtils.getMatch(page, GCConstants.PATTERN_USERTOKEN, ""); + final String userToken = getUserToken(cache); if (StringUtils.isEmpty(userToken)) { return false; } @@ -1400,6 +1401,11 @@ public abstract class GCParser { return false; } + private static String getUserToken(final Geocache cache) { + final String page = requestHtmlPage(cache.getGeocode(), null, "n", "0"); + return TextUtils.getMatch(page, GCConstants.PATTERN_USERTOKEN, ""); + } + /** * Removes the cache from the favorites. * @@ -1613,19 +1619,20 @@ public abstract class GCParser { } /** - * Load logs from a cache details page. + * Extract logs from a cache details page. * * @param page * the text of the details page - * @param cache - * the cache object to put the logs in * @param friends - * retrieve friend logs + * return friends logs only (will require a network request) + * @return a list of log entries or <code>null</code> if the logs could not be retrieved + * */ - private static List<LogEntry> loadLogsFromDetails(final String page, final Geocache cache, final boolean friends, final boolean getDataFromPage) { + @Nullable + private static List<LogEntry> getLogsFromDetails(final String page, final boolean friends) { String rawResponse; - if (!getDataFromPage) { + if (friends) { final MatcherWrapper userTokenMatcher = new MatcherWrapper(GCConstants.PATTERN_USERTOKEN, page); if (!userTokenMatcher.find()) { Log.e("GCParser.loadLogsFromDetails: unable to extract userToken"); @@ -1712,7 +1719,7 @@ public abstract class GCParser { final JSONArray images = entry.getJSONArray("Images"); for (int i = 0; i < images.length(); i++) { final JSONObject image = images.getJSONObject(i); - final String url = "http://img.geocaching.com/cache/log/large/" + image.getString("FileName"); + final String url = "http://imgcdn.geocaching.com/cache/log/large/" + image.getString("FileName"); final String title = TextUtils.removeControlCharacters(image.getString("Name")); final Image logImage = new Image(url, title); logDone.addLogImage(logImage); @@ -1825,7 +1832,7 @@ public abstract class GCParser { if (Settings.isFriendLogsWanted()) { CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_logs); final List<LogEntry> allLogs = cache.getLogs(); - final List<LogEntry> friendLogs = loadLogsFromDetails(page, cache, true, false); + final List<LogEntry> friendLogs = getLogsFromDetails(page, true); if (friendLogs != null) { for (final LogEntry log : friendLogs) { if (allLogs.contains(log)) { @@ -1860,8 +1867,7 @@ public abstract class GCParser { } public static boolean editModifiedCoordinates(Geocache cache, Geopoint wpt) { - final String page = requestHtmlPage(cache.getGeocode(), null, "n", "0"); - final String userToken = TextUtils.getMatch(page, GCConstants.PATTERN_USERTOKEN, ""); + final String userToken = getUserToken(cache); if (StringUtils.isEmpty(userToken)) { return false; } @@ -1896,8 +1902,7 @@ public abstract class GCParser { } public static boolean uploadPersonalNote(Geocache cache) { - final String page = requestHtmlPage(cache.getGeocode(), null, "n", "0"); - final String userToken = TextUtils.getMatch(page, GCConstants.PATTERN_USERTOKEN, ""); + final String userToken = getUserToken(cache); if (StringUtils.isEmpty(userToken)) { return false; } diff --git a/main/src/cgeo/geocaching/connector/oc/OkapiClient.java b/main/src/cgeo/geocaching/connector/oc/OkapiClient.java index 2175935..c33a90e 100644 --- a/main/src/cgeo/geocaching/connector/oc/OkapiClient.java +++ b/main/src/cgeo/geocaching/connector/oc/OkapiClient.java @@ -75,7 +75,8 @@ final class OkapiClient { private static final String CACHE_STATUS_ARCHIVED = "Archived"; private static final String CACHE_STATUS_DISABLED = "Temporarily unavailable"; private static final String CACHE_IS_FOUND = "is_found"; - private static final String CACHE_SIZE = "size"; + private static final String CACHE_SIZE_DEPRECATED = "size"; + private static final String CACHE_SIZE2 = "size2"; private static final String CACHE_VOTES = "rating_votes"; private static final String CACHE_NOTFOUNDS = "notfounds"; private static final String CACHE_FOUNDS = "founds"; @@ -112,7 +113,7 @@ final class OkapiClient { // the several realms of possible fields for cache retrieval: // Core: for livemap requests (L3 - only with level 3 auth) // Additional: additional fields for full cache (L3 - only for level 3 auth, current - only for connectors with current api) - private static final String SERVICE_CACHE_CORE_FIELDS = "code|name|location|type|status|difficulty|terrain|size|date_hidden"; + private static final String SERVICE_CACHE_CORE_FIELDS = "code|name|location|type|status|difficulty|terrain|size|size2|date_hidden"; private static final String SERVICE_CACHE_CORE_L3_FIELDS = "is_found"; private static final String SERVICE_CACHE_ADDITIONAL_FIELDS = "owner|founds|notfounds|rating|rating_votes|recommendations|description|hint|images|latest_logs|alt_wpts|attrnames|req_passwd"; private static final String SERVICE_CACHE_ADDITIONAL_CURRENT_FIELDS = "gc_code|attribution_note|attr_acodes"; @@ -152,19 +153,14 @@ final class OkapiClient { } public static List<Geocache> getCachesByOwner(final String username, final OCApiConnector connector) { - final Parameters params = new Parameters("search_method", METHOD_SEARCH_ALL); - final Map<String, String> valueMap = new LinkedHashMap<String, String>(); - final @Nullable - String uuid = getUserUUID(connector, username); - if (StringUtils.isEmpty(uuid)) { - return Collections.emptyList(); - } - valueMap.put("owner_uuid", uuid); - - return requestCaches(connector, params, valueMap); + return getCachesByUser(username, connector, "owner_uuid"); } public static List<Geocache> getCachesByFinder(final String username, final OCApiConnector connector) { + return getCachesByUser(username, connector, "found_by"); + } + + private static List<Geocache> getCachesByUser(final String username, final OCApiConnector connector, final String userRequestParam) { final Parameters params = new Parameters("search_method", METHOD_SEARCH_ALL); final Map<String, String> valueMap = new LinkedHashMap<String, String>(); final @Nullable @@ -172,7 +168,7 @@ final class OkapiClient { if (StringUtils.isEmpty(uuid)) { return Collections.emptyList(); } - valueMap.put("found_by", uuid); + valueMap.put(userRequestParam, uuid); return requestCaches(connector, params, valueMap); } @@ -567,12 +563,25 @@ final class OkapiClient { } private static CacheSize getCacheSize(final JSONObject response) { - if (response.isNull(CACHE_SIZE)) { + if (response.isNull(CACHE_SIZE2)) { + return getCacheSizeDeprecated(response); + } + try { + final String size = response.getString(CACHE_SIZE2); + return CacheSize.getById(size); + } catch (JSONException e) { + Log.e("OkapiClient.getCacheSize", e); + return getCacheSizeDeprecated(response); + } + } + + private static CacheSize getCacheSizeDeprecated(final JSONObject response) { + if (response.isNull(CACHE_SIZE_DEPRECATED)) { return CacheSize.NOT_CHOSEN; } double size = 0; try { - size = response.getDouble(CACHE_SIZE); + size = response.getDouble(CACHE_SIZE_DEPRECATED); } catch (final JSONException e) { Log.e("OkapiClient.getCacheSize", e); } @@ -586,7 +595,7 @@ final class OkapiClient { case 4: return CacheSize.LARGE; case 5: - return CacheSize.LARGE; + return CacheSize.VERY_LARGE; default: break; } diff --git a/main/src/cgeo/geocaching/connector/ox/OXGPXParser.java b/main/src/cgeo/geocaching/connector/ox/OXGPXParser.java index f72b698..5f11a11 100644 --- a/main/src/cgeo/geocaching/connector/ox/OXGPXParser.java +++ b/main/src/cgeo/geocaching/connector/ox/OXGPXParser.java @@ -28,10 +28,10 @@ public class OXGPXParser extends GPX10Parser { /** * The short description of OX caches contains "title by owner, type(T/D/Awesomeness)". That is a lot of * duplication. - * + * * @param cache */ private static void removeTitleFromShortDescription(final @NonNull Geocache cache) { - cache.setShortDescription(StringUtils.substringAfterLast(cache.getShortDescription(), ",")); + cache.setShortDescription(StringUtils.trim(StringUtils.substringAfterLast(cache.getShortDescription(), ","))); } } diff --git a/main/src/cgeo/geocaching/connector/ox/OpenCachingApi.java b/main/src/cgeo/geocaching/connector/ox/OpenCachingApi.java index 2defc52..0137af4 100644 --- a/main/src/cgeo/geocaching/connector/ox/OpenCachingApi.java +++ b/main/src/cgeo/geocaching/connector/ox/OpenCachingApi.java @@ -11,7 +11,6 @@ import cgeo.geocaching.utils.CryptUtils; import cgeo.geocaching.utils.Log; import ch.boye.httpclientandroidlib.HttpResponse; - import org.apache.commons.collections4.CollectionUtils; import org.eclipse.jdt.annotation.NonNull; diff --git a/main/src/cgeo/geocaching/connector/trackable/AbstractTrackableConnector.java b/main/src/cgeo/geocaching/connector/trackable/AbstractTrackableConnector.java index 69efddc..fb554b9 100644 --- a/main/src/cgeo/geocaching/connector/trackable/AbstractTrackableConnector.java +++ b/main/src/cgeo/geocaching/connector/trackable/AbstractTrackableConnector.java @@ -4,6 +4,7 @@ import cgeo.geocaching.connector.AbstractConnector; import cgeo.geocaching.connector.UserAction; import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; import java.util.List; @@ -15,7 +16,8 @@ public abstract class AbstractTrackableConnector implements TrackableConnector { } @Override - public String getTrackableCodeFromUrl(@NonNull String url) { + public @Nullable + String getTrackableCodeFromUrl(@NonNull String url) { return null; } diff --git a/main/src/cgeo/geocaching/connector/trackable/GeokretyConnector.java b/main/src/cgeo/geocaching/connector/trackable/GeokretyConnector.java index 8387076..03052f9 100644 --- a/main/src/cgeo/geocaching/connector/trackable/GeokretyConnector.java +++ b/main/src/cgeo/geocaching/connector/trackable/GeokretyConnector.java @@ -4,6 +4,10 @@ import cgeo.geocaching.Trackable; import cgeo.geocaching.network.Network; import cgeo.geocaching.utils.Log; +import org.apache.commons.lang3.StringUtils; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; + import java.util.regex.Pattern; public class GeokretyConnector extends AbstractTrackableConnector { @@ -29,7 +33,7 @@ public class GeokretyConnector extends AbstractTrackableConnector { return GeokretyParser.parse(page); } - private static int getId(String geocode) { + protected static int getId(String geocode) { try { final String hex = geocode.substring(2); return Integer.parseInt(hex, 16); @@ -39,4 +43,25 @@ public class GeokretyConnector extends AbstractTrackableConnector { return -1; } + @Override + public @Nullable + String getTrackableCodeFromUrl(@NonNull String url) { + // http://geokrety.org/konkret.php?id=38545 + String id = StringUtils.substringAfterLast(url, "konkret.php?id="); + if (StringUtils.isNumeric(id)) { + return geocode(Integer.parseInt(id)); + } + return null; + } + + /** + * Get geocode from geokrety id + * + * @param id + * @return + */ + public static String geocode(final int id) { + return String.format("GK%04X", id); + } + } diff --git a/main/src/cgeo/geocaching/connector/trackable/GeokretyParser.java b/main/src/cgeo/geocaching/connector/trackable/GeokretyParser.java index ee8c8c0..0e64abd 100644 --- a/main/src/cgeo/geocaching/connector/trackable/GeokretyParser.java +++ b/main/src/cgeo/geocaching/connector/trackable/GeokretyParser.java @@ -37,8 +37,8 @@ public class GeokretyParser { public void start(Attributes attributes) { try { final String kretyId = attributes.getValue("id"); - if (StringUtils.isNotBlank(kretyId)) { - trackable.setGeocode(geocode(Integer.parseInt(kretyId))); + if (StringUtils.isNumeric(kretyId)) { + trackable.setGeocode(GeokretyConnector.geocode(Integer.parseInt(kretyId))); } final String distance = attributes.getValue("dist"); if (StringUtils.isNotBlank(distance)) { @@ -88,8 +88,4 @@ public class GeokretyParser { } return null; } - - protected static String geocode(final int id) { - return String.format("GK%04X", id); - } } diff --git a/main/src/cgeo/geocaching/connector/trackable/TrackableConnector.java b/main/src/cgeo/geocaching/connector/trackable/TrackableConnector.java index 0990d96..6071b5f 100644 --- a/main/src/cgeo/geocaching/connector/trackable/TrackableConnector.java +++ b/main/src/cgeo/geocaching/connector/trackable/TrackableConnector.java @@ -4,6 +4,7 @@ import cgeo.geocaching.Trackable; import cgeo.geocaching.connector.UserAction; import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; import java.util.List; @@ -21,7 +22,8 @@ public interface TrackableConnector { public Trackable searchTrackable(String geocode, String guid, String id); - public String getTrackableCodeFromUrl(final @NonNull String url); + public @Nullable + String getTrackableCodeFromUrl(final @NonNull String url); public @NonNull List<UserAction> getUserActions(); diff --git a/main/src/cgeo/geocaching/connector/trackable/TravelBugConnector.java b/main/src/cgeo/geocaching/connector/trackable/TravelBugConnector.java index dad285c..77848d7 100644 --- a/main/src/cgeo/geocaching/connector/trackable/TravelBugConnector.java +++ b/main/src/cgeo/geocaching/connector/trackable/TravelBugConnector.java @@ -7,6 +7,7 @@ import cgeo.geocaching.connector.gc.GCParser; import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; import java.util.List; import java.util.regex.Pattern; @@ -20,7 +21,7 @@ public class TravelBugConnector extends AbstractTrackableConnector { @Override public boolean canHandleTrackable(String geocode) { - return TravelBugConnector.PATTERN_TB_CODE.matcher(geocode).matches(); + return TravelBugConnector.PATTERN_TB_CODE.matcher(geocode).matches() && !StringUtils.startsWithIgnoreCase(geocode, "GC"); } @Override @@ -54,8 +55,18 @@ public class TravelBugConnector extends AbstractTrackableConnector { } @Override - public String getTrackableCodeFromUrl(@NonNull String url) { - return StringUtils.substringAfterLast(url, "?tracker="); + public @Nullable + String getTrackableCodeFromUrl(@NonNull String url) { + // coord.info URLs + String code = StringUtils.substringAfterLast(url, "coord.info/"); + if (code != null && canHandleTrackable(code)) { + return code; + } + code = StringUtils.substringAfterLast(url, "?tracker="); + if (code != null && canHandleTrackable(code)) { + return code; + } + return null; } @Override diff --git a/main/src/cgeo/geocaching/enumerations/CacheAttribute.java b/main/src/cgeo/geocaching/enumerations/CacheAttribute.java index 472bad5..0703c3c 100644 --- a/main/src/cgeo/geocaching/enumerations/CacheAttribute.java +++ b/main/src/cgeo/geocaching/enumerations/CacheAttribute.java @@ -164,15 +164,11 @@ public enum CacheAttribute { } private final static Map<String, CacheAttribute> FIND_BY_GCRAWNAME; - private final static SparseArray<CacheAttribute> FIND_BY_GCID = new SparseArray<CacheAttribute>(); private final static SparseArray<CacheAttribute> FIND_BY_OCACODE = new SparseArray<CacheAttribute>(); static { final HashMap<String, CacheAttribute> mapGcRawNames = new HashMap<String, CacheAttribute>(); for (CacheAttribute attr : values()) { mapGcRawNames.put(attr.rawName, attr); - if (attr.gcid != NO_ID) { - FIND_BY_GCID.put(attr.gcid, attr); - } if (attr.ocacode != NO_ID) { FIND_BY_OCACODE.put(attr.ocacode, attr); } @@ -184,10 +180,6 @@ public enum CacheAttribute { 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 getByOcACode(final int ocAcode) { return FIND_BY_OCACODE.get(ocAcode); } diff --git a/main/src/cgeo/geocaching/enumerations/CacheListType.java b/main/src/cgeo/geocaching/enumerations/CacheListType.java index f482d5b..1fce282 100644 --- a/main/src/cgeo/geocaching/enumerations/CacheListType.java +++ b/main/src/cgeo/geocaching/enumerations/CacheListType.java @@ -1,23 +1,32 @@ package cgeo.geocaching.enumerations; +import cgeo.geocaching.loaders.AbstractSearchLoader.CacheListLoaderType; + public enum CacheListType { - OFFLINE(true), - POCKET(false), - HISTORY(true), - NEAREST(false), - COORDINATE(false), - KEYWORD(false), - ADDRESS(false), - USERNAME(false), - OWNER(false), - MAP(false); + OFFLINE(true, CacheListLoaderType.OFFLINE), + POCKET(false, CacheListLoaderType.POCKET), + HISTORY(true, CacheListLoaderType.HISTORY), + NEAREST(false, CacheListLoaderType.NEAREST), + COORDINATE(false, CacheListLoaderType.COORDINATE), + KEYWORD(false, CacheListLoaderType.KEYWORD), + ADDRESS(false, CacheListLoaderType.ADDRESS), + FINDER(false, CacheListLoaderType.FINDER), + OWNER(false, CacheListLoaderType.OWNER), + MAP(false, CacheListLoaderType.MAP); /** * whether or not this list allows switching to another list */ public final boolean canSwitch; - private CacheListType(final boolean canSwitch) { + public final CacheListLoaderType loaderType; + + CacheListType(final boolean canSwitch, final CacheListLoaderType loaderType) { this.canSwitch = canSwitch; + this.loaderType = loaderType; + } + + public int getLoaderId() { + return loaderType.getLoaderId(); } } diff --git a/main/src/cgeo/geocaching/enumerations/CacheSize.java b/main/src/cgeo/geocaching/enumerations/CacheSize.java index ee42c66..1255455 100644 --- a/main/src/cgeo/geocaching/enumerations/CacheSize.java +++ b/main/src/cgeo/geocaching/enumerations/CacheSize.java @@ -12,30 +12,38 @@ import java.util.Map; * Enum listing cache sizes */ public enum CacheSize { - MICRO("Micro", 1, R.string.cache_size_micro), - SMALL("Small", 2, R.string.cache_size_small), - REGULAR("Regular", 3, R.string.cache_size_regular), - LARGE("Large", 4, R.string.cache_size_large), - VIRTUAL("Virtual", 0, R.string.cache_size_virtual), - NOT_CHOSEN("Not chosen", 0, R.string.cache_size_notchosen), - OTHER("Other", 0, R.string.cache_size_other), - UNKNOWN("Unknown", 0, R.string.cache_size_unknown); // CacheSize not init. yet + NANO("Nano", 0, R.string.cache_size_nano, "nano"), // used by OC only + MICRO("Micro", 1, R.string.cache_size_micro, "micro"), + SMALL("Small", 2, R.string.cache_size_small, "small"), + REGULAR("Regular", 3, R.string.cache_size_regular, "regular"), + LARGE("Large", 4, R.string.cache_size_large, "large"), + VERY_LARGE("Very large", 5, R.string.cache_size_very_large, "xlarge"), // used by OC only + NOT_CHOSEN("Not chosen", 6, R.string.cache_size_notchosen, ""), + VIRTUAL("Virtual", 7, R.string.cache_size_virtual, "none"), + OTHER("Other", 8, R.string.cache_size_other, "other"), + UNKNOWN("Unknown", -1, R.string.cache_size_unknown, ""); // CacheSize not init. yet public final String id; public final int comparable; private final int stringId; + /** + * lookup for OC JSON requests (the numeric size is deprecated for OC) + */ + private final String ocSize2; - CacheSize(String id, int comparable, int stringId) { + CacheSize(final String id, final int comparable, final int stringId, final String ocSize2) { this.id = id; this.comparable = comparable; this.stringId = stringId; + this.ocSize2 = ocSize2; } final private static Map<String, CacheSize> FIND_BY_ID; static { final HashMap<String, CacheSize> mapping = new HashMap<String, CacheSize>(); - for (CacheSize cs : values()) { + for (final CacheSize cs : values()) { mapping.put(cs.id.toLowerCase(Locale.US), cs); + mapping.put(cs.ocSize2.toLowerCase(Locale.US), cs); } // add medium as additional string for Regular mapping.put("medium", CacheSize.REGULAR); @@ -61,21 +69,21 @@ public enum CacheSize { /** * Bad GPX files can contain the container size encoded as number. - * + * * @param id * @return */ private static CacheSize getByNumber(final String id) { try { - int numerical = Integer.parseInt(id); + final int numerical = Integer.parseInt(id); if (numerical != 0) { - for (CacheSize size : CacheSize.values()) { + for (final CacheSize size : CacheSize.values()) { if (size.comparable == numerical) { return size; } } } - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { // ignore, as this might be a number or not } return UNKNOWN; @@ -85,4 +93,3 @@ public enum CacheSize { return CgeoApplication.getInstance().getBaseContext().getResources().getString(stringId); } } - diff --git a/main/src/cgeo/geocaching/enumerations/CacheType.java b/main/src/cgeo/geocaching/enumerations/CacheType.java index c952ba0..35fe7a1 100644 --- a/main/src/cgeo/geocaching/enumerations/CacheType.java +++ b/main/src/cgeo/geocaching/enumerations/CacheType.java @@ -1,8 +1,8 @@ package cgeo.geocaching.enumerations; +import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.ICache; import cgeo.geocaching.R; -import cgeo.geocaching.CgeoApplication; import java.util.Collections; import java.util.HashMap; diff --git a/main/src/cgeo/geocaching/enumerations/LiveMapStrategy.java b/main/src/cgeo/geocaching/enumerations/LiveMapStrategy.java index 710c3ba..5bcaf4a 100644 --- a/main/src/cgeo/geocaching/enumerations/LiveMapStrategy.java +++ b/main/src/cgeo/geocaching/enumerations/LiveMapStrategy.java @@ -1,7 +1,7 @@ package cgeo.geocaching.enumerations; -import cgeo.geocaching.R; import cgeo.geocaching.CgeoApplication; +import cgeo.geocaching.R; import java.util.EnumSet; diff --git a/main/src/cgeo/geocaching/enumerations/LogTypeTrackable.java b/main/src/cgeo/geocaching/enumerations/LogTypeTrackable.java index 68a17a5..e008294 100644 --- a/main/src/cgeo/geocaching/enumerations/LogTypeTrackable.java +++ b/main/src/cgeo/geocaching/enumerations/LogTypeTrackable.java @@ -1,29 +1,22 @@ package cgeo.geocaching.enumerations; +import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.R; public enum LogTypeTrackable { - DO_NOTHING(0, "", R.string.log_tb_nothing), - VISITED(1, "_Visited", R.string.log_tb_visit), - DROPPED_OFF(2, "_DroppedOff", R.string.log_tb_drop); + DO_NOTHING("", R.string.log_tb_nothing), + VISITED("_Visited", R.string.log_tb_visit), + DROPPED_OFF("_DroppedOff", R.string.log_tb_drop); - final public int id; final public String action; - final public int resourceId; + final private int resourceId; - LogTypeTrackable(int id, String action, int resourceId) { - this.id = id; + LogTypeTrackable(String action, int resourceId) { this.action = action; this.resourceId = resourceId; } - public static LogTypeTrackable findById(int id) { - for (LogTypeTrackable logType : values()) { - if (logType.id == id) { - return logType; - } - } - return DO_NOTHING; + public String getLabel() { + return CgeoApplication.getInstance().getString(resourceId); } - } diff --git a/main/src/cgeo/geocaching/enumerations/StatusCode.java b/main/src/cgeo/geocaching/enumerations/StatusCode.java index e1a1aaa..8bda371 100644 --- a/main/src/cgeo/geocaching/enumerations/StatusCode.java +++ b/main/src/cgeo/geocaching/enumerations/StatusCode.java @@ -6,7 +6,6 @@ import android.content.res.Resources; public enum StatusCode { - COMMUNICATION_NOT_STARTED(R.string.err_start), NO_ERROR(R.string.err_none), LOG_SAVED(R.string.info_log_saved), LOGIN_PARSE_ERROR(R.string.err_parse), @@ -24,7 +23,6 @@ public enum StatusCode { LOG_POST_ERROR(R.string.err_log_post_failed), LOG_POST_ERROR_EC(R.string.err_log_post_failed_ec), 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), LOGIMAGE_POST_ERROR(R.string.err_logimage_post_failed); diff --git a/main/src/cgeo/geocaching/export/GpxExport.java b/main/src/cgeo/geocaching/export/GpxExport.java index a2e0f93..08fca0b 100644 --- a/main/src/cgeo/geocaching/export/GpxExport.java +++ b/main/src/cgeo/geocaching/export/GpxExport.java @@ -1,8 +1,8 @@ package cgeo.geocaching.export; +import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.Geocache; import cgeo.geocaching.R; -import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.activity.ActivityMixin; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.utils.AsyncTaskWithProgress; diff --git a/main/src/cgeo/geocaching/files/LocalStorage.java b/main/src/cgeo/geocaching/files/LocalStorage.java index edbecf6..133f39a 100644 --- a/main/src/cgeo/geocaching/files/LocalStorage.java +++ b/main/src/cgeo/geocaching/files/LocalStorage.java @@ -7,7 +7,6 @@ import cgeo.geocaching.utils.Log; import ch.boye.httpclientandroidlib.Header; import ch.boye.httpclientandroidlib.HttpResponse; - import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.Nullable; @@ -122,7 +121,7 @@ public final class LocalStorage { * the geocode * @return the cache directory */ - public static File getStorageDir(final String geocode) { + public static File getStorageDir(@Nullable final String geocode) { return storageDir(getStorage(), geocode); } @@ -134,11 +133,11 @@ public final class LocalStorage { * the geocode * @return the cache directory */ - private static File getStorageSecDir(final String geocode) { + private static File getStorageSecDir(@Nullable final String geocode) { return storageDir(getStorageSec(), geocode); } - private static File storageDir(final File base, final String geocode) { + private static File storageDir(final File base, @Nullable final String geocode) { return new File(base, StringUtils.defaultIfEmpty(geocode, "_others")); } @@ -155,7 +154,7 @@ public final class LocalStorage { * true if an url was given, false if a file name was given * @return the file */ - public static File getStorageFile(final String geocode, final String fileNameOrUrl, final boolean isUrl, final boolean createDirs) { + public static File getStorageFile(@Nullable final String geocode, final String fileNameOrUrl, final boolean isUrl, final boolean createDirs) { return buildFile(getStorageDir(geocode), fileNameOrUrl, isUrl, createDirs); } diff --git a/main/src/cgeo/geocaching/files/SimpleDirChooser.java b/main/src/cgeo/geocaching/files/SimpleDirChooser.java index 3e09cc4..e63c09f 100644 --- a/main/src/cgeo/geocaching/files/SimpleDirChooser.java +++ b/main/src/cgeo/geocaching/files/SimpleDirChooser.java @@ -89,9 +89,9 @@ public class SimpleDirChooser extends AbstractListActivity { } public void editPath() { - AlertDialog.Builder builder = new AlertDialog.Builder(SimpleDirChooser.this); + AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.simple_dir_chooser_current_path); - final EditText input = new EditText(SimpleDirChooser.this); + final EditText input = new EditText(this); input.setInputType(InputType.TYPE_CLASS_TEXT); input.setText(currentDir.getPath()); builder.setView(input); diff --git a/main/src/cgeo/geocaching/filter/DistanceFilter.java b/main/src/cgeo/geocaching/filter/DistanceFilter.java index 54225d2..cddcf72 100644 --- a/main/src/cgeo/geocaching/filter/DistanceFilter.java +++ b/main/src/cgeo/geocaching/filter/DistanceFilter.java @@ -1,9 +1,9 @@ package cgeo.geocaching.filter; +import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.Geocache; import cgeo.geocaching.IGeoData; import cgeo.geocaching.R; -import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.geopoint.Geopoint; import java.util.ArrayList; diff --git a/main/src/cgeo/geocaching/filter/FilterUserInterface.java b/main/src/cgeo/geocaching/filter/FilterUserInterface.java index d77341b..0033b9a 100644 --- a/main/src/cgeo/geocaching/filter/FilterUserInterface.java +++ b/main/src/cgeo/geocaching/filter/FilterUserInterface.java @@ -5,7 +5,8 @@ import cgeo.geocaching.R; import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.utils.Log; -import cgeo.geocaching.utils.RunnableWithArgument; + +import rx.util.functions.Action1; import android.app.Activity; import android.app.AlertDialog; @@ -77,7 +78,7 @@ public final class FilterUserInterface { registry.add(new FactoryEntry(res.getString(resourceId), factoryClass)); } - public void selectFilter(final RunnableWithArgument<IFilter> runAfterwards) { + public void selectFilter(final Action1<IFilter> runAfterwards) { final AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setTitle(R.string.caches_filter_title); @@ -89,7 +90,7 @@ public final class FilterUserInterface { FactoryEntry entry = adapter.getItem(itemIndex); // reset? if (entry.filterFactory == null) { - runAfterwards.run(null); + runAfterwards.call(null); } else { try { @@ -105,10 +106,10 @@ public final class FilterUserInterface { builder.create().show(); } - private void selectFromFactory(final IFilterFactory factory, final String menuTitle, final RunnableWithArgument<IFilter> runAfterwards) { + private void selectFromFactory(final IFilterFactory factory, final String menuTitle, final Action1<IFilter> runAfterwards) { final List<IFilter> filters = Collections.unmodifiableList(factory.getFilters()); if (filters.size() == 1) { - runAfterwards.run(filters.get(0)); + runAfterwards.call(filters.get(0)); return; } @@ -119,7 +120,7 @@ public final class FilterUserInterface { builder.setAdapter(adapter, new DialogInterface.OnClickListener() { @Override public void onClick(final DialogInterface dialog, final int item) { - runAfterwards.run(filters.get(item)); + runAfterwards.call(filters.get(item)); } }); diff --git a/main/src/cgeo/geocaching/filter/ModifiedFilter.java b/main/src/cgeo/geocaching/filter/ModifiedFilter.java index d976b69..2ac088a 100644 --- a/main/src/cgeo/geocaching/filter/ModifiedFilter.java +++ b/main/src/cgeo/geocaching/filter/ModifiedFilter.java @@ -1,8 +1,8 @@ package cgeo.geocaching.filter; +import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.Geocache; import cgeo.geocaching.R; -import cgeo.geocaching.CgeoApplication; import java.util.Collections; import java.util.List; diff --git a/main/src/cgeo/geocaching/geopoint/Geopoint.java b/main/src/cgeo/geocaching/geopoint/Geopoint.java index f21df01..e91a93d 100644 --- a/main/src/cgeo/geocaching/geopoint/Geopoint.java +++ b/main/src/cgeo/geocaching/geopoint/Geopoint.java @@ -4,6 +4,7 @@ import cgeo.geocaching.ICoordinates; import cgeo.geocaching.R; import org.apache.commons.lang3.StringUtils; +import org.eclipse.jdt.annotation.NonNull; import android.location.Location; import android.os.Build; @@ -11,10 +12,13 @@ import android.os.Parcel; import android.os.Parcelable; /** - * Abstraction of geographic point. + * Abstraction of geographic point. This class is immutable. */ public final class Geopoint implements ICoordinates, Parcelable { - public static final Geopoint ZERO = new Geopoint(0.0, 0.0); + /** + * Reusable default object + */ + public static final @NonNull Geopoint ZERO = new Geopoint(0.0, 0.0); private static final double DEG_TO_RAD = Math.PI / 180; private static final double RAD_TO_DEG = 180 / Math.PI; @@ -64,7 +68,6 @@ public final class Geopoint implements ICoordinates, Parcelable { * longitude string to parse * @throws Geopoint.ParseException * if any argument string cannot be parsed - * @see GeopointParser#parse(String, String) */ public Geopoint(final String latText, final String lonText) { this(GeopointParser.parseLatitude(latText), GeopointParser.parseLongitude(lonText)); @@ -273,19 +276,6 @@ public final class Geopoint implements ICoordinates, Parcelable { } /** - * Checks if given Geopoint is similar to this Geopoint with tolerance. - * - * @param gp - * Geopoint to check - * @param tolerance - * tolerance in km - * @return true if similar, false otherwise - */ - public boolean isEqualTo(Geopoint gp, double tolerance) { - return null != gp && distanceTo(gp) <= tolerance; - } - - /** * Returns formatted coordinates. * * @param format @@ -300,7 +290,7 @@ public final class Geopoint implements ICoordinates, Parcelable { /** * Returns formatted coordinates with default format. * Default format is decimalminutes, e.g. N 52° 36.123 E 010° 03.456 - * + * * @return formatted coordinates */ @Override @@ -365,7 +355,7 @@ public final class Geopoint implements ICoordinates, Parcelable { /** * Get longitude character (E or W). - * + * * @return */ public char getLonDir() { diff --git a/main/src/cgeo/geocaching/geopoint/GeopointParser.java b/main/src/cgeo/geocaching/geopoint/GeopointParser.java index c043d6f..120e02e 100644 --- a/main/src/cgeo/geocaching/geopoint/GeopointParser.java +++ b/main/src/cgeo/geocaching/geopoint/GeopointParser.java @@ -70,37 +70,11 @@ class GeopointParser { } /** - * Parses a pair of coordinates (latitude and longitude) out of a String. - * Accepts following formats and combinations of it: - * X DD - * X DD° - * X DD° MM - * X DD° MM.MMM - * X DD° MM SS - * - * as well as: - * DD.DDDDDDD - * - * Both . and , are accepted, also variable count of spaces (also 0) - * - * @param latitude - * the latitude string to parse - * @param longitude - * the longitude string to parse - * @return an Geopoint with parsed latitude and longitude - * @throws Geopoint.ParseException - * if lat or lon could not be parsed - */ - public static Geopoint parse(final String latitude, final String longitude) { - final double lat = parseLatitude(latitude); - final double lon = parseLongitude(longitude); - - return new Geopoint(lat, lon); - } - - /* - * (non JavaDoc) - * Helper for coordinates-parsing. + * Helper for coordinates-parsing + * + * @param text + * @param latlon + * @return */ private static ResultWrapper parseHelper(final String text, final LatLon latlon) { diff --git a/main/src/cgeo/geocaching/geopoint/Viewport.java b/main/src/cgeo/geocaching/geopoint/Viewport.java index 9d55f69..ba0e040 100644 --- a/main/src/cgeo/geocaching/geopoint/Viewport.java +++ b/main/src/cgeo/geocaching/geopoint/Viewport.java @@ -5,16 +5,15 @@ import cgeo.geocaching.ICoordinates; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; -import java.util.Set; +import java.util.Collection; - -public class Viewport { +public final class Viewport { public final @NonNull Geopoint center; public final @NonNull Geopoint bottomLeft; public final @NonNull Geopoint topRight; - public Viewport(final ICoordinates point1, final ICoordinates point2) { + public Viewport(final @NonNull ICoordinates point1, final @NonNull ICoordinates point2) { final Geopoint gp1 = point1.getCoords(); final Geopoint gp2 = point2.getCoords(); this.bottomLeft = new Geopoint(Math.min(gp1.getLatitude(), gp2.getLatitude()), @@ -25,7 +24,7 @@ public class Viewport { (gp1.getLongitude() + gp2.getLongitude()) / 2); } - public Viewport(final ICoordinates center, final double latSpan, final double lonSpan) { + public Viewport(final @NonNull ICoordinates center, final double latSpan, final double lonSpan) { this.center = center.getCoords(); final double centerLat = this.center.getLatitude(); final double centerLon = this.center.getLongitude(); @@ -71,7 +70,7 @@ public class Viewport { * the coordinates to check * @return true if the point is contained in this viewport, false otherwise or if the point contains no coordinates */ - public boolean contains(final ICoordinates point) { + public boolean contains(final @NonNull ICoordinates point) { final Geopoint coords = point.getCoords(); return coords != null && coords.getLongitudeE6() >= bottomLeft.getLongitudeE6() @@ -87,12 +86,12 @@ public class Viewport { /** * Check whether another viewport is fully included into the current one. - * + * * @param vp * the other viewport - * @return true if the vp is fully included into this one, false otherwise + * @return true if the viewport is fully included into this one, false otherwise */ - public boolean includes(final Viewport vp) { + public boolean includes(final @NonNull Viewport vp) { return contains(vp.bottomLeft) && contains(vp.topRight); } @@ -124,46 +123,37 @@ public class Viewport { } /** - * Return a viewport that contains the current viewport as well as another point. - * - * @param point - * the point we want in the viewport - * @return either the same or an expanded viewport - */ - public Viewport expand(final ICoordinates point) { - if (contains(point)) { - return this; - } - - final Geopoint coords = point.getCoords(); - final double latitude = coords.getLatitude(); - final double longitude = coords.getLongitude(); - final double latMin = Math.min(getLatitudeMin(), latitude); - final double latMax = Math.max(getLatitudeMax(), latitude); - final double lonMin = Math.min(getLongitudeMin(), longitude); - final double lonMax = Math.max(getLongitudeMax(), longitude); - return new Viewport(new Geopoint(latMin, lonMin), new Geopoint(latMax, lonMax)); - } - - /** * Return the smallest viewport containing all the given points. * * @param points * a set of points. Point with null coordinates (or null themselves) will be ignored * @return the smallest viewport containing the non-null coordinates, or null if no coordinates are non-null */ - static public Viewport containing(final Set<? extends ICoordinates> points) { - Viewport viewport = null; + static public @Nullable + Viewport containing(final Collection<? extends ICoordinates> points) { + boolean valid = false; + double latMin = Double.MAX_VALUE; + double latMax = -Double.MAX_VALUE; + double lonMin = Double.MAX_VALUE; + double lonMax = -Double.MAX_VALUE; for (final ICoordinates point : points) { - if (point != null && point.getCoords() != null) { - if (viewport == null) { - viewport = new Viewport(point, point); - } else { - viewport = viewport.expand(point); + if (point != null) { + final Geopoint coords = point.getCoords(); + if (coords != null) { + valid = true; + final double latitude = coords.getLatitude(); + final double longitude = coords.getLongitude(); + latMin = Math.min(latMin, latitude); + latMax = Math.max(latMax, latitude); + lonMin = Math.min(lonMin, longitude); + lonMax = Math.max(lonMax, longitude); } } } - return viewport; + if (!valid) { + return null; + } + return new Viewport(new Geopoint(latMin, lonMin), new Geopoint(latMax, lonMax)); } @Override diff --git a/main/src/cgeo/geocaching/list/PseudoList.java b/main/src/cgeo/geocaching/list/PseudoList.java index 365d6fd..f2cc7ed 100644 --- a/main/src/cgeo/geocaching/list/PseudoList.java +++ b/main/src/cgeo/geocaching/list/PseudoList.java @@ -17,6 +17,12 @@ public class PseudoList extends AbstractList { */ public static final AbstractList NEW_LIST = new PseudoList(NEW_LIST_ID, R.string.list_menu_create); + private static final int HISTORY_LIST_ID = 4; + /** + * list entry to create a new list + */ + public static final AbstractList HISTORY_LIST = new PseudoList(HISTORY_LIST_ID, R.string.menu_history); + /** * private constructor to have all instances as constants in the class */ diff --git a/main/src/cgeo/geocaching/list/StoredList.java b/main/src/cgeo/geocaching/list/StoredList.java index 8106073..ba8ce35 100644 --- a/main/src/cgeo/geocaching/list/StoredList.java +++ b/main/src/cgeo/geocaching/list/StoredList.java @@ -5,11 +5,12 @@ import cgeo.geocaching.DataStore; import cgeo.geocaching.R; import cgeo.geocaching.activity.ActivityMixin; import cgeo.geocaching.ui.dialog.Dialogs; -import cgeo.geocaching.utils.RunnableWithArgument; import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.NonNull; +import rx.util.functions.Action1; + import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; @@ -67,15 +68,15 @@ public final class StoredList extends AbstractList { res = app.getResources(); } - public void promptForListSelection(final int titleId, @NonNull final RunnableWithArgument<Integer> runAfterwards) { + public void promptForListSelection(final int titleId, @NonNull final Action1<Integer> runAfterwards) { promptForListSelection(titleId, runAfterwards, false, -1); } - public void promptForListSelection(final int titleId, @NonNull final RunnableWithArgument<Integer> runAfterwards, final boolean onlyConcreteLists, final int exceptListId) { + public void promptForListSelection(final int titleId, @NonNull final Action1<Integer> runAfterwards, final boolean onlyConcreteLists, final int exceptListId) { promptForListSelection(titleId, runAfterwards, onlyConcreteLists, exceptListId, StringUtils.EMPTY); } - public void promptForListSelection(final int titleId, @NonNull final RunnableWithArgument<Integer> runAfterwards, final boolean onlyConcreteLists, final int exceptListId, final String newListName) { + public void promptForListSelection(final int titleId, @NonNull final Action1<Integer> runAfterwards, final boolean onlyConcreteLists, final int exceptListId, final String newListName) { final List<AbstractList> lists = new ArrayList<AbstractList>(); lists.addAll(getSortedLists()); @@ -87,7 +88,12 @@ public final class StoredList extends AbstractList { } if (!onlyConcreteLists) { - lists.add(PseudoList.ALL_LIST); + if (exceptListId != PseudoList.ALL_LIST.id) { + lists.add(PseudoList.ALL_LIST); + } + if (exceptListId != PseudoList.HISTORY_LIST.id) { + lists.add(PseudoList.HISTORY_LIST); + } } lists.add(PseudoList.NEW_LIST); @@ -109,7 +115,7 @@ public final class StoredList extends AbstractList { promptForListCreation(runAfterwards, newListName); } else { - runAfterwards.run(lists.get(itemId).id); + runAfterwards.call(lists.get(itemId).id); } } }); @@ -138,17 +144,17 @@ public final class StoredList extends AbstractList { return lists; } - public void promptForListCreation(@NonNull final RunnableWithArgument<Integer> runAfterwards, String newListName) { - handleListNameInput(newListName, R.string.list_dialog_create_title, R.string.list_dialog_create, new RunnableWithArgument<String>() { + public void promptForListCreation(@NonNull final Action1<Integer> runAfterwards, String newListName) { + handleListNameInput(newListName, R.string.list_dialog_create_title, R.string.list_dialog_create, new Action1<String>() { @Override - public void run(final String listName) { + public void call(final String listName) { final int newId = DataStore.createList(listName); new StoredList(newId, listName, 0); if (newId >= DataStore.customListIdOffset) { ActivityMixin.showToast(activity, res.getString(R.string.list_dialog_create_ok)); - runAfterwards.run(newId); + runAfterwards.call(newId); } else { ActivityMixin.showToast(activity, res.getString(R.string.list_dialog_create_err)); } @@ -156,15 +162,15 @@ public final class StoredList extends AbstractList { }); } - private void handleListNameInput(final String defaultValue, int dialogTitle, int buttonTitle, final RunnableWithArgument<String> runnable) { - Dialogs.input(activity, dialogTitle, defaultValue, buttonTitle, new RunnableWithArgument<String>() { + private void handleListNameInput(final String defaultValue, int dialogTitle, int buttonTitle, final Action1<String> runnable) { + Dialogs.input(activity, dialogTitle, defaultValue, buttonTitle, new Action1<String>() { @Override - public void run(final String input) { + public void call(final String input) { // remove whitespaces added by autocompletion of Android keyboard String listName = StringUtils.trim(input); if (StringUtils.isNotBlank(listName)) { - runnable.run(listName); + runnable.call(listName); } } }); @@ -172,10 +178,10 @@ public final class StoredList extends AbstractList { public void promptForListRename(final int listId, @NonNull final Runnable runAfterRename) { final StoredList list = DataStore.getList(listId); - handleListNameInput(list.title, R.string.list_dialog_rename_title, R.string.list_dialog_rename, new RunnableWithArgument<String>() { + handleListNameInput(list.title, R.string.list_dialog_rename_title, R.string.list_dialog_rename, new Action1<String>() { @Override - public void run(final String listName) { + public void call(final String listName) { DataStore.renameList(listId, listName); runAfterRename.run(); } @@ -197,7 +203,7 @@ public final class StoredList extends AbstractList { * Return the given list, if it is a concrete list. Return the default list otherwise. */ public static int getConcreteList(int listId) { - if (listId == PseudoList.ALL_LIST.id || listId == TEMPORARY_LIST_ID) { + if (listId == PseudoList.ALL_LIST.id || listId == TEMPORARY_LIST_ID || listId == PseudoList.HISTORY_LIST.id) { return STANDARD_LIST_ID; } return listId; diff --git a/main/src/cgeo/geocaching/loaders/AbstractSearchLoader.java b/main/src/cgeo/geocaching/loaders/AbstractSearchLoader.java index 7524b76..e3306f3 100644 --- a/main/src/cgeo/geocaching/loaders/AbstractSearchLoader.java +++ b/main/src/cgeo/geocaching/loaders/AbstractSearchLoader.java @@ -28,6 +28,10 @@ public abstract class AbstractSearchLoader extends AsyncTaskLoader<SearchResult> MAP, REMOVE_FROM_HISTORY, NEXT_PAGE; + + public int getLoaderId() { + return ordinal(); + } } private Handler recaptchaHandler = null; diff --git a/main/src/cgeo/geocaching/loaders/AddressGeocacheListLoader.java b/main/src/cgeo/geocaching/loaders/AddressGeocacheListLoader.java index dd7c7a6..e1573c9 100644 --- a/main/src/cgeo/geocaching/loaders/AddressGeocacheListLoader.java +++ b/main/src/cgeo/geocaching/loaders/AddressGeocacheListLoader.java @@ -1,8 +1,8 @@ package cgeo.geocaching.loaders; import cgeo.geocaching.SearchResult; -import cgeo.geocaching.settings.Settings; import cgeo.geocaching.connector.gc.GCParser; +import cgeo.geocaching.settings.Settings; import android.content.Context; diff --git a/main/src/cgeo/geocaching/loaders/HistoryGeocacheListLoader.java b/main/src/cgeo/geocaching/loaders/HistoryGeocacheListLoader.java index 605f461..fdb35f2 100644 --- a/main/src/cgeo/geocaching/loaders/HistoryGeocacheListLoader.java +++ b/main/src/cgeo/geocaching/loaders/HistoryGeocacheListLoader.java @@ -2,9 +2,9 @@ package cgeo.geocaching.loaders; import cgeo.geocaching.DataStore; import cgeo.geocaching.SearchResult; -import cgeo.geocaching.settings.Settings; import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.settings.Settings; import android.content.Context; diff --git a/main/src/cgeo/geocaching/loaders/NextPageGeocacheListLoader.java b/main/src/cgeo/geocaching/loaders/NextPageGeocacheListLoader.java index 1104f83..05eac18 100644 --- a/main/src/cgeo/geocaching/loaders/NextPageGeocacheListLoader.java +++ b/main/src/cgeo/geocaching/loaders/NextPageGeocacheListLoader.java @@ -1,8 +1,8 @@ package cgeo.geocaching.loaders; import cgeo.geocaching.SearchResult; -import cgeo.geocaching.settings.Settings; import cgeo.geocaching.connector.gc.GCParser; +import cgeo.geocaching.settings.Settings; import android.content.Context; diff --git a/main/src/cgeo/geocaching/loaders/OfflineGeocacheListLoader.java b/main/src/cgeo/geocaching/loaders/OfflineGeocacheListLoader.java index 5088484..b80a1b8 100644 --- a/main/src/cgeo/geocaching/loaders/OfflineGeocacheListLoader.java +++ b/main/src/cgeo/geocaching/loaders/OfflineGeocacheListLoader.java @@ -1,9 +1,9 @@ package cgeo.geocaching.loaders; -import cgeo.geocaching.SearchResult; -import cgeo.geocaching.settings.Settings; import cgeo.geocaching.DataStore; +import cgeo.geocaching.SearchResult; import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.settings.Settings; import android.content.Context; diff --git a/main/src/cgeo/geocaching/maps/CGeoMap.java b/main/src/cgeo/geocaching/maps/CGeoMap.java index c98ba72..05b05be 100644 --- a/main/src/cgeo/geocaching/maps/CGeoMap.java +++ b/main/src/cgeo/geocaching/maps/CGeoMap.java @@ -38,12 +38,13 @@ 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.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.HashCodeBuilder; +import rx.util.functions.Action1; + import android.app.Activity; import android.app.AlertDialog; import android.app.ProgressDialog; @@ -466,7 +467,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto prepareFilterBar(); - if (!app.isLiveMapHintShown() && !Settings.getHideLiveMapHint()) { + if (!app.isLiveMapHintShownInThisSession() && !Settings.getHideLiveMapHint() && Settings.getLiveMapHintShowCount() <= 3) { LiveMapInfoDialogBuilder.create(activity).show(); } } @@ -661,9 +662,9 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto if (Settings.getChooseList()) { // let user select list to store cache in new StoredList.UserInterface(activity).promptForListSelection(R.string.list_title, - new RunnableWithArgument<Integer>() { + new Action1<Integer>() { @Override - public void run(final Integer selectedListId) { + public void call(final Integer selectedListId) { storeCaches(geocodes, selectedListId); } }, true, StoredList.TEMPORARY_LIST_ID); @@ -898,7 +899,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto private long timeLastPositionOverlayCalculation = 0; @Override - protected void updateGeoData(final IGeoData geo) { + public void updateGeoData(final IGeoData geo) { if (geo.isPseudoLocation()) { locationValid = false; } else { @@ -1182,7 +1183,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } } } - final SearchResult searchResult = ConnectorFactory.searchByViewport(viewport.resize(0.8), tokens); + final SearchResult searchResult = ConnectorFactory.searchByViewport(viewport.resize(0.8), tokens).toBlockingObservable().single(); downloaded = true; Set<Geocache> result = searchResult.getCachesFromSearchResult(LoadFlags.LOAD_CACHE_OR_DB); diff --git a/main/src/cgeo/geocaching/maps/MapProviderFactory.java b/main/src/cgeo/geocaching/maps/MapProviderFactory.java index 2e43e19..b928a1e 100644 --- a/main/src/cgeo/geocaching/maps/MapProviderFactory.java +++ b/main/src/cgeo/geocaching/maps/MapProviderFactory.java @@ -1,7 +1,7 @@ package cgeo.geocaching.maps; -import cgeo.geocaching.R; import cgeo.geocaching.CgeoApplication; +import cgeo.geocaching.R; import cgeo.geocaching.maps.google.GoogleMapProvider; import cgeo.geocaching.maps.interfaces.MapProvider; import cgeo.geocaching.maps.interfaces.MapSource; diff --git a/main/src/cgeo/geocaching/maps/google/GoogleCacheOverlay.java b/main/src/cgeo/geocaching/maps/google/GoogleCacheOverlay.java index 3339650..d14c687 100644 --- a/main/src/cgeo/geocaching/maps/google/GoogleCacheOverlay.java +++ b/main/src/cgeo/geocaching/maps/google/GoogleCacheOverlay.java @@ -77,11 +77,6 @@ public class GoogleCacheOverlay extends ItemizedOverlay<GoogleCacheOverlayItem> } @Override - public Drawable superBoundCenter(Drawable markerIn) { - return ItemizedOverlay.boundCenter(markerIn); - } - - @Override public Drawable superBoundCenterBottom(Drawable marker) { return ItemizedOverlay.boundCenterBottom(marker); } diff --git a/main/src/cgeo/geocaching/maps/google/GoogleMapProvider.java b/main/src/cgeo/geocaching/maps/google/GoogleMapProvider.java index cb95b2c..38d7d96 100644 --- a/main/src/cgeo/geocaching/maps/google/GoogleMapProvider.java +++ b/main/src/cgeo/geocaching/maps/google/GoogleMapProvider.java @@ -1,7 +1,7 @@ package cgeo.geocaching.maps.google; -import cgeo.geocaching.R; import cgeo.geocaching.CgeoApplication; +import cgeo.geocaching.R; import cgeo.geocaching.maps.AbstractMapProvider; import cgeo.geocaching.maps.AbstractMapSource; import cgeo.geocaching.maps.interfaces.MapItemFactory; diff --git a/main/src/cgeo/geocaching/maps/google/GoogleMapView.java b/main/src/cgeo/geocaching/maps/google/GoogleMapView.java index d02e3c2..471e061 100644 --- a/main/src/cgeo/geocaching/maps/google/GoogleMapView.java +++ b/main/src/cgeo/geocaching/maps/google/GoogleMapView.java @@ -11,13 +11,11 @@ import cgeo.geocaching.maps.interfaces.MapControllerImpl; import cgeo.geocaching.maps.interfaces.MapProjectionImpl; import cgeo.geocaching.maps.interfaces.MapViewImpl; import cgeo.geocaching.maps.interfaces.OnMapDragListener; -import cgeo.geocaching.maps.interfaces.OverlayImpl; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.utils.Log; import com.google.android.maps.GeoPoint; import com.google.android.maps.MapView; -import com.google.android.maps.Overlay; import org.apache.commons.lang3.reflect.MethodUtils; @@ -54,7 +52,7 @@ public class GoogleMapView extends MapView implements MapViewImpl { } @Override - public void draw(Canvas canvas) { + public void draw(final Canvas canvas) { try { if (getMapZoomLevel() > 22) { // to avoid too close zoom level (mostly on Samsung Galaxy S series) getController().setZoom(22); @@ -102,11 +100,6 @@ public class GoogleMapView extends MapView implements MapViewImpl { } @Override - public void addOverlay(OverlayImpl ovl) { - getOverlays().add((Overlay) ovl); - } - - @Override public void clearOverlays() { getOverlays().clear(); } diff --git a/main/src/cgeo/geocaching/maps/interfaces/ItemizedOverlayImpl.java b/main/src/cgeo/geocaching/maps/interfaces/ItemizedOverlayImpl.java index 90c5b31..ee61f12 100644 --- a/main/src/cgeo/geocaching/maps/interfaces/ItemizedOverlayImpl.java +++ b/main/src/cgeo/geocaching/maps/interfaces/ItemizedOverlayImpl.java @@ -18,8 +18,6 @@ public interface ItemizedOverlayImpl extends OverlayImpl { void superSetLastFocusedItemIndex(int i); - Drawable superBoundCenter(Drawable markerIn); - Drawable superBoundCenterBottom(Drawable marker); boolean superOnTap(int index); diff --git a/main/src/cgeo/geocaching/maps/interfaces/MapViewImpl.java b/main/src/cgeo/geocaching/maps/interfaces/MapViewImpl.java index cb7ddc6..c30f65f 100644 --- a/main/src/cgeo/geocaching/maps/interfaces/MapViewImpl.java +++ b/main/src/cgeo/geocaching/maps/interfaces/MapViewImpl.java @@ -22,8 +22,6 @@ public interface MapViewImpl { void clearOverlays(); - void addOverlay(OverlayImpl ovl); - MapControllerImpl getMapController(); void destroyDrawingCache(); diff --git a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeCacheOverlay.java b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeCacheOverlay.java index 9e14e36..b9e40d7 100644 --- a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeCacheOverlay.java +++ b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeCacheOverlay.java @@ -70,11 +70,6 @@ public class MapsforgeCacheOverlay extends ItemizedOverlay<MapsforgeCacheOverlay } @Override - public Drawable superBoundCenter(Drawable markerIn) { - return ItemizedOverlay.boundCenter(markerIn); - } - - @Override public Drawable superBoundCenterBottom(Drawable marker) { return ItemizedOverlay.boundCenterBottom(marker); } diff --git a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapView.java b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapView.java index 78aa47d..e993548 100644 --- a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapView.java +++ b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapView.java @@ -11,7 +11,6 @@ import cgeo.geocaching.maps.interfaces.MapProjectionImpl; import cgeo.geocaching.maps.interfaces.MapSource; import cgeo.geocaching.maps.interfaces.MapViewImpl; import cgeo.geocaching.maps.interfaces.OnMapDragListener; -import cgeo.geocaching.maps.interfaces.OverlayImpl; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.utils.Log; @@ -86,11 +85,6 @@ public class MapsforgeMapView extends MapView implements MapViewImpl { } @Override - public void addOverlay(OverlayImpl ovl) { - getOverlays().add((Overlay) ovl); - } - - @Override public void clearOverlays() { getOverlays().clear(); } @@ -229,7 +223,7 @@ public class MapsforgeMapView extends MapView implements MapViewImpl { @Override public void setMapTheme() { String customRenderTheme = Settings.getCustomRenderThemeFilePath(); - if (!StringUtils.isEmpty(customRenderTheme)) { + if (StringUtils.isNotEmpty(customRenderTheme)) { try { setRenderTheme(new File(customRenderTheme)); } catch (FileNotFoundException e) { diff --git a/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeCacheOverlay.java b/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeCacheOverlay.java index 30355fd..a8111ed 100644 --- a/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeCacheOverlay.java +++ b/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeCacheOverlay.java @@ -70,11 +70,6 @@ public class MapsforgeCacheOverlay extends ItemizedOverlay<MapsforgeCacheOverlay } @Override - public Drawable superBoundCenter(Drawable markerIn) { - return ItemizedOverlay.boundCenter(markerIn); - } - - @Override public Drawable superBoundCenterBottom(Drawable marker) { return ItemizedOverlay.boundCenterBottom(marker); } diff --git a/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapView024.java b/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapView024.java index c741a31..6573304 100644 --- a/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapView024.java +++ b/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapView024.java @@ -10,7 +10,6 @@ import cgeo.geocaching.maps.interfaces.MapControllerImpl; import cgeo.geocaching.maps.interfaces.MapProjectionImpl; import cgeo.geocaching.maps.interfaces.MapViewImpl; import cgeo.geocaching.maps.interfaces.OnMapDragListener; -import cgeo.geocaching.maps.interfaces.OverlayImpl; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.utils.Log; @@ -77,11 +76,6 @@ public class MapsforgeMapView024 extends MapView implements MapViewImpl { } @Override - public void addOverlay(OverlayImpl ovl) { - getOverlays().add((Overlay) ovl); - } - - @Override public void clearOverlays() { getOverlays().clear(); } diff --git a/main/src/cgeo/geocaching/network/HtmlImage.java b/main/src/cgeo/geocaching/network/HtmlImage.java index 0daa588..774dbf6 100644 --- a/main/src/cgeo/geocaching/network/HtmlImage.java +++ b/main/src/cgeo/geocaching/network/HtmlImage.java @@ -6,6 +6,7 @@ import cgeo.geocaching.compatibility.Compatibility; import cgeo.geocaching.connector.ConnectorFactory; import cgeo.geocaching.files.LocalStorage; import cgeo.geocaching.list.StoredList; +import cgeo.geocaching.utils.CancellableHandler; import cgeo.geocaching.utils.FileUtils; import cgeo.geocaching.utils.ImageUtils; import cgeo.geocaching.utils.Log; @@ -14,6 +15,17 @@ import ch.boye.httpclientandroidlib.HttpResponse; import ch.boye.httpclientandroidlib.androidextra.Base64; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; +import org.eclipse.jdt.annotation.Nullable; +import rx.Observable; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Scheduler; +import rx.Subscription; +import rx.concurrency.Schedulers; +import rx.subjects.PublishSubject; +import rx.subscriptions.CompositeSubscription; +import rx.subscriptions.Subscriptions; +import rx.util.functions.Func1; import android.content.res.Resources; import android.graphics.Bitmap; @@ -31,6 +43,11 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.Date; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; public class HtmlImage implements Html.ImageGetter { @@ -64,6 +81,14 @@ public class HtmlImage implements Html.ImageGetter { final private int maxHeight; final private Resources resources; + // Background loading + final private PublishSubject<Observable<String>> loading = PublishSubject.create(); + final Observable<String> waitForEnd = Observable.merge(loading).publish().refCount(); + final CompositeSubscription subscription = new CompositeSubscription(waitForEnd.subscribe()); + final private Scheduler downloadScheduler = Schedulers.executor(new ThreadPoolExecutor(5, 5, 5, TimeUnit.SECONDS, + new LinkedBlockingQueue<Runnable>())); + final private Set<String> downloading = new HashSet<String>(); + public HtmlImage(final String geocode, final boolean returnErrorImage, final int listId, final boolean onlySave) { this.geocode = geocode; this.returnErrorImage = returnErrorImage; @@ -80,60 +105,71 @@ public class HtmlImage implements Html.ImageGetter { this.resources = CgeoApplication.getInstance().getResources(); } + @Nullable @Override public BitmapDrawable getDrawable(final String url) { + if (!onlySave) { + return loadDrawable(url); + } else { + synchronized(downloading) { + if (!downloading.contains(url)) { + loading.onNext(fetchDrawable(url).map(new Func1<BitmapDrawable, String>() { + @Override + public String call(final BitmapDrawable bitmapDrawable) { + return url; + } + })); + downloading.add(url); + } + return null; + } + } + } + + public Observable<BitmapDrawable> fetchDrawable(final String url) { + return Observable.create(new OnSubscribeFunc<BitmapDrawable>() { + @Override + public Subscription onSubscribe(final Observer<? super BitmapDrawable> observer) { + if (!subscription.isUnsubscribed()) { + observer.onNext(loadDrawable(url)); + } + observer.onCompleted(); + return Subscriptions.empty(); + } + }).subscribeOn(downloadScheduler); + } + + public void waitForBackgroundLoading(@Nullable final CancellableHandler handler) { + if (handler != null) { + handler.unsubscribeIfCancelled(subscription); + } + loading.onCompleted(); + waitForEnd.toBlockingObservable().lastOrDefault(null); + } + + private BitmapDrawable loadDrawable(final String url) { // Reject empty and counter images URL if (StringUtils.isBlank(url) || isCounter(url)) { - return new BitmapDrawable(resources, getTransparent1x1Image()); + return getTransparent1x1Image(resources); } final boolean shared = url.contains("/images/icons/icon_"); final String pseudoGeocode = shared ? SHARED : geocode; - Bitmap imagePre = loadImageFromStorage(url, pseudoGeocode, shared); + Bitmap image = loadImageFromStorage(url, pseudoGeocode, shared); // Download image and save it to the cache - if (imagePre == null) { + if (image == null) { final File file = LocalStorage.getStorageFile(pseudoGeocode, url, true, true); if (url.startsWith("data:image/")) { if (url.contains(";base64,")) { - // TODO: when we use SDK level 8 or above, we can use the streaming version of the base64 - // Android utilities. - byte[] decoded = Base64.decode(StringUtils.substringAfter(url, ";base64,"), Base64.DEFAULT); - OutputStream out = null; - try { - out = new FileOutputStream(file); - out.write(decoded); - } catch (final IOException e) { - Log.e("HtmlImage.getDrawable: cannot write file for decoded inline image", e); - return null; - } finally { - IOUtils.closeQuietly(out); - } + saveBase64ToFile(url, file); } else { Log.e("HtmlImage.getDrawable: unable to decode non-base64 inline image"); return null; } } else { - final String absoluteURL = makeAbsoluteURL(url); - - if (absoluteURL != null) { - try { - final HttpResponse httpResponse = Network.getRequest(absoluteURL, null, file); - if (httpResponse != null) { - final int statusCode = httpResponse.getStatusLine().getStatusCode(); - if (statusCode == 200) { - LocalStorage.saveEntityToFile(httpResponse, file); - } else if (statusCode == 304) { - if (!file.setLastModified(System.currentTimeMillis())) { - makeFreshCopy(file); - } - } - } - } catch (Exception e) { - Log.e("HtmlImage.getDrawable (downloading from web)", e); - } - } + downloadOrRefreshCopy(url, file); } } @@ -142,22 +178,56 @@ public class HtmlImage implements Html.ImageGetter { } // now load the newly downloaded image - if (imagePre == null) { - imagePre = loadImageFromStorage(url, pseudoGeocode, shared); + if (image == null) { + image = loadImageFromStorage(url, pseudoGeocode, shared); } // get image and return - if (imagePre == null) { + if (image == null) { Log.d("HtmlImage.getDrawable: Failed to obtain image"); - if (returnErrorImage) { - imagePre = BitmapFactory.decodeResource(resources, R.drawable.image_not_loaded); - } else { - imagePre = getTransparent1x1Image(); + return returnErrorImage + ? new BitmapDrawable(resources, BitmapFactory.decodeResource(resources, R.drawable.image_not_loaded)) + : getTransparent1x1Image(resources); + } + + return ImageUtils.scaleBitmapToFitDisplay(image); + } + + private void downloadOrRefreshCopy(final String url, final File file) { + final String absoluteURL = makeAbsoluteURL(url); + + if (absoluteURL != null) { + try { + final HttpResponse httpResponse = Network.getRequest(absoluteURL, null, file); + if (httpResponse != null) { + final int statusCode = httpResponse.getStatusLine().getStatusCode(); + if (statusCode == 200) { + LocalStorage.saveEntityToFile(httpResponse, file); + } else if (statusCode == 304) { + if (!file.setLastModified(System.currentTimeMillis())) { + makeFreshCopy(file); + } + } + } + } catch (Exception e) { + Log.e("HtmlImage.downloadOrRefreshCopy", e); } } + } - return imagePre != null ? ImageUtils.scaleBitmapToFitDisplay(imagePre) : null; + private static void saveBase64ToFile(final String url, final File file) { + // TODO: when we use SDK level 8 or above, we can use the streaming version of the base64 + // Android utilities. + OutputStream out = null; + try { + out = new FileOutputStream(file); + out.write(Base64.decode(StringUtils.substringAfter(url, ";base64,"), Base64.DEFAULT)); + } catch (final IOException e) { + Log.e("HtmlImage.saveBase64ToFile: cannot write file for decoded inline image", e); + } finally { + IOUtils.closeQuietly(out); + } } /** @@ -180,10 +250,11 @@ public class HtmlImage implements Html.ImageGetter { } } - private Bitmap getTransparent1x1Image() { - return BitmapFactory.decodeResource(resources, R.drawable.image_no_placement); + private BitmapDrawable getTransparent1x1Image(final Resources res) { + return new BitmapDrawable(res, BitmapFactory.decodeResource(resources, R.drawable.image_no_placement)); } + @Nullable private Bitmap loadImageFromStorage(final String url, final String pseudoGeocode, final boolean forceKeep) { try { final File file = LocalStorage.getStorageFile(pseudoGeocode, url, true, false); @@ -194,11 +265,12 @@ 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); + Log.w("HtmlImage.loadImageFromStorage", e); } return null; } + @Nullable private String makeAbsoluteURL(final String url) { // Check if uri is absolute or not, if not attach the connector hostname // FIXME: that should also include the scheme @@ -222,6 +294,7 @@ public class HtmlImage implements Html.ImageGetter { return null; } + @Nullable private Bitmap loadCachedImage(final File file, final boolean forceKeep) { if (file.exists()) { if (listId >= StoredList.STANDARD_LIST_ID || file.lastModified() > (new Date().getTime() - (24 * 60 * 60 * 1000)) || forceKeep) { diff --git a/main/src/cgeo/geocaching/network/Network.java b/main/src/cgeo/geocaching/network/Network.java index e891d3b..b737fd2 100644 --- a/main/src/cgeo/geocaching/network/Network.java +++ b/main/src/cgeo/geocaching/network/Network.java @@ -34,7 +34,6 @@ import ch.boye.httpclientandroidlib.params.CoreProtocolPNames; 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.eclipse.jdt.annotation.Nullable; diff --git a/main/src/cgeo/geocaching/network/OAuth.java b/main/src/cgeo/geocaching/network/OAuth.java index c033660..1e22551 100644 --- a/main/src/cgeo/geocaching/network/OAuth.java +++ b/main/src/cgeo/geocaching/network/OAuth.java @@ -3,7 +3,6 @@ package cgeo.geocaching.network; import cgeo.geocaching.utils.CryptUtils; import ch.boye.httpclientandroidlib.NameValuePair; - import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; diff --git a/main/src/cgeo/geocaching/network/OAuthAuthorizationActivity.java b/main/src/cgeo/geocaching/network/OAuthAuthorizationActivity.java index 888cf77..fd6ccc6 100644 --- a/main/src/cgeo/geocaching/network/OAuthAuthorizationActivity.java +++ b/main/src/cgeo/geocaching/network/OAuthAuthorizationActivity.java @@ -10,7 +10,6 @@ import cgeo.geocaching.utils.MatcherWrapper; import ch.boye.httpclientandroidlib.ParseException; import ch.boye.httpclientandroidlib.client.entity.UrlEncodedFormEntity; import ch.boye.httpclientandroidlib.util.EntityUtils; - import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; import org.eclipse.jdt.annotation.NonNull; diff --git a/main/src/cgeo/geocaching/network/StatusUpdater.java b/main/src/cgeo/geocaching/network/StatusUpdater.java index cb4c7f4..2d565bf 100644 --- a/main/src/cgeo/geocaching/network/StatusUpdater.java +++ b/main/src/cgeo/geocaching/network/StatusUpdater.java @@ -1,21 +1,22 @@ package cgeo.geocaching.network; import cgeo.geocaching.CgeoApplication; -import cgeo.geocaching.utils.MemorySubject; -import cgeo.geocaching.utils.PeriodicHandler; -import cgeo.geocaching.utils.PeriodicHandler.PeriodicHandlerListener; import cgeo.geocaching.utils.Version; import org.json.JSONException; import org.json.JSONObject; +import rx.Observable; +import rx.concurrency.Schedulers; +import rx.subjects.BehaviorSubject; +import rx.util.functions.Func1; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; -import android.os.Looper; import java.util.Locale; +import java.util.concurrent.TimeUnit; -public class StatusUpdater extends MemorySubject<StatusUpdater.Status> implements Runnable, PeriodicHandlerListener { +public class StatusUpdater { static public class Status { final public String message; @@ -30,24 +31,41 @@ public class StatusUpdater extends MemorySubject<StatusUpdater.Status> implement this.url = url; } + Status(final JSONObject response) { + message = get(response, "message"); + messageId = get(response, "message_id"); + icon = get(response, "icon"); + url = get(response, "url"); + } + final static public Status closeoutStatus = new Status("", "status_closeout_warning", "attribute_abandonedbuilding", "http://faq.cgeo.org/#7_69"); - final static public Status defaultStatus() { + final static public Status defaultStatus(final Status upToDate) { + if (upToDate != null && upToDate.message != null) { + return upToDate; + } return VERSION.SDK_INT < VERSION_CODES.ECLAIR_MR1 ? closeoutStatus : null; } } - @Override - public void onPeriodic() { - final JSONObject response = - Network.requestJSON("http://status.cgeo.org/api/status.json", - new Parameters("version_code", String.valueOf(Version.getVersionCode(CgeoApplication.getInstance())), - "version_name", Version.getVersionName(CgeoApplication.getInstance()), - "locale", Locale.getDefault().toString())); - if (response != null) { - notifyObservers(new Status(get(response, "message"), get(response, "message_id"), get(response, "icon"), get(response, "url"))); - } + final static private Observable<Status> statusObservable = + Observable.interval(1800, TimeUnit.SECONDS).startWith(-1L).flatMap(new Func1<Long, Observable<Status>>() { + @Override + public Observable<Status> call(Long id) { + final JSONObject response = + Network.requestJSON("http://status.cgeo.org/api/status.json", + new Parameters("version_code", String.valueOf(Version.getVersionCode(CgeoApplication.getInstance())), + "version_name", Version.getVersionName(CgeoApplication.getInstance()), + "locale", Locale.getDefault().toString())); + return response != null ? Observable.from(Status.defaultStatus((new Status(response)))) : Observable.<Status>empty(); + } + }).subscribeOn(Schedulers.threadPoolForIO()); + + final static public BehaviorSubject<Status> latestStatus = BehaviorSubject.create(Status.defaultStatus(null)); + + static { + statusObservable.subscribe(latestStatus); } private static String get(final JSONObject json, final String key) { @@ -58,11 +76,4 @@ public class StatusUpdater extends MemorySubject<StatusUpdater.Status> implement } } - @Override - public void run() { - Looper.prepare(); - new PeriodicHandler(1800000L, this).start(); - Looper.loop(); - } - } diff --git a/main/src/cgeo/geocaching/search/AutoCompleteAdapter.java b/main/src/cgeo/geocaching/search/AutoCompleteAdapter.java new file mode 100644 index 0000000..123469c --- /dev/null +++ b/main/src/cgeo/geocaching/search/AutoCompleteAdapter.java @@ -0,0 +1,71 @@ +package cgeo.geocaching.search; + +import org.apache.commons.lang3.StringUtils; + +import rx.util.functions.Func1; + +import android.content.Context; +import android.widget.ArrayAdapter; +import android.widget.Filter; + +/** + * The standard auto completion only matches user input at word boundaries. Therefore searching "est" will not match + * "test". This adapter matches everywhere. + * + */ +public class AutoCompleteAdapter extends ArrayAdapter<String> { + + private String[] suggestions; + private final Func1<String, String[]> suggestionFunction; + + public AutoCompleteAdapter(Context context, int textViewResourceId, final Func1<String, String[]> suggestionFunction) { + super(context, textViewResourceId); + this.suggestionFunction = suggestionFunction; + } + + @Override + public int getCount() { + return suggestions.length; + } + + @Override + public String getItem(int index) { + return suggestions[index]; + } + + @Override + public Filter getFilter() { + Filter filter = new Filter() { + + @Override + protected FilterResults performFiltering(CharSequence constraint) { + FilterResults filterResults = new FilterResults(); + if (constraint == null) { + return filterResults; + } + String trimmed = StringUtils.trim(constraint.toString()); + if (StringUtils.length(trimmed) >= 2) { + String[] newResults = suggestionFunction.call(trimmed); + + // Assign the data to the FilterResults, but do not yet store in the global member. + // Otherwise we might invalidate the adapter and cause an IllegalStateException. + filterResults.values = newResults; + filterResults.count = newResults.length; + } + return filterResults; + } + + @Override + protected void publishResults(CharSequence constraint, FilterResults filterResults) { + if (filterResults != null && filterResults.count > 0) { + suggestions = (String[]) filterResults.values; + notifyDataSetChanged(); + } + else { + notifyDataSetInvalidated(); + } + } + }; + return filter; + } +}
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/search/SuggestionProvider.java b/main/src/cgeo/geocaching/search/SuggestionProvider.java new file mode 100644 index 0000000..c0a7728 --- /dev/null +++ b/main/src/cgeo/geocaching/search/SuggestionProvider.java @@ -0,0 +1,57 @@ +package cgeo.geocaching.search; + +import cgeo.geocaching.DataStore; + +import org.apache.commons.lang3.StringUtils; + +import android.app.SearchManager; +import android.content.ContentProvider; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; + +public class SuggestionProvider extends ContentProvider { + + private static Cursor lastCursor; + + @Override + public boolean onCreate() { + return true; + } + + @Override + public String getType(final Uri arg0) { + return SearchManager.SUGGEST_MIME_TYPE; + } + + @Override + public Cursor query(final Uri uri, final String[] projection, final String selection, final String[] selectionArgs, final String sortOrder) { + final String searchTerm = uri.getLastPathSegment(); + // can be empty when deleting the query + if (StringUtils.equals(searchTerm, SearchManager.SUGGEST_URI_PATH_QUERY)) { + return lastCursor; + } + return getSuggestions(searchTerm); + } + + private static Cursor getSuggestions(final String searchTerm) { + lastCursor = DataStore.findSuggestions(searchTerm); + return lastCursor; + } + + @Override + public int delete(final Uri uri, final String selection, final String[] selectionArgs) { + throw new UnsupportedOperationException(); + } + + @Override + public Uri insert(final Uri uri, final ContentValues values) { + throw new UnsupportedOperationException(); + } + + @Override + public int update(final Uri uri, final ContentValues values, final String selection, final String[] selectionArgs) { + throw new UnsupportedOperationException(); + } + +} diff --git a/main/src/cgeo/geocaching/settings/OAuthPreference.java b/main/src/cgeo/geocaching/settings/OAuthPreference.java index 3550947..06982f5 100644 --- a/main/src/cgeo/geocaching/settings/OAuthPreference.java +++ b/main/src/cgeo/geocaching/settings/OAuthPreference.java @@ -23,8 +23,8 @@ public class OAuthPreference extends Preference { OCPL(R.string.pref_fakekey_ocpl_authorization, OCPLAuthorizationActivity.class), TWITTER(R.string.pref_fakekey_twitter_authorization, TwitterAuthorizationActivity.class); - public int prefKeyId; - public Class<?> authActivity; + public final int prefKeyId; + public final Class<?> authActivity; OAuthActivityMapping(int prefKeyId, Class<?> clazz) { this.prefKeyId = prefKeyId; diff --git a/main/src/cgeo/geocaching/settings/RegisterSend2CgeoPreference.java b/main/src/cgeo/geocaching/settings/RegisterSend2CgeoPreference.java index 3e838ab..2ca8df1 100644 --- a/main/src/cgeo/geocaching/settings/RegisterSend2CgeoPreference.java +++ b/main/src/cgeo/geocaching/settings/RegisterSend2CgeoPreference.java @@ -8,7 +8,6 @@ import cgeo.geocaching.ui.dialog.Dialogs; import cgeo.geocaching.utils.Log; import ch.boye.httpclientandroidlib.HttpResponse; - import org.apache.commons.lang3.StringUtils; import android.app.ProgressDialog; diff --git a/main/src/cgeo/geocaching/settings/Settings.java b/main/src/cgeo/geocaching/settings/Settings.java index 0732866..ee97c13 100644 --- a/main/src/cgeo/geocaching/settings/Settings.java +++ b/main/src/cgeo/geocaching/settings/Settings.java @@ -313,16 +313,16 @@ public class Settings { return getBoolean(R.string.pref_connectorOXActive, false); } - public static boolean isPremiumMember() { + public static boolean isGCPremiumMember() { // Basic Member, Premium Member, ??? - return GCConstants.MEMBER_STATUS_PM.equalsIgnoreCase(Settings.getMemberStatus()); + return GCConstants.MEMBER_STATUS_PM.equalsIgnoreCase(Settings.getGCMemberStatus()); } - public static String getMemberStatus() { + public static String getGCMemberStatus() { return getString(R.string.pref_memberstatus, ""); } - public static boolean setMemberStatus(final String memberStatus) { + public static boolean setGCMemberStatus(final String memberStatus) { if (StringUtils.isBlank(memberStatus)) { return remove(R.string.pref_memberstatus); } @@ -478,7 +478,7 @@ public class Settings { } public static boolean getLoadDirImg() { - return !isPremiumMember() && getBoolean(R.string.pref_loaddirectionimg, true); + return !isGCPremiumMember() && getBoolean(R.string.pref_loaddirectionimg, true); } public static void setGcCustomDate(final String format) { @@ -506,7 +506,7 @@ public class Settings { } public static boolean isShowCaptcha() { - return !isPremiumMember() && getBoolean(R.string.pref_showcaptcha, false); + return !isGCPremiumMember() && getBoolean(R.string.pref_showcaptcha, false); } public static boolean isExcludeDisabledCaches() { diff --git a/main/src/cgeo/geocaching/settings/SettingsActivity.java b/main/src/cgeo/geocaching/settings/SettingsActivity.java index 58acfc1..bcf6715 100644 --- a/main/src/cgeo/geocaching/settings/SettingsActivity.java +++ b/main/src/cgeo/geocaching/settings/SettingsActivity.java @@ -1,6 +1,7 @@ package cgeo.geocaching.settings; import cgeo.geocaching.CgeoApplication; +import cgeo.geocaching.DataStore; import cgeo.geocaching.Intents; import cgeo.geocaching.R; import cgeo.geocaching.SelectMapfileActivity; @@ -19,8 +20,10 @@ import cgeo.geocaching.utils.Log; import org.apache.commons.lang3.StringUtils; import org.openintents.intents.FileManagerIntents; +import android.app.ProgressDialog; import android.content.Context; import android.content.Intent; +import android.content.res.Resources; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -52,8 +55,7 @@ import java.util.Locale; */ public class SettingsActivity extends PreferenceActivity { - private static final String INTENT_GOTO = "GOTO"; - private static final int INTENT_GOTO_SERVICES = 1; + private static final String INTENT_OPEN_SCREEN = "OPEN_SCREEN"; /** * Enumeration for directory choosers. This is how we can retrieve information about the @@ -89,18 +91,21 @@ public class SettingsActivity extends PreferenceActivity { initPreferences(); Intent intent = getIntent(); - int gotoPage = intent.getIntExtra(INTENT_GOTO, 0); - if (gotoPage == INTENT_GOTO_SERVICES) { - // start with services screen - PreferenceScreen main = (PreferenceScreen) getPreference(R.string.pref_fakekey_main_screen); - try { - if (main != null) { - int index = getPreference(R.string.pref_fakekey_services_screen).getOrder(); - main.onItemClick(null, null, index, 0); - } - } catch (RuntimeException e) { - Log.e("could not open services preferences", e); - } + openInitialScreen(intent.getIntExtra(INTENT_OPEN_SCREEN, 0)); + } + + private void openInitialScreen(int initialScreen) { + if (initialScreen == 0) { + return; + } + PreferenceScreen screen = (PreferenceScreen) getPreference(initialScreen); + if (screen == null) { + return; + } + try { + setPreferenceScreen(screen); + } catch (RuntimeException e) { + Log.e("could not open preferences " + initialScreen, e); } } @@ -121,6 +126,7 @@ public class SettingsActivity extends PreferenceActivity { initSend2CgeoPreferences(); initServicePreferences(); initNavigationMenuPreferences(); + initMaintenanceButtons(); for (int k : new int[] { R.string.pref_username, R.string.pref_password, R.string.pref_pass_vote, R.string.pref_signature, @@ -141,9 +147,9 @@ public class SettingsActivity extends PreferenceActivity { getPreference(appEnum.preferenceKey).setEnabled(true); } } - getPreference(R.string.pref_fakekey_basicmembers_screen) - .setEnabled(!Settings.isPremiumMember()); - redrawScreen(R.string.pref_fakekey_navigation_menu_screen); + getPreference(R.string.preference_screen_basicmembers) + .setEnabled(!Settings.isGCPremiumMember()); + redrawScreen(R.string.preference_screen_navigation_menu); } private void initServicePreferences() { @@ -310,6 +316,35 @@ public class SettingsActivity extends PreferenceActivity { }); } + public void initMaintenanceButtons() { + Preference dirMaintenance = getPreference(R.string.pref_fakekey_preference_maintenance_directories); + dirMaintenance.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(final Preference preference) { + // disable the button, as the cleanup runs in background and should not be invoked a second time + preference.setEnabled(false); + + Resources res = getResources(); + final SettingsActivity activity = SettingsActivity.this; + final ProgressDialog dialog = ProgressDialog.show(activity, res.getString(R.string.init_maintenance), res.getString(R.string.init_maintenance_directories), true, false); + new Thread() { + @Override + public void run() { + DataStore.removeObsoleteCacheDirectories(); + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + dialog.dismiss(); + } + }); + } + }.start(); + + return true; + } + }); + } + private void initDbLocationPreference() { Preference p = getPreference(R.string.pref_dbonsdcard); p.setPersistent(false); @@ -336,21 +371,29 @@ public class SettingsActivity extends PreferenceActivity { } void initBasicMemberPreferences() { - getPreference(R.string.pref_fakekey_basicmembers_screen) - .setEnabled(!Settings.isPremiumMember()); + getPreference(R.string.preference_screen_basicmembers) + .setEnabled(!Settings.isGCPremiumMember()); getPreference(R.string.pref_loaddirectionimg) - .setEnabled(!Settings.isPremiumMember()); + .setEnabled(!Settings.isGCPremiumMember()); getPreference(R.string.pref_showcaptcha) - .setEnabled(!Settings.isPremiumMember()); + .setEnabled(!Settings.isGCPremiumMember()); - redrawScreen(R.string.pref_fakekey_services_screen); + redrawScreen(R.string.preference_screen_services); } - void redrawScreen(int key) { - PreferenceScreen screen = (PreferenceScreen) getPreference(key); - if (screen == null) { + /** + * Refresh a preference screen. Has no effect when called for a preference, that is not actually a preference + * screen. + * + * @param key + * Key of a preference screen. + */ + void redrawScreen(final int key) { + final Preference preference = getPreference(key); + if (!(preference instanceof PreferenceScreen)) { return; } + final PreferenceScreen screen = (PreferenceScreen) preference; ListAdapter adapter = screen.getRootAdapter(); if (adapter instanceof BaseAdapter) { ((BaseAdapter) adapter).notifyDataSetChanged(); @@ -399,9 +442,9 @@ public class SettingsActivity extends PreferenceActivity { : R.string.settings_authorize)); } - public static void jumpToServicesPage(final Context fromActivity) { + public static void openForScreen(final int preferenceScreenKey, final Context fromActivity) { final Intent intent = new Intent(fromActivity, SettingsActivity.class); - intent.putExtra(INTENT_GOTO, INTENT_GOTO_SERVICES); + intent.putExtra(INTENT_OPEN_SCREEN, preferenceScreenKey); fromActivity.startActivity(intent); } @@ -445,15 +488,15 @@ public class SettingsActivity extends PreferenceActivity { break; case R.string.pref_fakekey_ocde_authorization: setOCDEAuthTitle(); - redrawScreen(R.string.pref_fakekey_services_screen); + redrawScreen(R.string.preference_screen_ocde); break; case R.string.pref_fakekey_ocpl_authorization: setOCPLAuthTitle(); - redrawScreen(R.string.pref_fakekey_services_screen); + redrawScreen(R.string.preference_screen_ocpl); break; case R.string.pref_fakekey_twitter_authorization: setTwitterAuthTitle(); - redrawScreen(R.string.pref_fakekey_services_screen); + redrawScreen(R.string.preference_screen_twitter); break; default: throw new IllegalArgumentException(); @@ -582,6 +625,13 @@ public class SettingsActivity extends PreferenceActivity { preferenceActivity.addPreferencesFromResource(preferencesResId); } + @SuppressWarnings("deprecation") + @Override + public void setPreferenceScreen(PreferenceScreen preferenceScreen) { + // TODO replace with fragment based code + super.setPreferenceScreen(preferenceScreen); + } + private static boolean isPreference(final Preference preference, int preferenceKeyId) { return getKey(preferenceKeyId).equals(preference.getKey()); } diff --git a/main/src/cgeo/geocaching/settings/TemplateTextPreference.java b/main/src/cgeo/geocaching/settings/TemplateTextPreference.java index a703231..667b02b 100644 --- a/main/src/cgeo/geocaching/settings/TemplateTextPreference.java +++ b/main/src/cgeo/geocaching/settings/TemplateTextPreference.java @@ -2,6 +2,7 @@ package cgeo.geocaching.settings; import cgeo.geocaching.R; import cgeo.geocaching.activity.ActivityMixin; +import cgeo.geocaching.ui.dialog.Dialogs; import cgeo.geocaching.utils.LogTemplateProvider; import cgeo.geocaching.utils.LogTemplateProvider.LogTemplate; @@ -49,6 +50,7 @@ public class TemplateTextPreference extends DialogPreference { editText = (EditText) view.findViewById(R.id.signature_dialog_text); editText.setText(getPersistedString(initialValue != null ? initialValue : StringUtils.EMPTY)); + Dialogs.moveCursorToEnd(editText); Button button = (Button) view.findViewById(R.id.signature_templates); button.setOnClickListener(new View.OnClickListener() { diff --git a/main/src/cgeo/geocaching/sorting/AbstractCacheComparator.java b/main/src/cgeo/geocaching/sorting/AbstractCacheComparator.java index a1c04a4..2b171b4 100644 --- a/main/src/cgeo/geocaching/sorting/AbstractCacheComparator.java +++ b/main/src/cgeo/geocaching/sorting/AbstractCacheComparator.java @@ -3,6 +3,7 @@ package cgeo.geocaching.sorting; import cgeo.geocaching.Geocache; import cgeo.geocaching.utils.Log; +import org.apache.commons.lang3.StringUtils; /** * abstract super implementation for all cache comparators @@ -13,25 +14,35 @@ public abstract class AbstractCacheComparator implements CacheComparator { @Override 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)) { - return 0; + final boolean canCompare1 = canCompare(cache1); + final boolean canCompare2 = canCompare(cache2); + if (!canCompare1) { + return canCompare2 ? 1 : fallbackToGeocode(cache1, cache2); } - return compareCaches(cache1, cache2); - } catch (Exception e) { + return canCompare2 ? compareCaches(cache1, cache2) : -1; + } catch (final Exception e) { Log.e("AbstractCacheComparator.compare", e); + // This may violate the Comparator interface if the exception is not systematic. + return fallbackToGeocode(cache1, cache2); } - return 0; + } + + private static int fallbackToGeocode(final Geocache cache1, final Geocache cache2) { + return StringUtils.defaultString(cache1.getGeocode()).compareToIgnoreCase(StringUtils.defaultString(cache2.getGeocode())); } /** - * Check necessary preconditions (like missing fields) before running the comparison itself - * - * @param cache1 - * @param cache2 - * @return + * Check necessary preconditions (like missing fields) before running the comparison itself. + * Caches not filling the conditions will be placed last, sorted by Geocode. + * + * The default returns <code>true</code> and can be overridden if needed in child classes. + * + * @param cache + * @return <code>true</code> if the cache holds the necessary data to be compared meaningfully */ - protected abstract boolean canCompare(final Geocache cache1, final Geocache cache2); + protected boolean canCompare(final Geocache cache) { + return true; + } /** * Compares two caches. Logging and exception handling is implemented outside this method already. diff --git a/main/src/cgeo/geocaching/sorting/CacheComparator.java b/main/src/cgeo/geocaching/sorting/CacheComparator.java index 7932729..b06a4b0 100644 --- a/main/src/cgeo/geocaching/sorting/CacheComparator.java +++ b/main/src/cgeo/geocaching/sorting/CacheComparator.java @@ -1,9 +1,9 @@ package cgeo.geocaching.sorting; -import java.util.Comparator; - import cgeo.geocaching.Geocache; +import java.util.Comparator; + 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 99a535a..8cf3d9d 100644 --- a/main/src/cgeo/geocaching/sorting/ComparatorUserInterface.java +++ b/main/src/cgeo/geocaching/sorting/ComparatorUserInterface.java @@ -2,7 +2,8 @@ package cgeo.geocaching.sorting; import cgeo.geocaching.R; import cgeo.geocaching.utils.Log; -import cgeo.geocaching.utils.RunnableWithArgument; + +import rx.util.functions.Action1; import android.app.Activity; import android.app.AlertDialog; @@ -70,7 +71,7 @@ public class ComparatorUserInterface { registry.add(new ComparatorEntry(res.getString(resourceId), comparatorClass)); } - public void selectComparator(final CacheComparator current, final RunnableWithArgument<CacheComparator> runAfterwards) { + public void selectComparator(final CacheComparator current, final Action1<CacheComparator> runAfterwards) { final AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setTitle(R.string.caches_sort_title); @@ -85,11 +86,11 @@ public class ComparatorUserInterface { ComparatorEntry entry = registry.get(itemIndex); try { if (entry.cacheComparator == null) { - runAfterwards.run(null); + runAfterwards.call(null); } else { CacheComparator comparator = entry.cacheComparator.newInstance(); - runAfterwards.run(comparator); + runAfterwards.call(comparator); } } catch (InstantiationException e) { Log.e("selectComparator", e); diff --git a/main/src/cgeo/geocaching/sorting/DateComparator.java b/main/src/cgeo/geocaching/sorting/DateComparator.java index 091f6a4..9df70f9 100644 --- a/main/src/cgeo/geocaching/sorting/DateComparator.java +++ b/main/src/cgeo/geocaching/sorting/DateComparator.java @@ -1,7 +1,7 @@ package cgeo.geocaching.sorting; -import cgeo.geocaching.Geocache; import cgeo.geocaching.CgeoApplication; +import cgeo.geocaching.Geocache; import java.util.ArrayList; import java.util.Date; @@ -12,11 +12,6 @@ import java.util.Date; public class DateComparator extends AbstractCacheComparator { @Override - protected boolean canCompare(Geocache cache1, Geocache cache2) { - return true; - } - - @Override protected int compareCaches(Geocache cache1, Geocache cache2) { final Date date1 = cache1.getHiddenDate(); final Date date2 = cache2.getHiddenDate(); diff --git a/main/src/cgeo/geocaching/sorting/DifficultyComparator.java b/main/src/cgeo/geocaching/sorting/DifficultyComparator.java index 73d12fa..459f38d 100644 --- a/main/src/cgeo/geocaching/sorting/DifficultyComparator.java +++ b/main/src/cgeo/geocaching/sorting/DifficultyComparator.java @@ -9,8 +9,8 @@ import cgeo.geocaching.Geocache; public class DifficultyComparator extends AbstractCacheComparator { @Override - protected boolean canCompare(Geocache cache1, Geocache cache2) { - return cache1.getDifficulty() != 0.0 && cache2.getDifficulty() != 0.0; + protected boolean canCompare(Geocache cache) { + return cache.getDifficulty() != 0.0; } @Override diff --git a/main/src/cgeo/geocaching/sorting/DistanceComparator.java b/main/src/cgeo/geocaching/sorting/DistanceComparator.java index 731e356..541ce48 100644 --- a/main/src/cgeo/geocaching/sorting/DistanceComparator.java +++ b/main/src/cgeo/geocaching/sorting/DistanceComparator.java @@ -36,11 +36,6 @@ public class DistanceComparator extends AbstractCacheComparator { } @Override - protected boolean canCompare(Geocache cache1, Geocache cache2) { - return true; - } - - @Override protected int compareCaches(final Geocache cache1, final Geocache cache2) { calculateAllDistances(); final Float distance1 = cache1.getDistance(); diff --git a/main/src/cgeo/geocaching/sorting/FindsComparator.java b/main/src/cgeo/geocaching/sorting/FindsComparator.java index c889776..7f2ef50 100644 --- a/main/src/cgeo/geocaching/sorting/FindsComparator.java +++ b/main/src/cgeo/geocaching/sorting/FindsComparator.java @@ -5,8 +5,8 @@ import cgeo.geocaching.Geocache; public class FindsComparator extends AbstractCacheComparator { @Override - protected boolean canCompare(Geocache cache1, Geocache cache2) { - return cache1.getLogCounts() != null && cache2.getLogCounts() != null; + protected boolean canCompare(Geocache cache) { + return cache.getLogCounts() != null; } @Override diff --git a/main/src/cgeo/geocaching/sorting/GeocodeComparator.java b/main/src/cgeo/geocaching/sorting/GeocodeComparator.java index fff26c6..e700f13 100644 --- a/main/src/cgeo/geocaching/sorting/GeocodeComparator.java +++ b/main/src/cgeo/geocaching/sorting/GeocodeComparator.java @@ -2,23 +2,20 @@ package cgeo.geocaching.sorting; import cgeo.geocaching.Geocache; -import org.apache.commons.lang3.StringUtils; - /** * sorts caches by geo code, therefore effectively sorting by cache age - * + * */ public class GeocodeComparator extends AbstractCacheComparator { @Override - protected boolean canCompare(Geocache cache1, Geocache cache2) { - return StringUtils.isNotBlank(cache1.getGeocode()) - && StringUtils.isNotBlank(cache2.getGeocode()); + protected boolean canCompare(final Geocache cache) { + // This will fall back to geocode comparisons. + return false; } @Override 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()); + throw new RuntimeException("should never be called"); } } diff --git a/main/src/cgeo/geocaching/sorting/InventoryComparator.java b/main/src/cgeo/geocaching/sorting/InventoryComparator.java index 73ea2c5..9d19b64 100644 --- a/main/src/cgeo/geocaching/sorting/InventoryComparator.java +++ b/main/src/cgeo/geocaching/sorting/InventoryComparator.java @@ -8,11 +8,6 @@ import cgeo.geocaching.Geocache; public class InventoryComparator extends AbstractCacheComparator { @Override - protected boolean canCompare(final Geocache cache1, final Geocache cache2) { - return true; - } - - @Override protected int compareCaches(final Geocache cache1, final Geocache cache2) { return cache2.getInventoryItems() - cache1.getInventoryItems(); } diff --git a/main/src/cgeo/geocaching/sorting/NameComparator.java b/main/src/cgeo/geocaching/sorting/NameComparator.java index b432ad0..2941b1c 100644 --- a/main/src/cgeo/geocaching/sorting/NameComparator.java +++ b/main/src/cgeo/geocaching/sorting/NameComparator.java @@ -11,8 +11,8 @@ import org.apache.commons.lang3.StringUtils; public class NameComparator extends AbstractCacheComparator { @Override - protected boolean canCompare(Geocache cache1, Geocache cache2) { - return StringUtils.isNotBlank(cache1.getName()) && StringUtils.isNotBlank(cache2.getName()); + protected boolean canCompare(Geocache cache) { + return StringUtils.isNotBlank(cache.getName()); } @Override diff --git a/main/src/cgeo/geocaching/sorting/PopularityComparator.java b/main/src/cgeo/geocaching/sorting/PopularityComparator.java index e256654..2dbee68 100644 --- a/main/src/cgeo/geocaching/sorting/PopularityComparator.java +++ b/main/src/cgeo/geocaching/sorting/PopularityComparator.java @@ -9,11 +9,6 @@ import cgeo.geocaching.Geocache; public class PopularityComparator extends AbstractCacheComparator { @Override - protected boolean canCompare(final Geocache cache1, final Geocache cache2) { - return true; - } - - @Override protected int compareCaches(final Geocache cache1, final Geocache cache2) { return cache2.getFavoritePoints() - cache1.getFavoritePoints(); } diff --git a/main/src/cgeo/geocaching/sorting/PopularityRatioComparator.java b/main/src/cgeo/geocaching/sorting/PopularityRatioComparator.java index f438762..1ed8e68 100644 --- a/main/src/cgeo/geocaching/sorting/PopularityRatioComparator.java +++ b/main/src/cgeo/geocaching/sorting/PopularityRatioComparator.java @@ -11,11 +11,6 @@ import cgeo.geocaching.Geocache; public class PopularityRatioComparator extends AbstractCacheComparator { @Override - protected boolean canCompare(final Geocache cache1, final Geocache cache2) { - return true; - } - - @Override protected int compareCaches(final Geocache cache1, final Geocache cache2) { float ratio1 = 0.0f; diff --git a/main/src/cgeo/geocaching/sorting/RatingComparator.java b/main/src/cgeo/geocaching/sorting/RatingComparator.java index 72cf6c8..6f2c615 100644 --- a/main/src/cgeo/geocaching/sorting/RatingComparator.java +++ b/main/src/cgeo/geocaching/sorting/RatingComparator.java @@ -9,11 +9,6 @@ import cgeo.geocaching.Geocache; public class RatingComparator extends AbstractCacheComparator { @Override - protected boolean canCompare(final Geocache cache1, final Geocache cache2) { - return true; - } - - @Override protected int compareCaches(final Geocache cache1, final Geocache cache2) { final float rating1 = cache1.getRating(); final float rating2 = cache2.getRating(); diff --git a/main/src/cgeo/geocaching/sorting/SizeComparator.java b/main/src/cgeo/geocaching/sorting/SizeComparator.java index d128822..c8de586 100644 --- a/main/src/cgeo/geocaching/sorting/SizeComparator.java +++ b/main/src/cgeo/geocaching/sorting/SizeComparator.java @@ -9,8 +9,8 @@ import cgeo.geocaching.Geocache; public class SizeComparator extends AbstractCacheComparator { @Override - protected boolean canCompare(Geocache cache1, Geocache cache2) { - return cache1.getSize() != null && cache2.getSize() != null; + protected boolean canCompare(Geocache cache) { + return cache.getSize() != null; } @Override diff --git a/main/src/cgeo/geocaching/sorting/StateComparator.java b/main/src/cgeo/geocaching/sorting/StateComparator.java index b99c3c0..9488bd9 100644 --- a/main/src/cgeo/geocaching/sorting/StateComparator.java +++ b/main/src/cgeo/geocaching/sorting/StateComparator.java @@ -9,11 +9,6 @@ import cgeo.geocaching.Geocache; public class StateComparator extends AbstractCacheComparator { @Override - protected boolean canCompare(final Geocache cache1, final Geocache cache2) { - return true; - } - - @Override protected int compareCaches(final Geocache cache1, final Geocache cache2) { return getState(cache1) - getState(cache2); } diff --git a/main/src/cgeo/geocaching/sorting/StorageTimeComparator.java b/main/src/cgeo/geocaching/sorting/StorageTimeComparator.java index 78ba742..b718d3b 100644 --- a/main/src/cgeo/geocaching/sorting/StorageTimeComparator.java +++ b/main/src/cgeo/geocaching/sorting/StorageTimeComparator.java @@ -5,11 +5,6 @@ import cgeo.geocaching.Geocache; public class StorageTimeComparator extends AbstractCacheComparator { @Override - protected boolean canCompare(Geocache cache1, Geocache cache2) { - return true; - } - - @Override 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 be1e9bb..9bbb5f7 100644 --- a/main/src/cgeo/geocaching/sorting/TerrainComparator.java +++ b/main/src/cgeo/geocaching/sorting/TerrainComparator.java @@ -9,8 +9,8 @@ import cgeo.geocaching.Geocache; public class TerrainComparator extends AbstractCacheComparator { @Override - protected boolean canCompare(final Geocache cache1, final Geocache cache2) { - return cache1.getTerrain() != 0.0 && cache2.getTerrain() != 0.0; + protected boolean canCompare(final Geocache cache) { + return cache.getTerrain() != 0.0; } @Override diff --git a/main/src/cgeo/geocaching/sorting/VisitComparator.java b/main/src/cgeo/geocaching/sorting/VisitComparator.java index 27d3170..1589a4c 100644 --- a/main/src/cgeo/geocaching/sorting/VisitComparator.java +++ b/main/src/cgeo/geocaching/sorting/VisitComparator.java @@ -9,11 +9,6 @@ import cgeo.geocaching.Geocache; public class VisitComparator extends AbstractCacheComparator { @Override - protected boolean canCompare(final Geocache cache1, final Geocache cache2) { - return true; - } - - @Override 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 dc0304b..cd4ad7e 100644 --- a/main/src/cgeo/geocaching/sorting/VoteComparator.java +++ b/main/src/cgeo/geocaching/sorting/VoteComparator.java @@ -8,11 +8,6 @@ import cgeo.geocaching.Geocache; public class VoteComparator extends AbstractCacheComparator { @Override - protected boolean canCompare(Geocache cache1, Geocache cache2) { - return true; - } - - @Override 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/speech/SpeechService.java b/main/src/cgeo/geocaching/speech/SpeechService.java index 2a72bbf..8c650c3 100644 --- a/main/src/cgeo/geocaching/speech/SpeechService.java +++ b/main/src/cgeo/geocaching/speech/SpeechService.java @@ -47,7 +47,7 @@ public class SpeechService extends Service implements OnInitListener { GeoDirHandler geoHandler = new GeoDirHandler() { @Override - protected void updateDirection(float newDirection) { + public void updateDirection(float newDirection) { if (CgeoApplication.getInstance().currentGeo().getSpeed() <= 5) { direction = DirectionProvider.getDirectionNow(startingActivity, newDirection); directionInitialized = true; @@ -56,7 +56,7 @@ public class SpeechService extends Service implements OnInitListener { } @Override - protected void updateGeoData(cgeo.geocaching.IGeoData newGeo) { + public void updateGeoData(cgeo.geocaching.IGeoData newGeo) { position = newGeo.getCoords(); positionInitialized = true; if (!Settings.isUseCompass() || newGeo.getSpeed() > 5) { diff --git a/main/src/cgeo/geocaching/speech/TextFactory.java b/main/src/cgeo/geocaching/speech/TextFactory.java index 2a3b6d7..eb780c6 100644 --- a/main/src/cgeo/geocaching/speech/TextFactory.java +++ b/main/src/cgeo/geocaching/speech/TextFactory.java @@ -2,9 +2,9 @@ package cgeo.geocaching.speech; import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.R; -import cgeo.geocaching.settings.Settings; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.IConversion; +import cgeo.geocaching.settings.Settings; import cgeo.geocaching.utils.AngleUtils; import java.util.Locale; diff --git a/main/src/cgeo/geocaching/twitter/Twitter.java b/main/src/cgeo/geocaching/twitter/Twitter.java index 7213789..1caa2d4 100644 --- a/main/src/cgeo/geocaching/twitter/Twitter.java +++ b/main/src/cgeo/geocaching/twitter/Twitter.java @@ -17,7 +17,6 @@ import cgeo.geocaching.utils.LogTemplateProvider; import cgeo.geocaching.utils.LogTemplateProvider.LogContext; import ch.boye.httpclientandroidlib.HttpResponse; - import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; diff --git a/main/src/cgeo/geocaching/ui/AbstractCachingPageViewCreator.java b/main/src/cgeo/geocaching/ui/AbstractCachingPageViewCreator.java index ed5d182..0c67384 100644 --- a/main/src/cgeo/geocaching/ui/AbstractCachingPageViewCreator.java +++ b/main/src/cgeo/geocaching/ui/AbstractCachingPageViewCreator.java @@ -3,7 +3,6 @@ package cgeo.geocaching.ui; import cgeo.geocaching.activity.AbstractViewPagerActivity.PageViewCreator; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; - import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; diff --git a/main/src/cgeo/geocaching/ui/AnchorAwareLinkMovementMethod.java b/main/src/cgeo/geocaching/ui/AnchorAwareLinkMovementMethod.java index db82e5c..d4c2e10 100644 --- a/main/src/cgeo/geocaching/ui/AnchorAwareLinkMovementMethod.java +++ b/main/src/cgeo/geocaching/ui/AnchorAwareLinkMovementMethod.java @@ -8,7 +8,7 @@ import android.widget.TextView; /** * <code>LinkMovementMethod</code> with built-in suppression of errors for links, where the URL cannot be handled * correctly by Android. - * + * */ public class AnchorAwareLinkMovementMethod extends LinkMovementMethod { diff --git a/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java b/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java index 7fe77c4..5d8ebef 100644 --- a/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java +++ b/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java @@ -39,6 +39,11 @@ public final class CacheDetailsCreator { parentView.removeAllViews(); } + /** + * @param nameId + * @param value + * @return the view containing the displayed string (i.e. the right side one from the pair of "label": "value") + */ public TextView add(final int nameId, final CharSequence value) { final RelativeLayout layout = (RelativeLayout) activity.getLayoutInflater().inflate(R.layout.cache_information_item, null); final TextView nameView = (TextView) layout.findViewById(R.id.name); @@ -188,14 +193,24 @@ public final class CacheDetailsCreator { if (!cache.isEventCache()) { return; } + addHiddenDate(cache); + } + + public TextView addHiddenDate(final @NonNull Geocache cache) { final Date hiddenDate = cache.getHiddenDate(); if (hiddenDate == null) { - return; + return null; } final long time = hiddenDate.getTime(); if (time > 0) { - final String dateString = DateUtils.formatDateTime(CgeoApplication.getInstance().getBaseContext(), time, DateUtils.FORMAT_SHOW_WEEKDAY) + ", " + Formatter.formatFullDate(time); - add(R.string.cache_event, dateString); + String dateString = Formatter.formatFullDate(time); + if (cache.isEventCache()) { + dateString = DateUtils.formatDateTime(CgeoApplication.getInstance().getBaseContext(), time, DateUtils.FORMAT_SHOW_WEEKDAY) + ", " + dateString; + } + final TextView view = add(cache.isEventCache() ? R.string.cache_event : R.string.cache_hidden, dateString); + view.setId(R.id.date); + return view; } + return null; } } diff --git a/main/src/cgeo/geocaching/ui/EditNoteDialog.java b/main/src/cgeo/geocaching/ui/EditNoteDialog.java index 2af1cb8..4bfa140 100644 --- a/main/src/cgeo/geocaching/ui/EditNoteDialog.java +++ b/main/src/cgeo/geocaching/ui/EditNoteDialog.java @@ -1,6 +1,7 @@ package cgeo.geocaching.ui; import cgeo.geocaching.R; +import cgeo.geocaching.ui.dialog.Dialogs; import android.app.AlertDialog; import android.app.Dialog; @@ -40,6 +41,7 @@ public class EditNoteDialog extends DialogFragment { String initialNote = getArguments().getString(ARGUMENT_INITIAL_NOTE); if (initialNote != null) { mEditText.setText(initialNote); + Dialogs.moveCursorToEnd(mEditText); getArguments().remove(ARGUMENT_INITIAL_NOTE); } diff --git a/main/src/cgeo/geocaching/ui/ImagesList.java b/main/src/cgeo/geocaching/ui/ImagesList.java index 4eaf06d..f564583 100644 --- a/main/src/cgeo/geocaching/ui/ImagesList.java +++ b/main/src/cgeo/geocaching/ui/ImagesList.java @@ -9,6 +9,9 @@ import cgeo.geocaching.utils.Log; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; +import rx.android.concurrency.AndroidSchedulers; +import rx.subscriptions.CompositeSubscription; +import rx.util.functions.Action1; import android.app.Activity; import android.content.Intent; @@ -18,7 +21,6 @@ import android.graphics.Bitmap.CompressFormat; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.net.Uri; -import android.os.AsyncTask; import android.text.Html; import android.util.SparseArray; import android.view.ContextMenu; @@ -41,11 +43,11 @@ public class ImagesList { private BitmapDrawable currentDrawable; private Image currentImage; + private CompositeSubscription subscriptions = new CompositeSubscription(); public enum ImageType { LogImages(R.string.cache_log_images_title), - SpoilerImages(R.string.cache_spoiler_images_title), - AllImages(R.string.cache_images_title); + SpoilerImages(R.string.cache_spoiler_images_title); private final int titleResId; @@ -76,11 +78,13 @@ public class ImagesList { } public void loadImages(final View parentView, final List<Image> images, final boolean offline) { - imagesView = (LinearLayout) parentView.findViewById(R.id.spoiler_list); + final HtmlImage imgGetter = new HtmlImage(geocode, true, offline ? StoredList.STANDARD_LIST_ID : StoredList.TEMPORARY_LIST_ID, false); + for (final Image img : images) { - LinearLayout rowView = (LinearLayout) inflater.inflate(R.layout.cache_image_item, null); + final LinearLayout rowView = (LinearLayout) inflater.inflate(R.layout.cache_image_item, null); + assert(rowView != null); if (StringUtils.isNotBlank(img.getTitle())) { ((TextView) rowView.findViewById(R.id.title)).setText(Html.fromHtml(img.getTitle())); @@ -93,66 +97,56 @@ public class ImagesList { descView.setVisibility(View.VISIBLE); } - new AsyncImgLoader(rowView, img, offline).execute(); + subscriptions.add(imgGetter.fetchDrawable(img.getUrl()) + .observeOn(AndroidSchedulers.mainThread()).subscribe(new Action1<BitmapDrawable>() { + @Override + public void call(final BitmapDrawable image) { + display(image, img, rowView); + } + })); + imagesView.addView(rowView); } } - private class AsyncImgLoader extends AsyncTask<Void, Void, BitmapDrawable> { + private void display(final BitmapDrawable image, final Image img, final LinearLayout view) { + if (image != null) { + bitmaps.add(image.getBitmap()); + final ImageView imageView = (ImageView) inflater.inflate(R.layout.image_item, null); + assert(imageView != null); - final private LinearLayout view; - final private Image img; - final boolean offline; + final Rect bounds = image.getBounds(); - public AsyncImgLoader(final LinearLayout view, final Image img, final boolean offline) { - this.view = view; - this.img = img; - this.offline = offline; - } + imageView.setImageResource(R.drawable.image_not_loaded); + imageView.setClickable(true); + imageView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View arg0) { + viewImageInStandardApp(image); + } + }); + activity.registerForContextMenu(imageView); + imageView.setImageDrawable(image); + imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); + imageView.setLayoutParams(new LayoutParams(bounds.width(), bounds.height())); - @Override - protected BitmapDrawable doInBackground(Void... params) { - final HtmlImage imgGetter = new HtmlImage(geocode, true, offline ? StoredList.STANDARD_LIST_ID : StoredList.TEMPORARY_LIST_ID, false); - return imgGetter.getDrawable(img.getUrl()); - } + view.findViewById(R.id.progress_bar).setVisibility(View.GONE); + view.addView(imageView); - @Override - protected void onPostExecute(final BitmapDrawable image) { - if (image != null) { - bitmaps.add(image.getBitmap()); - final ImageView imageView = (ImageView) inflater.inflate(R.layout.image_item, null); - - final Rect bounds = image.getBounds(); - - imageView.setImageResource(R.drawable.image_not_loaded); - imageView.setClickable(true); - imageView.setOnClickListener(new View.OnClickListener() { - - @Override - public void onClick(View arg0) { - viewImageInStandardApp(image); - } - }); - activity.registerForContextMenu(imageView); - imageView.setImageDrawable(image); - imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); - imageView.setLayoutParams(new LayoutParams(bounds.width(), bounds.height())); - - view.findViewById(R.id.progress_bar).setVisibility(View.GONE); - view.addView(imageView); - - imageView.setId(image.hashCode()); - images.put(imageView.getId(), img); - } + imageView.setId(image.hashCode()); + images.put(imageView.getId(), img); } } public void removeAllViews() { - imagesView.removeAllViews(); for (final Bitmap b : bitmaps) { b.recycle(); } bitmaps.clear(); + + // Stop loading images if some are still in progress + subscriptions.unsubscribe(); + imagesView.removeAllViews(); } public void onCreateContextMenu(ContextMenu menu, View v) { diff --git a/main/src/cgeo/geocaching/ui/dialog/CoordinatesInputDialog.java b/main/src/cgeo/geocaching/ui/dialog/CoordinatesInputDialog.java index 93f50e1..651ff6e 100644 --- a/main/src/cgeo/geocaching/ui/dialog/CoordinatesInputDialog.java +++ b/main/src/cgeo/geocaching/ui/dialog/CoordinatesInputDialog.java @@ -54,7 +54,7 @@ public class CoordinatesInputDialog extends NoTitleDialog { } else if (geo != null && geo.getCoords() != null) { this.gp = geo.getCoords(); } else { - this.gp = new Geopoint(0.0, 0.0); + this.gp = Geopoint.ZERO; } } @@ -396,7 +396,7 @@ public class CoordinatesInputDialog extends NoTitleDialog { if (geo != null && geo.getCoords() != null) { gp = geo.getCoords(); } else { - gp = new Geopoint(0.0, 0.0); + gp = Geopoint.ZERO; } } } diff --git a/main/src/cgeo/geocaching/ui/dialog/Dialogs.java b/main/src/cgeo/geocaching/ui/dialog/Dialogs.java index 865ba70..cbd5c94 100644 --- a/main/src/cgeo/geocaching/ui/dialog/Dialogs.java +++ b/main/src/cgeo/geocaching/ui/dialog/Dialogs.java @@ -1,11 +1,12 @@ package cgeo.geocaching.ui.dialog; import cgeo.geocaching.CgeoApplication; -import cgeo.geocaching.utils.RunnableWithArgument; import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.Nullable; +import rx.util.functions.Action1; + import android.app.Activity; import android.app.AlertDialog; import android.app.AlertDialog.Builder; @@ -298,7 +299,7 @@ public final class Dialogs { /** * Show a message dialog for input from the user. The okay button is only enabled on non empty input. - * + * * @param context * activity owning the dialog * @param title @@ -310,7 +311,7 @@ public final class Dialogs { * @param okayListener * listener to be run on okay */ - public static void input(final Activity context, final int title, final String defaultValue, final int buttonTitle, final RunnableWithArgument<String> okayListener) { + public static void input(final Activity context, final int title, final String defaultValue, final int buttonTitle, final Action1<String> okayListener) { final EditText input = new EditText(context); input.setInputType(InputType.TYPE_TEXT_FLAG_CAP_SENTENCES | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_CLASS_TEXT); input.setText(defaultValue); @@ -322,7 +323,7 @@ public final class Dialogs { @Override public void onClick(DialogInterface dialog, int which) { - okayListener.run(input.getText().toString()); + okayListener.call(input.getText().toString()); } }); builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @@ -357,8 +358,16 @@ public final class Dialogs { dialog.show(); enableDialogButtonIfNotEmpty(dialog, defaultValue); - // position cursor after text - input.setSelection(input.getText().length()); + moveCursorToEnd(input); + } + + /** + * Move the cursor to the end of the input field. + * + * @param input + */ + public static void moveCursorToEnd(final EditText input) { + input.setSelection(input.getText().length(), input.getText().length()); } private static void enableDialogButtonIfNotEmpty(final AlertDialog dialog, final String input) { diff --git a/main/src/cgeo/geocaching/ui/dialog/LiveMapInfoDialogBuilder.java b/main/src/cgeo/geocaching/ui/dialog/LiveMapInfoDialogBuilder.java index 6ad59ec..c29f549 100644 --- a/main/src/cgeo/geocaching/ui/dialog/LiveMapInfoDialogBuilder.java +++ b/main/src/cgeo/geocaching/ui/dialog/LiveMapInfoDialogBuilder.java @@ -1,15 +1,14 @@ package cgeo.geocaching.ui.dialog; +import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.R; import cgeo.geocaching.settings.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 { @@ -20,12 +19,7 @@ public class LiveMapInfoDialogBuilder { 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() { @@ -33,10 +27,7 @@ public class LiveMapInfoDialogBuilder { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); - CgeoApplication.getInstance().setLiveMapHintShown(); - if (checkBoxHide.getVisibility() == View.VISIBLE && checkBoxHide.isChecked()) { - Settings.setHideLiveHint(true); - } + CgeoApplication.getInstance().setLiveMapHintShownInThisSession(); } }); return builder.create(); diff --git a/main/src/cgeo/geocaching/utils/CancellableHandler.java b/main/src/cgeo/geocaching/utils/CancellableHandler.java index cb4b9db..01fb568 100644 --- a/main/src/cgeo/geocaching/utils/CancellableHandler.java +++ b/main/src/cgeo/geocaching/utils/CancellableHandler.java @@ -2,6 +2,9 @@ package cgeo.geocaching.utils; import cgeo.geocaching.CgeoApplication; +import rx.Subscription; +import rx.subscriptions.CompositeSubscription; + import android.os.Handler; import android.os.Message; @@ -13,6 +16,7 @@ public abstract class CancellableHandler extends Handler { protected static final int UPDATE_LOAD_PROGRESS_DETAIL = 42186; private volatile boolean cancelled = false; + private static CompositeSubscription subscriptions = new CompositeSubscription(); private static class CancelHolder { final Object payload; @@ -30,6 +34,7 @@ public abstract class CancellableHandler extends Handler { if (message.obj instanceof CancelHolder) { cancelled = true; + subscriptions.unsubscribe(); handleCancel(((CancelHolder) message.obj).payload); } else { handleRegularMessage(message); @@ -37,6 +42,17 @@ public abstract class CancellableHandler extends Handler { } /** + * Add a subscription to the list of subscriptions to be subscribed at cancellation time. + */ + final public void unsubscribeIfCancelled(final Subscription subscription) { + subscriptions.add(subscription); + if (cancelled) { + // Protect against race conditions + subscriptions.unsubscribe(); + } + } + + /** * Handle a non-cancel message.<br> * Subclasses must implement this to handle messages. * diff --git a/main/src/cgeo/geocaching/utils/GeoDirHandler.java b/main/src/cgeo/geocaching/utils/GeoDirHandler.java index c85648b..b9c605b 100644 --- a/main/src/cgeo/geocaching/utils/GeoDirHandler.java +++ b/main/src/cgeo/geocaching/utils/GeoDirHandler.java @@ -4,12 +4,15 @@ import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.IGeoData; import cgeo.geocaching.settings.Settings; -import android.os.Handler; -import android.os.Message; +import rx.Observable; +import rx.Subscription; +import rx.android.concurrency.AndroidSchedulers; +import rx.util.functions.Action1; + +import java.util.concurrent.TimeUnit; /** - * GeoData and Direction handler. Manipulating geodata and direction information - * through a GeoDirHandler ensures that all listeners are registered from a {@link android.os.Looper} thread. + * GeoData and Direction handler. * <p> * To use this class, override at least one of {@link #updateDirection(float)} or {@link #updateGeoData(IGeoData)}. You * need to start the handler using one of @@ -21,47 +24,11 @@ import android.os.Message; * A good place might be the {@code onResume} method of the Activity. Stop the Handler accordingly in {@code onPause}. * </p> */ -public abstract class GeoDirHandler extends Handler implements IObserver<Object> { - - private static final int OBSERVABLE = 1 << 1; - private static final int START_GEO = 1 << 2; - private static final int START_DIR = 1 << 3; - private static final int STOP_GEO = 1 << 4; - private static final int STOP_DIR = 1 << 5; - +public abstract class GeoDirHandler { private static final CgeoApplication app = CgeoApplication.getInstance(); - @Override - final public void handleMessage(final Message message) { - if ((message.what & START_GEO) != 0) { - app.addGeoObserver(this); - } - - if ((message.what & START_DIR) != 0) { - app.addDirectionObserver(this); - } - - if ((message.what & STOP_GEO) != 0) { - app.deleteGeoObserver(this); - } - - if ((message.what & STOP_DIR) != 0) { - app.deleteDirectionObserver(this); - } - - if ((message.what & OBSERVABLE) != 0) { - if (message.obj instanceof IGeoData) { - updateGeoData((IGeoData) message.obj); - } else { - updateDirection((Float) message.obj); - } - } - } - - @Override - final public void update(final Object o) { - obtainMessage(OBSERVABLE, o).sendToTarget(); - } + private Subscription dirSubscription = null; + private Subscription geoSubscription = null; /** * Update method called when new IGeoData is available. @@ -69,7 +36,7 @@ public abstract class GeoDirHandler extends Handler implements IObserver<Object> * @param data * the new data */ - protected void updateGeoData(final IGeoData data) { + public void updateGeoData(final IGeoData data) { // Override this in children } @@ -79,24 +46,38 @@ public abstract class GeoDirHandler extends Handler implements IObserver<Object> * @param direction * the new direction */ - protected void updateDirection(final float direction) { + public void updateDirection(final float direction) { // Override this in children } /** * Register the current GeoDirHandler for GeoData information. */ - public void startGeo() { - sendEmptyMessage(START_GEO); + public synchronized void startGeo() { + geoSubscription = app.currentGeoObject() + .subscribeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1<IGeoData>() { + @Override + public void call(final IGeoData geoData) { + updateGeoData(geoData); + } + }); } /** * Register the current GeoDirHandler for direction information if the preferences * allow it. */ - public void startDir() { + public synchronized void startDir() { if (Settings.isUseCompass()) { - sendEmptyMessage(START_DIR); + dirSubscription = app.currentDirObject() + .subscribeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1<Float>() { + @Override + public void call(final Float direction) { + updateDirection(direction); + } + }); } } @@ -105,27 +86,43 @@ public abstract class GeoDirHandler extends Handler implements IObserver<Object> * preferences allow it). */ public void startGeoAndDir() { - sendEmptyMessage(START_GEO | (Settings.isUseCompass() ? START_DIR : 0)); + startGeo(); + startDir(); } /** * Unregister the current GeoDirHandler for GeoData information. */ - public void stopGeo() { - sendEmptyMessage(STOP_GEO); + public synchronized void stopGeo() { + // Delay the unsubscription by 2.5 seconds, so that another activity has + // the time to subscribe and the GPS receiver will not be turned down. + if (geoSubscription != null) { + final Subscription subscription = geoSubscription; + geoSubscription = null; + Observable.interval(2500, TimeUnit.MILLISECONDS).take(1).subscribe(new Action1<Long>() { + @Override + public void call(final Long aLong) { + subscription.unsubscribe(); + } + }); + } } /** * Unregister the current GeoDirHandler for direction information. */ - public void stopDir() { - sendEmptyMessage(STOP_DIR); + public synchronized void stopDir() { + if (dirSubscription != null) { + dirSubscription.unsubscribe(); + dirSubscription = null; + } } /** * Unregister the current GeoDirHandler for GeoData and direction information. */ public void stopGeoAndDir() { - sendEmptyMessage(STOP_GEO | STOP_DIR); + stopGeo(); + stopDir(); } } diff --git a/main/src/cgeo/geocaching/utils/IObserver.java b/main/src/cgeo/geocaching/utils/IObserver.java deleted file mode 100644 index bfcc798..0000000 --- a/main/src/cgeo/geocaching/utils/IObserver.java +++ /dev/null @@ -1,22 +0,0 @@ -package cgeo.geocaching.utils; - -/** - * Observer interface. - * <p/> - * An observer will receive updates about the observed object (implementing the {@link ISubject} interface) through its - * {@link #update(Object)} method. - * - * @param <T> - * the kind of data to observe - */ -public interface IObserver<T> { - - /** - * Called when an observed object has updated its data. - * - * @param data - * the updated data - */ - void update(final T data); - -} diff --git a/main/src/cgeo/geocaching/utils/ISubject.java b/main/src/cgeo/geocaching/utils/ISubject.java deleted file mode 100644 index c325db0..0000000 --- a/main/src/cgeo/geocaching/utils/ISubject.java +++ /dev/null @@ -1,57 +0,0 @@ -package cgeo.geocaching.utils; - -/** - * Interface for subjects objects. Those can be observed by objects implementing the {@link IObserver} interface. - * - * @param <T> - * the kind of data to observe - */ - -public interface ISubject<T> { - - /** - * Add an observer to the observers list. - * <p/> - * Observers will be notified with no particular order. - * - * @param observer - * the observer to add - * @return true if the observer has been added, false if it was present already - */ - public boolean addObserver(final IObserver<? super T> observer); - - /** - * Delete an observer from the observers list. - * - * @param observer - * the observer to remove - * @return true if the observer has been removed, false if it was not in the list of observers - */ - public boolean deleteObserver(final IObserver<? super T> observer); - - /** - * Number of observers currently observing the object. - * - * @return the number of observers - */ - public int sizeObservers(); - - /** - * Notify all the observers that new data is available. - * <p/> - * The {@link IObserver#update(Object)} method of each observer will be called with no particular order. - * - * @param data - * the updated data - * @return true if at least one observer was notified, false if there were no observers - */ - public boolean notifyObservers(final T data); - - /** - * Clear the observers list. - * - * @return true if there were observers before calling this method, false if the observers list was empty - */ - public boolean clearObservers(); - -} diff --git a/main/src/cgeo/geocaching/utils/ImageUtils.java b/main/src/cgeo/geocaching/utils/ImageUtils.java index 73f322c..9f47ead 100644 --- a/main/src/cgeo/geocaching/utils/ImageUtils.java +++ b/main/src/cgeo/geocaching/utils/ImageUtils.java @@ -78,7 +78,7 @@ public final class ImageUtils { * @return BitmapDrawable The scaled image */ @NonNull - public static BitmapDrawable scaleBitmapTo(@NonNull final Bitmap image, final int maxWidth, final int maxHeight) { + private static BitmapDrawable scaleBitmapTo(@NonNull final Bitmap image, final int maxWidth, final int maxHeight) { final CgeoApplication app = CgeoApplication.getInstance(); Bitmap result = image; int width = image.getWidth(); diff --git a/main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java b/main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java index 708dff0..259e94a 100644 --- a/main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java +++ b/main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java @@ -1,5 +1,7 @@ package cgeo.geocaching.utils; +import org.eclipse.jdt.annotation.NonNull; + import java.util.AbstractSet; import java.util.ArrayList; import java.util.Collection; @@ -42,6 +44,7 @@ public class LeastRecentlyUsedSet<E> extends AbstractSet<E> * * @see HashSet */ + @NonNull @Override public Iterator<E> iterator() { return map.keySet().iterator(); diff --git a/main/src/cgeo/geocaching/utils/LogTemplateProvider.java b/main/src/cgeo/geocaching/utils/LogTemplateProvider.java index 76fa0f7..5fa0982 100644 --- a/main/src/cgeo/geocaching/utils/LogTemplateProvider.java +++ b/main/src/cgeo/geocaching/utils/LogTemplateProvider.java @@ -47,10 +47,6 @@ public final class LogTemplateProvider { this.logEntry = logEntry; } - public LogContext(final boolean offline) { - this(null, null, offline); - } - public LogContext(final Geocache cache, LogEntry logEntry, final boolean offline) { this.cache = cache; this.offline = offline; diff --git a/main/src/cgeo/geocaching/utils/MemorySubject.java b/main/src/cgeo/geocaching/utils/MemorySubject.java deleted file mode 100644 index c424528..0000000 --- a/main/src/cgeo/geocaching/utils/MemorySubject.java +++ /dev/null @@ -1,45 +0,0 @@ -package cgeo.geocaching.utils; - -/** - * Synchronized implementation of the {@link ISubject} interface with an added pull interface. - * - * @param <T> - * the kind of data to observe - */ -public class MemorySubject<T> extends Subject<T> { - - /** - * The latest version of the observed data. - * <p/> - * A child class implementation may want to set this field from its constructors, in case early observers request - * the data before it got a chance to get updated. Otherwise, <code>null</code> will be returned until updated - * data is available. - */ - private T memory; - - @Override - public synchronized boolean addObserver(final IObserver<? super T> observer) { - final boolean added = super.addObserver(observer); - if (added && memory != null) { - observer.update(memory); - } - return added; - } - - @Override - public synchronized boolean notifyObservers(final T data) { - memory = data; - return super.notifyObservers(data); - } - - /** - * Get the memorized version of the data. - * - * @return the initial data set by the subject (which may be <code>null</code>), - * or the updated data if it is available - */ - public synchronized T getMemory() { - return memory; - } - -} diff --git a/main/src/cgeo/geocaching/utils/RunnableWithArgument.java b/main/src/cgeo/geocaching/utils/RunnableWithArgument.java deleted file mode 100644 index 6137efd..0000000 --- a/main/src/cgeo/geocaching/utils/RunnableWithArgument.java +++ /dev/null @@ -1,7 +0,0 @@ -package cgeo.geocaching.utils; - -public interface RunnableWithArgument<T> { - - abstract void run(final T argument); - -} diff --git a/main/src/cgeo/geocaching/utils/Subject.java b/main/src/cgeo/geocaching/utils/Subject.java deleted file mode 100644 index b1754cc..0000000 --- a/main/src/cgeo/geocaching/utils/Subject.java +++ /dev/null @@ -1,76 +0,0 @@ -package cgeo.geocaching.utils; - -import java.util.LinkedHashSet; -import java.util.Set; - -/** - * Synchronized implementation of the {@link ISubject} interface. - * - * @param <T> - * the kind of data to observe - */ -public class Subject<T> implements ISubject<T> { - - /** - * Collection of observers. - */ - protected final Set<IObserver<? super T>> observers = new LinkedHashSet<IObserver<? super T>>(); - - @Override - public synchronized boolean addObserver(final IObserver<? super T> observer) { - final boolean added = observers.add(observer); - if (added && observers.size() == 1) { - onFirstObserver(); - } - return added; - } - - @Override - public synchronized boolean deleteObserver(final IObserver<? super T> observer) { - final boolean removed = observers.remove(observer); - if (removed && observers.isEmpty()) { - onLastObserver(); - } - return removed; - } - - @Override - public synchronized boolean notifyObservers(final T arg) { - final boolean nonEmpty = !observers.isEmpty(); - for (final IObserver<? super T> observer : observers) { - observer.update(arg); - } - return nonEmpty; - } - - @Override - public synchronized int sizeObservers() { - return observers.size(); - } - - @Override - public synchronized boolean clearObservers() { - final boolean nonEmpty = !observers.isEmpty(); - for (final IObserver<? super T> observer : observers) { - deleteObserver(observer); - } - return nonEmpty; - } - - /** - * Method called when the collection of observers goes from empty to non-empty. - * <p/> - * The default implementation does nothing and may be overwritten by child classes. - */ - protected void onFirstObserver() { - } - - /** - * Method called when the collection of observers goes from non-empty to empty. - * <p/> - * The default implementation does nothing and may be overwritten by child classes. - */ - protected void onLastObserver() { - } - -} diff --git a/main/src/cgeo/geocaching/utils/TextUtils.java b/main/src/cgeo/geocaching/utils/TextUtils.java index efbb2d7..c4e1128 100644 --- a/main/src/cgeo/geocaching/utils/TextUtils.java +++ b/main/src/cgeo/geocaching/utils/TextUtils.java @@ -4,7 +4,6 @@ package cgeo.geocaching.utils; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; - import org.eclipse.jdt.annotation.Nullable; import java.util.regex.Matcher; |
