diff options
Diffstat (limited to 'main/src')
117 files changed, 4073 insertions, 2685 deletions
diff --git a/main/src/cgeo/calendar/CalendarAddon.java b/main/src/cgeo/calendar/CalendarAddon.java index 117fb9a..4a672fa 100644 --- a/main/src/cgeo/calendar/CalendarAddon.java +++ b/main/src/cgeo/calendar/CalendarAddon.java @@ -18,6 +18,11 @@ import android.net.Uri; import java.util.Date; public class CalendarAddon { + + private CalendarAddon() { + // utility class + } + public static boolean isAvailable() { return ProcessUtils.isIntentAvailable(ICalendar.INTENT, Uri.parse(ICalendar.URI_SCHEME + "://" + ICalendar.URI_HOST)); } diff --git a/main/src/cgeo/contacts/ContactsAddon.java b/main/src/cgeo/contacts/ContactsAddon.java index f2498ea..7165a77 100644 --- a/main/src/cgeo/contacts/ContactsAddon.java +++ b/main/src/cgeo/contacts/ContactsAddon.java @@ -8,6 +8,11 @@ import android.content.Intent; import android.net.Uri; public class ContactsAddon { + + private ContactsAddon() { + // utility class + } + public static void openContactCard(Activity context, String userName) { final Parameters params = new Parameters( IContacts.PARAM_NAME, userName diff --git a/main/src/cgeo/geocaching/AboutActivity.java b/main/src/cgeo/geocaching/AboutActivity.java index 6cda723..5bf0f06 100644 --- a/main/src/cgeo/geocaching/AboutActivity.java +++ b/main/src/cgeo/geocaching/AboutActivity.java @@ -14,6 +14,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; +import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Bundle; @@ -28,6 +29,8 @@ import java.util.Scanner; public class AboutActivity extends AbstractViewPagerActivity<AboutActivity.Page> { + private static final String EXTRA_ABOUT_STARTPAGE = "cgeo.geocaching.extra.about.startpage"; + class LicenseViewCreator extends AbstractCachingPageViewCreator<ScrollView> { @InjectView(R.id.license) protected TextView licenseLink; @@ -84,7 +87,6 @@ public class AboutActivity extends AbstractViewPagerActivity<AboutActivity.Page> @InjectView(R.id.website) protected TextView website; @InjectView(R.id.facebook) protected TextView facebook; @InjectView(R.id.twitter) protected TextView twitter; - @InjectView(R.id.nutshellmanual) protected TextView nutshellmanual; @InjectView(R.id.market) protected TextView market; @InjectView(R.id.faq) protected TextView faq; @@ -96,7 +98,6 @@ public class AboutActivity extends AbstractViewPagerActivity<AboutActivity.Page> setClickListener(website, "http://www.cgeo.org/"); setClickListener(facebook, "http://www.facebook.com/pages/cgeo/297269860090"); setClickListener(twitter, "http://twitter.com/android_gc"); - setClickListener(nutshellmanual, "http://manual.cgeo.org/"); setClickListener(faq, "http://faq.cgeo.org/"); market.setOnClickListener(new View.OnClickListener() { @@ -142,7 +143,13 @@ public class AboutActivity extends AbstractViewPagerActivity<AboutActivity.Page> @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState, R.layout.viewpager_activity); - createViewPager(0, null); + + int startPage = Page.VERSION.ordinal(); + Bundle extras = getIntent().getExtras(); + if (extras != null) { + startPage = extras.getInt(EXTRA_ABOUT_STARTPAGE, startPage); + } + createViewPager(startPage, null); reinitializeViewPager(); } @@ -210,4 +217,10 @@ public class AboutActivity extends AbstractViewPagerActivity<AboutActivity.Page> return result; } + public static void showChangeLog(Context fromActivity) { + final Intent intent = new Intent(fromActivity, AboutActivity.class); + intent.putExtra(EXTRA_ABOUT_STARTPAGE, Page.CHANGELOG.ordinal()); + fromActivity.startActivity(intent); + } + } diff --git a/main/src/cgeo/geocaching/AbstractDialogFragment.java b/main/src/cgeo/geocaching/AbstractDialogFragment.java new file mode 100644 index 0000000..84a20ea --- /dev/null +++ b/main/src/cgeo/geocaching/AbstractDialogFragment.java @@ -0,0 +1,358 @@ +package cgeo.geocaching; + +import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.activity.ActivityMixin; +import cgeo.geocaching.enumerations.CacheSize; +import cgeo.geocaching.enumerations.LoadFlags; +import cgeo.geocaching.gcvote.GCVote; +import cgeo.geocaching.gcvote.GCVoteRating; +import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.geopoint.Units; +import cgeo.geocaching.sensors.GeoDirHandler; +import cgeo.geocaching.sensors.IGeoData; +import cgeo.geocaching.settings.Settings; +import cgeo.geocaching.ui.CacheDetailsCreator; +import cgeo.geocaching.ui.LoggingUI; +import cgeo.geocaching.utils.Log; + +import rx.Observable; +import rx.Subscription; +import rx.android.observables.AndroidObservable; +import rx.functions.Action1; +import rx.functions.Func0; +import rx.schedulers.Schedulers; +import rx.subscriptions.Subscriptions; + +import android.annotation.TargetApi; +import android.content.DialogInterface; +import android.content.res.Resources; +import android.os.Build; +import android.os.Bundle; +import android.support.v4.app.DialogFragment; +import android.support.v7.widget.PopupMenu; +import android.view.ContextMenu; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; + +public abstract class AbstractDialogFragment extends DialogFragment implements CacheMenuHandler.ActivityInterface, PopupMenu.OnMenuItemClickListener, MenuItem.OnMenuItemClickListener { + protected CgeoApplication app = null; + protected Resources res = null; + protected String geocode; + protected CacheDetailsCreator details; + + private Subscription resumeSubscription = Subscriptions.empty(); + private TextView cacheDistance = null; + + + protected static final String GEOCODE_ARG= "GEOCODE"; + protected static final String WAYPOINT_ARG= "WAYPOINT"; + + protected Geocache cache; + + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + res = getResources(); + app = (CgeoApplication) getActivity().getApplication(); + setHasOptionsMenu(true); + } + + protected void initCustomActionBar(View v) + { + final ImageView defaultNavigationImageView = (ImageView) v.findViewById(R.id.defaultNavigation); + defaultNavigationImageView.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + startDefaultNavigation2(); + return true; + } + }); + defaultNavigationImageView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + navigateTo(); + } + }); + + final View overflowActionBar = v.findViewById(R.id.overflowActionBar); + overflowActionBar.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showPopup(v); + } + }); + /* Use a context menu instead popup where the popup menu is not working */ + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { + registerForContextMenu(overflowActionBar); + } + + } + + final public void setTitle(final CharSequence title) { + final TextView titleview = (TextView) getView().findViewById(R.id.actionbar_title); + if (titleview != null) { + titleview.setText(title); + + } + } + + @Override + public void onStart() { + super.onStart(); + geocode = getArguments().getString(GEOCODE_ARG); + } + + + protected void showPopup(View view) + { + // For reason I totally not understand the PopupMenu from Appcompat is broken beyond + // repair. Chicken out here and show the old menu on Gingerbread. + // The "correct" way of implementing this is stil in + // showPopupCompat(view) + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { + view.showContextMenu(); + } else { + showPopupHoneycomb(view); + } + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + private void showPopupHoneycomb(View view) { + android.widget.PopupMenu popupMenu = new android.widget.PopupMenu(getActivity(), view); + CacheMenuHandler.addMenuItems(new MenuInflater(getActivity()), popupMenu.getMenu(), cache); + popupMenu.setOnMenuItemClickListener( + new android.widget.PopupMenu.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + return AbstractDialogFragment.this.onMenuItemClick(item); + } + } + ); + popupMenu.show(); + } + + protected void showPopupCompat(View view) + { + PopupMenu popupMenu = new PopupMenu(getActivity(), view); + + // Directly instantiate SupportMenuInflater instead of getActivity().getMenuinflator + // getMenuinflator will throw a NPE since it tries to get the not displayed ActionBar + // menuinflator = getActivity().getMenuInflater(); + // MenuInflater menuinflator = new SupportMenuInflater(getActivity()); + CacheMenuHandler.addMenuItems(popupMenu.getMenuInflater(), popupMenu.getMenu(), cache); + popupMenu.setOnMenuItemClickListener(this); + popupMenu.show(); + } + + + protected void init() + { + cache = DataStore.loadCache(geocode, LoadFlags.LOAD_CACHE_OR_DB); + + if (cache == null) { + ((AbstractActivity) getActivity()).showToast(res.getString(R.string.err_detail_cache_find)); + + getActivity().finish(); + return; + } + + geocode = cache.getGeocode(); + } + + @Override + public void onResume() { + super.onResume(); + this.resumeSubscription = geoUpdate.start(GeoDirHandler.UPDATE_GEODATA); + init(); + } + + + @Override + public void onPause() { + resumeSubscription.unsubscribe(); + super.onPause(); + } + + + private void aquireGCVote() { + if (!Settings.isRatingWanted()) { + return; + } + if (!cache.supportsGCVote()) { + return; + } + AndroidObservable.bindActivity(getActivity(), Observable.defer(new Func0<Observable<GCVoteRating>>() { + @Override + public Observable<GCVoteRating> call() { + final GCVoteRating rating = GCVote.getRating(cache.getGuid(), geocode); + return rating != null ? Observable.just(rating) : Observable.<GCVoteRating>empty(); + } + })).subscribe(new Action1<GCVoteRating>() { + @Override + public void call(final GCVoteRating rating) { + cache.setRating(rating.getRating()); + cache.setVotes(rating.getVotes()); + DataStore.saveChangedCache(cache); + details.addRating(cache); + } + }, Schedulers.io()); + } + + protected final void addCacheDetails() { + assert cache != null; + // cache type + final String cacheType = cache.getType().getL10n(); + final String cacheSize = cache.getSize() != CacheSize.UNKNOWN ? " (" + cache.getSize().getL10n() + ")" : ""; + details.add(R.string.cache_type, cacheType + cacheSize); + + details.add(R.string.cache_geocode, cache.getGeocode()); + details.addCacheState(cache); + + details.addDistance(cache, cacheDistance); + cacheDistance = details.getValueView(); + + details.addDifficulty(cache); + details.addTerrain(cache); + details.addEventDate(cache); + + // rating + if (cache.getRating() > 0) { + details.addRating(cache); + } else { + aquireGCVote(); + } + + // favorite count + details.add(R.string.cache_favorite, cache.getFavoritePoints() + "×"); + + // more details + final Button buttonMore = (Button) getView().findViewById(R.id.more_details); + + buttonMore.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View arg0) { + CacheDetailActivity.startActivity(getActivity(), geocode); + getActivity().finish(); + } + }); + + /* Only working combination as it seems */ + registerForContextMenu(buttonMore); + } + + public final void showToast(String text) { + ActivityMixin.showToast(getActivity(), text); + } + + private final GeoDirHandler geoUpdate = new GeoDirHandler() { + + @Override + 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()))); + cacheDistance.bringToFront(); + } + onUpdateGeoData(geo); + } catch (final RuntimeException e) { + Log.w("Failed to UpdateLocation location."); + } + } + }; + + /** + * @param geo + * location + */ + protected void onUpdateGeoData(final IGeoData geo) { + // do nothing by default + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + CacheMenuHandler.addMenuItems(inflater, menu, cache); + + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + CacheMenuHandler.addMenuItems(new MenuInflater(getActivity()), menu, cache); + for (int i=0;i<menu.size();i++) { + MenuItem m = menu.getItem(i); + m.setOnMenuItemClickListener(this); + } + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + return onOptionsItemSelected(item); + } + + + @Override + public boolean onMenuItemClick(MenuItem menuItem) { + return onOptionsItemSelected(menuItem); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (CacheMenuHandler.onMenuItemSelected(item, this, cache)) { + return true; + } + if (LoggingUI.onMenuItemSelected(item, getActivity(), cache)) { + return true; + } + + return super.onOptionsItemSelected(item); + } + + @Override + public void onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + + try { + CacheMenuHandler.onPrepareOptionsMenu(menu, cache); + LoggingUI.onPrepareOptionsMenu(menu, cache); + } catch (final RuntimeException e) { + // nothing + } + } + + + protected abstract Geopoint getCoordinates(); + + protected abstract void startDefaultNavigation2(); + + + @Override + public void cachesAround() { + final Geopoint coords = getCoordinates(); + if (coords == null) { + showToast(res.getString(R.string.err_location_unknown)); + return; + } + CacheListActivity.startActivityCoordinates((AbstractActivity) getActivity(), coords); + getActivity().finish(); + } + + @Override + public void onCancel(DialogInterface dialog) { + super.onCancel(dialog); + getActivity().finish(); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + } +} diff --git a/main/src/cgeo/geocaching/AbstractLoggingActivity.java b/main/src/cgeo/geocaching/AbstractLoggingActivity.java index c3ba7d2..bca5db1 100644 --- a/main/src/cgeo/geocaching/AbstractLoggingActivity.java +++ b/main/src/cgeo/geocaching/AbstractLoggingActivity.java @@ -1,32 +1,29 @@ package cgeo.geocaching; -import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.activity.AbstractActionBarActivity; import cgeo.geocaching.activity.ActivityMixin; import cgeo.geocaching.connector.ConnectorFactory; import cgeo.geocaching.connector.gc.GCConnector; import cgeo.geocaching.connector.gc.GCSmiliesProvider; import cgeo.geocaching.connector.gc.GCSmiliesProvider.Smiley; import cgeo.geocaching.connector.trackable.TravelBugConnector; -import cgeo.geocaching.settings.Settings; import cgeo.geocaching.utils.LogTemplateProvider; import cgeo.geocaching.utils.LogTemplateProvider.LogContext; import cgeo.geocaching.utils.LogTemplateProvider.LogTemplate; -import org.apache.commons.lang3.StringUtils; - import android.view.Menu; import android.view.MenuItem; import android.view.SubMenu; import android.widget.EditText; -public abstract class AbstractLoggingActivity extends AbstractActivity { +public abstract class AbstractLoggingActivity extends AbstractActionBarActivity { @Override public boolean onCreateOptionsMenu(final Menu menu) { getMenuInflater().inflate(R.menu.abstract_logging_activity, menu); final SubMenu menuLog = menu.findItem(R.id.menu_templates).getSubMenu(); - for (final LogTemplate template : LogTemplateProvider.getTemplates()) { + for (final LogTemplate template : LogTemplateProvider.getTemplatesWithSignature()) { menuLog.add(0, template.getItemId(), 0, template.getResourceId()); } @@ -39,10 +36,7 @@ public abstract class AbstractLoggingActivity extends AbstractActivity { } @Override - public boolean onPrepareOptionsMenu(Menu menu) { - final boolean signatureAvailable = StringUtils.isNotBlank(Settings.getSignature()); - menu.findItem(R.id.menu_signature).setVisible(signatureAvailable); - + public boolean onPrepareOptionsMenu(final Menu menu) { boolean smileyVisible = false; final Geocache cache = getLogContext().getCache(); if (cache != null && ConnectorFactory.getConnector(cache).equals(GCConnector.getInstance())) { @@ -59,14 +53,9 @@ public abstract class AbstractLoggingActivity extends AbstractActivity { } @Override - public boolean onOptionsItemSelected(MenuItem item) { + public boolean onOptionsItemSelected(final MenuItem item) { final int id = item.getItemId(); - if (id == R.id.menu_signature) { - insertIntoLog(LogTemplateProvider.applyTemplates(Settings.getSignature(), getLogContext()), true); - return true; - } - final LogTemplate template = LogTemplateProvider.getTemplate(id); if (template != null) { insertIntoLog(template.getValue(getLogContext()), true); @@ -79,12 +68,12 @@ public abstract class AbstractLoggingActivity extends AbstractActivity { return true; } - return false; + return super.onOptionsItemSelected(item); } protected abstract LogContext getLogContext(); - protected void insertIntoLog(String newText, final boolean moveCursor) { + protected void insertIntoLog(final String newText, final boolean moveCursor) { final EditText log = (EditText) findViewById(R.id.log); ActivityMixin.insertAtPosition(log, newText, moveCursor); } diff --git a/main/src/cgeo/geocaching/AbstractPopupActivity.java b/main/src/cgeo/geocaching/AbstractPopupActivity.java deleted file mode 100644 index 88cad01..0000000 --- a/main/src/cgeo/geocaching/AbstractPopupActivity.java +++ /dev/null @@ -1,260 +0,0 @@ -package cgeo.geocaching; - -import cgeo.geocaching.activity.AbstractActivity; -import cgeo.geocaching.activity.ActivityMixin; -import cgeo.geocaching.enumerations.CacheSize; -import cgeo.geocaching.enumerations.LoadFlags; -import cgeo.geocaching.gcvote.GCVote; -import cgeo.geocaching.gcvote.GCVoteRating; -import cgeo.geocaching.geopoint.Geopoint; -import cgeo.geocaching.geopoint.Units; -import cgeo.geocaching.sensors.GeoDirHandler; -import cgeo.geocaching.sensors.IGeoData; -import cgeo.geocaching.settings.Settings; -import cgeo.geocaching.ui.CacheDetailsCreator; -import cgeo.geocaching.ui.LoggingUI; -import cgeo.geocaching.utils.Log; - -import org.apache.commons.lang3.StringUtils; - -import rx.Observable; -import rx.android.observables.AndroidObservable; -import rx.functions.Action1; -import rx.functions.Func0; -import rx.schedulers.Schedulers; - -import android.graphics.Rect; -import android.os.Bundle; -import android.view.Menu; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.View.OnLongClickListener; -import android.widget.Button; -import android.widget.ImageView; -import android.widget.TextView; - -public abstract class AbstractPopupActivity extends AbstractActivity implements CacheMenuHandler.ActivityInterface { - - protected Geocache cache = null; - protected String geocode = null; - protected CacheDetailsCreator details; - - private TextView cacheDistance = null; - private final int layout; - - private final GeoDirHandler geoUpdate = new GeoDirHandler() { - - @Override - 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()))); - cacheDistance.bringToFront(); - } - onUpdateGeoData(geo); - } catch (final RuntimeException e) { - Log.w("Failed to UpdateLocation location."); - } - } - }; - - /** - * Callback to run when new location information is available. - * This may be overridden by deriving classes. The default implementation does nothing. - * - * @param geo - * the new data - */ - public void onUpdateGeoData(final IGeoData geo) { - } - - protected AbstractPopupActivity(int layout) { - this.layout = layout; - } - - private void aquireGCVote() { - if (!Settings.isRatingWanted()) { - return; - } - if (!cache.supportsGCVote()) { - return; - } - AndroidObservable.bindActivity(this, Observable.defer(new Func0<Observable<GCVoteRating>>() { - @Override - public Observable<GCVoteRating> call() { - final GCVoteRating rating = GCVote.getRating(cache.getGuid(), geocode); - return rating != null ? Observable.just(rating) : Observable.<GCVoteRating>empty(); - } - })).subscribe(new Action1<GCVoteRating>() { - @Override - public void call(final GCVoteRating rating) { - cache.setRating(rating.getRating()); - cache.setVotes(rating.getVotes()); - DataStore.saveChangedCache(cache); - details.addRating(cache); - } - }, Schedulers.io()); - } - - protected void init() { - cache = DataStore.loadCache(geocode, LoadFlags.LOAD_CACHE_OR_DB); - - if (cache == null) { - showToast(res.getString(R.string.err_detail_cache_find)); - - finish(); - return; - } - - geocode = cache.getGeocode(); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - // set theme - this.setTheme(ActivityMixin.getDialogTheme()); - // set layout - setContentView(layout); - - // get parameters - final Bundle extras = getIntent().getExtras(); - if (extras != null) { - geocode = extras.getString(Intents.EXTRA_GEOCODE); - } - - if (StringUtils.isBlank(geocode)) { - showToast(res.getString(R.string.err_detail_cache_find)); - - finish(); - return; - } - - final ImageView defaultNavigationImageView = (ImageView) findViewById(R.id.defaultNavigation); - defaultNavigationImageView.setOnLongClickListener(new OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - startDefaultNavigation2(); - return true; - } - }); - - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - CacheMenuHandler.addMenuItems(this, menu, cache); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (CacheMenuHandler.onMenuItemSelected(item, this, cache)) { - return true; - } - if (LoggingUI.onMenuItemSelected(item, this, cache)) { - return true; - } - - return true; - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - super.onPrepareOptionsMenu(menu); - - try { - CacheMenuHandler.onPrepareOptionsMenu(menu, cache); - LoggingUI.onPrepareOptionsMenu(menu, cache); - } catch (final RuntimeException e) { - // nothing - } - - return true; - } - - protected abstract Geopoint getCoordinates(); - - @Override - public void onResume() { - super.onResume(geoUpdate.start(GeoDirHandler.UPDATE_GEODATA)); - init(); - } - - @Override - public boolean onTouchEvent(final MotionEvent event) { - if (event.getAction() == MotionEvent.ACTION_UP) { - final Rect r = new Rect(0, 0, 0, 0); - getWindow().getDecorView().getHitRect(r); - if (!r.contains((int) event.getX(), (int) event.getY())) { - finish(); - return true; - } - } - return super.onTouchEvent(event); - } - - protected abstract void startDefaultNavigation2(); - - protected final void addCacheDetails() { - assert cache != null; - // cache type - final String cacheType = cache.getType().getL10n(); - final String cacheSize = cache.getSize() != CacheSize.UNKNOWN ? " (" + cache.getSize().getL10n() + ")" : ""; - details.add(R.string.cache_type, cacheType + cacheSize); - - details.add(R.string.cache_geocode, cache.getGeocode()); - details.addCacheState(cache); - - details.addDistance(cache, cacheDistance); - cacheDistance = details.getValueView(); - - details.addDifficulty(cache); - details.addTerrain(cache); - details.addEventDate(cache); - - // rating - if (cache.getRating() > 0) { - details.addRating(cache); - } else { - aquireGCVote(); - } - - // favorite count - details.add(R.string.cache_favorite, cache.getFavoritePoints() + "×"); - - // more details - final Button buttonMore = (Button) findViewById(R.id.more_details); - buttonMore.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View arg0) { - CacheDetailActivity.startActivity(AbstractPopupActivity.this, geocode); - finish(); - } - }); - } - - @Override - public void cachesAround() { - final Geopoint coords = getCoordinates(); - if (coords == null) { - showToast(res.getString(R.string.err_location_unknown)); - return; - } - CacheListActivity.startActivityCoordinates(this, coords); - finish(); - } - - /** - * @param view - * unused here but needed since this method is referenced from XML layout - */ - public final void goDefaultNavigation(View view) { - navigateTo(); - finish(); - } - -} diff --git a/main/src/cgeo/geocaching/CacheDetailActivity.java b/main/src/cgeo/geocaching/CacheDetailActivity.java index a66d181..76411a2 100644 --- a/main/src/cgeo/geocaching/CacheDetailActivity.java +++ b/main/src/cgeo/geocaching/CacheDetailActivity.java @@ -46,6 +46,7 @@ import cgeo.geocaching.utils.CryptUtils; import cgeo.geocaching.utils.ImageUtils; import cgeo.geocaching.utils.Log; import cgeo.geocaching.utils.MatcherWrapper; +import cgeo.geocaching.utils.RxUtils; import cgeo.geocaching.utils.SimpleCancellableHandler; import cgeo.geocaching.utils.SimpleHandler; import cgeo.geocaching.utils.TextUtils; @@ -60,9 +61,9 @@ import org.apache.commons.lang3.tuple.Pair; import rx.Observable; import rx.Observable.OnSubscribe; import rx.Observer; -import rx.Scheduler.Inner; import rx.Subscriber; import rx.android.observables.AndroidObservable; +import rx.functions.Action0; import rx.functions.Action1; import rx.schedulers.Schedulers; import rx.subscriptions.CompositeSubscription; @@ -84,6 +85,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.support.v4.app.FragmentManager; +import android.support.v7.view.ActionMode; import android.text.Editable; import android.text.Html; import android.text.Spannable; @@ -165,7 +167,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc private Waypoint selectedWaypoint; @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState, R.layout.cachedetail_activity); createSubscriptions = new CompositeSubscription(); @@ -272,22 +274,13 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc // nothing, we lost the window } - final ImageView defaultNavigationImageView = (ImageView) findViewById(R.id.defaultNavigation); - defaultNavigationImageView.setOnLongClickListener(new OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - startDefaultNavigation2(); - return true; - } - }); - final int pageToOpen = savedInstanceState != null ? savedInstanceState.getInt(STATE_PAGE_INDEX, 0) : Settings.isOpenLastDetailsPage() ? Settings.getLastDetailsPage() : 1; createViewPager(pageToOpen, new OnPageSelectedListener() { @Override - public void onPageSelected(int position) { + public void onPageSelected(final int position) { if (Settings.isOpenLastDetailsPage()) { Settings.setLastDetailsPage(position); } @@ -300,9 +293,9 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc final String realGeocode = geocode; final String realGuid = guid; - Schedulers.io().schedule(new Action1<Inner>() { + Schedulers.io().createWorker().schedule(new Action0() { @Override - public void call(final Inner inner) { + public void call() { search = Geocache.searchByGeocode(realGeocode, StringUtils.isBlank(realGeocode) ? realGuid : null, 0, false, loadCacheHandler); loadCacheHandler.sendMessage(Message.obtain()); } @@ -342,53 +335,10 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } @Override - public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo info) { + public void onCreateContextMenu(final ContextMenu menu, final View view, final ContextMenu.ContextMenuInfo info) { super.onCreateContextMenu(menu, view, info); final int viewId = view.getId(); switch (viewId) { - case R.id.value: // coordinates, gc-code, name - assert view instanceof TextView; - clickedItemText = ((TextView) view).getText(); - final CharSequence itemTitle = ((TextView) ((View) view.getParent()).findViewById(R.id.name)).getText(); - buildDetailsContextMenu(menu, clickedItemText, itemTitle, true); - break; - case R.id.shortdesc: - assert view instanceof TextView; - clickedItemText = ((TextView) view).getText(); - buildDetailsContextMenu(menu, clickedItemText, res.getString(R.string.cache_description), false); - break; - case R.id.longdesc: - assert view instanceof TextView; - // combine short and long description - final String shortDesc = cache.getShortDescription(); - if (StringUtils.isBlank(shortDesc)) { - clickedItemText = ((TextView) view).getText(); - } else { - clickedItemText = shortDesc + "\n\n" + ((TextView) view).getText(); - } - buildDetailsContextMenu(menu, clickedItemText, res.getString(R.string.cache_description), false); - break; - case R.id.personalnote: - assert view instanceof TextView; - clickedItemText = ((TextView) view).getText(); - buildDetailsContextMenu(menu, clickedItemText, res.getString(R.string.cache_personal_note), true); - break; - case R.id.hint: - assert view instanceof TextView; - clickedItemText = ((TextView) view).getText(); - buildDetailsContextMenu(menu, clickedItemText, res.getString(R.string.cache_hint), false); - break; - case R.id.log: - assert view instanceof TextView; - clickedItemText = ((TextView) view).getText(); - buildDetailsContextMenu(menu, clickedItemText, res.getString(R.string.cache_logs), false); - break; - case R.id.date: // event date - assert view instanceof TextView; - clickedItemText = ((TextView) view).getText(); - buildDetailsContextMenu(menu, clickedItemText, 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); @@ -414,25 +364,25 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } @Override - public boolean onContextItemSelected(MenuItem item) { - if (onClipboardItemSelected(item, clickedItemText)) { - return true; - } + public boolean onContextItemSelected(final MenuItem item) { switch (item.getItemId()) { // waypoints case R.id.menu_waypoint_edit: if (selectedWaypoint != null) { + ensureSaved(); EditWaypointActivity.startActivityEditWaypoint(this, cache, selectedWaypoint.getId()); refreshOnResume = true; } return true; case R.id.menu_waypoint_duplicate: + ensureSaved(); if (cache.duplicateWaypoint(selectedWaypoint)) { DataStore.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); notifyDataSetChanged(); } return true; case R.id.menu_waypoint_delete: + ensureSaved(); if (cache.deleteWaypoint(selectedWaypoint)) { DataStore.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); notifyDataSetChanged(); @@ -454,10 +404,10 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } return true; case R.id.menu_waypoint_reset_cache_coords: + ensureSaved(); if (ConnectorFactory.getConnector(cache).supportsOwnCoordinates()) { createResetCacheCoordinatesDialog(cache, selectedWaypoint).show(); - } - else { + } else { final ProgressDialog progressDialog = ProgressDialog.show(this, getString(R.string.cache), getString(R.string.waypoint_reset), true); final HandlerResetCoordinates handler = new HandlerResetCoordinates(this, progressDialog, false); new ResetCoordsThread(cache, handler, selectedWaypoint, true, false, progressDialog).start(); @@ -476,20 +426,23 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } @Override - public boolean onCreateOptionsMenu(Menu menu) { + public boolean onCreateOptionsMenu(final Menu menu) { CacheMenuHandler.addMenuItems(this, menu, cache); return true; } @Override - public boolean onPrepareOptionsMenu(Menu menu) { + public boolean onPrepareOptionsMenu(final Menu menu) { CacheMenuHandler.onPrepareOptionsMenu(menu, cache); LoggingUI.onPrepareOptionsMenu(menu, cache); + menu.findItem(R.id.menu_store).setVisible(cache != null && !cache.isOffline()); + menu.findItem(R.id.menu_delete).setVisible(cache != null && cache.isOffline()); + menu.findItem(R.id.menu_refresh).setVisible(cache != null && cache.isOffline()); return super.onPrepareOptionsMenu(menu); } @Override - public boolean onOptionsItemSelected(MenuItem item) { + public boolean onOptionsItemSelected(final MenuItem item) { if (CacheMenuHandler.onMenuItemSelected(item, this, cache)) { return true; } @@ -497,9 +450,15 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc final int menuItem = item.getItemId(); switch (menuItem) { - case 0: - // no menu selected, but a new sub menu shown - return false; + case R.id.menu_delete: + dropCache(); + return true; + case R.id.menu_store: + storeCache(); + return true; + case R.id.menu_refresh: + refreshCache(); + return true; default: if (NavigationAppFactory.onMenuItemSelected(item, this, cache)) { return true; @@ -510,7 +469,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } } - return true; + return super.onOptionsItemSelected(item); } private static final class CacheDetailsGeoDirHandler extends GeoDirHandler { @@ -539,7 +498,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc private final static class LoadCacheHandler extends SimpleCancellableHandler { - public LoadCacheHandler(CacheDetailActivity activity, Progress progress) { + public LoadCacheHandler(final CacheDetailActivity activity, final Progress progress) { super(activity, progress); } @@ -574,7 +533,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } private void updateStatusMsg(final String msg) { - CacheDetailActivity activity = ((CacheDetailActivity) activityRef.get()); + final CacheDetailActivity activity = ((CacheDetailActivity) activityRef.get()); if (activity == null) { return; } @@ -613,7 +572,18 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } else { setTitle(cache.getGeocode()); } - ((TextView) findViewById(R.id.actionbar_title)).setCompoundDrawablesWithIntrinsicBounds(getResources().getDrawable(cache.getType().markerId), null, null, null); + + getSupportActionBar().setIcon(getResources().getDrawable(cache.getType().markerId)); + + // if we have a newer Android device setup Android Beam for easy cache sharing + initializeAndroidBeam( + new ActivitySharingInterface() { + @Override + public String getUri() { + return cache.getCgeoUrl(); + } + } + ); // reset imagesList so Images view page will be redrawn imagesList = null; @@ -622,8 +592,11 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc // rendering done! remove progress popup if any there invalidateOptionsMenuCompatible(); progress.dismiss(); + + Settings.addCacheToHistory(cache.getGeocode()); } + /** * Tries to navigate to the {@link Geocache} of this activity. */ @@ -632,23 +605,16 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } /** - * Tries to navigate to the {@link Geocache} of this activity. - */ - private void startDefaultNavigation2() { - NavigationAppFactory.startDefaultNavigationApplication(2, this, cache); - } - - /** * Wrapper for the referenced method in the xml-layout. */ - public void goDefaultNavigation(@SuppressWarnings("unused") View view) { + public void goDefaultNavigation(@SuppressWarnings("unused") final View view) { startDefaultNavigation(); } /** * referenced from XML view */ - public void showNavigationMenu(@SuppressWarnings("unused") View view) { + public void showNavigationMenu(@SuppressWarnings("unused") final View view) { NavigationAppFactory.showNavigationMenu(this, cache, null, null, true, true); } @@ -681,7 +647,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc DETAILS(R.string.detail), DESCRIPTION(R.string.cache_description), LOGS(R.string.cache_logs), - LOGSFRIENDS(R.string.cache_logsfriends), + LOGSFRIENDS(R.string.cache_logs_friends_and_own), WAYPOINTS(R.string.cache_waypoints), INVENTORY(R.string.cache_inventory), IMAGES(R.string.cache_images); @@ -723,7 +689,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc attributeBox.setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View v) { + public void onClick(final View v) { // toggle between attribute icons and descriptions toggleAttributeDisplay(attributeBox, attributeBoxMaxWidth); } @@ -748,7 +714,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc * lazy-creates the layout holding the icons of the caches attributes * and makes it visible */ - private void showAttributeIcons(LinearLayout attribBox, int parentWidth) { + private void showAttributeIcons(final LinearLayout attribBox, final int parentWidth) { if (attributeIconsLayout == null) { attributeIconsLayout = createAttributeIconsLayout(parentWidth); // no matching icons found? show text @@ -766,7 +732,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc * lazy-creates the layout holding the descriptions of the caches attributes * and makes it visible */ - private void showAttributeDescriptions(LinearLayout attribBox) { + private void showAttributeDescriptions(final LinearLayout attribBox) { if (attributeDescriptionsLayout == null) { attributeDescriptionsLayout = createAttributeDescriptionsLayout(); } @@ -778,7 +744,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc /** * toggle attribute descriptions and icons */ - private void toggleAttributeDisplay(LinearLayout attribBox, int parentWidth) { + private void toggleAttributeDisplay(final LinearLayout attribBox, final int parentWidth) { // Don't toggle when there are no icons to show. if (noAttributeIconsFound) { return; @@ -792,7 +758,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } } - private ViewGroup createAttributeIconsLayout(int parentWidth) { + private ViewGroup createAttributeIconsLayout(final int parentWidth) { final LinearLayout rows = new LinearLayout(CacheDetailActivity.this); rows.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); rows.setOrientation(LinearLayout.VERTICAL); @@ -857,7 +823,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc for (String attributeName : cache.getAttributes()) { final boolean enabled = CacheAttribute.isEnabled(attributeName); // search for a translation of the attribute - CacheAttribute attrib = CacheAttribute.getByRawName(CacheAttribute.trimAttributeName(attributeName)); + final CacheAttribute attrib = CacheAttribute.getByRawName(CacheAttribute.trimAttributeName(attributeName)); if (attrib != null) { attributeName = attrib.getL10n(enabled); } @@ -873,6 +839,54 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } } + private void refreshCache() { + if (progress.isShowing()) { + showToast(res.getString(R.string.err_detail_still_working)); + return; + } + + if (!Network.isNetworkConnected(getApplicationContext())) { + showToast(getString(R.string.err_server)); + return; + } + + final RefreshCacheHandler refreshCacheHandler = new RefreshCacheHandler(CacheDetailActivity.this, progress); + + progress.show(CacheDetailActivity.this, res.getString(R.string.cache_dialog_refresh_title), res.getString(R.string.cache_dialog_refresh_message), true, refreshCacheHandler.cancelMessage()); + + cache.refresh(refreshCacheHandler, Schedulers.io()); + } + + private void dropCache() { + if (progress.isShowing()) { + showToast(res.getString(R.string.err_detail_still_working)); + return; + } + + progress.show(CacheDetailActivity.this, res.getString(R.string.cache_dialog_offline_drop_title), res.getString(R.string.cache_dialog_offline_drop_message), true, null); + cache.drop(new ChangeNotificationHandler(CacheDetailActivity.this, progress), Schedulers.io()); + } + + private void storeCache() { + if (progress.isShowing()) { + showToast(res.getString(R.string.err_detail_still_working)); + return; + } + + if (Settings.getChooseList()) { + // let user select list to store cache in + new StoredList.UserInterface(CacheDetailActivity.this).promptForListSelection(R.string.list_title, + new Action1<Integer>() { + @Override + public void call(final Integer selectedListId) { + storeCache(selectedListId, new StoreCacheHandler(CacheDetailActivity.this, progress)); + } + }, true, StoredList.TEMPORARY_LIST_ID); + } else { + storeCache(StoredList.TEMPORARY_LIST_ID, new StoreCacheHandler(CacheDetailActivity.this, progress)); + } + } + /** * Creator for details-view. */ @@ -918,10 +932,10 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc span.setSpan(new ForegroundColorSpan(res.getColor(R.color.archived_cache_color)), 0, span.toString().length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } - registerForContextMenu(details.add(R.string.cache_name, span)); + addContextMenu(details.add(R.string.cache_name, span)); details.add(R.string.cache_type, cache.getType().getL10n()); details.addSize(cache); - registerForContextMenu(details.add(R.string.cache_geocode, cache.getGeocode())); + addContextMenu(details.add(R.string.cache_geocode, cache.getGeocode())); details.addCacheState(cache); details.addDistance(cache, cacheDistanceView); @@ -955,7 +969,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc // hidden or event date final TextView hiddenView = details.addHiddenDate(cache); if (hiddenView != null) { - registerForContextMenu(hiddenView); + addContextMenu(hiddenView); } // cache location @@ -967,7 +981,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc if (cache.getCoords() != null) { final TextView valueView = details.add(R.string.cache_coordinates, cache.getCoords().toString()); valueView.setOnClickListener(new CoordinatesFormatSwitcher(cache.getCoords())); - registerForContextMenu(valueView); + addContextMenu(valueView); } // cache attributes @@ -1015,59 +1029,23 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc private class StoreCacheClickListener implements View.OnClickListener { @Override - public void onClick(View arg0) { - if (progress.isShowing()) { - showToast(res.getString(R.string.err_detail_still_working)); - return; - } - - if (Settings.getChooseList()) { - // let user select list to store cache in - new StoredList.UserInterface(CacheDetailActivity.this).promptForListSelection(R.string.list_title, - new Action1<Integer>() { - @Override - public void call(final Integer selectedListId) { - storeCache(selectedListId, new StoreCacheHandler(CacheDetailActivity.this, progress)); - } - }, true, StoredList.TEMPORARY_LIST_ID); - } else { - storeCache(StoredList.TEMPORARY_LIST_ID, new StoreCacheHandler(CacheDetailActivity.this, progress)); - } + public void onClick(final View arg0) { + storeCache(); } } private class RefreshCacheClickListener implements View.OnClickListener { @Override - public void onClick(View arg0) { - if (progress.isShowing()) { - showToast(res.getString(R.string.err_detail_still_working)); - return; - } - - if (!Network.isNetworkConnected(getApplicationContext())) { - showToast(getString(R.string.err_server)); - return; - } - - final RefreshCacheHandler refreshCacheHandler = new RefreshCacheHandler(CacheDetailActivity.this, progress); - - progress.show(CacheDetailActivity.this, res.getString(R.string.cache_dialog_refresh_title), res.getString(R.string.cache_dialog_refresh_message), true, refreshCacheHandler.cancelMessage()); - - cache.refresh(cache.getListId(), refreshCacheHandler, Schedulers.io()); + public void onClick(final View arg0) { + refreshCache(); } } private class DropCacheClickListener implements View.OnClickListener { @Override - public void onClick(View arg0) { - if (progress.isShowing()) { - showToast(res.getString(R.string.err_detail_still_working)); - return; - } - - progress.show(CacheDetailActivity.this, res.getString(R.string.cache_dialog_offline_drop_title), res.getString(R.string.cache_dialog_offline_drop_message), true, null); - cache.drop(new ChangeNotificationHandler(CacheDetailActivity.this, progress), Schedulers.io()); + public void onClick(final View arg0) { + dropCache(); } } @@ -1075,7 +1053,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc * Abstract Listener for add / remove buttons for watchlist */ private abstract class AbstractWatchlistClickListener implements View.OnClickListener { - public void doExecute(int titleId, int messageId, Thread thread) { + public void doExecute(final int titleId, final int messageId, final Thread thread) { if (progress.isShowing()) { showToast(res.getString(R.string.err_watchlist_still_managing)); return; @@ -1096,7 +1074,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc */ private class AddToWatchlistClickListener extends AbstractWatchlistClickListener { @Override - public void onClick(View arg0) { + public void onClick(final View arg0) { doExecute(R.string.cache_dialog_watchlist_add_title, R.string.cache_dialog_watchlist_add_message, new WatchlistAddThread(new SimpleUpdateHandler(CacheDetailActivity.this, progress))); @@ -1108,7 +1086,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc */ private class RemoveFromWatchlistClickListener extends AbstractWatchlistClickListener { @Override - public void onClick(View arg0) { + public void onClick(final View arg0) { doExecute(R.string.cache_dialog_watchlist_remove_title, R.string.cache_dialog_watchlist_remove_message, new WatchlistRemoveThread(new SimpleUpdateHandler(CacheDetailActivity.this, progress))); @@ -1119,7 +1097,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc private class WatchlistAddThread extends Thread { private final Handler handler; - public WatchlistAddThread(Handler handler) { + public WatchlistAddThread(final Handler handler) { this.handler = handler; } @@ -1131,7 +1109,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc msg = Message.obtain(handler, MESSAGE_SUCCEEDED); } else { msg = Message.obtain(handler, MESSAGE_FAILED); - Bundle bundle = new Bundle(); + final Bundle bundle = new Bundle(); bundle.putString(SimpleCancellableHandler.MESSAGE_TEXT, res.getString(R.string.err_watchlist_failed)); msg.setData(bundle); } @@ -1143,7 +1121,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc private class WatchlistRemoveThread extends Thread { private final Handler handler; - public WatchlistRemoveThread(Handler handler) { + public WatchlistRemoveThread(final Handler handler) { this.handler = handler; } @@ -1155,7 +1133,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc msg = Message.obtain(handler, MESSAGE_SUCCEEDED); } else { msg = Message.obtain(handler, MESSAGE_FAILED); - Bundle bundle = new Bundle(); + final Bundle bundle = new Bundle(); bundle.putString(SimpleCancellableHandler.MESSAGE_TEXT, res.getString(R.string.err_watchlist_failed)); msg.setData(bundle); } @@ -1167,7 +1145,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc private class FavoriteAddThread extends Thread { private final Handler handler; - public FavoriteAddThread(Handler handler) { + public FavoriteAddThread(final Handler handler) { this.handler = handler; } @@ -1179,7 +1157,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc msg = Message.obtain(handler, MESSAGE_SUCCEEDED); } else { msg = Message.obtain(handler, MESSAGE_FAILED); - Bundle bundle = new Bundle(); + final Bundle bundle = new Bundle(); bundle.putString(SimpleCancellableHandler.MESSAGE_TEXT, res.getString(R.string.err_favorite_failed)); msg.setData(bundle); } @@ -1191,7 +1169,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc private class FavoriteRemoveThread extends Thread { private final Handler handler; - public FavoriteRemoveThread(Handler handler) { + public FavoriteRemoveThread(final Handler handler) { this.handler = handler; } @@ -1203,7 +1181,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc msg = Message.obtain(handler, MESSAGE_SUCCEEDED); } else { msg = Message.obtain(handler, MESSAGE_FAILED); - Bundle bundle = new Bundle(); + final Bundle bundle = new Bundle(); bundle.putString(SimpleCancellableHandler.MESSAGE_TEXT, res.getString(R.string.err_favorite_failed)); msg.setData(bundle); } @@ -1216,7 +1194,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc */ private class FavoriteAddClickListener extends AbstractWatchlistClickListener { @Override - public void onClick(View arg0) { + public void onClick(final View arg0) { doExecute(R.string.cache_dialog_favorite_add_title, R.string.cache_dialog_favorite_add_message, new FavoriteAddThread(new SimpleUpdateHandler(CacheDetailActivity.this, progress))); @@ -1228,7 +1206,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc */ private class FavoriteRemoveClickListener extends AbstractWatchlistClickListener { @Override - public void onClick(View arg0) { + public void onClick(final View arg0) { doExecute(R.string.cache_dialog_favorite_remove_title, R.string.cache_dialog_favorite_remove_message, new FavoriteRemoveThread(new SimpleUpdateHandler(CacheDetailActivity.this, progress))); @@ -1240,7 +1218,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc */ private class ChangeListClickListener implements View.OnClickListener { @Override - public void onClick(View view) { + public void onClick(final View view) { new StoredList.UserInterface(CacheDetailActivity.this).promptForListSelection(R.string.list_title, new Action1<Integer>() { @Override @@ -1257,7 +1235,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc * @param listId * the ID of the list */ - public void switchListById(int listId) { + public void switchListById(final int listId) { if (listId < 0) { return; } @@ -1360,7 +1338,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } } - private Observable<BitmapDrawable> previewMap = Observable.create(new OnSubscribe<BitmapDrawable>() { + private final Observable<BitmapDrawable> previewMap = Observable.create(new OnSubscribe<BitmapDrawable>() { @Override public void call(final Subscriber<? super BitmapDrawable> subscriber) { try { @@ -1369,7 +1347,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc if (image == null) { if (Settings.isStoreOfflineMaps() && cache.getCoords() != null) { - StaticMapsProvider.storeCachePreviewMap(cache); + RxUtils.waitForCompletion(StaticMapsProvider.storeCachePreviewMap(cache)); image = StaticMapsProvider.getPreviewMap(cache); } } @@ -1417,7 +1395,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc showDesc.setVisibility(View.VISIBLE); showDesc.setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View arg0) { + public void onClick(final View arg0) { loadLongDescription(); } }); @@ -1427,16 +1405,13 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc // cache personal note setPersonalNote(personalNoteView, cache.getPersonalNote()); personalNoteView.setMovementMethod(AnchorAwareLinkMovementMethod.getInstance()); - registerForContextMenu(personalNoteView); + addContextMenu(personalNoteView); final Button personalNoteEdit = (Button) view.findViewById(R.id.edit_personalnote); personalNoteEdit.setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View v) { - if (cache.isOffline()) { - editPersonalNote(cache, CacheDetailActivity.this); - } else { - warnPersonalNoteNeedsStoring(); - } + public void onClick(final View v) { + ensureSaved(); + editPersonalNote(cache, CacheDetailActivity.this); } }); final Button personalNoteUpload = (Button) view.findViewById(R.id.upload_personalnote); @@ -1444,7 +1419,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc personalNoteUpload.setVisibility(View.VISIBLE); personalNoteUpload.setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View v) { + public void onClick(final View v) { if (StringUtils.length(cache.getPersonalNote()) > GCConstants.PERSONAL_NOTE_MAX_CHARS) { warnPersonalNoteExceedsLimit(); } else { @@ -1478,7 +1453,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc hintView.setOnClickListener(new DecryptTextClickListener(hintView)); hintBoxView.setOnClickListener(new DecryptTextClickListener(hintView)); hintBoxView.setClickable(true); - registerForContextMenu(hintView); + addContextMenu(hintView); } else { hintView.setVisibility(View.GONE); hintView.setClickable(false); @@ -1493,7 +1468,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc spoilerlinkView.setClickable(true); spoilerlinkView.setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View arg0) { + public void onClick(final View arg0) { if (cache == null || CollectionUtils.isEmpty(cache.getSpoilers())) { showToast(res.getString(R.string.err_detail_no_spoiler)); return; @@ -1516,7 +1491,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc private void uploadPersonalNote() { final SimpleCancellableHandler myHandler = new SimpleCancellableHandler(CacheDetailActivity.this, progress); - Message cancelMessage = myHandler.cancelMessage(res.getString(R.string.cache_personal_note_upload_cancelled)); + final Message cancelMessage = myHandler.cancelMessage(res.getString(R.string.cache_personal_note_upload_cancelled)); progress.show(CacheDetailActivity.this, res.getString(R.string.cache_personal_note_uploading), res.getString(R.string.cache_personal_note_uploading), true, cancelMessage); if (currentThread != null) { @@ -1546,25 +1521,12 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } } - private void warnPersonalNoteNeedsStoring() { - Dialogs.confirm(CacheDetailActivity.this, R.string.cache_personal_note_unstored, R.string.cache_personal_note_store, - new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - storeCache(StoredList.STANDARD_LIST_ID, new StoreCachePersonalNoteHandler(CacheDetailActivity.this, progress)); - } - - }); - } - private void warnPersonalNoteExceedsLimit() { Dialogs.confirm(CacheDetailActivity.this, R.string.cache_personal_note_limit, getString(R.string.cache_personal_note_truncation, GCConstants.PERSONAL_NOTE_MAX_CHARS), new DialogInterface.OnClickListener() { @Override - public void onClick(DialogInterface dialog, int which) { + public void onClick(final DialogInterface dialog, final int which) { dialog.dismiss(); uploadPersonalNote(); } @@ -1648,7 +1610,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc descriptionView.setMovementMethod(AnchorAwareLinkMovementMethod.getInstance()); fixTextColor(descriptionString); descriptionView.setVisibility(View.VISIBLE); - registerForContextMenu(descriptionView); + addContextMenu(descriptionView); } } @@ -1687,6 +1649,14 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc }, Schedulers.io()); } + private void ensureSaved() { + if (!cache.isOffline()) { + showToast(getString(R.string.info_cache_saved)); + cache.setListId(StoredList.STANDARD_LIST_ID); + DataStore.saveCache(cache, LoadFlags.SAVE_ALL); + } + } + private class WaypointsViewCreator extends AbstractCachingPageViewCreator<ListView> { private final int VISITED_INSET = (int) (6.6f * CgeoApplication.getInstance().getResources().getDisplayMetrics().density + 0.5f); @@ -1703,12 +1673,13 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc view = (ListView) getLayoutInflater().inflate(R.layout.cachedetail_waypoints_page, null); view.setClickable(true); - View addWaypointButton = getLayoutInflater().inflate(R.layout.cachedetail_waypoints_footer, null); + final View addWaypointButton = getLayoutInflater().inflate(R.layout.cachedetail_waypoints_footer, null); view.addFooterView(addWaypointButton); addWaypointButton.setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View v) { + public void onClick(final View v) { + ensureSaved(); EditWaypointActivity.startActivityAddWaypoint(CacheDetailActivity.this, cache); refreshOnResume = true; } @@ -1716,7 +1687,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc view.setAdapter(new ArrayAdapter<Waypoint>(CacheDetailActivity.this, R.layout.waypoint_item, sortedWaypoints) { @Override - public View getView(int position, View convertView, ViewGroup parent) { + public View getView(final int position, final View convertView, final ViewGroup parent) { View rowView = convertView; if (null == rowView) { rowView = getLayoutInflater().inflate(R.layout.waypoint_item, null); @@ -1737,7 +1708,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc return view; } - protected void fillViewHolder(View rowView, final WaypointViewHolder holder, final Waypoint wpt) { + protected void fillViewHolder(final View rowView, final WaypointViewHolder holder, final Waypoint wpt) { // coordinates final TextView coordinatesView = holder.coordinatesView; if (null != wpt.getCoords()) { @@ -1800,22 +1771,22 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc final View wpNavView = holder.wpNavView; wpNavView.setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View v) { + public void onClick(final View v) { NavigationAppFactory.startDefaultNavigationApplication(1, CacheDetailActivity.this, wpt); } }); wpNavView.setOnLongClickListener(new View.OnLongClickListener() { @Override - public boolean onLongClick(View v) { + public boolean onLongClick(final View v) { NavigationAppFactory.startDefaultNavigationApplication(2, CacheDetailActivity.this, wpt); return true; } }); - registerForContextMenu(rowView); + addContextMenu(rowView); rowView.setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View v) { + public void onClick(final View v) { selectedWaypoint = wpt; openContextMenu(v); } @@ -1823,8 +1794,9 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc rowView.setOnLongClickListener(new View.OnLongClickListener() { @Override - public boolean onLongClick(View v) { + public boolean onLongClick(final View v) { selectedWaypoint = wpt; + ensureSaved(); EditWaypointActivity.startActivityEditWaypoint(CacheDetailActivity.this, cache, wpt.getId()); refreshOnResume = true; return true; @@ -1836,7 +1808,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc final WaypointType waypointType = wpt.getWaypointType(); final Drawable icon; if (wpt.isVisited()) { - LayerDrawable ld = new LayerDrawable(new Drawable[] { + final LayerDrawable ld = new LayerDrawable(new Drawable[] { res.getDrawable(waypointType.markerId), res.getDrawable(R.drawable.tick) }); ld.setLayerInset(0, 0, 0, VISITED_INSET, VISITED_INSET); @@ -1865,7 +1837,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc view.setAdapter(new ArrayAdapter<Trackable>(CacheDetailActivity.this, R.layout.simple_list_item_1, cache.getInventory())); view.setOnItemClickListener(new OnItemClickListener() { @Override - public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) { + public void onItemClick(final AdapterView<?> arg0, final View arg1, final int arg2, final long arg3) { final Object selection = arg0.getItemAtPosition(arg2); if (selection instanceof Trackable) { final Trackable trackable = (Trackable) selection; @@ -1901,6 +1873,86 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc context.startActivity(cachesIntent); } + public void addContextMenu(final View view) { + view.setOnLongClickListener(new OnLongClickListener() { + + @Override + public boolean onLongClick(final View v) { + startSupportActionMode(new ActionMode.Callback() { + + @Override + public boolean onPrepareActionMode(final ActionMode actionMode, final Menu menu) { + switch (view.getId()) { + case R.id.value: // coordinates, gc-code, name + assert view instanceof TextView; + clickedItemText = ((TextView) view).getText(); + final CharSequence itemTitle = ((TextView) ((View) view.getParent()).findViewById(R.id.name)).getText(); + buildDetailsContextMenu(actionMode, menu, clickedItemText, itemTitle, true); + return true; + case R.id.shortdesc: + assert view instanceof TextView; + clickedItemText = ((TextView) view).getText(); + buildDetailsContextMenu(actionMode, menu, clickedItemText, res.getString(R.string.cache_description), false); + return true; + case R.id.longdesc: + assert view instanceof TextView; + // combine short and long description + final String shortDesc = cache.getShortDescription(); + if (StringUtils.isBlank(shortDesc)) { + clickedItemText = ((TextView) view).getText(); + } else { + clickedItemText = shortDesc + "\n\n" + ((TextView) view).getText(); + } + buildDetailsContextMenu(actionMode, menu, clickedItemText, res.getString(R.string.cache_description), false); + return true; + case R.id.personalnote: + assert view instanceof TextView; + clickedItemText = ((TextView) view).getText(); + buildDetailsContextMenu(actionMode, menu, clickedItemText, res.getString(R.string.cache_personal_note), true); + return true; + case R.id.hint: + assert view instanceof TextView; + clickedItemText = ((TextView) view).getText(); + buildDetailsContextMenu(actionMode, menu, clickedItemText, res.getString(R.string.cache_hint), false); + return true; + case R.id.log: + assert view instanceof TextView; + clickedItemText = ((TextView) view).getText(); + buildDetailsContextMenu(actionMode, menu, clickedItemText, res.getString(R.string.cache_logs), false); + return true; + case R.id.date: // event date + assert view instanceof TextView; + clickedItemText = ((TextView) view).getText(); + buildDetailsContextMenu(actionMode, menu, clickedItemText, res.getString(R.string.cache_event), true); + menu.findItem(R.id.menu_calendar).setVisible(cache.canBeAddedToCalendar()); + return true; + } + return false; + } + + @Override + public void onDestroyActionMode(final ActionMode actionMode) { + // do nothing + } + + @Override + public boolean onCreateActionMode(final ActionMode actionMode, final Menu menu) { + actionMode.getMenuInflater().inflate(R.menu.details_context, menu); + + // Return true so that the action mode is shown + return true; + } + + @Override + public boolean onActionItemClicked(final ActionMode actionMode, final MenuItem menuItem) { + return onClipboardItemSelected(actionMode, menuItem, clickedItemText); + } + }); + return false; + } + }); + } + public static void startActivityGuid(final Context context, final String guid, final String cacheName) { final Intent cacheIntent = new Intent(context, CacheDetailActivity.class); cacheIntent.putExtra(Intents.EXTRA_GUID, guid); @@ -1920,7 +1972,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc builder.setSingleChoiceItems(items, 0, new DialogInterface.OnClickListener() { @Override - public void onClick(DialogInterface dialog, final int which) { + public void onClick(final DialogInterface dialog, final int which) { dialog.dismiss(); final ProgressDialog progressDialog = ProgressDialog.show(CacheDetailActivity.this, getString(R.string.cache), getString(R.string.waypoint_reset), true); final HandlerResetCoordinates handler = new HandlerResetCoordinates(CacheDetailActivity.this, progressDialog, which == 1); @@ -1936,14 +1988,14 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc private final ProgressDialog progressDialog; private final boolean resetRemote; - protected HandlerResetCoordinates(CacheDetailActivity activity, ProgressDialog progressDialog, boolean resetRemote) { + protected HandlerResetCoordinates(final CacheDetailActivity activity, final ProgressDialog progressDialog, final boolean resetRemote) { super(activity); this.progressDialog = progressDialog; this.resetRemote = resetRemote; } @Override - public void handleMessage(Message msg) { + public void handleMessage(final Message msg) { if (msg.what == ResetCoordsThread.LOCAL) { localFinished = true; } else { @@ -1972,7 +2024,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc public static final int LOCAL = 0; public static final int ON_WEBSITE = 1; - public ResetCoordsThread(Geocache cache, Handler handler, final Waypoint wpt, boolean local, boolean remote, final ProgressDialog progress) { + public ResetCoordsThread(final Geocache cache, final Handler handler, final Waypoint wpt, final boolean local, final boolean remote, final ProgressDialog progress) { this.cache = cache; this.handler = handler; this.local = local; @@ -2032,19 +2084,19 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc private Geocache cache = null; private CancellableHandler handler = null; - public UploadPersonalNoteThread(Geocache cache, CancellableHandler handler) { + public UploadPersonalNoteThread(final Geocache cache, final CancellableHandler handler) { this.cache = cache; this.handler = handler; } @Override public void run() { - IConnector con = ConnectorFactory.getConnector(cache); + final IConnector con = ConnectorFactory.getConnector(cache); if (con.supportsPersonalNote()) { con.uploadPersonalNote(cache); } - Message msg = Message.obtain(); - Bundle bundle = new Bundle(); + final Message msg = Message.obtain(); + final Bundle bundle = new Bundle(); bundle.putString(SimpleCancellableHandler.MESSAGE_TEXT, res.getString(R.string.cache_personal_note_upload_done)); msg.setData(bundle); handler.sendMessage(msg); @@ -2052,7 +2104,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } @Override - protected String getTitle(Page page) { + protected String getTitle(final Page page) { // show number of waypoints directly in waypoint title if (page == Page.WAYPOINTS) { final int waypointCount = cache.getWaypoints().size(); @@ -2084,7 +2136,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } @Override - protected AbstractViewPagerActivity.PageViewCreator createViewCreator(Page page) { + protected AbstractViewPagerActivity.PageViewCreator createViewCreator(final Page page) { switch (page) { case DETAILS: return new DetailsViewCreator(); @@ -2112,9 +2164,9 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } static void updateOfflineBox(final View view, final Geocache cache, final Resources res, - final OnClickListener refreshCacheClickListener, - final OnClickListener dropCacheClickListener, - final OnClickListener storeCacheClickListener) { + final OnClickListener refreshCacheClickListener, + final OnClickListener dropCacheClickListener, + final OnClickListener storeCacheClickListener) { // offline use final TextView offlineText = (TextView) view.findViewById(R.id.offline_text); final Button offlineRefresh = (Button) view.findViewById(R.id.offline_refresh); @@ -2160,12 +2212,12 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc private static class StoreCacheHandler extends SimpleCancellableHandler { - public StoreCacheHandler(CacheDetailActivity activity, Progress progress) { + public StoreCacheHandler(final CacheDetailActivity activity, final Progress progress) { super(activity, progress); } @Override - public void handleRegularMessage(Message msg) { + public void handleRegularMessage(final Message msg) { if (UPDATE_LOAD_PROGRESS_DETAIL == msg.what && msg.obj instanceof String) { updateStatusMsg(R.string.cache_dialog_offline_save_message, (String) msg.obj); } else { @@ -2176,12 +2228,12 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc private static final class RefreshCacheHandler extends SimpleCancellableHandler { - public RefreshCacheHandler(CacheDetailActivity activity, Progress progress) { + public RefreshCacheHandler(final CacheDetailActivity activity, final Progress progress) { super(activity, progress); } @Override - public void handleRegularMessage(Message msg) { + public void handleRegularMessage(final Message msg) { if (UPDATE_LOAD_PROGRESS_DETAIL == msg.what && msg.obj instanceof String) { updateStatusMsg(R.string.cache_dialog_refresh_message, (String) msg.obj); } else { @@ -2192,24 +2244,24 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc private static final class ChangeNotificationHandler extends SimpleHandler { - public ChangeNotificationHandler(CacheDetailActivity activity, Progress progress) { + public ChangeNotificationHandler(final CacheDetailActivity activity, final Progress progress) { super(activity, progress); } @Override - public void handleMessage(Message msg) { + public void handleMessage(final Message msg) { notifyDatasetChanged(activityRef); } } private static final class SimpleUpdateHandler extends SimpleHandler { - public SimpleUpdateHandler(CacheDetailActivity activity, Progress progress) { + public SimpleUpdateHandler(final CacheDetailActivity activity, final Progress progress) { super(activity, progress); } @Override - public void handleMessage(Message msg) { + public void handleMessage(final Message msg) { if (msg.what == MESSAGE_FAILED) { super.handleMessage(msg); } else { @@ -2218,8 +2270,8 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } } - private static void notifyDatasetChanged(WeakReference<AbstractActivity> activityRef) { - CacheDetailActivity activity = ((CacheDetailActivity) activityRef.get()); + private static void notifyDatasetChanged(final WeakReference<AbstractActivity> activityRef) { + final CacheDetailActivity activity = ((CacheDetailActivity) activityRef.get()); if (activity != null) { activity.notifyDataSetChanged(); } @@ -2227,42 +2279,22 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc protected void storeCache(final int listId, final StoreCacheHandler storeCacheHandler) { progress.show(this, res.getString(R.string.cache_dialog_offline_save_title), res.getString(R.string.cache_dialog_offline_save_message), true, storeCacheHandler.cancelMessage()); - Schedulers.io().schedule(new Action1<Inner>() { + Schedulers.io().createWorker().schedule(new Action0() { @Override - public void call(final Inner inner) { + public void call() { cache.store(listId, storeCacheHandler); } }); } - private static final class StoreCachePersonalNoteHandler extends StoreCacheHandler { - - public StoreCachePersonalNoteHandler(CacheDetailActivity activity, Progress progress) { - super(activity, progress); - } - - @Override - public void handleRegularMessage(Message msg) { - if (UPDATE_LOAD_PROGRESS_DETAIL == msg.what && msg.obj instanceof String) { - updateStatusMsg(R.string.cache_dialog_offline_save_message, (String) msg.obj); - } else { - dismissProgress(); - CacheDetailActivity activity = (CacheDetailActivity) activityRef.get(); - if (activity != null) { - editPersonalNote(activity.getCache(), activity); - } - } - } - } - public static void editPersonalNote(final Geocache cache, final CacheDetailActivity activity) { if (cache.isOffline()) { - EditNoteDialogListener editNoteDialogListener = new EditNoteDialogListener() { + final EditNoteDialogListener editNoteDialogListener = new EditNoteDialogListener() { @Override public void onFinishEditNoteDialog(final String note) { cache.setPersonalNote(note); cache.parseWaypointsFromNote(); - TextView personalNoteView = (TextView) activity.findViewById(R.id.personalnote); + final TextView personalNoteView = (TextView) activity.findViewById(R.id.personalnote); setPersonalNote(personalNoteView, note); DataStore.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); activity.notifyDataSetChanged(); diff --git a/main/src/cgeo/geocaching/CacheListActivity.java b/main/src/cgeo/geocaching/CacheListActivity.java index 0aec119..0fd3826 100644 --- a/main/src/cgeo/geocaching/CacheListActivity.java +++ b/main/src/cgeo/geocaching/CacheListActivity.java @@ -18,6 +18,7 @@ import cgeo.geocaching.files.GPXImporter; import cgeo.geocaching.filter.FilterUserInterface; import cgeo.geocaching.filter.IFilter; import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.list.AbstractList; import cgeo.geocaching.list.PseudoList; import cgeo.geocaching.list.StoredList; import cgeo.geocaching.loaders.AbstractSearchLoader; @@ -40,6 +41,7 @@ import cgeo.geocaching.sensors.DirectionProvider; import cgeo.geocaching.sensors.GeoDirHandler; import cgeo.geocaching.sensors.IGeoData; import cgeo.geocaching.settings.Settings; +import cgeo.geocaching.settings.SettingsActivity; import cgeo.geocaching.sorting.CacheComparator; import cgeo.geocaching.sorting.ComparatorUserInterface; import cgeo.geocaching.ui.CacheListAdapter; @@ -57,6 +59,7 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.ListUtils; import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; import rx.Subscription; import rx.functions.Action1; @@ -67,8 +70,10 @@ import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.content.res.Configuration; +import android.content.res.Resources; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; @@ -77,6 +82,7 @@ import android.os.Message; import android.provider.OpenableColumns; import android.support.v4.app.LoaderManager; import android.support.v4.content.Loader; +import android.support.v7.app.ActionBar; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.KeyEvent; @@ -160,7 +166,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA dialog.setNegativeButton(res.getString(R.string.license_dismiss), new DialogInterface.OnClickListener() { @Override - public void onClick(DialogInterface dialog, int id) { + public void onClick(final DialogInterface dialog, final int id) { Cookies.clearCookies(); dialog.cancel(); } @@ -168,7 +174,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA dialog.setPositiveButton(res.getString(R.string.license_show), new DialogInterface.OnClickListener() { @Override - public void onClick(DialogInterface dialog, int id) { + public void onClick(final DialogInterface dialog, final int id) { Cookies.clearCookies(); startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.geocaching.com/software/agreement.aspx?ID=0"))); } @@ -212,12 +218,12 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA private static class LoadCachesHandler extends WeakReferenceHandler<CacheListActivity> { - protected LoadCachesHandler(CacheListActivity activity) { + protected LoadCachesHandler(final CacheListActivity activity) { super(activity); } @Override - public void handleMessage(Message msg) { + public void handleMessage(final Message msg) { final CacheListActivity activity = getActivity(); if (activity == null) { return; @@ -252,26 +258,20 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } } + private String getCacheNumberString(final Resources res, final int count) { + return res.getQuantityString(R.plurals.cache_counts, count, count); + } + protected void updateTitle() { - final ArrayList<Integer> numbers = new ArrayList<Integer>(); - if (adapter.isFiltered()) { - numbers.add(adapter.getCount()); - } - if (search != null) { - numbers.add(search.getCount()); - } - if (numbers.isEmpty()) { - setTitle(title); - } - else { - setTitle(title + " [" + StringUtils.join(numbers, '/') + ']'); - } + setTitle(title); + getSupportActionBar().setSubtitle(getCurrentSubtitle()); + refreshSpinnerAdapter(); } private final CancellableHandler loadDetailsHandler = new CancellableHandler() { @Override - public void handleRegularMessage(Message msg) { + public void handleRegularMessage(final Message msg) { updateAdapter(); if (msg.what > -1) { @@ -310,7 +310,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA */ private class DownloadFromWebHandler extends CancellableHandler { @Override - public void handleRegularMessage(Message msg) { + public void handleRegularMessage(final Message msg) { updateAdapter(); adapter.notifyDataSetChanged(); @@ -348,7 +348,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA private final CancellableHandler clearOfflineLogsHandler = new CancellableHandler() { @Override - public void handleRegularMessage(Message msg) { + public void handleRegularMessage(final Message msg) { adapter.setSelectMode(false); refreshCurrentList(); @@ -361,7 +361,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA private final Handler importGpxAttachementFinishedHandler = new Handler() { @Override - public void handleMessage(Message msg) { + public void handleMessage(final Message msg) { refreshCurrentList(); } }; @@ -373,10 +373,12 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); + setTheme(); + setContentView(R.layout.cacheslist_activity); // get parameters @@ -396,22 +398,16 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } } - // Add the list selection in code. This way we can leave the XML layout of the action bar the same as for other activities. - final View titleBar = findViewById(R.id.actionbar_title); - titleBar.setClickable(true); - titleBar.setOnClickListener(new View.OnClickListener() { - - @Override - public void onClick(View v) { - selectList(); - } - }); - setTitle(title); + initAdapter(); prepareFilterBar(); + if (type.canSwitch) { + initActionBarSpinner(); + } + currentLoader = (AbstractSearchLoader) getSupportLoaderManager().initLoader(type.getLoaderId(), extras, this); // init @@ -427,11 +423,55 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA if (isInvokedFromAttachment()) { importGpxAttachement(); } + + + } + /** + * Action bar spinner adapter. {@code null} for list types that don't allow switching (search results, ...). + */ + CacheListSpinnerAdapter mCacheListSpinnerAdapter; + + /** + * remember current filter when switching between lists, so it can be re-applied afterwards + */ + private IFilter currentFilter = null; + + private void initActionBarSpinner() { + mCacheListSpinnerAdapter = new CacheListSpinnerAdapter(this, R.layout.support_simple_spinner_dropdown_item); + getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); + getSupportActionBar().setDisplayShowTitleEnabled(false); + getSupportActionBar().setListNavigationCallbacks(mCacheListSpinnerAdapter, new ActionBar.OnNavigationListener() { + @Override + public boolean onNavigationItemSelected(final int i, final long l) { + final int newListId = mCacheListSpinnerAdapter.getItem(i).id; + if (newListId != listId) { + switchListById(newListId); + } + return true; + } + }); + } + + private void refreshSpinnerAdapter() { + /* If the activity does not use the Spinner this will be null */ + if (mCacheListSpinnerAdapter==null) { + return; + } + mCacheListSpinnerAdapter.clear(); + + final AbstractList list = AbstractList.getListById(listId); + + for (final AbstractList l: StoredList.UserInterface.getMenuLists(false, PseudoList.NEW_LIST.id)) { + mCacheListSpinnerAdapter.add(l); + } + + getSupportActionBar().setSelectedNavigationItem(mCacheListSpinnerAdapter.getPosition(list)); + } @Override - public void onConfigurationChanged(Configuration newConfig) { + public void onConfigurationChanged(final Configuration newConfig) { super.onConfigurationChanged(newConfig); if (currentLoader != null && currentLoader.isLoading()) { showFooterLoadingCaches(); @@ -452,7 +492,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA new StoredList.UserInterface(this).promptForListSelection(R.string.gpx_import_select_list_title, new Action1<Integer>() { @Override - public void call(Integer listId) { + public void call(final Integer listId) { new GPXImporter(CacheListActivity.this, listId, importGpxAttachementFinishedHandler).importGPX(); switchListById(listId); } @@ -499,7 +539,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } @Override - public boolean onCreateOptionsMenu(Menu menu) { + public boolean onCreateOptionsMenu(final Menu menu) { getMenuInflater().inflate(R.menu.cache_list_options, menu); CacheListAppFactory.addMenuItems(menu, this, res); @@ -512,7 +552,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } @Override - public boolean onPrepareOptionsMenu(Menu menu) { + public boolean onPrepareOptionsMenu(final Menu menu) { super.onPrepareOptionsMenu(menu); final boolean isHistory = type == CacheListType.HISTORY; @@ -532,18 +572,19 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA setVisible(menu, R.id.menu_switch_select_mode, !isEmpty); setVisible(menu, R.id.submenu_manage, (isHistory && !isEmpty) || isOffline); - setVisible(menu, R.id.submenu_manage_lists, isOffline); + + setVisible(menu, R.id.menu_create_list, isOffline); setVisible(menu, R.id.menu_sort, !isEmpty && !isHistory); setVisible(menu, R.id.menu_refresh_stored, !isEmpty && (isConcrete || type != CacheListType.OFFLINE)); setVisible(menu, R.id.menu_drop_caches, !isEmpty && isOffline); setVisible(menu, R.id.menu_drop_caches_and_list, isConcrete && !isEmpty && isOffline); - setVisible(menu, R.id.menu_delete_events, isConcrete && !isEmpty && containsEvents()); + setVisible(menu, R.id.menu_delete_events, isConcrete && !isEmpty && containsPastEvents()); setVisible(menu, R.id.menu_move_to_list, isOffline && !isEmpty); setVisible(menu, R.id.menu_export, !isEmpty && (isHistory || isOffline)); setVisible(menu, R.id.menu_remove_from_history, !isEmpty && isHistory); setVisible(menu, R.id.menu_clear_offline_logs, !isEmpty && containsOfflineLogs() && (isHistory || isOffline)); - setVisible(menu, R.id.menu_import_web, isOffline && Settings.getWebDeviceCode() != null); + setVisible(menu, R.id.menu_import_web, isOffline); setVisible(menu, R.id.menu_import_gpx, isOffline); setVisible(menu, R.id.menu_refresh_stored_top, !isOffline && !isEmpty); @@ -568,13 +609,11 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA menu.findItem(R.id.menu_drop_list).setVisible(isNonDefaultList); menu.findItem(R.id.menu_rename_list).setVisible(isNonDefaultList); - final boolean multipleLists = DataStore.getLists().size() >= 2; - menu.findItem(R.id.menu_switch_list).setVisible(multipleLists); menu.findItem(R.id.menu_move_to_list).setVisible(!isEmpty); setMenuItemLabel(menu, R.id.menu_remove_from_history, R.string.cache_remove_from_history, R.string.cache_clear_history); setMenuItemLabel(menu, R.id.menu_export, R.string.export, R.string.export); - menu.findItem(R.id.menu_import_android).setVisible(Compatibility.isStorageAccessFrameworkAvailable()); + menu.findItem(R.id.menu_import_android).setVisible(Compatibility.isStorageAccessFrameworkAvailable() && isOffline); } catch (final RuntimeException e) { Log.e("CacheListActivity.onPrepareOptionsMenu", e); } @@ -582,9 +621,9 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA return true; } - private boolean containsEvents() { + private boolean containsPastEvents() { for (final Geocache cache : adapter.getCheckedOrAllCaches()) { - if (cache.isEventCache()) { + if (DateUtils.isPastEvent(cache)) { return true; } } @@ -614,8 +653,14 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } @Override - public boolean onOptionsItemSelected(MenuItem item) { + public boolean onOptionsItemSelected(final MenuItem item) { + if (super.onOptionsItemSelected(item)) { + return true; + } switch (item.getItemId()) { + case R.id.menu_show_on_map: + goMap(); + return true; case R.id.menu_switch_select_mode: adapter.switchSelectMode(); invalidateOptionsMenuCompatible(); @@ -643,6 +688,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA return false; case R.id.menu_create_list: new StoredList.UserInterface(this).promptForListCreation(getListSwitchingRunnable(), newListName); + refreshSpinnerAdapter(); invalidateOptionsMenuCompatible(); return false; case R.id.menu_drop_list: @@ -656,10 +702,6 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA adapter.invertSelection(); invalidateOptionsMenuCompatible(); return false; - case R.id.menu_switch_list: - selectList(); - invalidateOptionsMenuCompatible(); - return false; case R.id.menu_filter: showFilterMenu(null); return true; @@ -667,7 +709,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA final CacheComparator oldComparator = adapter.getCacheComparator(); new ComparatorUserInterface(this).selectComparator(oldComparator, new Action1<CacheComparator>() { @Override - public void call(CacheComparator selectedComparator) { + public void call(final CacheComparator selectedComparator) { // selecting the same sorting twice will toggle the order if (selectedComparator != null && oldComparator != null && selectedComparator.getClass().equals(oldComparator.getClass())) { adapter.toggleInverseSort(); @@ -750,13 +792,8 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA public void showFilterMenu(final View view) { new FilterUserInterface(this).selectFilter(new Action1<IFilter>() { @Override - public void call(IFilter selectedFilter) { - if (selectedFilter != null) { - setFilter(selectedFilter); - } else { - // clear filter - setFilter(null); - } + public void call(@Nullable final IFilter selectedFilter) { + setFilter(selectedFilter); } }); } @@ -806,7 +843,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA new StoredList.UserInterface(this).promptForListSelection(R.string.cache_menu_move_list, new Action1<Integer>() { @Override - public void call(Integer newListId) { + public void call(final Integer newListId) { DataStore.moveToList(adapter.getCheckedOrAllCaches(), newListId); adapter.setSelectMode(false); @@ -816,7 +853,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } @Override - public boolean onContextItemSelected(MenuItem item) { + public boolean onContextItemSelected(final MenuItem item) { ContextMenu.ContextMenuInfo info = item.getMenuInfo(); // restore menu info for sub menu items, see @@ -853,7 +890,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA case R.id.menu_drop_cache: cache.drop(new Handler() { @Override - public void handleMessage(Message msg) { + public void handleMessage(final Message msg) { adapter.notifyDataSetChanged(); refreshCurrentList(); } @@ -863,7 +900,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA new StoredList.UserInterface(this).promptForListSelection(R.string.cache_menu_move_list, new Action1<Integer>() { @Override - public void call(Integer newListId) { + public void call(final Integer newListId) { DataStore.moveToList(Collections.singletonList(cache), newListId); adapter.setSelectMode(false); refreshCurrentList(); @@ -904,7 +941,8 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA return adapter.findCacheByGeocode(contextMenuGeocode); } - private boolean setFilter(IFilter filter) { + private boolean setFilter(final IFilter filter) { + currentFilter = filter; adapter.setFilter(filter); prepareFilterBar(); updateTitle(); @@ -913,7 +951,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { + public boolean onKeyDown(final int keyCode, final KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { if (adapter.isSelectMode()) { adapter.setSelectMode(false); @@ -927,12 +965,15 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA final ListView list = getListView(); registerForContextMenu(list); adapter = new CacheListAdapter(this, cacheList, type); + adapter.setFilter(currentFilter); - listFooter = getLayoutInflater().inflate(R.layout.cacheslist_footer, null); - listFooter.setClickable(true); - listFooter.setOnClickListener(new MoreCachesListener()); - listFooterText = (TextView) listFooter.findViewById(R.id.more_caches); - list.addFooterView(listFooter); + if (listFooter == null) { + listFooter = getLayoutInflater().inflate(R.layout.cacheslist_footer, null); + listFooter.setClickable(true); + listFooter.setOnClickListener(new MoreCachesListener()); + listFooterText = (TextView) listFooter.findViewById(R.id.more_caches); + list.addFooterView(listFooter); + } setListAdapter(adapter); adapter.forceSort(); } @@ -975,7 +1016,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { + protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_CODE_IMPORT_GPX && resultCode == Activity.RESULT_OK) { @@ -991,7 +1032,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA refreshCurrentList(); } - private String getDisplayName(Uri uri) { + private String getDisplayName(final Uri uri) { Cursor cursor = null; try { cursor = getContentResolver().query(uri, new String[] { OpenableColumns.DISPLAY_NAME }, null, null, null); @@ -1017,21 +1058,25 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA return; } - if (Settings.getChooseList() && type != CacheListType.OFFLINE) { + if (Settings.getChooseList() && (type != CacheListType.OFFLINE && type != CacheListType.HISTORY)) { // let user select list to store cache in new StoredList.UserInterface(this).promptForListSelection(R.string.list_title, new Action1<Integer>() { @Override public void call(final Integer selectedListId) { - refreshStored(caches, selectedListId); + // in case of online lists, set the list id to a concrete list now + for (final Geocache geocache : caches) { + geocache.setListId(selectedListId); + } + refreshStoredInternal(caches); } }, true, StoredList.TEMPORARY_LIST_ID, newListName); } else { - refreshStored(caches, this.listId); + refreshStoredInternal(caches); } } - private void refreshStored(final List<Geocache> caches, final int storeListId) { + private void refreshStoredInternal(final List<Geocache> caches) { detailProgress = 0; showProgress(false); @@ -1049,16 +1094,16 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA detailProgressTime = System.currentTimeMillis(); - final LoadDetailsThread threadDetails = new LoadDetailsThread(loadDetailsHandler, caches, storeListId); + final LoadDetailsThread threadDetails = new LoadDetailsThread(loadDetailsHandler, caches); threadDetails.start(); } public void removeFromHistoryCheck() { - int message = (adapter != null && adapter.getCheckedCount() > 0) ? R.string.cache_remove_from_history + final int message = (adapter != null && adapter.getCheckedCount() > 0) ? R.string.cache_remove_from_history : R.string.cache_clear_history; Dialogs.confirmYesNo(this, R.string.caches_removing_from_history, message, new DialogInterface.OnClickListener() { @Override - public void onClick(DialogInterface dialog, int id) { + public void onClick(final DialogInterface dialog, final int id) { removeFromHistory(); dialog.cancel(); } @@ -1077,8 +1122,19 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } public void importWeb() { - detailProgress = 0; + // menu is also shown with no device connected + if (Settings.getWebDeviceCode() == null) { + Dialogs.confirm(this, R.string.web_import_title, R.string.init_sendToCgeo_description, new OnClickListener() { + @Override + public void onClick(final DialogInterface dialog, final int which) { + SettingsActivity.openForScreen(R.string.preference_screen_sendtocgeo, CacheListActivity.this); + } + }); + return; + } + + detailProgress = 0; showProgress(false); final DownloadFromWebHandler downloadFromWebHandler = new DownloadFromWebHandler(); progress.show(this, null, res.getString(R.string.web_import_waiting), true, downloadFromWebHandler.cancelMessage()); @@ -1088,18 +1144,18 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } public void dropStored(final boolean removeListAfterwards) { - int message = (adapter.getCheckedCount() > 0) ? R.string.caches_drop_selected_ask : R.string.caches_drop_all_ask; + final int message = (adapter.getCheckedCount() > 0) ? R.string.caches_drop_selected_ask : R.string.caches_drop_all_ask; Dialogs.confirmYesNo(this, R.string.caches_drop_stored, message, new DialogInterface.OnClickListener() { @Override - public void onClick(DialogInterface dialog, int id) { + public void onClick(final DialogInterface dialog, final int id) { dropSelected(removeListAfterwards); dialog.cancel(); } }); } - public void dropSelected(boolean removeListAfterwards) { + public void dropSelected(final boolean removeListAfterwards) { final List<Geocache> selected = adapter.getCheckedOrAllCaches(); new DropDetailsTask(removeListAfterwards).execute(selected.toArray(new Geocache[selected.size()])); } @@ -1111,15 +1167,11 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA private class LoadDetailsThread extends Thread { final private CancellableHandler handler; - final private int listIdLD; final private List<Geocache> caches; - public LoadDetailsThread(CancellableHandler handler, List<Geocache> caches, int listId) { + public LoadDetailsThread(final CancellableHandler handler, final List<Geocache> caches) { 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); } @Override @@ -1148,13 +1200,13 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA * @return * <code>false</code> if the storing was interrupted, <code>true</code> otherwise */ - private boolean refreshCache(Geocache cache) { + private boolean refreshCache(final Geocache cache) { try { if (handler.isCancelled()) { throw new InterruptedException("Stopped storing process."); } detailProgress++; - cache.refreshSynchronous(listIdLD, null); + cache.refreshSynchronous(null); handler.sendEmptyMessage(cacheList.indexOf(cache)); } catch (final InterruptedException e) { Log.i(e.getMessage()); @@ -1172,7 +1224,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA final private CancellableHandler handler; final private int listIdLFW; - public LoadFromWebThread(CancellableHandler handler, int listId) { + public LoadFromWebThread(final CancellableHandler handler, final int listId) { this.handler = handler; listIdLFW = StoredList.getConcreteList(listId); } @@ -1224,19 +1276,19 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA private final boolean removeListAfterwards; - public DropDetailsTask(boolean removeListAfterwards) { + public DropDetailsTask(final boolean removeListAfterwards) { super(CacheListActivity.this, null, res.getString(R.string.caches_drop_progress), true); this.removeListAfterwards = removeListAfterwards; } @Override - protected Void doInBackgroundInternal(Geocache[] caches) { + protected Void doInBackgroundInternal(final Geocache[] caches) { DataStore.markDropped(Arrays.asList(caches)); return null; } @Override - protected void onPostExecuteInternal(Void result) { + protected void onPostExecuteInternal(final Void result) { // remove list in UI because of toast if (removeListAfterwards) { removeList(false); @@ -1254,7 +1306,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA final private Handler handler; final private List<Geocache> selected; - public ClearOfflineLogsThread(Handler handlerIn) { + public ClearOfflineLogsThread(final Handler handlerIn) { handler = handlerIn; selected = adapter.getCheckedOrAllCaches(); } @@ -1269,7 +1321,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA private class MoreCachesListener implements View.OnClickListener { @Override - public void onClick(View arg0) { + public void onClick(final View arg0) { showProgress(true); showFooterLoadingCaches(); listFooter.setOnClickListener(null); @@ -1287,13 +1339,6 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } } - public void selectList() { - if (!type.canSwitch) { - return; - } - new StoredList.UserInterface(this).promptForListSelection(R.string.list_title, getListSwitchingRunnable()); - } - @NonNull private Action1<Integer> getListSwitchingRunnable() { return new Action1<Integer>() { @@ -1305,7 +1350,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA }; } - public void switchListById(int id) { + public void switchListById(final int id) { if (id < 0) { return; } @@ -1323,18 +1368,17 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA listId = list.id; title = list.title; + type = CacheListType.OFFLINE; Settings.saveLastList(listId); + initAdapter(); + showProgress(true); showFooterLoadingCaches(); DataStore.moveToList(adapter.getCheckedCaches(), listId); - currentLoader = (OfflineGeocacheListLoader) getSupportLoaderManager().initLoader(CacheListType.OFFLINE.getLoaderId(), new Bundle(), this); - currentLoader.reset(); - ((OfflineGeocacheListLoader) currentLoader).setListId(listId); - ((OfflineGeocacheListLoader) currentLoader).setSearchCenter(coords); - currentLoader.startLoading(); + currentLoader = (OfflineGeocacheListLoader) getSupportLoaderManager().restartLoader(CacheListType.OFFLINE.getLoaderId(), OfflineGeocacheListLoader.getBundleForList(listId), this); invalidateOptionsMenuCompatible(); } @@ -1352,6 +1396,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA private void removeListInternal() { if (DataStore.removeList(listId)) { showToast(res.getString(R.string.list_dialog_remove_ok)); + refreshSpinnerAdapter(); switchListById(StoredList.STANDARD_LIST_ID); } else { showToast(res.getString(R.string.list_dialog_remove_err)); @@ -1374,17 +1419,13 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA // ask him, if there are caches on the list Dialogs.confirm(this, R.string.list_dialog_remove_title, R.string.list_dialog_remove_description, R.string.list_dialog_remove, new DialogInterface.OnClickListener() { @Override - public void onClick(DialogInterface dialog, int whichButton) { + public void onClick(final DialogInterface dialog, final int whichButton) { removeListInternal(); } }); } - /** - * @param view - * unused here but needed since this method is referenced from XML layout - */ - public void goMap(View view) { + public void goMap() { if (!cacheToShow()) { return; } @@ -1400,6 +1441,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } private void refreshCurrentList() { + refreshSpinnerAdapter(); switchListById(listId); } @@ -1463,7 +1505,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA context.startActivity(cachesIntent); } - public static void startActivityHistory(Context context) { + public static void startActivityHistory(final Context context) { final Intent cachesIntent = new Intent(context, CacheListActivity.class); cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.HISTORY); context.startActivity(cachesIntent); @@ -1487,7 +1529,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA context.startActivity(cachesIntent); } - private static boolean isValidCoords(AbstractActivity context, Geopoint coords) { + private static boolean isValidCoords(final AbstractActivity context, final Geopoint coords) { if (coords == null) { context.showToast(CgeoApplication.getInstance().getString(R.string.warn_no_coordinates)); return false; @@ -1529,7 +1571,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA // Loaders @Override - public Loader<SearchResult> onCreateLoader(int type, Bundle extras) { + public Loader<SearchResult> onCreateLoader(final int type, final Bundle extras) { if (type >= CacheListLoaderType.values().length) { throw new IllegalArgumentException("invalid loader type " + type); } @@ -1562,6 +1604,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA break; case HISTORY: title = res.getString(R.string.caches_history); + listId = PseudoList.HISTORY_LIST.id; loader = new HistoryGeocacheListLoader(app, coords); break; case NEAREST: @@ -1621,7 +1664,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA loader = new PocketGeocacheListLoader(app, guid); break; } - setTitle(title); + updateTitle(); showProgress(true); showFooterLoadingCaches(); @@ -1631,7 +1674,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA return loader; } - private void rememberTerm(String term) { + private void rememberTerm(final String term) { // set the title of the activity title = term; // and remember this term for potential use in list creation @@ -1639,7 +1682,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } @Override - public void onLoadFinished(Loader<SearchResult> arg0, SearchResult searchIn) { + public void onLoadFinished(final Loader<SearchResult> arg0, final SearchResult searchIn) { // The database search was moved into the UI call intentionally. If this is done before the runOnUIThread, // then we have 2 sets of caches in memory. This can lead to OOM for huge cache lists. if (searchIn != null) { @@ -1656,10 +1699,46 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } showProgress(false); hideLoading(); + invalidateOptionsMenuCompatible(); } @Override - public void onLoaderReset(Loader<SearchResult> arg0) { + public void onLoaderReset(final Loader<SearchResult> arg0) { //Not interesting } + + /** + * Allow the title bar spinner to show the same subtitle like the activity itself would show. + * + * @param list + * @return + */ + public CharSequence getCacheListSubtitle(@NonNull final AbstractList list) { + // if this is the current list, be aware of filtering + if (list.id == listId) { + return getCurrentSubtitle(); + } + // otherwise return the overall number + final int numberOfCaches = list.getNumberOfCaches(); + if (numberOfCaches < 0) { + return StringUtils.EMPTY; + } + return getCacheNumberString(getResources(), numberOfCaches); + } + + /** + * Calculate the subtitle of the current list depending on (optional) filters. + * + * @return + */ + private CharSequence getCurrentSubtitle() { + final ArrayList<String> numbers = new ArrayList<String>(); + if (adapter.isFiltered()) { + numbers.add(getCacheNumberString(getResources(), adapter.getCount())); + } + if (search != null) { + numbers.add(getCacheNumberString(getResources(), search.getCount())); + } + return numbers.isEmpty() ? null : StringUtils.join(numbers, '/'); + } } diff --git a/main/src/cgeo/geocaching/CacheListSpinnerAdapter.java b/main/src/cgeo/geocaching/CacheListSpinnerAdapter.java new file mode 100644 index 0000000..6311e47 --- /dev/null +++ b/main/src/cgeo/geocaching/CacheListSpinnerAdapter.java @@ -0,0 +1,68 @@ +package cgeo.geocaching; + +import cgeo.geocaching.list.AbstractList; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; + +class CacheListSpinnerAdapter extends ArrayAdapter<AbstractList> { + + static class ViewHolder { + TextView title; + TextView subtitle; + } + + private final CacheListActivity cacheListActivity; + + public CacheListSpinnerAdapter(final CacheListActivity context, final int resource) { + super(context, resource); + cacheListActivity = context; + } + + + @Override + public View getView(final int position, final View convertView, final ViewGroup parent) { + return getCustomView(position, convertView, parent); + } + + + @Override + public View getDropDownView(final int position, final View convertView, final ViewGroup parent) { + return getCustomView(position, convertView, parent); + } + + public View getCustomView(final int position, final View convertView, final ViewGroup parent) { + + View resultView = convertView; + final LayoutInflater inflater = + (LayoutInflater) cacheListActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + + CacheListSpinnerAdapter.ViewHolder holder; + if (resultView == null) { + resultView = inflater.inflate(R.layout.cachelist_spinneritem, parent, false); + holder = new ViewHolder(); + holder.title = (TextView) resultView.findViewById(android.R.id.text1); + holder.subtitle = (TextView) resultView.findViewById(android.R.id.text2); + + resultView.setTag(holder); + } else { + holder = (CacheListSpinnerAdapter.ViewHolder) resultView.getTag(); + } + + final AbstractList list = getItem(position); + holder.title.setText(list.getTitle()); + if (list.getNumberOfCaches() >= 0) { + holder.subtitle.setVisibility(View.VISIBLE); + holder.subtitle.setText(cacheListActivity.getCacheListSubtitle(list)); + } else { + holder.subtitle.setVisibility(View.GONE); + } + + return resultView; + } +}
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/CacheMenuHandler.java b/main/src/cgeo/geocaching/CacheMenuHandler.java index cfe9eeb..5b0fdba 100644 --- a/main/src/cgeo/geocaching/CacheMenuHandler.java +++ b/main/src/cgeo/geocaching/CacheMenuHandler.java @@ -5,7 +5,11 @@ import cgeo.geocaching.apps.cache.navi.NavigationAppFactory; import cgeo.geocaching.ui.AbstractUIFactory; import android.app.Activity; +import android.support.v4.app.Fragment; +import android.support.v4.view.MenuItemCompat; +import android.support.v7.widget.ShareActionProvider; import android.view.Menu; +import android.view.MenuInflater; import android.view.MenuItem; /** @@ -19,7 +23,7 @@ public class CacheMenuHandler extends AbstractUIFactory { * Methods to be implemented by the activity to react to the cache menu selections. * */ - protected interface ActivityInterface { + interface ActivityInterface { public void navigateTo(); public void showNavigationMenu(); @@ -29,8 +33,14 @@ public class CacheMenuHandler extends AbstractUIFactory { } public static boolean onMenuItemSelected(MenuItem item, CacheMenuHandler.ActivityInterface activityInterface, Geocache cache) { - assert activityInterface instanceof Activity; - final Activity activity = (Activity) activityInterface; + assert activityInterface instanceof Activity || activityInterface instanceof Fragment; + final Activity activity; + if (activityInterface instanceof Activity) { + activity = (Activity) activityInterface; + } else { + activity = ((Fragment)activityInterface).getActivity(); + } + switch (item.getItemId()) { case R.id.menu_default_navigation: activityInterface.navigateTo(); @@ -68,10 +78,22 @@ public class CacheMenuHandler extends AbstractUIFactory { menu.findItem(R.id.menu_show_in_browser).setVisible(cache.canOpenInBrowser()); menu.findItem(R.id.menu_default_navigation).setTitle(NavigationAppFactory.getDefaultNavigationApplication().getName()); + + MenuItem shareItem = menu.findItem(R.id.menu_share); + ShareActionProvider shareActionProvider = (ShareActionProvider) + MenuItemCompat.getActionProvider(shareItem); + if(shareActionProvider != null) { + shareActionProvider.setShareIntent(cache.getIntent()); + } + } - public static void addMenuItems(Activity activity, Menu menu, Geocache cache) { - activity.getMenuInflater().inflate(R.menu.cache_options, menu); + public static void addMenuItems(MenuInflater inflater, Menu menu, Geocache cache) { + inflater.inflate(R.menu.cache_options, menu); onPrepareOptionsMenu(menu, cache); } + + public static void addMenuItems(Activity activity, Menu menu, Geocache cache) { + addMenuItems(activity.getMenuInflater(), menu, cache); + } } diff --git a/main/src/cgeo/geocaching/CachePopup.java b/main/src/cgeo/geocaching/CachePopup.java index 543be22..9036d00 100644 --- a/main/src/cgeo/geocaching/CachePopup.java +++ b/main/src/cgeo/geocaching/CachePopup.java @@ -1,203 +1,63 @@ package cgeo.geocaching; -import cgeo.geocaching.activity.Progress; -import cgeo.geocaching.apps.cache.navi.NavigationAppFactory; -import cgeo.geocaching.geopoint.Geopoint; -import cgeo.geocaching.list.StoredList; -import cgeo.geocaching.network.Network; -import cgeo.geocaching.settings.Settings; -import cgeo.geocaching.ui.CacheDetailsCreator; -import cgeo.geocaching.utils.CancellableHandler; -import cgeo.geocaching.utils.Log; +import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.activity.ActivityMixin; import org.apache.commons.lang3.StringUtils; -import rx.Scheduler.Inner; -import rx.functions.Action1; -import rx.schedulers.Schedulers; import android.content.Context; import android.content.Intent; -import android.content.res.Configuration; -import android.os.Handler; -import android.os.Message; -import android.view.View; -import android.widget.LinearLayout; -import android.widget.TextView; +import android.os.Bundle; +import android.support.v4.app.DialogFragment; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentTransaction; +import android.view.Window; -public class CachePopup extends AbstractPopupActivity { - private final Progress progress = new Progress(); +public class CachePopup extends AbstractActivity { - private class StoreCacheHandler extends CancellableHandler { - private final int progressMessage; + protected String geocode = null; - public StoreCacheHandler(final int progressMessage) { - this.progressMessage = progressMessage; - } - - @Override - public void handleRegularMessage(Message msg) { - if (UPDATE_LOAD_PROGRESS_DETAIL == msg.what && msg.obj instanceof String) { - updateStatusMsg((String) msg.obj); - } else { - init(); - } - } - private void updateStatusMsg(final String msg) { - progress.setMessage(res.getString(progressMessage) - + "\n\n" - + msg); + void showDialog() { + // DialogFragment.show() will take care of adding the fragment + // in a transaction. We also want to remove any currently showing + // dialog, so make our own transaction and take care of that here. + FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); + Fragment prev = getSupportFragmentManager().findFragmentByTag("dialog"); + if (prev != null) { + ft.remove(prev); } - } + ft.addToBackStack(null); - private class DropCacheHandler extends Handler { - @Override - public void handleMessage(Message msg) { - CachePopup.this.finish(); - } - } - - public CachePopup() { - super(R.layout.popup); + // Create and show the dialog. + DialogFragment newFragment = CachePopupFragment.newInstance(geocode); + newFragment.show(ft, "dialog"); } @Override - public void showNavigationMenu() { - NavigationAppFactory.showNavigationMenu(this, cache, null, null); - } - - @Override - protected void init() { - super.init(); - try { - if (StringUtils.isNotBlank(cache.getName())) { - setTitle(cache.getName()); - } else { - setTitle(geocode); - } - - // actionbar icon - ((TextView) findViewById(R.id.actionbar_title)).setCompoundDrawablesWithIntrinsicBounds(getResources().getDrawable(cache.getType().markerId), null, null, null); + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + supportRequestWindowFeature(Window.FEATURE_NO_TITLE); + this.setTheme(ActivityMixin.getDialogTheme()); - details = new CacheDetailsCreator(this, (LinearLayout) findViewById(R.id.details_list)); - addCacheDetails(); - - // offline use - CacheDetailActivity.updateOfflineBox(findViewById(android.R.id.content), cache, res, new RefreshCacheClickListener(), new DropCacheClickListener(), new StoreCacheClickListener()); - - } catch (Exception e) { - Log.e("CachePopup.init", e); + final Bundle extras = getIntent().getExtras(); + if (extras != null) { + geocode = extras.getString(Intents.EXTRA_GEOCODE); } - // cache is loaded. remove progress-popup if any there - progress.dismiss(); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - - init(); - } - - private class StoreCacheClickListener implements View.OnClickListener { - @Override - public void onClick(View arg0) { - if (progress.isShowing()) { - showToast(res.getString(R.string.err_detail_still_working)); - return; - } - - if (Settings.getChooseList()) { - // let user select list to store cache in - new StoredList.UserInterface(CachePopup.this).promptForListSelection(R.string.list_title, - new Action1<Integer>() { - @Override - public void call(final Integer selectedListId) { - storeCache(selectedListId); - } - }, true, StoredList.TEMPORARY_LIST_ID); - } else { - storeCache(StoredList.TEMPORARY_LIST_ID); - } - } + if (StringUtils.isBlank(geocode)) { + showToast(res.getString(R.string.err_detail_cache_find)); - protected void storeCache(final int listId) { - final StoreCacheHandler storeCacheHandler = new StoreCacheHandler(R.string.cache_dialog_offline_save_message); - progress.show(CachePopup.this, res.getString(R.string.cache_dialog_offline_save_title), res.getString(R.string.cache_dialog_offline_save_message), true, storeCacheHandler.cancelMessage()); - Schedulers.io().schedule(new Action1<Inner>() { - @Override - public void call(final Inner inner) { - cache.store(listId, storeCacheHandler); - invalidateOptionsMenuCompatible(); - } - }); - } - } - - private class RefreshCacheClickListener implements View.OnClickListener { - @Override - public void onClick(View arg0) { - if (progress.isShowing()) { - showToast(res.getString(R.string.err_detail_still_working)); - return; - } - - if (!Network.isNetworkConnected(getApplicationContext())) { - showToast(getString(R.string.err_server)); - return; - } - - final StoreCacheHandler refreshCacheHandler = new StoreCacheHandler(R.string.cache_dialog_offline_save_message); - progress.show(CachePopup.this, res.getString(R.string.cache_dialog_refresh_title), res.getString(R.string.cache_dialog_refresh_message), true, refreshCacheHandler.cancelMessage()); - cache.refresh(cache.getListId(), refreshCacheHandler, Schedulers.io()); - } - } - - private class DropCacheClickListener implements View.OnClickListener { - @Override - public void onClick(View arg0) { - if (progress.isShowing()) { - showToast(res.getString(R.string.err_detail_still_working)); - return; - } - - final DropCacheHandler dropCacheHandler = new DropCacheHandler(); - progress.show(CachePopup.this, res.getString(R.string.cache_dialog_offline_drop_title), res.getString(R.string.cache_dialog_offline_drop_message), true, null); - cache.drop(dropCacheHandler, Schedulers.io()); - } - } - - @Override - public void navigateTo() { - NavigationAppFactory.startDefaultNavigationApplication(1, this, cache); - } - - /** - * Tries to navigate to the {@link Geocache} of this activity. - */ - @Override - protected void startDefaultNavigation2() { - if (cache == null || cache.getCoords() == null) { - showToast(res.getString(R.string.cache_coordinates_no)); + finish(); return; } - NavigationAppFactory.startDefaultNavigationApplication(2, this, cache); - finish(); + showDialog(); } - public static void startActivity(final Context context, final String geocode) { + public static void startActivity(Context context, String geocode) { final Intent popupIntent = new Intent(context, CachePopup.class); popupIntent.putExtra(Intents.EXTRA_GEOCODE, geocode); context.startActivity(popupIntent); } - - @Override - protected Geopoint getCoordinates() { - if (cache == null) { - return null; - } - return cache.getCoords(); - } } diff --git a/main/src/cgeo/geocaching/CachePopupFragment.java b/main/src/cgeo/geocaching/CachePopupFragment.java new file mode 100644 index 0000000..010d701 --- /dev/null +++ b/main/src/cgeo/geocaching/CachePopupFragment.java @@ -0,0 +1,229 @@ +package cgeo.geocaching; + +import cgeo.geocaching.activity.Progress; +import cgeo.geocaching.apps.cache.navi.NavigationAppFactory; +import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.list.StoredList; +import cgeo.geocaching.network.Network; +import cgeo.geocaching.settings.Settings; +import cgeo.geocaching.ui.CacheDetailsCreator; +import cgeo.geocaching.utils.CancellableHandler; +import cgeo.geocaching.utils.Log; + +import org.apache.commons.lang3.StringUtils; + +import rx.functions.Action0; +import rx.functions.Action1; +import rx.schedulers.Schedulers; + +import android.content.Context; +import android.content.Intent; +import android.content.res.Configuration; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.support.v4.app.DialogFragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.TextView; + +public class CachePopupFragment extends AbstractDialogFragment { + private final Progress progress = new Progress(); + + public static DialogFragment newInstance(String geocode) { + + Bundle args = new Bundle(); + args.putString(GEOCODE_ARG,geocode); + + DialogFragment f = new CachePopupFragment(); + f.setStyle(DialogFragment.STYLE_NO_TITLE,0); + f.setArguments(args); + + return f; + } + + private class StoreCacheHandler extends CancellableHandler { + private final int progressMessage; + + public StoreCacheHandler(final int progressMessage) { + this.progressMessage = progressMessage; + } + + @Override + public void handleRegularMessage(Message msg) { + if (UPDATE_LOAD_PROGRESS_DETAIL == msg.what && msg.obj instanceof String) { + updateStatusMsg((String) msg.obj); + } else { + init(); + } + } + + private void updateStatusMsg(final String msg) { + progress.setMessage(res.getString(progressMessage) + + "\n\n" + + msg); + } + } + + private class DropCacheHandler extends Handler { + @Override + public void handleMessage(Message msg) { + getActivity().finish(); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.popup, container, false); + initCustomActionBar(v); + return v; + } + + @Override + protected void init() { + super.init(); + + try { + if (StringUtils.isNotBlank(cache.getName())) { + setTitle(cache.getName()); + } else { + setTitle(geocode); + } + + ((TextView) getView().findViewById(R.id.actionbar_title)).setCompoundDrawablesWithIntrinsicBounds(getResources().getDrawable(cache.getType().markerId), null, null, null); + + details = new CacheDetailsCreator(getActivity(), (LinearLayout) getView().findViewById(R.id.details_list)); + + addCacheDetails(); + + // offline use + CacheDetailActivity.updateOfflineBox(getView(), cache, res, new RefreshCacheClickListener(), new DropCacheClickListener(), new StoreCacheClickListener()); + + } catch (Exception e) { + Log.e("CachePopupFragment.init", e); + } + + // cache is loaded. remove progress-popup if any there + progress.dismiss(); + } + + + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + init(); + } + + private class StoreCacheClickListener implements View.OnClickListener { + @Override + public void onClick(View arg0) { + if (progress.isShowing()) { + showToast(res.getString(R.string.err_detail_still_working)); + return; + } + + if (Settings.getChooseList()) { + // let user select list to store cache in + new StoredList.UserInterface(getActivity()).promptForListSelection(R.string.list_title, + new Action1<Integer>() { + @Override + public void call(final Integer selectedListId) { + storeCache(selectedListId); + } + }, true, StoredList.TEMPORARY_LIST_ID); + } else { + storeCache(StoredList.TEMPORARY_LIST_ID); + } + } + + protected void storeCache(final int listId) { + final StoreCacheHandler storeCacheHandler = new StoreCacheHandler(R.string.cache_dialog_offline_save_message); + progress.show(getActivity(), res.getString(R.string.cache_dialog_offline_save_title), res.getString(R.string.cache_dialog_offline_save_message), true, storeCacheHandler.cancelMessage()); + Schedulers.io().createWorker().schedule(new Action0() { + @Override + public void call() { + cache.store(listId, storeCacheHandler); + getActivity().supportInvalidateOptionsMenu(); + } + }); + } + } + + private class RefreshCacheClickListener implements View.OnClickListener { + @Override + public void onClick(View arg0) { + if (progress.isShowing()) { + showToast(res.getString(R.string.err_detail_still_working)); + return; + } + + if (!Network.isNetworkConnected(getActivity())) { + showToast(getString(R.string.err_server)); + return; + } + + final StoreCacheHandler refreshCacheHandler = new StoreCacheHandler(R.string.cache_dialog_offline_save_message); + progress.show(getActivity(), res.getString(R.string.cache_dialog_refresh_title), res.getString(R.string.cache_dialog_refresh_message), true, refreshCacheHandler.cancelMessage()); + cache.refresh(refreshCacheHandler, Schedulers.io()); + } + } + + private class DropCacheClickListener implements View.OnClickListener { + @Override + public void onClick(View arg0) { + if (progress.isShowing()) { + showToast(res.getString(R.string.err_detail_still_working)); + return; + } + + final DropCacheHandler dropCacheHandler = new DropCacheHandler(); + progress.show(getActivity(), res.getString(R.string.cache_dialog_offline_drop_title), res.getString(R.string.cache_dialog_offline_drop_message), true, null); + cache.drop(dropCacheHandler, Schedulers.io()); + } + } + + + @Override + public void navigateTo() { + NavigationAppFactory.startDefaultNavigationApplication(1, getActivity(), cache); + } + + @Override + public void showNavigationMenu() { + // TODO + } + + + /** + * Tries to navigate to the {@link cgeo.geocaching.Geocache} of this activity. + */ + @Override + protected void startDefaultNavigation2() { + if (cache == null || cache.getCoords() == null) { + showToast(res.getString(R.string.cache_coordinates_no)); + return; + } + NavigationAppFactory.startDefaultNavigationApplication(2, getActivity(), cache); + getActivity().finish(); + } + + public static void startActivity(final Context context, final String geocode) { + final Intent popupIntent = new Intent(context, CachePopup.class); + popupIntent.putExtra(Intents.EXTRA_GEOCODE, geocode); + context.startActivity(popupIntent); + } + + @Override + protected Geopoint getCoordinates() { + if (cache == null) { + return null; + } + return cache.getCoords(); + } + + +} diff --git a/main/src/cgeo/geocaching/CgeoApplication.java b/main/src/cgeo/geocaching/CgeoApplication.java index 09aee93..c3125ab 100644 --- a/main/src/cgeo/geocaching/CgeoApplication.java +++ b/main/src/cgeo/geocaching/CgeoApplication.java @@ -3,6 +3,7 @@ package cgeo.geocaching; import cgeo.geocaching.sensors.DirectionProvider; import cgeo.geocaching.sensors.GeoDataProvider; import cgeo.geocaching.sensors.IGeoData; +import cgeo.geocaching.settings.Settings; import cgeo.geocaching.utils.Log; import rx.Observable; @@ -10,6 +11,12 @@ import rx.functions.Action1; import rx.observables.ConnectableObservable; import android.app.Application; +import android.os.Environment; +import android.view.ViewConfiguration; + +import java.io.IOException; +import java.lang.Thread.UncaughtExceptionHandler; +import java.lang.reflect.Field; public class CgeoApplication extends Application { @@ -22,6 +29,31 @@ public class CgeoApplication extends Application { private volatile IGeoData currentGeo = null; private volatile float currentDirection = 0.0f; + static { + final UncaughtExceptionHandler defaultHandler = Thread.getDefaultUncaughtExceptionHandler(); + + Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() { + + @Override + public void uncaughtException(Thread thread, Throwable ex) { + Log.e("UncaughtException", ex); + Throwable exx = ex; + while (exx.getCause() != null) { + exx = exx.getCause(); + } + if (exx.getClass().equals(OutOfMemoryError.class)) { + try { + Log.e("OutOfMemory"); + android.os.Debug.dumpHprofData(Environment.getExternalStorageDirectory().getPath() + "/dump.hprof"); + } catch (IOException e) { + Log.e("Error writing dump", e); + } + } + defaultHandler.uncaughtException(thread, ex); + } + }); + } + public CgeoApplication() { setInstance(this); } @@ -35,6 +67,24 @@ public class CgeoApplication extends Application { } @Override + public void onCreate() { + if (Settings.isAlwaysShowOverlfowMenu()) { + try { + ViewConfiguration config = ViewConfiguration.get(this); + Field menuKeyField = ViewConfiguration.class.getDeclaredField("sHasPermanentMenuKey"); + + if (menuKeyField != null) { + menuKeyField.setAccessible(true); + menuKeyField.setBoolean(config, false); + } + } catch (Exception ex) { + // Ignore + } + } + } + + + @Override public void onLowMemory() { Log.i("Cleaning applications cache."); DataStore.removeAllFromCache(); diff --git a/main/src/cgeo/geocaching/CompassActivity.java b/main/src/cgeo/geocaching/CompassActivity.java index 36dcf27..531e07d 100644 --- a/main/src/cgeo/geocaching/CompassActivity.java +++ b/main/src/cgeo/geocaching/CompassActivity.java @@ -3,7 +3,7 @@ package cgeo.geocaching; import butterknife.ButterKnife; import butterknife.InjectView; -import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.activity.AbstractActionBarActivity; import cgeo.geocaching.enumerations.LoadFlags; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.Units; @@ -38,7 +38,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -public class CompassActivity extends AbstractActivity { +public class CompassActivity extends AbstractActionBarActivity { private static final int COORDINATES_OFFSET = 10; @@ -69,7 +69,7 @@ public class CompassActivity extends AbstractActivity { private boolean hasMagneticFieldSensor; @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState, R.layout.compass_activity); final SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); @@ -79,7 +79,7 @@ public class CompassActivity extends AbstractActivity { } // get parameters - Bundle extras = getIntent().getExtras(); + final Bundle extras = getIntent().getExtras(); if (extras != null) { final String geocode = extras.getString(EXTRAS_GEOCODE); if (StringUtils.isNotEmpty(geocode)) { @@ -98,7 +98,7 @@ public class CompassActivity extends AbstractActivity { } } } else { - Intent pointIntent = new Intent(this, NavigateAnyPointActivity.class); + final Intent pointIntent = new Intent(this, NavigateAnyPointActivity.class); startActivity(pointIntent); finish(); @@ -129,7 +129,7 @@ public class CompassActivity extends AbstractActivity { } @Override - public void onConfigurationChanged(Configuration newConfig) { + public void onConfigurationChanged(final Configuration newConfig) { super.onConfigurationChanged(newConfig); setContentView(R.layout.compass_activity); @@ -167,7 +167,7 @@ public class CompassActivity extends AbstractActivity { } @Override - public boolean onPrepareOptionsMenu(Menu menu) { + public boolean onPrepareOptionsMenu(final Menu menu) { super.onPrepareOptionsMenu(menu); menu.findItem(R.id.menu_switch_compass_gps).setTitle(res.getString(Settings.isUseCompass() ? R.string.use_gps : R.string.use_compass)); menu.findItem(R.id.menu_tts_start).setVisible(!SpeechService.isRunning()); @@ -176,34 +176,36 @@ public class CompassActivity extends AbstractActivity { } @Override - public boolean onOptionsItemSelected(MenuItem item) { - int id = item.getItemId(); + public boolean onOptionsItemSelected(final MenuItem item) { + final int id = item.getItemId(); switch (id) { case R.id.menu_map: CGeoMap.startActivityCoords(this, dstCoords, null, null); return true; case R.id.menu_switch_compass_gps: - boolean oldSetting = Settings.isUseCompass(); + final boolean oldSetting = Settings.isUseCompass(); Settings.setUseCompass(!oldSetting); invalidateOptionsMenuCompatible(); return true; case R.id.menu_edit_destination: - Intent pointIntent = new Intent(this, NavigateAnyPointActivity.class); + final Intent pointIntent = new Intent(this, NavigateAnyPointActivity.class); startActivity(pointIntent); finish(); return true; case R.id.menu_tts_start: SpeechService.startService(this, dstCoords); + invalidateOptionsMenuCompatible(); return true; case R.id.menu_tts_stop: SpeechService.stopService(this); + invalidateOptionsMenuCompatible(); return true; default: if (LoggingUI.onMenuItemSelected(item, this, cache)) { return true; } - int coordinatesIndex = id - COORDINATES_OFFSET; + final int coordinatesIndex = id - COORDINATES_OFFSET; if (coordinatesIndex >= 0 && coordinatesIndex < coordinates.size()) { final IWaypoint coordinate = coordinates.get(coordinatesIndex); title = coordinate.getName(); @@ -217,7 +219,7 @@ public class CompassActivity extends AbstractActivity { return true; } } - return false; + return super.onOptionsItemSelected(item); } private void setTitle() { @@ -255,7 +257,7 @@ public class CompassActivity extends AbstractActivity { headingView.setText(Math.round(cacheHeading) + "°"); } - private GeoDirHandler geoDirHandler = new GeoDirHandler() { + private final GeoDirHandler geoDirHandler = new GeoDirHandler() { @Override public void updateGeoDir(final IGeoData geo, final float dir) { try { @@ -283,7 +285,7 @@ public class CompassActivity extends AbstractActivity { } updateNorthHeading(DirectionProvider.getDirectionNow(dir)); - } catch (RuntimeException e) { + } catch (final RuntimeException e) { Log.w("Failed to LocationUpdater location."); } } @@ -299,7 +301,7 @@ public class CompassActivity extends AbstractActivity { final String info) { coordinates.clear(); if (coordinatesWithType != null) { - for (IWaypoint coordinate : coordinatesWithType) { + for (final IWaypoint coordinate : coordinatesWithType) { if (coordinate != null) { coordinates.add(coordinate); } diff --git a/main/src/cgeo/geocaching/CreateShortcutActivity.java b/main/src/cgeo/geocaching/CreateShortcutActivity.java index b6ea4f6..ffcf81b 100644 --- a/main/src/cgeo/geocaching/CreateShortcutActivity.java +++ b/main/src/cgeo/geocaching/CreateShortcutActivity.java @@ -1,6 +1,6 @@ package cgeo.geocaching; -import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.activity.AbstractActionBarActivity; import cgeo.geocaching.list.PseudoList; import cgeo.geocaching.list.StoredList; @@ -10,7 +10,7 @@ import android.content.Intent; import android.content.Intent.ShortcutIconResource; import android.os.Bundle; -public class CreateShortcutActivity extends AbstractActivity { +public class CreateShortcutActivity extends AbstractActionBarActivity { @Override public void onCreate(Bundle savedInstanceState) { @@ -27,7 +27,7 @@ public class CreateShortcutActivity extends AbstractActivity { @Override public void call(final Integer listId) { - final Intent shortcut = createShortcut(listId.intValue()); + final Intent shortcut = createShortcut(listId); setResult(RESULT_OK, shortcut); // finish activity to return the shortcut diff --git a/main/src/cgeo/geocaching/DataStore.java b/main/src/cgeo/geocaching/DataStore.java index 32a4b64..6b0f641 100644 --- a/main/src/cgeo/geocaching/DataStore.java +++ b/main/src/cgeo/geocaching/DataStore.java @@ -16,6 +16,7 @@ import cgeo.geocaching.geopoint.Viewport; import cgeo.geocaching.list.AbstractList; import cgeo.geocaching.list.PseudoList; import cgeo.geocaching.list.StoredList; +import cgeo.geocaching.search.SearchSuggestionCursor; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.ui.dialog.Dialogs; import cgeo.geocaching.utils.FileUtils; @@ -36,7 +37,6 @@ import rx.util.async.Async; import android.app.Activity; import android.app.ProgressDialog; -import android.app.SearchManager; import android.content.ContentValues; import android.content.Context; import android.content.ContextWrapper; @@ -49,7 +49,6 @@ 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; @@ -57,6 +56,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.Date; import java.util.EnumSet; import java.util.HashMap; @@ -325,7 +325,7 @@ public class DataStore { final DbHelper dbHelper = new DbHelper(new DBContext(CgeoApplication.getInstance())); try { database = dbHelper.getWritableDatabase(); - } catch (Exception e) { + } catch (final Exception e) { Log.e("DataStore.init: unable to open database for R/W", e); recreateDatabase(dbHelper); } @@ -347,7 +347,7 @@ public class DataStore { } try { database = dbHelper.getWritableDatabase(); - } catch (Exception f) { + } catch (final Exception f) { Log.e("DataStore.init: unable to recreate database and open it for R/W", f); } } @@ -464,7 +464,7 @@ public class DataStore { private static class DBContext extends ContextWrapper { - public DBContext(Context base) { + public DBContext(final Context base) { super(base); } @@ -473,8 +473,8 @@ public class DataStore { * causes issues on other devices too. */ @Override - public SQLiteDatabase openOrCreateDatabase(String name, int mode, - CursorFactory factory) { + public SQLiteDatabase openOrCreateDatabase(final String name, final int mode, + final CursorFactory factory) { final File file = new File(name); FileUtils.mkdirs(file.getParentFile()); return SQLiteDatabase.openOrCreateDatabase(file, factory); @@ -486,12 +486,12 @@ public class DataStore { private static boolean firstRun = true; - DbHelper(Context context) { + DbHelper(final Context context) { super(context, databasePath().getPath(), null, dbVersion); } @Override - public void onCreate(SQLiteDatabase db) { + public void onCreate(final SQLiteDatabase db) { newlyCreatedDatabase = true; db.execSQL(dbCreateCaches); db.execSQL(dbCreateLists); @@ -528,7 +528,7 @@ public class DataStore { } @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) { Log.i("Upgrade database from ver. " + oldVersion + " to ver. " + newVersion + ": start"); try { @@ -553,7 +553,7 @@ public class DataStore { db.execSQL(dbCreateSearchDestinationHistory); Log.i("Added table " + dbTableSearchDestionationHistory + "."); - } catch (Exception e) { + } catch (final Exception e) { Log.e("Failed to upgrade to ver. 52", e); } } @@ -563,7 +563,7 @@ public class DataStore { db.execSQL("alter table " + dbTableCaches + " add column onWatchlist integer"); Log.i("Column onWatchlist added to " + dbTableCaches + "."); - } catch (Exception e) { + } catch (final Exception e) { Log.e("Failed to upgrade to ver. 53", e); } } @@ -571,7 +571,7 @@ public class DataStore { if (oldVersion < 54) { // update to 54 try { db.execSQL(dbCreateLogImages); - } catch (Exception e) { + } catch (final Exception e) { Log.e("Failed to upgrade to ver. 54", e); } @@ -580,7 +580,7 @@ public class DataStore { if (oldVersion < 55) { // update to 55 try { db.execSQL("alter table " + dbTableCaches + " add column personal_note text"); - } catch (Exception e) { + } catch (final Exception e) { Log.e("Failed to upgrade to ver. 55", e); } } @@ -592,7 +592,7 @@ public class DataStore { db.execSQL("update " + dbTableAttributes + " set attribute = " + "lower(attribute) where attribute like \"%_yes\" " + "or attribute like \"%_no\""); - } catch (Exception e) { + } catch (final Exception e) { Log.e("Failed to upgrade to ver. 56", e); } } @@ -607,7 +607,7 @@ public class DataStore { db.execSQL("drop index in_e"); db.execSQL("drop index in_f"); createIndices(db); - } catch (Exception e) { + } catch (final Exception e) { Log.e("Failed to upgrade to ver. 57", e); } } @@ -696,7 +696,7 @@ public class DataStore { db.setTransactionSuccessful(); Log.i("Removed latitude_string and longitude_string columns"); - } catch (Exception e) { + } catch (final Exception e) { Log.e("Failed to upgrade to ver. 58", e); } finally { db.endTransaction(); @@ -708,7 +708,7 @@ public class DataStore { // Add new indices and remove obsolete cache files createIndices(db); removeObsoleteCacheDirectories(db); - } catch (Exception e) { + } catch (final Exception e) { Log.e("Failed to upgrade to ver. 59", e); } } @@ -716,7 +716,7 @@ public class DataStore { if (oldVersion < 60) { try { removeSecEmptyDirs(); - } catch (Exception e) { + } catch (final Exception e) { Log.e("Failed to upgrade to ver. 60", e); } } @@ -724,7 +724,7 @@ public class DataStore { try { db.execSQL("alter table " + dbTableLogs + " add column friend integer"); db.execSQL("alter table " + dbTableCaches + " add column coordsChanged integer default 0"); - } catch (Exception e) { + } catch (final Exception e) { Log.e("Failed to upgrade to ver. 61", e); } @@ -735,7 +735,7 @@ public class DataStore { db.execSQL("alter table " + dbTableCaches + " add column finalDefined integer default 0"); db.execSQL("alter table " + dbTableWaypoints + " add column own integer default 0"); db.execSQL("update " + dbTableWaypoints + " set own = 1 where type = 'own'"); - } catch (Exception e) { + } catch (final Exception e) { Log.e("Failed to upgrade to ver. 62", e); } @@ -743,7 +743,7 @@ public class DataStore { if (oldVersion < 63) { try { removeDoubleUnderscoreMapFiles(); - } catch (Exception e) { + } catch (final Exception e) { Log.e("Failed to upgrade to ver. 63", e); } @@ -755,7 +755,7 @@ public class DataStore { // rather than symbolic ones because the fix must be applied with the values at the time // of the problem. The problem was introduced in release 2012.06.01. db.execSQL("update " + dbTableCaches + " set reason=1 where reason=2"); - } catch (Exception e) { + } catch (final Exception e) { Log.e("Failed to upgrade to ver. 64", e); } } @@ -764,7 +764,7 @@ public class DataStore { try { // Set all waypoints where name is Original coordinates to type ORIGINAL db.execSQL("update " + dbTableWaypoints + " set type='original', own=0 where name='Original Coordinates'"); - } catch (Exception e) { + } catch (final Exception e) { Log.e("Failed to upgrade to ver. 65:", e); } } @@ -772,7 +772,7 @@ public class DataStore { if (oldVersion < 66) { try { db.execSQL("alter table " + dbTableWaypoints + " add column visited integer default 0"); - } catch (Exception e) { + } catch (final Exception e) { Log.e("Failed to upgrade to ver. 66", e); } @@ -782,7 +782,7 @@ public class DataStore { try { db.execSQL("update " + dbTableAttributes + " set attribute = 'easy_climbing_yes' where geocode like 'OC%' and attribute = 'climbing_yes'"); db.execSQL("update " + dbTableAttributes + " set attribute = 'easy_climbing_no' where geocode like 'OC%' and attribute = 'climbing_no'"); - } catch (Exception e) { + } catch (final Exception e) { Log.e("Failed to upgrade to ver. 67", e); } @@ -791,7 +791,7 @@ public class DataStore { if (oldVersion < 68) { try { db.execSQL("alter table " + dbTableCaches + " add column logPasswordRequired integer default 0"); - } catch (Exception e) { + } catch (final Exception e) { Log.e("Failed to upgrade to ver. 68", e); } @@ -838,7 +838,7 @@ public class DataStore { if (ArrayUtils.isNotEmpty(geocodeDirs)) { final FilenameFilter filter = new FilenameFilter() { @Override - public boolean accept(File dir, String filename) { + public boolean accept(final File dir, final String filename) { return filename.startsWith("map_") && filename.contains("__"); } }; @@ -914,7 +914,7 @@ public class DataStore { } } - private static void dropDatabase(SQLiteDatabase db) { + private static void dropDatabase(final SQLiteDatabase db) { db.execSQL("drop table if exists " + dbTableCaches); db.execSQL("drop table if exists " + dbTableAttributes); db.execSQL("drop table if exists " + dbTableWaypoints); @@ -925,7 +925,7 @@ public class DataStore { db.execSQL("drop table if exists " + dbTableTrackables); } - public static boolean isThere(String geocode, String guid, boolean detailed, boolean checkTime) { + public static boolean isThere(final String geocode, final String guid, final boolean detailed, final boolean checkTime) { init(); long dataUpdated = 0; @@ -990,7 +990,7 @@ public class DataStore { } /** is cache stored in one of the lists (not only temporary) */ - public static boolean isOffline(String geocode, String guid) { + public static boolean isOffline(final String geocode, final String guid) { if (StringUtils.isBlank(geocode) && StringUtils.isBlank(guid)) { return false; } @@ -1011,16 +1011,16 @@ public class DataStore { listId.bindString(1, value); return listId.simpleQueryForLong() != StoredList.TEMPORARY_LIST_ID; } - } catch (SQLiteDoneException e) { + } catch (final SQLiteDoneException e) { // Do nothing, it only means we have no information on the cache - } catch (Exception e) { + } catch (final Exception e) { Log.e("DataStore.isOffline", e); } return false; } - public static String getGeocodeForGuid(String guid) { + public static String getGeocodeForGuid(final String guid) { if (StringUtils.isBlank(guid)) { return null; } @@ -1032,16 +1032,16 @@ public class DataStore { description.bindString(1, guid); return description.simpleQueryForString(); } - } catch (SQLiteDoneException e) { + } catch (final SQLiteDoneException e) { // Do nothing, it only means we have no information on the cache - } catch (Exception e) { + } catch (final Exception e) { Log.e("DataStore.getGeocodeForGuid", e); } return null; } - public static String getCacheidForGeocode(String geocode) { + public static String getCacheidForGeocode(final String geocode) { if (StringUtils.isBlank(geocode)) { return null; } @@ -1053,9 +1053,9 @@ public class DataStore { description.bindString(1, geocode); return description.simpleQueryForString(); } - } catch (SQLiteDoneException e) { + } catch (final SQLiteDoneException e) { // Do nothing, it only means we have no information on the cache - } catch (Exception e) { + } catch (final Exception e) { Log.e("DataStore.getCacheidForGeocode", e); } @@ -1070,7 +1070,7 @@ public class DataStore { * @param saveFlags * */ - public static void saveCache(Geocache cache, EnumSet<LoadFlags.SaveFlag> saveFlags) { + public static void saveCache(final Geocache cache, final EnumSet<LoadFlags.SaveFlag> saveFlags) { saveCaches(Collections.singletonList(cache), saveFlags); } @@ -1082,7 +1082,7 @@ public class DataStore { * @param saveFlags * */ - public static void saveCaches(Collection<Geocache> caches, EnumSet<LoadFlags.SaveFlag> saveFlags) { + public static void saveCaches(final Collection<Geocache> caches, final EnumSet<LoadFlags.SaveFlag> saveFlags) { if (CollectionUtils.isEmpty(caches)) { return; } @@ -1090,7 +1090,7 @@ public class DataStore { final HashMap<String, Geocache> existingCaches = new HashMap<String, Geocache>(); // first check which caches are in the memory cache - for (Geocache cache : caches) { + for (final Geocache cache : caches) { final String geocode = cache.getGeocode(); final Geocache cacheFromCache = cacheCache.getCacheFromCache(geocode); if (cacheFromCache == null) { @@ -1102,7 +1102,7 @@ public class DataStore { } // then load all remaining caches from the database in one step - for (Geocache cacheFromDatabase : loadCaches(cachesFromDatabase, LoadFlags.LOAD_ALL_DB_ONLY)) { + for (final Geocache cacheFromDatabase : loadCaches(cachesFromDatabase, LoadFlags.LOAD_ALL_DB_ONLY)) { existingCaches.put(cacheFromDatabase.getGeocode(), cacheFromDatabase); } @@ -1113,7 +1113,7 @@ public class DataStore { // (as it may be more recent than the version in the database), or if the // version coming from the database is different than the version we are entering // into the cache (that includes absence from the database). - for (Geocache cache : caches) { + for (final Geocache cache : caches) { final String geocode = cache.getGeocode(); final Geocache existingCache = existingCaches.get(geocode); final boolean dbUpdateRequired = !cache.gatherMissingFrom(existingCache) || cacheCache.getCacheFromCache(geocode) != null; @@ -1127,7 +1127,7 @@ public class DataStore { } } - for (Geocache geocache : toBeStored) { + for (final Geocache geocache : toBeStored) { storeIntoDatabase(geocache); } } @@ -1137,7 +1137,7 @@ public class DataStore { cacheCache.putCacheInCache(cache); Log.d("Saving " + cache.toString() + " (" + cache.getListId() + ") to DB"); - ContentValues values = new ContentValues(); + final ContentValues values = new ContentValues(); if (cache.getUpdated() == 0) { values.put("updated", System.currentTimeMillis()); @@ -1200,7 +1200,7 @@ public class DataStore { saveLogCountsWithoutTransaction(cache); saveInventoryWithoutTransaction(cache.getGeocode(), cache.getInventory()); - int rows = database.update(dbTableCaches, values, "geocode = ?", new String[] { cache.getGeocode() }); + final int rows = database.update(dbTableCaches, values, "geocode = ?", new String[] { cache.getGeocode() }); if (rows == 0) { // cache is not in the DB, insert it /* long id = */ @@ -1208,7 +1208,7 @@ public class DataStore { } database.setTransactionSuccessful(); return true; - } catch (Exception e) { + } catch (final Exception e) { Log.e("SaveCache", e); } finally { database.endTransaction(); @@ -1228,7 +1228,7 @@ public class DataStore { if (attributes.isEmpty()) { return; } - SQLiteStatement statement = PreparedStatements.getInsertAttribute(); + final SQLiteStatement statement = PreparedStatements.getInsertAttribute(); final long timestamp = System.currentTimeMillis(); for (final String attribute : attributes) { statement.bindString(1, geocode); @@ -1251,10 +1251,10 @@ public class DataStore { database.beginTransaction(); try { - SQLiteStatement insertDestination = PreparedStatements.getInsertSearchDestination(destination); + final SQLiteStatement insertDestination = PreparedStatements.getInsertSearchDestination(destination); insertDestination.executeInsert(); database.setTransactionSuccessful(); - } catch (Exception e) { + } catch (final Exception e) { Log.e("Updating searchedDestinations db failed", e); } finally { database.endTransaction(); @@ -1269,7 +1269,7 @@ public class DataStore { saveWaypointsWithoutTransaction(cache); database.setTransactionSuccessful(); return true; - } catch (Exception e) { + } catch (final Exception e) { Log.e("saveWaypoints", e); } finally { database.endTransaction(); @@ -1278,14 +1278,14 @@ public class DataStore { } private static void saveWaypointsWithoutTransaction(final Geocache cache) { - String geocode = cache.getGeocode(); + final String geocode = cache.getGeocode(); - List<Waypoint> waypoints = cache.getWaypoints(); + final List<Waypoint> waypoints = cache.getWaypoints(); if (CollectionUtils.isNotEmpty(waypoints)) { final ArrayList<String> currentWaypointIds = new ArrayList<String>(); - ContentValues values = new ContentValues(); - long timeStamp = System.currentTimeMillis(); - for (Waypoint oneWaypoint : waypoints) { + final ContentValues values = new ContentValues(); + final long timeStamp = System.currentTimeMillis(); + for (final Waypoint oneWaypoint : waypoints) { values.clear(); values.put("geocode", geocode); @@ -1356,7 +1356,7 @@ public class DataStore { return new Geopoint(cursor.getDouble(indexLat), cursor.getDouble(indexLon)); } - private static boolean saveWaypointInternal(int id, String geocode, Waypoint waypoint) { + private static boolean saveWaypointInternal(final int id, final String geocode, final Waypoint waypoint) { if ((StringUtils.isBlank(geocode) && id <= 0) || waypoint == null) { return false; } @@ -1366,7 +1366,7 @@ public class DataStore { database.beginTransaction(); boolean ok = false; try { - ContentValues values = new ContentValues(); + final ContentValues values = new ContentValues(); values.put("geocode", geocode); values.put("updated", System.currentTimeMillis()); values.put("type", waypoint.getWaypointType() != null ? waypoint.getWaypointType().id : null); @@ -1394,7 +1394,7 @@ public class DataStore { return ok; } - public static boolean deleteWaypoint(int id) { + public static boolean deleteWaypoint(final int id) { if (id == 0) { return false; } @@ -1405,14 +1405,14 @@ public class DataStore { } private static void saveSpoilersWithoutTransaction(final Geocache cache) { - String geocode = cache.getGeocode(); + final String geocode = cache.getGeocode(); database.delete(dbTableSpoilers, "geocode = ?", new String[]{geocode}); - List<Image> spoilers = cache.getSpoilers(); + final List<Image> spoilers = cache.getSpoilers(); if (CollectionUtils.isNotEmpty(spoilers)) { - SQLiteStatement insertSpoiler = PreparedStatements.getInsertSpoiler(); + final SQLiteStatement insertSpoiler = PreparedStatements.getInsertSpoiler(); final long timestamp = System.currentTimeMillis(); - for (Image spoiler : spoilers) { + for (final Image spoiler : spoilers) { insertSpoiler.bindString(1, geocode); insertSpoiler.bindLong(2, timestamp); insertSpoiler.bindString(3, spoiler.getUrl()); @@ -1429,17 +1429,13 @@ public class DataStore { } } - public static void saveLogsWithoutTransaction(final String geocode, final List<LogEntry> logs) { + public static void saveLogsWithoutTransaction(final String geocode, final Iterable<LogEntry> logs) { // TODO delete logimages referring these logs database.delete(dbTableLogs, "geocode = ?", new String[]{geocode}); - if (logs.isEmpty()) { - return; - } - - SQLiteStatement insertLog = PreparedStatements.getInsertLog(); + final SQLiteStatement insertLog = PreparedStatements.getInsertLog(); final long timestamp = System.currentTimeMillis(); - for (LogEntry log : logs) { + for (final LogEntry log : logs) { insertLog.bindString(1, geocode); insertLog.bindLong(2, timestamp); insertLog.bindLong(3, log.type.id); @@ -1448,10 +1444,10 @@ public class DataStore { insertLog.bindLong(6, log.date); insertLog.bindLong(7, log.found); insertLog.bindLong(8, log.friend ? 1 : 0); - long logId = insertLog.executeInsert(); + final long logId = insertLog.executeInsert(); if (log.hasLogImages()) { - SQLiteStatement insertImage = PreparedStatements.getInsertLogImage(); - for (Image img : log.getLogImages()) { + final SQLiteStatement insertImage = PreparedStatements.getInsertLogImage(); + for (final Image img : log.getLogImages()) { insertImage.bindLong(1, logId); insertImage.bindString(2, img.getTitle()); insertImage.bindString(3, img.getUrl()); @@ -1462,15 +1458,15 @@ public class DataStore { } private static void saveLogCountsWithoutTransaction(final Geocache cache) { - String geocode = cache.getGeocode(); + final String geocode = cache.getGeocode(); database.delete(dbTableLogCount, "geocode = ?", new String[]{geocode}); - Map<LogType, Integer> logCounts = cache.getLogCounts(); + final Map<LogType, Integer> logCounts = cache.getLogCounts(); if (MapUtils.isNotEmpty(logCounts)) { - Set<Entry<LogType, Integer>> logCountsItems = logCounts.entrySet(); - SQLiteStatement insertLogCounts = PreparedStatements.getInsertLogCounts(); + final Set<Entry<LogType, Integer>> logCountsItems = logCounts.entrySet(); + final SQLiteStatement insertLogCounts = PreparedStatements.getInsertLogCounts(); final long timestamp = System.currentTimeMillis(); - for (Entry<LogType, Integer> pair : logCountsItems) { + for (final Entry<LogType, Integer> pair : logCountsItems) { insertLogCounts.bindString(1, geocode); insertLogCounts.bindLong(2, timestamp); insertLogCounts.bindLong(3, pair.getKey().id); @@ -1499,9 +1495,9 @@ public class DataStore { } if (CollectionUtils.isNotEmpty(trackables)) { - ContentValues values = new ContentValues(); - long timeStamp = System.currentTimeMillis(); - for (Trackable trackable : trackables) { + final ContentValues values = new ContentValues(); + final long timeStamp = System.currentTimeMillis(); + for (final Trackable trackable : trackables) { final String tbCode = trackable.getGeocode(); if (StringUtils.isNotBlank(tbCode)) { database.delete(dbTableTrackables, "tbcode = ?", new String[] { tbCode }); @@ -1566,12 +1562,12 @@ public class DataStore { return new HashSet<Geocache>(); } - Set<Geocache> result = new HashSet<Geocache>(); - Set<String> remaining = new HashSet<String>(geocodes); + final Set<Geocache> result = new HashSet<Geocache>(); + final Set<String> remaining = new HashSet<String>(geocodes); if (loadFlags.contains(LoadFlag.LOAD_CACHE_BEFORE)) { - for (String geocode : new HashSet<String>(remaining)) { - Geocache cache = cacheCache.getCacheFromCache(geocode); + for (final String geocode : new HashSet<String>(remaining)) { + final Geocache cache = cacheCache.getCacheFromCache(geocode); if (cache != null) { result.add(cache); remaining.remove(cache.getGeocode()); @@ -1595,8 +1591,8 @@ public class DataStore { } if (loadFlags.contains(LoadFlag.LOAD_CACHE_AFTER)) { - for (String geocode : new HashSet<String>(remaining)) { - Geocache cache = cacheCache.getCacheFromCache(geocode); + for (final String geocode : new HashSet<String>(remaining)) { + final Geocache cache = cacheCache.getCacheFromCache(geocode); if (cache != null) { result.add(cache); remaining.remove(cache.getGeocode()); @@ -1638,13 +1634,13 @@ public class DataStore { query.append(" WHERE ").append(dbTableCaches).append('.'); query.append(DataStore.whereGeocodeIn(geocodes)); - Cursor cursor = database.rawQuery(query.toString(), null); + final Cursor cursor = database.rawQuery(query.toString(), null); try { final Set<Geocache> caches = new HashSet<Geocache>(); int logIndex = -1; while (cursor.moveToNext()) { - Geocache cache = DataStore.createCacheFromDatabaseContent(cursor); + final Geocache cache = DataStore.createCacheFromDatabaseContent(cursor); if (loadFlags.contains(LoadFlag.LOAD_ATTRIBUTES)) { cache.setAttributes(loadAttributes(cache.getGeocode())); @@ -1718,8 +1714,8 @@ public class DataStore { * @param cursor * @return Cache from DB */ - private static Geocache createCacheFromDatabaseContent(Cursor cursor) { - Geocache cache = new Geocache(); + private static Geocache createCacheFromDatabaseContent(final Cursor cursor) { + final Geocache cache = new Geocache(); cache.setUpdated(cursor.getLong(0)); cache.setListId(cursor.getInt(1)); @@ -1733,7 +1729,7 @@ public class DataStore { cache.setName(cursor.getString(9)); cache.setOwnerDisplayName(cursor.getString(10)); cache.setOwnerUserId(cursor.getString(11)); - long dateValue = cursor.getLong(12); + final long dateValue = cursor.getLong(12); if (dateValue != 0) { cache.setHidden(new Date(dateValue)); } @@ -1779,7 +1775,7 @@ public class DataStore { return cache; } - public static List<String> loadAttributes(String geocode) { + public static List<String> loadAttributes(final String geocode) { if (StringUtils.isBlank(geocode)) { return null; } @@ -1796,7 +1792,7 @@ public class DataStore { GET_STRING_0); } - public static Waypoint loadWaypoint(int id) { + public static Waypoint loadWaypoint(final int id) { if (id == 0) { return null; } @@ -1915,7 +1911,7 @@ public class DataStore { database.delete(dbTableSearchDestionationHistory, null, null); database.setTransactionSuccessful(); return true; - } catch (Exception e) { + } catch (final Exception e) { Log.e("Unable to clear searched destinations", e); } finally { database.endTransaction(); @@ -1929,8 +1925,8 @@ public class DataStore { * @return an immutable, non null list of logs */ @NonNull - public static List<LogEntry> loadLogs(String geocode) { - List<LogEntry> logs = new ArrayList<LogEntry>(); + public static List<LogEntry> loadLogs(final String geocode) { + final List<LogEntry> logs = new ArrayList<LogEntry>(); if (StringUtils.isBlank(geocode)) { return logs; @@ -1967,7 +1963,7 @@ public class DataStore { return Collections.unmodifiableList(logs); } - public static Map<LogType, Integer> loadLogCounts(String geocode) { + public static Map<LogType, Integer> loadLogCounts(final String geocode) { if (StringUtils.isBlank(geocode)) { return null; } @@ -1995,7 +1991,7 @@ public class DataStore { return logCounts; } - private static List<Trackable> loadInventory(String geocode) { + private static List<Trackable> loadInventory(final String geocode) { if (StringUtils.isBlank(geocode)) { return null; } @@ -2056,7 +2052,7 @@ public class DataStore { final String released = cursor.getString(cursor.getColumnIndex("released")); if (released != null) { try { - long releaseMilliSeconds = Long.parseLong(released); + final long releaseMilliSeconds = Long.parseLong(released); trackable.setReleased(new Date(releaseMilliSeconds)); } catch (final NumberFormatException e) { Log.e("createTrackableFromDatabaseContent", e); @@ -2085,7 +2081,7 @@ public class DataStore { init(); try { - StringBuilder sql = new StringBuilder("select count(_id) from " + dbTableCaches + " where detailed = 1"); + final StringBuilder sql = new StringBuilder("select count(_id) from " + dbTableCaches + " where detailed = 1"); String typeKey; int reasonIndex; if (cacheType != CacheType.ALL) { @@ -2106,9 +2102,9 @@ public class DataStore { listKey = "list"; } - String key = "CountCaches_" + typeKey + "_" + listKey; + final String key = "CountCaches_" + typeKey + "_" + listKey; - SQLiteStatement compiledStmnt = PreparedStatements.getStatement(key, sql.toString()); + final SQLiteStatement compiledStmnt = PreparedStatements.getStatement(key, sql.toString()); if (cacheType != CacheType.ALL) { compiledStmnt.bindString(1, cacheType.id); } @@ -2116,7 +2112,7 @@ public class DataStore { compiledStmnt.bindLong(reasonIndex, list); } return (int) compiledStmnt.simpleQueryForLong(); - } catch (Exception e) { + } catch (final Exception e) { Log.e("DataStore.loadAllStoredCachesCount", e); } @@ -2128,7 +2124,7 @@ public class DataStore { try { return (int) PreparedStatements.getCountHistoryCaches().simpleQueryForLong(); - } catch (Exception e) { + } catch (final Exception e) { Log.e("DataStore.getAllHistoricCachesCount", e); } @@ -2239,7 +2235,7 @@ public class DataStore { null, new HashSet<String>(), GET_STRING_0); - } catch (Exception e) { + } catch (final Exception e) { Log.e("DataStore.loadBatchOfHistoricGeocodes", e); } @@ -2399,7 +2395,7 @@ public class DataStore { cacheCache.removeAllFromCache(); } - public static void removeCache(final String geocode, EnumSet<LoadFlags.RemoveFlag> removeFlags) { + public static void removeCache(final String geocode, final EnumSet<LoadFlags.RemoveFlag> removeFlags) { removeCaches(Collections.singleton(geocode), removeFlags); } @@ -2409,7 +2405,7 @@ public class DataStore { * @param geocodes * list of geocodes to drop from cache */ - public static void removeCaches(final Set<String> geocodes, EnumSet<LoadFlags.RemoveFlag> removeFlags) { + public static void removeCaches(final Set<String> geocodes, final EnumSet<LoadFlags.RemoveFlag> removeFlags) { if (CollectionUtils.isEmpty(geocodes)) { return; } @@ -2457,7 +2453,7 @@ public class DataStore { } } - public static boolean saveLogOffline(String geocode, Date date, LogType type, String log) { + public static boolean saveLogOffline(final String geocode, final Date date, final LogType type, final String log) { if (StringUtils.isBlank(geocode)) { Log.e("DataStore.saveLogOffline: cannot log a blank geocode"); return false; @@ -2484,7 +2480,7 @@ public class DataStore { return id != -1; } - public static LogEntry loadLogOffline(String geocode) { + public static LogEntry loadLogOffline(final String geocode) { if (StringUtils.isBlank(geocode)) { return null; } @@ -2515,7 +2511,7 @@ public class DataStore { return log; } - public static void clearLogOffline(String geocode) { + public static void clearLogOffline(final String geocode) { if (StringUtils.isBlank(geocode)) { return; } @@ -2525,15 +2521,15 @@ public class DataStore { database.delete(dbTableLogsOffline, "geocode = ?", new String[]{geocode}); } - public static void clearLogsOffline(List<Geocache> caches) { + public static void clearLogsOffline(final List<Geocache> caches) { if (CollectionUtils.isEmpty(caches)) { return; } init(); - Set<String> geocodes = new HashSet<String>(caches.size()); - for (Geocache cache : caches) { + final Set<String> geocodes = new HashSet<String>(caches.size()); + for (final Geocache cache : caches) { geocodes.add(cache.getGeocode()); cache.setLogOffline(false); } @@ -2553,14 +2549,14 @@ public class DataStore { logCount.bindString(1, geocode); return logCount.simpleQueryForLong() > 0; } - } catch (Exception e) { + } catch (final Exception e) { Log.e("DataStore.hasLogOffline", e); } return false; } - private static void setVisitDate(List<String> geocodes, long visitedDate) { + private static void setVisitDate(final List<String> geocodes, final long visitedDate) { if (geocodes.isEmpty()) { return; } @@ -2569,9 +2565,9 @@ public class DataStore { database.beginTransaction(); try { - SQLiteStatement setVisit = PreparedStatements.getUpdateVisitDate(); + final SQLiteStatement setVisit = PreparedStatements.getUpdateVisitDate(); - for (String geocode : geocodes) { + for (final String geocode : geocodes) { setVisit.bindLong(1, visitedDate); setVisit.bindString(2, geocode); setVisit.execute(); @@ -2617,10 +2613,10 @@ public class DataStore { }); } - public static StoredList getList(int id) { + public static StoredList getList(final int id) { init(); if (id >= customListIdOffset) { - Cursor cursor = database.query( + final Cursor cursor = database.query( dbTableLists, new String[]{"_id", "title"}, "_id = ? ", @@ -2628,13 +2624,13 @@ public class DataStore { null, null, null); - ArrayList<StoredList> lists = getListsFromCursor(cursor); + final ArrayList<StoredList> lists = getListsFromCursor(cursor); if (!lists.isEmpty()) { return lists.get(0); } } - Resources res = CgeoApplication.getInstance().getResources(); + final Resources res = CgeoApplication.getInstance().getResources(); if (id == PseudoList.ALL_LIST.id) { return new StoredList(PseudoList.ALL_LIST.id, res.getString(R.string.list_all_lists), getAllCachesCount()); } @@ -2658,7 +2654,7 @@ public class DataStore { * Name * @return new listId */ - public static int createList(String name) { + public static int createList(final String name) { int id = -1; if (StringUtils.isBlank(name)) { return id; @@ -2668,7 +2664,7 @@ public class DataStore { database.beginTransaction(); try { - ContentValues values = new ContentValues(); + final ContentValues values = new ContentValues(); values.put("title", name); values.put("updated", System.currentTimeMillis()); @@ -2698,7 +2694,7 @@ public class DataStore { database.beginTransaction(); int count = 0; try { - ContentValues values = new ContentValues(); + final ContentValues values = new ContentValues(); values.put("title", name); values.put("updated", System.currentTimeMillis()); @@ -2717,7 +2713,7 @@ public class DataStore { * @param listId * @return true if the list got deleted, false else */ - public static boolean removeList(int listId) { + public static boolean removeList(final int listId) { if (listId < customListIdOffset) { return false; } @@ -2727,11 +2723,11 @@ public class DataStore { database.beginTransaction(); boolean status = false; try { - int cnt = database.delete(dbTableLists, "_id = " + (listId - customListIdOffset), null); + final int cnt = database.delete(dbTableLists, "_id = " + (listId - customListIdOffset), null); if (cnt > 0) { // move caches from deleted list to standard list - SQLiteStatement moveToStandard = PreparedStatements.getMoveToStandardList(); + final SQLiteStatement moveToStandard = PreparedStatements.getMoveToStandardList(); moveToStandard.bindLong(1, listId); moveToStandard.execute(); @@ -2763,11 +2759,11 @@ public class DataStore { } init(); - SQLiteStatement move = PreparedStatements.getMoveToList(); + final SQLiteStatement move = PreparedStatements.getMoveToList(); database.beginTransaction(); try { - for (Geocache cache : caches) { + for (final Geocache cache : caches) { move.bindLong(1, listId); move.bindString(2, cache.getGeocode()); move.execute(); @@ -2783,7 +2779,7 @@ public class DataStore { return database != null; } - public static boolean removeSearchedDestination(Destination destination) { + public static boolean removeSearchedDestination(final Destination destination) { if (destination == null) { return false; } @@ -2794,7 +2790,7 @@ public class DataStore { database.delete(dbTableSearchDestionationHistory, "_id = " + destination.getId(), null); database.setTransactionSuccessful(); return true; - } catch (Exception e) { + } catch (final Exception e) { Log.e("Unable to remove searched destination", e); } finally { database.endTransaction(); @@ -2839,9 +2835,9 @@ public class DataStore { } cursor.close(); - } catch (SQLiteDoneException e) { + } catch (final SQLiteDoneException e) { // Do nothing, it only means we have no information on the cache - } catch (Exception e) { + } catch (final Exception e) { Log.e("DataStore.getCacheDescription", e); } @@ -2888,13 +2884,14 @@ public class DataStore { * @return */ - public static Set<Waypoint> loadWaypoints(final Viewport viewport, boolean excludeMine, boolean excludeDisabled, CacheType type) { + public static Set<Waypoint> loadWaypoints(final Viewport viewport, final boolean excludeMine, final boolean excludeDisabled, final CacheType type) { final StringBuilder where = buildCoordinateWhere(dbTableWaypoints, viewport); if (excludeMine) { where.append(" and ").append(dbTableCaches).append(".found == 0"); } if (excludeDisabled) { where.append(" and ").append(dbTableCaches).append(".disabled == 0"); + where.append(" and ").append(dbTableCaches).append(".archived == 0"); } if (type != CacheType.ALL) { where.append(" and ").append(dbTableCaches).append(".type == '").append(type.id).append('\''); @@ -2916,7 +2913,7 @@ public class DataStore { }); } - public static void saveChangedCache(Geocache cache) { + public static void saveChangedCache(final Geocache cache) { DataStore.saveCache(cache, cache.getStorageLocation().contains(StorageLocation.DATABASE) ? LoadFlags.SAVE_ALL : EnumSet.of(SaveFlag.SAVE_CACHE)); } @@ -2948,7 +2945,7 @@ public class DataStore { return getStatement("InsertSpoiler", "INSERT INTO " + dbTableSpoilers + " (geocode, updated, url, title, description) VALUES (?, ?, ?, ?, ?)"); } - public static SQLiteStatement getInsertSearchDestination(Destination destination) { + public static SQLiteStatement getInsertSearchDestination(final Destination destination) { final SQLiteStatement statement = getStatement("InsertSearch", "INSERT INTO " + dbTableSearchDestionationHistory + " (date, latitude, longitude) VALUES (?, ?, ?)"); statement.bindLong(1, destination.getDate()); final Geopoint coords = destination.getCoords(); @@ -2958,7 +2955,7 @@ public class DataStore { } private static void clearPreparedStatements() { - for (SQLiteStatement statement : statements.values()) { + for (final SQLiteStatement statement : statements.values()) { statement.close(); } statements.clear(); @@ -3020,11 +3017,11 @@ public class DataStore { setVisitDate(Collections.singletonList(geocode), System.currentTimeMillis()); } - public static void markDropped(List<Geocache> caches) { + public static void markDropped(final List<Geocache> caches) { moveToList(caches, StoredList.TEMPORARY_LIST_ID); } - public static Viewport getBounds(String geocode) { + public static Viewport getBounds(final String geocode) { if (geocode == null) { return null; } @@ -3032,21 +3029,21 @@ public class DataStore { return DataStore.getBounds(Collections.singleton(geocode)); } - public static void clearVisitDate(String[] selected) { + public static void clearVisitDate(final String[] selected) { setVisitDate(Arrays.asList(selected), 0); } - public static SearchResult getBatchOfStoredCaches(Geopoint coords, CacheType cacheType, int listId) { + public static SearchResult getBatchOfStoredCaches(final Geopoint coords, final CacheType cacheType, final int listId) { final Set<String> geocodes = DataStore.loadBatchOfStoredGeocodes(coords, cacheType, listId); return new SearchResult(geocodes, DataStore.getAllStoredCachesCount(cacheType, listId)); } - public static SearchResult getHistoryOfCaches(boolean detailedOnly, CacheType cacheType) { + public static SearchResult getHistoryOfCaches(final boolean detailedOnly, final CacheType cacheType) { final Set<String> geocodes = DataStore.loadBatchOfHistoricGeocodes(detailedOnly, cacheType); return new SearchResult(geocodes, DataStore.getAllHistoryCachesCount()); } - public static boolean saveWaypoint(int id, String geocode, Waypoint waypoint) { + public static boolean saveWaypoint(final int id, final String geocode, final Waypoint waypoint) { if (DataStore.saveWaypointInternal(id, geocode, waypoint)) { DataStore.removeCache(geocode, EnumSet.of(RemoveFlag.REMOVE_CACHE)); return true; @@ -3090,13 +3087,7 @@ public class DataStore { 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 - }); + final SearchSuggestionCursor resultCursor = new SearchSuggestionCursor(); try { final String selectionArg = getSuggestionArgument(searchTerm); findCaches(resultCursor, selectionArg); @@ -3107,10 +3098,10 @@ public class DataStore { return resultCursor; } - private static void findCaches(final MatrixCursor resultCursor, final String selectionArg) { - Cursor cursor = database.query( + private static void findCaches(final SearchSuggestionCursor resultCursor, final String selectionArg) { + final Cursor cursor = database.query( dbTableCaches, - new String[] { "geocode", "name" }, + new String[] { "geocode", "name", "type" }, "geocode IS NOT NULL AND geocode != '' AND (geocode LIKE ? OR name LIKE ? OR owner LIKE ?)", new String[] { selectionArg, selectionArg, selectionArg }, null, @@ -3118,23 +3109,19 @@ public class DataStore { "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 - }); + final String cacheName = cursor.getString(1); + final String type = cursor.getString(2); + resultCursor.addCache(geocode, cacheName, type); } cursor.close(); } - private static String getSuggestionArgument(String input) { + private static String getSuggestionArgument(final String input) { return "%" + StringUtils.trim(input) + "%"; } private static void findTrackables(final MatrixCursor resultCursor, final String selectionArg) { - Cursor cursor = database.query( + final Cursor cursor = database.query( dbTableTrackables, new String[] { "tbcode", "title" }, "tbcode IS NOT NULL AND tbcode != '' AND (tbcode LIKE ? OR title LIKE ?)", @@ -3156,31 +3143,53 @@ public class DataStore { } public static String[] getSuggestions(final String table, final String column, final String input) { - Cursor cursor = database.rawQuery("SELECT DISTINCT " + column + final Cursor cursor = database.rawQuery("SELECT DISTINCT " + column + " FROM " + table + " WHERE " + column + " LIKE ?" + " ORDER BY " + column + " COLLATE NOCASE ASC;", new String[] { getSuggestionArgument(input) }); return cursorToColl(cursor, new LinkedList<String>(), GET_STRING_0).toArray(new String[cursor.getCount()]); } - public static String[] getSuggestionsOwnerName(String input) { - return getSuggestions(dbTableCaches, "owner", input); + public static String[] getSuggestionsOwnerName(final String input) { + return getSuggestions(dbTableCaches, "owner_real", input); } - public static String[] getSuggestionsTrackableCode(String input) { + public static String[] getSuggestionsTrackableCode(final String input) { return getSuggestions(dbTableTrackables, "tbcode", input); } - public static String[] getSuggestionsFinderName(String input) { + public static String[] getSuggestionsFinderName(final String input) { return getSuggestions(dbTableLogs, "author", input); } - public static String[] getSuggestionsGeocode(String input) { + public static String[] getSuggestionsGeocode(final String input) { return getSuggestions(dbTableCaches, "geocode", input); } - public static String[] getSuggestionsKeyword(String input) { + public static String[] getSuggestionsKeyword(final String input) { return getSuggestions(dbTableCaches, "name", input); } + /** + * + * @return list of last caches opened in the details view, ordered by most recent first + */ + public static ArrayList<Geocache> getLastOpenedCaches() { + final List<String> geocodes = Settings.getLastOpenedCaches(); + final Set<Geocache> cachesSet = DataStore.loadCaches(geocodes, LoadFlags.LOAD_CACHE_OR_DB); + + // order result set by time again + final ArrayList<Geocache> caches = new ArrayList<Geocache>(cachesSet); + Collections.sort(caches, new Comparator<Geocache>() { + + @Override + public int compare(final Geocache lhs, final Geocache rhs) { + final int lhsIndex = geocodes.indexOf(lhs.getGeocode()); + final int rhsIndex = geocodes.indexOf(rhs.getGeocode()); + return lhsIndex < rhsIndex ? -1 : (lhsIndex == rhsIndex ? 0 : 1); + } + }); + return caches; + } + } diff --git a/main/src/cgeo/geocaching/EditWaypointActivity.java b/main/src/cgeo/geocaching/EditWaypointActivity.java index 55de0a6..0eeb0d7 100644 --- a/main/src/cgeo/geocaching/EditWaypointActivity.java +++ b/main/src/cgeo/geocaching/EditWaypointActivity.java @@ -1,6 +1,6 @@ package cgeo.geocaching; -import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.activity.AbstractActionBarActivity; import cgeo.geocaching.connector.ConnectorFactory; import cgeo.geocaching.connector.IConnector; import cgeo.geocaching.enumerations.CacheType; @@ -48,7 +48,7 @@ import java.util.EnumSet; import java.util.List; @EActivity -public class EditWaypointActivity extends AbstractActivity { +public class EditWaypointActivity extends AbstractActionBarActivity implements CoordinatesInputDialog.CoordinateUpdate { private static final ArrayList<WaypointType> POSSIBLE_WAYPOINT_TYPES = new ArrayList<WaypointType>(WaypointType.ALL_TYPES_EXCEPT_OWN_AND_ORIGINAL); @ViewById(R.id.buttonLatitude) protected Button buttonLat; @@ -294,17 +294,18 @@ public class EditWaypointActivity extends AbstractActivity { // button text is blank when creating new waypoint } Geocache cache = DataStore.loadCache(geocode, LoadFlags.LOAD_WAYPOINTS); - CoordinatesInputDialog coordsDialog = new CoordinatesInputDialog(EditWaypointActivity.this, cache, gp, app.currentGeo()); + CoordinatesInputDialog coordsDialog = CoordinatesInputDialog.getInstance(cache, gp, app.currentGeo()); coordsDialog.setCancelable(true); - coordsDialog.setOnCoordinateUpdate(new CoordinatesInputDialog.CoordinateUpdate() { - @Override - public void update(final Geopoint gp) { - buttonLat.setText(gp.format(GeopointFormatter.Format.LAT_DECMINUTE)); - buttonLon.setText(gp.format(GeopointFormatter.Format.LON_DECMINUTE)); - } - }); - coordsDialog.show(); + coordsDialog.show(getSupportFragmentManager(),"wpeditdialog"); } + + + } + + @Override + public void updateCoordinates(Geopoint gp) { + buttonLat.setText(gp.format(GeopointFormatter.Format.LAT_DECMINUTE)); + buttonLon.setText(gp.format(GeopointFormatter.Format.LON_DECMINUTE)); } public static final int SUCCESS = 0; @@ -442,7 +443,7 @@ public class EditWaypointActivity extends AbstractActivity { if (!StaticMapsProvider.hasAllStaticMapsForWaypoint(geocode, waypoint)) { StaticMapsProvider.removeWpStaticMaps(oldWaypoint, geocode); if (Settings.isStoreOfflineWpMaps()) { - StaticMapsProvider.storeWaypointStaticMap(cache, waypoint, false); + StaticMapsProvider.storeWaypointStaticMap(cache, waypoint).subscribe(); } } if (modifyLocal.isChecked() || modifyBoth.isChecked()) { diff --git a/main/src/cgeo/geocaching/Geocache.java b/main/src/cgeo/geocaching/Geocache.java index 19c15fd..193930c 100644 --- a/main/src/cgeo/geocaching/Geocache.java +++ b/main/src/cgeo/geocaching/Geocache.java @@ -2,6 +2,7 @@ package cgeo.geocaching; import cgeo.geocaching.DataStore.StorageLocation; import cgeo.geocaching.activity.ActivityMixin; +import cgeo.geocaching.activity.SimpleWebviewActivity; import cgeo.geocaching.connector.ConnectorFactory; import cgeo.geocaching.connector.IConnector; import cgeo.geocaching.connector.ILoggingManager; @@ -32,6 +33,7 @@ import cgeo.geocaching.utils.Log; import cgeo.geocaching.utils.LogTemplateProvider; import cgeo.geocaching.utils.LogTemplateProvider.LogContext; import cgeo.geocaching.utils.MatcherWrapper; +import cgeo.geocaching.utils.RxUtils; import cgeo.geocaching.utils.UncertainProperty; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; @@ -46,9 +48,8 @@ import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import rx.Scheduler; -import rx.Scheduler.Inner; import rx.Subscription; -import rx.functions.Action1; +import rx.functions.Action0; import android.app.Activity; import android.content.Intent; @@ -58,6 +59,7 @@ import android.net.Uri; import android.os.Environment; import android.os.Handler; import android.os.Message; +import android.os.Parcelable; import android.text.Html; import android.text.Html.ImageGetter; @@ -80,10 +82,6 @@ import java.util.regex.Pattern; /** * Internal c:geo representation of a "cache" */ -/** - * @author kep9fe - * - */ public class Geocache implements ICache, IWaypoint { private static final int OWN_WP_PREFIX_OFFSET = 17; @@ -160,7 +158,6 @@ public class Geocache implements ICache, IWaypoint { private final EnumSet<StorageLocation> storageLocation = EnumSet.of(StorageLocation.HEAP); private boolean finalDefined = false; private boolean logPasswordRequired = false; - // private int zoomlevel = Tile.ZOOMLEVEL_MIN - 1; private static final Pattern NUMBER_PATTERN = Pattern.compile("\\d+"); @@ -183,13 +180,13 @@ public class Geocache implements ICache, IWaypoint { * * @param gpxParser */ - public Geocache(GPXParser gpxParser) { + public Geocache(final GPXParser gpxParser) { setReliableLatLon(true); setAttributes(Collections.<String> emptyList()); setWaypoints(Collections.<Waypoint> emptyList(), false); } - public void setChangeNotificationHandler(Handler newNotificationHandler) { + public void setChangeNotificationHandler(final Handler newNotificationHandler) { changeNotificationHandler = newNotificationHandler; } @@ -471,7 +468,7 @@ public class Geocache implements ICache, IWaypoint { logOffline(fromActivity, initial, Calendar.getInstance(), logType); } - void logOffline(final Activity fromActivity, final String log, Calendar date, final LogType logType) { + void logOffline(final Activity fromActivity, final String log, final Calendar date, final LogType logType) { if (logType == LogType.UNKNOWN) { return; } @@ -498,18 +495,30 @@ public class Geocache implements ICache, IWaypoint { return getConnector().getPossibleLogTypes(this); } - public void openInBrowser(Activity fromActivity) { - fromActivity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(getBrowserCacheUrl()))); + public void openInBrowser(final Activity fromActivity) { + final Intent viewIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(getUrl())); + + // Check if cgeo is the default, show the chooser to let the user choose a browser + if (viewIntent.resolveActivity(fromActivity.getPackageManager()).getPackageName().equals(fromActivity.getPackageName())) { + final Intent chooser = Intent.createChooser(viewIntent, fromActivity.getString(R.string.cache_menu_browser)); + + final Intent internalBrowser = new Intent(fromActivity, SimpleWebviewActivity.class); + internalBrowser.setData(Uri.parse(getUrl())); + + chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Parcelable[] {internalBrowser}); + + + fromActivity.startActivity(chooser); + } else { + fromActivity.startActivity(viewIntent); + } } + private String getCacheUrl() { return getConnector().getCacheUrl(this); } - private String getBrowserCacheUrl() { - return getConnector().getLongCacheUrl(this); - } - private IConnector getConnector() { return ConnectorFactory.getConnector(this); } @@ -589,7 +598,7 @@ public class Geocache implements ICache, IWaypoint { return BooleanUtils.isTrue(premiumMembersOnly); } - public void setPremiumMembersOnly(boolean members) { + public void setPremiumMembersOnly(final boolean members) { this.premiumMembersOnly = members; } @@ -704,11 +713,17 @@ public class Geocache implements ICache, IWaypoint { return getConnector() instanceof ISearchByCenter; } - public void shareCache(Activity fromActivity, Resources res) { + public void shareCache(final Activity fromActivity, final Resources res) { if (geocode == null) { return; } + final Intent intent = getIntent(); + + fromActivity.startActivity(Intent.createChooser(intent, res.getText(R.string.action_bar_share_title))); + } + + public Intent getIntent() { final StringBuilder subject = new StringBuilder("Geocache "); subject.append(geocode); if (StringUtils.isNotBlank(name)) { @@ -720,13 +735,15 @@ public class Geocache implements ICache, IWaypoint { intent.putExtra(Intent.EXTRA_SUBJECT, subject.toString()); intent.putExtra(Intent.EXTRA_TEXT, getUrl()); - fromActivity.startActivity(Intent.createChooser(intent, res.getText(R.string.action_bar_share_title))); + return intent; } public String getUrl() { return getConnector().getCacheUrl(this); } + public String getCgeoUrl() { return getConnector().getCacheUrl(this); } + public boolean supportsGCVote() { return StringUtils.startsWithIgnoreCase(geocode, "GC"); } @@ -745,7 +762,7 @@ public class Geocache implements ICache, IWaypoint { return BooleanUtils.isTrue(favorite); } - public void setFavorite(boolean favorite) { + public void setFavorite(final boolean favorite) { this.favorite = favorite; } @@ -816,7 +833,7 @@ public class Geocache implements ICache, IWaypoint { return updated; } - public void setUpdated(long updated) { + public void setUpdated(final long updated) { this.updated = updated; } @@ -824,7 +841,7 @@ public class Geocache implements ICache, IWaypoint { return detailedUpdate; } - public void setDetailedUpdate(long detailedUpdate) { + public void setDetailedUpdate(final long detailedUpdate) { this.detailedUpdate = detailedUpdate; } @@ -832,7 +849,7 @@ public class Geocache implements ICache, IWaypoint { return visitedDate; } - public void setVisitedDate(long visitedDate) { + public void setVisitedDate(final long visitedDate) { this.visitedDate = visitedDate; } @@ -840,7 +857,7 @@ public class Geocache implements ICache, IWaypoint { return listId; } - public void setListId(int listId) { + public void setListId(final int listId) { this.listId = listId; } @@ -848,7 +865,7 @@ public class Geocache implements ICache, IWaypoint { return detailed; } - public void setDetailed(boolean detailed) { + public void setDetailed(final boolean detailed) { this.detailed = detailed; } @@ -865,7 +882,7 @@ public class Geocache implements ICache, IWaypoint { return direction; } - public void setDirection(Float direction) { + public void setDirection(final Float direction) { this.direction = direction; } @@ -873,7 +890,7 @@ public class Geocache implements ICache, IWaypoint { return distance; } - public void setDistance(Float distance) { + public void setDistance(final Float distance) { this.distance = distance; } @@ -891,7 +908,7 @@ public class Geocache implements ICache, IWaypoint { * * @param coords */ - public void setCoords(Geopoint coords) { + public void setCoords(final Geopoint coords) { this.coords = new UncertainProperty<Geopoint>(coords); } @@ -901,7 +918,7 @@ public class Geocache implements ICache, IWaypoint { * @param coords * @param zoomlevel */ - public void setCoords(Geopoint coords, int zoomlevel) { + public void setCoords(final Geopoint coords, final int zoomlevel) { this.coords = new UncertainProperty<Geopoint>(coords, zoomlevel); } @@ -912,15 +929,15 @@ public class Geocache implements ICache, IWaypoint { return getConnector().isReliableLatLon(reliableLatLon); } - public void setReliableLatLon(boolean reliableLatLon) { + public void setReliableLatLon(final boolean reliableLatLon) { this.reliableLatLon = reliableLatLon; } - public void setShortDescription(String shortdesc) { + public void setShortDescription(final String shortdesc) { this.shortdesc = shortdesc; } - public void setFavoritePoints(int favoriteCnt) { + public void setFavoritePoints(final int favoriteCnt) { this.favoritePoints = favoriteCnt; } @@ -928,7 +945,7 @@ public class Geocache implements ICache, IWaypoint { return rating; } - public void setRating(float rating) { + public void setRating(final float rating) { this.rating = rating; } @@ -936,7 +953,7 @@ public class Geocache implements ICache, IWaypoint { return votes; } - public void setVotes(int votes) { + public void setVotes(final int votes) { this.votes = votes; } @@ -944,7 +961,7 @@ public class Geocache implements ICache, IWaypoint { return myVote; } - public void setMyVote(float myVote) { + public void setMyVote(final float myVote) { this.myVote = myVote; } @@ -952,7 +969,7 @@ public class Geocache implements ICache, IWaypoint { return inventoryItems; } - public void setInventoryItems(int inventoryItems) { + public void setInventoryItems(final int inventoryItems) { this.inventoryItems = inventoryItems; } @@ -961,7 +978,7 @@ public class Geocache implements ICache, IWaypoint { return BooleanUtils.isTrue(onWatchlist); } - public void setOnWatchlist(boolean onWatchlist) { + public void setOnWatchlist(final boolean onWatchlist) { this.onWatchlist = onWatchlist; } @@ -982,7 +999,7 @@ public class Geocache implements ICache, IWaypoint { * called while loading or building a cache * @return <code>true</code> if waypoints successfully added to waypoint database */ - public boolean setWaypoints(List<Waypoint> waypoints, boolean saveToDatabase) { + public boolean setWaypoints(final List<Waypoint> waypoints, final boolean saveToDatabase) { this.waypoints.clear(); if (waypoints != null) { this.waypoints.addAll(waypoints); @@ -1029,7 +1046,7 @@ public class Geocache implements ICache, IWaypoint { return BooleanUtils.isTrue(logOffline); } - public void setLogOffline(boolean logOffline) { + public void setLogOffline(final boolean logOffline) { this.logOffline = logOffline; } @@ -1037,7 +1054,7 @@ public class Geocache implements ICache, IWaypoint { return statusChecked; } - public void setStatusChecked(boolean statusChecked) { + public void setStatusChecked(final boolean statusChecked) { this.statusChecked = statusChecked; } @@ -1045,39 +1062,39 @@ public class Geocache implements ICache, IWaypoint { return directionImg; } - public void setDirectionImg(String directionImg) { + public void setDirectionImg(final String directionImg) { this.directionImg = directionImg; } - public void setGeocode(String geocode) { + public void setGeocode(final String geocode) { this.geocode = StringUtils.upperCase(geocode); } - public void setCacheId(String cacheId) { + public void setCacheId(final String cacheId) { this.cacheId = cacheId; } - public void setGuid(String guid) { + public void setGuid(final String guid) { this.guid = guid; } - public void setName(String name) { + public void setName(final String name) { this.name = name; } - public void setOwnerDisplayName(String ownerDisplayName) { + public void setOwnerDisplayName(final String ownerDisplayName) { this.ownerDisplayName = ownerDisplayName; } - public void setOwnerUserId(String ownerUserId) { + public void setOwnerUserId(final String ownerUserId) { this.ownerUserId = ownerUserId; } - public void setHint(String hint) { + public void setHint(final String hint) { this.hint = hint; } - public void setSize(CacheSize size) { + public void setSize(final CacheSize size) { if (size == null) { this.size = CacheSize.UNKNOWN; } @@ -1086,50 +1103,50 @@ public class Geocache implements ICache, IWaypoint { } } - public void setDifficulty(float difficulty) { + public void setDifficulty(final float difficulty) { this.difficulty = difficulty; } - public void setTerrain(float terrain) { + public void setTerrain(final float terrain) { this.terrain = terrain; } - public void setLocation(String location) { + public void setLocation(final String location) { this.location = location; } - public void setPersonalNote(String personalNote) { + public void setPersonalNote(final String personalNote) { this.personalNote = StringUtils.trimToNull(personalNote); } - public void setDisabled(boolean disabled) { + public void setDisabled(final boolean disabled) { this.disabled = disabled; } - public void setArchived(boolean archived) { + public void setArchived(final boolean archived) { this.archived = archived; } - public void setFound(boolean found) { + public void setFound(final boolean found) { this.found = found; } - public void setAttributes(List<String> attributes) { + public void setAttributes(final List<String> attributes) { this.attributes.clear(); if (attributes != null) { this.attributes.addAll(attributes); } } - public void setSpoilers(List<Image> spoilers) { + public void setSpoilers(final List<Image> spoilers) { this.spoilers = spoilers; } - public void setInventory(List<Trackable> inventory) { + public void setInventory(final List<Trackable> inventory) { this.inventory = inventory; } - public void setLogCounts(Map<LogType, Integer> logCounts) { + public void setLogCounts(final Map<LogType, Integer> logCounts) { this.logCounts = logCounts; } @@ -1145,14 +1162,14 @@ public class Geocache implements ICache, IWaypoint { return cacheType.getValue(); } - public void setType(CacheType cacheType) { + public void setType(final CacheType cacheType) { if (cacheType == null || CacheType.ALL == cacheType) { throw new IllegalArgumentException("Illegal cache type"); } this.cacheType = new UncertainProperty<CacheType>(cacheType); } - public void setType(CacheType cacheType, final int zoomlevel) { + public void setType(final CacheType cacheType, final int zoomlevel) { if (cacheType == null || CacheType.ALL == cacheType) { throw new IllegalArgumentException("Illegal cache type"); } @@ -1190,7 +1207,7 @@ public class Geocache implements ICache, IWaypoint { * called while loading or building a cache * @return <code>true</code> if waypoint successfully added to waypoint database */ - public boolean addOrChangeWaypoint(final Waypoint waypoint, boolean saveToDatabase) { + public boolean addOrChangeWaypoint(final Waypoint waypoint, final boolean saveToDatabase) { waypoint.setGeocode(geocode); if (waypoint.getId() < 0) { // this is a new waypoint @@ -1202,7 +1219,7 @@ public class Geocache implements ICache, IWaypoint { } else { // this is a waypoint being edited final int index = getWaypointIndex(waypoint); if (index >= 0) { - Waypoint oldWaypoint = waypoints.remove(index); + final Waypoint oldWaypoint = waypoints.remove(index); waypoint.setPrefix(oldWaypoint.getPrefix()); //migration if (StringUtils.isBlank(waypoint.getPrefix()) @@ -1221,15 +1238,15 @@ public class Geocache implements ICache, IWaypoint { * Assigns a unique two-digit (compatibility with gc.com) * prefix within the scope of this cache. */ - private void assignUniquePrefix(Waypoint waypoint) { + private void assignUniquePrefix(final Waypoint waypoint) { // gather existing prefixes - Set<String> assignedPrefixes = new HashSet<String>(); - for (Waypoint wp : waypoints) { + final Set<String> assignedPrefixes = new HashSet<String>(); + for (final Waypoint wp : waypoints) { assignedPrefixes.add(wp.getPrefix()); } for (int i = OWN_WP_PREFIX_OFFSET; i < 100; i++) { - String prefixCandidate = StringUtils.leftPad(String.valueOf(i), 2, '0'); + final String prefixCandidate = StringUtils.leftPad(String.valueOf(i), 2, '0'); if (!assignedPrefixes.contains(prefixCandidate)) { waypoint.setPrefix(prefixCandidate); break; @@ -1247,7 +1264,7 @@ public class Geocache implements ICache, IWaypoint { } // Only for loading - public void setFinalDefined(boolean finalDefined) { + public void setFinalDefined(final boolean finalDefined) { this.finalDefined = finalDefined; } @@ -1268,7 +1285,7 @@ public class Geocache implements ICache, IWaypoint { return userModifiedCoords; } - public void setUserModifiedCoords(boolean coordsChanged) { + public void setUserModifiedCoords(final boolean coordsChanged) { userModifiedCoords = coordsChanged; } @@ -1325,7 +1342,7 @@ public class Geocache implements ICache, IWaypoint { * @param waypoint */ - public void deleteWaypointForce(Waypoint waypoint) { + public void deleteWaypointForce(final Waypoint waypoint) { final int index = getWaypointIndex(waypoint); waypoints.remove(index); DataStore.deleteWaypoint(waypoint.getId()); @@ -1405,17 +1422,17 @@ public class Geocache implements ICache, IWaypoint { } @Override - public boolean equals(Object obj) { + public boolean equals(final Object obj) { // TODO: explain the following line or remove this non-standard equality method // just compare the geocode even if that is not what "equals" normally does return this == obj || (obj instanceof Geocache && StringUtils.isNotEmpty(geocode) && geocode.equals(((Geocache) obj).geocode)); } - public void store(CancellableHandler handler) { + public void store(final CancellableHandler handler) { store(StoredList.TEMPORARY_LIST_ID, handler); } - public void store(final int listId, CancellableHandler handler) { + public void store(final int listId, final CancellableHandler handler) { final int newListId = listId < StoredList.STANDARD_LIST_ID ? Math.max(getListId(), StoredList.STANDARD_LIST_ID) : listId; @@ -1438,9 +1455,9 @@ public class Geocache implements ICache, IWaypoint { } public Subscription drop(final Handler handler, final Scheduler scheduler) { - return scheduler.schedule(new Action1<Inner>() { + return scheduler.createWorker().schedule(new Action0() { @Override - public void call(final Inner inner) { + public void call() { try { dropSynchronous(); handler.sendMessage(Message.obtain()); @@ -1498,22 +1515,22 @@ public class Geocache implements ICache, IWaypoint { } } - public Subscription refresh(final int newListId, final CancellableHandler handler, final Scheduler scheduler) { - return scheduler.schedule(new Action1<Inner>() { + public Subscription refresh(final CancellableHandler handler, final Scheduler scheduler) { + return scheduler.createWorker().schedule(new Action0() { @Override - public void call(final Inner inner) { - refreshSynchronous(newListId, handler); + public void call() { + refreshSynchronous(handler); handler.sendEmptyMessage(CancellableHandler.DONE); } }); } - public void refreshSynchronous(final int newListId, final CancellableHandler handler) { + public void refreshSynchronous(final CancellableHandler handler) { DataStore.removeCache(geocode, EnumSet.of(RemoveFlag.REMOVE_CACHE)); - storeCache(null, geocode, newListId, true, handler); + storeCache(null, geocode, listId, true, handler); } - public static void storeCache(Geocache origCache, String geocode, int listId, boolean forceRedownload, CancellableHandler handler) { + public static void storeCache(final Geocache origCache, final String geocode, final int listId, final boolean forceRedownload, final CancellableHandler handler) { try { Geocache cache = null; // get cache details, they may not yet be complete @@ -1591,9 +1608,7 @@ public class Geocache implements ICache, IWaypoint { return; } - StaticMapsProvider.downloadMaps(cache); - - imgGetter.waitForBackgroundLoading(handler); + RxUtils.waitForCompletion(StaticMapsProvider.downloadMaps(cache), imgGetter.waitForEndObservable(handler)); if (handler != null) { handler.sendMessage(Message.obtain()); @@ -1643,7 +1658,7 @@ public class Geocache implements ICache, IWaypoint { } final String hourLocalized = CgeoApplication.getInstance().getString(R.string.cache_time_full_hours); - ArrayList<Pattern> patterns = new ArrayList<Pattern>(); + final ArrayList<Pattern> patterns = new ArrayList<Pattern>(); // 12:34 patterns.add(Pattern.compile("\\b(\\d{1,2})\\:(\\d\\d)\\b")); @@ -1655,7 +1670,7 @@ public class Geocache implements ICache, IWaypoint { } final String searchText = getShortDescription() + ' ' + getDescription(); - for (Pattern pattern : patterns) { + for (final Pattern pattern : patterns) { final MatcherWrapper matcher = new MatcherWrapper(pattern, searchText); while (matcher.find()) { try { @@ -1683,7 +1698,7 @@ public class Geocache implements ICache, IWaypoint { * true if we are looking for the attribute_yes version, false for the attribute_no version * @return */ - public boolean hasAttribute(CacheAttribute attribute, boolean yes) { + public boolean hasAttribute(final CacheAttribute attribute, final boolean yes) { Geocache fullCache = DataStore.loadCache(getGeocode(), EnumSet.of(LoadFlag.LOAD_ATTRIBUTES)); if (fullCache == null) { fullCache = this; @@ -1762,7 +1777,7 @@ public class Geocache implements ICache, IWaypoint { * Gets whether the user has logged the specific log type for this cache. Only checks the currently stored logs of * the cache, so the result might be wrong. */ - public boolean hasOwnLog(LogType logType) { + public boolean hasOwnLog(final LogType logType) { for (final LogEntry logEntry : getLogs()) { if (logEntry.type == logType && logEntry.isOwn()) { return true; @@ -1779,15 +1794,15 @@ public class Geocache implements ICache, IWaypoint { return logPasswordRequired; } - public void setLogPasswordRequired(boolean required) { + public void setLogPasswordRequired(final boolean required) { logPasswordRequired = required; } - public String getWaypointGpxId(String prefix) { + public String getWaypointGpxId(final String prefix) { return getConnector().getWaypointGpxId(prefix, geocode); } - public String getWaypointPrefix(String name) { + public String getWaypointPrefix(final String name) { return getConnector().getWaypointPrefix(name); } @@ -1800,7 +1815,7 @@ public class Geocache implements ICache, IWaypoint { if (getLogCounts().isEmpty()) { setLogCounts(DataStore.loadLogCounts(getGeocode())); } - Integer logged = getLogCounts().get(LogType.FOUND_IT); + final Integer logged = getLogCounts().get(LogType.FOUND_IT); if (logged != null) { return logged; } @@ -1814,7 +1829,7 @@ public class Geocache implements ICache, IWaypoint { public LogType getDefaultLogType() { if (isEventCache()) { final Date eventDate = getHiddenDate(); - boolean expired = DateUtils.isPastEvent(this); + final boolean expired = DateUtils.isPastEvent(this); if (hasOwnLog(LogType.WILL_ATTEND) || expired || (eventDate != null && DateUtils.daysSince(eventDate.getTime()) == 0)) { return hasOwnLog(LogType.ATTENDED) ? LogType.NOTE : LogType.ATTENDED; diff --git a/main/src/cgeo/geocaching/ImageSelectActivity.java b/main/src/cgeo/geocaching/ImageSelectActivity.java index b5fb38e..a64b4cf 100644 --- a/main/src/cgeo/geocaching/ImageSelectActivity.java +++ b/main/src/cgeo/geocaching/ImageSelectActivity.java @@ -3,7 +3,7 @@ package cgeo.geocaching; import butterknife.ButterKnife; import butterknife.InjectView; -import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.activity.AbstractActionBarActivity; import cgeo.geocaching.files.LocalStorage; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.ui.dialog.Dialogs; @@ -37,7 +37,7 @@ import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; -public class ImageSelectActivity extends AbstractActivity { +public class ImageSelectActivity extends AbstractActionBarActivity { @InjectView(R.id.caption) protected EditText captionView; @InjectView(R.id.description) protected EditText descriptionView; diff --git a/main/src/cgeo/geocaching/ImagesActivity.java b/main/src/cgeo/geocaching/ImagesActivity.java index 3da1ade..bc2616b 100644 --- a/main/src/cgeo/geocaching/ImagesActivity.java +++ b/main/src/cgeo/geocaching/ImagesActivity.java @@ -1,6 +1,6 @@ package cgeo.geocaching; -import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.activity.AbstractActionBarActivity; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.ui.ImagesList; import cgeo.geocaching.ui.ImagesList.ImageType; @@ -19,7 +19,7 @@ import android.view.View; import java.util.ArrayList; import java.util.List; -public class ImagesActivity extends AbstractActivity { +public class ImagesActivity extends AbstractActionBarActivity { private boolean offline; private ArrayList<Image> imageNames; diff --git a/main/src/cgeo/geocaching/LogCacheActivity.java b/main/src/cgeo/geocaching/LogCacheActivity.java index 2b05263..25906de 100644 --- a/main/src/cgeo/geocaching/LogCacheActivity.java +++ b/main/src/cgeo/geocaching/LogCacheActivity.java @@ -25,7 +25,6 @@ import org.apache.commons.lang3.StringUtils; import android.app.Activity; import android.app.AlertDialog; import android.app.AlertDialog.Builder; -import android.app.Dialog; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; @@ -35,12 +34,13 @@ import android.util.SparseArray; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; -import android.view.SubMenu; import android.view.View; import android.widget.Button; import android.widget.CheckBox; import android.widget.EditText; import android.widget.LinearLayout; +import android.widget.RatingBar; +import android.widget.RatingBar.OnRatingBarChangeListener; import android.widget.TextView; import java.util.ArrayList; @@ -52,7 +52,6 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia static final String EXTRAS_GEOCODE = "geocode"; static final String EXTRAS_ID = "id"; - private static final int SUBMENU_VOTE = 3; private static final String SAVED_STATE_RATING = "cgeo.geocaching.saved_state_rating"; private static final String SAVED_STATE_TYPE = "cgeo.geocaching.saved_state_type"; private static final String SAVED_STATE_DATE = "cgeo.geocaching.saved_state_date"; @@ -68,7 +67,6 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia private String text = null; private List<LogType> possibleLogTypes = new ArrayList<LogType>(); private List<TrackableLog> trackables = null; - private Button postButton = null; private CheckBox tweetCheck = null; private LinearLayout tweetBox = null; private LinearLayout logPasswordBox = null; @@ -84,6 +82,7 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia private String imageCaption; private String imageDescription; private Uri imageUri; + private boolean sendButtonEnabled; public void onLoadFinished() { @@ -123,7 +122,7 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia private void initializeTrackablesAction() { if (Settings.isTrackableAutoVisit()) { - for (TrackableLog trackable : trackables) { + for (final TrackableLog trackable : trackables) { trackable.action = LogTypeTrackable.VISITED; tbChanged = true; } @@ -142,8 +141,8 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia final LinearLayout inventoryView = (LinearLayout) findViewById(R.id.inventory); inventoryView.removeAllViews(); - for (TrackableLog tb : trackables) { - LinearLayout inventoryItem = (LinearLayout) inflater.inflate(R.layout.logcache_trackable_item, null); + for (final TrackableLog tb : trackables) { + final LinearLayout inventoryItem = (LinearLayout) inflater.inflate(R.layout.logcache_trackable_item, null); ((TextView) inventoryItem.findViewById(R.id.trackcode)).setText(tb.trackCode); ((TextView) inventoryItem.findViewById(R.id.name)).setText(tb.name); @@ -154,7 +153,7 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia actionButton.setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View view) { + public void onClick(final View view) { selectTrackableAction(view); } }); @@ -164,7 +163,7 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia inventoryItem.findViewById(R.id.info).setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View view) { + public void onClick(final View view) { final Intent trackablesIntent = new Intent(LogCacheActivity.this, TrackableActivity.class); trackablesIntent.putExtra(Intents.EXTRA_GEOCODE, tbCode); startActivity(trackablesIntent); @@ -184,7 +183,7 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia changeButton.setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View view) { + public void onClick(final View view) { selectAllTrackablesAction(); } }); @@ -193,33 +192,9 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia } } - private void enablePostButton(boolean enabled) { - postButton.setEnabled(enabled); - if (enabled) { - postButton.setOnClickListener(new PostListener()); - } - else { - postButton.setOnTouchListener(null); - postButton.setOnClickListener(null); - } - updatePostButtonText(); - } - - private void updatePostButtonText() { - postButton.setText(getPostButtonText()); - } - - private String getPostButtonText() { - if (!postButton.isEnabled()) { - return res.getString(R.string.log_post_not_possible); - } - if (!GCVote.isVotingPossible(cache)) { - return res.getString(R.string.log_post); - } - if (GCVote.isValidRating(rating)) { - return res.getString(R.string.log_post_rate) + " " + GCVote.getRatingText(rating) + "*"; - } - return res.getString(R.string.log_post_no_rate); + private void enablePostButton(final boolean enabled) { + sendButtonEnabled = enabled; + invalidateOptionsMenuCompatible(); } @Override @@ -239,6 +214,7 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia } cache = DataStore.loadCache(geocode, LoadFlags.LOAD_CACHE_OR_DB); + invalidateOptionsMenuCompatible(); possibleLogTypes = cache.getPossibleLogTypes(); if (StringUtils.isNotBlank(cache.getName())) { @@ -248,11 +224,13 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia } // Get ids for later use - postButton = (Button) findViewById(R.id.post); tweetBox = (LinearLayout) findViewById(R.id.tweet_box); tweetCheck = (CheckBox) findViewById(R.id.tweet); logPasswordBox = (LinearLayout) findViewById(R.id.log_password_box); + final RatingBar ratingBar = (RatingBar) findViewById(R.id.gcvoteRating); + initializeRatingBar(ratingBar); + // initialize with default values setDefaultValues(); @@ -277,8 +255,6 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia insertIntoLog(LogTemplateProvider.applyTemplates(Settings.getSignature(), new LogContext(cache, null)), false); } } - updatePostButtonText(); - updateImageButton(); enablePostButton(false); final Button typeButton = (Button) findViewById(R.id.type); @@ -286,7 +262,7 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia typeButton.setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View view) { + public void onClick(final View view) { selectLogType(); } }); @@ -305,36 +281,30 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia updateTweetBox(typeSelected); updateLogPasswordBox(typeSelected); - final Button imageButton = (Button) findViewById(R.id.image_btn); - imageButton.setOnClickListener(new View.OnClickListener() { - - @Override - public void onClick(View view) { - selectImage(); - } - }); - - final Button saveButton = (Button) findViewById(R.id.save); - saveButton.setOnClickListener(new View.OnClickListener() { + loggingManager = cache.getLoggingManager(this); - @Override - public void onClick(View v) { - saveLog(true); - } - }); + loggingManager.init(); + } - final Button clearButton = (Button) findViewById(R.id.clear); - clearButton.setOnClickListener(new View.OnClickListener() { + private void initializeRatingBar(final RatingBar ratingBar) { + final TextView label = (TextView) findViewById(R.id.gcvoteLabel); + if (GCVote.isVotingPossible(cache)) { + ratingBar.setVisibility(View.VISIBLE); + label.setVisibility(View.VISIBLE); + } + ratingBar.setOnRatingBarChangeListener(new OnRatingBarChangeListener() { @Override - public void onClick(View v) { - clearLog(); + public void onRatingChanged(final RatingBar ratingBar, final float stars, final boolean fromUser) { + // 0.5 is not a valid rating, therefore we must limit + rating = GCVote.isValidRating(stars) ? stars : 0; + if (rating < stars) { + ratingBar.setRating(rating); + } + label.setText(GCVote.getDescription(rating)); } }); - - loggingManager = cache.getLoggingManager(this); - - loggingManager.init(); + ratingBar.setRating(cache.getMyVote()); } private void setDefaultValues() { @@ -364,8 +334,6 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia final EditText logPasswordView = (EditText) findViewById(R.id.log_password); logPasswordView.setText(StringUtils.EMPTY); - updateImageButton(); - showToast(res.getString(R.string.info_log_cleared)); } @@ -382,53 +350,6 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia } @Override - public boolean onCreateOptionsMenu(final Menu menu) { - super.onCreateOptionsMenu(menu); - - final SubMenu menuStars = menu.addSubMenu(0, SUBMENU_VOTE, 0, res.getString(R.string.log_rating)).setIcon(R.drawable.ic_menu_sort_by_size); - menuStars.add(0, 10, 0, res.getString(R.string.log_no_rating)); - menuStars.add(0, 19, 0, res.getString(R.string.log_stars_5) + " (" + res.getString(R.string.log_stars_5_description) + ")"); - menuStars.add(0, 18, 0, res.getString(R.string.log_stars_45) + " (" + res.getString(R.string.log_stars_45_description) + ")"); - menuStars.add(0, 17, 0, res.getString(R.string.log_stars_4) + " (" + res.getString(R.string.log_stars_4_description) + ")"); - menuStars.add(0, 16, 0, res.getString(R.string.log_stars_35) + " (" + res.getString(R.string.log_stars_35_description) + ")"); - menuStars.add(0, 15, 0, res.getString(R.string.log_stars_3) + " (" + res.getString(R.string.log_stars_3_description) + ")"); - menuStars.add(0, 14, 0, res.getString(R.string.log_stars_25) + " (" + res.getString(R.string.log_stars_25_description) + ")"); - menuStars.add(0, 13, 0, res.getString(R.string.log_stars_2) + " (" + res.getString(R.string.log_stars_2_description) + ")"); - menuStars.add(0, 12, 0, res.getString(R.string.log_stars_15) + " (" + res.getString(R.string.log_stars_15_description) + ")"); - menuStars.add(0, 11, 0, res.getString(R.string.log_stars_1) + " (" + res.getString(R.string.log_stars_1_description) + ")"); - - return true; - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - super.onPrepareOptionsMenu(menu); - - menu.findItem(SUBMENU_VOTE).setVisible(GCVote.isVotingPossible(cache)); - - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (super.onOptionsItemSelected(item)) { - return true; - } - - final int id = item.getItemId(); - if (id >= 10 && id <= 19) { - rating = (id - 9) / 2.0f; - if (!GCVote.isValidRating(rating)) { - rating = GCVote.NO_RATING; - } - updatePostButtonText(); - return true; - } - - return false; - } - - @Override protected void onSaveInstanceState(final Bundle outState) { super.onSaveInstanceState(outState); outState.putDouble(SAVED_STATE_RATING, rating); @@ -440,14 +361,14 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia } @Override - public void setDate(Calendar dateIn) { + public void setDate(final Calendar dateIn) { date = dateIn; final Button dateButton = (Button) findViewById(R.id.date); dateButton.setText(Formatter.formatShortDateVerbally(date.getTime().getTime())); } - public void setType(LogType type) { + public void setType(final LogType type) { final Button typeButton = (Button) findViewById(R.id.type); typeSelected = type; @@ -461,11 +382,9 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia updateTweetBox(type); updateLogPasswordBox(type); - - updatePostButtonText(); } - private void updateTweetBox(LogType type) { + private void updateTweetBox(final LogType type) { if (type == LogType.FOUND_IT && Settings.isUseTwitter() && Settings.isTwitterLoginValid()) { tweetBox.setVisibility(View.VISIBLE); } else { @@ -473,7 +392,7 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia } } - private void updateLogPasswordBox(LogType type) { + private void updateLogPasswordBox(final LogType type) { if (type == LogType.FOUND_IT && cache.isLogPasswordRequired()) { logPasswordBox.setVisibility(View.VISIBLE); } else { @@ -484,20 +403,10 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia private class DateListener implements View.OnClickListener { @Override - public void onClick(View arg0) { - final Dialog dateDialog = new DateDialog(LogCacheActivity.this, LogCacheActivity.this, date); + public void onClick(final View arg0) { + final DateDialog dateDialog = DateDialog.getInstance(date); dateDialog.setCancelable(true); - dateDialog.show(); - } - } - - private class PostListener implements View.OnClickListener { - @Override - public void onClick(View arg0) { - final String message = res.getString(StringUtils.isBlank(imageUri.getPath()) ? - R.string.log_saving : - R.string.log_saving_and_uploading); - new Poster(LogCacheActivity.this, message).execute(currentLogText(), currentLogPassword()); + dateDialog.show(getSupportFragmentManager(), "date_dialog"); } } @@ -516,15 +425,16 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia if (logResult.getPostLogResult() == StatusCode.NO_ERROR) { // update geocache in DB - if (typeSelected == LogType.FOUND_IT || typeSelected == LogType.ATTENDED) { + if (typeSelected == LogType.FOUND_IT || typeSelected == LogType.ATTENDED || typeSelected == LogType.WEBCAM_PHOTO_TAKEN) { cache.setFound(true); cache.setVisitedDate(new Date().getTime()); } DataStore.saveChangedCache(cache); // update logs in DB - ArrayList<LogEntry> newLogs = new ArrayList<LogEntry>(cache.getLogs()); + final ArrayList<LogEntry> newLogs = new ArrayList<LogEntry>(cache.getLogs()); final LogEntry logNow = new LogEntry(date.getTimeInMillis(), typeSelected, log); + logNow.friend = true; newLogs.add(0, logNow); DataStore.saveLogsWithoutTransaction(cache.getGeocode(), newLogs); @@ -541,7 +451,7 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia } if (StringUtils.isNotBlank(imageUri.getPath())) { - ImageResult imageResult = loggingManager.postLogImage(logResult.getLogId(), imageCaption, imageDescription, imageUri); + final ImageResult imageResult = loggingManager.postLogImage(logResult.getLogId(), imageCaption, imageDescription, imageUri); final String uploadedImageUrl = imageResult.getImageUri(); if (StringUtils.isNotEmpty(uploadedImageUrl)) { logNow.addLogImage(new Image(uploadedImageUrl, imageCaption, imageDescription)); @@ -552,7 +462,7 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia } return logResult.getPostLogResult(); - } catch (RuntimeException e) { + } catch (final RuntimeException e) { Log.e("VisitCacheActivity.Poster.doInBackgroundInternal", e); } @@ -601,15 +511,15 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia } private void selectAllTrackablesAction() { - Builder alert = new AlertDialog.Builder(this); + final Builder alert = new AlertDialog.Builder(this); alert.setTitle(res.getString(R.string.log_tb_changeall)); - String[] tbLogTypes = getTBLogTypes(); + final String[] tbLogTypes = getTBLogTypes(); alert.setItems(tbLogTypes, new OnClickListener() { @Override - public void onClick(DialogInterface dialog, int position) { + public void onClick(final DialogInterface dialog, final int position) { final LogTypeTrackable logType = LogTypeTrackable.values()[position]; - for (TrackableLog tb : trackables) { + for (final TrackableLog tb : trackables) { tb.action = logType; } tbChanged = true; @@ -622,7 +532,7 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia private static String[] getTBLogTypes() { final LogTypeTrackable[] logTypeValues = LogTypeTrackable.values(); - String[] logTypes = new String[logTypeValues.length]; + final String[] logTypes = new String[logTypeValues.length]; for (int i = 0; i < logTypes.length; i++) { logTypes[i] = logTypeValues[i].getLabel(); } @@ -633,15 +543,15 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia // use a local copy of the possible types, as that one might be modified in the background by the loader final ArrayList<LogType> possible = new ArrayList<LogType>(possibleLogTypes); - Builder alert = new AlertDialog.Builder(this); - String[] choices = new String[possible.size()]; + final Builder alert = new AlertDialog.Builder(this); + final String[] choices = new String[possible.size()]; for (int i = 0; i < choices.length; i++) { choices[i] = possible.get(i).getL10n(); } alert.setSingleChoiceItems(choices, possible.indexOf(typeSelected), new OnClickListener() { @Override - public void onClick(DialogInterface dialog, int position) { + public void onClick(final DialogInterface dialog, final int position) { setType(possible.get(position)); dialog.dismiss(); } @@ -649,16 +559,16 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia alert.create().show(); } - private void selectTrackableAction(View view) { + private void selectTrackableAction(final View view) { final int realViewId = view.getId(); - Builder alert = new AlertDialog.Builder(this); + final Builder alert = new AlertDialog.Builder(this); final TrackableLog trackableLog = actionButtons.get(realViewId); alert.setTitle(trackableLog.name); - String[] tbLogTypes = getTBLogTypes(); + final String[] tbLogTypes = getTBLogTypes(); alert.setItems(tbLogTypes, new OnClickListener() { @Override - public void onClick(DialogInterface dialog, int position) { + public void onClick(final DialogInterface dialog, final int position) { final LogTypeTrackable logType = LogTypeTrackable.values()[position]; tbChanged = true; trackableLog.action = logType; @@ -671,7 +581,7 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia } private void selectImage() { - Intent selectImageIntent = new Intent(this, ImageSelectActivity.class); + final Intent selectImageIntent = new Intent(this, ImageSelectActivity.class); selectImageIntent.putExtra(ImageSelectActivity.EXTRAS_CAPTION, imageCaption); selectImageIntent.putExtra(ImageSelectActivity.EXTRAS_DESCRIPTION, imageDescription); selectImageIntent.putExtra(ImageSelectActivity.EXTRAS_URI_AS_STRING, imageUri.toString()); @@ -680,7 +590,7 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia } @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { + protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) { if (requestCode == SELECT_IMAGE) { if (resultCode == RESULT_OK) { imageCaption = data.getStringExtra(ImageSelectActivity.EXTRAS_CAPTION); @@ -690,19 +600,51 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia // Image capture failed, advise user showToast(getResources().getString(R.string.err_select_logimage_failed)); } - updateImageButton(); + } + } + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_send: + sendLog(); + return true; + case R.id.menu_image: + selectImage(); + return true; + case R.id.save: + saveLog(true); + finish(); + return true; + case R.id.clear: + clearLog(); + return true; + default: + break; } + + return super.onOptionsItemSelected(item); } - private void updateImageButton() { - final Button imageButton = (Button) findViewById(R.id.image_btn); - if (cache.supportsLogImages()) { - imageButton.setVisibility(View.VISIBLE); - imageButton.setText(StringUtils.isNotBlank(imageUri.getPath()) ? - res.getString(R.string.log_image_edit) : res.getString(R.string.log_image_attach)); - } else { - imageButton.setVisibility(View.GONE); + private void sendLog() { + if (!sendButtonEnabled) { + Dialogs.message(this, R.string.log_post_not_possible); } + else { + final String message = res.getString(StringUtils.isBlank(imageUri.getPath()) ? + R.string.log_saving : + R.string.log_saving_and_uploading); + new Poster(LogCacheActivity.this, message).execute(currentLogText(), currentLogPassword()); + } + } + + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + super.onCreateOptionsMenu(menu); + menu.findItem(R.id.menu_image).setVisible(cache.supportsLogImages()); + menu.findItem(R.id.save).setVisible(true); + menu.findItem(R.id.clear).setVisible(true); + return true; } + } diff --git a/main/src/cgeo/geocaching/LogTrackableActivity.java b/main/src/cgeo/geocaching/LogTrackableActivity.java index fabe391..9f1bc88 100644 --- a/main/src/cgeo/geocaching/LogTrackableActivity.java +++ b/main/src/cgeo/geocaching/LogTrackableActivity.java @@ -20,7 +20,6 @@ import cgeo.geocaching.utils.LogTemplateProvider.LogContext; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; -import android.app.Dialog; import android.app.ProgressDialog; import android.content.Context; import android.content.Intent; @@ -240,9 +239,9 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat @Override public void onClick(View arg0) { - final Dialog dateDialog = new DateDialog(LogTrackableActivity.this, LogTrackableActivity.this, date); + final DateDialog dateDialog = DateDialog.getInstance(date); dateDialog.setCancelable(true); - dateDialog.show(); + dateDialog.show(getSupportFragmentManager(),"date_dialog"); } } diff --git a/main/src/cgeo/geocaching/MainActivity.java b/main/src/cgeo/geocaching/MainActivity.java index 42dd58d..2ed130f 100644 --- a/main/src/cgeo/geocaching/MainActivity.java +++ b/main/src/cgeo/geocaching/MainActivity.java @@ -3,7 +3,7 @@ package cgeo.geocaching; import butterknife.ButterKnife; import butterknife.InjectView; -import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.activity.AbstractActionBarActivity; import cgeo.geocaching.connector.ConnectorFactory; import cgeo.geocaching.connector.capability.ILogin; import cgeo.geocaching.enumerations.CacheType; @@ -25,7 +25,9 @@ import cgeo.geocaching.utils.Version; import com.google.zxing.integration.android.IntentIntegrator; import com.google.zxing.integration.android.IntentResult; + import org.apache.commons.lang3.StringUtils; + import rx.Observable; import rx.Observable.OnSubscribe; import rx.Subscriber; @@ -37,6 +39,7 @@ import rx.subscriptions.Subscriptions; import android.app.AlertDialog; import android.app.AlertDialog.Builder; import android.app.SearchManager; +import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.res.Configuration; @@ -45,6 +48,8 @@ import android.location.Geocoder; import android.os.Bundle; import android.os.Handler; import android.os.Message; +import android.support.v4.view.MenuItemCompat; +import android.support.v7.widget.SearchView; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; @@ -61,7 +66,7 @@ import java.util.Comparator; import java.util.List; import java.util.Locale; -public class MainActivity extends AbstractActivity { +public class MainActivity extends AbstractActionBarActivity { @InjectView(R.id.nav_satellites) protected TextView navSatellites; @InjectView(R.id.filter_button_title)protected TextView filterTitle; @InjectView(R.id.map) protected ImageView findOnMap; @@ -86,24 +91,24 @@ public class MainActivity extends AbstractActivity { private final UpdateLocation locationUpdater = new UpdateLocation(); - private Handler updateUserInfoHandler = new Handler() { + private final Handler updateUserInfoHandler = new Handler() { @Override public void handleMessage(final Message msg) { // Get active connectors with login status - ILogin[] loginConns = ConnectorFactory.getActiveLiveConnectors(); + final ILogin[] loginConns = ConnectorFactory.getActiveLiveConnectors(); // Update UI infoArea.removeAllViews(); - LayoutInflater inflater = getLayoutInflater(); + final LayoutInflater inflater = getLayoutInflater(); - for (ILogin conn : loginConns) { + for (final ILogin conn : loginConns) { - TextView connectorInfo = (TextView) inflater.inflate(R.layout.main_activity_connectorstatus, null); + final TextView connectorInfo = (TextView) inflater.inflate(R.layout.main_activity_connectorstatus, null); infoArea.addView(connectorInfo); - StringBuilder userInfo = new StringBuilder(conn.getName()).append(Formatter.SEPARATOR); + final StringBuilder userInfo = new StringBuilder(conn.getName()).append(Formatter.SEPARATOR); if (conn.isLoggedIn()) { userInfo.append(conn.getUserName()); if (conn.getCachesFound() >= 0) { @@ -167,9 +172,9 @@ public class MainActivity extends AbstractActivity { } - private SatellitesHandler satellitesHandler = new SatellitesHandler(); + private final SatellitesHandler satellitesHandler = new SatellitesHandler(); - private Handler firstLoginHandler = new Handler() { + private final Handler firstLoginHandler = new Handler() { @Override public void handleMessage(final Message msg) { @@ -179,7 +184,7 @@ public class MainActivity extends AbstractActivity { if (reason != null && reason != StatusCode.NO_ERROR) { //LoginFailed showToast(res.getString(reason == StatusCode.MAINTENANCE ? reason.getErrorString() : R.string.err_login_failed_toast)); } - } catch (Exception e) { + } catch (final Exception e) { Log.w("MainActivity.firstLoginHander", e); } } @@ -189,6 +194,10 @@ public class MainActivity extends AbstractActivity { public void onCreate(final Bundle savedInstanceState) { // don't call the super implementation with the layout argument, as that would set the wrong theme super.onCreate(savedInstanceState); + + // Disable the up navigation for this activity + getSupportActionBar().setDisplayHomeAsUpEnabled(false); + setContentView(R.layout.main_activity); ButterKnife.inject(this); @@ -204,6 +213,8 @@ public class MainActivity extends AbstractActivity { Log.i("Starting " + getPackageName() + ' ' + version + " a.k.a " + Version.getVersionName(this)); init(); + + checkShowChangelog(); } @Override @@ -262,6 +273,11 @@ public class MainActivity extends AbstractActivity { @Override public boolean onCreateOptionsMenu(final Menu menu) { getMenuInflater().inflate(R.menu.main_activity_options, menu); + final SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE); + final MenuItem searchItem = menu.findItem(R.id.menu_gosearch); + final SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem); + searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName())); + return true; } @@ -276,6 +292,10 @@ public class MainActivity extends AbstractActivity { public boolean onOptionsItemSelected(final MenuItem item) { final int id = item.getItemId(); switch (id) { + case android.R.id.home: + // this activity must handle the home navigation different than all others + showAbout(null); + return true; case R.id.menu_about: showAbout(null); return true; @@ -303,13 +323,12 @@ public class MainActivity extends AbstractActivity { } }); return true; - default: - return super.onOptionsItemSelected(item); } + return super.onOptionsItemSelected(item); } private void startScannerApplication() { - IntentIntegrator integrator = new IntentIntegrator(this); + final IntentIntegrator integrator = new IntentIntegrator(this); // integrator dialog is English only, therefore localize it integrator.setButtonYesByID(android.R.string.yes); integrator.setButtonNoByID(android.R.string.no); @@ -320,9 +339,9 @@ public class MainActivity extends AbstractActivity { @Override public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) { - IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent); + final IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent); if (scanResult != null) { - String scan = scanResult.getContents(); + final String scan = scanResult.getContents(); if (StringUtils.isBlank(scan)) { return; } @@ -434,7 +453,7 @@ public class MainActivity extends AbstractActivity { cacheTypes.add(CacheType.MYSTERY); // then add all other cache types sorted alphabetically - List<CacheType> sorted = new ArrayList<CacheType>(); + final List<CacheType> sorted = new ArrayList<CacheType>(); sorted.addAll(Arrays.asList(CacheType.values())); sorted.removeAll(cacheTypes); @@ -453,18 +472,18 @@ public class MainActivity extends AbstractActivity { checkedItem = 0; } - String[] items = new String[cacheTypes.size()]; + final String[] items = new String[cacheTypes.size()]; for (int i = 0; i < cacheTypes.size(); i++) { items[i] = cacheTypes.get(i).getL10n(); } - Builder builder = new AlertDialog.Builder(this); + final Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.menu_filter); builder.setSingleChoiceItems(items, checkedItem, new DialogInterface.OnClickListener() { @Override public void onClick(final DialogInterface dialog, final int position) { - CacheType cacheType = cacheTypes.get(position); + final CacheType cacheType = cacheTypes.get(position); Settings.setCacheType(cacheType); setFilterTitle(); dialog.dismiss(); @@ -524,7 +543,7 @@ public class MainActivity extends AbstractActivity { navType.setText(res.getString(geo.getLocationProvider().resourceId)); if (geo.getAccuracy() >= 0) { - int speed = Math.round(geo.getSpeed()) * 60 * 60 / 1000; + final int speed = Math.round(geo.getSpeed()) * 60 * 60 / 1000; navAccuracy.setText("±" + Units.getDistanceFromMeters(geo.getAccuracy()) + Formatter.SEPARATOR + Units.getSpeed(speed)); } else { navAccuracy.setText(null); @@ -633,7 +652,7 @@ public class MainActivity extends AbstractActivity { } private class CountBubbleUpdateThread extends Thread { - private Handler countBubbleHandler = new Handler() { + private final Handler countBubbleHandler = new Handler() { @Override public void handleMessage(final Message msg) { @@ -645,7 +664,7 @@ public class MainActivity extends AbstractActivity { countBubble.bringToFront(); countBubble.setVisibility(View.VISIBLE); } - } catch (Exception e) { + } catch (final Exception e) { Log.w("MainActivity.countBubbleHander", e); } } @@ -662,7 +681,7 @@ public class MainActivity extends AbstractActivity { try { sleep(500); checks++; - } catch (Exception e) { + } catch (final Exception e) { Log.e("MainActivity.CountBubbleUpdateThread.run", e); } @@ -705,20 +724,21 @@ public class MainActivity extends AbstractActivity { } } - /** - * @param view - * unused here but needed since this method is referenced from XML layout - */ - public void showAbout(final View view) { - startActivity(new Intent(this, AboutActivity.class)); + private void checkShowChangelog() { + final int lastVersion = Settings.getLastChangelogVersion(); + final int version = Version.getVersionCode(this); + Settings.setLastChangelogVersion(version); + // don't show change log after new install... + if (lastVersion > 0 && version != lastVersion) { + AboutActivity.showChangeLog(this); + } } /** * @param view * unused here but needed since this method is referenced from XML layout */ - public void goSearch(final View view) { - onSearchRequested(); + public void showAbout(final View view) { + startActivity(new Intent(this, AboutActivity.class)); } - } diff --git a/main/src/cgeo/geocaching/NavigateAnyPointActivity.java b/main/src/cgeo/geocaching/NavigateAnyPointActivity.java index 0a750e0..39531f1 100644 --- a/main/src/cgeo/geocaching/NavigateAnyPointActivity.java +++ b/main/src/cgeo/geocaching/NavigateAnyPointActivity.java @@ -4,7 +4,7 @@ import butterknife.ButterKnife; import butterknife.InjectView; import butterknife.Optional; -import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.activity.AbstractActionBarActivity; import cgeo.geocaching.apps.cache.navi.NavigationAppFactory; import cgeo.geocaching.geopoint.DistanceParser; import cgeo.geocaching.geopoint.Geopoint; @@ -44,7 +44,7 @@ import android.widget.TextView; import java.util.List; -public class NavigateAnyPointActivity extends AbstractActivity { +public class NavigateAnyPointActivity extends AbstractActionBarActivity implements CoordinatesInputDialog.CoordinateUpdate { @InjectView(R.id.historyList) protected ListView historyListView; @@ -278,18 +278,17 @@ public class NavigateAnyPointActivity extends AbstractActivity { if (latButton.getText().length() > 0 && lonButton.getText().length() > 0) { gp = new Geopoint(latButton.getText().toString() + " " + lonButton.getText().toString()); } - CoordinatesInputDialog coordsDialog = new CoordinatesInputDialog(NavigateAnyPointActivity.this, null, gp, app.currentGeo()); + CoordinatesInputDialog coordsDialog = CoordinatesInputDialog.getInstance(null, gp, app.currentGeo()); coordsDialog.setCancelable(true); - coordsDialog.setOnCoordinateUpdate(new CoordinatesInputDialog.CoordinateUpdate() { - @Override - public void update(Geopoint gp) { - latButton.setText(gp.format(GeopointFormatter.Format.LAT_DECMINUTE)); - lonButton.setText(gp.format(GeopointFormatter.Format.LON_DECMINUTE)); - changed = true; - } - }); - coordsDialog.show(); + coordsDialog.show(getSupportFragmentManager(),"wpedit_dialog"); } + + } + @Override + public void updateCoordinates(Geopoint gp) { + latButton.setText(gp.format(GeopointFormatter.Format.LAT_DECMINUTE)); + lonButton.setText(gp.format(GeopointFormatter.Format.LON_DECMINUTE)); + changed = true; } private static class ChangeDistanceUnit implements OnItemSelectedListener { @@ -328,7 +327,7 @@ public class NavigateAnyPointActivity extends AbstractActivity { menu.findItem(R.id.menu_default_navigation).setVisible(visible); menu.findItem(R.id.menu_caches_around).setVisible(visible); - menu.findItem(R.id.menu_clear_history).setEnabled(!getHistoryOfSearchedLocations().isEmpty()); + menu.findItem(R.id.menu_clear_history).setVisible(!getHistoryOfSearchedLocations().isEmpty()); } catch (RuntimeException e) { // nothing } @@ -362,9 +361,8 @@ public class NavigateAnyPointActivity extends AbstractActivity { case R.id.menu_navigate: NavigationAppFactory.showNavigationMenu(this, null, null, coords); return true; - default: - return false; } + return super.onOptionsItemSelected(item); } private void addToHistory(final Geopoint coords) { diff --git a/main/src/cgeo/geocaching/SearchActivity.java b/main/src/cgeo/geocaching/SearchActivity.java index 2a37e27..81dec98 100644 --- a/main/src/cgeo/geocaching/SearchActivity.java +++ b/main/src/cgeo/geocaching/SearchActivity.java @@ -3,7 +3,7 @@ package cgeo.geocaching; import butterknife.ButterKnife; import butterknife.InjectView; -import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.activity.AbstractActionBarActivity; import cgeo.geocaching.connector.ConnectorFactory; import cgeo.geocaching.connector.IConnector; import cgeo.geocaching.connector.capability.ISearchByGeocode; @@ -37,7 +37,7 @@ import android.widget.Button; import java.util.Locale; -public class SearchActivity extends AbstractActivity { +public class SearchActivity extends AbstractActionBarActivity implements CoordinatesInputDialog.CoordinateUpdate { @InjectView(R.id.buttonLatitude) protected Button buttonLatitude; @InjectView(R.id.buttonLongitude) protected Button buttonLongitude; @@ -174,8 +174,20 @@ public class SearchActivity extends AbstractActivity { } private void init() { - buttonLatitude.setOnClickListener(new FindByCoordsAction()); - buttonLongitude.setOnClickListener(new FindByCoordsAction()); + buttonLatitude.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + updateCoordinates(); + } + }); + buttonLongitude.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + updateCoordinates(); + } + }); buttonSearchCoords.setOnClickListener(new View.OnClickListener() { @@ -277,21 +289,16 @@ public class SearchActivity extends AbstractActivity { } } - private class FindByCoordsAction implements OnClickListener { - - @Override - public void onClick(final View arg0) { - final CoordinatesInputDialog coordsDialog = new CoordinatesInputDialog(SearchActivity.this, null, null, app.currentGeo()); - coordsDialog.setCancelable(true); - coordsDialog.setOnCoordinateUpdate(new CoordinatesInputDialog.CoordinateUpdate() { - @Override - public void update(final Geopoint gp) { - buttonLatitude.setText(gp.format(GeopointFormatter.Format.LAT_DECMINUTE)); - buttonLongitude.setText(gp.format(GeopointFormatter.Format.LON_DECMINUTE)); - } - }); - coordsDialog.show(); - } + private void updateCoordinates() { + final CoordinatesInputDialog coordsDialog = CoordinatesInputDialog.getInstance(null, null, app.currentGeo()); + coordsDialog.setCancelable(true); + coordsDialog.show(getSupportFragmentManager(), "wpedit_dialog"); + } + + @Override + public void updateCoordinates(final Geopoint gp) { + buttonLatitude.setText(gp.format(GeopointFormatter.Format.LAT_DECMINUTE)); + buttonLongitude.setText(gp.format(GeopointFormatter.Format.LON_DECMINUTE)); } private void findByCoordsFn() { diff --git a/main/src/cgeo/geocaching/SearchResult.java b/main/src/cgeo/geocaching/SearchResult.java index 12a2522..1916afd 100644 --- a/main/src/cgeo/geocaching/SearchResult.java +++ b/main/src/cgeo/geocaching/SearchResult.java @@ -11,7 +11,9 @@ import cgeo.geocaching.gcvote.GCVote; 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.Observable; import rx.functions.Func1; import rx.functions.Func2; @@ -60,6 +62,14 @@ public class SearchResult implements Parcelable { } /** + * Build a new empty search result with an error status. + */ + public SearchResult(final StatusCode statusCode) { + this(); + error = statusCode; + } + + /** * Copy a search result, for example to apply different filters on it. * * @param searchResult the original search result, which cannot be null @@ -155,6 +165,7 @@ public class SearchResult implements Parcelable { return 0; } + @NonNull public Set<String> getGeocodes() { return Collections.unmodifiableSet(geocodes); } @@ -218,7 +229,7 @@ public class SearchResult implements Parcelable { int excluded = 0; for (Geocache cache : caches) { // Is there any reason to exclude the cache from the list? - final boolean excludeCache = (excludeDisabled && cache.isDisabled()) || + final boolean excludeCache = (excludeDisabled && (cache.isDisabled() || cache.isArchived())) || (excludeMine && (cache.isOwner() || cache.isFound())) || (!cacheType.contains(cache)); if (excludeCache) { diff --git a/main/src/cgeo/geocaching/SelectMapfileActivity.java b/main/src/cgeo/geocaching/SelectMapfileActivity.java index c617012..b2cdf17 100644 --- a/main/src/cgeo/geocaching/SelectMapfileActivity.java +++ b/main/src/cgeo/geocaching/SelectMapfileActivity.java @@ -115,8 +115,7 @@ public class SelectMapfileActivity extends AbstractFileListActivity<FileSelectio } if (requestCode == REQUEST_DIRECTORY) { - final String directory = new File(data.getData().getPath()).getAbsolutePath(); - mapFile = directory; + mapFile = new File(data.getData().getPath()).getAbsolutePath(); close(); } } diff --git a/main/src/cgeo/geocaching/StaticMapsActivity.java b/main/src/cgeo/geocaching/StaticMapsActivity.java index 16fce37..134e134 100644 --- a/main/src/cgeo/geocaching/StaticMapsActivity.java +++ b/main/src/cgeo/geocaching/StaticMapsActivity.java @@ -1,8 +1,9 @@ package cgeo.geocaching; -import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.activity.AbstractActionBarActivity; import cgeo.geocaching.enumerations.LoadFlags; import cgeo.geocaching.utils.Log; +import cgeo.geocaching.utils.RxUtils; import org.androidannotations.annotations.EActivity; import org.androidannotations.annotations.Extra; @@ -11,7 +12,6 @@ import org.androidannotations.annotations.OptionsMenu; import org.apache.commons.collections4.CollectionUtils; import android.app.ProgressDialog; -import android.content.Context; import android.graphics.Bitmap; import android.os.Bundle; import android.os.Handler; @@ -25,7 +25,7 @@ import java.util.List; @EActivity @OptionsMenu(R.menu.static_maps_activity_options) -public class StaticMapsActivity extends AbstractActivity { +public class StaticMapsActivity extends AbstractActionBarActivity { private static final String EXTRAS_WAYPOINT = "waypoint"; private static final String EXTRAS_DOWNLOAD = "download"; @@ -153,7 +153,7 @@ public class StaticMapsActivity extends AbstractActivity { final Geocache cache = DataStore.loadCache(geocode, LoadFlags.LOAD_CACHE_OR_DB); if (waypointId == null) { showToast(res.getString(R.string.info_storing_static_maps)); - StaticMapsProvider.storeCacheStaticMap(cache, true); + RxUtils.waitForCompletion(StaticMapsProvider.storeCacheStaticMap(cache)); return cache.hasStaticMap(); } final Waypoint waypoint = cache.getWaypointById(waypointId); @@ -161,18 +161,10 @@ public class StaticMapsActivity extends AbstractActivity { showToast(res.getString(R.string.info_storing_static_maps)); // refresh always removes old waypoint files StaticMapsProvider.removeWpStaticMaps(waypoint, geocode); - StaticMapsProvider.storeWaypointStaticMap(cache, waypoint, true); + RxUtils.waitForCompletion(StaticMapsProvider.storeWaypointStaticMap(cache, waypoint)); return StaticMapsProvider.hasStaticMapForWaypoint(geocode, waypoint); } showToast(res.getString(R.string.err_detail_not_load_map_static)); return false; } - - public static void startActivity(final Context activity, final String geocode, final boolean download, final Waypoint waypoint) { - StaticMapsActivity_.IntentBuilder_ builder = StaticMapsActivity_.intent(activity).geocode(geocode).download(download); - if (waypoint != null) { - builder.waypointId(waypoint.getId()); - } - builder.start(); - } }
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/StaticMapsProvider.java b/main/src/cgeo/geocaching/StaticMapsProvider.java index eaab159..0551fc0 100644 --- a/main/src/cgeo/geocaching/StaticMapsProvider.java +++ b/main/src/cgeo/geocaching/StaticMapsProvider.java @@ -1,7 +1,6 @@ package cgeo.geocaching; import cgeo.geocaching.compatibility.Compatibility; -import cgeo.geocaching.concurrent.BlockingThreadPool; import cgeo.geocaching.files.LocalStorage; import cgeo.geocaching.geopoint.GeopointFormatter.Format; import cgeo.geocaching.network.Network; @@ -11,15 +10,22 @@ import cgeo.geocaching.utils.FileUtils; import cgeo.geocaching.utils.Log; import ch.boye.httpclientandroidlib.HttpResponse; + import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.NonNull; +import rx.Observable; +import rx.functions.Action0; +import rx.schedulers.Schedulers; +import rx.util.async.Async; + import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Point; import java.io.File; -import java.util.concurrent.TimeUnit; +import java.util.LinkedList; +import java.util.List; public final class StaticMapsProvider { static final int MAPS_LEVEL_MAX = 5; @@ -34,9 +40,6 @@ public final class StaticMapsProvider { /** We assume there is no real usable image with less than 1k. */ private static final int MIN_MAP_IMAGE_BYTES = 1000; - /** ThreadPool restricting this to 1 Thread. **/ - private static final BlockingThreadPool POOL = new BlockingThreadPool(1, Thread.MIN_PRIORITY); - /** * max size in free API version: https://developers.google.com/maps/documentation/staticmaps/#Imagesizes */ @@ -50,69 +53,78 @@ public final class StaticMapsProvider { return LocalStorage.getStorageFile(geocode, MAP_FILENAME_PREFIX + prefix, false, createDirs); } - private static void downloadDifferentZooms(final String geocode, final String markerUrl, final String prefix, final String latlonMap, final int edge, final Parameters waypoints) { - downloadMap(geocode, 20, SATELLITE, markerUrl, prefix + '1', "", latlonMap, edge, edge, waypoints); - downloadMap(geocode, 18, SATELLITE, markerUrl, prefix + '2', "", latlonMap, edge, edge, waypoints); - downloadMap(geocode, 16, ROADMAP, markerUrl, prefix + '3', "", latlonMap, edge, edge, waypoints); - downloadMap(geocode, 14, ROADMAP, markerUrl, prefix + '4', "", latlonMap, edge, edge, waypoints); - downloadMap(geocode, 11, ROADMAP, markerUrl, prefix + '5', "", latlonMap, edge, edge, waypoints); + private static Observable<String> downloadDifferentZooms(final String geocode, final String markerUrl, final String prefix, final String latlonMap, final int edge, final Parameters waypoints) { + return Observable.merge(downloadMap(geocode, 20, SATELLITE, markerUrl, prefix + '1', "", latlonMap, edge, edge, waypoints), + downloadMap(geocode, 18, SATELLITE, markerUrl, prefix + '2', "", latlonMap, edge, edge, waypoints), + downloadMap(geocode, 16, ROADMAP, markerUrl, prefix + '3', "", latlonMap, edge, edge, waypoints), + downloadMap(geocode, 14, ROADMAP, markerUrl, prefix + '4', "", latlonMap, edge, edge, waypoints), + downloadMap(geocode, 11, ROADMAP, markerUrl, prefix + '5', "", latlonMap, edge, edge, waypoints)); } - private static void downloadMap(final String geocode, final int zoom, final String mapType, final String markerUrl, final String prefix, final String shadow, final String latlonMap, final int width, final int height, final Parameters waypoints) { - final Parameters params = new Parameters( - "center", latlonMap, - "zoom", String.valueOf(zoom), - "size", String.valueOf(limitSize(width)) + 'x' + String.valueOf(limitSize(height)), - "maptype", mapType, - "markers", "icon:" + markerUrl + '|' + shadow + latlonMap, - "sensor", "false"); - if (waypoints != null) { - params.addAll(waypoints); - } - final HttpResponse httpResponse = Network.getRequest(GOOGLE_STATICMAP_URL, params); + private static Observable<String> downloadMap(final String geocode, final int zoom, final String mapType, final String markerUrl, final String prefix, final String shadow, final String latlonMap, final int width, final int height, final Parameters waypoints) { + return Async.fromAction(new Action0() { + @Override + public void call() { + final Parameters params = new Parameters( + "center", latlonMap, + "zoom", String.valueOf(zoom), + "size", String.valueOf(limitSize(width)) + 'x' + String.valueOf(limitSize(height)), + "maptype", mapType, + "markers", "icon:" + markerUrl + '|' + shadow + latlonMap, + "sensor", "false"); + if (waypoints != null) { + params.addAll(waypoints); + } + final HttpResponse httpResponse = Network.getRequest(GOOGLE_STATICMAP_URL, params); - if (httpResponse == null) { - Log.e("StaticMapsProvider.downloadMap: httpResponse is null"); - return; - } - if (httpResponse.getStatusLine().getStatusCode() != 200) { - Log.d("StaticMapsProvider.downloadMap: httpResponseCode = " + httpResponse.getStatusLine().getStatusCode()); - return; - } - final File file = getMapFile(geocode, prefix, true); - if (LocalStorage.saveEntityToFile(httpResponse, file)) { - // Delete image if it has no contents - final long fileSize = file.length(); - if (fileSize < MIN_MAP_IMAGE_BYTES) { - FileUtils.deleteIgnoringFailure(file); + if (httpResponse == null) { + Log.e("StaticMapsProvider.downloadMap: httpResponse is null"); + return; + } + if (httpResponse.getStatusLine().getStatusCode() != 200) { + Log.d("StaticMapsProvider.downloadMap: httpResponseCode = " + httpResponse.getStatusLine().getStatusCode()); + return; + } + final File file = getMapFile(geocode, prefix, true); + if (LocalStorage.saveEntityToFile(httpResponse, file)) { + // Delete image if it has no contents + final long fileSize = file.length(); + if (fileSize < MIN_MAP_IMAGE_BYTES) { + FileUtils.deleteIgnoringFailure(file); + } + } } - } + }, prefix, Schedulers.io()); } private static int limitSize(final int imageSize) { return Math.min(imageSize, GOOGLE_MAPS_MAX_SIZE); } - public static void downloadMaps(final Geocache cache) { + public static Observable<String> downloadMaps(final Geocache cache) { if ((!Settings.isStoreOfflineMaps() && !Settings.isStoreOfflineWpMaps()) || StringUtils.isBlank(cache.getGeocode())) { - return; + return Observable.empty(); } int edge = guessMaxDisplaySide(); + final List<Observable<String>> downloaders = new LinkedList<Observable<String>>(); + if (Settings.isStoreOfflineMaps() && cache.getCoords() != null) { - storeCachePreviewMap(cache); - storeCacheStaticMap(cache, edge, false); + downloaders.add(storeCachePreviewMap(cache)); + downloaders.add(storeCacheStaticMap(cache, edge)); } // clean old and download static maps for waypoints if one is missing if (Settings.isStoreOfflineWpMaps()) { for (final Waypoint waypoint : cache.getWaypoints()) { if (!hasAllStaticMapsForWaypoint(cache.getGeocode(), waypoint)) { - refreshAllWpStaticMaps(cache, edge); + downloaders.add(refreshAllWpStaticMaps(cache, edge)); } } } + + return Observable.merge(downloaders); } /** @@ -123,44 +135,47 @@ public final class StaticMapsProvider { * @param edge * The boundings */ - private static void refreshAllWpStaticMaps(final Geocache cache, final int edge) { + private static Observable<String> refreshAllWpStaticMaps(final Geocache cache, final int edge) { LocalStorage.deleteFilesWithPrefix(cache.getGeocode(), MAP_FILENAME_PREFIX + WAYPOINT_PREFIX); + final List<Observable<String>> downloaders = new LinkedList<Observable<String>>(); for (Waypoint waypoint : cache.getWaypoints()) { - storeWaypointStaticMap(cache.getGeocode(), edge, waypoint, false); + downloaders.add(storeWaypointStaticMap(cache.getGeocode(), edge, waypoint)); } + return Observable.merge(downloaders); } - public static void storeWaypointStaticMap(final Geocache cache, final Waypoint waypoint, final boolean waitForResult) { - int edge = StaticMapsProvider.guessMaxDisplaySide(); - storeWaypointStaticMap(cache.getGeocode(), edge, waypoint, waitForResult); + public static Observable<String> storeWaypointStaticMap(final Geocache cache, final Waypoint waypoint) { + final int edge = StaticMapsProvider.guessMaxDisplaySide(); + return storeWaypointStaticMap(cache.getGeocode(), edge, waypoint); } - private static void storeWaypointStaticMap(final String geocode, final int edge, final Waypoint waypoint, final boolean waitForResult) { + private static Observable<String> storeWaypointStaticMap(final String geocode, final int edge, final Waypoint waypoint) { if (geocode == null) { Log.e("storeWaypointStaticMap - missing input parameter geocode"); - return; + return Observable.empty(); } if (waypoint == null) { Log.e("storeWaypointStaticMap - missing input parameter waypoint"); - return; + return Observable.empty(); } if (waypoint.getCoords() == null) { - return; + return Observable.empty(); } String wpLatlonMap = waypoint.getCoords().format(Format.LAT_LON_DECDEGREE_COMMA); String wpMarkerUrl = getWpMarkerUrl(waypoint); if (!hasAllStaticMapsForWaypoint(geocode, waypoint)) { // download map images in separate background thread for higher performance - downloadMaps(geocode, wpMarkerUrl, WAYPOINT_PREFIX + waypoint.getId() + '_' + waypoint.getStaticMapsHashcode() + "_", wpLatlonMap, edge, null, waitForResult); + return downloadMaps(geocode, wpMarkerUrl, WAYPOINT_PREFIX + waypoint.getId() + '_' + waypoint.getStaticMapsHashcode() + "_", wpLatlonMap, edge, null); } + return Observable.empty(); } - public static void storeCacheStaticMap(final Geocache cache, final boolean waitForResult) { + public static Observable<String> storeCacheStaticMap(final Geocache cache) { int edge = guessMaxDisplaySide(); - storeCacheStaticMap(cache, edge, waitForResult); + return storeCacheStaticMap(cache, edge); } - private static void storeCacheStaticMap(final Geocache cache, final int edge, final boolean waitForResult) { + private static Observable<String> storeCacheStaticMap(final Geocache cache, final int edge) { final String latlonMap = cache.getCoords().format(Format.LAT_LON_DECDEGREE_COMMA); final Parameters waypoints = new Parameters(); for (final Waypoint waypoint : cache.getWaypoints()) { @@ -172,15 +187,15 @@ public final class StaticMapsProvider { } // download map images in separate background thread for higher performance final String cacheMarkerUrl = getCacheMarkerUrl(cache); - downloadMaps(cache.getGeocode(), cacheMarkerUrl, "", latlonMap, edge, waypoints, waitForResult); + return downloadMaps(cache.getGeocode(), cacheMarkerUrl, "", latlonMap, edge, waypoints); } - public static void storeCachePreviewMap(final Geocache cache) { + public static Observable<String> storeCachePreviewMap(final Geocache cache) { final String latlonMap = cache.getCoords().format(Format.LAT_LON_DECDEGREE_COMMA); final Point displaySize = Compatibility.getDisplaySize(); final int minSize = Math.min(displaySize.x, displaySize.y); final String markerUrl = MARKERS_URL + "my_location_mdpi.png"; - downloadMap(cache.getGeocode(), 15, ROADMAP, markerUrl, PREFIX_PREVIEW, "shadow:false|", latlonMap, minSize, minSize, null); + return downloadMap(cache.getGeocode(), 15, ROADMAP, markerUrl, PREFIX_PREVIEW, "shadow:false|", latlonMap, minSize, minSize, null); } private static int guessMaxDisplaySide() { @@ -188,24 +203,10 @@ public final class StaticMapsProvider { return Math.max(displaySize.x, displaySize.y) - 25; } - private static void downloadMaps(final String geocode, final String markerUrl, final String prefix, final String latlonMap, final int edge, - final Parameters waypoints, final boolean waitForResult) { - if (waitForResult) { - downloadDifferentZooms(geocode, markerUrl, prefix, latlonMap, edge, waypoints); - } - else { - final Runnable currentTask = new Runnable() { - @Override - public void run() { - downloadDifferentZooms(geocode, markerUrl, prefix, latlonMap, edge, waypoints); - } - }; - try { - POOL.add(currentTask, 20, TimeUnit.SECONDS); - } catch (InterruptedException e) { - Log.e("StaticMapsProvider.downloadMaps error adding task", e); - } - } + private static Observable<String> downloadMaps(final String geocode, final String markerUrl, final String prefix, + final String latlonMap, final int edge, + final Parameters waypoints) { + return downloadDifferentZooms(geocode, markerUrl, prefix, latlonMap, edge, waypoints); } private static String getCacheMarkerUrl(final Geocache cache) { @@ -213,7 +214,7 @@ public final class StaticMapsProvider { url.append("marker_cache_").append(cache.getType().id); if (cache.isFound()) { url.append("_found"); - } else if (cache.isDisabled()) { + } else if (cache.isDisabled() || cache.isArchived()) { url.append("_disabled"); } url.append(".png"); diff --git a/main/src/cgeo/geocaching/TrackableActivity.java b/main/src/cgeo/geocaching/TrackableActivity.java index 81d23c9..2a228c1 100644 --- a/main/src/cgeo/geocaching/TrackableActivity.java +++ b/main/src/cgeo/geocaching/TrackableActivity.java @@ -25,6 +25,7 @@ import cgeo.geocaching.utils.UnknownTagsHandler; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; + import rx.android.observables.AndroidObservable; import rx.android.observables.ViewObservable; import rx.functions.Action1; @@ -36,12 +37,15 @@ import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; +import android.support.v7.app.ActionBar; +import android.support.v7.view.ActionMode; import android.text.Html; -import android.view.ContextMenu; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnLongClickListener; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ScrollView; @@ -74,7 +78,7 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi private final Handler loadTrackableHandler = new Handler() { @Override - public void handleMessage(Message msg) { + public void handleMessage(final Message msg) { if (trackable == null) { if (waitDialog != null) { waitDialog.dismiss(); @@ -110,13 +114,27 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi if (waitDialog != null) { waitDialog.dismiss(); } + + // if we have a newer Android device setup Android Beam for easy cache sharing + initializeAndroidBeam( + new ActivitySharingInterface() { + @Override + public String getUri() { + return trackable.getUrl(); + } + } + ); } }; private CharSequence clickedItemText = null; + /** + * Action mode of the current contextual action bar (e.g. for copy and share actions). + */ + private ActionMode currentActionMode; @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState, R.layout.viewpager_activity); // set title in code, as the activity needs a hard coded title due to the intent filters @@ -198,43 +216,13 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi } @Override - public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo info) { - super.onCreateContextMenu(menu, view, info); - final int viewId = view.getId(); - assert view instanceof TextView; - clickedItemText = ((TextView) view).getText(); - switch (viewId) { - case R.id.value: // name, TB-code, origin, released, distance - final String itemTitle = (String) ((TextView) ((View) view.getParent()).findViewById(R.id.name)).getText(); - buildDetailsContextMenu(menu, clickedItemText, itemTitle, true); - break; - case R.id.goal: - buildDetailsContextMenu(menu, clickedItemText, res.getString(R.string.trackable_goal), false); - break; - case R.id.details: - buildDetailsContextMenu(menu, clickedItemText, res.getString(R.string.trackable_details), false); - break; - case R.id.log: - buildDetailsContextMenu(menu, clickedItemText, res.getString(R.string.cache_logs), false); - break; - default: - break; - } - } - - @Override - public boolean onContextItemSelected(MenuItem item) { - return onClipboardItemSelected(item, clickedItemText) || onOptionsItemSelected(item); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { + public boolean onCreateOptionsMenu(final Menu menu) { getMenuInflater().inflate(R.menu.trackable_activity, menu); return true; } @Override - public boolean onOptionsItemSelected(MenuItem item) { + public boolean onOptionsItemSelected(final MenuItem item) { switch (item.getItemId()) { case R.id.menu_log_touch: LogTrackableActivity.startActivity(this, trackable); @@ -242,16 +230,15 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi case R.id.menu_browser_trackable: startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(trackable.getUrl()))); return true; - default: - return false; } + return super.onOptionsItemSelected(item); } @Override - public boolean onPrepareOptionsMenu(Menu menu) { + public boolean onPrepareOptionsMenu(final Menu menu) { if (trackable != null) { - menu.findItem(R.id.menu_log_touch).setEnabled(StringUtils.isNotBlank(geocode) && trackable.isLoggable()); - menu.findItem(R.id.menu_browser_trackable).setEnabled(StringUtils.isNotBlank(trackable.getUrl())); + menu.findItem(R.id.menu_log_touch).setVisible(StringUtils.isNotBlank(geocode) && trackable.isLoggable()); + menu.findItem(R.id.menu_browser_trackable).setVisible(StringUtils.isNotBlank(trackable.getUrl())); } return super.onPrepareOptionsMenu(menu); } @@ -262,7 +249,7 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi final private String guid; final private String id; - public LoadTrackableThread(Handler handlerIn, String geocodeIn, String guidIn, String idIn) { + public LoadTrackableThread(final Handler handlerIn, final String geocodeIn, final String guidIn, final String idIn) { handler = handlerIn; geocode = geocodeIn; guid = guidIn; @@ -272,19 +259,20 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi @Override public void run() { if (StringUtils.isNotEmpty(geocode)) { - trackable = DataStore.loadTrackable(geocode); - - if (trackable == null || trackable.isLoggable()) { - // iterate over the connectors as some codes may be handled by multiple connectors - for (final TrackableConnector trackableConnector : ConnectorFactory.getTrackableConnectors()) { - if (trackableConnector.canHandleTrackable(geocode)) { - trackable = trackableConnector.searchTrackable(geocode, guid, id); - if (trackable != null) { - break; - } + + // iterate over the connectors as some codes may be handled by multiple connectors + for (final TrackableConnector trackableConnector : ConnectorFactory.getTrackableConnectors()) { + if (trackableConnector.canHandleTrackable(geocode)) { + trackable = trackableConnector.searchTrackable(geocode, guid, id); + if (trackable != null) { + break; } } } + // Check local storage (offline case) + if (trackable == null) { + trackable = DataStore.loadTrackable(geocode); + } } // fall back to GC search by GUID if (trackable == null) { @@ -298,7 +286,7 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi final private String url; final private Handler handler; - public TrackableIconThread(String urlIn, Handler handlerIn) { + public TrackableIconThread(final String urlIn, final Handler handlerIn) { url = urlIn; handler = handlerIn; } @@ -322,18 +310,18 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi } private static class TrackableIconHandler extends Handler { - final private TextView view; + final private ActionBar view; - public TrackableIconHandler(TextView viewIn) { + public TrackableIconHandler(final ActionBar viewIn) { view = viewIn; } @Override - public void handleMessage(Message message) { + public void handleMessage(final Message message) { final BitmapDrawable image = (BitmapDrawable) message.obj; if (image != null && view != null) { image.setBounds(0, 0, view.getHeight(), view.getHeight()); - view.setCompoundDrawables(image, null, null, null); + view.setIcon(image); } } } @@ -348,7 +336,7 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi } @Override - protected PageViewCreator createViewCreator(Page page) { + protected PageViewCreator createViewCreator(final Page page) { switch (page) { case DETAILS: return new DetailsViewCreator(); @@ -359,7 +347,7 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi } @Override - protected String getTitle(Page page) { + protected String getTitle(final Page page) { return res.getString(page.resId); } @@ -392,13 +380,13 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi // action bar icon if (StringUtils.isNotBlank(trackable.getIconUrl())) { - final TrackableIconHandler iconHandler = new TrackableIconHandler(((TextView) findViewById(R.id.actionbar_title))); + final TrackableIconHandler iconHandler = new TrackableIconHandler(getSupportActionBar()); final TrackableIconThread iconThread = new TrackableIconThread(trackable.getIconUrl(), iconHandler); iconThread.start(); } // trackable name - registerForContextMenu(details.add(R.string.trackable_name, StringUtils.isNotBlank(trackable.getName()) ? Html.fromHtml(trackable.getName()).toString() : res.getString(R.string.trackable_unknown))); + addContextMenu(details.add(R.string.trackable_name, StringUtils.isNotBlank(trackable.getName()) ? Html.fromHtml(trackable.getName()).toString() : res.getString(R.string.trackable_unknown))); // trackable type String tbType; @@ -410,7 +398,7 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi details.add(R.string.trackable_type, tbType); // trackable geocode - registerForContextMenu(details.add(R.string.trackable_code, trackable.getGeocode())); + addContextMenu(details.add(R.string.trackable_code, trackable.getGeocode())); // trackable owner final TextView owner = details.add(R.string.trackable_owner, res.getString(R.string.trackable_unknown)); @@ -455,7 +443,7 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi if (Trackable.SPOTTED_CACHE == trackable.getSpottedType()) { spotted.setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View arg0) { + public void onClick(final View arg0) { if (StringUtils.isNotBlank(trackable.getSpottedGuid())) { CacheDetailActivity.startActivityGuid(TrackableActivity.this, trackable.getSpottedGuid(), trackable.getSpottedName()); } @@ -479,17 +467,17 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi if (StringUtils.isNotBlank(trackable.getOrigin())) { final TextView origin = details.add(R.string.trackable_origin, ""); origin.setText(Html.fromHtml(trackable.getOrigin()), TextView.BufferType.SPANNABLE); - registerForContextMenu(origin); + addContextMenu(origin); } // trackable released if (trackable.getReleased() != null) { - registerForContextMenu(details.add(R.string.trackable_released, Formatter.formatDate(trackable.getReleased().getTime()))); + addContextMenu(details.add(R.string.trackable_released, Formatter.formatDate(trackable.getReleased().getTime()))); } // trackable distance if (trackable.getDistance() >= 0) { - registerForContextMenu(details.add(R.string.trackable_distance, Units.getDistanceFromKilometers(trackable.getDistance()))); + addContextMenu(details.add(R.string.trackable_distance, Units.getDistanceFromKilometers(trackable.getDistance()))); } // trackable goal @@ -498,7 +486,7 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi goalTextView.setVisibility(View.VISIBLE); goalTextView.setText(Html.fromHtml(trackable.getGoal(), new HtmlImage(geocode, true, 0, false), null), TextView.BufferType.SPANNABLE); goalTextView.setMovementMethod(AnchorAwareLinkMovementMethod.getInstance()); - registerForContextMenu(goalTextView); + addContextMenu(goalTextView); } // trackable details @@ -507,7 +495,7 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi detailsTextView.setVisibility(View.VISIBLE); detailsTextView.setText(Html.fromHtml(trackable.getDetails(), new HtmlImage(geocode, true, 0, false), new UnknownTagsHandler()), TextView.BufferType.SPANNABLE); detailsTextView.setMovementMethod(AnchorAwareLinkMovementMethod.getInstance()); - registerForContextMenu(detailsTextView); + addContextMenu(detailsTextView); } // trackable image @@ -538,4 +526,70 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi } + public void addContextMenu(final View view) { + view.setOnLongClickListener(new OnLongClickListener() { + + @Override + public boolean onLongClick(final View v) { + return startContextualActionBar(view); + } + }); + + view.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(final View v) { + startContextualActionBar(view); + } + }); + } + + private boolean startContextualActionBar(final View view) { + if (currentActionMode != null) { + return false; + } + currentActionMode = startSupportActionMode(new ActionMode.Callback() { + + @Override + public boolean onPrepareActionMode(final ActionMode actionMode, final Menu menu) { + final int viewId = view.getId(); + assert view instanceof TextView; + clickedItemText = ((TextView) view).getText(); + switch (viewId) { + case R.id.value: // name, TB-code, origin, released, distance + final String itemTitle = (String) ((TextView) ((View) view.getParent()).findViewById(R.id.name)).getText(); + buildDetailsContextMenu(actionMode, menu, clickedItemText, itemTitle, true); + return true; + case R.id.goal: + buildDetailsContextMenu(actionMode, menu, clickedItemText, res.getString(R.string.trackable_goal), false); + return true; + case R.id.details: + buildDetailsContextMenu(actionMode, menu, clickedItemText, res.getString(R.string.trackable_details), false); + return true; + case R.id.log: + buildDetailsContextMenu(actionMode, menu, clickedItemText, res.getString(R.string.cache_logs), false); + return true; + } + return false; + } + + @Override + public void onDestroyActionMode(final ActionMode actionMode) { + currentActionMode = null; + } + + @Override + public boolean onCreateActionMode(final ActionMode actionMode, final Menu menu) { + actionMode.getMenuInflater().inflate(R.menu.details_context, menu); + return true; + } + + @Override + public boolean onActionItemClicked(final ActionMode actionMode, final MenuItem menuItem) { + return onClipboardItemSelected(actionMode, menuItem, clickedItemText); + } + }); + return false; + } + } diff --git a/main/src/cgeo/geocaching/UsefulAppsActivity.java b/main/src/cgeo/geocaching/UsefulAppsActivity.java index 39c527d..1159453 100644 --- a/main/src/cgeo/geocaching/UsefulAppsActivity.java +++ b/main/src/cgeo/geocaching/UsefulAppsActivity.java @@ -3,7 +3,7 @@ package cgeo.geocaching; import butterknife.ButterKnife; import butterknife.InjectView; -import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.activity.AbstractActionBarActivity; import cgeo.geocaching.ui.AbstractViewHolder; import android.app.Activity; @@ -18,7 +18,7 @@ import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; -public class UsefulAppsActivity extends AbstractActivity { +public class UsefulAppsActivity extends AbstractActionBarActivity { @InjectView(R.id.apps_list) protected ListView list; diff --git a/main/src/cgeo/geocaching/Waypoint.java b/main/src/cgeo/geocaching/Waypoint.java index efedff5..275882f 100644 --- a/main/src/cgeo/geocaching/Waypoint.java +++ b/main/src/cgeo/geocaching/Waypoint.java @@ -157,7 +157,7 @@ public class Waypoint implements IWaypoint { } public String getUrl() { - return "http://www.geocaching.com//seek/cache_details.aspx?wp=" + geocode; + return "http://www.geocaching.com/seek/cache_details.aspx?wp=" + geocode; } @Override diff --git a/main/src/cgeo/geocaching/WaypointPopup.java b/main/src/cgeo/geocaching/WaypointPopup.java index 916ad4c..f1ffc1d 100644 --- a/main/src/cgeo/geocaching/WaypointPopup.java +++ b/main/src/cgeo/geocaching/WaypointPopup.java @@ -1,115 +1,35 @@ package cgeo.geocaching; -import butterknife.ButterKnife; -import butterknife.InjectView; - -import cgeo.geocaching.apps.cache.navi.NavigationAppFactory; -import cgeo.geocaching.geopoint.Geopoint; -import cgeo.geocaching.geopoint.Units; -import cgeo.geocaching.sensors.IGeoData; -import cgeo.geocaching.ui.CacheDetailsCreator; -import cgeo.geocaching.utils.Log; - -import org.apache.commons.lang3.StringUtils; - import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.Button; -import android.widget.LinearLayout; -import android.widget.TextView; +import android.support.v4.app.DialogFragment; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentTransaction; +import android.view.Window; -public class WaypointPopup extends AbstractPopupActivity { - @InjectView(R.id.actionbar_title) protected TextView actionBarTitle; - @InjectView(R.id.waypoint_details_list) protected LinearLayout waypointDetailsLayout; - @InjectView(R.id.edit) protected Button buttonEdit; - @InjectView(R.id.details_list) protected LinearLayout cacheDetailsLayout; +import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.activity.ActivityMixin; +public class WaypointPopup extends AbstractActivity { private int waypointId = 0; - private Waypoint waypoint = null; - private TextView waypointDistance = null; - - public WaypointPopup() { - super(R.layout.waypoint_popup); - } + private String geocode; @Override - public void onCreate(final Bundle savedInstanceState) { + public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - ButterKnife.inject(this); - // get parameters - final Bundle extras = getIntent().getExtras(); - if (extras != null) { - waypointId = extras.getInt(Intents.EXTRA_WAYPOINT_ID); - } - } - - @Override - public void onUpdateGeoData(IGeoData geo) { - if (geo.getCoords() != null && waypoint != null && waypoint.getCoords() != null) { - waypointDistance.setText(Units.getDistanceFromKilometers(geo.getCoords().distanceTo(waypoint.getCoords()))); - waypointDistance.bringToFront(); - } - } - - @Override - protected void init() { - super.init(); - waypoint = DataStore.loadWaypoint(waypointId); - try { - if (StringUtils.isNotBlank(waypoint.getName())) { - setTitle(waypoint.getName()); - } else { - setTitle(waypoint.getGeocode()); - } - - actionBarTitle.setCompoundDrawablesWithIntrinsicBounds(getResources().getDrawable(waypoint.getWaypointType().markerId), null, null, null); - - details = new CacheDetailsCreator(this, waypointDetailsLayout); + supportRequestWindowFeature(Window.FEATURE_NO_TITLE); - //Waypoint geocode - details.add(R.string.cache_geocode, waypoint.getPrefix() + waypoint.getGeocode().substring(2)); - details.addDistance(waypoint, waypointDistance); - waypointDistance = details.getValueView(); - details.add(R.string.waypoint_note, waypoint.getNote()); + this.setTheme(ActivityMixin.getDialogTheme()); - buttonEdit.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View arg0) { - EditWaypointActivity.startActivityEditWaypoint(WaypointPopup.this, cache, waypoint.getId()); - finish(); - } - }); - - details = new CacheDetailsCreator(this, cacheDetailsLayout); - details.add(R.string.cache_name, cache.getName()); - - addCacheDetails(); - - } catch (Exception e) { - Log.e("WaypointPopup.init", e); + final Bundle extras = getIntent().getExtras(); + if (extras != null) { + waypointId = extras.getInt(Intents.EXTRA_WAYPOINT_ID); + geocode = extras.getString(Intents.EXTRA_GEOCODE); } - } - - @Override - public void navigateTo() { - NavigationAppFactory.startDefaultNavigationApplication(1, this, waypoint); - } + showDialog(); - /** - * Tries to navigate to the {@link Geocache} of this activity. - */ - @Override - protected void startDefaultNavigation2() { - if (waypoint == null || waypoint.getCoords() == null) { - showToast(res.getString(R.string.cache_coordinates_no)); - return; - } - NavigationAppFactory.startDefaultNavigationApplication(2, this, waypoint); - finish(); } public static void startActivity(final Context context, final int waypointId, final String geocode) { @@ -119,16 +39,22 @@ public class WaypointPopup extends AbstractPopupActivity { context.startActivity(popupIntent); } - @Override - public void showNavigationMenu() { - NavigationAppFactory.showNavigationMenu(this, null, waypoint, null); - } - @Override - protected Geopoint getCoordinates() { - if (waypoint == null) { - return null; + void showDialog() { + // DialogFragment.show() will take care of adding the fragment + // in a transaction. We also want to remove any currently showing + // dialog, so make our own transaction and take care of that here. + FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); + Fragment prev = getSupportFragmentManager().findFragmentByTag("dialog"); + if (prev != null) { + ft.remove(prev); } - return waypoint.getCoords(); + ft.addToBackStack(null); + + // Create and show the dialog. + DialogFragment newFragment = WaypointPopupFragment.newInstance(geocode, waypointId); + newFragment.show(ft, "dialog"); } + + } diff --git a/main/src/cgeo/geocaching/WaypointPopupFragment.java b/main/src/cgeo/geocaching/WaypointPopupFragment.java new file mode 100644 index 0000000..03d95e5 --- /dev/null +++ b/main/src/cgeo/geocaching/WaypointPopupFragment.java @@ -0,0 +1,147 @@ +package cgeo.geocaching; + +import butterknife.ButterKnife; +import butterknife.InjectView; + +import cgeo.geocaching.apps.cache.navi.NavigationAppFactory; +import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.geopoint.Units; +import cgeo.geocaching.sensors.IGeoData; +import cgeo.geocaching.ui.CacheDetailsCreator; +import cgeo.geocaching.utils.Log; + +import org.apache.commons.lang3.StringUtils; + +import android.os.Bundle; +import android.support.v4.app.DialogFragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.TextView; + +public class WaypointPopupFragment extends AbstractDialogFragment { + @InjectView(R.id.actionbar_title) protected TextView actionBarTitle; + @InjectView(R.id.waypoint_details_list) protected LinearLayout waypointDetailsLayout; + @InjectView(R.id.edit) protected Button buttonEdit; + @InjectView(R.id.details_list) protected LinearLayout cacheDetailsLayout; + + private int waypointId = 0; + private Waypoint waypoint = null; + private TextView waypointDistance = null; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.waypoint_popup, container, false); + initCustomActionBar(v); + ButterKnife.inject(this,v); + + return v; + } + + @Override + public void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + waypointId = getArguments().getInt(WAYPOINT_ARG); + } + + @Override + protected void onUpdateGeoData(IGeoData geo) { + if (geo.getCoords() != null && waypoint != null && waypoint.getCoords() != null) { + waypointDistance.setText(Units.getDistanceFromKilometers(geo.getCoords().distanceTo(waypoint.getCoords()))); + waypointDistance.bringToFront(); + } + } + + @Override + protected void init() { + super.init(); + + waypoint = DataStore.loadWaypoint(waypointId); + try { + if (StringUtils.isNotBlank(waypoint.getName())) { + setTitle(waypoint.getName()); + } else { + setTitle(waypoint.getGeocode()); + } + + + actionBarTitle.setCompoundDrawablesWithIntrinsicBounds(getResources().getDrawable(waypoint.getWaypointType().markerId), null, null, null); + + //getSupportActionBar().setIcon(getResources().getDrawable(waypoint.getWaypointType().markerId)); + + details = new CacheDetailsCreator(getActivity(), waypointDetailsLayout); + + //Waypoint geocode + details.add(R.string.cache_geocode, waypoint.getPrefix() + waypoint.getGeocode().substring(2)); + details.addDistance(waypoint, waypointDistance); + waypointDistance = details.getValueView(); + details.add(R.string.waypoint_note, waypoint.getNote()); + + buttonEdit.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View arg0) { + EditWaypointActivity.startActivityEditWaypoint(getActivity(), cache, waypoint.getId()); + getActivity().finish(); + } + }); + + details = new CacheDetailsCreator(getActivity(), cacheDetailsLayout); + details.add(R.string.cache_name, cache.getName()); + + addCacheDetails(); + + } catch (Exception e) { + Log.e("WaypointPopup.init", e); + } + } + + @Override + public void navigateTo() { + NavigationAppFactory.startDefaultNavigationApplication(1, getActivity(), waypoint); + } + + /** + * Tries to navigate to the {@link Geocache} of this activity. + */ + @Override + public void startDefaultNavigation2() { + if (waypoint == null || waypoint.getCoords() == null) { + showToast(res.getString(R.string.cache_coordinates_no)); + return; + } + NavigationAppFactory.startDefaultNavigationApplication(2, getActivity(), waypoint); + getActivity().finish(); + } + + + + @Override + public void showNavigationMenu() { + NavigationAppFactory.showNavigationMenu(getActivity(), null, waypoint, null); + } + + @Override + protected Geopoint getCoordinates() { + if (waypoint == null) { + return null; + } + return waypoint.getCoords(); + } + + public static DialogFragment newInstance(String geocode, int waypointId) { + + Bundle args = new Bundle(); + args.putInt(WAYPOINT_ARG, waypointId); + args.putString(GEOCODE_ARG, geocode); + + DialogFragment f = new WaypointPopupFragment(); + f.setArguments(args); + f.setStyle(DialogFragment.STYLE_NO_TITLE,0); + + return f; + } +} diff --git a/main/src/cgeo/geocaching/activity/AbstractActionBarActivity.java b/main/src/cgeo/geocaching/activity/AbstractActionBarActivity.java new file mode 100644 index 0000000..a732f65 --- /dev/null +++ b/main/src/cgeo/geocaching/activity/AbstractActionBarActivity.java @@ -0,0 +1,34 @@ +package cgeo.geocaching.activity; + +import android.os.Bundle; + +/** + * Classes actually having an ActionBar (as opposed to the Dialog activities) + */ +public class AbstractActionBarActivity extends AbstractActivity { + @Override + protected void onCreate(Bundle savedInstanceState, int resourceLayoutID) { + super.onCreate(savedInstanceState, resourceLayoutID); + initUpAction(); + showProgress(false); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + initUpAction(); + showProgress(false); + } + + + private void initUpAction() { + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + } + + @Override + public void setTitle(CharSequence title) { + super.setTitle(title); + // reflect the title in the actionbar + ActivityMixin.setTitle(this, title); + } +} diff --git a/main/src/cgeo/geocaching/activity/AbstractActivity.java b/main/src/cgeo/geocaching/activity/AbstractActivity.java index 542dd05..e3df1f7 100644 --- a/main/src/cgeo/geocaching/activity/AbstractActivity.java +++ b/main/src/cgeo/geocaching/activity/AbstractActivity.java @@ -12,21 +12,31 @@ import cgeo.geocaching.utils.HtmlUtils; import cgeo.geocaching.utils.TranslationUtils; import org.apache.commons.lang3.StringUtils; +import org.eclipse.jdt.annotation.NonNull; +import rx.Subscription; +import rx.subscriptions.Subscriptions; + +import android.annotation.TargetApi; import android.content.Intent; import android.content.res.Resources; +import android.nfc.NdefMessage; +import android.nfc.NdefRecord; +import android.nfc.NfcAdapter; +import android.nfc.NfcEvent; +import android.os.Build; import android.os.Bundle; -import android.support.v4.app.FragmentActivity; -import android.view.ContextMenu; +import android.support.v7.app.ActionBarActivity; +import android.support.v7.view.ActionMode; +import android.view.Menu; import android.view.MenuItem; import android.view.View; +import android.view.Window; import android.widget.EditText; -import rx.Subscription; -import rx.subscriptions.Subscriptions; import java.util.Locale; -public abstract class AbstractActivity extends FragmentActivity implements IAbstractActivity { +public abstract class AbstractActivity extends ActionBarActivity implements IAbstractActivity { protected CgeoApplication app = null; protected Resources res = null; @@ -41,15 +51,6 @@ public abstract class AbstractActivity extends FragmentActivity implements IAbst this.keepScreenOn = keepScreenOn; } - @Override - final public void goHome(final View view) { - ActivityMixin.goHome(this); - } - - final protected void setTitle(final String title) { - ActivityMixin.setTitle(this, title); - } - final protected void showProgress(final boolean show) { ActivityMixin.showProgress(this, show); } @@ -71,7 +72,18 @@ public abstract class AbstractActivity extends FragmentActivity implements IAbst @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + supportRequestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); + initializeCommonFields(); + + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + return ActivityMixin.navigateUp(this); + } + return super.onOptionsItemSelected(item); } public void onResume(final Subscription resumeSubscription) { @@ -104,21 +116,14 @@ public abstract class AbstractActivity extends FragmentActivity implements IAbst } protected void onCreate(final Bundle savedInstanceState, final int resourceLayoutID) { - onCreate(savedInstanceState, resourceLayoutID, false); - } - - protected void onCreate(final Bundle savedInstanceState, final int resourceLayoutID, boolean useDialogTheme) { super.onCreate(savedInstanceState); initializeCommonFields(); // non declarative part of layout - if (useDialogTheme) { - setTheme(ActivityMixin.getDialogTheme()); - } else { - setTheme(); - } + setTheme(); + setContentView(resourceLayoutID); // create view variables @@ -151,9 +156,8 @@ public abstract class AbstractActivity extends FragmentActivity implements IAbst new Keyboard(this).show(view); } - protected void buildDetailsContextMenu(final ContextMenu menu, final CharSequence clickedItemText, final CharSequence fieldTitle, final boolean copyOnly) { - menu.setHeaderTitle(fieldTitle); - getMenuInflater().inflate(R.menu.details_context, menu); + protected void buildDetailsContextMenu(final ActionMode actionMode, final Menu menu, final CharSequence clickedItemText, final CharSequence fieldTitle, final boolean copyOnly) { + actionMode.setTitle(fieldTitle); menu.findItem(R.id.menu_translate_to_sys_lang).setVisible(!copyOnly); if (!copyOnly) { if (clickedItemText.length() > TranslationUtils.TRANSLATION_TEXT_LENGTH_WARN) { @@ -165,27 +169,62 @@ public abstract class AbstractActivity extends FragmentActivity implements IAbst menu.findItem(R.id.menu_translate_to_english).setVisible(!copyOnly && !localeIsEnglish); } - protected boolean onClipboardItemSelected(final MenuItem item, final CharSequence clickedItemText) { + protected boolean onClipboardItemSelected(@NonNull final ActionMode actionMode, final MenuItem item, final CharSequence clickedItemText) { switch (item.getItemId()) { // detail fields case R.id.menu_copy: ClipboardUtils.copyToClipboard(clickedItemText); showToast(res.getString(R.string.clipboard_copy_ok)); + actionMode.finish(); return true; case R.id.menu_translate_to_sys_lang: TranslationUtils.startActivityTranslate(this, Locale.getDefault().getLanguage(), HtmlUtils.extractText(clickedItemText)); + actionMode.finish(); return true; case R.id.menu_translate_to_english: TranslationUtils.startActivityTranslate(this, Locale.ENGLISH.getLanguage(), HtmlUtils.extractText(clickedItemText)); + actionMode.finish(); return true; case R.id.menu_cache_share_field: final Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("text/plain"); intent.putExtra(Intent.EXTRA_TEXT, clickedItemText.toString()); startActivity(Intent.createChooser(intent, res.getText(R.string.cache_share_field))); + actionMode.finish(); return true; default: return false; } } + + // Do not support older devices than Android 4.0 + // Although there even are 2.3 devices (Nexus S) + // these are so few that we don't want to deal with the older (non Android Beam) API + + public interface ActivitySharingInterface { + /** Return an URL that represent the current activity for sharing */ + public String getUri(); + } + + protected void initializeAndroidBeam(ActivitySharingInterface sharingInterface) { + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + initializeICSAndroidBeam(sharingInterface); + } + } + + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + protected void initializeICSAndroidBeam(final ActivitySharingInterface sharingInterface) { + NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this); + if (nfcAdapter == null) { + return; + } + nfcAdapter.setNdefPushMessageCallback(new NfcAdapter.CreateNdefMessageCallback() { + @Override + public NdefMessage createNdefMessage(NfcEvent event) { + NdefRecord record = NdefRecord.createUri(sharingInterface.getUri()); + return new NdefMessage(new NdefRecord[]{record}); + } + }, this); + + } } diff --git a/main/src/cgeo/geocaching/activity/AbstractListActivity.java b/main/src/cgeo/geocaching/activity/AbstractListActivity.java index a5d5c14..eac191a 100644 --- a/main/src/cgeo/geocaching/activity/AbstractListActivity.java +++ b/main/src/cgeo/geocaching/activity/AbstractListActivity.java @@ -4,10 +4,10 @@ import cgeo.geocaching.CgeoApplication; import android.content.res.Resources; import android.os.Bundle; -import android.support.v4.app.FragmentListActivity; -import android.view.View; +import android.view.MenuItem; +import android.view.Window; -public abstract class AbstractListActivity extends FragmentListActivity implements +public abstract class AbstractListActivity extends ActionBarListActivity implements IAbstractActivity { private boolean keepScreenOn = false; @@ -23,11 +23,6 @@ public abstract class AbstractListActivity extends FragmentListActivity implemen this.keepScreenOn = keepScreenOn; } - @Override - final public void goHome(View view) { - ActivityMixin.goHome(this); - } - final public void showProgress(final boolean show) { ActivityMixin.showProgress(this, show); } @@ -49,7 +44,22 @@ public abstract class AbstractListActivity extends FragmentListActivity implemen @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + supportRequestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); + initializeCommonFields(); + initUpAction(); + } + + protected void initUpAction() { + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId()== android.R.id.home) { + return ActivityMixin.navigateUp(this); + } + return super.onOptionsItemSelected(item); } private void initializeCommonFields() { diff --git a/main/src/cgeo/geocaching/activity/AbstractViewPagerActivity.java b/main/src/cgeo/geocaching/activity/AbstractViewPagerActivity.java index 6e2900d..e98c935 100644 --- a/main/src/cgeo/geocaching/activity/AbstractViewPagerActivity.java +++ b/main/src/cgeo/geocaching/activity/AbstractViewPagerActivity.java @@ -30,7 +30,7 @@ import java.util.Map; * Enum listing all available pages of this activity. The pages available at a certain point of time are * defined by overriding {@link #getOrderedPages()}. */ -public abstract class AbstractViewPagerActivity<Page extends Enum<Page>> extends AbstractActivity { +public abstract class AbstractViewPagerActivity<Page extends Enum<Page>> extends AbstractActionBarActivity { /** * A {@link List} of all available pages. diff --git a/main/src/cgeo/geocaching/activity/ActionBarListActivity.java b/main/src/cgeo/geocaching/activity/ActionBarListActivity.java new file mode 100644 index 0000000..07c7ec3 --- /dev/null +++ b/main/src/cgeo/geocaching/activity/ActionBarListActivity.java @@ -0,0 +1,34 @@ +package cgeo.geocaching.activity; + +import android.support.v7.app.ActionBarActivity; +import android.widget.HeaderViewListAdapter; +import android.widget.ListAdapter; +import android.widget.ListView; + +/** + * Compatbility Class until cgeo switches from ListActivities to ListFragments + */ +public class ActionBarListActivity extends ActionBarActivity { + + private ListView mListView; + protected ListView getListView() { + if (mListView == null) { + mListView = (ListView) findViewById(android.R.id.list); + } + return mListView; + } + + protected void setListAdapter(ListAdapter adapter) { + getListView().setAdapter(adapter); + } + + protected ListAdapter getListAdapter() { + ListAdapter adapter = getListView().getAdapter(); + if (adapter instanceof HeaderViewListAdapter) { + return ((HeaderViewListAdapter)adapter).getWrappedAdapter(); + } + return adapter; + } +} + + diff --git a/main/src/cgeo/geocaching/activity/ActivityMixin.java b/main/src/cgeo/geocaching/activity/ActivityMixin.java index bfd45da..e2181d0 100644 --- a/main/src/cgeo/geocaching/activity/ActivityMixin.java +++ b/main/src/cgeo/geocaching/activity/ActivityMixin.java @@ -1,55 +1,45 @@ package cgeo.geocaching.activity; -import cgeo.geocaching.MainActivity; import cgeo.geocaching.R; -import cgeo.geocaching.compatibility.Compatibility; import cgeo.geocaching.settings.Settings; import org.apache.commons.lang3.StringUtils; +import org.eclipse.jdt.annotation.NonNull; import android.app.Activity; import android.content.Intent; -import android.os.Build; +import android.support.v4.app.ActivityCompat; +import android.support.v4.app.NavUtils; +import android.support.v4.app.TaskStackBuilder; +import android.support.v7.app.ActionBar; +import android.support.v7.app.ActionBarActivity; import android.view.Gravity; -import android.view.View; import android.view.WindowManager; import android.widget.EditText; -import android.widget.ProgressBar; -import android.widget.TextView; import android.widget.Toast; public final class ActivityMixin { - public final static void goHome(final Activity fromActivity) { - final Intent intent = new Intent(fromActivity, MainActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - - fromActivity.startActivity(intent); - fromActivity.finish(); - } - public static void setTitle(final Activity activity, final CharSequence text) { if (StringUtils.isBlank(text)) { return; } - final TextView title = (TextView) activity.findViewById(R.id.actionbar_title); - if (title != null) { - title.setText(text); + if (activity instanceof ActionBarActivity) { + final ActionBar actionBar = ((ActionBarActivity) activity).getSupportActionBar(); + if (actionBar != null) { + actionBar.setTitle(text); + } } } - public static void showProgress(final Activity activity, final boolean show) { + public static void showProgress(final ActionBarActivity activity, final boolean show) { if (activity == null) { return; } - final ProgressBar progress = (ProgressBar) activity.findViewById(R.id.actionbar_progress); - if (show) { - progress.setVisibility(View.VISIBLE); - } else { - progress.setVisibility(View.GONE); - } + activity.setSupportProgressBarIndeterminateVisibility(show); + } public static void setTheme(final Activity activity) { @@ -62,10 +52,10 @@ public final class ActivityMixin { public static int getDialogTheme() { // Light theme dialogs don't work on Android Api < 11 - if (Settings.isLightSkin() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + // The compat theme should fix this + if (Settings.isLightSkin()) { return R.style.popup_light; } - return R.style.popup_dark; } @@ -98,7 +88,12 @@ public final class ActivityMixin { } public static void invalidateOptionsMenu(Activity activity) { - Compatibility.invalidateOptionsMenu(activity); + if (activity instanceof ActionBarActivity) { + ((ActionBarActivity) activity).supportInvalidateOptionsMenu(); + } + else { + ActivityCompat.invalidateOptionsMenu(activity); + } } /** @@ -127,4 +122,27 @@ public final class ActivityMixin { int newCursor = moveCursor ? start + completeText.length() : start; editText.setSelection(newCursor); } + + public static boolean navigateUp(@NonNull final Activity activity) { + // see http://developer.android.com/training/implementing-navigation/ancestral.html + Intent upIntent = NavUtils.getParentActivityIntent(activity); + if (upIntent == null) { + activity.finish(); + return true; + } + if (NavUtils.shouldUpRecreateTask(activity, upIntent)) { + // This activity is NOT part of this app's task, so create a new task + // when navigating up, with a synthesized back stack. + TaskStackBuilder.create(activity) + // Add all of this activity's parents to the back stack + .addNextIntentWithParentStack(upIntent) + // Navigate up to the closest parent + .startActivities(); + } else { + // This activity is part of this app's task, so simply + // navigate up to the logical parent activity. + NavUtils.navigateUpTo(activity, upIntent); + } + return true; + } } diff --git a/main/src/cgeo/geocaching/activity/IAbstractActivity.java b/main/src/cgeo/geocaching/activity/IAbstractActivity.java index 7ca2322..4fb6a2a 100644 --- a/main/src/cgeo/geocaching/activity/IAbstractActivity.java +++ b/main/src/cgeo/geocaching/activity/IAbstractActivity.java @@ -1,11 +1,8 @@ package cgeo.geocaching.activity; -import android.view.View; public interface IAbstractActivity { - public void goHome(View view); - public void showToast(String text); public void showShortToast(String text); diff --git a/main/src/cgeo/geocaching/activity/SimpleWebviewActivity.java b/main/src/cgeo/geocaching/activity/SimpleWebviewActivity.java new file mode 100644 index 0000000..83b9281 --- /dev/null +++ b/main/src/cgeo/geocaching/activity/SimpleWebviewActivity.java @@ -0,0 +1,32 @@ +package cgeo.geocaching.activity; + +import cgeo.geocaching.R; + +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.webkit.WebView; +import android.webkit.WebViewClient; + +public class SimpleWebviewActivity extends AbstractActionBarActivity { + + private WebView webview; + + class SimplelWebviewClient extends WebViewClient { + @Override + public boolean shouldOverrideUrlLoading(final WebView view, final String url) { + webview.loadUrl(url); + return true; + } + } + + @SuppressLint("SetJavaScriptEnabled") + @Override + public void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState, R.layout.internal_browser); + + webview = (WebView) findViewById(R.id.webview); + webview.getSettings().setJavaScriptEnabled(true); + webview.setWebViewClient(new SimplelWebviewClient()); + webview.loadUrl(String.valueOf(getIntent().getData())); + } +} diff --git a/main/src/cgeo/geocaching/apps/cache/GccApp.java b/main/src/cgeo/geocaching/apps/cache/GccApp.java deleted file mode 100644 index 4423977..0000000 --- a/main/src/cgeo/geocaching/apps/cache/GccApp.java +++ /dev/null @@ -1,28 +0,0 @@ -package cgeo.geocaching.apps.cache; - -import cgeo.geocaching.R; -import cgeo.geocaching.utils.ProcessUtils; - -import android.content.Intent; - -public class GccApp extends AbstractGeneralApp { - private static final String PACKAGE = "eisbehr.gcc"; - private static final String PACKAGE_PRO = "eisbehr.gcc.pro"; - - public GccApp() { - super(getString(R.string.cache_menu_gcc), R.id.cache_app_gcc, null); - } - - @Override - public boolean isInstalled() { - return ProcessUtils.isLaunchable(PACKAGE) || ProcessUtils.isLaunchable(PACKAGE_PRO); - } - - @Override - protected Intent getLaunchIntent() { - if (ProcessUtils.isLaunchable(PACKAGE_PRO)) { - return ProcessUtils.getLaunchIntent(PACKAGE_PRO); - } - return ProcessUtils.getLaunchIntent(PACKAGE); - } -} diff --git a/main/src/cgeo/geocaching/apps/cache/WhereYouGoApp.java b/main/src/cgeo/geocaching/apps/cache/WhereYouGoApp.java index 79a5975..b2a2cad 100644 --- a/main/src/cgeo/geocaching/apps/cache/WhereYouGoApp.java +++ b/main/src/cgeo/geocaching/apps/cache/WhereYouGoApp.java @@ -3,14 +3,34 @@ package cgeo.geocaching.apps.cache; import cgeo.geocaching.Geocache; import cgeo.geocaching.R; import cgeo.geocaching.enumerations.CacheType; +import cgeo.geocaching.utils.TextUtils; + +import org.apache.commons.lang3.StringUtils; + +import android.app.Activity; +import android.content.Intent; +import android.net.Uri; + +import java.util.regex.Pattern; public class WhereYouGoApp extends AbstractGeneralApp { + private static final Pattern PATTERN_CARTRIDGE = Pattern.compile("(" + Pattern.quote("http://www.wherigo.com/cartridge/details.aspx?") + ".*?)" + Pattern.quote("\"")); + public WhereYouGoApp() { super(getString(R.string.cache_menu_whereyougo), R.id.cache_app_whereyougo, "menion.android.whereyougo"); } @Override public boolean isEnabled(Geocache cache) { - return cache.getType() == CacheType.WHERIGO; + return cache.getType() == CacheType.WHERIGO && StringUtils.isNotEmpty(getWhereIGoUrl(cache)); + } + + @Override + public void navigate(Activity activity, Geocache cache) { + activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(getWhereIGoUrl(cache)))); + } + + protected static String getWhereIGoUrl(Geocache cache) { + return TextUtils.getMatch(cache.getDescription(), PATTERN_CARTRIDGE, null); } } diff --git a/main/src/cgeo/geocaching/apps/cache/navi/AbstractStaticMapsApp.java b/main/src/cgeo/geocaching/apps/cache/navi/AbstractStaticMapsApp.java index c42c2a2..a2a5803 100644 --- a/main/src/cgeo/geocaching/apps/cache/navi/AbstractStaticMapsApp.java +++ b/main/src/cgeo/geocaching/apps/cache/navi/AbstractStaticMapsApp.java @@ -4,7 +4,7 @@ import cgeo.geocaching.DataStore; import cgeo.geocaching.Geocache; import cgeo.geocaching.ILogable; import cgeo.geocaching.R; -import cgeo.geocaching.StaticMapsActivity; +import cgeo.geocaching.StaticMapsActivity_; import cgeo.geocaching.StaticMapsProvider; import cgeo.geocaching.Waypoint; import cgeo.geocaching.activity.ActivityMixin; @@ -49,7 +49,11 @@ abstract class AbstractStaticMapsApp extends AbstractApp implements CacheNavigat } final String geocode = StringUtils.upperCase(logable.getGeocode()); - StaticMapsActivity.startActivity(activity, geocode, download, waypoint); + StaticMapsActivity_.IntentBuilder_ builder = StaticMapsActivity_.intent(activity).geocode(geocode).download(download); + if (waypoint != null) { + builder.waypointId(waypoint.getId()); + } + builder.start(); return true; } } diff --git a/main/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java b/main/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java index 3177a29..1446d8a 100644 --- a/main/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java +++ b/main/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java @@ -8,7 +8,6 @@ import cgeo.geocaching.activity.ActivityMixin; import cgeo.geocaching.apps.AbstractAppFactory; import cgeo.geocaching.apps.App; import cgeo.geocaching.apps.cache.CacheBeaconApp; -import cgeo.geocaching.apps.cache.GccApp; import cgeo.geocaching.apps.cache.WhereYouGoApp; import cgeo.geocaching.apps.cache.navi.GoogleNavigationApp.GoogleNavigationBikeApp; import cgeo.geocaching.apps.cache.navi.GoogleNavigationApp.GoogleNavigationDrivingApp; @@ -70,7 +69,6 @@ public final class NavigationAppFactory extends AbstractAppFactory { GOOGLE_MAPS_DIRECTIONS(new GoogleMapsDirectionApp(), 13, R.string.pref_navigation_menu_google_maps_directions), CACHE_BEACON(new CacheBeaconApp(), 14, R.string.pref_navigation_menu_cache_beacon), - GCC(new GccApp(), 15, R.string.pref_navigation_menu_gcc), WHERE_YOU_GO(new WhereYouGoApp(), 16, R.string.pref_navigation_menu_where_you_go), PEBBLE(new PebbleApp(), 17, R.string.pref_navigation_menu_pebble), MAPSWITHME(new MapsWithMeApp(), 22, R.string.pref_navigation_menu_mapswithme); diff --git a/main/src/cgeo/geocaching/apps/cache/navi/NavigonApp.java b/main/src/cgeo/geocaching/apps/cache/navi/NavigonApp.java index da988aa..024bf37 100644 --- a/main/src/cgeo/geocaching/apps/cache/navi/NavigonApp.java +++ b/main/src/cgeo/geocaching/apps/cache/navi/NavigonApp.java @@ -23,8 +23,8 @@ class NavigonApp extends AbstractPointNavigationApp { /* * Long/Lat are float values in decimal degree format (+-DDD.DDDDD). * Example: - * intent.putExtra(INTENT_EXTRA_KEY_LATITUDE, 46.12345f); - * intent.putExtra(INTENT_EXTRA_KEY_LONGITUDE, 23.12345f); + * intent.putExtra(INTENT_EXTRA_KEY_LATITUDE, 46.12345f) + * intent.putExtra(INTENT_EXTRA_KEY_LONGITUDE, 23.12345f) */ intent.putExtra(INTENT_EXTRA_KEY_LATITUDE, (float) point.getLatitude()); intent.putExtra(INTENT_EXTRA_KEY_LONGITUDE, (float) point.getLongitude()); diff --git a/main/src/cgeo/geocaching/compatibility/AndroidLevel11.java b/main/src/cgeo/geocaching/compatibility/AndroidLevel11.java deleted file mode 100644 index a425ee5..0000000 --- a/main/src/cgeo/geocaching/compatibility/AndroidLevel11.java +++ /dev/null @@ -1,17 +0,0 @@ -package cgeo.geocaching.compatibility; - -import android.annotation.TargetApi; -import android.app.Activity; - -/** - * Android level 11 support - */ -@TargetApi(11) -public class AndroidLevel11 implements AndroidLevel11Interface { - - @Override - public void invalidateOptionsMenu(final Activity activity) { - activity.invalidateOptionsMenu(); - } - -} diff --git a/main/src/cgeo/geocaching/compatibility/AndroidLevel11Emulation.java b/main/src/cgeo/geocaching/compatibility/AndroidLevel11Emulation.java deleted file mode 100644 index 6a23ed5..0000000 --- a/main/src/cgeo/geocaching/compatibility/AndroidLevel11Emulation.java +++ /dev/null @@ -1,15 +0,0 @@ -package cgeo.geocaching.compatibility; - -import android.app.Activity; - -/** - * implement level 11 API using older methods - */ -public class AndroidLevel11Emulation implements AndroidLevel11Interface { - - @Override - public void invalidateOptionsMenu(final Activity activity) { - // do nothing - } - -} diff --git a/main/src/cgeo/geocaching/compatibility/AndroidLevel11Interface.java b/main/src/cgeo/geocaching/compatibility/AndroidLevel11Interface.java deleted file mode 100644 index 236e92d..0000000 --- a/main/src/cgeo/geocaching/compatibility/AndroidLevel11Interface.java +++ /dev/null @@ -1,8 +0,0 @@ -package cgeo.geocaching.compatibility; - -import android.app.Activity; - -public interface AndroidLevel11Interface { - public void invalidateOptionsMenu(final Activity activity); - -} diff --git a/main/src/cgeo/geocaching/compatibility/Compatibility.java b/main/src/cgeo/geocaching/compatibility/Compatibility.java index a293cfd..54e2966 100644 --- a/main/src/cgeo/geocaching/compatibility/Compatibility.java +++ b/main/src/cgeo/geocaching/compatibility/Compatibility.java @@ -8,35 +8,33 @@ import android.os.Build; public final class Compatibility { - private final static int sdkVersion = Build.VERSION.SDK_INT; + private static final int SDK_VERSION = Build.VERSION.SDK_INT; - private final static AndroidLevel11Interface level11; - private final static AndroidLevel13Interface level13; - private final static AndroidLevel19Interface level19; + private static final AndroidLevel13Interface LEVEL_13; + private static final AndroidLevel19Interface LEVEL_19; static { - level11 = sdkVersion >= 11 ? new AndroidLevel11() : new AndroidLevel11Emulation(); - level13 = sdkVersion >= 13 ? new AndroidLevel13() : new AndroidLevel13Emulation(); - level19 = sdkVersion >= 19 ? new AndroidLevel19() : new AndroidLevel19Emulation(); + LEVEL_13 = SDK_VERSION >= 13 ? new AndroidLevel13() : new AndroidLevel13Emulation(); + LEVEL_19 = SDK_VERSION >= 19 ? new AndroidLevel19() : new AndroidLevel19Emulation(); } - public static void invalidateOptionsMenu(final Activity activity) { - level11.invalidateOptionsMenu(activity); + private Compatibility() { + // utility class } public static int getDisplayWidth() { - return level13.getDisplayWidth(); + return LEVEL_13.getDisplayWidth(); } public static Point getDisplaySize() { - return level13.getDisplaySize(); + return LEVEL_13.getDisplaySize(); } public static void importGpxFromStorageAccessFramework(final @NonNull Activity activity, int requestCodeImportGpx) { - level19.importGpxFromStorageAccessFramework(activity, requestCodeImportGpx); + LEVEL_19.importGpxFromStorageAccessFramework(activity, requestCodeImportGpx); } public static boolean isStorageAccessFrameworkAvailable() { - return sdkVersion >= 19; + return SDK_VERSION >= 19; } } diff --git a/main/src/cgeo/geocaching/connector/gc/GCConnector.java b/main/src/cgeo/geocaching/connector/gc/GCConnector.java index 925f6f0..294e969 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCConnector.java +++ b/main/src/cgeo/geocaching/connector/gc/GCConnector.java @@ -50,7 +50,7 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, private static final String CACHE_URL_SHORT = "http://coord.info/"; // Double slash is used to force open in browser - private static final String CACHE_URL_LONG = "http://www.geocaching.com//seek/cache_details.aspx?wp="; + private static final String CACHE_URL_LONG = "http://www.geocaching.com/seek/cache_details.aspx?wp="; /** * Pocket queries downloaded from the website use a numeric prefix. The pocket query creator Android app adds a * verbatim "pocketquery" prefix. @@ -192,8 +192,8 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, @Override public boolean isOwner(final ICache cache) { - return StringUtils.equalsIgnoreCase(cache.getOwnerUserId(), Settings.getUsername()); - + final String user = Settings.getUsername(); + return StringUtils.isNotEmpty(user) && StringUtils.equalsIgnoreCase(cache.getOwnerUserId(), user); } @Override diff --git a/main/src/cgeo/geocaching/connector/gc/GCMap.java b/main/src/cgeo/geocaching/connector/gc/GCMap.java index dc2408f..dd81507 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCMap.java +++ b/main/src/cgeo/geocaching/connector/gc/GCMap.java @@ -146,30 +146,6 @@ public class GCMap { throw new JSONException("No data inside JSON"); } - /* - * Optimization: the grid can get ignored. The keys are the grid position in the format x_y - * It's not used at the moment due to optimizations - * But maybe we need it some day... - * - * // attach all keys with the cache positions in the tile - * Map<String, UTFGridPosition> keyPositions = new HashMap<String, UTFGridPosition>(); // JSON key, (x/y) in - * grid - * for (int y = 0; y < grid.length(); y++) { - * String rowUTF8 = grid.getString(y); - * if (rowUTF8.length() != (UTFGrid.GRID_MAXX + 1)) { - * throw new JSONException("Grid has wrong size"); - * } - * - * for (int x = 0; x < UTFGrid.GRID_MAXX; x++) { - * char c = rowUTF8.charAt(x); - * if (c != ' ') { - * short id = UTFGrid.getUTFGridId(c); - * keyPositions.put(keys.getString(id), new UTFGridPosition(x, y)); - * } - * } - * } - */ - // iterate over the data and construct all caches in this tile Map<String, List<UTFGridPosition>> positions = new HashMap<String, List<UTFGridPosition>>(); // JSON id as key Map<String, List<UTFGridPosition>> singlePositions = new HashMap<String, List<UTFGridPosition>>(); // JSON id as key diff --git a/main/src/cgeo/geocaching/connector/gc/GCParser.java b/main/src/cgeo/geocaching/connector/gc/GCParser.java index 5422c11..4ae03bf 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCParser.java +++ b/main/src/cgeo/geocaching/connector/gc/GCParser.java @@ -47,6 +47,14 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import rx.Observable; +import rx.Observable.OnSubscribe; +import rx.Subscriber; +import rx.functions.Action1; +import rx.functions.Func0; +import rx.functions.Func2; +import rx.schedulers.Schedulers; + import android.net.Uri; import android.text.Html; @@ -136,7 +144,6 @@ public abstract class GCParser { while (matcherGuidAndDisabled.find()) { if (matcherGuidAndDisabled.groupCount() > 0) { - //cache.setGuid(matcherGuidAndDisabled.group(1)); if (matcherGuidAndDisabled.group(2) != null) { cache.setName(Html.fromHtml(matcherGuidAndDisabled.group(2).trim()).toString()); } @@ -335,32 +342,50 @@ public abstract class GCParser { } static SearchResult parseCache(final String page, final CancellableHandler handler) { - final SearchResult searchResult = parseCacheFromText(page, handler); + final ImmutablePair<StatusCode, Geocache> parsed = parseCacheFromText(page, handler); // 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(); - if (CancellableHandler.isCancelled(handler)) { - return null; - } + if (parsed.left != StatusCode.NO_ERROR) { + return new SearchResult(parsed.left); + } + + final Geocache cache = parsed.right; + getExtraOnlineInfo(cache, page, handler); + // too late: it is already stored through parseCacheFromText + cache.setDetailedUpdatedNow(); + if (CancellableHandler.isCancelled(handler)) { + return null; + } - // save full detailed caches - CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_cache); - DataStore.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); + // save full detailed caches + CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_cache); + DataStore.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); - // update progress message so user knows we're still working. This is more of a place holder than - // actual indication of what the program is doing - CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_render); + // update progress message so user knows we're still working. This is more of a place holder than + // actual indication of what the program is doing + CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_render); + return new SearchResult(cache); + } + + static SearchResult parseAndSaveCacheFromText(final String page, @Nullable final CancellableHandler handler) { + final ImmutablePair<StatusCode, Geocache> parsed = parseCacheFromText(page, handler); + final SearchResult result = new SearchResult(parsed.left); + if (parsed.left == StatusCode.NO_ERROR) { + result.addAndPutInCache(Collections.singletonList(parsed.right)); + DataStore.saveLogsWithoutTransaction(parsed.right.getGeocode(), getLogsFromDetails(page).toBlockingObservable().toIterable()); } - return searchResult; + return result; } - static SearchResult parseCacheFromText(final String pageIn, final CancellableHandler handler) { + /** + * Parse cache from text and return either an error code or a cache object in a pair. Note that inline logs are + * not parsed nor saved, while the cache itself is. + * + * @param pageIn the page text to parse + * @param handler the handler to send the progress notifications to + * @return a pair, with a {@link StatusCode} on the left, and a non-nulll cache objet on the right + * iff the status code is {@link StatusCode.NO_ERROR}. + */ + static private ImmutablePair<StatusCode, Geocache> parseCacheFromText(final String pageIn, @Nullable final CancellableHandler handler) { CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_details); if (StringUtils.isBlank(pageIn)) { @@ -368,22 +393,17 @@ public abstract class GCParser { return null; } - final SearchResult searchResult = new SearchResult(); - if (pageIn.contains(GCConstants.STRING_UNPUBLISHED_OTHER) || pageIn.contains(GCConstants.STRING_UNPUBLISHED_FROM_SEARCH)) { - searchResult.setError(StatusCode.UNPUBLISHED_CACHE); - return searchResult; + return ImmutablePair.of(StatusCode.UNPUBLISHED_CACHE, null); } if (pageIn.contains(GCConstants.STRING_PREMIUMONLY_1) || pageIn.contains(GCConstants.STRING_PREMIUMONLY_2)) { - searchResult.setError(StatusCode.PREMIUM_ONLY); - return searchResult; + return ImmutablePair.of(StatusCode.PREMIUM_ONLY, null); } final String cacheName = Html.fromHtml(TextUtils.getMatch(pageIn, GCConstants.PATTERN_NAME, true, "")).toString(); if (GCConstants.STRING_UNKNOWN_ERROR.equalsIgnoreCase(cacheName)) { - searchResult.setError(StatusCode.UNKNOWN_ERROR); - return searchResult; + return ImmutablePair.of(StatusCode.UNKNOWN_ERROR, null); } // first handle the content with line breaks, then trim everything for easier matching and reduced memory consumption in parsed fields @@ -726,14 +746,11 @@ public abstract class GCParser { // last check for necessary cache conditions if (StringUtils.isBlank(cache.getGeocode())) { - searchResult.setError(StatusCode.UNKNOWN_ERROR); - return searchResult; + return ImmutablePair.of(StatusCode.UNKNOWN_ERROR, null); } cache.setDetailedUpdatedNow(); - searchResult.addAndPutInCache(Collections.singletonList(cache)); - DataStore.saveLogsWithoutTransaction(cache.getGeocode(), getLogsFromDetails(page, false)); - return searchResult; + return ImmutablePair.of(StatusCode.NO_ERROR, cache); } private static String getNumberString(final String numberWithPunctuation) { @@ -1622,116 +1639,142 @@ public abstract class GCParser { * * @param page * the text of the details page - * @param friends - * 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 + * @return a list of log entries which will be empty if the logs could not be retrieved * */ - @Nullable - private static List<LogEntry> getLogsFromDetails(final String page, final boolean friends) { - String rawResponse; + @NonNull + private static Observable<LogEntry> getLogsFromDetails(final String page) { + // extract embedded JSON data from page + return parseLogs(false, TextUtils.getMatch(page, GCConstants.PATTERN_LOGBOOK, "")); + } - if (friends) { - final MatcherWrapper userTokenMatcher = new MatcherWrapper(GCConstants.PATTERN_USERTOKEN, page); - if (!userTokenMatcher.find()) { - Log.e("GCParser.loadLogsFromDetails: unable to extract userToken"); - return null; - } + private enum SpecialLogs { + FRIENDS("sf"), + OWN("sp"); - final String userToken = userTokenMatcher.group(1); - final Parameters params = new Parameters( - "tkn", userToken, - "idx", "1", - "num", String.valueOf(GCConstants.NUMBER_OF_LOGS), - "decrypt", "true", - // "sp", Boolean.toString(personal), // personal logs - "sf", Boolean.toString(friends)); + final String paramName; - final HttpResponse response = Network.getRequest("http://www.geocaching.com/seek/geocache.logbook", params); - if (response == null) { - Log.e("GCParser.loadLogsFromDetails: cannot log logs, response is null"); - return null; - } - final int statusCode = response.getStatusLine().getStatusCode(); - if (statusCode != 200) { - Log.e("GCParser.loadLogsFromDetails: error " + statusCode + " when requesting log information"); - return null; - } - rawResponse = Network.getResponseData(response); - if (rawResponse == null) { - Log.e("GCParser.loadLogsFromDetails: unable to read whole response"); - return null; - } - } else { - // extract embedded JSON data from page - rawResponse = TextUtils.getMatch(page, GCConstants.PATTERN_LOGBOOK, ""); + SpecialLogs(String paramName) { + this.paramName = paramName; } - return parseLogs(friends, rawResponse); + private String getParamName() { + return this.paramName; + } } - private static List<LogEntry> parseLogs(final boolean friends, String rawResponse) { - final List<LogEntry> logs = new ArrayList<LogEntry>(); - - // for non logged in users the log book is not shown - if (StringUtils.isBlank(rawResponse)) { - return logs; - } + /** + * Extract special logs (friends, own) through seperate request. + * + * @param page + * The page to extrat userToken from + * @param logType + * The logType to request + * @return Observable<LogEntry> The logs + */ + private static Observable<LogEntry> getSpecialLogs(final String page, final SpecialLogs logType) { + return Observable.defer(new Func0<Observable<? extends LogEntry>>() { + @Override + public Observable<? extends LogEntry> call() { + final MatcherWrapper userTokenMatcher = new MatcherWrapper(GCConstants.PATTERN_USERTOKEN, page); + if (!userTokenMatcher.find()) { + Log.e("GCParser.loadLogsFromDetails: unable to extract userToken"); + return Observable.empty(); + } - try { - final JSONObject resp = new JSONObject(rawResponse); - if (!resp.getString("status").equals("success")) { - Log.e("GCParser.loadLogsFromDetails: status is " + resp.getString("status")); - return null; + final String userToken = userTokenMatcher.group(1); + final Parameters params = new Parameters( + "tkn", userToken, + "idx", "1", + "num", String.valueOf(GCConstants.NUMBER_OF_LOGS), + logType.getParamName(), Boolean.toString(Boolean.TRUE), + "decrypt", "true"); + final HttpResponse response = Network.getRequest("http://www.geocaching.com/seek/geocache.logbook", params); + if (response == null) { + Log.e("GCParser.loadLogsFromDetails: cannot log logs, response is null"); + return Observable.empty(); + } + final int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode != 200) { + Log.e("GCParser.loadLogsFromDetails: error " + statusCode + " when requesting log information"); + return Observable.empty(); + } + String rawResponse = Network.getResponseData(response); + if (rawResponse == null) { + Log.e("GCParser.loadLogsFromDetails: unable to read whole response"); + return Observable.empty(); + } + return parseLogs(true, rawResponse); } + }).subscribeOn(Schedulers.io()); + } - final JSONArray data = resp.getJSONArray("data"); + private static Observable<LogEntry> parseLogs(final boolean markAsFriendsLog, final String rawResponse) { + return Observable.create(new OnSubscribe<LogEntry>() { + @Override + public void call(final Subscriber<? super LogEntry> subscriber) { + // for non logged in users the log book is not shown + if (StringUtils.isBlank(rawResponse)) { + subscriber.onCompleted(); + return; + } - for (int index = 0; index < data.length(); index++) { - final JSONObject entry = data.getJSONObject(index); + try { + final JSONObject resp = new JSONObject(rawResponse); + if (!resp.getString("status").equals("success")) { + Log.e("GCParser.loadLogsFromDetails: status is " + resp.getString("status")); + subscriber.onCompleted(); + return; + } - // FIXME: use the "LogType" field instead of the "LogTypeImage" one. - final String logIconNameExt = entry.optString("LogTypeImage", ".gif"); - final String logIconName = logIconNameExt.substring(0, logIconNameExt.length() - 4); + final JSONArray data = resp.getJSONArray("data"); - long date = 0; - try { - date = GCLogin.parseGcCustomDate(entry.getString("Visited")).getTime(); - } catch (final ParseException e) { - Log.e("GCParser.loadLogsFromDetails: failed to parse log date."); - } + for (int index = 0; index < data.length(); index++) { + final JSONObject entry = data.getJSONObject(index); - // TODO: we should update our log data structure to be able to record - // proper coordinates, and make them clickable. In the meantime, it is - // better to integrate those coordinates into the text rather than not - // display them at all. - final String latLon = entry.getString("LatLonString"); - final String logText = (StringUtils.isEmpty(latLon) ? "" : (latLon + "<br/><br/>")) + TextUtils.removeControlCharacters(entry.getString("LogText")); - final LogEntry logDone = new LogEntry( - TextUtils.removeControlCharacters(entry.getString("UserName")), - date, - LogType.getByIconName(logIconName), - logText); - logDone.found = entry.getInt("GeocacheFindCount"); - logDone.friend = friends; - - final JSONArray images = entry.getJSONArray("Images"); - for (int i = 0; i < images.length(); i++) { - final JSONObject image = images.getJSONObject(i); - 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); - } + // FIXME: use the "LogType" field instead of the "LogTypeImage" one. + final String logIconNameExt = entry.optString("LogTypeImage", ".gif"); + final String logIconName = logIconNameExt.substring(0, logIconNameExt.length() - 4); - logs.add(logDone); - } - } catch (final JSONException e) { - // failed to parse logs - Log.w("GCParser.loadLogsFromDetails: Failed to parse cache logs", e); - } + long date = 0; + try { + date = GCLogin.parseGcCustomDate(entry.getString("Visited")).getTime(); + } catch (final ParseException e) { + Log.e("GCParser.loadLogsFromDetails: failed to parse log date."); + } - return logs; + // TODO: we should update our log data structure to be able to record + // proper coordinates, and make them clickable. In the meantime, it is + // better to integrate those coordinates into the text rather than not + // display them at all. + final String latLon = entry.getString("LatLonString"); + final String logText = (StringUtils.isEmpty(latLon) ? "" : (latLon + "<br/><br/>")) + TextUtils.removeControlCharacters(entry.getString("LogText")); + final LogEntry logDone = new LogEntry( + TextUtils.removeControlCharacters(entry.getString("UserName")), + date, + LogType.getByIconName(logIconName), + logText); + logDone.found = entry.getInt("GeocacheFindCount"); + logDone.friend = markAsFriendsLog; + + final JSONArray images = entry.getJSONArray("Images"); + for (int i = 0; i < images.length(); i++) { + final JSONObject image = images.getJSONObject(i); + 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); + } + + subscriber.onNext(logDone); + } + } catch (final JSONException e) { + // failed to parse logs + Log.w("GCParser.loadLogsFromDetails: Failed to parse cache logs", e); + } + subscriber.onCompleted(); + } + }); } @NonNull @@ -1823,32 +1866,39 @@ public abstract class GCParser { } private static void getExtraOnlineInfo(final Geocache cache, final String page, final CancellableHandler handler) { + // This method starts the page parsing for logs in the background, as well as retrieve the friends and own logs + // if requested. It merges them and stores them in the background, while the rating is retrieved if needed and + // stored. Then we wait for the log merging and saving to be completed before returning. if (CancellableHandler.isCancelled(handler)) { return; } - //cache.setLogs(loadLogsFromDetails(page, cache, false)); + final Observable<LogEntry> logs = getLogsFromDetails(page).subscribeOn(Schedulers.computation()); + Observable<LogEntry> specialLogs; if (Settings.isFriendLogsWanted()) { CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_logs); - final List<LogEntry> friendLogs = getLogsFromDetails(page, true); - if (friendLogs != null && !friendLogs.isEmpty()) { - // create new list, as the existing log list is immutable - ArrayList<LogEntry> mergedLogs = new ArrayList<LogEntry>(cache.getLogs()); - for (final LogEntry log : friendLogs) { - if (mergedLogs.contains(log)) { - mergedLogs.get(mergedLogs.indexOf(log)).friend = true; - } else { - mergedLogs.add(log); + specialLogs = Observable.merge(getSpecialLogs(page, SpecialLogs.FRIENDS), + getSpecialLogs(page, SpecialLogs.OWN)); + } else { + CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_logs); + specialLogs = Observable.empty(); + } + final Observable<List<LogEntry>> mergedLogs = Observable.zip(logs.toList(), specialLogs.toList(), + new Func2<List<LogEntry>, List<LogEntry>, List<LogEntry>>() { + @Override + public List<LogEntry> call(final List<LogEntry> logEntries, final List<LogEntry> specialLogEntries) { + mergeFriendsLogs(logEntries, specialLogEntries); + return logEntries; } - } - DataStore.saveLogsWithoutTransaction(cache.getGeocode(), mergedLogs); - } - } - - if (Settings.isRatingWanted()) { - if (CancellableHandler.isCancelled(handler)) { - return; - } + }).cache(); + mergedLogs.subscribe(new Action1<List<LogEntry>>() { + @Override + public void call(final List<LogEntry> logEntries) { + DataStore.saveLogsWithoutTransaction(cache.getGeocode(), logEntries); + } + }); + + if (Settings.isRatingWanted() && !CancellableHandler.isCancelled(handler)) { CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_gcvote); final GCVoteRating rating = GCVote.getRating(cache.getGuid(), cache.getGeocode()); if (rating != null) { @@ -1857,6 +1907,28 @@ public abstract class GCParser { cache.setMyVote(rating.getMyVote()); } } + + // Wait for completion of logs parsing, retrieving and merging + mergedLogs.toBlockingObservable().last(); + } + + /** + * Merge log entries and mark them as friends logs (personal and friends) to identify + * them on friends/personal logs tab. + * + * @param mergedLogs + * the list to merge logs with + * @param logsToMerge + * the list of logs to merge + */ + private static void mergeFriendsLogs(final List<LogEntry> mergedLogs, final Iterable<LogEntry> logsToMerge) { + for (final LogEntry log : logsToMerge) { + if (mergedLogs.contains(log)) { + mergedLogs.get(mergedLogs.indexOf(log)).friend = true; + } else { + mergedLogs.add(log); + } + } } public static boolean uploadModifiedCoordinates(Geocache cache, Geopoint wpt) { diff --git a/main/src/cgeo/geocaching/connector/gc/IconDecoder.java b/main/src/cgeo/geocaching/connector/gc/IconDecoder.java index c7b470a..c6a2afc 100644 --- a/main/src/cgeo/geocaching/connector/gc/IconDecoder.java +++ b/main/src/cgeo/geocaching/connector/gc/IconDecoder.java @@ -35,7 +35,7 @@ public abstract class IconDecoder { return false; //out of image position } - int numberOfDetections = 7; //for level 12 and 13; + int numberOfDetections = 7; //for level 12 and 13 if (zoomlevel < 12) { numberOfDetections = 5; } diff --git a/main/src/cgeo/geocaching/connector/gc/Tile.java b/main/src/cgeo/geocaching/connector/gc/Tile.java index ca70111..d7b3a48 100644 --- a/main/src/cgeo/geocaching/connector/gc/Tile.java +++ b/main/src/cgeo/geocaching/connector/gc/Tile.java @@ -9,6 +9,7 @@ import cgeo.geocaching.utils.LeastRecentlyUsedSet; import cgeo.geocaching.utils.Log; import ch.boye.httpclientandroidlib.HttpResponse; + import org.eclipse.jdt.annotation.NonNull; import android.graphics.Bitmap; @@ -88,10 +89,6 @@ public class Tile { * */ private static int calcY(final Geopoint origin, final int zoomlevel) { - - // double latRad = Math.toRadians(origin.getLatitude()); - // return (int) ((1 - (Math.log(Math.tan(latRad) + (1 / Math.cos(latRad))) / Math.PI)) / 2 * numberOfTiles); - // Optimization from Bing double sinLatRad = Math.sin(Math.toRadians(origin.getLatitude())); // The cut of the fractional part instead of rounding to the nearest integer is intentional and part of the algorithm diff --git a/main/src/cgeo/geocaching/connector/oc/OCApiLiveConnector.java b/main/src/cgeo/geocaching/connector/oc/OCApiLiveConnector.java index 049c633..3771443 100644 --- a/main/src/cgeo/geocaching/connector/oc/OCApiLiveConnector.java +++ b/main/src/cgeo/geocaching/connector/oc/OCApiLiveConnector.java @@ -3,6 +3,7 @@ package cgeo.geocaching.connector.oc; import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.DataStore; import cgeo.geocaching.Geocache; +import cgeo.geocaching.ICache; import cgeo.geocaching.LogCacheActivity; import cgeo.geocaching.SearchResult; import cgeo.geocaching.connector.ILoggingManager; @@ -149,6 +150,11 @@ public class OCApiLiveConnector extends OCApiConnector implements ISearchByCente } @Override + public boolean isOwner(ICache cache) { + return StringUtils.isNotEmpty(getUserName()) && StringUtils.equals(cache.getOwnerDisplayName(), getUserName()); + } + + @Override public String getUserName() { return userInfo.getName(); } diff --git a/main/src/cgeo/geocaching/connector/oc/OkapiClient.java b/main/src/cgeo/geocaching/connector/oc/OkapiClient.java index 3c93488..7d64b31 100644 --- a/main/src/cgeo/geocaching/connector/oc/OkapiClient.java +++ b/main/src/cgeo/geocaching/connector/oc/OkapiClient.java @@ -6,6 +6,7 @@ import cgeo.geocaching.Geocache; import cgeo.geocaching.Image; import cgeo.geocaching.LogEntry; import cgeo.geocaching.R; +import cgeo.geocaching.Trackable; import cgeo.geocaching.Waypoint; import cgeo.geocaching.connector.ConnectorFactory; import cgeo.geocaching.connector.IConnector; @@ -98,6 +99,11 @@ final class OkapiClient { private static final String CACHE_CODE = "code"; private static final String CACHE_REQ_PASSWORD = "req_passwd"; private static final String CACHE_MY_NOTES = "my_notes"; + private static final String CACHE_TRACKABLES_COUNT = "trackables_count"; + private static final String CACHE_TRACKABLES = "trackables"; + + private static final String TRK_GEOCODE = "code"; + private static final String TRK_NAME = "name"; private static final String LOG_TYPE = "type"; private static final String LOG_COMMENT = "comment"; @@ -112,9 +118,9 @@ 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|size2|date_hidden"; + private static final String SERVICE_CACHE_CORE_FIELDS = "code|name|location|type|status|difficulty|terrain|size|size2|date_hidden|trackables_count"; 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_FIELDS = "owner|founds|notfounds|rating|rating_votes|recommendations|description|hint|images|latest_logs|alt_wpts|attrnames|req_passwd|trackables"; private static final String SERVICE_CACHE_ADDITIONAL_CURRENT_FIELDS = "gc_code|attribution_note|attr_acodes"; private static final String SERVICE_CACHE_ADDITIONAL_L3_FIELDS = "is_watched|my_notes"; @@ -328,8 +334,11 @@ final class OkapiClient { parseCoreCache(response, cache); // not used: url - final JSONObject owner = response.getJSONObject(CACHE_OWNER); - cache.setOwnerDisplayName(parseUser(owner)); + final JSONObject ownerObject = response.getJSONObject(CACHE_OWNER); + final String owner = parseUser(ownerObject); + cache.setOwnerDisplayName(owner); + // OpenCaching has no distinction between user id and user display name. Set the ID anyway to simplify c:geo workflows. + cache.setOwnerUserId(owner); cache.getLogCounts().put(LogType.FOUND_IT, response.getInt(CACHE_FOUNDS)); cache.getLogCounts().put(LogType.DIDNT_FIND_IT, response.getInt(CACHE_NOTFOUNDS)); @@ -375,6 +384,9 @@ final class OkapiClient { //TODO: Store license per cache //cache.setLicense(response.getString("attribution_note")); cache.setWaypoints(parseWaypoints(response.getJSONArray(CACHE_WPTS)), false); + + cache.setInventory(parseTrackables(response.getJSONArray(CACHE_TRACKABLES))); + if (!response.isNull(CACHE_IS_WATCHED)) { cache.setOnWatchlist(response.getBoolean(CACHE_IS_WATCHED)); } @@ -409,6 +421,8 @@ final class OkapiClient { cache.setDifficulty((float) response.getDouble(CACHE_DIFFICULTY)); cache.setTerrain((float) response.getDouble(CACHE_TERRAIN)); + cache.setInventoryItems(response.getInt(CACHE_TRACKABLES_COUNT)); + if (!response.isNull(CACHE_IS_FOUND)) { cache.setFound(response.getBoolean(CACHE_IS_FOUND)); } @@ -478,6 +492,27 @@ final class OkapiClient { return result; } + private static List<Trackable> parseTrackables(final JSONArray trackablesJson) { + if (trackablesJson.length() == 0) { + return Collections.emptyList(); + } + final List<Trackable> result = new ArrayList<Trackable>(); + for (int i = 0; i < trackablesJson.length(); i++) { + try { + final JSONObject trackableResponse = trackablesJson.getJSONObject(i); + final Trackable trk = new Trackable(); + trk.setGeocode(trackableResponse.getString(TRK_GEOCODE)); + trk.setName(trackableResponse.getString(TRK_NAME)); + result.add(trk); + } catch (final JSONException e) { + Log.e("OkapiClient.parseWaypoints", e); + // Don't overwrite internal state with possibly partial result + return null; + } + } + return result; + } + private static LogType parseLogType(final String logType) { if ("Found it".equalsIgnoreCase(logType)) { return LogType.FOUND_IT; @@ -593,7 +628,7 @@ final class OkapiClient { try { final String size = response.getString(CACHE_SIZE2); return CacheSize.getById(size); - } catch (JSONException e) { + } catch (final JSONException e) { Log.e("OkapiClient.getCacheSize", e); return getCacheSizeDeprecated(response); } @@ -702,7 +737,7 @@ final class OkapiClient { params.add("langpref", getPreferredLanguage()); if (connector.getSupportedAuthLevel() == OAuthLevel.Level3) { - ImmutablePair<String, String> tokens = Settings.getTokenPair(connector.getTokenPublicPrefKeyId(), connector.getTokenSecretPrefKeyId()); + final ImmutablePair<String, String> tokens = Settings.getTokenPair(connector.getTokenPublicPrefKeyId(), connector.getTokenSecretPrefKeyId()); OAuth.signOAuth(host, service.methodName, "GET", false, params, tokens.left, tokens.right, connector.getCK(), connector.getCS()); } else { connector.addAuthentication(params); @@ -769,7 +804,7 @@ final class OkapiClient { return null; } - JSONObject data = result.data; + final JSONObject data = result.data; if (!data.isNull(USER_UUID)) { try { return data.getString(USER_UUID); @@ -792,7 +827,7 @@ final class OkapiClient { return new UserInfo(StringUtils.EMPTY, 0, UserInfoStatus.getFromOkapiError(error.getResult())); } - JSONObject data = result.data; + final JSONObject data = result.data; String name = StringUtils.EMPTY; boolean successUserName = false; @@ -828,7 +863,7 @@ final class OkapiClient { * response containing an error object * @return OkapiError object with detailed information */ - public static OkapiError decodeErrorResponse(HttpResponse response) { + public static OkapiError decodeErrorResponse(final HttpResponse response) { final JSONResult result = new JSONResult(response); if (!result.isSuccess) { return new OkapiError(result.data); @@ -846,7 +881,7 @@ final class OkapiClient { public final JSONObject data; public JSONResult(final @Nullable HttpResponse response) { - boolean isSuccess = Network.isSuccess(response); + final boolean isSuccess = Network.isSuccess(response); final String responseData = Network.getResponseDataAlways(response); JSONObject data = null; if (responseData != null) { diff --git a/main/src/cgeo/geocaching/export/GpxExport.java b/main/src/cgeo/geocaching/export/GpxExport.java index 08fca0b..39f4bcf 100644 --- a/main/src/cgeo/geocaching/export/GpxExport.java +++ b/main/src/cgeo/geocaching/export/GpxExport.java @@ -8,6 +8,7 @@ import cgeo.geocaching.settings.Settings; import cgeo.geocaching.utils.AsyncTaskWithProgress; import cgeo.geocaching.utils.FileUtils; import cgeo.geocaching.utils.Log; +import cgeo.geocaching.utils.ShareUtils; import org.apache.commons.lang3.CharEncoding; @@ -15,8 +16,6 @@ import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; -import android.content.Intent; -import android.net.Uri; import android.os.Environment; import android.view.ContextThemeWrapper; import android.view.View; @@ -168,11 +167,7 @@ class GpxExport extends AbstractExport { if (exportFile != null) { ActivityMixin.showToast(activity, getName() + ' ' + getString(R.string.export_exportedto) + ": " + exportFile.toString()); if (Settings.getShareAfterExport()) { - final Intent shareIntent = new Intent(); - shareIntent.setAction(Intent.ACTION_SEND); - shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(exportFile)); - shareIntent.setType("application/xml"); - activity.startActivity(Intent.createChooser(shareIntent, getString(R.string.export_gpx_to))); + ShareUtils.share(activity, exportFile, "application/xml", R.string.export_gpx_to); } } else { ActivityMixin.showToast(activity, getString(R.string.export_failed)); diff --git a/main/src/cgeo/geocaching/files/FileParser.java b/main/src/cgeo/geocaching/files/FileParser.java index 396a589..973e65f 100644 --- a/main/src/cgeo/geocaching/files/FileParser.java +++ b/main/src/cgeo/geocaching/files/FileParser.java @@ -4,6 +4,8 @@ import cgeo.geocaching.Geocache; import cgeo.geocaching.utils.CancellableHandler; import org.apache.commons.io.IOUtils; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; import java.io.BufferedInputStream; import java.io.BufferedReader; @@ -29,7 +31,7 @@ public abstract class FileParser { * @throws ParserException * if the input stream contains data not matching the file format of the parser */ - public abstract Collection<Geocache> parse(final InputStream stream, final CancellableHandler progressHandler) throws IOException, ParserException; + public abstract Collection<Geocache> parse(@NonNull final InputStream stream, @Nullable final CancellableHandler progressHandler) throws IOException, ParserException; /** * Convenience method for parsing a file. @@ -49,7 +51,7 @@ public abstract class FileParser { } } - protected static StringBuilder readStream(InputStream is, CancellableHandler progressHandler) throws IOException { + protected static StringBuilder readStream(@NonNull final InputStream is, @Nullable final CancellableHandler progressHandler) throws IOException { final StringBuilder buffer = new StringBuilder(); ProgressInputStream progressInputStream = new ProgressInputStream(is); final BufferedReader input = new BufferedReader(new InputStreamReader(progressInputStream, "UTF-8")); @@ -66,7 +68,7 @@ public abstract class FileParser { } } - protected static void showProgressMessage(final CancellableHandler handler, final int bytesRead) { + protected static void showProgressMessage(@Nullable final CancellableHandler handler, final int bytesRead) { if (handler != null) { if (handler.isCancelled()) { throw new CancellationException(); diff --git a/main/src/cgeo/geocaching/files/FileType.java b/main/src/cgeo/geocaching/files/FileType.java new file mode 100644 index 0000000..ef62351 --- /dev/null +++ b/main/src/cgeo/geocaching/files/FileType.java @@ -0,0 +1,8 @@ +package cgeo.geocaching.files; + +public enum FileType { + UNKNOWN, + LOC, + GPX, + ZIP +} diff --git a/main/src/cgeo/geocaching/files/FileTypeDetector.java b/main/src/cgeo/geocaching/files/FileTypeDetector.java new file mode 100644 index 0000000..389b83a --- /dev/null +++ b/main/src/cgeo/geocaching/files/FileTypeDetector.java @@ -0,0 +1,77 @@ +package cgeo.geocaching.files; + +import cgeo.geocaching.utils.Log; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.eclipse.jdt.annotation.NonNull; + +import android.content.ContentResolver; +import android.net.Uri; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +public class FileTypeDetector { + + private final ContentResolver contentResolver; + private final Uri uri; + + public FileTypeDetector(Uri uri, ContentResolver contentResolver) { + this.uri = uri; + this.contentResolver = contentResolver; + } + + public @NonNull FileType getFileType() { + InputStream is = null; + BufferedReader reader = null; + FileType type = FileType.UNKNOWN; + try { + is = contentResolver.openInputStream(uri); + if (is == null) { + return FileType.UNKNOWN; + } + reader = new BufferedReader(new InputStreamReader(is)); + type = detectHeader(reader); + reader.close(); + } catch (FileNotFoundException e) { + Log.e("FileTypeDetector", e); + } catch (IOException e) { + Log.e("FileTypeDetector", e); + } finally { + IOUtils.closeQuietly(reader); + IOUtils.closeQuietly(is); + } + return type; + } + + private static FileType detectHeader(BufferedReader reader) + throws IOException { + String line = reader.readLine(); + if (isZip(line)) { + return FileType.ZIP; + } + // scan at most 5 lines of a GPX file + for (int i = 0; i < 5; i++) { + line = StringUtils.trim(line); + if (StringUtils.contains(line, "<loc")) { + return FileType.LOC; + } + if (StringUtils.contains(line, "<gpx")) { + return FileType.GPX; + } + line = reader.readLine(); + } + return FileType.UNKNOWN; + } + + private static boolean isZip(String line) { + return StringUtils.length(line) >= 4 + && StringUtils.startsWith(line, "PK") && line.charAt(2) == 3 + && line.charAt(3) == 4; + } + +} diff --git a/main/src/cgeo/geocaching/files/GPXImporter.java b/main/src/cgeo/geocaching/files/GPXImporter.java index cd2f445..52f68e1 100644 --- a/main/src/cgeo/geocaching/files/GPXImporter.java +++ b/main/src/cgeo/geocaching/files/GPXImporter.java @@ -12,8 +12,10 @@ import cgeo.geocaching.settings.Settings; import cgeo.geocaching.ui.dialog.Dialogs; import cgeo.geocaching.utils.CancellableHandler; import cgeo.geocaching.utils.Log; +import cgeo.geocaching.utils.RxUtils; import org.apache.commons.lang3.StringUtils; +import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import android.app.Activity; @@ -93,46 +95,77 @@ public class GPXImporter { * * @param uri * URI of the file to import - * @param knownMimeType - * @param knownPathName + * @param mimeType + * @param pathName */ - public void importGPX(final Uri uri, final @Nullable String knownMimeType, final @Nullable String knownPathName) { + public void importGPX(final Uri uri, final @Nullable String mimeType, final @Nullable String pathName) { final ContentResolver contentResolver = fromActivity.getContentResolver(); - String mimeType = knownMimeType; - final String pathName = knownPathName != null ? knownPathName : uri.getPath(); - - // if mimetype can't be determined (e.g. for emulators email app), derive it from uri file extension - // contentResolver.getType(uri) doesn't help but throws exception for emulators email app - // Permission Denial: reading com.android.email.provider.EmailProvider uri - // Google search says: there is no solution for this problem - // Gmail doesn't work at all, see #967 - if (mimeType == null) { - if (StringUtils.endsWithIgnoreCase(pathName, GPX_FILE_EXTENSION) || StringUtils.endsWithIgnoreCase(pathName, LOC_FILE_EXTENSION)) { - mimeType = "application/xml"; - } else { - // if we can't determine a better type, default to zip import - // emulator email sends e.g. content://com.android.email.attachmentprovider/1/1/RAW, mimetype=null - mimeType = "application/zip"; - } - } Log.i("importGPX: " + uri + ", mimetype=" + mimeType); - if (GPX_MIME_TYPES.contains(mimeType)) { - if (StringUtils.endsWithIgnoreCase(pathName, LOC_FILE_EXTENSION)) { - new ImportLocAttachmentThread(uri, contentResolver, listId, importStepHandler, progressHandler).start(); - } else { - new ImportGpxAttachmentThread(uri, contentResolver, listId, importStepHandler, progressHandler).start(); - } - } else if (ZIP_MIME_TYPES.contains(mimeType)) { - new ImportGpxZipAttachmentThread(uri, contentResolver, listId, importStepHandler, progressHandler).start(); - } else { - importFinished(); + @NonNull + FileType fileType = new FileTypeDetector(uri, contentResolver) + .getFileType(); + + if (fileType == FileType.UNKNOWN) { + fileType = getFileTypeFromPathName(pathName); + } + if (fileType == FileType.UNKNOWN) { + fileType = getFileTypeFromMimeType(mimeType); + } + + ImportThread importer = getImporterFromFileType(uri, contentResolver, + fileType); + + if (importer != null) { + importer.start(); + } else { + importFinished(); + } + } + + private static @NonNull FileType getFileTypeFromPathName( + final String pathName) { + if (StringUtils.endsWithIgnoreCase(pathName, GPX_FILE_EXTENSION)) { + return FileType.GPX; } - } - /** - * Import GPX provided via intent of activity that instantiated this GPXImporter. - */ + if (StringUtils.endsWithIgnoreCase(pathName, LOC_FILE_EXTENSION)) { + return FileType.LOC; + } + return FileType.UNKNOWN; + } + + private static @NonNull FileType getFileTypeFromMimeType( + final String mimeType) { + if (GPX_MIME_TYPES.contains(mimeType)) { + return FileType.GPX; + } else if (ZIP_MIME_TYPES.contains(mimeType)) { + return FileType.ZIP; + } + return FileType.UNKNOWN; + } + + private ImportThread getImporterFromFileType(Uri uri, + ContentResolver contentResolver, FileType fileType) { + switch (fileType) { + case ZIP: + return new ImportGpxZipAttachmentThread(uri, contentResolver, + listId, importStepHandler, progressHandler); + case GPX: + return new ImportGpxAttachmentThread(uri, contentResolver, listId, + importStepHandler, progressHandler); + case LOC: + return new ImportLocAttachmentThread(uri, contentResolver, listId, + importStepHandler, progressHandler); + default: + return null; + } + } + + /** + * Import GPX provided via intent of activity that instantiated this + * GPXImporter. + */ public void importGPX() { final Intent intent = fromActivity.getIntent(); final Uri uri = intent.getData(); @@ -194,7 +227,7 @@ public class GPXImporter { final Geocache cache = DataStore.loadCache(geocode, LoadFlags.LOAD_WAYPOINTS); if (cache != null) { Log.d("GPXImporter.ImportThread.importStaticMaps start downloadMaps for cache " + geocode); - StaticMapsProvider.downloadMaps(cache); + RxUtils.waitForCompletion(StaticMapsProvider.downloadMaps(cache)); } else { Log.d("GPXImporter.ImportThread.importStaticMaps: no data found for " + geocode); } diff --git a/main/src/cgeo/geocaching/files/GPXParser.java b/main/src/cgeo/geocaching/files/GPXParser.java index 6161088..f3cd326 100644 --- a/main/src/cgeo/geocaching/files/GPXParser.java +++ b/main/src/cgeo/geocaching/files/GPXParser.java @@ -25,6 +25,8 @@ import cgeo.geocaching.utils.SynchronizedDateFormat; import org.apache.commons.lang3.CharEncoding; import org.apache.commons.lang3.StringUtils; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; import org.xml.sax.Attributes; import org.xml.sax.SAXException; @@ -270,7 +272,7 @@ public abstract class GPXParser extends FileParser { } @Override - public Collection<Geocache> parse(final InputStream stream, final CancellableHandler progressHandler) throws IOException, ParserException { + public Collection<Geocache> parse(@NonNull final InputStream stream, @Nullable final CancellableHandler progressHandler) throws IOException, ParserException { resetCache(); final RootElement root = new RootElement(namespace, "gpx"); final Element waypoint = root.getChild(namespace, "wpt"); diff --git a/main/src/cgeo/geocaching/files/LocParser.java b/main/src/cgeo/geocaching/files/LocParser.java index 3d01c1b..223fb5a 100644 --- a/main/src/cgeo/geocaching/files/LocParser.java +++ b/main/src/cgeo/geocaching/files/LocParser.java @@ -12,6 +12,8 @@ import cgeo.geocaching.utils.Log; import cgeo.geocaching.utils.MatcherWrapper; import org.apache.commons.lang3.StringUtils; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; import java.io.IOException; import java.io.InputStream; @@ -116,9 +118,9 @@ public final class LocParser extends FileParser { } @Override - public Collection<Geocache> parse(InputStream stream, CancellableHandler progressHandler) throws IOException, ParserException { - // TODO: progress reporting happens during reading stream only, not during parsing - String streamContent = readStream(stream, progressHandler).toString(); + public Collection<Geocache> parse(@NonNull final InputStream stream, @Nullable final CancellableHandler progressHandler) throws IOException, ParserException { + final String streamContent = readStream(stream, null).toString(); + final int maxSize = streamContent.length(); final Map<String, Geocache> coords = parseCoordinates(streamContent); final List<Geocache> caches = new ArrayList<Geocache>(); for (Entry<String, Geocache> entry : coords.entrySet()) { @@ -136,6 +138,9 @@ public final class LocParser extends FileParser { cache.setListId(listId); cache.setDetailed(true); cache.store(null); + if (progressHandler != null) { + progressHandler.sendMessage(progressHandler.obtainMessage(0, maxSize * caches.size() / coords.size(), 0)); + } } Log.i("Caches found in .loc file: " + caches.size()); return caches; diff --git a/main/src/cgeo/geocaching/filter/PopularityRatioFilter.java b/main/src/cgeo/geocaching/filter/PopularityRatioFilter.java index 2d7207a..5bfab28 100644 --- a/main/src/cgeo/geocaching/filter/PopularityRatioFilter.java +++ b/main/src/cgeo/geocaching/filter/PopularityRatioFilter.java @@ -24,22 +24,15 @@ class PopularityRatioFilter extends AbstractFilter { @Override public boolean accepts(final Geocache cache) { + final int finds = getFindsCount(cache); - int finds; - int favorites; - float ratio; - - finds = getFindsCount(cache); - - // prevent division by zero - if (finds == 0) { + if (finds == 0) { // Prevent division by zero return false; } - favorites = cache.getFavoritePoints(); - ratio = ((float) favorites / (float) finds) * 100.0f; - - return (ratio > minRatio) && (ratio <= maxRatio); + final int favorites = cache.getFavoritePoints(); + final float ratio = 100.0f * favorites / finds; + return ratio > minRatio && ratio <= maxRatio; } private static int getFindsCount(Geocache cache) { diff --git a/main/src/cgeo/geocaching/filter/StateFilter.java b/main/src/cgeo/geocaching/filter/StateFilter.java index f452259..fd14b69 100644 --- a/main/src/cgeo/geocaching/filter/StateFilter.java +++ b/main/src/cgeo/geocaching/filter/StateFilter.java @@ -32,6 +32,19 @@ abstract class StateFilter extends AbstractFilter { } + static class StateNotFoundFilter extends StateFilter { + + public StateNotFoundFilter() { + super(res.getString(R.string.cache_not_status_found)); + } + + @Override + public boolean accepts(final Geocache cache) { + return !cache.isFound(); + } + + } + static class StateArchivedFilter extends StateFilter { public StateArchivedFilter() { super(res.getString(R.string.cache_status_archived)); @@ -115,6 +128,7 @@ abstract class StateFilter extends AbstractFilter { public List<StateFilter> getFilters() { final List<StateFilter> filters = new ArrayList<StateFilter>(6); filters.add(new StateFoundFilter()); + filters.add(new StateNotFoundFilter()); filters.add(new StateArchivedFilter()); filters.add(new StateDisabledFilter()); filters.add(new StatePremiumFilter()); diff --git a/main/src/cgeo/geocaching/gcvote/GCVote.java b/main/src/cgeo/geocaching/gcvote/GCVote.java index 0ab1fe3..d77a4e6 100644 --- a/main/src/cgeo/geocaching/gcvote/GCVote.java +++ b/main/src/cgeo/geocaching/gcvote/GCVote.java @@ -1,6 +1,8 @@ package cgeo.geocaching.gcvote; +import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.Geocache; +import cgeo.geocaching.R; import cgeo.geocaching.network.Network; import cgeo.geocaching.network.Parameters; import cgeo.geocaching.settings.Settings; @@ -253,7 +255,7 @@ public final class GCVote { /** * Get geocodes of all the caches, which can be used with GCVote. Non-GC caches will be filtered out. - * + * * @param caches * @return */ @@ -281,4 +283,33 @@ public final class GCVote { return Settings.isGCvoteLogin() && StringUtils.isNotBlank(cache.getGuid()) && cache.supportsGCVote(); } + public static String getDescription(final float rating) { + switch (Math.round(rating * 2f)) { + case 2: + return getString(R.string.log_stars_1_description); + case 3: + return getString(R.string.log_stars_15_description); + case 4: + return getString(R.string.log_stars_2_description); + case 5: + return getString(R.string.log_stars_25_description); + case 6: + return getString(R.string.log_stars_3_description); + case 7: + return getString(R.string.log_stars_35_description); + case 8: + return getString(R.string.log_stars_4_description); + case 9: + return getString(R.string.log_stars_45_description); + case 10: + return getString(R.string.log_stars_5_description); + default: + return getString(R.string.log_no_rating); + } + } + + private static String getString(int resId) { + return CgeoApplication.getInstance().getString(resId); + } + } diff --git a/main/src/cgeo/geocaching/geopoint/Geopoint.java b/main/src/cgeo/geocaching/geopoint/Geopoint.java index 1655343..bb34114 100644 --- a/main/src/cgeo/geocaching/geopoint/Geopoint.java +++ b/main/src/cgeo/geocaching/geopoint/Geopoint.java @@ -556,15 +556,14 @@ public final class Geopoint implements ICoordinates, Parcelable { * Gets distance in meters (workaround for 4.2.1 JIT bug). */ public static double getDistance(double lat1, double lon1, double lat2, double lon2) { - double earthRadius = 6372.8; // for haversine use R = 6372.8 km instead of 6371 km + // for haversine use R = 6372.8 km instead of 6371 km + double earthRadius = 6372.8; double dLat = toRadians(lat2 - lat1); double dLon = toRadians(lon2 - lon1); double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(toRadians(lat1)) * Math.cos(toRadians(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2); - //double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); - //return R * c * 1000; - // simplify haversine: + // simplify haversine return (2 * earthRadius * 1000 * Math.asin(Math.sqrt(a))); } @@ -582,8 +581,8 @@ public final class Geopoint implements ICoordinates, Parcelable { } /** - * Check whether a lo bngitudeuilt from user supplied data is valid. We accept both E180/W180. - * + * Check whether a longitude from user supplied data is valid. We accept both E180/W180. + * * @return <tt>true</tt> if the longitude looks valid, <tt>false</tt> otherwise */ public static boolean isValidLongitude(final double longitude) { diff --git a/main/src/cgeo/geocaching/geopoint/Units.java b/main/src/cgeo/geocaching/geopoint/Units.java index b99e00e..d00e075 100644 --- a/main/src/cgeo/geocaching/geopoint/Units.java +++ b/main/src/cgeo/geocaching/geopoint/Units.java @@ -4,6 +4,8 @@ import cgeo.geocaching.settings.Settings; import org.apache.commons.lang3.tuple.ImmutablePair; +import java.util.Locale; + public class Units { public static ImmutablePair<Double, String> scaleDistance(final double distanceKilometers) { @@ -51,11 +53,10 @@ public class Units { return getDistanceFromKilometers(meters / 1000f); } - public static String getSpeed(float kilometersPerHour) { - final String speed = getDistanceFromKilometers(kilometersPerHour); - if (speed.endsWith("mi")) { - return speed.substring(0, speed.length() - 2) + "mph"; + public static String getSpeed(final float kilometersPerHour) { + if (Settings.isUseImperialUnits()) { + return String.format(Locale.US, "%.0f mph", kilometersPerHour / IConversion.MILES_TO_KILOMETER); } - return speed + (!Settings.isUseImperialUnits() ? "/h" : "ph"); + return String.format(Locale.US, "%.0f km/h", kilometersPerHour); } } diff --git a/main/src/cgeo/geocaching/list/AbstractList.java b/main/src/cgeo/geocaching/list/AbstractList.java index ec783eb..06f44a2 100644 --- a/main/src/cgeo/geocaching/list/AbstractList.java +++ b/main/src/cgeo/geocaching/list/AbstractList.java @@ -20,6 +20,10 @@ public abstract class AbstractList { public abstract boolean isConcrete(); + public abstract String getTitle(); + + public abstract int getNumberOfCaches(); + @Nullable public static AbstractList getListById(int listId) { return LISTS.get(listId); diff --git a/main/src/cgeo/geocaching/list/PseudoList.java b/main/src/cgeo/geocaching/list/PseudoList.java index f2cc7ed..9ee920c 100644 --- a/main/src/cgeo/geocaching/list/PseudoList.java +++ b/main/src/cgeo/geocaching/list/PseudoList.java @@ -1,32 +1,48 @@ package cgeo.geocaching.list; import cgeo.geocaching.CgeoApplication; +import cgeo.geocaching.DataStore; import cgeo.geocaching.R; -public class PseudoList extends AbstractList { +public abstract class PseudoList extends AbstractList { private static final int ALL_LIST_ID = 2; /** * list entry to show all caches */ - public static final PseudoList ALL_LIST = new PseudoList(ALL_LIST_ID, R.string.list_all_lists); + public static final PseudoList ALL_LIST = new PseudoList(ALL_LIST_ID, R.string.list_all_lists) { + @Override + public int getNumberOfCaches() { + return DataStore.getAllCachesCount(); + } + }; private static final int NEW_LIST_ID = 3; /** * list entry to create a new list */ - public static final AbstractList NEW_LIST = new PseudoList(NEW_LIST_ID, R.string.list_menu_create); + public static final AbstractList NEW_LIST = new PseudoList(NEW_LIST_ID, R.string.list_menu_create) { + @Override + public int getNumberOfCaches() { + return -1; + } + }; 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); + public static final AbstractList HISTORY_LIST = new PseudoList(HISTORY_LIST_ID, R.string.menu_history) { + @Override + public int getNumberOfCaches() { + return DataStore.getAllHistoryCachesCount(); + } + }; /** * private constructor to have all instances as constants in the class */ - private PseudoList(int id, final int titleResourceId) { + private PseudoList(final int id, final int titleResourceId) { super(id, CgeoApplication.getInstance().getResources().getString(titleResourceId)); } @@ -36,6 +52,11 @@ public class PseudoList extends AbstractList { } @Override + public String getTitle() { + return title; + } + + @Override public boolean isConcrete() { return false; } diff --git a/main/src/cgeo/geocaching/list/StoredList.java b/main/src/cgeo/geocaching/list/StoredList.java index e557fc8..6dac1a7 100644 --- a/main/src/cgeo/geocaching/list/StoredList.java +++ b/main/src/cgeo/geocaching/list/StoredList.java @@ -16,6 +16,7 @@ import android.app.AlertDialog; import android.content.DialogInterface; import android.content.res.Resources; +import java.lang.ref.WeakReference; import java.text.Collator; import java.util.ArrayList; import java.util.Collections; @@ -58,12 +59,12 @@ public final class StoredList extends AbstractList { } public static class UserInterface { - private final Activity activity; + private final WeakReference<Activity> activityRef; private final CgeoApplication app; private final Resources res; - public UserInterface(final Activity activity) { - this.activity = activity; + public UserInterface(final @NonNull Activity activity) { + this.activityRef = new WeakReference<Activity>(activity); app = CgeoApplication.getInstance(); res = app.getResources(); } @@ -77,25 +78,7 @@ public final class StoredList extends AbstractList { } 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()); - - if (exceptListId > StoredList.TEMPORARY_LIST_ID) { - StoredList exceptList = DataStore.getList(exceptListId); - if (exceptList != null) { - lists.remove(exceptList); - } - } - - if (!onlyConcreteLists) { - 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); + final List<AbstractList> lists = getMenuLists(onlyConcreteLists, exceptListId); final List<CharSequence> listsTitle = new ArrayList<CharSequence>(); for (AbstractList list : lists) { @@ -104,6 +87,7 @@ public final class StoredList extends AbstractList { final CharSequence[] items = new CharSequence[listsTitle.size()]; + final Activity activity = activityRef.get(); AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setTitle(res.getString(titleId)); builder.setItems(listsTitle.toArray(items), new DialogInterface.OnClickListener() { @@ -122,6 +106,31 @@ public final class StoredList extends AbstractList { builder.create().show(); } + public static List<AbstractList> getMenuLists(boolean onlyConcreteLists, int exceptListId) { + final List<AbstractList> lists = new ArrayList<AbstractList>(); + lists.addAll(getSortedLists()); + + if (exceptListId > StoredList.TEMPORARY_LIST_ID) { + StoredList exceptList = DataStore.getList(exceptListId); + if (exceptList != null) { + lists.remove(exceptList); + } + } + + if (!onlyConcreteLists) { + if (exceptListId != PseudoList.ALL_LIST.id) { + lists.add(PseudoList.ALL_LIST); + } + if (exceptListId != PseudoList.HISTORY_LIST.id) { + lists.add(PseudoList.HISTORY_LIST); + } + } + if (exceptListId != PseudoList.NEW_LIST.id) { + lists.add(PseudoList.NEW_LIST); + } + return lists; + } + @NonNull private static List<StoredList> getSortedLists() { final Collator collator = Collator.getInstance(); @@ -151,6 +160,10 @@ public final class StoredList extends AbstractList { @SuppressWarnings("unused") @Override public void call(final String listName) { + final Activity activity = activityRef.get(); + if (activity == null) { + return; + } final int newId = DataStore.createList(listName); new StoredList(newId, listName, 0); @@ -165,6 +178,10 @@ public final class StoredList extends AbstractList { } private void handleListNameInput(final String defaultValue, int dialogTitle, int buttonTitle, final Action1<String> runnable) { + final Activity activity = activityRef.get(); + if (activity == null) { + return; + } Dialogs.input(activity, dialogTitle, defaultValue, buttonTitle, new Action1<String>() { @Override @@ -193,14 +210,18 @@ public final class StoredList extends AbstractList { } /** - * Get the list title. This method is not public by intention to make clients use the {@link UserInterface} class. - * - * @return + * Get the list title. */ - protected String getTitle() { + @Override + public String getTitle() { return title; } + @Override + public int getNumberOfCaches() { + return count; + } + /** * Return the given list, if it is a concrete list. Return the default list otherwise. */ diff --git a/main/src/cgeo/geocaching/loaders/OfflineGeocacheListLoader.java b/main/src/cgeo/geocaching/loaders/OfflineGeocacheListLoader.java index b80a1b8..0d5af6a 100644 --- a/main/src/cgeo/geocaching/loaders/OfflineGeocacheListLoader.java +++ b/main/src/cgeo/geocaching/loaders/OfflineGeocacheListLoader.java @@ -1,18 +1,20 @@ package cgeo.geocaching.loaders; import cgeo.geocaching.DataStore; +import cgeo.geocaching.Intents; import cgeo.geocaching.SearchResult; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.settings.Settings; import android.content.Context; +import android.os.Bundle; public class OfflineGeocacheListLoader extends AbstractSearchLoader { - private int listId; - private Geopoint searchCenter; + private final int listId; + private final Geopoint searchCenter; - public OfflineGeocacheListLoader(Context context, Geopoint searchCenter, int listId) { + public OfflineGeocacheListLoader(final Context context, final Geopoint searchCenter, final int listId) { super(context); this.searchCenter = searchCenter; this.listId = listId; @@ -23,12 +25,14 @@ public class OfflineGeocacheListLoader extends AbstractSearchLoader { return DataStore.getBatchOfStoredCaches(searchCenter, Settings.getCacheType(), listId); } - public void setListId(int listId) { - this.listId = listId; - } - - public void setSearchCenter(Geopoint searchCenter) { - this.searchCenter = searchCenter; + /** + * @param listId + * @return the bundle needed for querying the LoaderManager for the offline list with the given id + */ + public static Bundle getBundleForList(final int listId) { + final Bundle bundle = new Bundle(); + bundle.putInt(Intents.EXTRA_LIST_ID, listId); + return bundle; } } diff --git a/main/src/cgeo/geocaching/maps/AbstractMap.java b/main/src/cgeo/geocaching/maps/AbstractMap.java index d341823..2eceadb 100644 --- a/main/src/cgeo/geocaching/maps/AbstractMap.java +++ b/main/src/cgeo/geocaching/maps/AbstractMap.java @@ -5,10 +5,11 @@ import cgeo.geocaching.maps.interfaces.MapActivityImpl; import android.app.Activity; import android.content.res.Resources; +import android.os.Build; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; -import android.view.View; +import android.view.Window; /** * Base class for the map activity. Delegates base class calls to the @@ -31,7 +32,11 @@ public abstract class AbstractMap { } public void onCreate(Bundle savedInstanceState) { + mapActivity.superOnCreate(savedInstanceState); + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB) { + mapActivity.getActivity().requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); + } } public void onResume() { @@ -64,8 +69,6 @@ public abstract class AbstractMap { return mapActivity.superOnOptionsItemSelected(item); } - public abstract void goHome(View view); - public abstract void onSaveInstanceState(final Bundle outState); } diff --git a/main/src/cgeo/geocaching/maps/CGeoMap.java b/main/src/cgeo/geocaching/maps/CGeoMap.java index 00aee36..15b2667 100644 --- a/main/src/cgeo/geocaching/maps/CGeoMap.java +++ b/main/src/cgeo/geocaching/maps/CGeoMap.java @@ -43,14 +43,16 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; -import rx.Scheduler; import rx.Subscription; +import rx.functions.Action0; import rx.functions.Action1; import rx.schedulers.Schedulers; import rx.subscriptions.CompositeSubscription; import rx.subscriptions.Subscriptions; +import android.annotation.TargetApi; import android.app.Activity; import android.app.AlertDialog; import android.app.ProgressDialog; @@ -61,6 +63,7 @@ import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.location.Location; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; @@ -70,9 +73,11 @@ import android.view.MenuItem; import android.view.SubMenu; import android.view.View; import android.view.ViewGroup.LayoutParams; +import android.widget.CheckBox; import android.widget.ImageSwitcher; import android.widget.ImageView; import android.widget.ImageView.ScaleType; +import android.widget.ProgressBar; import android.widget.TextView; import android.widget.ViewSwitcher.ViewFactory; @@ -92,7 +97,7 @@ import java.util.concurrent.TimeUnit; /** * Class representing the Map in c:geo */ -public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFactory { +public class CGeoMap extends AbstractMap implements ViewFactory { /** max. number of caches displayed in the Live Map */ public static final int MAX_CACHES = 500; @@ -171,7 +176,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto private static final int[][] INSET_USERMODIFIEDCOORDS = { { 21, 28, 0, 0 }, { 19, 25, 0, 0 }, { 25, 33, 0, 0 } }; // bottom right, 12x12 / 26x26 / 35x35 private static final int[][] INSET_PERSONALNOTE = { { 0, 28, 21, 0 }, { 0, 25, 19, 0 }, { 0, 33, 25, 0 } }; // bottom left, 12x12 / 26x26 / 35x35 - private SparseArray<LayerDrawable> overlaysCache = new SparseArray<LayerDrawable>(); + private final SparseArray<LayerDrawable> overlaysCache = new SparseArray<LayerDrawable>(); /** Count of caches currently visible */ private int cachesCnt = 0; /** List of caches in the viewport */ @@ -184,9 +189,9 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto private int detailProgress = 0; private long detailProgressTime = 0L; // views - private ImageSwitcher myLocSwitch = null; + private CheckBox myLocSwitch = null; - /** Controls the map behaviour */ + /** Controls the map behavior */ private MapMode mapMode = null; /** Live mode enabled for map. **/ private boolean isLiveEnabled; @@ -210,74 +215,130 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto // handlers /** Updates the titles */ - final private Handler displayHandler = new Handler() { + private final static class DisplayHandler extends Handler { + + private final WeakReference<CGeoMap> mapRef; + + public DisplayHandler(@NonNull final CGeoMap map) { + this.mapRef = new WeakReference<CGeoMap>(map); + } @Override - public void handleMessage(Message msg) { + public void handleMessage(final Message msg) { final int what = msg.what; + final CGeoMap map = mapRef.get(); + if (map == null) { + return; + } switch (what) { case UPDATE_TITLE: // set title final StringBuilder title = new StringBuilder(); - if (mapMode == MapMode.LIVE && isLiveEnabled) { - title.append(res.getString(R.string.map_live)); + if (map.mapMode == MapMode.LIVE && map.isLiveEnabled) { + title.append(map.res.getString(R.string.map_live)); } else { - title.append(mapTitle); + title.append(map.mapTitle); } - countVisibleCaches(); - if (caches != null && !caches.isEmpty() && !mapTitle.contains("[")) { - title.append(" [").append(cachesCnt); - if (cachesCnt != caches.size()) { - title.append('/').append(caches.size()); + map.countVisibleCaches(); + if (map.caches != null && !map.caches.isEmpty() && !map.mapTitle.contains("[")) { + title.append(" [").append(map.cachesCnt); + if (map.cachesCnt != map.caches.size()) { + title.append('/').append(map.caches.size()); } title.append(']'); } - if (Settings.isDebug() && lastSearchResult != null && StringUtils.isNotBlank(lastSearchResult.getUrl())) { - title.append('[').append(lastSearchResult.getUrl()).append(']'); + if (Settings.isDebug() && map.lastSearchResult != null && StringUtils.isNotBlank(map.lastSearchResult.getUrl())) { + title.append('[').append(map.lastSearchResult.getUrl()).append(']'); } - ActivityMixin.setTitle(activity, title.toString()); + map.setTitle(title.toString()); break; case INVALIDATE_MAP: - mapView.repaintRequired(null); + map.mapView.repaintRequired(null); break; default: break; } } - }; - /** Updates the progress. */ - final private Handler showProgressHandler = new Handler() { + } + + final private Handler displayHandler = new DisplayHandler(this); + + private void setTitle(final String title) { + /* Compatibility for the old Action Bar, only used by the maps activity at the moment */ + final TextView titleview = (TextView) activity.findViewById(R.id.actionbar_title); + if (titleview != null) { + titleview.setText(title); + } + if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)) { + setTitleHoneyComb(title); + } + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + private void setTitleHoneyComb(final String title) { + activity.getActionBar().setTitle(title); + } + + /** Updates the progress. */ + private static final class ShowProgressHandler extends Handler { private int counter = 0; + @NonNull private final WeakReference<CGeoMap> mapRef; + + public ShowProgressHandler(@NonNull final CGeoMap map) { + this.mapRef = new WeakReference<CGeoMap>(map); + } @Override - public void handleMessage(Message msg) { + public void handleMessage(final Message msg) { final int what = msg.what; if (what == HIDE_PROGRESS) { if (--counter == 0) { - ActivityMixin.showProgress(activity, false); + showProgress(false); } } else if (what == SHOW_PROGRESS) { - ActivityMixin.showProgress(activity, true); + showProgress(true); counter++; } } - }; + + private void showProgress(final boolean show) { + final CGeoMap map = mapRef.get(); + if (map == null) { + return; + } + + final ProgressBar progress = (ProgressBar) map.activity.findViewById(R.id.actionbar_progress); + if (progress != null) { + if (show) { + progress.setVisibility(View.VISIBLE); + } else { + progress.setVisibility(View.GONE); + + } + } + if (Build.VERSION.SDK_INT >= 11) { + map.activity.setProgressBarIndeterminateVisibility(show); + } + } + } + + final private Handler showProgressHandler = new ShowProgressHandler(this); final private class LoadDetailsHandler extends CancellableHandler { @Override - public void handleRegularMessage(Message msg) { + public void handleRegularMessage(final Message msg) { if (msg.what == UPDATE_PROGRESS) { if (waitDialog != null) { - int secondsElapsed = (int) ((System.currentTimeMillis() - detailProgressTime) / 1000); + final int secondsElapsed = (int) ((System.currentTimeMillis() - detailProgressTime) / 1000); int secondsRemaining; if (detailProgress > 0) { secondsRemaining = (detailTotal - detailProgress) * secondsElapsed / detailProgress; @@ -289,7 +350,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto if (secondsRemaining < 40) { waitDialog.setMessage(res.getString(R.string.caches_downloading) + " " + res.getString(R.string.caches_eta_ltm)); } else { - int minsRemaining = secondsRemaining / 60; + final int minsRemaining = secondsRemaining / 60; waitDialog.setMessage(res.getString(R.string.caches_downloading) + " " + minsRemaining + " " + res.getQuantityString(R.plurals.caches_eta_mins, minsRemaining)); } } @@ -312,7 +373,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto final private Handler noMapTokenHandler = new Handler() { @Override - public void handleMessage(Message msg) { + public void handleMessage(final Message msg) { if (!noMapTokenShowed) { ActivityMixin.showToast(activity, res.getString(R.string.map_token_err)); @@ -328,7 +389,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto /* Current source id */ private int currentSourceId; - public CGeoMap(MapActivityImpl activity) { + public CGeoMap(final MapActivityImpl activity) { super(activity); geoDirUpdate = new UpdateLoc(this); } @@ -361,8 +422,9 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } } + @TargetApi(Build.VERSION_CODES.HONEYCOMB) @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); // class init @@ -370,7 +432,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto activity = this.getActivity(); app = (CgeoApplication) activity.getApplication(); - int countBubbleCnt = DataStore.getAllCachesCount(); + final int countBubbleCnt = DataStore.getAllCachesCount(); caches = new LeastRecentlyUsedSet<Geocache>(MAX_CACHES + countBubbleCnt); final MapProvider mapProvider = Settings.getMapProvider(); @@ -418,10 +480,16 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto ActivityMixin.keepScreenOn(activity, true); + // set layout - ActivityMixin.setTheme(activity); + //ActivityMixin.setTheme(activity); + // TODO: set a proper theme + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB) { + activity.setTheme(android.R.style.Theme_Holo); + activity.getActionBar().setDisplayHomeAsUpEnabled(true); + } activity.setContentView(mapProvider.getMapLayoutId()); - ActivityMixin.setTitle(activity, res.getString(R.string.map_map)); + setTitle(res.getString(R.string.map_map)); // initialize map mapView = (MapViewImpl) activity.findViewById(mapProvider.getMapViewId()); @@ -429,7 +497,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto mapView.setBuiltInZoomControls(true); mapView.displayZoomControls(true); mapView.preLoad(); - mapView.setOnDragListener(this); + mapView.setOnDragListener(new MapDragListener(this)); // initialize overlays mapView.clearOverlays(); @@ -439,7 +507,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } if (overlayPositionAndScale == null) { - overlayPositionAndScale = mapView.createAddPositionAndScaleOverlay(activity); + overlayPositionAndScale = mapView.createAddPositionAndScaleOverlay(); if (trailHistory != null) { overlayPositionAndScale.setHistory(trailHistory); } @@ -462,14 +530,11 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto centerMap(geocodeIntent, searchIntent, coordsIntent, mapStateIntent); } - // prepare my location button - myLocSwitch = (ImageSwitcher) activity.findViewById(R.id.my_position); - myLocSwitch.setFactory(this); - myLocSwitch.setInAnimation(activity, android.R.anim.fade_in); - myLocSwitch.setOutAnimation(activity, android.R.anim.fade_out); - myLocSwitch.setOnClickListener(new MyLocationListener()); - switchMyLocationButton(); + final CheckBox locSwitch = (CheckBox) activity.findViewById(R.id.my_position); + if (locSwitch!=null) { + initMyLocationSwitchButton(locSwitch); + } prepareFilterBar(); if (!app.isLiveMapHintShownInThisSession() && !Settings.getHideLiveMapHint() && Settings.getLiveMapHintShowCount() <= 3) { @@ -477,6 +542,16 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } } + private void initMyLocationSwitchButton(final CheckBox locSwitch) { + myLocSwitch = locSwitch; + /* TODO: Switch back to ImageSwitcher for animations? + myLocSwitch.setFactory(this); + myLocSwitch.setInAnimation(activity, android.R.anim.fade_in); + myLocSwitch.setOutAnimation(activity, android.R.anim.fade_out); */ + myLocSwitch.setOnClickListener(new MyLocationListener(this)); + switchMyLocationButton(); + } + /** * Set the zoom of the map. The zoom is restricted to a certain minimum in case of live map. * @@ -489,7 +564,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto private void prepareFilterBar() { // show the filter warning bar if the filter is set if (Settings.getCacheType() != CacheType.ALL) { - String cacheType = Settings.getCacheType().getL10n(); + final String cacheType = Settings.getCacheType().getL10n(); ((TextView) activity.findViewById(R.id.filter_text)).setText(cacheType); activity.findViewById(R.id.filter_bar).setVisibility(View.VISIBLE); } else { @@ -503,8 +578,8 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto resumeSubscription = Subscriptions.from(geoDirUpdate.start(GeoDirHandler.UPDATE_GEODIR), startTimer()); if (!CollectionUtils.isEmpty(dirtyCaches)) { - for (String geocode : dirtyCaches) { - Geocache cache = DataStore.loadCache(geocode, LoadFlags.LOAD_WAYPOINTS); + for (final String geocode : dirtyCaches) { + final Geocache cache = DataStore.loadCache(geocode, LoadFlags.LOAD_WAYPOINTS); if (cache != null) { // new collection type needs to remove first caches.remove(cache); @@ -514,7 +589,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } dirtyCaches.clear(); // Update display - displayExecutor.execute(new DisplayRunnable(mapView.getViewport())); + displayExecutor.execute(new DisplayRunnable(this)); } } @@ -532,8 +607,9 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto super.onPause(); } + @TargetApi(Build.VERSION_CODES.HONEYCOMB) @Override - public boolean onCreateOptionsMenu(Menu menu) { + public boolean onCreateOptionsMenu(final Menu menu) { // menu inflation happens in Google/Mapsforge specific classes super.onCreateOptionsMenu(menu); @@ -541,26 +617,35 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto final SubMenu subMenuStrategy = menu.findItem(R.id.submenu_strategy).getSubMenu(); subMenuStrategy.setHeaderTitle(res.getString(R.string.map_strategy_title)); + + + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB) { + /* if we have an Actionbar find the my position toggle */ + final MenuItem item = menu.findItem(R.id.menu_toggle_mypos); + myLocSwitch = new CheckBox(activity); + myLocSwitch.setButtonDrawable(R.drawable.ic_menu_myposition); + item.setActionView(myLocSwitch); + initMyLocationSwitchButton(myLocSwitch); + } else { + // Already on the fake Actionbar + menu.removeItem(R.id.menu_toggle_mypos); + } return true; } @Override - public boolean onPrepareOptionsMenu(Menu menu) { + public boolean onPrepareOptionsMenu(final Menu menu) { super.onPrepareOptionsMenu(menu); - for (MapSource mapSource : MapProviderFactory.getMapSources()) { + for (final MapSource mapSource : MapProviderFactory.getMapSources()) { final MenuItem menuItem = menu.findItem(mapSource.getNumericalId()); if (menuItem != null) { - menuItem.setEnabled(mapSource.isAvailable()); + menuItem.setVisible(mapSource.isAvailable()); } } try { MenuItem item = menu.findItem(R.id.menu_trail_mode); - if (Settings.isMapTrail()) { - item.setTitle(res.getString(R.string.map_trail_hide)); - } else { - item.setTitle(res.getString(R.string.map_trail_show)); - } + item.setChecked(Settings.isMapTrail()); item = menu.findItem(R.id.menu_map_live); // live map if (isLiveEnabled) { @@ -570,28 +655,20 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } item = menu.findItem(R.id.menu_mycaches_mode); // own & found caches - if (Settings.isExcludeMyCaches()) { - item.setTitle(res.getString(R.string.map_mycaches_show)); - } else { - item.setTitle(res.getString(R.string.map_mycaches_hide)); - } + item.setChecked(Settings.isExcludeMyCaches()); final Set<String> geocodesInViewport = getGeocodesForCachesInViewport(); - menu.findItem(R.id.menu_store_caches).setEnabled(!isLoading() && CollectionUtils.isNotEmpty(geocodesInViewport) && new SearchResult(geocodesInViewport).hasUnsavedCaches()); + menu.findItem(R.id.menu_store_caches).setVisible(!isLoading() && CollectionUtils.isNotEmpty(geocodesInViewport) && new SearchResult(geocodesInViewport).hasUnsavedCaches()); item = menu.findItem(R.id.menu_circle_mode); // show circles - if (overlayCaches != null && overlayCaches.getCircles()) { - item.setTitle(res.getString(R.string.map_circles_hide)); - } else { - item.setTitle(res.getString(R.string.map_circles_show)); - } + item.setChecked(overlayCaches != null && overlayCaches.getCircles()); item = menu.findItem(R.id.menu_theme_mode); // show theme selection item.setVisible(mapView.hasMapThemes()); - menu.findItem(R.id.menu_as_list).setEnabled(!isLoading()); + menu.findItem(R.id.menu_as_list).setVisible(!isLoading()); - menu.findItem(R.id.submenu_strategy).setEnabled(isLiveEnabled); + menu.findItem(R.id.submenu_strategy).setVisible(isLiveEnabled); switch (Settings.getLiveMapStrategy()) { case FASTEST: @@ -606,7 +683,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto default: // DETAILED menu.findItem(R.id.menu_strategy_detailed).setChecked(true); } - } catch (RuntimeException e) { + } catch (final RuntimeException e) { Log.e("CGeoMap.onPrepareOptionsMenu", e); } @@ -614,9 +691,12 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } @Override - public boolean onOptionsItemSelected(MenuItem item) { + public boolean onOptionsItemSelected(final MenuItem item) { final int id = item.getItemId(); switch (id) { + case android.R.id.home: + ActivityMixin.navigateUp(activity); + return true; case R.id.menu_trail_mode: Settings.setMapTrail(!Settings.isMapTrail()); mapView.repaintRequired(overlayPositionAndScale); @@ -726,16 +806,16 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto final File[] themeFiles = Settings.getMapThemeFiles(); String currentTheme = StringUtils.EMPTY; - String currentThemePath = Settings.getCustomRenderThemeFilePath(); + final String currentThemePath = Settings.getCustomRenderThemeFilePath(); if (StringUtils.isNotEmpty(currentThemePath)) { - File currentThemeFile = new File(currentThemePath); + final File currentThemeFile = new File(currentThemePath); currentTheme = currentThemeFile.getName(); } - List<String> names = new ArrayList<String>(); + final List<String> names = new ArrayList<String>(); names.add(res.getString(R.string.map_theme_builtin)); int currentItem = 0; - for (File file : themeFiles) { + for (final File file : themeFiles) { if (currentTheme.equalsIgnoreCase(file.getName())) { currentItem = names.size(); } @@ -744,7 +824,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto final int selectedItem = currentItem; - AlertDialog.Builder builder = new AlertDialog.Builder(activity); + final AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setTitle(R.string.map_theme_select); @@ -752,7 +832,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto new DialogInterface.OnClickListener() { @Override - public void onClick(DialogInterface dialog, int newItem) { + public void onClick(final DialogInterface dialog, final int newItem) { if (newItem != selectedItem) { // Adjust index because of <default> selection if (newItem > 0) { @@ -818,7 +898,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto activity.finish(); // prepare information to restart a similar view - Intent mapIntent = new Intent(activity, Settings.getMapProvider().getMapClass()); + final Intent mapIntent = new Intent(activity, Settings.getMapProvider().getMapClass()); mapIntent.putExtra(EXTRAS_SEARCH, searchIntent); mapIntent.putExtra(EXTRAS_GEOCODE, geocodeIntent); @@ -896,7 +976,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto */ private final WeakReference<CGeoMap> map; - public UpdateLoc(CGeoMap map) { + public UpdateLoc(final CGeoMap map) { this.map = new WeakReference<CGeoMap>(map); } @@ -922,15 +1002,15 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto timeLastPositionOverlayCalculation = currentTimeMillis; try { - CGeoMap cgeoMapRef = map.get(); + final CGeoMap cgeoMapRef = map.get(); if (cgeoMapRef != null) { if (cgeoMapRef.mapView != null) { if (cgeoMapRef.overlayPositionAndScale == null) { - cgeoMapRef.overlayPositionAndScale = cgeoMapRef.mapView.createAddPositionAndScaleOverlay(cgeoMapRef.activity); + cgeoMapRef.overlayPositionAndScale = cgeoMapRef.mapView.createAddPositionAndScaleOverlay(); } - boolean needsRepaintForDistance = needsRepaintForDistance(); - boolean needsRepaintForHeading = needsRepaintForHeading(); + final boolean needsRepaintForDistance = needsRepaintForDistance(); + final boolean needsRepaintForHeading = needsRepaintForHeading(); if (needsRepaintForDistance) { if (cgeoMapRef.followMyLocation) { @@ -945,7 +1025,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } } } - } catch (RuntimeException e) { + } catch (final RuntimeException e) { Log.w("Failed to update location."); } } @@ -1003,50 +1083,63 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto return loadTimer; } - /** - * loading timer Triggers every 250ms and checks for viewport change and starts a {@link LoadRunnable}. - */ - private Subscription startLoadTimer() { - return Schedulers.newThread().schedulePeriodically(new Action1<Scheduler.Inner>() { - @Override - public void call(Scheduler.Inner inner) { - try { - if (mapView != null) { - // get current viewport - final Viewport viewportNow = mapView.getViewport(); - // Since zoomNow is used only for local comparison purposes, - // it is ok to use the Google Maps compatible zoom level of OSM Maps - final int zoomNow = mapView.getMapZoomLevel(); - - // check if map moved or zoomed - //TODO Portree Use Rectangle inside with bigger search window. That will stop reloading on every move - final boolean moved = markersInvalidated || (isLiveEnabled && !downloaded) || (viewport == null) || zoomNow != zoom || - (mapMoved(viewport, viewportNow) && (cachesCnt <= 0 || CollectionUtils.isEmpty(caches) || !viewport.includes(viewportNow))); - - // update title on any change - if (moved || !viewportNow.equals(viewport)) { - displayHandler.sendEmptyMessage(UPDATE_TITLE); - } - zoom = zoomNow; + private static final class LoadTimerAction implements Action0 { - // save new values - if (moved) { - markersInvalidated = false; + @NonNull private final WeakReference<CGeoMap> mapRef; - long currentTime = System.currentTimeMillis(); + public LoadTimerAction(@NonNull final CGeoMap map) { + this.mapRef = new WeakReference<CGeoMap>(map); + } - if (1000 < (currentTime - loadThreadRun)) { - viewport = viewportNow; - loadExecutor.execute(new LoadRunnable(viewport)); - } - } + @Override + public void call() { + final CGeoMap map = mapRef.get(); + if (map == null) { + return; + } + try { + if (map.mapView != null) { + // get current viewport + final Viewport viewportNow = map.mapView.getViewport(); + // Since zoomNow is used only for local comparison purposes, + // it is ok to use the Google Maps compatible zoom level of OSM Maps + final int zoomNow = map.mapView.getMapZoomLevel(); + + // check if map moved or zoomed + //TODO Portree Use Rectangle inside with bigger search window. That will stop reloading on every move + final boolean moved = map.markersInvalidated || (map.isLiveEnabled && !map.downloaded) || (map.viewport == null) || zoomNow != map.zoom || + (mapMoved(map.viewport, viewportNow) && (map.cachesCnt <= 0 || CollectionUtils.isEmpty(map.caches) || !map.viewport.includes(viewportNow))); + + // update title on any change + if (moved || !viewportNow.equals(map.viewport)) { + map.displayHandler.sendEmptyMessage(UPDATE_TITLE); } + map.zoom = zoomNow; + + // save new values + if (moved) { + map.markersInvalidated = false; + + final long currentTime = System.currentTimeMillis(); - } catch (Exception e) { - Log.w("CGeoMap.startLoadtimer.start", e); + if (1000 < (currentTime - map.loadThreadRun)) { + map.viewport = viewportNow; + loadExecutor.execute(new LoadRunnable(map)); + } + } } + + } catch (final Exception e) { + Log.w("CGeoMap.startLoadtimer.start", e); } - }, 250, 250, TimeUnit.MILLISECONDS); + } + } + + /** + * loading timer Triggers every 250ms and checks for viewport change and starts a {@link LoadRunnable}. + */ + private Subscription startLoadTimer() { + return Schedulers.newThread().createWorker().schedulePeriodically(new LoadTimerAction(this), 250, 250, TimeUnit.MILLISECONDS); } /** @@ -1066,79 +1159,87 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto * started by {@link LoadTimer} */ - private class LoadRunnable extends DoRunnable { + private static class LoadRunnable extends DoRunnable { - public LoadRunnable(final Viewport viewport) { - super(viewport); + public LoadRunnable(@NonNull final CGeoMap map) { + super(map); } @Override public void run() { - try { - showProgressHandler.sendEmptyMessage(SHOW_PROGRESS); - loadThreadRun = System.currentTimeMillis(); - - SearchResult searchResult; - if (mapMode == MapMode.LIVE) { - searchResult = isLiveEnabled ? new SearchResult() : new SearchResult(DataStore.loadStoredInViewport(viewport, Settings.getCacheType())); - } else { - // map started from another activity - searchResult = searchIntent != null ? new SearchResult(searchIntent) : new SearchResult(); - if (geocodeIntent != null) { - searchResult.addGeocode(geocodeIntent); - } - } - // live mode search result - if (isLiveEnabled) { - searchResult.addSearchResult(DataStore.loadCachedInViewport(viewport, Settings.getCacheType())); - } + final CGeoMap map = getMap(); + if (map != null) { + map.doLoadRun(); + } + } + } - downloaded = true; - Set<Geocache> cachesFromSearchResult = searchResult.getCachesFromSearchResult(LoadFlags.LOAD_WAYPOINTS); - // update the caches - // new collection type needs to remove first - caches.removeAll(cachesFromSearchResult); - caches.addAll(cachesFromSearchResult); + private void doLoadRun() { + try { + showProgressHandler.sendEmptyMessage(SHOW_PROGRESS); + loadThreadRun = System.currentTimeMillis(); - final boolean excludeMine = Settings.isExcludeMyCaches(); - final boolean excludeDisabled = Settings.isExcludeDisabledCaches(); - if (mapMode == MapMode.LIVE) { - CGeoMap.filter(caches); + SearchResult searchResult; + if (mapMode == MapMode.LIVE) { + searchResult = isLiveEnabled ? new SearchResult() : new SearchResult(DataStore.loadStoredInViewport(viewport, Settings.getCacheType())); + } else { + // map started from another activity + searchResult = searchIntent != null ? new SearchResult(searchIntent) : new SearchResult(); + if (geocodeIntent != null) { + searchResult.addGeocode(geocodeIntent); } - countVisibleCaches(); - if (cachesCnt < Settings.getWayPointsThreshold() || geocodeIntent != null) { - // we don't want to see any stale waypoints - waypoints.clear(); - if (isLiveEnabled || mapMode == MapMode.LIVE - || mapMode == MapMode.COORDS) { - //All visible waypoints - CacheType type = Settings.getCacheType(); - Set<Waypoint> waypointsInViewport = DataStore.loadWaypoints(viewport, excludeMine, excludeDisabled, type); - waypoints.addAll(waypointsInViewport); - } - else { - //All waypoints from the viewed caches - for (Geocache c : caches.getAsList()) { - waypoints.addAll(c.getWaypoints()); - } - } + } + // live mode search result + if (isLiveEnabled) { + searchResult.addSearchResult(DataStore.loadCachedInViewport(viewport, Settings.getCacheType())); + } + + downloaded = true; + final Set<Geocache> cachesFromSearchResult = searchResult.getCachesFromSearchResult(LoadFlags.LOAD_WAYPOINTS); + // update the caches + // new collection type needs to remove first + caches.removeAll(cachesFromSearchResult); + caches.addAll(cachesFromSearchResult); + + final boolean excludeMine = Settings.isExcludeMyCaches(); + final boolean excludeDisabled = Settings.isExcludeDisabledCaches(); + if (mapMode == MapMode.LIVE) { + CGeoMap.filter(caches); + } + countVisibleCaches(); + if (cachesCnt < Settings.getWayPointsThreshold() || geocodeIntent != null) { + // we don't want to see any stale waypoints + waypoints.clear(); + if (isLiveEnabled || mapMode == MapMode.LIVE + || mapMode == MapMode.COORDS) { + //All visible waypoints + final CacheType type = Settings.getCacheType(); + final Set<Waypoint> waypointsInViewport = DataStore.loadWaypoints(viewport, excludeMine, excludeDisabled, type); + waypoints.addAll(waypointsInViewport); } else { - // we don't want to see any stale waypoints when above threshold - waypoints.clear(); + //All waypoints from the viewed caches + for (final Geocache c : caches.getAsList()) { + waypoints.addAll(c.getWaypoints()); + } } + } + else { + // we don't want to see any stale waypoints when above threshold + waypoints.clear(); + } - //render - displayExecutor.execute(new DisplayRunnable(viewport)); + //render + displayExecutor.execute(new DisplayRunnable(this)); - if (isLiveEnabled) { - downloadExecutor.execute(new DownloadRunnable(viewport)); - } - lastSearchResult = searchResult; - } finally { - showProgressHandler.sendEmptyMessage(HIDE_PROGRESS); // hide progress + if (isLiveEnabled) { + downloadExecutor.execute(new DownloadRunnable(this)); } + lastSearchResult = searchResult; + } finally { + showProgressHandler.sendEmptyMessage(HIDE_PROGRESS); // hide progress } + } /** @@ -1146,111 +1247,125 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto * Started by {@link LoadRunnable}. */ - private class DownloadRunnable extends DoRunnable { + private static class DownloadRunnable extends DoRunnable { - public DownloadRunnable(final Viewport viewport) { - super(viewport); + public DownloadRunnable(final CGeoMap map) { + super(map); } @Override public void run() { - try { - showProgressHandler.sendEmptyMessage(SHOW_PROGRESS); // show progress - if (Settings.isGCConnectorActive()) { - if (tokens == null) { - tokens = GCLogin.getInstance().getMapTokens(); - if (noMapTokenHandler != null && (StringUtils.isEmpty(tokens.getUserSession()) || StringUtils.isEmpty(tokens.getSessionToken()))) { - tokens = null; - noMapTokenHandler.sendEmptyMessage(0); - } + final CGeoMap map = getMap(); + if (map != null) { + map.doDownloadRun(); + } + } + } + + private void doDownloadRun() { + try { + showProgressHandler.sendEmptyMessage(SHOW_PROGRESS); // show progress + if (Settings.isGCConnectorActive()) { + if (tokens == null) { + tokens = GCLogin.getInstance().getMapTokens(); + if (noMapTokenHandler != null && (StringUtils.isEmpty(tokens.getUserSession()) || StringUtils.isEmpty(tokens.getSessionToken()))) { + tokens = null; + noMapTokenHandler.sendEmptyMessage(0); } } - final SearchResult searchResult = ConnectorFactory.searchByViewport(viewport.resize(0.8), tokens); - downloaded = true; - - Set<Geocache> result = searchResult.getCachesFromSearchResult(LoadFlags.LOAD_CACHE_OR_DB); - CGeoMap.filter(result); - // update the caches - // first remove filtered out - final Set<String> filteredCodes = searchResult.getFilteredGeocodes(); - Log.d("Filtering out " + filteredCodes.size() + " caches: " + filteredCodes.toString()); - caches.removeAll(DataStore.loadCaches(filteredCodes, LoadFlags.LOAD_CACHE_ONLY)); - DataStore.removeCaches(filteredCodes, EnumSet.of(RemoveFlag.REMOVE_CACHE)); - // new collection type needs to remove first to refresh - caches.removeAll(result); - caches.addAll(result); - lastSearchResult = searchResult; - - //render - displayExecutor.execute(new DisplayRunnable(viewport)); - - } catch (ThreadDeath e) { - Log.d("DownloadThread stopped"); - displayHandler.sendEmptyMessage(UPDATE_TITLE); - } finally { - showProgressHandler.sendEmptyMessage(HIDE_PROGRESS); // hide progress } + final SearchResult searchResult = ConnectorFactory.searchByViewport(viewport.resize(0.8), tokens); + downloaded = true; + + final Set<Geocache> result = searchResult.getCachesFromSearchResult(LoadFlags.LOAD_CACHE_OR_DB); + CGeoMap.filter(result); + // update the caches + // first remove filtered out + final Set<String> filteredCodes = searchResult.getFilteredGeocodes(); + Log.d("Filtering out " + filteredCodes.size() + " caches: " + filteredCodes.toString()); + caches.removeAll(DataStore.loadCaches(filteredCodes, LoadFlags.LOAD_CACHE_ONLY)); + DataStore.removeCaches(filteredCodes, EnumSet.of(RemoveFlag.REMOVE_CACHE)); + // new collection type needs to remove first to refresh + caches.removeAll(result); + caches.addAll(result); + lastSearchResult = searchResult; + + //render + displayExecutor.execute(new DisplayRunnable(this)); + + } catch (final ThreadDeath e) { + Log.d("DownloadThread stopped"); + displayHandler.sendEmptyMessage(UPDATE_TITLE); + } finally { + showProgressHandler.sendEmptyMessage(HIDE_PROGRESS); // hide progress } } /** * Thread to Display (down)loaded caches. Started by {@link LoadRunnable} and {@link DownloadRunnable} */ - private class DisplayRunnable extends DoRunnable { + private static class DisplayRunnable extends DoRunnable { - public DisplayRunnable(final Viewport viewport) { - super(viewport); + public DisplayRunnable(@NonNull final CGeoMap map) { + super(map); } @Override public void run() { - try { - showProgressHandler.sendEmptyMessage(SHOW_PROGRESS); - if (mapView == null || caches == null) { - throw new ThreadDeath(); - } - - // display caches - final List<Geocache> cachesToDisplay = caches.getAsList(); - final List<Waypoint> waypointsToDisplay = new ArrayList<Waypoint>(waypoints); - final List<CachesOverlayItemImpl> itemsToDisplay = new ArrayList<CachesOverlayItemImpl>(); + final CGeoMap map = getMap(); + if (map != null) { + map.doDisplayRun(); + } + } + } - if (!cachesToDisplay.isEmpty()) { - // Only show waypoints for single view or setting - // when less than showWaypointsthreshold Caches shown - if (mapMode == MapMode.SINGLE || (cachesCnt < Settings.getWayPointsThreshold())) { - for (Waypoint waypoint : waypointsToDisplay) { + private void doDisplayRun() { + try { + showProgressHandler.sendEmptyMessage(SHOW_PROGRESS); + if (mapView == null || caches == null) { + throw new ThreadDeath(); + } - if (waypoint == null || waypoint.getCoords() == null) { - continue; - } + // display caches + final List<Geocache> cachesToDisplay = caches.getAsList(); + final List<Waypoint> waypointsToDisplay = new ArrayList<Waypoint>(waypoints); + final List<CachesOverlayItemImpl> itemsToDisplay = new ArrayList<CachesOverlayItemImpl>(); - itemsToDisplay.add(getWaypointItem(waypoint)); - } - } - for (Geocache cache : cachesToDisplay) { + if (!cachesToDisplay.isEmpty()) { + // Only show waypoints for single view or setting + // when less than showWaypointsthreshold Caches shown + if (mapMode == MapMode.SINGLE || (cachesCnt < Settings.getWayPointsThreshold())) { + for (final Waypoint waypoint : waypointsToDisplay) { - if (cache == null || cache.getCoords() == null) { + if (waypoint == null || waypoint.getCoords() == null) { continue; } - itemsToDisplay.add(getCacheItem(cache)); - } - overlayCaches.updateItems(itemsToDisplay); - displayHandler.sendEmptyMessage(INVALIDATE_MAP); + itemsToDisplay.add(getWaypointItem(waypoint)); + } + } + for (final Geocache cache : cachesToDisplay) { - } else { - overlayCaches.updateItems(itemsToDisplay); - displayHandler.sendEmptyMessage(INVALIDATE_MAP); + if (cache == null || cache.getCoords() == null) { + continue; + } + itemsToDisplay.add(getCacheItem(cache)); } - displayHandler.sendEmptyMessage(UPDATE_TITLE); - } catch (ThreadDeath e) { - Log.d("DisplayThread stopped"); - displayHandler.sendEmptyMessage(UPDATE_TITLE); - } finally { - showProgressHandler.sendEmptyMessage(HIDE_PROGRESS); + overlayCaches.updateItems(itemsToDisplay); + displayHandler.sendEmptyMessage(INVALIDATE_MAP); + + } else { + overlayCaches.updateItems(itemsToDisplay); + displayHandler.sendEmptyMessage(INVALIDATE_MAP); } + + displayHandler.sendEmptyMessage(UPDATE_TITLE); + } catch (final ThreadDeath e) { + Log.d("DisplayThread stopped"); + displayHandler.sendEmptyMessage(UPDATE_TITLE); + } finally { + showProgressHandler.sendEmptyMessage(HIDE_PROGRESS); } } @@ -1268,10 +1383,15 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto private static abstract class DoRunnable implements Runnable { - final protected Viewport viewport; + private final WeakReference<CGeoMap> mapRef; + + protected DoRunnable(@NonNull final CGeoMap map) { + mapRef = new WeakReference<CGeoMap>(map); + } - protected DoRunnable(final Viewport viewport) { - this.viewport = viewport; + protected @Nullable + final CGeoMap getMap() { + return mapRef.get(); } } @@ -1281,7 +1401,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto * @param listId * the list to store the caches in */ - private void storeCaches(List<String> geocodes, int listId) { + private void storeCaches(final List<String> geocodes, final int listId) { final LoadDetailsHandler loadDetailsHandler = new LoadDetailsHandler(); waitDialog = new ProgressDialog(activity); @@ -1292,19 +1412,19 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto waitDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { @Override - public void onCancel(DialogInterface arg0) { + public void onCancel(final DialogInterface arg0) { try { if (loadDetailsThread != null) { loadDetailsThread.stopIt(); } - } catch (Exception e) { + } catch (final Exception e) { Log.e("CGeoMap.storeCaches.onCancel", e); } } }); - float etaTime = detailTotal * 7.0f / 60.0f; - int roundedEta = Math.round(etaTime); + final float etaTime = detailTotal * 7.0f / 60.0f; + final int roundedEta = Math.round(etaTime); if (etaTime < 0.4) { waitDialog.setMessage(res.getString(R.string.caches_downloading) + " " + res.getString(R.string.caches_eta_ltm)); } else { @@ -1353,7 +1473,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto if (!DataStore.isOffline(geocode, null)) { Geocache.storeCache(null, geocode, listId, false, handler); } - } catch (Exception e) { + } catch (final Exception e) { Log.e("CGeoMap.LoadDetails.run", e); } finally { // one more cache over @@ -1367,13 +1487,13 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } } - private static synchronized void filter(Collection<Geocache> caches) { - boolean excludeMine = Settings.isExcludeMyCaches(); - boolean excludeDisabled = Settings.isExcludeDisabledCaches(); + private static synchronized void filter(final Collection<Geocache> caches) { + final boolean excludeMine = Settings.isExcludeMyCaches(); + final boolean excludeDisabled = Settings.isExcludeDisabledCaches(); - List<Geocache> removeList = new ArrayList<Geocache>(); - for (Geocache cache : caches) { - if ((excludeMine && cache.isFound()) || (excludeMine && cache.isOwner()) || (excludeDisabled && cache.isDisabled())) { + final List<Geocache> removeList = new ArrayList<Geocache>(); + for (final Geocache cache : caches) { + if ((excludeMine && cache.isFound()) || (excludeMine && cache.isOwner()) || (excludeDisabled && cache.isDisabled()) || (excludeDisabled && cache.isArchived())) { removeList.add(cache); } } @@ -1410,14 +1530,14 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } // move map to view results of searchIntent - private void centerMap(String geocodeCenter, final SearchResult searchCenter, final Geopoint coordsCenter, int[] mapState) { + private void centerMap(final String geocodeCenter, final SearchResult searchCenter, final Geopoint coordsCenter, final int[] mapState) { final MapControllerImpl mapController = mapView.getMapController(); if (!centered && mapState != null) { try { mapController.setCenter(mapItemFactory.getGeoPointBase(new Geopoint(mapState[0] / 1.0e6, mapState[1] / 1.0e6))); setZoom(mapState[2]); - } catch (RuntimeException e) { + } catch (final RuntimeException e) { Log.e("centermap", e); } @@ -1441,7 +1561,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto if (viewport.getLatitudeSpan() != 0 && viewport.getLongitudeSpan() != 0) { mapController.zoomToSpan((int) (viewport.getLatitudeSpan() * 1e6), (int) (viewport.getLongitudeSpan() * 1e6)); } - } catch (RuntimeException e) { + } catch (final RuntimeException e) { Log.e("centermap", e); } @@ -1450,7 +1570,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } else if (!centered && coordsCenter != null) { try { mapController.setCenter(makeGeoPoint(coordsCenter)); - } catch (Exception e) { + } catch (final Exception e) { Log.e("centermap", e); } @@ -1461,25 +1581,54 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto // switch My Location button image private void switchMyLocationButton() { + myLocSwitch.setChecked(followMyLocation); if (followMyLocation) { - myLocSwitch.setImageResource(R.drawable.actionbar_mylocation_on); myLocationInMiddle(app.currentGeo()); - } else { - myLocSwitch.setImageResource(R.drawable.actionbar_mylocation_off); } } // set my location listener - private class MyLocationListener implements View.OnClickListener { + private static class MyLocationListener implements View.OnClickListener { + + private final WeakReference<CGeoMap> mapRef; + + public MyLocationListener(@NonNull final CGeoMap map) { + mapRef = new WeakReference<CGeoMap>(map); + } + @Override - public void onClick(View view) { - followMyLocation = !followMyLocation; - switchMyLocationButton(); + public void onClick(final View view) { + final CGeoMap map = mapRef.get(); + if (map != null) { + map.onFollowMyLocationClicked(); + } } } - @Override - public void onDrag() { + private void onFollowMyLocationClicked() { + followMyLocation = !followMyLocation; + switchMyLocationButton(); + } + + public static class MapDragListener implements OnMapDragListener { + + private final WeakReference<CGeoMap> mapRef; + + public MapDragListener(@NonNull final CGeoMap map) { + mapRef = new WeakReference<CGeoMap>(map); + } + + @Override + public void onDrag() { + final CGeoMap map = mapRef.get(); + if (map != null) { + map.onDrag(); + } + } + + } + + private void onDrag() { if (followMyLocation) { followMyLocation = false; switchMyLocationButton(); @@ -1491,15 +1640,9 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto return mapItemFactory.getGeoPointBase(coords); } - // close activity and open homescreen - @Override - public void goHome(View view) { - ActivityMixin.goHome(activity); - } - @Override public View makeView() { - ImageView imageView = new ImageView(activity); + final ImageView imageView = new ImageView(activity); imageView.setScaleType(ScaleType.CENTER); imageView.setLayoutParams(new ImageSwitcher.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); return imageView; @@ -1637,7 +1780,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto private CachesOverlayItemImpl getWaypointItem(final Waypoint waypoint) { final CachesOverlayItemImpl item = mapItemFactory.getCachesOverlayItem(waypoint, waypoint.getWaypointType().applyDistanceRule()); - Drawable marker = getResources().getDrawable(!waypoint.isVisited() ? R.drawable.marker : R.drawable.marker_transparent); + final Drawable marker = getResources().getDrawable(!waypoint.isVisited() ? R.drawable.marker : R.drawable.marker_transparent); final Drawable[] layers = new Drawable[] { marker, getResources().getDrawable(waypoint.getWaypointType().markerId) diff --git a/main/src/cgeo/geocaching/maps/CachesOverlay.java b/main/src/cgeo/geocaching/maps/CachesOverlay.java index 0c7c296..8607c88 100644 --- a/main/src/cgeo/geocaching/maps/CachesOverlay.java +++ b/main/src/cgeo/geocaching/maps/CachesOverlay.java @@ -211,9 +211,9 @@ public class CachesOverlay extends AbstractItemizedOverlay { progress.show(context, context.getResources().getString(R.string.map_live), context.getResources().getString(R.string.cache_dialog_loading_details), true, null); - CachesOverlayItemImpl item = null; // prevent concurrent changes getOverlayImpl().lock(); + CachesOverlayItemImpl item = null; try { if (index < items.size()) { item = items.get(index); diff --git a/main/src/cgeo/geocaching/maps/PositionAndScaleOverlay.java b/main/src/cgeo/geocaching/maps/PositionAndScaleOverlay.java index 6b34b75..63fcd73 100644 --- a/main/src/cgeo/geocaching/maps/PositionAndScaleOverlay.java +++ b/main/src/cgeo/geocaching/maps/PositionAndScaleOverlay.java @@ -5,7 +5,6 @@ import cgeo.geocaching.maps.interfaces.MapProjectionImpl; import cgeo.geocaching.maps.interfaces.MapViewImpl; import cgeo.geocaching.maps.interfaces.OverlayImpl; -import android.app.Activity; import android.graphics.Canvas; import android.graphics.Point; import android.location.Location; @@ -18,10 +17,10 @@ public class PositionAndScaleOverlay implements GeneralOverlay { PositionDrawer positionDrawer = null; ScaleDrawer scaleDrawer = null; - public PositionAndScaleOverlay(Activity activity, OverlayImpl ovlImpl) { + public PositionAndScaleOverlay(OverlayImpl ovlImpl) { this.ovlImpl = ovlImpl; - positionDrawer = new PositionDrawer(activity); - scaleDrawer = new ScaleDrawer(activity); + positionDrawer = new PositionDrawer(); + scaleDrawer = new ScaleDrawer(); } public void setCoordinates(Location coordinatesIn) { diff --git a/main/src/cgeo/geocaching/maps/PositionDrawer.java b/main/src/cgeo/geocaching/maps/PositionDrawer.java index 1a5dcaf..0e20e7c 100644 --- a/main/src/cgeo/geocaching/maps/PositionDrawer.java +++ b/main/src/cgeo/geocaching/maps/PositionDrawer.java @@ -1,5 +1,6 @@ package cgeo.geocaching.maps; +import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.R; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.maps.interfaces.GeoPointImpl; @@ -7,7 +8,6 @@ import cgeo.geocaching.maps.interfaces.MapItemFactory; import cgeo.geocaching.maps.interfaces.MapProjectionImpl; import cgeo.geocaching.settings.Settings; -import android.app.Activity; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; @@ -36,11 +36,9 @@ public class PositionDrawer { private PaintFlagsDrawFilter setfil = null; private PaintFlagsDrawFilter remfil = null; private PositionHistory positionHistory = new PositionHistory(); - private Activity activity; private MapItemFactory mapItemFactory; - public PositionDrawer(Activity activity) { - this.activity = activity; + public PositionDrawer() { this.mapItemFactory = Settings.getMapProvider().getMapItemFactory(); } @@ -144,7 +142,7 @@ public class PositionDrawer { } if (arrow == null) { - arrow = BitmapFactory.decodeResource(activity.getResources(), R.drawable.my_location_chevron); + arrow = BitmapFactory.decodeResource(CgeoApplication.getInstance().getResources(), R.drawable.my_location_chevron); widthArrowHalf = arrow.getWidth() / 2; heightArrowHalf = arrow.getHeight() / 2; } diff --git a/main/src/cgeo/geocaching/maps/ScaleDrawer.java b/main/src/cgeo/geocaching/maps/ScaleDrawer.java index fb46408..95c987d 100644 --- a/main/src/cgeo/geocaching/maps/ScaleDrawer.java +++ b/main/src/cgeo/geocaching/maps/ScaleDrawer.java @@ -1,5 +1,6 @@ package cgeo.geocaching.maps; +import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.Units; import cgeo.geocaching.maps.interfaces.GeoPointImpl; @@ -7,12 +8,13 @@ import cgeo.geocaching.maps.interfaces.MapViewImpl; import org.apache.commons.lang3.tuple.ImmutablePair; -import android.app.Activity; +import android.content.Context; import android.graphics.BlurMaskFilter; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Typeface; import android.util.DisplayMetrics; +import android.view.WindowManager; public class ScaleDrawer { private static final double SCALE_WIDTH_FACTOR = 1.0 / 2.5; @@ -22,9 +24,10 @@ public class ScaleDrawer { private BlurMaskFilter blur = null; private float pixelDensity = 0; - public ScaleDrawer(Activity activity) { + public ScaleDrawer() { DisplayMetrics metrics = new DisplayMetrics(); - activity.getWindowManager().getDefaultDisplay().getMetrics(metrics); + WindowManager windowManager = (WindowManager) CgeoApplication.getInstance().getSystemService(Context.WINDOW_SERVICE); + windowManager.getDefaultDisplay().getMetrics(metrics); pixelDensity = metrics.density; } diff --git a/main/src/cgeo/geocaching/maps/google/GoogleMapActivity.java b/main/src/cgeo/geocaching/maps/google/GoogleMapActivity.java index a98241f..2a29cc9 100644 --- a/main/src/cgeo/geocaching/maps/google/GoogleMapActivity.java +++ b/main/src/cgeo/geocaching/maps/google/GoogleMapActivity.java @@ -1,5 +1,6 @@ package cgeo.geocaching.maps.google; +import cgeo.geocaching.activity.ActivityMixin; import cgeo.geocaching.activity.FilteredActivity; import cgeo.geocaching.maps.AbstractMap; import cgeo.geocaching.maps.CGeoMap; @@ -97,6 +98,11 @@ public class GoogleMapActivity extends MapActivity implements MapActivityImpl, F } @Override + public void navigateUp(View view) { + ActivityMixin.navigateUp(this); + } + + @Override public void superOnResume() { super.onResume(); } @@ -116,12 +122,6 @@ public class GoogleMapActivity extends MapActivity implements MapActivityImpl, F return super.onPrepareOptionsMenu(menu); } - // close activity and open homescreen - @Override - public void goHome(View view) { - mapBase.goHome(view); - } - @Override public void showFilterMenu(View view) { // do nothing, the filter bar only shows the global filter diff --git a/main/src/cgeo/geocaching/maps/google/GoogleMapView.java b/main/src/cgeo/geocaching/maps/google/GoogleMapView.java index 610dbe1..ea815ab 100644 --- a/main/src/cgeo/geocaching/maps/google/GoogleMapView.java +++ b/main/src/cgeo/geocaching/maps/google/GoogleMapView.java @@ -20,7 +20,6 @@ import com.google.android.maps.MapView; import org.apache.commons.lang3.reflect.MethodUtils; import org.eclipse.jdt.annotation.NonNull; -import android.app.Activity; import android.content.Context; import android.graphics.Canvas; import android.graphics.drawable.Drawable; @@ -39,16 +38,23 @@ public class GoogleMapView extends MapView implements MapViewImpl { public GoogleMapView(Context context, AttributeSet attrs) { super(context, attrs); - gestureDetector = new GestureDetector(context, new GestureListener()); + initialize(context); } public GoogleMapView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - gestureDetector = new GestureDetector(context, new GestureListener()); + initialize(context); } public GoogleMapView(Context context, String apiKey) { super(context, apiKey); + initialize(context); + } + + private void initialize(Context context) { + if (isInEditMode()) { + return; + } gestureDetector = new GestureDetector(context, new GestureListener()); } @@ -120,9 +126,9 @@ public class GoogleMapView extends MapView implements MapViewImpl { } @Override - public PositionAndScaleOverlay createAddPositionAndScaleOverlay(Activity activity) { + public PositionAndScaleOverlay createAddPositionAndScaleOverlay() { - GoogleOverlay ovl = new GoogleOverlay(activity); + GoogleOverlay ovl = new GoogleOverlay(); getOverlays().add(ovl); return (PositionAndScaleOverlay) ovl.getBase(); } diff --git a/main/src/cgeo/geocaching/maps/google/GoogleOverlay.java b/main/src/cgeo/geocaching/maps/google/GoogleOverlay.java index 0a5cf69..c684b9a 100644 --- a/main/src/cgeo/geocaching/maps/google/GoogleOverlay.java +++ b/main/src/cgeo/geocaching/maps/google/GoogleOverlay.java @@ -8,7 +8,6 @@ import cgeo.geocaching.maps.interfaces.OverlayImpl; import com.google.android.maps.MapView; import com.google.android.maps.Overlay; -import android.app.Activity; import android.graphics.Canvas; import java.util.concurrent.locks.Lock; @@ -19,8 +18,8 @@ public class GoogleOverlay extends Overlay implements OverlayImpl { private PositionAndScaleOverlay overlayBase = null; private Lock lock = new ReentrantLock(); - public GoogleOverlay(Activity activityIn) { - overlayBase = new PositionAndScaleOverlay(activityIn, this); + public GoogleOverlay() { + overlayBase = new PositionAndScaleOverlay(this); } @Override diff --git a/main/src/cgeo/geocaching/maps/interfaces/MapActivityImpl.java b/main/src/cgeo/geocaching/maps/interfaces/MapActivityImpl.java index e7deebd..3596d5f 100644 --- a/main/src/cgeo/geocaching/maps/interfaces/MapActivityImpl.java +++ b/main/src/cgeo/geocaching/maps/interfaces/MapActivityImpl.java @@ -33,6 +33,5 @@ public interface MapActivityImpl { boolean superOnOptionsItemSelected(MenuItem item); - public abstract void goHome(View view); - + public abstract void navigateUp(View view); } diff --git a/main/src/cgeo/geocaching/maps/interfaces/MapViewImpl.java b/main/src/cgeo/geocaching/maps/interfaces/MapViewImpl.java index 5ae8e15..4a6d733 100644 --- a/main/src/cgeo/geocaching/maps/interfaces/MapViewImpl.java +++ b/main/src/cgeo/geocaching/maps/interfaces/MapViewImpl.java @@ -6,7 +6,6 @@ import cgeo.geocaching.maps.PositionAndScaleOverlay; import org.eclipse.jdt.annotation.NonNull; -import android.app.Activity; import android.content.Context; import android.graphics.drawable.Drawable; @@ -47,7 +46,7 @@ public interface MapViewImpl { CachesOverlay createAddMapOverlay(Context context, Drawable drawable); - PositionAndScaleOverlay createAddPositionAndScaleOverlay(Activity activity); + PositionAndScaleOverlay createAddPositionAndScaleOverlay(); void setMapSource(); diff --git a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapActivity.java b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapActivity.java index a0384b8..94213ba 100644 --- a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapActivity.java +++ b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapActivity.java @@ -1,5 +1,6 @@ package cgeo.geocaching.maps.mapsforge; +import cgeo.geocaching.activity.ActivityMixin; import cgeo.geocaching.activity.FilteredActivity; import cgeo.geocaching.maps.AbstractMap; import cgeo.geocaching.maps.CGeoMap; @@ -111,10 +112,9 @@ public class MapsforgeMapActivity extends MapActivity implements MapActivityImpl return super.onPrepareOptionsMenu(menu); } - // close activity and open homescreen @Override - public void goHome(View view) { - mapBase.goHome(view); + public void navigateUp(View view) { + ActivityMixin.navigateUp(this); } @Override diff --git a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapView.java b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapView.java index 7a5aab2..d95cc80 100644 --- a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapView.java +++ b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapView.java @@ -24,7 +24,6 @@ import org.mapsforge.android.maps.mapgenerator.MapGeneratorInternal; import org.mapsforge.android.maps.overlay.Overlay; import org.mapsforge.core.GeoPoint; -import android.app.Activity; import android.content.Context; import android.graphics.Canvas; import android.graphics.drawable.Drawable; @@ -43,6 +42,13 @@ public class MapsforgeMapView extends MapView implements MapViewImpl { public MapsforgeMapView(Context context, AttributeSet attrs) { super(context, attrs); + initialize(context); + } + + private void initialize(Context context) { + if (isInEditMode()) { + return; + } gestureDetector = new GestureDetector(context, new GestureListener()); if (Settings.isScaleMapsforgeText()) { this.setTextScale(getResources().getDisplayMetrics().density); @@ -105,8 +111,8 @@ public class MapsforgeMapView extends MapView implements MapViewImpl { } @Override - public PositionAndScaleOverlay createAddPositionAndScaleOverlay(Activity activity) { - MapsforgeOverlay ovl = new MapsforgeOverlay(activity); + public PositionAndScaleOverlay createAddPositionAndScaleOverlay() { + MapsforgeOverlay ovl = new MapsforgeOverlay(); getOverlays().add(ovl); return (PositionAndScaleOverlay) ovl.getBase(); } diff --git a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeOverlay.java b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeOverlay.java index 74a8601..3df4ab0 100644 --- a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeOverlay.java +++ b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeOverlay.java @@ -8,7 +8,6 @@ import cgeo.geocaching.maps.interfaces.OverlayImpl; import org.mapsforge.android.maps.Projection; import org.mapsforge.android.maps.overlay.Overlay; -import android.app.Activity; import android.graphics.Canvas; import android.graphics.Point; @@ -20,8 +19,8 @@ public class MapsforgeOverlay extends Overlay implements OverlayImpl { private PositionAndScaleOverlay overlayBase = null; private Lock lock = new ReentrantLock(); - public MapsforgeOverlay(Activity activityIn) { - overlayBase = new PositionAndScaleOverlay(activityIn, this); + public MapsforgeOverlay() { + overlayBase = new PositionAndScaleOverlay(this); } @Override diff --git a/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapActivity024.java b/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapActivity024.java index 33ed30e..daeb2b8 100644 --- a/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapActivity024.java +++ b/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapActivity024.java @@ -1,5 +1,6 @@ package cgeo.geocaching.maps.mapsforge.v024; +import cgeo.geocaching.activity.ActivityMixin; import cgeo.geocaching.activity.FilteredActivity; import cgeo.geocaching.maps.AbstractMap; import cgeo.geocaching.maps.CGeoMap; @@ -111,10 +112,9 @@ public class MapsforgeMapActivity024 extends MapActivity implements MapActivityI return super.onPrepareOptionsMenu(menu); } - // close activity and open homescreen @Override - public void goHome(View view) { - mapBase.goHome(view); + public void navigateUp(View view) { + ActivityMixin.navigateUp(this); } @Override diff --git a/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapView024.java b/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapView024.java index 4fa4e02..8dd15fc 100644 --- a/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapView024.java +++ b/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapView024.java @@ -21,7 +21,6 @@ import org.mapsforge.android.mapsold.MapViewMode; import org.mapsforge.android.mapsold.Overlay; import org.mapsforge.android.mapsold.Projection; -import android.app.Activity; import android.content.Context; import android.graphics.Canvas; import android.graphics.drawable.Drawable; @@ -37,6 +36,13 @@ public class MapsforgeMapView024 extends MapView implements MapViewImpl { public MapsforgeMapView024(Context context, AttributeSet attrs) { super(context, attrs); + initialize(context); + } + + private void initialize(Context context) { + if (isInEditMode()) { + return; + } gestureDetector = new GestureDetector(context, new GestureListener()); } @@ -96,8 +102,8 @@ public class MapsforgeMapView024 extends MapView implements MapViewImpl { } @Override - public PositionAndScaleOverlay createAddPositionAndScaleOverlay(Activity activity) { - MapsforgeOverlay ovl = new MapsforgeOverlay(activity); + public PositionAndScaleOverlay createAddPositionAndScaleOverlay() { + MapsforgeOverlay ovl = new MapsforgeOverlay(); getOverlays().add(ovl); return (PositionAndScaleOverlay) ovl.getBase(); } diff --git a/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeOverlay.java b/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeOverlay.java index 655e0b9..bfb3548 100644 --- a/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeOverlay.java +++ b/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeOverlay.java @@ -8,7 +8,6 @@ import cgeo.geocaching.maps.interfaces.OverlayImpl; import org.mapsforge.android.mapsold.Overlay; import org.mapsforge.android.mapsold.Projection; -import android.app.Activity; import android.graphics.Canvas; import android.graphics.Point; @@ -20,8 +19,8 @@ public class MapsforgeOverlay extends Overlay implements OverlayImpl { private PositionAndScaleOverlay overlayBase = null; private Lock lock = new ReentrantLock(); - public MapsforgeOverlay(Activity activityIn) { - overlayBase = new PositionAndScaleOverlay(activityIn, this); + public MapsforgeOverlay() { + overlayBase = new PositionAndScaleOverlay(this); } @Override diff --git a/main/src/cgeo/geocaching/network/HtmlImage.java b/main/src/cgeo/geocaching/network/HtmlImage.java index 9c55fe9..7c3434e 100644 --- a/main/src/cgeo/geocaching/network/HtmlImage.java +++ b/main/src/cgeo/geocaching/network/HtmlImage.java @@ -13,21 +13,20 @@ import cgeo.geocaching.utils.Log; import cgeo.geocaching.utils.RxUtils; import ch.boye.httpclientandroidlib.HttpResponse; + import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; + import rx.Observable; import rx.Observable.OnSubscribe; -import rx.Scheduler; -import rx.Scheduler.Inner; import rx.Subscriber; -import rx.functions.Action1; +import rx.functions.Action0; import rx.functions.Func0; import rx.functions.Func1; -import rx.schedulers.Schedulers; import rx.subjects.PublishSubject; import rx.subscriptions.CompositeSubscription; @@ -44,6 +43,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.util.Date; +import java.util.concurrent.Executor; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -92,10 +92,9 @@ public class HtmlImage implements Html.ImageGetter { // Background loading final private PublishSubject<Observable<String>> loading = PublishSubject.create(); - final Observable<String> waitForEnd = Observable.merge(loading).publish().refCount(); + final private Observable<String> waitForEnd = Observable.merge(loading).publish().refCount(); final CompositeSubscription subscription = new CompositeSubscription(waitForEnd.subscribe()); - final private Scheduler downloadScheduler = Schedulers.executor(new ThreadPoolExecutor(10, 10, 5, TimeUnit.SECONDS, - new LinkedBlockingQueue<Runnable>())); + final private Executor downloadExecutor = new ThreadPoolExecutor(10, 10, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); public HtmlImage(final String geocode, final boolean returnErrorImage, final int listId, final boolean onlySave) { this.geocode = geocode; @@ -152,9 +151,9 @@ public class HtmlImage implements Html.ImageGetter { @Override public void call(final Subscriber<? super BitmapDrawable> subscriber) { subscription.add(subscriber); - subscriber.add(RxUtils.computationScheduler.schedule(new Action1<Inner>() { + subscriber.add(RxUtils.computationScheduler.createWorker().schedule(new Action0() { @Override - public void call(final Inner inner) { + public void call() { final Pair<BitmapDrawable, Boolean> loaded = loadFromDisk(); final BitmapDrawable bitmap = loaded.getLeft(); if (loaded.getRight()) { @@ -165,12 +164,11 @@ public class HtmlImage implements Html.ImageGetter { if (bitmap != null && !onlySave) { subscriber.onNext(bitmap); } - subscriber.add(downloadScheduler.schedule(new Action1<Inner>() { - @Override - public void call(final Inner inner) { + downloadExecutor.execute(new Runnable() { + @Override public void run() { downloadAndSave(subscriber); } - })); + }); } })); } @@ -205,9 +203,9 @@ public class HtmlImage implements Html.ImageGetter { if (onlySave) { subscriber.onCompleted(); } else { - RxUtils.computationScheduler.schedule(new Action1<Inner>() { + RxUtils.computationScheduler.createWorker().schedule(new Action0() { @Override - public void call(final Inner inner) { + public void call() { final Pair<BitmapDrawable, Boolean> loaded = loadFromDisk(); final BitmapDrawable image = loaded.getLeft(); if (image != null) { @@ -225,12 +223,12 @@ public class HtmlImage implements Html.ImageGetter { }); } - public void waitForBackgroundLoading(@Nullable final CancellableHandler handler) { + public Observable<String> waitForEndObservable(@Nullable final CancellableHandler handler) { if (handler != null) { handler.unsubscribeIfCancelled(subscription); } loading.onCompleted(); - waitForEnd.toBlockingObservable().lastOrDefault(null); + return waitForEnd; } /** diff --git a/main/src/cgeo/geocaching/network/OAuthAuthorizationActivity.java b/main/src/cgeo/geocaching/network/OAuthAuthorizationActivity.java index e74751b..eb56f0b 100644 --- a/main/src/cgeo/geocaching/network/OAuthAuthorizationActivity.java +++ b/main/src/cgeo/geocaching/network/OAuthAuthorizationActivity.java @@ -106,7 +106,7 @@ public abstract class OAuthAuthorizationActivity extends AbstractActivity { @Override public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState, R.layout.authorization_activity, true); + super.onCreate(savedInstanceState, R.layout.authorization_activity); Bundle extras = getIntent().getExtras(); if (extras != null) { diff --git a/main/src/cgeo/geocaching/network/StatusUpdater.java b/main/src/cgeo/geocaching/network/StatusUpdater.java index 4055f01..bf9ebdf 100644 --- a/main/src/cgeo/geocaching/network/StatusUpdater.java +++ b/main/src/cgeo/geocaching/network/StatusUpdater.java @@ -5,10 +5,9 @@ import cgeo.geocaching.utils.Version; import org.json.JSONException; import org.json.JSONObject; -import rx.Scheduler; +import rx.functions.Action0; import rx.schedulers.Schedulers; import rx.subjects.BehaviorSubject; -import rx.functions.Action1; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; @@ -52,9 +51,9 @@ public class StatusUpdater { final static public BehaviorSubject<Status> latestStatus = BehaviorSubject.create(Status.defaultStatus(null)); static { - Schedulers.io().schedulePeriodically(new Action1<Scheduler.Inner>() { + Schedulers.io().createWorker().schedulePeriodically(new Action0() { @Override - public void call(final Scheduler.Inner inner) { + public void call() { final JSONObject response = Network.requestJSON("http://status.cgeo.org/api/status.json", new Parameters("version_code", String.valueOf(Version.getVersionCode(CgeoApplication.getInstance())), diff --git a/main/src/cgeo/geocaching/search/AutoCompleteAdapter.java b/main/src/cgeo/geocaching/search/AutoCompleteAdapter.java index 45559f4..21cf089 100644 --- a/main/src/cgeo/geocaching/search/AutoCompleteAdapter.java +++ b/main/src/cgeo/geocaching/search/AutoCompleteAdapter.java @@ -1,6 +1,7 @@ package cgeo.geocaching.search; import org.apache.commons.lang3.StringUtils; + import rx.functions.Func1; import android.content.Context; @@ -14,11 +15,11 @@ import android.widget.Filter; */ public class AutoCompleteAdapter extends ArrayAdapter<String> { - private final String[] EMPTY = new String[0]; + private final static String[] EMPTY = new String[0]; private String[] suggestions = EMPTY; private final Func1<String, String[]> suggestionFunction; - public AutoCompleteAdapter(Context context, int textViewResourceId, final Func1<String, String[]> suggestionFunction) { + public AutoCompleteAdapter(final Context context, final int textViewResourceId, final Func1<String, String[]> suggestionFunction) { super(context, textViewResourceId); this.suggestionFunction = suggestionFunction; } @@ -29,7 +30,7 @@ public class AutoCompleteAdapter extends ArrayAdapter<String> { } @Override - public String getItem(int index) { + public String getItem(final int index) { return suggestions[index]; } @@ -38,14 +39,14 @@ public class AutoCompleteAdapter extends ArrayAdapter<String> { return new Filter() { @Override - protected FilterResults performFiltering(CharSequence constraint) { - FilterResults filterResults = new FilterResults(); + protected FilterResults performFiltering(final CharSequence constraint) { + final FilterResults filterResults = new FilterResults(); if (constraint == null) { return filterResults; } - String trimmed = StringUtils.trim(constraint.toString()); + final String trimmed = StringUtils.trim(constraint.toString()); if (StringUtils.length(trimmed) >= 2) { - String[] newResults = suggestionFunction.call(trimmed); + final 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. @@ -56,7 +57,7 @@ public class AutoCompleteAdapter extends ArrayAdapter<String> { } @Override - protected void publishResults(CharSequence constraint, FilterResults filterResults) { + protected void publishResults(final CharSequence constraint, final FilterResults filterResults) { if (filterResults != null && filterResults.count > 0) { suggestions = (String[]) filterResults.values; notifyDataSetChanged(); diff --git a/main/src/cgeo/geocaching/search/SearchSuggestionCursor.java b/main/src/cgeo/geocaching/search/SearchSuggestionCursor.java new file mode 100644 index 0000000..350e23a --- /dev/null +++ b/main/src/cgeo/geocaching/search/SearchSuggestionCursor.java @@ -0,0 +1,46 @@ +package cgeo.geocaching.search; + +import cgeo.geocaching.Intents; +import cgeo.geocaching.enumerations.CacheType; + +import org.eclipse.jdt.annotation.NonNull; + +import android.app.SearchManager; +import android.database.MatrixCursor; +import android.provider.BaseColumns; + +/** + * Fixed fields cursor holding the necessary data for the search provider of the global search bar. + * + */ +public class SearchSuggestionCursor extends MatrixCursor { + + /** + * id of the row for callbacks after selection + */ + private int rowId = 0; + + public SearchSuggestionCursor() { + super(new String[] { + BaseColumns._ID, + SearchManager.SUGGEST_COLUMN_TEXT_1, + SearchManager.SUGGEST_COLUMN_TEXT_2, + SearchManager.SUGGEST_COLUMN_INTENT_ACTION, + SearchManager.SUGGEST_COLUMN_QUERY, + SearchManager.SUGGEST_COLUMN_ICON_1 }); + } + + public void addCache(@NonNull final String geocode, @NonNull final String name, final String type) { + final int icon = CacheType.getById(type).markerId; + addRow(new String[] { + String.valueOf(rowId), + name, + geocode, + Intents.ACTION_GEOCACHE, + geocode, + String.valueOf(icon) + }); + rowId++; + } + +} diff --git a/main/src/cgeo/geocaching/search/SuggestionProvider.java b/main/src/cgeo/geocaching/search/SuggestionProvider.java index c0a7728..f60a43e 100644 --- a/main/src/cgeo/geocaching/search/SuggestionProvider.java +++ b/main/src/cgeo/geocaching/search/SuggestionProvider.java @@ -1,6 +1,7 @@ package cgeo.geocaching.search; import cgeo.geocaching.DataStore; +import cgeo.geocaching.Geocache; import org.apache.commons.lang3.StringUtils; @@ -12,8 +13,6 @@ import android.net.Uri; public class SuggestionProvider extends ContentProvider { - private static Cursor lastCursor; - @Override public boolean onCreate() { return true; @@ -29,14 +28,21 @@ public class SuggestionProvider extends ContentProvider { final String searchTerm = uri.getLastPathSegment(); // can be empty when deleting the query if (StringUtils.equals(searchTerm, SearchManager.SUGGEST_URI_PATH_QUERY)) { - return lastCursor; + return getLastOpenedCaches(); } return getSuggestions(searchTerm); } + private static Cursor getLastOpenedCaches() { + final SearchSuggestionCursor resultCursor = new SearchSuggestionCursor(); + for (final Geocache geocache : DataStore.getLastOpenedCaches()) { + resultCursor.addCache(geocache.getGeocode(), geocache.getName(), geocache.getType().id); + } + return resultCursor; + } + private static Cursor getSuggestions(final String searchTerm) { - lastCursor = DataStore.findSuggestions(searchTerm); - return lastCursor; + return DataStore.findSuggestions(searchTerm); } @Override diff --git a/main/src/cgeo/geocaching/sensors/DirectionProvider.java b/main/src/cgeo/geocaching/sensors/DirectionProvider.java index ff4a439..ed5d76a 100644 --- a/main/src/cgeo/geocaching/sensors/DirectionProvider.java +++ b/main/src/cgeo/geocaching/sensors/DirectionProvider.java @@ -21,9 +21,13 @@ import android.view.WindowManager; public class DirectionProvider { - private static final BehaviorSubject<Float> subject = BehaviorSubject.create(0.0f); + private static final BehaviorSubject<Float> SUBJECT = BehaviorSubject.create(0.0f); - private static final WindowManager windowManager = (WindowManager) CgeoApplication.getInstance().getSystemService(Context.WINDOW_SERVICE); + private static final WindowManager WINDOW_MANAGER = (WindowManager) CgeoApplication.getInstance().getSystemService(Context.WINDOW_SERVICE); + + private DirectionProvider() { + // utility class + } static class Listener implements SensorEventListener, StartableHandlerThread.Callback { @@ -33,22 +37,17 @@ public class DirectionProvider { @Override public void onSensorChanged(final SensorEvent event) { - subject.onNext(event.values[0]); + SUBJECT.onNext(event.values[0]); } @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 - */ - - //Log.i(Settings.tag, "Compass' accuracy is low (" + 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. Do not have any code in here. + * + * See for example https://code.google.com/p/android/issues/detail?id=14792 + */ } @Override @@ -83,7 +82,7 @@ public class DirectionProvider { private boolean hasSensorChecked = false; public boolean hasSensor(Context context) { - if (hasSensorChecked == false) { + if (!hasSensorChecked) { hasSensor = getOrientationSensor(context) != null; hasSensorChecked = true; } @@ -99,18 +98,19 @@ public class DirectionProvider { } - private static final StartableHandlerThread handlerThread = + private static final StartableHandlerThread HANDLER_THREAD = new StartableHandlerThread("DirectionProvider thread", Process.THREAD_PRIORITY_BACKGROUND, new Listener()); static { - handlerThread.start(); + HANDLER_THREAD.start(); } - static public Observable<Float> create(final Context context) { + + public static Observable<Float> create(final Context context) { return Observable.create(new OnSubscribe<Float>() { @Override public void call(final Subscriber<? super Float> subscriber) { - handlerThread.start(subscriber, context); - subject.subscribe(subscriber); + HANDLER_THREAD.start(subscriber, context); + SUBJECT.subscribe(subscriber); } }); } @@ -131,7 +131,7 @@ public class DirectionProvider { } private static int getRotationOffset() { - switch (windowManager.getDefaultDisplay().getRotation()) { + switch (WINDOW_MANAGER.getDefaultDisplay().getRotation()) { case Surface.ROTATION_90: return 90; case Surface.ROTATION_180: diff --git a/main/src/cgeo/geocaching/sensors/GeoDataProvider.java b/main/src/cgeo/geocaching/sensors/GeoDataProvider.java index a77b477..a68f2a6 100644 --- a/main/src/cgeo/geocaching/sensors/GeoDataProvider.java +++ b/main/src/cgeo/geocaching/sensors/GeoDataProvider.java @@ -1,18 +1,15 @@ package cgeo.geocaching.sensors; -import android.os.*; import cgeo.geocaching.utils.Log; - import cgeo.geocaching.utils.StartableHandlerThread; + import org.apache.commons.lang3.StringUtils; import rx.Observable; import rx.Observable.OnSubscribe; -import rx.Scheduler.Inner; import rx.Subscriber; import rx.Subscription; import rx.android.schedulers.AndroidSchedulers; import rx.functions.Action0; -import rx.functions.Action1; import rx.observables.ConnectableObservable; import rx.subjects.BehaviorSubject; import rx.subscriptions.CompositeSubscription; @@ -24,6 +21,7 @@ import android.location.GpsStatus; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; +import android.os.Bundle; import java.util.concurrent.TimeUnit; @@ -98,9 +96,9 @@ public class GeoDataProvider implements OnSubscribe<IGeoData> { @Override public Subscription connect() { final CompositeSubscription subscription = new CompositeSubscription(); - AndroidSchedulers.handlerThread(handlerThread.getHandler()).schedule(new Action1<Inner>() { + AndroidSchedulers.handlerThread(handlerThread.getHandler()).createWorker().schedule(new Action0() { @Override - public void call(final Inner inner) { + public void call() { synchronized(lock) { if (count++ == 0) { Log.d("GeoDataProvider: starting the GPS and network listeners" + " (" + ++debugSessionCounter + ")"); @@ -118,9 +116,9 @@ public class GeoDataProvider implements OnSubscribe<IGeoData> { subscription.add(Subscriptions.create(new Action0() { @Override public void call() { - AndroidSchedulers.handlerThread(handlerThread.getHandler()).schedule(new Action1<Inner>() { + AndroidSchedulers.handlerThread(handlerThread.getHandler()).createWorker().schedule(new Action0() { @Override - public void call(final Inner inner) { + public void call() { synchronized (lock) { if (--count == 0) { Log.d("GeoDataProvider: stopping the GPS and network listeners" + " (" + debugSessionCounter + ")"); diff --git a/main/src/cgeo/geocaching/settings/Settings.java b/main/src/cgeo/geocaching/settings/Settings.java index 7a4dfdd..d4adcbd 100644 --- a/main/src/cgeo/geocaching/settings/Settings.java +++ b/main/src/cgeo/geocaching/settings/Settings.java @@ -39,6 +39,7 @@ import android.preference.PreferenceManager; import java.io.File; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Locale; @@ -47,6 +48,7 @@ import java.util.Locale; */ public class Settings { + private static final char HISTORY_SEPARATOR = ','; public static final int SHOW_WP_THRESHOLD_DEFAULT = 10; public static final int SHOW_WP_THRESHOLD_MAX = 50; private static final int MAP_SOURCE_DEFAULT = GoogleMapProvider.GOOGLE_MAP_ID.hashCode(); @@ -63,7 +65,7 @@ public class Settings { Min, Sec; - public static CoordInputFormatEnum fromInt(int id) { + public static CoordInputFormatEnum fromInt(final int id) { final CoordInputFormatEnum[] values = CoordInputFormatEnum.values(); if (id < 0 || id >= values.length) { return Min; @@ -91,7 +93,7 @@ public class Settings { private static void migrateSettings() { // migrate from non standard file location and integer based boolean types - int oldVersion = getInt(R.string.pref_settingsversion, 0); + final int oldVersion = getInt(R.string.pref_settingsversion, 0); if (oldVersion < 1) { final String oldPreferencesName = "cgeo.pref"; final SharedPreferences old = CgeoApplication.getInstance().getSharedPreferences(oldPreferencesName, Context.MODE_PRIVATE); @@ -173,13 +175,13 @@ public class Settings { e.putInt(getKey(R.string.pref_showwaypointsthreshold), wpThreshold); // KEY_MAP_SOURCE must be string, because it is the key for a ListPreference now - int ms = sharedPrefs.getInt(getKey(R.string.pref_mapsource), MAP_SOURCE_DEFAULT); + final int ms = sharedPrefs.getInt(getKey(R.string.pref_mapsource), MAP_SOURCE_DEFAULT); e.remove(getKey(R.string.pref_mapsource)); e.putString(getKey(R.string.pref_mapsource), String.valueOf(ms)); // navigation tool ids must be string, because ListPreference uses strings as keys - int dnt1 = sharedPrefs.getInt(getKey(R.string.pref_defaultNavigationTool), NavigationAppsEnum.COMPASS.id); - int dnt2 = sharedPrefs.getInt(getKey(R.string.pref_defaultNavigationTool2), NavigationAppsEnum.INTERNAL_MAP.id); + final int dnt1 = sharedPrefs.getInt(getKey(R.string.pref_defaultNavigationTool), NavigationAppsEnum.COMPASS.id); + final int dnt2 = sharedPrefs.getInt(getKey(R.string.pref_defaultNavigationTool2), NavigationAppsEnum.INTERNAL_MAP.id); e.remove(getKey(R.string.pref_defaultNavigationTool)); e.remove(getKey(R.string.pref_defaultNavigationTool2)); e.putString(getKey(R.string.pref_defaultNavigationTool), String.valueOf(dnt1)); @@ -258,7 +260,7 @@ public class Settings { return sharedPrefs.contains(getKey(prefKeyId)); } - public static void setLanguage(boolean useEnglish) { + public static void setLanguage(final boolean useEnglish) { final Configuration config = new Configuration(); config.locale = useEnglish ? Locale.ENGLISH : Locale.getDefault(); final Resources resources = CgeoApplication.getInstance().getResources(); @@ -346,11 +348,11 @@ public class Settings { } } - public static boolean isOCConnectorActive(int isActivePrefKeyId) { + public static boolean isOCConnectorActive(final int isActivePrefKeyId) { return getBoolean(isActivePrefKeyId, false); } - public static boolean hasOCAuthorization(int tokenPublicPrefKeyId, int tokenSecretPrefKeyId) { + public static boolean hasOCAuthorization(final int tokenPublicPrefKeyId, final int tokenSecretPrefKeyId) { return StringUtils.isNotBlank(getString(tokenPublicPrefKeyId, "")) && StringUtils.isNotBlank(getString(tokenSecretPrefKeyId, "")); } @@ -374,7 +376,7 @@ public class Settings { } public static String getSignature() { - return getString(R.string.pref_signature, null); + return getString(R.string.pref_signature, StringUtils.EMPTY); } public static boolean setCookieStore(final String cookies) { @@ -424,7 +426,7 @@ public class Settings { } public static boolean setMapFile(final String mapFile) { - boolean result = putString(R.string.pref_mapfile, mapFile); + final boolean result = putString(R.string.pref_mapfile, mapFile); if (mapFile != null) { setMapFileDirectory(new File(mapFile).getParent()); } @@ -444,7 +446,7 @@ public class Settings { } public static boolean setMapFileDirectory(final String mapFileDirectory) { - boolean result = putString(R.string.pref_mapDirectory, mapFileDirectory); + final boolean result = putString(R.string.pref_mapDirectory, mapFileDirectory); MapsforgeMapProvider.getInstance().updateOfflineMaps(); return result; } @@ -557,6 +559,10 @@ public class Settings { return getBoolean(R.string.pref_units, getImperialUnitsDefault()); } + public static boolean isAlwaysShowOverlfowMenu() { + return getBoolean(R.string.pref_alwaysshowoverflowmenu, false); + } + static boolean getImperialUnitsDefault() { final String countryCode = Locale.getDefault().getCountry(); return "US".equals(countryCode) // USA @@ -624,6 +630,7 @@ public class Settings { private final static int MAPNIK = 1; private final static int CYCLEMAP = 3; private final static int OFFLINE = 4; + private static final int HISTORY_SIZE = 10; /** * convert old preference ids for maps (based on constant values) into new hash based ids @@ -676,8 +683,8 @@ public class Settings { public static Geopoint getAnyCoordinates() { if (contains(R.string.pref_anylatitude) && contains(R.string.pref_anylongitude)) { - float lat = getFloat(R.string.pref_anylatitude, 0); - float lon = getFloat(R.string.pref_anylongitude, 0); + final float lat = getFloat(R.string.pref_anylatitude, 0); + final float lon = getFloat(R.string.pref_anylongitude, 0); return new Geopoint(lat, lon); } return null; @@ -760,7 +767,7 @@ public class Settings { } public static void setTwitterTokens(@Nullable final String tokenPublic, - @Nullable final String tokenSecret, boolean enableTwitter) { + @Nullable final String tokenSecret, final boolean enableTwitter) { putString(R.string.pref_twitter_token_public, tokenPublic); putString(R.string.pref_twitter_token_secret, tokenSecret); if (tokenPublic != null) { @@ -777,8 +784,8 @@ public class Settings { } public static ImmutablePair<String, String> getTempToken() { - String tokenPublic = getString(R.string.pref_temp_twitter_token_public, null); - String tokenSecret = getString(R.string.pref_temp_twitter_token_secret, null); + final String tokenPublic = getString(R.string.pref_temp_twitter_token_public, null); + final String tokenSecret = getString(R.string.pref_temp_twitter_token_secret, null); return new ImmutablePair<String, String>(tokenPublic, tokenSecret); } @@ -889,8 +896,8 @@ public class Settings { } public static File[] getMapThemeFiles() { - File directory = new File(Settings.getCustomRenderThemeBaseFolder()); - List<File> result = new ArrayList<File>(); + final File directory = new File(Settings.getCustomRenderThemeBaseFolder()); + final List<File> result = new ArrayList<File>(); FileUtils.listDir(result, directory, new ExtensionsBasedFileSelector(new String[] { "xml" }), null); return result.toArray(new File[result.size()]); @@ -898,13 +905,13 @@ public class Settings { private static class ExtensionsBasedFileSelector extends FileSelector { private final String[] extensions; - public ExtensionsBasedFileSelector(String[] extensions) { + public ExtensionsBasedFileSelector(final String[] extensions) { this.extensions = extensions; } @Override - public boolean isSelected(File file) { - String filename = file.getName(); - for (String ext : extensions) { + public boolean isSelected(final File file) { + final String filename = file.getName(); + for (final String ext : extensions) { if (StringUtils.endsWithIgnoreCase(filename, ext)) { return true; } @@ -970,7 +977,7 @@ public class Settings { putLong(R.string.pref_fieldNoteExportDate, date); } - public static boolean isUseNavigationApp(NavigationAppsEnum navApp) { + public static boolean isUseNavigationApp(final NavigationAppsEnum navApp) { return getBoolean(navApp.preferenceKey, true); } @@ -979,7 +986,7 @@ public class Settings { * * @param upload */ - public static void setFieldNoteExportUpload(boolean upload) { + public static void setFieldNoteExportUpload(final boolean upload) { putBoolean(R.string.pref_fieldNoteExportUpload, upload); } @@ -992,7 +999,7 @@ public class Settings { * * @param onlyNew */ - public static void setFieldNoteExportOnlyNew(boolean onlyNew) { + public static void setFieldNoteExportOnlyNew(final boolean onlyNew) { putBoolean(R.string.pref_fieldNoteExportOnlyNew, onlyNew); } @@ -1004,4 +1011,25 @@ public class Settings { return getString(R.string.pref_ec_icons, "1"); } + /* Store last version for the changelog display */ + public static int getLastChangelogVersion() { + return getInt(R.string.pref_changelog_last_version, 0); + } + + public static void setLastChangelogVersion(final int version) { + putInt(R.string.pref_changelog_last_version, version); + } + + public static List<String> getLastOpenedCaches() { + final List<String> history = Arrays.asList(StringUtils.split(getString(R.string.pref_caches_history, StringUtils.EMPTY), HISTORY_SEPARATOR)); + return history.subList(0, Math.min(HISTORY_SIZE, history.size())); + } + + public static void addCacheToHistory(@NonNull final String geocode) { + final ArrayList<String> history = new ArrayList<String>(getLastOpenedCaches()); + // bring entry to front, if it already existed + history.remove(geocode); + history.add(0, geocode); + putString(R.string.pref_caches_history, StringUtils.join(history, HISTORY_SEPARATOR)); + } } diff --git a/main/src/cgeo/geocaching/settings/SettingsActivity.java b/main/src/cgeo/geocaching/settings/SettingsActivity.java index dc1a39d..315c73a 100644 --- a/main/src/cgeo/geocaching/settings/SettingsActivity.java +++ b/main/src/cgeo/geocaching/settings/SettingsActivity.java @@ -14,6 +14,7 @@ import cgeo.geocaching.files.SimpleDirChooser; import cgeo.geocaching.maps.MapProviderFactory; import cgeo.geocaching.maps.interfaces.MapSource; import cgeo.geocaching.utils.DatabaseBackupUtils; +import cgeo.geocaching.utils.DebugUtils; import cgeo.geocaching.utils.Log; import org.apache.commons.lang3.StringUtils; @@ -62,7 +63,7 @@ public class SettingsActivity extends PreferenceActivity { * directory and preference key in onActivityResult() easily just by knowing * the result code. */ - private enum DirChooserType { + private static enum DirChooserType { GPX_IMPORT_DIR(1, R.string.pref_gpxImportDir, Environment.getExternalStorageDirectory().getPath() + "/gpx", false), GPX_EXPORT_DIR(2, R.string.pref_gpxExportDir, @@ -90,8 +91,19 @@ public class SettingsActivity extends PreferenceActivity { SettingsActivity.addPreferencesFromResource(this, R.xml.preferences); initPreferences(); + /* Remove the show overflow preference on Android version where the platform always or never + * shows the overflow menu and the app cannot influence the behaviour + */ + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB || Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2) { + Preference pref = findPreference(this, getString(R.string.pref_alwaysshowoverflowmenu)); + PreferenceScreen appearence = (PreferenceScreen) findPreference(this, getString(R.string.pref_appearance)); + appearence.removePreference(pref); + + } Intent intent = getIntent(); openInitialScreen(intent.getIntExtra(INTENT_OPEN_SCREEN, 0)); + + } private void openInitialScreen(int initialScreen) { @@ -189,7 +201,7 @@ public class SettingsActivity extends PreferenceActivity { private static void setServiceScreenSummary(PreferenceManager preferenceManager, final int preferenceKey) { - String summary = StringUtils.EMPTY; + String summary; switch (preferenceKey) { case R.string.pref_connectorGCActive: @@ -380,6 +392,15 @@ public class SettingsActivity extends PreferenceActivity { return true; } }); + Preference memoryDumpPref = getPreference(R.string.pref_memory_dump); + memoryDumpPref + .setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override public boolean onPreferenceClick( + Preference preference) { + DebugUtils.createMemoryDump(SettingsActivity.this); + return true; + } + }); } private void initDbLocationPreference() { @@ -582,7 +603,7 @@ public class SettingsActivity extends PreferenceActivity { || isPreference(preference, R.string.pref_connectorOXActive) || isPreference(preference, R.string.pref_connectorECActive)) { // update summary - boolean boolVal = ((Boolean) value).booleanValue(); + final boolean boolVal = (Boolean) value; String summary = getServiceSummary(boolVal); if (OCPreferenceKeys.isOCPreference(preference.getKey())) { OCPreferenceKeys prefKey = OCPreferenceKeys.getByKey(preference.getKey()); diff --git a/main/src/cgeo/geocaching/settings/TemplateTextPreference.java b/main/src/cgeo/geocaching/settings/TemplateTextPreference.java index 667b02b..1f420ef 100644 --- a/main/src/cgeo/geocaching/settings/TemplateTextPreference.java +++ b/main/src/cgeo/geocaching/settings/TemplateTextPreference.java @@ -58,7 +58,7 @@ public class TemplateTextPreference extends DialogPreference { public void onClick(View button) { AlertDialog.Builder alert = new AlertDialog.Builder(TemplateTextPreference.this.getContext()); alert.setTitle(R.string.init_signature_template_button); - final ArrayList<LogTemplate> templates = LogTemplateProvider.getTemplates(); + final ArrayList<LogTemplate> templates = LogTemplateProvider.getTemplatesWithoutSignature(); String[] items = new String[templates.size()]; for (int i = 0; i < templates.size(); i++) { items[i] = settingsActivity.getResources().getString(templates.get(i).getResourceId()); diff --git a/main/src/cgeo/geocaching/sorting/PopularityRatioComparator.java b/main/src/cgeo/geocaching/sorting/PopularityRatioComparator.java index 1ed8e68..57a69ee 100644 --- a/main/src/cgeo/geocaching/sorting/PopularityRatioComparator.java +++ b/main/src/cgeo/geocaching/sorting/PopularityRatioComparator.java @@ -12,16 +12,14 @@ public class PopularityRatioComparator extends AbstractCacheComparator { @Override protected int compareCaches(final Geocache cache1, final Geocache cache2) { - - float ratio1 = 0.0f; - float ratio2 = 0.0f; - int finds1 = cache1.getFindsCount(); int finds2 = cache2.getFindsCount(); + float ratio1 = 0.0f; if (finds1 != 0) { ratio1 = (((float) cache1.getFavoritePoints()) / ((float) finds1)); } + float ratio2 = 0.0f; if (finds2 != 0) { ratio2 = (((float) cache2.getFavoritePoints()) / ((float) finds2)); } diff --git a/main/src/cgeo/geocaching/ui/AbstractCachingListViewPageViewCreator.java b/main/src/cgeo/geocaching/ui/AbstractCachingListViewPageViewCreator.java index 799b695..06fa1fa 100644 --- a/main/src/cgeo/geocaching/ui/AbstractCachingListViewPageViewCreator.java +++ b/main/src/cgeo/geocaching/ui/AbstractCachingListViewPageViewCreator.java @@ -51,7 +51,6 @@ public abstract class AbstractCachingListViewPageViewCreator extends AbstractCac int logViewPosition = state.getInt(STATE_POSITION); int logViewPositionFromTop = state.getInt(STATE_POSITION_FROM_TOP); view.setSelectionFromTop(logViewPosition, logViewPositionFromTop); - return; } } diff --git a/main/src/cgeo/geocaching/ui/AbstractCachingPageViewCreator.java b/main/src/cgeo/geocaching/ui/AbstractCachingPageViewCreator.java index 0c67384..71cd3b4 100644 --- a/main/src/cgeo/geocaching/ui/AbstractCachingPageViewCreator.java +++ b/main/src/cgeo/geocaching/ui/AbstractCachingPageViewCreator.java @@ -52,6 +52,5 @@ public abstract class AbstractCachingPageViewCreator<ViewClass extends View> imp */ @Override public void setViewState(@NonNull Bundle state) { - return; } } diff --git a/main/src/cgeo/geocaching/ui/CacheListAdapter.java b/main/src/cgeo/geocaching/ui/CacheListAdapter.java index 0d90d9f..3a451a5 100644 --- a/main/src/cgeo/geocaching/ui/CacheListAdapter.java +++ b/main/src/cgeo/geocaching/ui/CacheListAdapter.java @@ -45,6 +45,7 @@ import android.widget.CheckBox; import android.widget.ImageView; import android.widget.TextView; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -377,7 +378,7 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> { final boolean lightSkin = Settings.isLightSkin(); - final TouchListener touchListener = new TouchListener(cache); + final TouchListener touchListener = new TouchListener(cache, this); v.setOnClickListener(touchListener); v.setOnLongClickListener(touchListener); v.setOnTouchListener(touchListener); @@ -536,24 +537,30 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> { } } - private class TouchListener implements View.OnClickListener, View.OnLongClickListener, View.OnTouchListener { + private static class TouchListener implements View.OnClickListener, View.OnLongClickListener, View.OnTouchListener { private final Geocache cache; private final GestureDetector gestureDetector; + private final @NonNull WeakReference<CacheListAdapter> adapterRef; - public TouchListener(final Geocache cache) { + public TouchListener(final Geocache cache, final @NonNull CacheListAdapter adapter) { this.cache = cache; - gestureDetector = new GestureDetector(getContext(), new FlingGesture(cache)); + gestureDetector = new GestureDetector(adapter.getContext(), new FlingGesture(cache, adapter)); + adapterRef = new WeakReference<CacheListAdapter>(adapter); } // Tap on item @Override public void onClick(final View view) { - if (isSelectMode()) { + final CacheListAdapter adapter = adapterRef.get(); + if (adapter == null) { + return; + } + if (adapter.isSelectMode()) { cache.setStatusChecked(!cache.isStatusChecked()); - notifyDataSetChanged(); + adapter.notifyDataSetChanged(); } else { - CacheDetailActivity.startActivity(getContext(), cache.getGeocode(), cache.getName()); + CacheDetailActivity.startActivity(adapter.getContext(), cache.getGeocode(), cache.getName()); } } @@ -572,12 +579,14 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> { } } - private class FlingGesture extends GestureDetector.SimpleOnGestureListener { + private static class FlingGesture extends GestureDetector.SimpleOnGestureListener { private final Geocache cache; + private final @NonNull WeakReference<CacheListAdapter> adapterRef; - public FlingGesture(final Geocache cache) { + public FlingGesture(final Geocache cache, final @NonNull CacheListAdapter adapter) { this.cache = cache; + adapterRef = new WeakReference<CacheListAdapter>(adapter); } @Override @@ -586,11 +595,15 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> { if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH) { return false; } + final CacheListAdapter adapter = adapterRef.get(); + if (adapter == null) { + return false; + } // left to right swipe if ((e2.getX() - e1.getX()) > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > Math.abs(velocityY)) { - if (!selectMode) { - switchSelectMode(); + if (!adapter.selectMode) { + adapter.switchSelectMode(); cache.setStatusChecked(true); } return true; @@ -598,8 +611,8 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> { // right to left swipe if ((e1.getX() - e2.getX()) > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > Math.abs(velocityY)) { - if (selectMode) { - switchSelectMode(); + if (adapter.selectMode) { + adapter.switchSelectMode(); } return true; } diff --git a/main/src/cgeo/geocaching/ui/CompassView.java b/main/src/cgeo/geocaching/ui/CompassView.java index f7111f7..60982a9 100644 --- a/main/src/cgeo/geocaching/ui/CompassView.java +++ b/main/src/cgeo/geocaching/ui/CompassView.java @@ -3,10 +3,9 @@ package cgeo.geocaching.ui; import cgeo.geocaching.R; import cgeo.geocaching.utils.AngleUtils; -import rx.Scheduler; import rx.Subscription; import rx.android.schedulers.AndroidSchedulers; -import rx.functions.Action1; +import rx.functions.Action0; import android.content.Context; import android.content.res.Resources; @@ -18,6 +17,7 @@ import android.graphics.PaintFlagsDrawFilter; import android.util.AttributeSet; import android.view.View; +import java.lang.ref.WeakReference; import java.util.concurrent.TimeUnit; public class CompassView extends View { @@ -56,11 +56,40 @@ public class CompassView extends View { private boolean initialDisplay; private Subscription periodicUpdate; + private static final class UpdateAction implements Action0 { + + private final WeakReference<CompassView> compassViewRef; + + private UpdateAction(CompassView view) { + this.compassViewRef = new WeakReference<CompassView>(view); + } + + @Override + public void call() { + final CompassView compassView = compassViewRef.get(); + if (compassView == null) { + return; + } + compassView.updateGraphics(); + } + } + public CompassView(Context contextIn) { super(contextIn); context = contextIn; } + public void updateGraphics() { + final float newAzimuthShown = smoothUpdate(northMeasured, azimuthShown); + final float newCacheHeadingShown = smoothUpdate(cacheHeadingMeasured, cacheHeadingShown); + if (Math.abs(AngleUtils.difference(azimuthShown, newAzimuthShown)) >= 2 || + Math.abs(AngleUtils.difference(cacheHeadingShown, newCacheHeadingShown)) >= 2) { + azimuthShown = newAzimuthShown; + cacheHeadingShown = newCacheHeadingShown; + invalidate(); + } + } + public CompassView(Context contextIn, AttributeSet attrs) { super(contextIn, attrs); context = contextIn; @@ -88,24 +117,13 @@ public class CompassView extends View { initialDisplay = true; - periodicUpdate = AndroidSchedulers.mainThread().schedulePeriodically(new Action1<Scheduler.Inner>() { - @Override - public void call(final Scheduler.Inner inner) { - final float newAzimuthShown = smoothUpdate(northMeasured, azimuthShown); - final float newCacheHeadingShown = smoothUpdate(cacheHeadingMeasured, cacheHeadingShown); - if (Math.abs(AngleUtils.difference(azimuthShown, newAzimuthShown)) >= 2 || - Math.abs(AngleUtils.difference(cacheHeadingShown, newCacheHeadingShown)) >= 2) { - azimuthShown = newAzimuthShown; - cacheHeadingShown = newCacheHeadingShown; - invalidate(); - } - } - }, 0, 40, TimeUnit.MILLISECONDS); + periodicUpdate = AndroidSchedulers.mainThread().createWorker().schedulePeriodically(new UpdateAction(this), 0, 40, TimeUnit.MILLISECONDS); } @Override public void onDetachedFromWindow() { periodicUpdate.unsubscribe(); + super.onDetachedFromWindow(); if (compassUnderlay != null) { diff --git a/main/src/cgeo/geocaching/ui/dialog/CoordinatesInputDialog.java b/main/src/cgeo/geocaching/ui/dialog/CoordinatesInputDialog.java index b2ce11a..00b5abe 100644 --- a/main/src/cgeo/geocaching/ui/dialog/CoordinatesInputDialog.java +++ b/main/src/cgeo/geocaching/ui/dialog/CoordinatesInputDialog.java @@ -1,12 +1,11 @@ package cgeo.geocaching.ui.dialog; import cgeo.geocaching.Geocache; -import cgeo.geocaching.sensors.IGeoData; import cgeo.geocaching.R; import cgeo.geocaching.activity.AbstractActivity; -import cgeo.geocaching.activity.ActivityMixin; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.GeopointFormatter; +import cgeo.geocaching.sensors.IGeoData; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.settings.Settings.CoordInputFormatEnum; import cgeo.geocaching.utils.EditUtils; @@ -14,9 +13,12 @@ import cgeo.geocaching.utils.EditUtils; import org.apache.commons.lang3.StringUtils; import android.os.Bundle; +import android.support.v4.app.DialogFragment; import android.text.Editable; import android.text.TextWatcher; +import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemSelectedListener; import android.widget.ArrayAdapter; @@ -25,12 +27,11 @@ import android.widget.EditText; import android.widget.Spinner; import android.widget.TextView; -public class CoordinatesInputDialog extends NoTitleDialog { +public class CoordinatesInputDialog extends DialogFragment { - final private AbstractActivity context; - final private IGeoData geo; - final private Geocache cache; private Geopoint gp; + private Geopoint gpinitial; + private Geopoint cacheCoords; private EditText eLat, eLon; private Button bLat, bLon; @@ -39,34 +40,64 @@ public class CoordinatesInputDialog extends NoTitleDialog { private TextView tLatSep1, tLatSep2, tLatSep3; private TextView tLonSep1, tLonSep2, tLonSep3; - private CoordinateUpdate cuListener; - private CoordInputFormatEnum currentFormat = null; - public CoordinatesInputDialog(final AbstractActivity context, final Geocache cache, final Geopoint gp, final IGeoData geo) { - super(context, ActivityMixin.getDialogTheme()); - this.context = context; - this.geo = geo; - this.cache = cache; + + private static final String GEOPOINT_ARG = "GEOPOINT"; + private static final String GEOPOINT_INTIAL_ARG = "GEOPOINT_INITIAL"; + private static final String CACHECOORDS_ARG = "CACHECOORDS"; + + + public static CoordinatesInputDialog getInstance(final Geocache cache, final Geopoint gp, final IGeoData geo) { + + Bundle args = new Bundle(); if (gp != null) { - this.gp = gp; + args.putParcelable(GEOPOINT_ARG, gp); } else if (geo != null && geo.getCoords() != null) { - this.gp = geo.getCoords(); + args.putParcelable(GEOPOINT_ARG, geo.getCoords()); } else { - this.gp = Geopoint.ZERO; + args.putParcelable(GEOPOINT_ARG, Geopoint.ZERO); } + + if (geo !=null) + args.putParcelable(GEOPOINT_INTIAL_ARG, geo.getCoords()); + + if (cache != null) + args.putParcelable(CACHECOORDS_ARG, cache.getCoords()); + + CoordinatesInputDialog cid = new CoordinatesInputDialog(); + cid.setArguments(args); + return cid; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + gp = getArguments().getParcelable(GEOPOINT_ARG); + gpinitial = getArguments().getParcelable(GEOPOINT_INTIAL_ARG); + cacheCoords = getArguments().getParcelable(CACHECOORDS_ARG); - setContentView(R.layout.coordinatesinput_dialog); + if (savedInstanceState != null && savedInstanceState.getParcelable(GEOPOINT_ARG)!=null) + gp = savedInstanceState.getParcelable(GEOPOINT_ARG); - final Spinner spinner = (Spinner) findViewById(R.id.spinnerCoordinateFormats); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + // TODO: if current input is not commited in gp, read the current input into gp + outState.putParcelable(GEOPOINT_ARG, gp); + } + + @Override + public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) { + getDialog().setTitle(R.string.cache_coordinates); + + View v = inflater.inflate(R.layout.coordinatesinput_dialog, container, false); + final Spinner spinner = (Spinner) v.findViewById(R.id.spinnerCoordinateFormats); final ArrayAdapter<CharSequence> adapter = - ArrayAdapter.createFromResource(context, + ArrayAdapter.createFromResource(getActivity(), R.array.waypoint_coordinate_formats, android.R.layout.simple_spinner_item); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); @@ -74,25 +105,25 @@ public class CoordinatesInputDialog extends NoTitleDialog { spinner.setSelection(Settings.getCoordInputFormat().ordinal()); spinner.setOnItemSelectedListener(new CoordinateFormatListener()); - bLat = (Button) findViewById(R.id.ButtonLat); - eLat = (EditText) findViewById(R.id.latitude); - eLatDeg = (EditText) findViewById(R.id.EditTextLatDeg); - eLatMin = (EditText) findViewById(R.id.EditTextLatMin); - eLatSec = (EditText) findViewById(R.id.EditTextLatSec); - eLatSub = (EditText) findViewById(R.id.EditTextLatSecFrac); - tLatSep1 = (TextView) findViewById(R.id.LatSeparator1); - tLatSep2 = (TextView) findViewById(R.id.LatSeparator2); - tLatSep3 = (TextView) findViewById(R.id.LatSeparator3); - - bLon = (Button) findViewById(R.id.ButtonLon); - eLon = (EditText) findViewById(R.id.longitude); - eLonDeg = (EditText) findViewById(R.id.EditTextLonDeg); - eLonMin = (EditText) findViewById(R.id.EditTextLonMin); - eLonSec = (EditText) findViewById(R.id.EditTextLonSec); - eLonSub = (EditText) findViewById(R.id.EditTextLonSecFrac); - tLonSep1 = (TextView) findViewById(R.id.LonSeparator1); - tLonSep2 = (TextView) findViewById(R.id.LonSeparator2); - tLonSep3 = (TextView) findViewById(R.id.LonSeparator3); + bLat = (Button) v.findViewById(R.id.ButtonLat); + eLat = (EditText) v.findViewById(R.id.latitude); + eLatDeg = (EditText) v.findViewById(R.id.EditTextLatDeg); + eLatMin = (EditText) v.findViewById(R.id.EditTextLatMin); + eLatSec = (EditText) v.findViewById(R.id.EditTextLatSec); + eLatSub = (EditText) v.findViewById(R.id.EditTextLatSecFrac); + tLatSep1 = (TextView) v.findViewById(R.id.LatSeparator1); + tLatSep2 = (TextView) v.findViewById(R.id.LatSeparator2); + tLatSep3 = (TextView) v.findViewById(R.id.LatSeparator3); + + bLon = (Button) v.findViewById(R.id.ButtonLon); + eLon = (EditText) v.findViewById(R.id.longitude); + eLonDeg = (EditText) v.findViewById(R.id.EditTextLonDeg); + eLonMin = (EditText) v.findViewById(R.id.EditTextLonMin); + eLonSec = (EditText) v.findViewById(R.id.EditTextLonSec); + eLonSub = (EditText) v.findViewById(R.id.EditTextLonSecFrac); + tLonSep1 = (TextView) v.findViewById(R.id.LonSeparator1); + tLonSep2 = (TextView) v.findViewById(R.id.LonSeparator2); + tLonSep3 = (TextView) v.findViewById(R.id.LonSeparator3); eLatDeg.addTextChangedListener(new TextChanged(eLatDeg)); eLatMin.addTextChangedListener(new TextChanged(eLatMin)); @@ -115,18 +146,24 @@ public class CoordinatesInputDialog extends NoTitleDialog { bLat.setOnClickListener(new ButtonClickListener()); bLon.setOnClickListener(new ButtonClickListener()); - final Button buttonCurrent = (Button) findViewById(R.id.current); + final Button buttonCurrent = (Button) v.findViewById(R.id.current); buttonCurrent.setOnClickListener(new CurrentListener()); - final Button buttonCache = (Button) findViewById(R.id.cache); - if (cache != null) { + final Button buttonCache = (Button) v.findViewById(R.id.cache); + + if (cacheCoords != null) { buttonCache.setOnClickListener(new CacheListener()); } else { buttonCache.setVisibility(View.GONE); } - final Button buttonDone = (Button) findViewById(R.id.done); + + final Button buttonDone = (Button) v.findViewById(R.id.done); buttonDone.setOnClickListener(new InputDoneListener()); + + return v; } + + private void updateGUI() { if (gp == null) { return; @@ -137,14 +174,14 @@ public class CoordinatesInputDialog extends NoTitleDialog { switch (currentFormat) { case Plain: - findViewById(R.id.coordTable).setVisibility(View.GONE); + getView().findViewById(R.id.coordTable).setVisibility(View.GONE); eLat.setVisibility(View.VISIBLE); eLon.setVisibility(View.VISIBLE); eLat.setText(gp.format(GeopointFormatter.Format.LAT_DECMINUTE)); eLon.setText(gp.format(GeopointFormatter.Format.LON_DECMINUTE)); break; case Deg: // DDD.DDDDD° - findViewById(R.id.coordTable).setVisibility(View.VISIBLE); + getView().findViewById(R.id.coordTable).setVisibility(View.VISIBLE); eLat.setVisibility(View.GONE); eLon.setVisibility(View.GONE); eLatSec.setVisibility(View.GONE); @@ -165,7 +202,7 @@ public class CoordinatesInputDialog extends NoTitleDialog { eLonMin.setText(addZeros(gp.getLonDegFrac(), 5)); break; case Min: // DDD° MM.MMM - findViewById(R.id.coordTable).setVisibility(View.VISIBLE); + getView().findViewById(R.id.coordTable).setVisibility(View.VISIBLE); eLat.setVisibility(View.GONE); eLon.setVisibility(View.GONE); eLatSec.setVisibility(View.VISIBLE); @@ -190,7 +227,7 @@ public class CoordinatesInputDialog extends NoTitleDialog { eLonSec.setText(addZeros(gp.getLonMinFrac(), 3)); break; case Sec: // DDD° MM SS.SSS - findViewById(R.id.coordTable).setVisibility(View.VISIBLE); + getView().findViewById(R.id.coordTable).setVisibility(View.VISIBLE); eLat.setVisibility(View.GONE); eLon.setVisibility(View.GONE); eLatSec.setVisibility(View.VISIBLE); @@ -371,7 +408,8 @@ public class CoordinatesInputDialog extends NoTitleDialog { // Signaled and returned below } if (signalError) { - context.showToast(context.getResources().getString(R.string.err_parse_lat_lon)); + final AbstractActivity activity = (AbstractActivity) getActivity(); + activity.showToast(activity.getResources().getString(R.string.err_parse_lat_lon)); } return false; } @@ -399,8 +437,8 @@ public class CoordinatesInputDialog extends NoTitleDialog { // Start new format with an acceptable value: either the current one // entered by the user, else our current coordinates, else (0,0). if (!areCurrentCoordinatesValid(false)) { - if (geo != null && geo.getCoords() != null) { - gp = geo.getCoords(); + if (gpinitial != null) { + gp = gpinitial; } else { gp = Geopoint.ZERO; } @@ -422,12 +460,13 @@ public class CoordinatesInputDialog extends NoTitleDialog { @Override public void onClick(View v) { - if (geo == null || geo.getCoords() == null) { - context.showToast(context.getResources().getString(R.string.err_point_unknown_position)); + if (gpinitial == null) { + final AbstractActivity activity = (AbstractActivity) getActivity(); + activity.showToast(activity.getResources().getString(R.string.err_point_unknown_position)); return; } - gp = geo.getCoords(); + gp = gpinitial; updateGUI(); } } @@ -436,16 +475,18 @@ public class CoordinatesInputDialog extends NoTitleDialog { @Override public void onClick(View v) { - if (cache == null || cache.getCoords() == null) { - context.showToast(context.getResources().getString(R.string.err_location_unknown)); + if (cacheCoords == null) { + final AbstractActivity activity = (AbstractActivity) getActivity(); + activity.showToast(activity.getResources().getString(R.string.err_location_unknown)); return; } - gp = cache.getCoords(); + gp = cacheCoords; updateGUI(); } } + private class InputDoneListener implements View.OnClickListener { @Override @@ -454,18 +495,14 @@ public class CoordinatesInputDialog extends NoTitleDialog { return; } if (gp != null) { - cuListener.update(gp); + ((CoordinateUpdate) getActivity()).updateCoordinates(gp); } dismiss(); } } - public void setOnCoordinateUpdate(CoordinateUpdate cu) { - cuListener = cu; - } - public interface CoordinateUpdate { - public void update(final Geopoint gp); + public void updateCoordinates(final Geopoint gp); } } diff --git a/main/src/cgeo/geocaching/ui/dialog/DateDialog.java b/main/src/cgeo/geocaching/ui/dialog/DateDialog.java index 18f8e2e..fc69f44 100644 --- a/main/src/cgeo/geocaching/ui/dialog/DateDialog.java +++ b/main/src/cgeo/geocaching/ui/dialog/DateDialog.java @@ -2,48 +2,56 @@ package cgeo.geocaching.ui.dialog; import cgeo.geocaching.R; -import android.app.Activity; import android.os.Bundle; +import android.support.v4.app.DialogFragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; import android.widget.DatePicker; import java.util.Calendar; -public class DateDialog extends NoTitleDialog { +public class DateDialog extends DialogFragment { public interface DateDialogParent { abstract public void setDate(final Calendar date); } - private final DateDialogParent parent; - private final Calendar date; + private Calendar date; - public DateDialog(Activity contextIn, DateDialogParent parentIn, Calendar dateIn) { - super(contextIn); - - // init - this.date = dateIn; - this.parent = parentIn; + public static DateDialog getInstance(Calendar date) { + DateDialog dd = new DateDialog(); + Bundle args = new Bundle(); + args.putSerializable("date", date); + dd.setArguments(args); + return dd; } @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); + setStyle(DialogFragment.STYLE_NO_TITLE, 0); + Bundle args = getArguments(); + date = (Calendar) args.getSerializable("date"); + } - setContentView(R.layout.date); + @Override + public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.date, container, false); - final DatePicker picker = (DatePicker) findViewById(R.id.picker); + final DatePicker picker = (DatePicker) v.findViewById(R.id.picker); picker.init(date.get(Calendar.YEAR), date.get(Calendar.MONTH), date.get(Calendar.DATE), new DatePickerListener()); + return v; } private class DatePickerListener implements DatePicker.OnDateChangedListener { @Override public void onDateChanged(DatePicker picker, int year, int month, int day) { - if (parent != null) { - date.set(year, month, day); + date.set(year, month, day); + + ((DateDialogParent) getActivity()).setDate(date); - parent.setDate(date); - } } } }
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/ui/dialog/Dialogs.java b/main/src/cgeo/geocaching/ui/dialog/Dialogs.java index cb8926a..6a2f9a5 100644 --- a/main/src/cgeo/geocaching/ui/dialog/Dialogs.java +++ b/main/src/cgeo/geocaching/ui/dialog/Dialogs.java @@ -218,7 +218,19 @@ public final class Dialogs { /** * Show a message dialog with a single "OK" button. - * + * + * @param context + * activity owning the dialog + * @param message + * message dialog content + */ + public static void message(final Activity context, final int message) { + message(context, null, getString(message)); + } + + /** + * Show a message dialog with a single "OK" button. + * * @param context * activity owning the dialog * @param title diff --git a/main/src/cgeo/geocaching/ui/dialog/NoTitleDialog.java b/main/src/cgeo/geocaching/ui/dialog/NoTitleDialog.java deleted file mode 100644 index 8660a7b..0000000 --- a/main/src/cgeo/geocaching/ui/dialog/NoTitleDialog.java +++ /dev/null @@ -1,32 +0,0 @@ -package cgeo.geocaching.ui.dialog; - -import cgeo.geocaching.utils.Log; - -import android.app.Dialog; -import android.content.Context; -import android.os.Bundle; -import android.view.ViewGroup.LayoutParams; -import android.view.Window; - -public abstract class NoTitleDialog extends Dialog { - - public NoTitleDialog(Context context) { - super(context); - } - - public NoTitleDialog(Context context, int theme) { - super(context, theme); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - try { - requestWindowFeature(Window.FEATURE_NO_TITLE); - getWindow().setLayout(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); - } catch (final Exception e) { - Log.e("NoTitleDialog.onCreate", e); - } - } -} diff --git a/main/src/cgeo/geocaching/utils/DatabaseBackupUtils.java b/main/src/cgeo/geocaching/utils/DatabaseBackupUtils.java index 4ce2e0c..14840b9 100644 --- a/main/src/cgeo/geocaching/utils/DatabaseBackupUtils.java +++ b/main/src/cgeo/geocaching/utils/DatabaseBackupUtils.java @@ -10,7 +10,6 @@ import org.apache.commons.lang3.StringUtils; import android.app.Activity; import android.app.ProgressDialog; -import android.content.Context; import android.content.res.Resources; import java.io.File; @@ -53,7 +52,6 @@ public class DatabaseBackupUtils { } public static boolean createBackup(final Activity activity, final Runnable runAfterwards) { - final Context context = activity; // avoid overwriting an existing backup with an empty database // (can happen directly after reinstalling the app) if (DataStore.getAllCachesCount() == 0) { @@ -61,9 +59,9 @@ public class DatabaseBackupUtils { return false; } - final ProgressDialog dialog = ProgressDialog.show(context, - context.getString(R.string.init_backup), - context.getString(R.string.init_backup_running), true, false); + final ProgressDialog dialog = ProgressDialog.show(activity, + activity.getString(R.string.init_backup), + activity.getString(R.string.init_backup_running), true, false); new Thread() { @Override public void run() { @@ -75,9 +73,9 @@ public class DatabaseBackupUtils { Dialogs.message(activity, R.string.init_backup_backup, backupFileName != null - ? context.getString(R.string.init_backup_success) + ? activity.getString(R.string.init_backup_success) + "\n" + backupFileName - : context.getString(R.string.init_backup_failed)); + : activity.getString(R.string.init_backup_failed)); if (runAfterwards != null) { runAfterwards.run(); } diff --git a/main/src/cgeo/geocaching/utils/DebugUtils.java b/main/src/cgeo/geocaching/utils/DebugUtils.java new file mode 100644 index 0000000..07aac64 --- /dev/null +++ b/main/src/cgeo/geocaching/utils/DebugUtils.java @@ -0,0 +1,37 @@ +package cgeo.geocaching.utils; + +import cgeo.geocaching.R; + +import org.eclipse.jdt.annotation.NonNull; + +import android.content.Context; +import android.os.Environment; +import android.widget.Toast; + +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +public class DebugUtils { + + private DebugUtils() { + // utility class + } + + public static void createMemoryDump(final @NonNull Context context) { + try { + final Date now = new Date(); + final SimpleDateFormat fileNameDateFormat = new SimpleDateFormat("yyyy-MM-dd_hh-mm", Locale.US); + File file = FileUtils.getUniqueNamedFile(Environment.getExternalStorageDirectory().getPath() + + File.separatorChar + "cgeo_dump_" + fileNameDateFormat.format(now) + ".hprof"); + android.os.Debug.dumpHprofData(file.getPath()); + Toast.makeText(context, context.getString(R.string.init_memory_dumped, file.getAbsolutePath()), + Toast.LENGTH_LONG).show(); + ShareUtils.share(context, file, R.string.init_memory_dump); + } catch (IOException e) { + Log.e("createMemoryDump", e); + } + } +} diff --git a/main/src/cgeo/geocaching/utils/LogTemplateProvider.java b/main/src/cgeo/geocaching/utils/LogTemplateProvider.java index 5fa0982..3507116 100644 --- a/main/src/cgeo/geocaching/utils/LogTemplateProvider.java +++ b/main/src/cgeo/geocaching/utils/LogTemplateProvider.java @@ -36,9 +36,9 @@ public final class LogTemplateProvider { private Geocache cache; private Trackable trackable; private boolean offline = false; - private LogEntry logEntry; + private final LogEntry logEntry; - public LogContext(final Geocache cache, LogEntry logEntry) { + public LogContext(final Geocache cache, final LogEntry logEntry) { this(cache, logEntry, false); } @@ -47,7 +47,7 @@ public final class LogTemplateProvider { this.logEntry = logEntry; } - public LogContext(final Geocache cache, LogEntry logEntry, final boolean offline) { + public LogContext(final Geocache cache, final LogEntry logEntry, final boolean offline) { this.cache = cache; this.offline = offline; this.logEntry = logEntry; @@ -104,8 +104,11 @@ public final class LogTemplateProvider { } } - public static ArrayList<LogTemplate> getTemplates() { - ArrayList<LogTemplate> templates = new ArrayList<LogTemplateProvider.LogTemplate>(); + /** + * @return all templates, but not the signature template itself + */ + public static ArrayList<LogTemplate> getTemplatesWithoutSignature() { + final ArrayList<LogTemplate> templates = new ArrayList<LogTemplateProvider.LogTemplate>(); templates.add(new LogTemplate("DATE", R.string.init_signature_template_date) { @Override @@ -132,6 +135,13 @@ public final class LogTemplateProvider { @Override public String getValue(final LogContext context) { + final Geocache cache = context.getCache(); + if (cache != null) { + final IConnector connector = ConnectorFactory.getConnector(cache); + if (connector instanceof ILogin) { + return ((ILogin) connector).getUserName(); + } + } return Settings.getUsername(); } }); @@ -171,11 +181,11 @@ public final class LogTemplateProvider { @Override public String getValue(final LogContext context) { - Trackable trackable = context.getTrackable(); + final Trackable trackable = context.getTrackable(); if (trackable != null) { return trackable.getOwner(); } - Geocache cache = context.getCache(); + final Geocache cache = context.getCache(); if (cache != null) { return cache.getOwnerDisplayName(); } @@ -184,12 +194,12 @@ public final class LogTemplateProvider { }); templates.add(new LogTemplate("NAME", R.string.init_signature_template_name) { @Override - public String getValue(LogContext context) { - Trackable trackable = context.getTrackable(); + public String getValue(final LogContext context) { + final Trackable trackable = context.getTrackable(); if (trackable != null) { return trackable.getName(); } - Geocache cache = context.getCache(); + final Geocache cache = context.getCache(); if (cache != null) { return cache.getName(); } @@ -199,12 +209,12 @@ public final class LogTemplateProvider { templates.add(new LogTemplate("URL", R.string.init_signature_template_url) { @Override - public String getValue(LogContext context) { - Trackable trackable = context.getTrackable(); + public String getValue(final LogContext context) { + final Trackable trackable = context.getTrackable(); if (trackable != null) { return trackable.getUrl(); } - Geocache cache = context.getCache(); + final Geocache cache = context.getCache(); if (cache != null) { return cache.getUrl(); } @@ -213,8 +223,8 @@ public final class LogTemplateProvider { }); templates.add(new LogTemplate("LOG", R.string.init_signature_template_log) { @Override - public String getValue(LogContext context) { - LogEntry logEntry = context.getLogEntry(); + public String getValue(final LogContext context) { + final LogEntry logEntry = context.getLogEntry(); if (logEntry != null) { return logEntry.getDisplayText(); } @@ -224,8 +234,26 @@ public final class LogTemplateProvider { return templates; } + /** + * @return all templates, including the signature template + */ + public static ArrayList<LogTemplate> getTemplatesWithSignature() { + final ArrayList<LogTemplate> templates = getTemplatesWithoutSignature(); + templates.add(new LogTemplate("SIGNATURE", R.string.init_signature) { + @Override + public String getValue(final LogContext context) { + final String nestedTemplate = StringUtils.defaultString(Settings.getSignature()); + if (StringUtils.contains(nestedTemplate, "SIGNATURE")) { + return "invalid signature template"; + } + return LogTemplateProvider.applyTemplates(nestedTemplate, context); + } + }); + return templates; + } + public static LogTemplate getTemplate(final int itemId) { - for (LogTemplate template : getTemplates()) { + for (final LogTemplate template : getTemplatesWithSignature()) { if (template.getItemId() == itemId) { return template; } @@ -238,7 +266,7 @@ public final class LogTemplateProvider { return StringUtils.EMPTY; } String result = signature; - for (LogTemplate template : getTemplates()) { + for (final LogTemplate template : getTemplatesWithSignature()) { result = template.apply(result, context); } return result; diff --git a/main/src/cgeo/geocaching/utils/RxUtils.java b/main/src/cgeo/geocaching/utils/RxUtils.java index 8e7864c..a5cdc5f 100644 --- a/main/src/cgeo/geocaching/utils/RxUtils.java +++ b/main/src/cgeo/geocaching/utils/RxUtils.java @@ -1,18 +1,23 @@ package cgeo.geocaching.utils; +import rx.Observable; import rx.Scheduler; +import rx.observables.BlockingObservable; import rx.schedulers.Schedulers; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - public class RxUtils { // Utility class, not to be instanciated private RxUtils() {} - final static private int cores = Runtime.getRuntime().availableProcessors(); - public final static Scheduler computationScheduler = Schedulers.executor(new ThreadPoolExecutor(1, cores, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>())); + public final static Scheduler computationScheduler = Schedulers.computation(); + + public static <T> void waitForCompletion(final BlockingObservable<T> observable) { + observable.lastOrDefault(null); + return; + } + public static void waitForCompletion(final Observable<?>... observables) { + waitForCompletion(Observable.merge(observables).toBlockingObservable()); + } } diff --git a/main/src/cgeo/geocaching/utils/ShareUtils.java b/main/src/cgeo/geocaching/utils/ShareUtils.java new file mode 100644 index 0000000..bfd6838 --- /dev/null +++ b/main/src/cgeo/geocaching/utils/ShareUtils.java @@ -0,0 +1,27 @@ +package cgeo.geocaching.utils; + +import org.eclipse.jdt.annotation.NonNull; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; + +import java.io.File; + +public class ShareUtils { + private ShareUtils() { + // utility class + } + + public static void share(final Context context, final @NonNull File file, final @NonNull String mimeType, final int titleResourceId) { + final Intent shareIntent = new Intent(); + shareIntent.setAction(Intent.ACTION_SEND); + shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file)); + shareIntent.setType(mimeType); + context.startActivity(Intent.createChooser(shareIntent, context.getString(titleResourceId))); + } + + public static void share(final Context context, final @NonNull File file, final int titleResourceId) { + share(context, file, "*/*", titleResourceId); + } +} diff --git a/main/src/cgeo/geocaching/utils/TranslationUtils.java b/main/src/cgeo/geocaching/utils/TranslationUtils.java index 619db08..ea3c395 100644 --- a/main/src/cgeo/geocaching/utils/TranslationUtils.java +++ b/main/src/cgeo/geocaching/utils/TranslationUtils.java @@ -1,10 +1,10 @@ package cgeo.geocaching.utils; -import cgeo.geocaching.activity.AbstractActivity; import cgeo.geocaching.network.Network; import org.apache.commons.lang3.StringUtils; +import android.app.Activity; import android.content.Intent; import android.net.Uri; @@ -51,7 +51,7 @@ public final class TranslationUtils { * @param text * The text to be translated */ - public static void startActivityTranslate(final AbstractActivity context, final String toLang, final String text) { + public static void startActivityTranslate(final Activity context, final String toLang, final String text) { context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(buildTranslationURI(toLang, text)))); } } |
