diff options
author | Samuel Tardieu <sam@rfc1149.net> | 2014-08-03 19:59:24 +0200 |
---|---|---|
committer | Samuel Tardieu <sam@rfc1149.net> | 2014-08-03 19:59:24 +0200 |
commit | 33b1036b2cad0456cb397c4027dbd05f7f95e3d1 (patch) | |
tree | 7da9655f6c89db624b902bdd36cedffe13bd73d8 /main/src/cgeo/geocaching | |
parent | b0c2dec591b72cb30b2505c26efe46924ee0de4a (diff) | |
parent | 2cd573148aba544b202bc046851e79324b39f494 (diff) | |
download | cgeo-33b1036b2cad0456cb397c4027dbd05f7f95e3d1.zip cgeo-33b1036b2cad0456cb397c4027dbd05f7f95e3d1.tar.gz cgeo-33b1036b2cad0456cb397c4027dbd05f7f95e3d1.tar.bz2 |
Merge commit '2cd573148aba544b202bc046851e79324b39f494' into release
Diffstat (limited to 'main/src/cgeo/geocaching')
206 files changed, 6084 insertions, 4452 deletions
diff --git a/main/src/cgeo/geocaching/AboutActivity.java b/main/src/cgeo/geocaching/AboutActivity.java index 8d3d978..f6d204b 100644 --- a/main/src/cgeo/geocaching/AboutActivity.java +++ b/main/src/cgeo/geocaching/AboutActivity.java @@ -14,10 +14,12 @@ 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; import android.view.View; +import android.view.ViewGroup; import android.widget.ScrollView; import android.widget.TextView; @@ -28,14 +30,16 @@ 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; @InjectView(R.id.license_text) protected TextView licenseText; @Override - public ScrollView getDispatchedView() { - final ScrollView view = (ScrollView) getLayoutInflater().inflate(R.layout.about_license_page, null); + public ScrollView getDispatchedView(final ViewGroup parentView) { + final ScrollView view = (ScrollView) getLayoutInflater().inflate(R.layout.about_license_page, parentView, false); ButterKnife.inject(this, view); setClickListener(licenseLink, "http://www.apache.org/licenses/LICENSE-2.0.html"); licenseText.setText(getRawResourceString(R.raw.license)); @@ -48,8 +52,8 @@ public class AboutActivity extends AbstractViewPagerActivity<AboutActivity.Page> @InjectView(R.id.contributors) protected TextView contributors; @Override - public ScrollView getDispatchedView() { - final ScrollView view = (ScrollView) getLayoutInflater().inflate(R.layout.about_contributors_page, null); + public ScrollView getDispatchedView(final ViewGroup parentView) { + final ScrollView view = (ScrollView) getLayoutInflater().inflate(R.layout.about_contributors_page, parentView, false); ButterKnife.inject(this, view); contributors.setMovementMethod(AnchorAwareLinkMovementMethod.getInstance()); return view; @@ -63,8 +67,8 @@ public class AboutActivity extends AbstractViewPagerActivity<AboutActivity.Page> @InjectView(R.id.changelog_release) protected TextView changeLogRelease; @Override - public ScrollView getDispatchedView() { - final ScrollView view = (ScrollView) getLayoutInflater().inflate(R.layout.about_changes_page, null); + public ScrollView getDispatchedView(final ViewGroup parentView) { + final ScrollView view = (ScrollView) getLayoutInflater().inflate(R.layout.about_changes_page, parentView, false); ButterKnife.inject(this, view); changeLogRelease.setMovementMethod(AnchorAwareLinkMovementMethod.getInstance()); final String changeLogMasterString = getString(R.string.changelog_master); @@ -84,19 +88,17 @@ 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; @Override - public ScrollView getDispatchedView() { - final ScrollView view = (ScrollView) getLayoutInflater().inflate(R.layout.about_help_page, null); + public ScrollView getDispatchedView(final ViewGroup parentView) { + final ScrollView view = (ScrollView) getLayoutInflater().inflate(R.layout.about_help_page, parentView, false); ButterKnife.inject(this, view); setClickListener(support, "mailto:support@cgeo.org?subject=" + Uri.encode("cgeo " + Version.getVersionName(AboutActivity.this))); 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() { @@ -116,8 +118,8 @@ public class AboutActivity extends AbstractViewPagerActivity<AboutActivity.Page> @InjectView(R.id.donate) protected TextView donateButton; @Override - public ScrollView getDispatchedView() { - final ScrollView view = (ScrollView) getLayoutInflater().inflate(R.layout.about_version_page, null); + public ScrollView getDispatchedView(final ViewGroup parentView) { + final ScrollView view = (ScrollView) getLayoutInflater().inflate(R.layout.about_version_page, parentView, false); ButterKnife.inject(this, view); version.setText(Version.getVersionName(AboutActivity.this)); setClickListener(donateButton, "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=AQBS7UP76CXW2"); @@ -142,7 +144,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(); + final Bundle extras = getIntent().getExtras(); + if (extras != null) { + startPage = extras.getInt(EXTRA_ABOUT_STARTPAGE, startPage); + } + createViewPager(startPage, null); reinitializeViewPager(); } @@ -210,4 +218,10 @@ public class AboutActivity extends AbstractViewPagerActivity<AboutActivity.Page> return result; } + public static void showChangeLog(final 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..4025347 --- /dev/null +++ b/main/src/cgeo/geocaching/AbstractDialogFragment.java @@ -0,0 +1,356 @@ +package cgeo.geocaching; + +import butterknife.ButterKnife; + +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 cgeo.geocaching.utils.RxUtils; + +import rx.Observable; +import rx.Subscription; +import rx.android.observables.AndroidObservable; +import rx.functions.Action1; +import rx.functions.Func0; +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(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + res = getResources(); + app = (CgeoApplication) getActivity().getApplication(); + setHasOptionsMenu(true); + } + + protected void initCustomActionBar(final View v) + { + final ImageView defaultNavigationImageView = ButterKnife.findById(v, R.id.defaultNavigation); + defaultNavigationImageView.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(final View v) { + startDefaultNavigation2(); + return true; + } + }); + defaultNavigationImageView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(final View v) { + navigateTo(); + } + }); + + final View overflowActionBar = v.findViewById(R.id.overflowActionBar); + overflowActionBar.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(final 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(final 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(final View view) { + final 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(final MenuItem item) { + return AbstractDialogFragment.this.onMenuItemClick(item); + } + } + ); + popupMenu.show(); + } + + protected void showPopupCompat(final View view) + { + final 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(); + } + })).subscribeOn(RxUtils.networkScheduler).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); + } + }); + } + + 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(final View arg0) { + CacheDetailActivity.startActivity(getActivity(), geocode); + getActivity().finish(); + } + }); + + /* Only working combination as it seems */ + registerForContextMenu(buttonMore); + } + + public final void showToast(final 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(final Menu menu, final MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + CacheMenuHandler.addMenuItems(inflater, menu, cache); + + } + + @Override + public void onCreateContextMenu(final ContextMenu menu, final View v, final ContextMenu.ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + CacheMenuHandler.addMenuItems(new MenuInflater(getActivity()), menu, cache); + for (int i=0;i<menu.size();i++) { + final MenuItem m = menu.getItem(i); + m.setOnMenuItemClickListener(this); + } + } + + @Override + public boolean onContextItemSelected(final MenuItem item) { + return onOptionsItemSelected(item); + } + + + @Override + public boolean onMenuItemClick(final MenuItem menuItem) { + return onOptionsItemSelected(menuItem); + } + + @Override + public boolean onOptionsItemSelected(final 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(final 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(final DialogInterface dialog) { + super.onCancel(dialog); + getActivity().finish(); + } + +} diff --git a/main/src/cgeo/geocaching/AbstractLoggingActivity.java b/main/src/cgeo/geocaching/AbstractLoggingActivity.java index c3ba7d2..90fda53 100644 --- a/main/src/cgeo/geocaching/AbstractLoggingActivity.java +++ b/main/src/cgeo/geocaching/AbstractLoggingActivity.java @@ -1,32 +1,34 @@ 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 { + + /** + * sub classes can disable the send button + */ + private boolean enableSend = true; @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 +41,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())) { @@ -54,19 +53,15 @@ public abstract class AbstractLoggingActivity extends AbstractActivity { } menu.findItem(R.id.menu_smilies).setVisible(smileyVisible); + menu.findItem(R.id.menu_send).setVisible(enableSend); return true; } @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,13 +74,18 @@ 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 final void insertIntoLog(final String newText, final boolean moveCursor) { final EditText log = (EditText) findViewById(R.id.log); ActivityMixin.insertAtPosition(log, newText, moveCursor); } + + protected final void setLoggingEnabled(final boolean enabled) { + enableSend = enabled; + invalidateOptionsMenuCompatible(); + } } 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/CacheCache.java b/main/src/cgeo/geocaching/CacheCache.java index cb4e14c..1913d3c 100644 --- a/main/src/cgeo/geocaching/CacheCache.java +++ b/main/src/cgeo/geocaching/CacheCache.java @@ -23,7 +23,7 @@ public class CacheCache { final private LeastRecentlyUsedMap<String, Geocache> cachesCache; public CacheCache() { - cachesCache = new LeastRecentlyUsedMap.LruCache<String, Geocache>(MAX_CACHED_CACHES); + cachesCache = new LeastRecentlyUsedMap.LruCache<>(MAX_CACHED_CACHES); cachesCache.setRemoveHandler(new CacheRemoveHandler()); } @@ -79,7 +79,7 @@ public class CacheCache { } public synchronized Set<String> getInViewport(final Viewport viewport, final CacheType cacheType) { - final Set<String> geocodes = new HashSet<String>(); + final Set<String> geocodes = new HashSet<>(); for (final Geocache cache : cachesCache.values()) { if (cache.getCoords() == null) { // FIXME: this kludge must be removed, it is only present to help us debug the cases where diff --git a/main/src/cgeo/geocaching/CacheDetailActivity.java b/main/src/cgeo/geocaching/CacheDetailActivity.java index a66d181..bb1f154 100644 --- a/main/src/cgeo/geocaching/CacheDetailActivity.java +++ b/main/src/cgeo/geocaching/CacheDetailActivity.java @@ -32,8 +32,6 @@ import cgeo.geocaching.ui.CoordinatesFormatSwitcher; import cgeo.geocaching.ui.DecryptTextClickListener; import cgeo.geocaching.ui.EditNoteDialog; import cgeo.geocaching.ui.EditNoteDialog.EditNoteDialogListener; -import cgeo.geocaching.ui.Formatter; -import cgeo.geocaching.ui.HtmlImageCounter; import cgeo.geocaching.ui.ImagesList; import cgeo.geocaching.ui.IndexOutOfBoundsAvoidingTextView; import cgeo.geocaching.ui.LoggingUI; @@ -43,9 +41,11 @@ import cgeo.geocaching.ui.dialog.Dialogs; import cgeo.geocaching.ui.logs.CacheLogsViewCreator; import cgeo.geocaching.utils.CancellableHandler; import cgeo.geocaching.utils.CryptUtils; +import cgeo.geocaching.utils.Formatter; 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; @@ -56,15 +56,14 @@ import org.apache.commons.lang3.StringEscapeUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; +import org.eclipse.jdt.annotation.Nullable; 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; import android.R.color; @@ -84,6 +83,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,14 +165,11 @@ 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(); - // set title in code, as the activity needs a hard coded title due to the intent filters - setTitle(res.getString(R.string.cache)); - // get parameters final Bundle extras = getIntent().getExtras(); final Uri uri = getIntent().getData(); @@ -258,6 +255,10 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc return; } + // if we open this cache from a search, let's properly initialize the title bar, even if we don't have cache details + cache = DataStore.loadCache(geocode, LoadFlags.LOAD_CACHE_ONLY); + updateTitleBar(geocode); + final LoadCacheHandler loadCacheHandler = new LoadCacheHandler(this, progress); try { @@ -272,22 +273,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 +292,9 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc final String realGeocode = geocode; final String realGuid = guid; - Schedulers.io().schedule(new Action1<Inner>() { + RxUtils.networkScheduler.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 +334,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,27 +363,27 @@ 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)); + DataStore.saveCache(cache, EnumSet.of(SaveFlag.DB)); notifyDataSetChanged(); } return true; case R.id.menu_waypoint_delete: + ensureSaved(); if (cache.deleteWaypoint(selectedWaypoint)) { - DataStore.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); + DataStore.saveCache(cache, EnumSet.of(SaveFlag.DB)); notifyDataSetChanged(); } return true; @@ -454,10 +403,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 +425,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 +449,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,14 +468,14 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } } - return true; + return super.onOptionsItemSelected(item); } private static final class CacheDetailsGeoDirHandler extends GeoDirHandler { private final WeakReference<CacheDetailActivity> activityRef; public CacheDetailsGeoDirHandler(final CacheDetailActivity activity) { - this.activityRef = new WeakReference<CacheDetailActivity>(activity); + this.activityRef = new WeakReference<>(activity); } @Override @@ -539,7 +497,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 +532,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; } @@ -591,6 +549,11 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } private void notifyDataSetChanged() { + // This might get called asynchronically when the activity is shut down + if (isFinishing()) { + return; + } + if (search == null) { return; } @@ -607,13 +570,17 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc // allow cache to notify CacheDetailActivity when it changes so it can be reloaded cache.setChangeNotificationHandler(new ChangeNotificationHandler(this, progress)); - // action bar: title and icon - if (StringUtils.isNotBlank(cache.getName())) { - setTitle(cache.getName() + " (" + cache.getGeocode() + ')'); - } else { - setTitle(cache.getGeocode()); - } - ((TextView) findViewById(R.id.actionbar_title)).setCompoundDrawablesWithIntrinsicBounds(getResources().getDrawable(cache.getType().markerId), null, null, null); + updateTitleBar(null); + + // 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,33 +589,44 @@ 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. - */ - private void startDefaultNavigation() { - NavigationAppFactory.startDefaultNavigationApplication(1, this, cache); + private void updateTitleBar(@Nullable final String geocode) { + if (cache == null) { + setTitle(StringUtils.isBlank(geocode) ? res.getString(R.string.cache) : geocode); + // avoid showing the traditional cache icon from the standard action bar (it may later change to the actual type icon) + getSupportActionBar().setIcon(android.R.color.transparent); + } + else { + if (StringUtils.isNotBlank(cache.getName())) { + setTitle(cache.getName() + " (" + cache.getGeocode() + ')'); + } else { + setTitle(cache.getGeocode()); + } + getSupportActionBar().setIcon(getResources().getDrawable(cache.getType().markerId)); + } } /** * Tries to navigate to the {@link Geocache} of this activity. */ - private void startDefaultNavigation2() { - NavigationAppFactory.startDefaultNavigationApplication(2, this, cache); + private void startDefaultNavigation() { + NavigationAppFactory.startDefaultNavigationApplication(1, 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); } @@ -660,7 +638,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc if (creator == null) { return; } - final View imageView = creator.getView(); + final View imageView = creator.getView(null); if (imageView == null) { return; } @@ -681,7 +659,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 +701,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 +726,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,9 +744,9 @@ 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(); + attributeDescriptionsLayout = createAttributeDescriptionsLayout(attribBox); } attribBox.removeAllViews(); attribBox.addView(attributeDescriptionsLayout); @@ -778,7 +756,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 +770,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); @@ -806,7 +784,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc // check if another attribute icon fits in this row attributeRow.measure(0, 0); final int rowWidth = attributeRow.getMeasuredWidth(); - final FrameLayout fl = (FrameLayout) getLayoutInflater().inflate(R.layout.attribute_image, null); + final FrameLayout fl = (FrameLayout) getLayoutInflater().inflate(R.layout.attribute_image, attributeRow, false); final ImageView iv = (ImageView) fl.getChildAt(0); if ((parentWidth - rowWidth) < iv.getLayoutParams().width) { // make a new row @@ -814,20 +792,20 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc rows.addView(attributeRow); } - final boolean strikethru = !CacheAttribute.isEnabled(attributeName); + final boolean strikeThrough = !CacheAttribute.isEnabled(attributeName); final CacheAttribute attrib = CacheAttribute.getByRawName(CacheAttribute.trimAttributeName(attributeName)); if (attrib != null) { noAttributeIconsFound = false; Drawable d = res.getDrawable(attrib.drawableId); iv.setImageDrawable(d); // strike through? - if (strikethru) { - // generate strikethru image with same properties as attribute image - final ImageView strikethruImage = new ImageView(CacheDetailActivity.this); - strikethruImage.setLayoutParams(iv.getLayoutParams()); + if (strikeThrough) { + // generate strike through image with same properties as attribute image + final ImageView strikeThroughImage = new ImageView(CacheDetailActivity.this); + strikeThroughImage.setLayoutParams(iv.getLayoutParams()); d = res.getDrawable(R.drawable.attribute__strikethru); - strikethruImage.setImageDrawable(d); - fl.addView(strikethruImage); + strikeThroughImage.setImageDrawable(d); + fl.addView(strikeThroughImage); } } else { final Drawable d = res.getDrawable(R.drawable.attribute_unknown); @@ -848,16 +826,16 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc return rowLayout; } - private ViewGroup createAttributeDescriptionsLayout() { + private ViewGroup createAttributeDescriptionsLayout(final LinearLayout parentView) { final LinearLayout descriptions = (LinearLayout) getLayoutInflater().inflate( - R.layout.attribute_descriptions, null); + R.layout.attribute_descriptions, parentView, false); final TextView attribView = (TextView) descriptions.getChildAt(0); final StringBuilder buffer = new StringBuilder(); 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 +851,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(this, progress); + + progress.show(this, res.getString(R.string.cache_dialog_refresh_title), res.getString(R.string.cache_dialog_refresh_message), true, refreshCacheHandler.cancelMessage()); + + cache.refresh(refreshCacheHandler, RxUtils.networkScheduler); + } + + private void dropCache() { + if (progress.isShowing()) { + showToast(res.getString(R.string.err_detail_still_working)); + return; + } + + progress.show(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(this, progress), RxUtils.networkScheduler); + } + + 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(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(this, progress)); + } + } + /** * Creator for details-view. */ @@ -885,32 +911,33 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc private Thread watchlistThread; @Override - public ScrollView getDispatchedView() { + public ScrollView getDispatchedView(final ViewGroup parentView) { if (cache == null) { // something is really wrong return null; } - view = (ScrollView) getLayoutInflater().inflate(R.layout.cachedetail_details_page, null); + view = (ScrollView) getLayoutInflater().inflate(R.layout.cachedetail_details_page, parentView, false); // Start loading preview map - AndroidObservable.bindActivity(CacheDetailActivity.this, previewMap).subscribe(new Action1<BitmapDrawable>() { - @Override - public void call(final BitmapDrawable image) { - final Bitmap bitmap = image.getBitmap(); - if (bitmap != null && bitmap.getWidth() > 10) { - final ImageView imageView = (ImageView) view.findViewById(R.id.map_preview); - imageView.setImageDrawable(image); - view.findViewById(R.id.map_preview_box).setVisibility(View.VISIBLE); - } - } - }, Schedulers.io()); + AndroidObservable.bindActivity(CacheDetailActivity.this, previewMap).subscribeOn(RxUtils.networkScheduler) + .subscribe(new Action1<BitmapDrawable>() { + @Override + public void call(final BitmapDrawable image) { + final Bitmap bitmap = image.getBitmap(); + if (bitmap != null && bitmap.getWidth() > 10) { + final ImageView imageView = ButterKnife.findById(view, R.id.map_preview); + imageView.setImageDrawable(image); + view.findViewById(R.id.map_preview_box).setVisibility(View.VISIBLE); + } + } + }); - detailsList = (LinearLayout) view.findViewById(R.id.details_list); + detailsList = ButterKnife.findById(view, R.id.details_list); final CacheDetailsCreator details = new CacheDetailsCreator(CacheDetailActivity.this, detailsList); // cache name (full name) - final Spannable span = (new Spannable.Factory()).newSpannable(Html.fromHtml(cache.getName()).toString()); + final Spannable span = (new Spannable.Factory()).newSpannable(cache.getName()); if (cache.isDisabled() || cache.isArchived()) { // strike span.setSpan(new StrikethroughSpan(), 0, span.toString().length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } @@ -918,10 +945,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 +982,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,33 +994,34 @@ 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 if (!cache.getAttributes().isEmpty()) { - new AttributeViewBuilder().fillView((LinearLayout) view.findViewById(R.id.attributes_innerbox)); + final LinearLayout innerLayout = ButterKnife.findById(view, R.id.attributes_innerbox); + new AttributeViewBuilder().fillView(innerLayout); view.findViewById(R.id.attributes_box).setVisibility(View.VISIBLE); } updateOfflineBox(view, cache, res, new RefreshCacheClickListener(), new DropCacheClickListener(), new StoreCacheClickListener()); // watchlist - final Button buttonWatchlistAdd = (Button) view.findViewById(R.id.add_to_watchlist); - final Button buttonWatchlistRemove = (Button) view.findViewById(R.id.remove_from_watchlist); + final Button buttonWatchlistAdd = ButterKnife.findById(view, R.id.add_to_watchlist); + final Button buttonWatchlistRemove = ButterKnife.findById(view, R.id.remove_from_watchlist); buttonWatchlistAdd.setOnClickListener(new AddToWatchlistClickListener()); buttonWatchlistRemove.setOnClickListener(new RemoveFromWatchlistClickListener()); updateWatchlistBox(); // favorite points - final Button buttonFavPointAdd = (Button) view.findViewById(R.id.add_to_favpoint); - final Button buttonFavPointRemove = (Button) view.findViewById(R.id.remove_from_favpoint); + final Button buttonFavPointAdd = ButterKnife.findById(view, R.id.add_to_favpoint); + final Button buttonFavPointRemove = ButterKnife.findById(view, R.id.remove_from_favpoint); buttonFavPointAdd.setOnClickListener(new FavoriteAddClickListener()); buttonFavPointRemove.setOnClickListener(new FavoriteRemoveClickListener()); updateFavPointBox(); // list - final Button buttonChangeList = (Button) view.findViewById(R.id.change_list); + final Button buttonChangeList = ButterKnife.findById(view, R.id.change_list); buttonChangeList.setOnClickListener(new ChangeListClickListener()); updateListBox(); @@ -1002,7 +1030,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc final String license = connector.getLicenseText(cache); if (StringUtils.isNotBlank(license)) { view.findViewById(R.id.license_box).setVisibility(View.VISIBLE); - final TextView licenseView = ((TextView) view.findViewById(R.id.license)); + final TextView licenseView = (ButterKnife.findById(view, R.id.license)); licenseView.setText(Html.fromHtml(license), BufferType.SPANNABLE); licenseView.setClickable(true); licenseView.setMovementMethod(AnchorAwareLinkMovementMethod.getInstance()); @@ -1015,59 +1043,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 +1067,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 +1088,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 +1100,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 +1111,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 +1123,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 +1135,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 +1147,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 +1159,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 +1171,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 +1183,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 +1195,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 +1208,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 +1220,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 +1232,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 +1249,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; } @@ -1271,15 +1263,15 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc * shows/hides buttons, sets text in watchlist box */ private void updateWatchlistBox() { - final LinearLayout layout = (LinearLayout) view.findViewById(R.id.watchlist_box); + final LinearLayout layout = ButterKnife.findById(view, R.id.watchlist_box); final boolean supportsWatchList = cache.supportsWatchList(); layout.setVisibility(supportsWatchList ? View.VISIBLE : View.GONE); if (!supportsWatchList) { return; } - final Button buttonAdd = (Button) view.findViewById(R.id.add_to_watchlist); - final Button buttonRemove = (Button) view.findViewById(R.id.remove_from_watchlist); - final TextView text = (TextView) view.findViewById(R.id.watchlist_text); + final Button buttonAdd = ButterKnife.findById(view, R.id.add_to_watchlist); + final Button buttonRemove = ButterKnife.findById(view, R.id.remove_from_watchlist); + final TextView text = ButterKnife.findById(view, R.id.watchlist_text); if (cache.isOnWatchlist() || cache.isOwner()) { buttonAdd.setVisibility(View.GONE); @@ -1305,15 +1297,15 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc * shows/hides buttons, sets text in watchlist box */ private void updateFavPointBox() { - final LinearLayout layout = (LinearLayout) view.findViewById(R.id.favpoint_box); + final LinearLayout layout = ButterKnife.findById(view, R.id.favpoint_box); final boolean supportsFavoritePoints = cache.supportsFavoritePoints(); layout.setVisibility(supportsFavoritePoints ? View.VISIBLE : View.GONE); if (!supportsFavoritePoints || cache.isOwner() || !Settings.isGCPremiumMember()) { return; } - final Button buttonAdd = (Button) view.findViewById(R.id.add_to_favpoint); - final Button buttonRemove = (Button) view.findViewById(R.id.remove_from_favpoint); - final TextView text = (TextView) view.findViewById(R.id.favpoint_text); + final Button buttonAdd = ButterKnife.findById(view, R.id.add_to_favpoint); + final Button buttonRemove = ButterKnife.findById(view, R.id.remove_from_favpoint); + final TextView text = ButterKnife.findById(view, R.id.favpoint_text); if (cache.isFavorite()) { buttonAdd.setVisibility(View.GONE); @@ -1345,7 +1337,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc box.setVisibility(View.VISIBLE); // update text - final TextView text = (TextView) view.findViewById(R.id.list_text); + final TextView text = ButterKnife.findById(view, R.id.list_text); final StoredList list = DataStore.getList(cache.getListId()); if (list != null) { text.setText(res.getString(R.string.cache_list_text) + " " + list.title); @@ -1360,7 +1352,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 +1361,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); } } @@ -1395,13 +1387,13 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc @InjectView(R.id.loading) protected View loadingView; @Override - public ScrollView getDispatchedView() { + public ScrollView getDispatchedView(final ViewGroup parentView) { if (cache == null) { // something is really wrong return null; } - view = (ScrollView) getLayoutInflater().inflate(R.layout.cachedetail_description_page, null); + view = (ScrollView) getLayoutInflater().inflate(R.layout.cachedetail_description_page, parentView, false); ButterKnife.inject(this, view); // cache short description @@ -1417,7 +1409,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,24 +1419,21 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc // cache personal note setPersonalNote(personalNoteView, cache.getPersonalNote()); personalNoteView.setMovementMethod(AnchorAwareLinkMovementMethod.getInstance()); - registerForContextMenu(personalNoteView); - final Button personalNoteEdit = (Button) view.findViewById(R.id.edit_personalnote); + addContextMenu(personalNoteView); + final Button personalNoteEdit = ButterKnife.findById(view, 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); + final Button personalNoteUpload = ButterKnife.findById(view, R.id.upload_personalnote); if (cache.isOffline() && ConnectorFactory.getConnector(cache).supportsPersonalNote()) { 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 { @@ -1464,7 +1453,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc hintBoxView.setVisibility(View.GONE); } - final TextView hintView = ((TextView) view.findViewById(R.id.hint)); + final TextView hintView = (ButterKnife.findById(view, R.id.hint)); if (StringUtils.isNotBlank(cache.getHint())) { if (TextUtils.containsHtml(cache.getHint())) { hintView.setText(Html.fromHtml(cache.getHint(), new HtmlImage(cache.getGeocode(), false, cache.getListId(), false), null), TextView.BufferType.SPANNABLE); @@ -1478,7 +1467,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); @@ -1487,13 +1476,13 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc hintBoxView.setOnClickListener(null); } - final TextView spoilerlinkView = ((TextView) view.findViewById(R.id.hint_spoilerlink)); + final TextView spoilerlinkView = (ButterKnife.findById(view, R.id.hint_spoilerlink)); if (CollectionUtils.isNotEmpty(cache.getSpoilers())) { spoilerlinkView.setVisibility(View.VISIBLE); 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 +1505,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) { @@ -1533,30 +1522,6 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc final String longDescription = cache.getDescription(); loadDescription(longDescription, longDescView, loadingView); - - // Hide the short description, if it is contained somewhere at the start of the long description. - if (shortDescView != null) { - final String shortDescription = cache.getShortDescription(); - if (StringUtils.isNotBlank(shortDescription)) { - final int index = longDescription.indexOf(shortDescription); - if (index >= 0 && index < 200) { - shortDescView.setVisibility(View.GONE); - } - } - } - } - - private void warnPersonalNoteNeedsStoring() { - 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() { @@ -1564,7 +1529,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc new DialogInterface.OnClickListener() { @Override - public void onClick(DialogInterface dialog, int which) { + public void onClick(final DialogInterface dialog, final int which) { dialog.dismiss(); uploadPersonalNote(); } @@ -1574,141 +1539,130 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } - /** + // If description has an HTML construct which may be problematic to render, add a note at the end of the long description. + // Technically, it may not be a table, but a pre, which has the same problems as a table, so the message is ok even though + // sometimes technically incorrect. + private void addWarning(final UnknownTagsHandler unknownTagsHandler, final Spanned description) { + if (unknownTagsHandler.isProblematicDetected()) { + final int startPos = description.length(); + final IConnector connector = ConnectorFactory.getConnector(cache); + final Spanned tableNote = Html.fromHtml(res.getString(R.string.cache_description_table_note, "<a href=\"" + cache.getUrl() + "\">" + connector.getName() + "</a>")); + ((Editable) description).append("\n\n").append(tableNote); + ((Editable) description).setSpan(new StyleSpan(Typeface.ITALIC), startPos, description.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + /** * Load the description in the background. - * @param descriptionString the HTML description as retrieved from the connector - * @param descriptionView the view to fill - * @param loadingIndicatorView the loading indicator view, will be hidden when completed - */ + * @param descriptionString the HTML description as retrieved from the connector + * @param descriptionView the view to fill + * @param loadingIndicatorView the loading indicator view, will be hidden when completed + */ private void loadDescription(final String descriptionString, final IndexOutOfBoundsAvoidingTextView descriptionView, final View loadingIndicatorView) { - // The producer produces successives (without then with images) versions of the description. - final Observable<Spanned> producer = Observable.create(new OnSubscribe<Spanned>() { - @Override - public void call(final Subscriber<? super Spanned> subscriber) { + try { + final UnknownTagsHandler unknownTagsHandler = new UnknownTagsHandler(); + final Spanned description = Html.fromHtml(descriptionString, new HtmlImage(cache.getGeocode(), true, cache.getListId(), false, descriptionView), unknownTagsHandler); + addWarning(unknownTagsHandler, description); + if (StringUtils.isNotBlank(descriptionString)) { try { - // Fast preview: parse only HTML without loading any images - final HtmlImageCounter imageCounter = new HtmlImageCounter(); - final UnknownTagsHandler unknownTagsHandler = new UnknownTagsHandler(); - Spanned description = Html.fromHtml(descriptionString, imageCounter, unknownTagsHandler); - addWarning(unknownTagsHandler, description); - subscriber.onNext(description); - - if (imageCounter.getImageCount() > 0) { - // Complete view: parse again with loading images - if necessary ! If there are any images causing problems the user can see at least the preview - description = Html.fromHtml(descriptionString, new HtmlImage(cache.getGeocode(), true, cache.getListId(), false), unknownTagsHandler); - addWarning(unknownTagsHandler, description); - subscriber.onNext(description); - } - - subscriber.onCompleted(); + descriptionView.setText(description, TextView.BufferType.SPANNABLE); } catch (final Exception e) { - Log.e("loadDescription", e); - subscriber.onError(e); + // On 4.1, there is sometimes a crash on measuring the layout: https://code.google.com/p/android/issues/detail?id=35412 + Log.e("Android bug setting text: ", e); + // remove the formatting by converting to a simple string + descriptionView.setText(description.toString()); } + descriptionView.setMovementMethod(AnchorAwareLinkMovementMethod.getInstance()); + fixTextColor(descriptionString, descriptionView); + descriptionView.setVisibility(View.VISIBLE); + addContextMenu(descriptionView); + potentiallyHideShortDescription(); } - - // If description has an HTML construct which may be problematic to render, add a note at the end of the long description. - // Technically, it may not be a table, but a pre, which has the same problems as a table, so the message is ok even though - // sometimes technically incorrect. - private void addWarning(final UnknownTagsHandler unknownTagsHandler, final Spanned description) { - if (unknownTagsHandler.isProblematicDetected()) { - final int startPos = description.length(); - final IConnector connector = ConnectorFactory.getConnector(cache); - final Spanned tableNote = Html.fromHtml(res.getString(R.string.cache_description_table_note, "<a href=\"" + cache.getUrl() + "\">" + connector.getName() + "</a>")); - ((Editable) description).append("\n\n").append(tableNote); - ((Editable) description).setSpan(new StyleSpan(Typeface.ITALIC), startPos, description.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } + if (null != loadingIndicatorView) { + loadingIndicatorView.setVisibility(View.GONE); } - }); + } catch (final Exception e) { + showToast(res.getString(R.string.err_load_descr_failed)); + } + } - AndroidObservable.bindActivity(this, producer).subscribe(new Observer<Spanned>() { - @Override - public void onCompleted() { - if (null != loadingIndicatorView) { - loadingIndicatorView.setVisibility(View.GONE); - } - } + private static void fixTextColor(final String descriptionString, final IndexOutOfBoundsAvoidingTextView descriptionView) { + int backcolor; + if (Settings.isLightSkin()) { + backcolor = color.white; - @Override - public void onError(final Throwable throwable) { - showToast(res.getString(R.string.err_load_descr_failed)); + for (final Pattern pattern : LIGHT_COLOR_PATTERNS) { + final MatcherWrapper matcher = new MatcherWrapper(pattern, descriptionString); + if (matcher.find()) { + descriptionView.setBackgroundResource(color.darker_gray); + return; + } } + } else { + backcolor = color.black; - @Override - public void onNext(final Spanned description) { - if (StringUtils.isNotBlank(descriptionString)) { - try { - descriptionView.setText(description, TextView.BufferType.SPANNABLE); - } catch (final Exception e) { - // On 4.1, there is sometimes a crash on measuring the layout: https://code.google.com/p/android/issues/detail?id=35412 - Log.e("Android bug setting text: ", e); - // remove the formatting by converting to a simple string - descriptionView.setText(description.toString()); - } - descriptionView.setMovementMethod(AnchorAwareLinkMovementMethod.getInstance()); - fixTextColor(descriptionString); - descriptionView.setVisibility(View.VISIBLE); - registerForContextMenu(descriptionView); + for (final Pattern pattern : DARK_COLOR_PATTERNS) { + final MatcherWrapper matcher = new MatcherWrapper(pattern, descriptionString); + if (matcher.find()) { + descriptionView.setBackgroundResource(color.darker_gray); + return; } } + } + descriptionView.setBackgroundResource(backcolor); + } - /** - * Handle caches with black font color in dark skin and white font color in light skin - * by changing background color of the view - * - * @param text - * to be checked - */ - private void fixTextColor(final String text) { - int backcolor; - if (Settings.isLightSkin()) { - backcolor = color.white; - - for (final Pattern pattern : LIGHT_COLOR_PATTERNS) { - final MatcherWrapper matcher = new MatcherWrapper(pattern, text); - if (matcher.find()) { - descriptionView.setBackgroundResource(color.darker_gray); - return; - } - } - } else { - backcolor = color.black; - - for (final Pattern pattern : DARK_COLOR_PATTERNS) { - final MatcherWrapper matcher = new MatcherWrapper(pattern, text); - if (matcher.find()) { - descriptionView.setBackgroundResource(color.darker_gray); - return; - } - } - } - descriptionView.setBackgroundResource(backcolor); + /** + * Hide the short description, if it is contained somewhere at the start of the long description. + */ + public void potentiallyHideShortDescription() { + final View shortView = ButterKnife.findById(this, R.id.shortdesc); + if (shortView == null) { + return; + } + if (shortView.getVisibility() == View.GONE) { + return; + } + final String shortDescription = cache.getShortDescription(); + if (StringUtils.isNotBlank(shortDescription)) { + final int index = StringUtils.indexOf(cache.getDescription(), shortDescription); + // allow up to 200 characters of HTML formatting + if (index >= 0 && index < 200) { + shortView.setVisibility(View.GONE); } - }, 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); @Override - public ListView getDispatchedView() { + public ListView getDispatchedView(final ViewGroup parentView) { if (cache == null) { // something is really wrong return null; } // sort waypoints: PP, Sx, FI, OWN - final List<Waypoint> sortedWaypoints = new ArrayList<Waypoint>(cache.getWaypoints()); + final List<Waypoint> sortedWaypoints = new ArrayList<>(cache.getWaypoints()); Collections.sort(sortedWaypoints, Waypoint.WAYPOINT_COMPARATOR); - view = (ListView) getLayoutInflater().inflate(R.layout.cachedetail_waypoints_page, null); + view = (ListView) getLayoutInflater().inflate(R.layout.cachedetail_waypoints_page, parentView, false); view.setClickable(true); - View addWaypointButton = getLayoutInflater().inflate(R.layout.cachedetail_waypoints_footer, null); + final View addWaypointButton = getLayoutInflater().inflate(R.layout.cachedetail_waypoints_footer, view, false); 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,12 +1670,13 @@ 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); + rowView = getLayoutInflater().inflate(R.layout.waypoint_item, parent, false); rowView.setClickable(true); rowView.setLongClickable(true); + registerForContextMenu(rowView); } WaypointViewHolder holder = (WaypointViewHolder) rowView.getTag(); if (null == holder) { @@ -1737,7 +1692,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,33 +1755,34 @@ 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); + ensureSaved(); + EditWaypointActivity.startActivityEditWaypoint(CacheDetailActivity.this, cache, wpt.getId()); + refreshOnResume = true; } }); rowView.setOnLongClickListener(new View.OnLongClickListener() { @Override - public boolean onLongClick(View v) { + public boolean onLongClick(final View v) { selectedWaypoint = wpt; - EditWaypointActivity.startActivityEditWaypoint(CacheDetailActivity.this, cache, wpt.getId()); - refreshOnResume = true; + openContextMenu(v); return true; } }); @@ -1836,7 +1792,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); @@ -1852,20 +1808,20 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc private class InventoryViewCreator extends AbstractCachingPageViewCreator<ListView> { @Override - public ListView getDispatchedView() { + public ListView getDispatchedView(final ViewGroup parentView) { if (cache == null) { // something is really wrong return null; } - view = (ListView) getLayoutInflater().inflate(R.layout.cachedetail_inventory_page, null); + view = (ListView) getLayoutInflater().inflate(R.layout.cachedetail_inventory_page, parentView, false); // TODO: fix layout, then switch back to Android-resource and delete copied one // this copy is modified to respect the text color - view.setAdapter(new ArrayAdapter<Trackable>(CacheDetailActivity.this, R.layout.simple_list_item_1, cache.getInventory())); + view.setAdapter(new ArrayAdapter<>(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; @@ -1881,12 +1837,12 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc private class ImagesViewCreator extends AbstractCachingPageViewCreator<View> { @Override - public View getDispatchedView() { + public View getDispatchedView(final ViewGroup parentView) { if (cache == null) { return null; // something is really wrong } - view = getLayoutInflater().inflate(R.layout.cachedetail_images_page, null); + view = getLayoutInflater().inflate(R.layout.cachedetail_images_page, parentView, false); if (imagesList == null && isCurrentPage(Page.IMAGES)) { loadCacheImages(); } @@ -1901,6 +1857,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 +1956,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 +1972,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 +2008,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; @@ -2028,31 +2064,31 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } } - private class UploadPersonalNoteThread extends Thread { + private static class UploadPersonalNoteThread extends Thread { 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(); - bundle.putString(SimpleCancellableHandler.MESSAGE_TEXT, res.getString(R.string.cache_personal_note_upload_done)); + final Message msg = Message.obtain(); + final Bundle bundle = new Bundle(); + bundle.putString(SimpleCancellableHandler.MESSAGE_TEXT, CgeoApplication.getInstance().getString(R.string.cache_personal_note_upload_done)); msg.setData(bundle); handler.sendMessage(msg); } } @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(); @@ -2063,7 +2099,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc @Override protected Pair<List<? extends Page>, Integer> getOrderedPages() { - final ArrayList<Page> pages = new ArrayList<Page>(); + final ArrayList<Page> pages = new ArrayList<>(); pages.add(Page.WAYPOINTS); pages.add(Page.DETAILS); final int detailsIndex = pages.size() - 1; @@ -2084,7 +2120,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,13 +2148,13 @@ 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); - final Button offlineStore = (Button) view.findViewById(R.id.offline_store); + final TextView offlineText = ButterKnife.findById(view, R.id.offline_text); + final Button offlineRefresh = ButterKnife.findById(view, R.id.offline_refresh); + final Button offlineStore = ButterKnife.findById(view, R.id.offline_store); if (cache.isOffline()) { final long diff = (System.currentTimeMillis() / (60 * 1000)) - (cache.getDetailedUpdate() / (60 * 1000)); // minutes @@ -2160,12 +2196,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 +2212,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 +2228,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 +2254,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,44 +2263,24 @@ 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>() { + RxUtils.networkScheduler.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 = ButterKnife.findById(activity, R.id.personalnote); setPersonalNote(personalNoteView, note); - DataStore.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); + DataStore.saveCache(cache, EnumSet.of(SaveFlag.DB)); activity.notifyDataSetChanged(); } }; diff --git a/main/src/cgeo/geocaching/CacheListActivity.java b/main/src/cgeo/geocaching/CacheListActivity.java index 053798e..9d15e47 100644 --- a/main/src/cgeo/geocaching/CacheListActivity.java +++ b/main/src/cgeo/geocaching/CacheListActivity.java @@ -1,5 +1,7 @@ package cgeo.geocaching; +import butterknife.ButterKnife; + import cgeo.geocaching.activity.AbstractActivity; import cgeo.geocaching.activity.AbstractListActivity; import cgeo.geocaching.activity.ActivityMixin; @@ -13,11 +15,13 @@ import cgeo.geocaching.enumerations.CacheListType; import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.enumerations.LoadFlags; import cgeo.geocaching.enumerations.StatusCode; -import cgeo.geocaching.export.ExportFactory; +import cgeo.geocaching.export.FieldnoteExport; +import cgeo.geocaching.export.GpxExport; 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,8 +44,9 @@ 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.sorting.SortActionProvider; import cgeo.geocaching.ui.CacheListAdapter; import cgeo.geocaching.ui.LoggingUI; import cgeo.geocaching.ui.WeakReferenceHandler; @@ -57,6 +62,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 +73,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 +85,8 @@ import android.os.Message; import android.provider.OpenableColumns; import android.support.v4.app.LoaderManager; import android.support.v4.content.Loader; +import android.support.v4.view.MenuItemCompat; +import android.support.v7.app.ActionBar; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.KeyEvent; @@ -111,7 +121,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA private Geopoint coords = null; private SearchResult search = null; /** The list of shown caches shared with Adapter. Don't manipulate outside of main thread only with Handler */ - private final List<Geocache> cacheList = new ArrayList<Geocache>(); + private final List<Geocache> cacheList = new ArrayList<>(); private CacheListAdapter adapter = null; private View listFooter = null; private TextView listFooterText = null; @@ -160,7 +170,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 +178,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 +222,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 +262,20 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } } + private static 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) { @@ -286,7 +290,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA if (minutesRemaining < 1) { progress.setMessage(res.getString(R.string.caches_downloading) + " " + res.getString(R.string.caches_eta_ltm)); } else { - progress.setMessage(res.getString(R.string.caches_downloading) + " " + minutesRemaining + " " + res.getQuantityString(R.plurals.caches_eta_mins, minutesRemaining)); + progress.setMessage(res.getString(R.string.caches_downloading) + " " + res.getQuantityString(R.plurals.caches_eta_mins, minutesRemaining, minutesRemaining)); } } else { if (search != null) { @@ -310,7 +314,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 +352,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 +365,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 +377,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 +402,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 +427,57 @@ 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 SortActionProvider sortProvider; + + 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 +498,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); } @@ -474,7 +520,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } // refresh standard list if it has changed (new caches downloaded) - if (type == CacheListType.OFFLINE && listId >= StoredList.STANDARD_LIST_ID && search != null) { + if (type == CacheListType.OFFLINE && (listId >= StoredList.STANDARD_LIST_ID || listId == PseudoList.ALL_LIST.id) && search != null) { final SearchResult newSearch = DataStore.getBatchOfStoredCaches(coords, Settings.getCacheType(), listId); if (newSearch.getTotalCountGC() != search.getTotalCountGC()) { refreshCurrentList(); @@ -499,11 +545,29 @@ 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); + sortProvider = (SortActionProvider) MenuItemCompat.getActionProvider(menu.findItem(R.id.menu_sort)); + sortProvider.setSelection(adapter.getCacheComparator()); + sortProvider.setClickListener(new Action1<CacheComparator>() { + @Override + public void call(final CacheComparator selectedComparator) { + final CacheComparator oldComparator = adapter.getCacheComparator(); + // selecting the same sorting twice will toggle the order + if (selectedComparator != null && oldComparator != null && selectedComparator.getClass().equals(oldComparator.getClass())) { + adapter.toggleInverseSort(); + } + else { + // always reset the inversion for a new sorting criteria + adapter.resetInverseSort(); + } + setComparator(selectedComparator); + sortProvider.setSelection(selectedComparator); + } + }); return true; } @@ -512,7 +576,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,49 +596,43 @@ 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, isOffline); + setVisible(menu, R.id.menu_import_web, isOffline); setVisible(menu, R.id.menu_import_gpx, isOffline); + setVisible(menu, R.id.menu_export, !isEmpty); setVisible(menu, R.id.menu_refresh_stored_top, !isOffline && !isEmpty); if (!isOffline && !isHistory) { menu.findItem(R.id.menu_refresh_stored_top).setTitle(R.string.caches_store_offline); } - final boolean hasSelection = adapter != null && adapter.getCheckedCount() > 0; final boolean isNonDefaultList = isConcrete && listId != StoredList.STANDARD_LIST_ID; if (isOffline || type == CacheListType.HISTORY) { // only offline list - setMenuItemLabel(menu, R.id.menu_drop_caches, R.string.caches_drop_selected, R.string.caches_drop_all); + setMenuItemLabel(menu, R.id.menu_drop_caches, R.string.caches_remove_selected, R.string.caches_remove_all); setMenuItemLabel(menu, R.id.menu_refresh_stored, R.string.caches_refresh_selected, R.string.caches_refresh_all); setMenuItemLabel(menu, R.id.menu_move_to_list, R.string.caches_move_selected, R.string.caches_move_all); } else { // search and global list (all other than offline and history) setMenuItemLabel(menu, R.id.menu_refresh_stored, R.string.caches_store_selected, R.string.caches_store_offline); } - // make combined list deletion only possible when there are no filters, as that leads to confusion for the hidden caches - menu.findItem(R.id.menu_drop_caches_and_list).setVisible(isOffline && !hasSelection && isNonDefaultList && !adapter.isFiltered() && Settings.getCacheType() == CacheType.ALL); - 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 +640,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 +672,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(); @@ -626,11 +690,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA invalidateOptionsMenuCompatible(); return true; case R.id.menu_drop_caches: - dropStored(false); - invalidateOptionsMenuCompatible(); - return false; - case R.id.menu_drop_caches_and_list: - dropStored(true); + dropStored(); invalidateOptionsMenuCompatible(); return true; case R.id.menu_import_gpx: @@ -643,10 +703,11 @@ 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: - removeList(true); + removeList(false); invalidateOptionsMenuCompatible(); return false; case R.id.menu_rename_list: @@ -656,36 +717,18 @@ 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; - case R.id.menu_sort: - final CacheComparator oldComparator = adapter.getCacheComparator(); - new ComparatorUserInterface(this).selectComparator(oldComparator, new Action1<CacheComparator>() { - @Override - public void call(CacheComparator selectedComparator) { - // selecting the same sorting twice will toggle the order - if (selectedComparator != null && oldComparator != null && selectedComparator.getClass().equals(oldComparator.getClass())) { - adapter.toggleInverseSort(); - } - else { - // always reset the inversion for a new sorting criteria - adapter.resetInverseSort(); - } - setComparator(selectedComparator); - } - }); - return true; case R.id.menu_import_web: importWeb(); - return false; - case R.id.menu_export: - ExportFactory.showExportMenu(adapter.getCheckedOrAllCaches(), this); - return false; + return true; + case R.id.menu_export_gpx: + new GpxExport().export(adapter.getCheckedOrAllCaches(), this); + return true; + case R.id.menu_export_fieldnotes: + new FieldnoteExport().export(adapter.getCheckedOrAllCaches(), this); + return true; case R.id.menu_remove_from_history: removeFromHistoryCheck(); invalidateOptionsMenuCompatible(); @@ -721,7 +764,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } private SearchResult getFilteredSearch() { - final Set<String> geocodes = new HashSet<String>(); + final Set<String> geocodes = new HashSet<>(); for (final Geocache cache : adapter.getFilteredList()) { geocodes.add(cache.getGeocode()); } @@ -729,13 +772,13 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } public void deletePastEvents() { - final List<Geocache> deletion = new ArrayList<Geocache>(); + final List<Geocache> deletion = new ArrayList<>(); for (final Geocache cache : adapter.getCheckedOrAllCaches()) { if (DateUtils.isPastEvent(cache)) { deletion.add(cache); } } - new DropDetailsTask(false).execute(deletion.toArray(new Geocache[deletion.size()])); + new DropDetailsTask().execute(deletion.toArray(new Geocache[deletion.size()])); } public void clearOfflineLogs() { @@ -750,13 +793,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); } }); } @@ -795,7 +833,6 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA final boolean isOffline = cache.isOffline(); menu.findItem(R.id.menu_drop_cache).setVisible(isOffline); menu.findItem(R.id.menu_move_to_list).setVisible(isOffline); - menu.findItem(R.id.menu_export).setVisible(isOffline); menu.findItem(R.id.menu_refresh).setVisible(isOffline); menu.findItem(R.id.menu_store_cache).setVisible(!isOffline); @@ -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(); @@ -874,9 +911,6 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA case R.id.menu_refresh: refreshStored(Collections.singletonList(cache)); break; - case R.id.menu_export: - ExportFactory.showExportMenu(Collections.singletonList(cache), this); - return false; default: // we must remember the menu info for the sub menu, there is a bug // in Android: @@ -904,7 +938,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 +948,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); @@ -924,15 +959,18 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } private void initAdapter() { - final ListView list = getListView(); - registerForContextMenu(list); + final ListView listView = getListView(); + registerForContextMenu(listView); 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, listView, false); + listFooter.setClickable(true); + listFooter.setOnClickListener(new MoreCachesListener()); + listFooterText = ButterKnife.findById(listFooter, R.id.more_caches); + listView.addFooterView(listFooter); + } setListAdapter(adapter); adapter.forceSort(); } @@ -944,26 +982,39 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } private void showFooterLoadingCaches() { + // no footer for offline lists + if (listFooter == null) { + return; + } listFooterText.setText(res.getString(R.string.caches_more_caches_loading)); listFooter.setClickable(false); listFooter.setOnClickListener(null); } private void showFooterMoreCaches() { + // no footer in offline lists + if (listFooter == null) { + return; + } + boolean enableMore = type != CacheListType.OFFLINE && cacheList.size() < MAX_LIST_ITEMS; if (enableMore && search != null) { final int count = search.getTotalCountGC(); enableMore = count > 0 && cacheList.size() < count; } + listFooter.setClickable(enableMore); if (enableMore) { listFooterText.setText(res.getString(R.string.caches_more_caches) + " (" + res.getString(R.string.caches_more_caches_currently) + ": " + cacheList.size() + ")"); listFooter.setOnClickListener(new MoreCachesListener()); - } else { + } else if (type != CacheListType.OFFLINE) { listFooterText.setText(res.getString(CollectionUtils.isEmpty(cacheList) ? R.string.caches_no_cache : R.string.caches_more_caches_no)); listFooter.setOnClickListener(null); + } else { + // hiding footer for offline list is not possible, it must be removed instead + // http://stackoverflow.com/questions/7576099/hiding-footer-in-listview + getListView().removeFooterView(listFooter); } - listFooter.setClickable(enableMore); } private void importGpx() { @@ -975,7 +1026,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 +1042,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,25 +1068,32 @@ 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 { if (type != CacheListType.OFFLINE) { - refreshStored(caches, StoredList.STANDARD_LIST_ID); - } else { - refreshStored(caches, this.listId); + for (final Geocache geocache : caches) { + if (geocache.getListId() == StoredList.TEMPORARY_LIST_ID) { + geocache.setListId(StoredList.STANDARD_LIST_ID); + } + } } + refreshStoredInternal(caches); } } - private void refreshStored(final List<Geocache> caches, final int storeListId) { + private void refreshStoredInternal(final List<Geocache> caches) { detailProgress = 0; showProgress(false); @@ -1045,7 +1103,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA if (etaTime < 1) { message = res.getString(R.string.caches_downloading) + " " + res.getString(R.string.caches_eta_ltm); } else { - message = res.getString(R.string.caches_downloading) + " " + etaTime + " " + res.getQuantityString(R.plurals.caches_eta_mins, etaTime); + message = res.getString(R.string.caches_downloading) + " " + res.getQuantityString(R.plurals.caches_eta_mins, etaTime, etaTime); } progress.show(this, null, message, ProgressDialog.STYLE_HORIZONTAL, loadDetailsHandler.cancelMessage()); @@ -1053,16 +1111,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(); } @@ -1081,8 +1139,19 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } public void importWeb() { - detailProgress = 0; + // menu is also shown with no device connected + if (!Settings.isRegisteredForSend2cgeo()) { + 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()); @@ -1091,23 +1160,21 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA threadWeb.start(); } - public void dropStored(final boolean removeListAfterwards) { - 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() { + public void dropStored() { + final int titleId = (adapter.getCheckedCount() > 0) ? R.string.caches_remove_selected : R.string.caches_remove_all; + final int messageId = (adapter.getCheckedCount() > 0) ? R.string.caches_remove_selected_confirm : R.string.caches_remove_all_confirm; + final String message = getString(messageId, adapter.getCheckedOrAllCount()); + Dialogs.confirmYesNo(this, titleId, message, new DialogInterface.OnClickListener() { @Override - public void onClick(DialogInterface dialog, int id) { - dropSelected(removeListAfterwards); + public void onClick(final DialogInterface dialog, final int id) { + final List<Geocache> selected = adapter.getCheckedOrAllCaches(); + new DropDetailsTask().execute(selected.toArray(new Geocache[selected.size()])); dialog.cancel(); } }); } - public void dropSelected(boolean removeListAfterwards) { - final List<Geocache> selected = adapter.getCheckedOrAllCaches(); - new DropDetailsTask(removeListAfterwards).execute(selected.toArray(new Geocache[selected.size()])); - } - /** * Thread to refresh the cache details. */ @@ -1115,15 +1182,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 @@ -1152,13 +1215,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()); @@ -1176,7 +1239,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); } @@ -1226,26 +1289,18 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA private class DropDetailsTask extends AsyncTaskWithProgress<Geocache, Void> { - private final boolean removeListAfterwards; - - public DropDetailsTask(boolean removeListAfterwards) { - super(CacheListActivity.this, null, res.getString(R.string.caches_drop_progress), true); - this.removeListAfterwards = removeListAfterwards; + public DropDetailsTask() { + super(CacheListActivity.this, null, res.getString(R.string.caches_remove_progress), true); } @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) { - // remove list in UI because of toast - if (removeListAfterwards) { - removeList(false); - } - + protected void onPostExecuteInternal(final Void result) { adapter.setSelectMode(false); refreshCurrentList(); replaceCacheListFromSearch(); @@ -1258,7 +1313,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(); } @@ -1273,10 +1328,9 @@ 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); getSupportLoaderManager().restartLoader(CacheListLoaderType.NEXT_PAGE.getLoaderId(), null, CacheListActivity.this); } @@ -1291,13 +1345,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>() { @@ -1309,7 +1356,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA }; } - public void switchListById(int id) { + public void switchListById(final int id) { if (id < 0) { return; } @@ -1320,25 +1367,28 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA return; } - final StoredList list = DataStore.getList(id); - if (list == null) { - return; + if (id == PseudoList.ALL_LIST.id) { + listId = id; + title = res.getString(R.string.list_all_lists); + } else { + final StoredList list = DataStore.getList(id); + if (list == null) { + return; + } + listId = list.id; + title = list.title; } - - 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(); } @@ -1356,6 +1406,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)); @@ -1365,12 +1416,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA private void removeList(final boolean askForConfirmation) { // if there are no caches on this list, don't bother the user with questions. // there is no harm in deleting the list, he could recreate it easily - if (CollectionUtils.isEmpty(cacheList)) { - removeListInternal(); - return; - } - - if (!askForConfirmation) { + if (!askForConfirmation && CollectionUtils.isEmpty(cacheList)) { removeListInternal(); return; } @@ -1378,17 +1424,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; } @@ -1404,6 +1446,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } private void refreshCurrentList() { + refreshSpinnerAdapter(); switchListById(listId); } @@ -1467,7 +1510,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); @@ -1491,7 +1534,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; @@ -1533,7 +1576,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); } @@ -1544,11 +1587,12 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA // open either the requested or the last list if (extras.containsKey(Intents.EXTRA_LIST_ID)) { listId = extras.getInt(Intents.EXTRA_LIST_ID); - } - else { + } else { listId = Settings.getLastList(); } - if (listId <= StoredList.TEMPORARY_LIST_ID) { + if (listId == PseudoList.ALL_LIST.id) { + title = res.getString(R.string.list_all_lists); + } else if (listId <= StoredList.TEMPORARY_LIST_ID) { listId = StoredList.STANDARD_LIST_ID; title = res.getString(R.string.stored_caches_button); } else { @@ -1566,6 +1610,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: @@ -1625,7 +1670,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA loader = new PocketGeocacheListLoader(app, guid); break; } - setTitle(title); + updateTitle(); showProgress(true); showFooterLoadingCaches(); @@ -1635,7 +1680,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 @@ -1643,7 +1688,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) { @@ -1660,10 +1705,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<>(); + 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..9c8af50 100644 --- a/main/src/cgeo/geocaching/CacheMenuHandler.java +++ b/main/src/cgeo/geocaching/CacheMenuHandler.java @@ -5,13 +5,17 @@ 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; /** * Shared menu handling for all activities having menu items related to a cache. <br> * TODO: replace by a fragment - * + * */ public class CacheMenuHandler extends AbstractUIFactory { @@ -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(); @@ -28,9 +32,15 @@ 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; + public static boolean onMenuItemSelected(final MenuItem item, final CacheMenuHandler.ActivityInterface activityInterface, final Geocache cache) { + 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(); @@ -45,8 +55,14 @@ public class CacheMenuHandler extends AbstractUIFactory { cache.openInBrowser(activity); return true; case R.id.menu_share: - cache.shareCache(activity, res); - return true; + /* If the share menu is a shareActionProvider do nothing and let the share ActionProvider do the work */ + final ShareActionProvider shareActionProvider = (ShareActionProvider) + MenuItemCompat.getActionProvider(item); + if (shareActionProvider == null) { + cache.shareCache(activity, res); + return true; + } + return false; case R.id.menu_calendar: CalendarAddon.addToCalendarWithIntent(activity, cache); return true; @@ -56,7 +72,6 @@ public class CacheMenuHandler extends AbstractUIFactory { } public static void onPrepareOptionsMenu(final Menu menu, final Geocache cache) { - // if (cache == null) { return; } @@ -68,10 +83,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()); + + final MenuItem shareItem = menu.findItem(R.id.menu_share); + final ShareActionProvider shareActionProvider = (ShareActionProvider) + MenuItemCompat.getActionProvider(shareItem); + if(shareActionProvider != null) { + shareActionProvider.setShareIntent(cache.getShareIntent()); + } + } - public static void addMenuItems(Activity activity, Menu menu, Geocache cache) { - activity.getMenuInflater().inflate(R.menu.cache_options, menu); + public static void addMenuItems(final MenuInflater inflater, final Menu menu, final Geocache cache) { + inflater.inflate(R.menu.cache_options, menu); onPrepareOptionsMenu(menu, cache); } + + public static void addMenuItems(final Activity activity, final Menu menu, final 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..b2af12c --- /dev/null +++ b/main/src/cgeo/geocaching/CachePopupFragment.java @@ -0,0 +1,230 @@ +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.utils.RxUtils; + +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, RxUtils.networkScheduler); + } + } + + 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() { + NavigationAppFactory.showNavigationMenu(getActivity(), cache, null, null, true, true); + } + + + /** + * 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..863dcdd 100644 --- a/main/src/cgeo/geocaching/CgeoApplication.java +++ b/main/src/cgeo/geocaching/CgeoApplication.java @@ -4,12 +4,16 @@ import cgeo.geocaching.sensors.DirectionProvider; import cgeo.geocaching.sensors.GeoDataProvider; import cgeo.geocaching.sensors.IGeoData; import cgeo.geocaching.utils.Log; +import cgeo.geocaching.utils.OOMDumpingUncaughtExceptionHandler; import rx.Observable; import rx.functions.Action1; import rx.observables.ConnectableObservable; import android.app.Application; +import android.view.ViewConfiguration; + +import java.lang.reflect.Field; public class CgeoApplication extends Application { @@ -22,6 +26,20 @@ public class CgeoApplication extends Application { private volatile IGeoData currentGeo = null; private volatile float currentDirection = 0.0f; + public static void dumpOnOutOfMemory(final boolean enable) { + + if (enable) { + + if (!OOMDumpingUncaughtExceptionHandler.activateHandler()) { + Log.e("OOM dumping handler not activated (either a problem occured or it was already active)"); + } + } else { + if (!OOMDumpingUncaughtExceptionHandler.resetToDefault()) { + Log.e("OOM dumping handler not resetted (either a problem occured or it was not active)"); + } + } + } + public CgeoApplication() { setInstance(this); } @@ -35,6 +53,24 @@ public class CgeoApplication extends Application { } @Override + public void onCreate() { + try { + final ViewConfiguration config = ViewConfiguration.get(this); + final Field menuKeyField = ViewConfiguration.class.getDeclaredField("sHasPermanentMenuKey"); + menuKeyField.setAccessible(true); + menuKeyField.setBoolean(config, false); + } catch (final IllegalArgumentException e) { + // ignore + } catch (final NoSuchFieldException e) { + // ignore + } catch (final IllegalAccessException e) { + // ignore + } + // ensure initialization of lists + DataStore.getLists(); + } + + @Override public void onLowMemory() { Log.i("Cleaning applications cache."); DataStore.removeAllFromCache(); @@ -69,7 +105,7 @@ public class CgeoApplication extends Application { } public IGeoData currentGeo() { - return currentGeo != null ? currentGeo : geoDataObservable().toBlockingObservable().first(); + return currentGeo != null ? currentGeo : geoDataObservable().toBlocking().first(); } public float currentDirection() { diff --git a/main/src/cgeo/geocaching/CompassActivity.java b/main/src/cgeo/geocaching/CompassActivity.java index 36dcf27..025d938 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; @@ -14,8 +14,8 @@ import cgeo.geocaching.sensors.IGeoData; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.speech.SpeechService; import cgeo.geocaching.ui.CompassView; -import cgeo.geocaching.ui.Formatter; import cgeo.geocaching.ui.LoggingUI; +import cgeo.geocaching.utils.Formatter; import cgeo.geocaching.utils.Log; import org.apache.commons.lang3.StringUtils; @@ -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; @@ -56,7 +56,7 @@ public class CompassActivity extends AbstractActivity { private static final String EXTRAS_NAME = "name"; private static final String EXTRAS_GEOCODE = "geocode"; private static final String EXTRAS_CACHE_INFO = "cacheinfo"; - private static final List<IWaypoint> coordinates = new ArrayList<IWaypoint>(); + private static final List<IWaypoint> coordinates = new ArrayList<>(); /** * Destination of the compass, or null (if the compass is used for a waypoint only). @@ -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); @@ -150,7 +150,7 @@ public class CompassActivity extends AbstractActivity { @Override public boolean onCreateOptionsMenu(final Menu menu) { getMenuInflater().inflate(R.menu.compass_activity_options, menu); - menu.findItem(R.id.menu_switch_compass_gps).setVisible(hasMagneticFieldSensor); + menu.findItem(R.id.menu_compass_sensor).setVisible(hasMagneticFieldSensor); final SubMenu subMenu = menu.findItem(R.id.menu_select_destination).getSubMenu(); if (coordinates.size() > 1) { for (int i = 0; i < coordinates.size(); i++) { @@ -167,43 +167,47 @@ 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)); + if (Settings.isUseCompass()) { + menu.findItem(R.id.menu_compass_sensor_magnetic).setChecked(true); + } + else { + menu.findItem(R.id.menu_compass_sensor_gps).setChecked(true); + } menu.findItem(R.id.menu_tts_start).setVisible(!SpeechService.isRunning()); menu.findItem(R.id.menu_tts_stop).setVisible(SpeechService.isRunning()); return true; } @Override - public boolean onOptionsItemSelected(MenuItem item) { - int id = item.getItemId(); + 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(); - Settings.setUseCompass(!oldSetting); + case R.id.menu_compass_sensor_gps: + Settings.setUseCompass(false); invalidateOptionsMenuCompatible(); return true; - case R.id.menu_edit_destination: - Intent pointIntent = new Intent(this, NavigateAnyPointActivity.class); - startActivity(pointIntent); - - finish(); + case R.id.menu_compass_sensor_magnetic: + Settings.setUseCompass(true); + invalidateOptionsMenuCompatible(); 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 +221,7 @@ public class CompassActivity extends AbstractActivity { return true; } } - return false; + return super.onOptionsItemSelected(item); } private void setTitle() { @@ -255,7 +259,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 +287,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 +303,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..e236f8f 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); } } @@ -420,14 +420,14 @@ public class DataStore { init(); return true; } - })).subscribe(new Action1<Boolean>() { + })).subscribeOn(Schedulers.io()).subscribe(new Action1<Boolean>() { @Override public void call(final Boolean success) { dialog.dismiss(); final String message = success ? fromActivity.getString(R.string.init_dbmove_success) : fromActivity.getString(R.string.init_dbmove_failed); Dialogs.message(fromActivity, R.string.init_dbmove_dbmove, message); } - }, Schedulers.io()); + }); } private static File databasePath(final boolean internal) { @@ -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("__"); } }; @@ -872,7 +872,7 @@ public class DataStore { if (ArrayUtils.isNotEmpty(files)) { final Pattern oldFilePattern = Pattern.compile("^[GC|TB|EC|GK|O][A-Z0-9]{4,7}$"); final SQLiteStatement select = db.compileStatement("select count(*) from " + dbTableCaches + " where geocode = ?"); - final ArrayList<File> toRemove = new ArrayList<File>(files.length); + final ArrayList<File> toRemove = new ArrayList<>(files.length); for (final File file : files) { if (file.isDirectory()) { final String geocode = file.getName(); @@ -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,15 +1082,15 @@ 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; } - final ArrayList<String> cachesFromDatabase = new ArrayList<String>(); - final HashMap<String, Geocache> existingCaches = new HashMap<String, Geocache>(); + final ArrayList<String> cachesFromDatabase = new ArrayList<>(); + final HashMap<String, Geocache> existingCaches = new HashMap<>(); // 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,18 +1102,18 @@ 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); } - final ArrayList<Geocache> toBeStored = new ArrayList<Geocache>(); + final ArrayList<Geocache> toBeStored = new ArrayList<>(); // Merge with the data already stored in the CacheCache or in the database if // the cache had not been loaded before, and update the CacheCache. // Also, a DB update is required if the merge data comes from the CacheCache // (as it may be more recent than the version in the database), or if the // version coming from the database is different than the version we are entering // into the cache (that includes absence from the database). - 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; @@ -1122,12 +1122,12 @@ public class DataStore { // Only save the cache in the database if it is requested by the caller and // the cache contains detailed information. - if (saveFlags.contains(SaveFlag.SAVE_DB) && cache.isDetailed() && dbUpdateRequired) { + if (saveFlags.contains(SaveFlag.DB) && cache.isDetailed() && dbUpdateRequired) { toBeStored.add(cache); } } - 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 ArrayList<String> currentWaypointIds = new ArrayList<>(); + 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 }); @@ -1563,15 +1559,15 @@ public class DataStore { */ public static Set<Geocache> loadCaches(final Collection<String> geocodes, final EnumSet<LoadFlag> loadFlags) { if (CollectionUtils.isEmpty(geocodes)) { - return new HashSet<Geocache>(); + return new HashSet<>(); } - Set<Geocache> result = new HashSet<Geocache>(); - Set<String> remaining = new HashSet<String>(geocodes); + final Set<Geocache> result = new HashSet<>(); + final Set<String> remaining = new HashSet<>(geocodes); - if (loadFlags.contains(LoadFlag.LOAD_CACHE_BEFORE)) { - for (String geocode : new HashSet<String>(remaining)) { - Geocache cache = cacheCache.getCacheFromCache(geocode); + if (loadFlags.contains(LoadFlag.CACHE_BEFORE)) { + for (final String geocode : new HashSet<>(remaining)) { + final Geocache cache = cacheCache.getCacheFromCache(geocode); if (cache != null) { result.add(cache); remaining.remove(cache.getGeocode()); @@ -1579,13 +1575,13 @@ public class DataStore { } } - if (loadFlags.contains(LoadFlag.LOAD_DB_MINIMAL) || - loadFlags.contains(LoadFlag.LOAD_ATTRIBUTES) || - loadFlags.contains(LoadFlag.LOAD_WAYPOINTS) || - loadFlags.contains(LoadFlag.LOAD_SPOILERS) || - loadFlags.contains(LoadFlag.LOAD_LOGS) || - loadFlags.contains(LoadFlag.LOAD_INVENTORY) || - loadFlags.contains(LoadFlag.LOAD_OFFLINE_LOG)) { + if (loadFlags.contains(LoadFlag.DB_MINIMAL) || + loadFlags.contains(LoadFlag.ATTRIBUTES) || + loadFlags.contains(LoadFlag.WAYPOINTS) || + loadFlags.contains(LoadFlag.SPOILERS) || + loadFlags.contains(LoadFlag.LOGS) || + loadFlags.contains(LoadFlag.INVENTORY) || + loadFlags.contains(LoadFlag.OFFLINE_LOG)) { final Set<Geocache> cachesFromDB = loadCachesFromGeocodes(remaining, loadFlags); result.addAll(cachesFromDB); @@ -1594,9 +1590,9 @@ public class DataStore { } } - if (loadFlags.contains(LoadFlag.LOAD_CACHE_AFTER)) { - for (String geocode : new HashSet<String>(remaining)) { - Geocache cache = cacheCache.getCacheFromCache(geocode); + if (loadFlags.contains(LoadFlag.CACHE_AFTER)) { + for (final String geocode : new HashSet<>(remaining)) { + final Geocache cache = cacheCache.getCacheFromCache(geocode); if (cache != null) { result.add(cache); remaining.remove(cache.getGeocode()); @@ -1626,43 +1622,43 @@ public class DataStore { init(); final StringBuilder query = new StringBuilder(QUERY_CACHE_DATA); - if (loadFlags.contains(LoadFlag.LOAD_OFFLINE_LOG)) { + if (loadFlags.contains(LoadFlag.OFFLINE_LOG)) { query.append(',').append(dbTableLogsOffline).append(".log"); } query.append(" FROM ").append(dbTableCaches); - if (loadFlags.contains(LoadFlag.LOAD_OFFLINE_LOG)) { + if (loadFlags.contains(LoadFlag.OFFLINE_LOG)) { query.append(" LEFT OUTER JOIN ").append(dbTableLogsOffline).append(" ON ( ").append(dbTableCaches).append(".geocode == ").append(dbTableLogsOffline).append(".geocode) "); } 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>(); + final Set<Geocache> caches = new HashSet<>(); int logIndex = -1; while (cursor.moveToNext()) { - Geocache cache = DataStore.createCacheFromDatabaseContent(cursor); + final Geocache cache = DataStore.createCacheFromDatabaseContent(cursor); - if (loadFlags.contains(LoadFlag.LOAD_ATTRIBUTES)) { + if (loadFlags.contains(LoadFlag.ATTRIBUTES)) { cache.setAttributes(loadAttributes(cache.getGeocode())); } - if (loadFlags.contains(LoadFlag.LOAD_WAYPOINTS)) { + if (loadFlags.contains(LoadFlag.WAYPOINTS)) { final List<Waypoint> waypoints = loadWaypoints(cache.getGeocode()); if (CollectionUtils.isNotEmpty(waypoints)) { cache.setWaypoints(waypoints, false); } } - if (loadFlags.contains(LoadFlag.LOAD_SPOILERS)) { + if (loadFlags.contains(LoadFlag.SPOILERS)) { final List<Image> spoilers = loadSpoilers(cache.getGeocode()); cache.setSpoilers(spoilers); } - if (loadFlags.contains(LoadFlag.LOAD_LOGS)) { + if (loadFlags.contains(LoadFlag.LOGS)) { final Map<LogType, Integer> logCounts = loadLogCounts(cache.getGeocode()); if (MapUtils.isNotEmpty(logCounts)) { cache.getLogCounts().clear(); @@ -1670,7 +1666,7 @@ public class DataStore { } } - if (loadFlags.contains(LoadFlag.LOAD_INVENTORY)) { + if (loadFlags.contains(LoadFlag.INVENTORY)) { final List<Trackable> inventory = loadInventory(cache.getGeocode()); if (CollectionUtils.isNotEmpty(inventory)) { if (cache.getInventory() == null) { @@ -1682,7 +1678,7 @@ public class DataStore { } } - if (loadFlags.contains(LoadFlag.LOAD_OFFLINE_LOG)) { + if (loadFlags.contains(LoadFlag.OFFLINE_LOG)) { if (logIndex < 0) { logIndex = cursor.getColumnIndex("log"); } @@ -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<>(); if (StringUtils.isBlank(geocode)) { return logs; @@ -1967,14 +1963,14 @@ 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; } init(); - final Map<LogType, Integer> logCounts = new HashMap<LogType, Integer>(); + final Map<LogType, Integer> logCounts = new HashMap<>(); final Cursor cursor = database.query( dbTableLogCount, @@ -1995,14 +1991,14 @@ 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; } init(); - final List<Trackable> trackables = new ArrayList<Trackable>(); + final List<Trackable> trackables = new ArrayList<>(); final Cursor cursor = database.query( dbTableTrackables, @@ -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); } @@ -2269,7 +2265,7 @@ public class DataStore { * @return Set with geocodes */ private static SearchResult loadInViewport(final boolean stored, final Viewport viewport, final CacheType cacheType) { - final Set<String> geocodes = new HashSet<String>(); + final Set<String> geocodes = new HashSet<>(); // if not stored only, get codes from CacheCache as well if (!stored) { @@ -2323,7 +2319,7 @@ public class DataStore { Log.d("Database clean: started"); try { - Set<String> geocodes = new HashSet<String>(); + Set<String> geocodes = new HashSet<>(); if (more) { queryToColl(dbTableCaches, new String[]{"geocode"}, @@ -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,22 +2405,22 @@ 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; } init(); - if (removeFlags.contains(RemoveFlag.REMOVE_CACHE)) { + if (removeFlags.contains(RemoveFlag.CACHE)) { for (final String geocode : geocodes) { cacheCache.removeCacheFromCache(geocode); } } - if (removeFlags.contains(RemoveFlag.REMOVE_DB)) { + if (removeFlags.contains(RemoveFlag.DB)) { // Drop caches from the database - final ArrayList<String> quotedGeocodes = new ArrayList<String>(geocodes.size()); + final ArrayList<String> quotedGeocodes = new ArrayList<>(geocodes.size()); for (final String geocode : geocodes) { quotedGeocodes.add(DatabaseUtils.sqlEscapeString(geocode)); } @@ -2440,7 +2436,7 @@ public class DataStore { database.delete(dbTableLogCount, baseWhereClause, null); database.delete(dbTableLogsOffline, baseWhereClause, null); String wayPointClause = baseWhereClause; - if (!removeFlags.contains(RemoveFlag.REMOVE_OWN_WAYPOINTS_ONLY_FOR_TESTING)) { + if (!removeFlags.contains(RemoveFlag.OWN_WAYPOINTS_ONLY_FOR_TESTING)) { wayPointClause += " and type <> 'own'"; } database.delete(dbTableWaypoints, wayPointClause, null); @@ -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<>(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(); @@ -2587,7 +2583,7 @@ public class DataStore { init(); final Resources res = CgeoApplication.getInstance().getResources(); - final List<StoredList> lists = new ArrayList<StoredList>(); + final List<StoredList> lists = new ArrayList<>(); lists.add(new StoredList(StoredList.STANDARD_LIST_ID, res.getString(R.string.list_inbox), (int) PreparedStatements.getCountCachesOnStandardList().simpleQueryForLong())); try { @@ -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,13 +2913,13 @@ public class DataStore { }); } - public static void saveChangedCache(Geocache cache) { - DataStore.saveCache(cache, cache.getStorageLocation().contains(StorageLocation.DATABASE) ? LoadFlags.SAVE_ALL : EnumSet.of(SaveFlag.SAVE_CACHE)); + public static void saveChangedCache(final Geocache cache) { + DataStore.saveCache(cache, cache.getStorageLocation().contains(StorageLocation.DATABASE) ? LoadFlags.SAVE_ALL : EnumSet.of(SaveFlag.CACHE)); } private static class PreparedStatements { - private static HashMap<String, SQLiteStatement> statements = new HashMap<String, SQLiteStatement>(); + private static HashMap<String, SQLiteStatement> statements = new HashMap<>(); public static SQLiteStatement getMoveToStandardList() { return getStatement("MoveToStandardList", "UPDATE " + dbTableCaches + " SET reason = " + StoredList.STANDARD_LIST_ID + " WHERE reason = ?"); @@ -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,23 +3029,23 @@ 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)); + DataStore.removeCache(geocode, EnumSet.of(RemoveFlag.CACHE)); return true; } return false; @@ -3057,7 +3054,7 @@ public class DataStore { public static Set<String> getCachedMissingFromSearch(final SearchResult searchResult, final Set<Tile> tiles, final IConnector connector, final int maxZoom) { // get cached CacheListActivity - final Set<String> cachedGeocodes = new HashSet<String>(); + final Set<String> cachedGeocodes = new HashSet<>(); for (final Tile tile : tiles) { cachedGeocodes.addAll(cacheCache.getInViewport(tile.getViewport(), CacheType.ALL)); } @@ -3065,7 +3062,7 @@ public class DataStore { cachedGeocodes.removeAll(searchResult.getGeocodes()); // check remaining against viewports - final Set<String> missingFromSearch = new HashSet<String>(); + final Set<String> missingFromSearch = new HashSet<>(); for (final String geocode : cachedGeocodes) { if (connector.canHandle(geocode)) { final Geocache geocache = cacheCache.getCacheFromCache(geocode); @@ -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<>(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..73cb477 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,8 +48,8 @@ import java.util.EnumSet; import java.util.List; @EActivity -public class EditWaypointActivity extends AbstractActivity { - private static final ArrayList<WaypointType> POSSIBLE_WAYPOINT_TYPES = new ArrayList<WaypointType>(WaypointType.ALL_TYPES_EXCEPT_OWN_AND_ORIGINAL); +public class EditWaypointActivity extends AbstractActionBarActivity implements CoordinatesInputDialog.CoordinateUpdate { + private static final ArrayList<WaypointType> POSSIBLE_WAYPOINT_TYPES = new ArrayList<>(WaypointType.ALL_TYPES_EXCEPT_OWN_AND_ORIGINAL); @ViewById(R.id.buttonLatitude) protected Button buttonLat; @ViewById(R.id.buttonLongitude) protected Button buttonLon; @@ -159,11 +159,11 @@ public class EditWaypointActivity extends AbstractActivity { addWaypoint.setOnClickListener(new SaveWaypointListener()); - List<String> wayPointNames = new ArrayList<String>(); + List<String> wayPointNames = new ArrayList<>(); for (WaypointType wpt : WaypointType.ALL_TYPES_EXCEPT_OWN_AND_ORIGINAL) { wayPointNames.add(wpt.getL10n()); } - ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_dropdown_item_1line, wayPointNames); + ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line, wayPointNames); waypointName.setAdapter(adapter); if (savedInstanceState != null) { @@ -205,7 +205,7 @@ public class EditWaypointActivity extends AbstractActivity { } private void initializeWaypointTypeSelector() { - ArrayAdapter<WaypointType> wpAdapter = new ArrayAdapter<WaypointType>(this, android.R.layout.simple_spinner_item, POSSIBLE_WAYPOINT_TYPES.toArray(new WaypointType[POSSIBLE_WAYPOINT_TYPES.size()])); + ArrayAdapter<WaypointType> wpAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, POSSIBLE_WAYPOINT_TYPES.toArray(new WaypointType[POSSIBLE_WAYPOINT_TYPES.size()])); wpAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); waypointTypeSelector.setAdapter(wpAdapter); @@ -247,7 +247,7 @@ public class EditWaypointActivity extends AbstractActivity { } private void initializeDistanceUnitSelector() { - distanceUnits = new ArrayList<String>(Arrays.asList(res.getStringArray(R.array.distance_units))); + distanceUnits = new ArrayList<>(Arrays.asList(res.getStringArray(R.array.distance_units))); if (initViews) { distanceUnitSelector.setSelection(Settings.isUseImperialUnits() ? 2 : 0); //0:m, 2:ft } @@ -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; @@ -438,11 +439,11 @@ public class EditWaypointActivity extends AbstractActivity { } Waypoint oldWaypoint = cache.getWaypointById(id); if (cache.addOrChangeWaypoint(waypoint, true)) { - DataStore.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); + DataStore.saveCache(cache, EnumSet.of(SaveFlag.DB)); 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..e0daae1 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; @@ -10,6 +11,7 @@ import cgeo.geocaching.connector.capability.ISearchByGeocode; import cgeo.geocaching.connector.gc.GCConnector; import cgeo.geocaching.connector.gc.GCConstants; import cgeo.geocaching.connector.gc.Tile; +import cgeo.geocaching.connector.gc.UncertainProperty; import cgeo.geocaching.enumerations.CacheAttribute; import cgeo.geocaching.enumerations.CacheSize; import cgeo.geocaching.enumerations.CacheType; @@ -32,7 +34,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.UncertainProperty; +import cgeo.geocaching.utils.RxUtils; 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; @@ -95,7 +93,7 @@ public class Geocache implements ICache, IWaypoint { private String geocode = ""; private String cacheId = ""; private String guid = ""; - private UncertainProperty<CacheType> cacheType = new UncertainProperty<CacheType>(CacheType.UNKNOWN, Tile.ZOOMLEVEL_MIN - 1); + private UncertainProperty<CacheType> cacheType = new UncertainProperty<>(CacheType.UNKNOWN, Tile.ZOOMLEVEL_MIN - 1); private String name = ""; private String ownerDisplayName = ""; private String ownerUserId = ""; @@ -113,7 +111,7 @@ public class Geocache implements ICache, IWaypoint { * lazy initialized */ private String location = null; - private UncertainProperty<Geopoint> coords = new UncertainProperty<Geopoint>(null); + private UncertainProperty<Geopoint> coords = new UncertainProperty<>(null); private boolean reliableLatLon = false; private String personalNote = null; /** @@ -151,7 +149,7 @@ public class Geocache implements ICache, IWaypoint { private List<Image> spoilers = null; private List<Trackable> inventory = null; - private Map<LogType, Integer> logCounts = new HashMap<LogType, Integer>(); + private Map<LogType, Integer> logCounts = new HashMap<>(); private boolean userModifiedCoords = false; // temporary values private boolean statusChecked = false; @@ -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; } @@ -339,7 +336,7 @@ public class Geocache implements ICache, IWaypoint { this.setWaypoints(other.waypoints, false); } else { - final ArrayList<Waypoint> newPoints = new ArrayList<Waypoint>(waypoints); + final ArrayList<Waypoint> newPoints = new ArrayList<>(waypoints); Waypoint.mergeWayPoints(newPoints, other.waypoints, false); this.setWaypoints(newPoints, false); } @@ -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(getLongUrl())); + + // 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,16 @@ 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 = getShareIntent(); + + fromActivity.startActivity(Intent.createChooser(intent, res.getText(R.string.cache_menu_share))); + } + public Intent getShareIntent() { final StringBuilder subject = new StringBuilder("Geocache "); subject.append(geocode); if (StringUtils.isNotBlank(name)) { @@ -720,13 +734,19 @@ 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 getLongUrl() { + return getConnector().getLongCacheUrl(this); + } + + public String getCgeoUrl() { return getConnector().getCacheUrl(this); } + public boolean supportsGCVote() { return StringUtils.startsWithIgnoreCase(geocode, "GC"); } @@ -745,7 +765,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; } @@ -767,7 +787,7 @@ public class Geocache implements ICache, IWaypoint { public void addSpoiler(final Image spoiler) { if (spoilers == null) { - spoilers = new ArrayList<Image>(); + spoilers = new ArrayList<>(); } spoilers.add(spoiler); } @@ -816,7 +836,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 +844,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 +852,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 +860,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 +868,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 +885,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 +893,7 @@ public class Geocache implements ICache, IWaypoint { return distance; } - public void setDistance(Float distance) { + public void setDistance(final Float distance) { this.distance = distance; } @@ -891,8 +911,8 @@ public class Geocache implements ICache, IWaypoint { * * @param coords */ - public void setCoords(Geopoint coords) { - this.coords = new UncertainProperty<Geopoint>(coords); + public void setCoords(final Geopoint coords) { + this.coords = new UncertainProperty<>(coords); } /** @@ -901,8 +921,8 @@ public class Geocache implements ICache, IWaypoint { * @param coords * @param zoomlevel */ - public void setCoords(Geopoint coords, int zoomlevel) { - this.coords = new UncertainProperty<Geopoint>(coords, zoomlevel); + public void setCoords(final Geopoint coords, final int zoomlevel) { + this.coords = new UncertainProperty<>(coords, zoomlevel); } /** @@ -912,15 +932,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 +948,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 +956,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 +964,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 +972,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 +981,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 +1002,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); @@ -1016,7 +1036,7 @@ public class Geocache implements ICache, IWaypoint { */ @NonNull public List<LogEntry> getFriendsLogs() { - final ArrayList<LogEntry> friendLogs = new ArrayList<LogEntry>(); + final ArrayList<LogEntry> friendLogs = new ArrayList<>(); for (final LogEntry log : getLogs()) { if (log.friend) { friendLogs.add(log); @@ -1029,7 +1049,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 +1057,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 +1065,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 +1106,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,18 +1165,18 @@ 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); + this.cacheType = new UncertainProperty<>(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"); } - this.cacheType = new UncertainProperty<CacheType>(cacheType, zoomlevel); + this.cacheType = new UncertainProperty<>(cacheType, zoomlevel); } public boolean hasDifficulty() { @@ -1190,7 +1210,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 +1222,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 +1241,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<>(); + 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 +1267,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 +1288,7 @@ public class Geocache implements ICache, IWaypoint { return userModifiedCoords; } - public void setUserModifiedCoords(boolean coordsChanged) { + public void setUserModifiedCoords(final boolean coordsChanged) { userModifiedCoords = coordsChanged; } @@ -1309,7 +1329,7 @@ public class Geocache implements ICache, IWaypoint { final int index = getWaypointIndex(waypoint); waypoints.remove(index); DataStore.deleteWaypoint(waypoint.getId()); - DataStore.removeCache(geocode, EnumSet.of(RemoveFlag.REMOVE_CACHE)); + DataStore.removeCache(geocode, EnumSet.of(RemoveFlag.CACHE)); // Check status if Final is defined if (waypoint.isFinalWithCoords()) { resetFinalDefined(); @@ -1325,11 +1345,11 @@ 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()); - DataStore.removeCache(geocode, EnumSet.of(RemoveFlag.REMOVE_CACHE)); + DataStore.removeCache(geocode, EnumSet.of(RemoveFlag.CACHE)); resetFinalDefined(); } @@ -1405,17 +1425,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 +1458,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()); @@ -1453,7 +1473,7 @@ public class Geocache implements ICache, IWaypoint { public void dropSynchronous() { DataStore.markDropped(Collections.singletonList(this)); - DataStore.removeCache(getGeocode(), EnumSet.of(RemoveFlag.REMOVE_CACHE)); + DataStore.removeCache(getGeocode(), EnumSet.of(RemoveFlag.CACHE)); } public void checkFields() { @@ -1498,22 +1518,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) { - DataStore.removeCache(geocode, EnumSet.of(RemoveFlag.REMOVE_CACHE)); - storeCache(null, geocode, newListId, true, handler); + public void refreshSynchronous(final CancellableHandler handler) { + DataStore.removeCache(geocode, EnumSet.of(RemoveFlag.CACHE)); + 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 @@ -1585,15 +1605,13 @@ public class Geocache implements ICache, IWaypoint { } cache.setListId(listId); - DataStore.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); + DataStore.saveCache(cache, EnumSet.of(SaveFlag.DB)); if (CancellableHandler.isCancelled(handler)) { return; } - StaticMapsProvider.downloadMaps(cache); - - imgGetter.waitForBackgroundLoading(handler); + RxUtils.waitForCompletion(StaticMapsProvider.downloadMaps(cache), imgGetter.waitForEndObservable(handler)); if (handler != null) { handler.sendMessage(Message.obtain()); @@ -1643,7 +1661,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<>(); // 12:34 patterns.add(Pattern.compile("\\b(\\d{1,2})\\:(\\d\\d)\\b")); @@ -1655,7 +1673,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,8 +1701,8 @@ 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) { - Geocache fullCache = DataStore.loadCache(getGeocode(), EnumSet.of(LoadFlag.LOAD_ATTRIBUTES)); + public boolean hasAttribute(final CacheAttribute attribute, final boolean yes) { + Geocache fullCache = DataStore.loadCache(getGeocode(), EnumSet.of(LoadFlag.ATTRIBUTES)); if (fullCache == null) { fullCache = this; } @@ -1703,7 +1721,7 @@ public class Geocache implements ICache, IWaypoint { }; private void addDescriptionImagesUrls(final Collection<Image> images) { - final Set<String> urls = new LinkedHashSet<String>(); + final Set<String> urls = new LinkedHashSet<>(); for (final Image image : images) { urls.add(image.getUrl()); } @@ -1720,13 +1738,13 @@ public class Geocache implements ICache, IWaypoint { } public Collection<Image> getImages() { - final LinkedList<Image> result = new LinkedList<Image>(); + final LinkedList<Image> result = new LinkedList<>(); result.addAll(getSpoilers()); addLocalSpoilersTo(result); for (final LogEntry log : getLogs()) { result.addAll(log.getLogImages()); } - final Set<String> urls = new HashSet<String>(result.size()); + final Set<String> urls = new HashSet<>(result.size()); for (final Image image : result) { urls.add(image.getUrl()); } @@ -1762,7 +1780,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 +1797,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 +1818,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 +1832,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..b75e5eb 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; @@ -90,7 +90,7 @@ public class ImagesActivity extends AbstractActivity { .putExtra(Intents.EXTRA_TYPE, imageType); // avoid forcing the array list as parameter type - final ArrayList<Image> arrayList = new ArrayList<Image>(logImages); + final ArrayList<Image> arrayList = new ArrayList<>(logImages); logImgIntent.putParcelableArrayListExtra(Intents.EXTRA_IMAGES, arrayList); fromActivity.startActivity(logImgIntent); } diff --git a/main/src/cgeo/geocaching/LogCacheActivity.java b/main/src/cgeo/geocaching/LogCacheActivity.java index 2b05263..83bbdf2 100644 --- a/main/src/cgeo/geocaching/LogCacheActivity.java +++ b/main/src/cgeo/geocaching/LogCacheActivity.java @@ -1,5 +1,7 @@ package cgeo.geocaching; +import butterknife.ButterKnife; + import cgeo.geocaching.connector.ILoggingManager; import cgeo.geocaching.connector.ImageResult; import cgeo.geocaching.connector.LogResult; @@ -10,11 +12,11 @@ import cgeo.geocaching.enumerations.StatusCode; import cgeo.geocaching.gcvote.GCVote; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.twitter.Twitter; -import cgeo.geocaching.ui.Formatter; import cgeo.geocaching.ui.dialog.DateDialog; import cgeo.geocaching.ui.dialog.Dialogs; import cgeo.geocaching.utils.AsyncTaskWithProgress; import cgeo.geocaching.utils.DateUtils; +import cgeo.geocaching.utils.Formatter; import cgeo.geocaching.utils.Log; import cgeo.geocaching.utils.LogTemplateProvider; import cgeo.geocaching.utils.LogTemplateProvider.LogContext; @@ -25,7 +27,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 +36,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 +54,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"; @@ -66,13 +67,11 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia private Geocache cache = null; private String geocode = null; private String text = null; - private List<LogType> possibleLogTypes = new ArrayList<LogType>(); + private List<LogType> possibleLogTypes = new ArrayList<>(); private List<TrackableLog> trackables = null; - private Button postButton = null; private CheckBox tweetCheck = null; private LinearLayout tweetBox = null; private LinearLayout logPasswordBox = null; - private boolean tbChanged = false; private SparseArray<TrackableLog> actionButtons; private ILoggingManager loggingManager; @@ -84,6 +83,7 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia private String imageCaption; private String imageDescription; private Uri imageUri; + private boolean sendButtonEnabled; public void onLoadFinished() { @@ -123,9 +123,8 @@ 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; } } } @@ -137,24 +136,26 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia if (inflater == null) { inflater = getLayoutInflater(); } - actionButtons = new SparseArray<TrackableLog>(); + actionButtons = new SparseArray<>(); 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, inventoryView, false); - ((TextView) inventoryItem.findViewById(R.id.trackcode)).setText(tb.trackCode); - ((TextView) inventoryItem.findViewById(R.id.name)).setText(tb.name); - final TextView actionButton = (TextView) inventoryItem.findViewById(R.id.action); + final TextView codeView = ButterKnife.findById(inventoryItem, R.id.trackcode); + codeView.setText(tb.trackCode); + final TextView nameView = ButterKnife.findById(inventoryItem, R.id.name); + nameView.setText(tb.name); + final TextView actionButton = ButterKnife.findById(inventoryItem, R.id.action); actionButton.setId(tb.id); actionButtons.put(actionButton.getId(), tb); actionButton.setText(tb.action.getLabel() + " ▼"); actionButton.setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View view) { + public void onClick(final View view) { selectTrackableAction(view); } }); @@ -164,7 +165,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); @@ -180,11 +181,11 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia if (inventoryView.getChildCount() > 1) { final LinearLayout inventoryChangeAllView = (LinearLayout) findViewById(R.id.inventory_changeall); - final Button changeButton = (Button) inventoryChangeAllView.findViewById(R.id.changebutton); + final Button changeButton = ButterKnife.findById(inventoryChangeAllView, R.id.changebutton); changeButton.setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View view) { + public void onClick(final View view) { selectAllTrackablesAction(); } }); @@ -193,33 +194,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 +216,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 +226,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 +257,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 +264,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 +283,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 +336,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 +352,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,32 +363,24 @@ 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; typeButton.setText(typeSelected.getL10n()); - if (LogType.FOUND_IT == type && !tbChanged) { - // TODO: change action - } else if (LogType.FOUND_IT != type && !tbChanged) { - // TODO: change action - } - 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 +388,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 +399,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 +421,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<>(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 +447,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 +458,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,18 +507,17 @@ 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; updateTrackablesList(); dialog.dismiss(); } @@ -622,7 +527,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(); } @@ -631,17 +536,17 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia private void selectLogType() { // use a local copy of the possible types, as that one might be modified in the background by the loader - final ArrayList<LogType> possible = new ArrayList<LogType>(possibleLogTypes); + final ArrayList<LogType> possible = new ArrayList<>(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,18 +554,17 @@ 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; Log.i("Trackable " + trackableLog.trackCode + " (" + trackableLog.name + ") has new action: #" + logType); updateTrackablesList(); @@ -671,7 +575,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 +584,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 +594,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(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/LogEntry.java b/main/src/cgeo/geocaching/LogEntry.java index ca4a3d1..b4b346c 100644 --- a/main/src/cgeo/geocaching/LogEntry.java +++ b/main/src/cgeo/geocaching/LogEntry.java @@ -68,7 +68,7 @@ public final class LogEntry { public void addLogImage(final Image image) { if (logImages == null) { - logImages = new ArrayList<Image>(); + logImages = new ArrayList<>(); } logImages.add(image); } @@ -88,7 +88,7 @@ public final class LogEntry { } public CharSequence getImageTitles() { - final List<String> titles = new ArrayList<String>(5); + final List<String> titles = new ArrayList<>(5); for (Image image : getLogImages()) { if (StringUtils.isNotBlank(image.getTitle())) { titles.add(HtmlUtils.extractText(image.getTitle())); diff --git a/main/src/cgeo/geocaching/LogTrackableActivity.java b/main/src/cgeo/geocaching/LogTrackableActivity.java index fabe391..b16b4f4 100644 --- a/main/src/cgeo/geocaching/LogTrackableActivity.java +++ b/main/src/cgeo/geocaching/LogTrackableActivity.java @@ -11,16 +11,15 @@ import cgeo.geocaching.network.Network; import cgeo.geocaching.network.Parameters; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.twitter.Twitter; -import cgeo.geocaching.ui.Formatter; import cgeo.geocaching.ui.dialog.DateDialog; import cgeo.geocaching.ui.dialog.Dialogs; +import cgeo.geocaching.utils.Formatter; import cgeo.geocaching.utils.Log; 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; @@ -42,14 +41,13 @@ import java.util.List; public class LogTrackableActivity extends AbstractLoggingActivity implements DateDialog.DateDialogParent { - @InjectView(R.id.post) protected Button buttonPost; @InjectView(R.id.type) protected Button typeButton; @InjectView(R.id.date) protected Button dateButton; @InjectView(R.id.tracking) protected EditText trackingEditText; @InjectView(R.id.tweet) protected CheckBox tweetCheck; @InjectView(R.id.tweet_box) protected LinearLayout tweetBox; - private List<LogType> possibleLogTypes = new ArrayList<LogType>(); + private List<LogType> possibleLogTypes = new ArrayList<>(); private ProgressDialog waitDialog = null; private String guid = null; private String geocode = null; @@ -60,14 +58,14 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat private int attempts = 0; private Trackable trackable; - private Handler showProgressHandler = new Handler() { + private final Handler showProgressHandler = new Handler() { @Override - public void handleMessage(Message msg) { + public void handleMessage(final Message msg) { showProgress(true); } }; - private Handler loadDataHandler = new Handler() { + private final Handler loadDataHandler = new Handler() { @Override public void handleMessage(final Message msg) { @@ -89,9 +87,7 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat } gettingViewstate = false; // we're done, user can post log - - buttonPost.setEnabled(true); - buttonPost.setOnClickListener(new PostListener()); + setLoggingEnabled(true); showProgress(false); } @@ -116,7 +112,7 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat }; @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState, R.layout.logtrackable_activity); ButterKnife.inject(this); @@ -151,14 +147,14 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat } @Override - public void onConfigurationChanged(Configuration newConfig) { + public void onConfigurationChanged(final Configuration newConfig) { super.onConfigurationChanged(newConfig); init(); } @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(); @@ -170,7 +166,7 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat } @Override - public boolean onContextItemSelected(MenuItem item) { + public boolean onContextItemSelected(final MenuItem item) { final int group = item.getGroupId(); final int id = item.getItemId(); @@ -187,7 +183,7 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat registerForContextMenu(typeButton); typeButton.setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View view) { + public void onClick(final View view) { openContextMenu(view); } }); @@ -203,26 +199,22 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat } if (GCLogin.isEmpty(viewstates)) { - buttonPost.setEnabled(false); - buttonPost.setOnTouchListener(null); - buttonPost.setOnClickListener(null); - + setLoggingEnabled(false); new LoadDataThread().start(); } else { - buttonPost.setEnabled(true); - buttonPost.setOnClickListener(new PostListener()); + setLoggingEnabled(true); } disableSuggestions(trackingEditText); } @Override - public void setDate(Calendar dateIn) { + public void setDate(final Calendar dateIn) { date = dateIn; dateButton.setText(Formatter.formatShortDateVerbally(date.getTime().getTime())); } - public void setType(LogType type) { + public void setType(final LogType type) { typeSelected = type; typeButton.setText(typeSelected.getL10n()); } @@ -239,31 +231,10 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat private class DateListener implements View.OnClickListener { @Override - public void onClick(View arg0) { - final Dialog dateDialog = new DateDialog(LogTrackableActivity.this, LogTrackableActivity.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 { - - protected EditText logEditText = (EditText) findViewById(R.id.log); - - @Override - public void onClick(View arg0) { - if (!gettingViewstate) { - waitDialog = ProgressDialog.show(LogTrackableActivity.this, null, res.getString(R.string.log_saving), true); - waitDialog.setCancelable(true); - - Settings.setTrackableAction(typeSelected.id); - - final String tracking = trackingEditText.getText().toString(); - final String log = logEditText.getText().toString(); - new PostLogThread(postLogHandler, tracking, log).start(); - } else { - showToast(res.getString(R.string.err_log_load_data_still)); - } + dateDialog.show(getSupportFragmentManager(),"date_dialog"); } } @@ -303,7 +274,7 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat possibleLogTypes.clear(); possibleLogTypes.addAll(typesPre); } - } catch (Exception e) { + } catch (final Exception e) { Log.e("LogTrackableActivity.LoadDataThread.run", e); } @@ -330,7 +301,7 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat } } - public StatusCode postLogFn(String tracking, String log) { + public StatusCode postLogFn(final String tracking, final String log) { try { final StatusCode status = GCParser.postLogTrackable(guid, tracking, viewstates, typeSelected, date.get(Calendar.YEAR), (date.get(Calendar.MONTH) + 1), date.get(Calendar.DATE), log); @@ -341,7 +312,7 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat } return status; - } catch (Exception e) { + } catch (final Exception e) { Log.e("LogTrackableActivity.postLogFn", e); } @@ -360,4 +331,34 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat protected LogContext getLogContext() { return new LogContext(trackable, null); } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_send: + sendLog(); + return true; + default: + break; + } + + return super.onOptionsItemSelected(item); + } + + private void sendLog() { + if (!gettingViewstate) { + waitDialog = ProgressDialog.show(this, null, res.getString(R.string.log_saving), true); + waitDialog.setCancelable(true); + + Settings.setTrackableAction(typeSelected.id); + + final EditText logEditText = (EditText) findViewById(R.id.log); + final String tracking = trackingEditText.getText().toString(); + final String log = logEditText.getText().toString(); + new PostLogThread(postLogHandler, tracking, log).start(); + } else { + showToast(res.getString(R.string.err_log_load_data_still)); + } + } + } diff --git a/main/src/cgeo/geocaching/MainActivity.java b/main/src/cgeo/geocaching/MainActivity.java index 42dd58d..2d6e9f0 100644 --- a/main/src/cgeo/geocaching/MainActivity.java +++ b/main/src/cgeo/geocaching/MainActivity.java @@ -3,9 +3,11 @@ 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.connector.gc.GCConnector; +import cgeo.geocaching.connector.gc.GCLogin; import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.enumerations.StatusCode; import cgeo.geocaching.geopoint.Geopoint; @@ -17,26 +19,30 @@ import cgeo.geocaching.sensors.GeoDirHandler; import cgeo.geocaching.sensors.IGeoData; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.settings.SettingsActivity; -import cgeo.geocaching.ui.Formatter; import cgeo.geocaching.ui.dialog.Dialogs; import cgeo.geocaching.utils.DatabaseBackupUtils; +import cgeo.geocaching.utils.Formatter; import cgeo.geocaching.utils.Log; +import cgeo.geocaching.utils.RxUtils; +import cgeo.geocaching.utils.TextUtils; 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; import rx.android.observables.AndroidObservable; import rx.functions.Action1; -import rx.schedulers.Schedulers; 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 +51,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 +69,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 +94,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) { @@ -119,7 +127,7 @@ public class MainActivity extends AbstractActivity { }; private static String formatAddress(final Address address) { - final ArrayList<String> addressParts = new ArrayList<String>(); + final ArrayList<String> addressParts = new ArrayList<>(); final String countryName = address.getCountryName(); if (countryName != null) { @@ -167,9 +175,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 +187,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 +197,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 +216,8 @@ public class MainActivity extends AbstractActivity { Log.i("Starting " + getPackageName() + ' ' + version + " a.k.a " + Version.getVersionName(this)); init(); + + checkShowChangelog(); } @Override @@ -231,6 +245,10 @@ public class MainActivity extends AbstractActivity { new Thread() { @Override public void run() { + if (mustLogin && conn == GCConnector.getInstance()) { + // Properly log out from geocaching.com + GCLogin.getInstance().logout(); + } conn.login(firstLoginHandler, MainActivity.this); updateUserInfoHandler.sendEmptyMessage(-1); } @@ -262,6 +280,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 +299,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 +330,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 +346,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; } @@ -425,7 +451,7 @@ public class MainActivity extends AbstractActivity { } protected void selectGlobalTypeFilter() { - final List<CacheType> cacheTypes = new ArrayList<CacheType>(); + final List<CacheType> cacheTypes = new ArrayList<>(); //first add the most used types cacheTypes.add(CacheType.ALL); @@ -434,7 +460,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<>(); sorted.addAll(Arrays.asList(CacheType.values())); sorted.removeAll(cacheTypes); @@ -453,18 +479,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 +550,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); @@ -553,12 +579,13 @@ public class MainActivity extends AbstractActivity { } }); AndroidObservable.bindActivity(MainActivity.this, address.onErrorResumeNext(Observable.from(geo.getCoords().toString()))) + .subscribeOn(RxUtils.networkScheduler) .subscribe(new Action1<String>() { @Override public void call(final String address) { navLocation.setText(address); } - }, Schedulers.io()); + }); } } else { navLocation.setText(geo.getCoords().toString()); @@ -633,7 +660,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 +672,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 +689,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 +732,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 long lastChecksum = Settings.getLastChangelogChecksum(); + final long checksum = TextUtils.checksum(getString(R.string.changelog_master) + getString(R.string.changelog_release)); + Settings.setLastChangelogChecksum(checksum); + // don't show change log after new install... + if (lastChecksum > 0 && lastChecksum != checksum) { + 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..48e0c17 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; @@ -13,9 +13,9 @@ import cgeo.geocaching.sensors.GeoDirHandler; import cgeo.geocaching.sensors.IGeoData; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.ui.AbstractViewHolder; -import cgeo.geocaching.ui.Formatter; import cgeo.geocaching.ui.dialog.CoordinatesInputDialog; import cgeo.geocaching.ui.dialog.Dialogs; +import cgeo.geocaching.utils.Formatter; import cgeo.geocaching.utils.Log; import org.apache.commons.lang3.StringUtils; @@ -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; @@ -74,7 +74,7 @@ public class NavigateAnyPointActivity extends AbstractActivity { @InjectView(R.id.simple_way_point_latitude) protected TextView latitude; @InjectView(R.id.date) protected TextView date; - public ViewHolder(View rowView) { + public ViewHolder(final View rowView) { super(rowView); } } @@ -82,8 +82,8 @@ public class NavigateAnyPointActivity extends AbstractActivity { private static class DestinationHistoryAdapter extends ArrayAdapter<Destination> { private LayoutInflater inflater = null; - public DestinationHistoryAdapter(Context context, - List<Destination> objects) { + public DestinationHistoryAdapter(final Context context, + final List<Destination> objects) { super(context, 0, objects); } @@ -93,7 +93,7 @@ public class NavigateAnyPointActivity extends AbstractActivity { ViewHolder viewHolder; if (rowView == null) { - rowView = getInflater().inflate(R.layout.simple_way_point, null); + rowView = getInflater().inflate(R.layout.simple_way_point, parent, false); viewHolder = new ViewHolder(rowView); } else { @@ -105,9 +105,9 @@ public class NavigateAnyPointActivity extends AbstractActivity { return rowView; } - private static void fillViewHolder(ViewHolder viewHolder, Destination loc) { - String lonString = loc.getCoords().format(GeopointFormatter.Format.LON_DECMINUTE); - String latString = loc.getCoords().format(GeopointFormatter.Format.LAT_DECMINUTE); + private static void fillViewHolder(final ViewHolder viewHolder, final Destination loc) { + final String lonString = loc.getCoords().format(GeopointFormatter.Format.LON_DECMINUTE); + final String latString = loc.getCoords().format(GeopointFormatter.Format.LAT_DECMINUTE); viewHolder.longitude.setText(lonString); viewHolder.latitude.setText(latString); @@ -124,7 +124,7 @@ public class NavigateAnyPointActivity extends AbstractActivity { } @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState, R.layout.navigateanypoint_activity); ButterKnife.inject(this); @@ -133,7 +133,7 @@ public class NavigateAnyPointActivity extends AbstractActivity { } private void createHistoryView() { - final View pointControls = getLayoutInflater().inflate(R.layout.navigateanypoint_header, null); + final View pointControls = getLayoutInflater().inflate(R.layout.navigateanypoint_header, historyListView, false); historyListView.addHeaderView(pointControls, null, false); // inject a second time to also find the dynamically expanded views above @@ -147,8 +147,8 @@ public class NavigateAnyPointActivity extends AbstractActivity { historyListView.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 Destination) { navigateTo(((Destination) selection).getCoords()); @@ -158,8 +158,8 @@ public class NavigateAnyPointActivity extends AbstractActivity { historyListView.setOnCreateContextMenuListener(new OnCreateContextMenuListener() { @Override - public void onCreateContextMenu(ContextMenu menu, View v, - ContextMenuInfo menuInfo) { + public void onCreateContextMenu(final ContextMenu menu, final View v, + final ContextMenuInfo menuInfo) { menu.add(Menu.NONE, CONTEXT_MENU_NAVIGATE, Menu.NONE, res.getString(R.string.cache_menu_navigate)); menu.add(Menu.NONE, CONTEXT_MENU_EDIT_WAYPOINT, Menu.NONE, R.string.waypoint_edit); menu.add(Menu.NONE, CONTEXT_MENU_DELETE_WAYPOINT, Menu.NONE, R.string.waypoint_delete); @@ -168,10 +168,10 @@ public class NavigateAnyPointActivity extends AbstractActivity { } @Override - public boolean onContextItemSelected(MenuItem item) { - AdapterView.AdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); + public boolean onContextItemSelected(final MenuItem item) { + final AdapterView.AdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); final int position = (null != menuInfo) ? menuInfo.position : contextMenuItemPosition; - Object destination = historyListView.getItemAtPosition(position); + final Object destination = historyListView.getItemAtPosition(position); switch (item.getItemId()) { case CONTEXT_MENU_NAVIGATE: @@ -203,7 +203,7 @@ public class NavigateAnyPointActivity extends AbstractActivity { private TextView getEmptyHistoryFooter() { if (historyFooter == null) { - historyFooter = (TextView) getLayoutInflater().inflate(R.layout.cacheslist_footer, null); + historyFooter = (TextView) getLayoutInflater().inflate(R.layout.cacheslist_footer, historyListView, false); historyFooter.setText(R.string.search_history_empty); } return historyFooter; @@ -226,7 +226,7 @@ public class NavigateAnyPointActivity extends AbstractActivity { } @Override - public void onConfigurationChanged(Configuration newConfig) { + public void onConfigurationChanged(final Configuration newConfig) { super.onConfigurationChanged(newConfig); init(); @@ -273,63 +273,62 @@ public class NavigateAnyPointActivity extends AbstractActivity { private class CoordDialogListener implements View.OnClickListener { @Override - public void onClick(View arg0) { + public void onClick(final View arg0) { Geopoint gp = null; 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()); + final 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(final 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 { - private ChangeDistanceUnit(NavigateAnyPointActivity unitView) { + private ChangeDistanceUnit(final NavigateAnyPointActivity unitView) { this.unitView = unitView; } - private NavigateAnyPointActivity unitView; + private final NavigateAnyPointActivity unitView; @Override - public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, - long arg3) { + public void onItemSelected(final AdapterView<?> arg0, final View arg1, final int arg2, + final long arg3) { unitView.distanceUnit = (String) arg0.getItemAtPosition(arg2); } @Override - public void onNothingSelected(AdapterView<?> arg0) { + public void onNothingSelected(final AdapterView<?> arg0) { } } @Override - public boolean onCreateOptionsMenu(Menu menu) { + public boolean onCreateOptionsMenu(final Menu menu) { getMenuInflater().inflate(R.menu.navigate_any_point_activity_options, menu); menu.findItem(R.id.menu_default_navigation).setTitle(NavigationAppFactory.getDefaultNavigationApplication().getName()); return true; } @Override - public boolean onPrepareOptionsMenu(Menu menu) { + public boolean onPrepareOptionsMenu(final Menu menu) { super.onPrepareOptionsMenu(menu); try { - boolean visible = getDestination() != null; + final boolean visible = getDestination() != null; menu.findItem(R.id.menu_navigate).setVisible(visible); menu.findItem(R.id.menu_default_navigation).setVisible(visible); menu.findItem(R.id.menu_caches_around).setVisible(visible); - menu.findItem(R.id.menu_clear_history).setEnabled(!getHistoryOfSearchedLocations().isEmpty()); - } catch (RuntimeException e) { + menu.findItem(R.id.menu_clear_history).setVisible(!getHistoryOfSearchedLocations().isEmpty()); + } catch (final RuntimeException e) { // nothing } @@ -337,7 +336,7 @@ public class NavigateAnyPointActivity extends AbstractActivity { } @Override - public boolean onOptionsItemSelected(MenuItem item) { + public boolean onOptionsItemSelected(final MenuItem item) { final int menuItem = item.getItemId(); final Geopoint coords = getDestination(); @@ -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) { @@ -389,7 +387,7 @@ public class NavigateAnyPointActivity extends AbstractActivity { } } - private void removeFromHistory(Destination destination) { + private void removeFromHistory(final Destination destination) { if (getHistoryOfSearchedLocations().contains(destination)) { getHistoryOfSearchedLocations().remove(destination); @@ -429,7 +427,7 @@ public class NavigateAnyPointActivity extends AbstractActivity { navigateTo(getDestination()); } - private void navigateTo(Geopoint geopoint) { + private void navigateTo(final Geopoint geopoint) { NavigationAppFactory.startDefaultNavigationApplication(1, this, geopoint); } @@ -461,7 +459,7 @@ public class NavigateAnyPointActivity extends AbstractActivity { private class CurrentListener implements View.OnClickListener { @Override - public void onClick(View arg0) { + public void onClick(final View arg0) { final Geopoint coords = app.currentGeo().getCoords(); if (coords == null) { showToast(res.getString(R.string.err_point_unknown_position)); @@ -476,11 +474,11 @@ public class NavigateAnyPointActivity extends AbstractActivity { } private Geopoint getDestination() { - String bearingText = bearingEditText.getText().toString(); + final String bearingText = bearingEditText.getText().toString(); // combine distance from EditText and distanceUnit saved from Spinner - String distanceText = distanceEditText.getText().toString() + distanceUnit; - String latText = latButton.getText().toString(); - String lonText = lonButton.getText().toString(); + final String distanceText = distanceEditText.getText().toString() + distanceUnit; + final String latText = latButton.getText().toString(); + final String lonText = lonButton.getText().toString(); if (StringUtils.isBlank(bearingText) && StringUtils.isBlank(distanceText) && StringUtils.isBlank(latText) && StringUtils.isBlank(lonText)) { @@ -493,7 +491,7 @@ public class NavigateAnyPointActivity extends AbstractActivity { if (StringUtils.isNotBlank(latText) && StringUtils.isNotBlank(lonText)) { try { coords = new Geopoint(latText, lonText); - } catch (Geopoint.ParseException e) { + } catch (final Geopoint.ParseException e) { showToast(res.getString(e.resource)); return null; } @@ -512,7 +510,7 @@ public class NavigateAnyPointActivity extends AbstractActivity { double bearing; try { bearing = Double.parseDouble(bearingText); - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { Dialogs.message(this, R.string.err_point_bear_and_dist_title, R.string.err_point_bear_and_dist); return null; } @@ -521,7 +519,7 @@ public class NavigateAnyPointActivity extends AbstractActivity { try { distance = DistanceParser.parseDistance(distanceText, !Settings.isUseImperialUnits()); - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { showToast(res.getString(R.string.err_parse_dist)); return null; } diff --git a/main/src/cgeo/geocaching/PocketQueryList.java b/main/src/cgeo/geocaching/PocketQueryList.java index 2ac137f..21f306e 100644 --- a/main/src/cgeo/geocaching/PocketQueryList.java +++ b/main/src/cgeo/geocaching/PocketQueryList.java @@ -2,14 +2,15 @@ package cgeo.geocaching; import cgeo.geocaching.activity.ActivityMixin; import cgeo.geocaching.connector.gc.GCParser; +import cgeo.geocaching.utils.RxUtils; import org.apache.commons.collections4.CollectionUtils; + import rx.Observable; import rx.Observable.OnSubscribe; import rx.Subscriber; import rx.android.observables.AndroidObservable; import rx.functions.Action1; -import rx.schedulers.Schedulers; import android.app.Activity; import android.app.AlertDialog; @@ -52,13 +53,13 @@ public final class PocketQueryList { subscriber.onNext(GCParser.searchPocketQueryList()); subscriber.onCompleted(); } - })).subscribe(new Action1<List<PocketQueryList>>() { + })).subscribeOn(RxUtils.networkScheduler).subscribe(new Action1<List<PocketQueryList>>() { @Override public void call(final List<PocketQueryList> pocketQueryLists) { waitDialog.dismiss(); selectFromPocketQueries(activity, pocketQueryLists, runAfterwards); } - }, Schedulers.io()); + }); } private static void selectFromPocketQueries(final Activity activity, final List<PocketQueryList> pocketQueryList, final Action1<PocketQueryList> runAfterwards) { if (CollectionUtils.isEmpty(pocketQueryList)) { 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..74cc59d 100644 --- a/main/src/cgeo/geocaching/SearchResult.java +++ b/main/src/cgeo/geocaching/SearchResult.java @@ -8,14 +8,16 @@ import cgeo.geocaching.enumerations.LoadFlags.LoadFlag; import cgeo.geocaching.enumerations.LoadFlags.SaveFlag; import cgeo.geocaching.enumerations.StatusCode; import cgeo.geocaching.gcvote.GCVote; +import cgeo.geocaching.utils.RxUtils; 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; -import rx.schedulers.Schedulers; import android.os.Parcel; import android.os.Parcelable; @@ -60,13 +62,21 @@ 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 */ public SearchResult(final SearchResult searchResult) { - geocodes = new HashSet<String>(searchResult.geocodes); - filteredGeocodes = new HashSet<String>(searchResult.filteredGeocodes); + geocodes = new HashSet<>(searchResult.geocodes); + filteredGeocodes = new HashSet<>(searchResult.filteredGeocodes); error = searchResult.error; url = searchResult.url; viewstates = searchResult.viewstates; @@ -83,9 +93,9 @@ public class SearchResult implements Parcelable { * from a web page) */ public SearchResult(final Collection<String> geocodes, final int totalCountGC) { - this.geocodes = new HashSet<String>(geocodes.size()); + this.geocodes = new HashSet<>(geocodes.size()); this.geocodes.addAll(geocodes); - this.filteredGeocodes = new HashSet<String>(); + this.filteredGeocodes = new HashSet<>(); this.setTotalCountGC(totalCountGC); } @@ -99,12 +109,12 @@ public class SearchResult implements Parcelable { } public SearchResult(final Parcel in) { - final ArrayList<String> list = new ArrayList<String>(); + final ArrayList<String> list = new ArrayList<>(); in.readStringList(list); - geocodes = new HashSet<String>(list); - final ArrayList<String> filteredList = new ArrayList<String>(); + geocodes = new HashSet<>(list); + final ArrayList<String> filteredList = new ArrayList<>(); in.readStringList(filteredList); - filteredGeocodes = new HashSet<String>(filteredList); + filteredGeocodes = new HashSet<>(filteredList); error = (StatusCode) in.readSerializable(); url = in.readString(); final int length = in.readInt(); @@ -155,6 +165,7 @@ public class SearchResult implements Parcelable { return 0; } + @NonNull public Set<String> getGeocodes() { return Collections.unmodifiableSet(geocodes); } @@ -213,12 +224,12 @@ public class SearchResult implements Parcelable { SearchResult result = new SearchResult(this); result.geocodes.clear(); - final ArrayList<Geocache> includedCaches = new ArrayList<Geocache>(); + final ArrayList<Geocache> includedCaches = new ArrayList<>(); final Set<Geocache> caches = DataStore.loadCaches(geocodes, LoadFlags.LOAD_CACHE_OR_DB); 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) { @@ -261,7 +272,7 @@ public class SearchResult implements Parcelable { for (Geocache geocache : caches) { addGeocode(geocache.getGeocode()); } - DataStore.saveCaches(caches, EnumSet.of(SaveFlag.SAVE_CACHE)); + DataStore.saveCaches(caches, EnumSet.of(SaveFlag.CACHE)); } public boolean isEmpty() { @@ -313,13 +324,13 @@ public class SearchResult implements Parcelable { } }); } - }, Schedulers.io()).reduce(new SearchResult(), new Func2<SearchResult, SearchResult, SearchResult>() { + }, RxUtils.networkScheduler).reduce(new SearchResult(), new Func2<SearchResult, SearchResult, SearchResult>() { @Override public SearchResult call(final SearchResult searchResult, final SearchResult searchResult2) { searchResult.addSearchResult(searchResult2); return searchResult; } - }).toBlockingObservable().first(); + }).toBlocking().first(); } } diff --git a/main/src/cgeo/geocaching/SelectMapfileActivity.java b/main/src/cgeo/geocaching/SelectMapfileActivity.java index c617012..dc898d7 100644 --- a/main/src/cgeo/geocaching/SelectMapfileActivity.java +++ b/main/src/cgeo/geocaching/SelectMapfileActivity.java @@ -83,7 +83,7 @@ public class SelectMapfileActivity extends AbstractFileListActivity<FileSelectio @Override protected List<File> getBaseFolders() { - List<File> folders = new ArrayList<File>(); + List<File> folders = new ArrayList<>(); for (File dir : LocalStorage.getStorages()) { folders.add(new File(dir, "mfmaps")); folders.add(new File(new File(dir, "Locus"), "mapsVector")); @@ -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..ceceab9 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"; @@ -35,7 +35,7 @@ public class StaticMapsActivity extends AbstractActivity { @Extra(EXTRAS_WAYPOINT) Integer waypointId = null; @Extra(EXTRAS_GEOCODE) String geocode = null; - private final List<Bitmap> maps = new ArrayList<Bitmap>(); + private final List<Bitmap> maps = new ArrayList<>(); private LayoutInflater inflater = null; private ProgressDialog waitDialog = null; private LinearLayout smapsView = null; @@ -62,7 +62,7 @@ public class StaticMapsActivity extends AbstractActivity { } else { showStaticMaps(); } - } catch (Exception e) { + } catch (final Exception e) { Log.e("StaticMapsActivity.loadMapsHandler", e); } } @@ -83,7 +83,7 @@ public class StaticMapsActivity extends AbstractActivity { for (final Bitmap image : maps) { if (image != null) { - final ImageView map = (ImageView) inflater.inflate(R.layout.staticmaps_activity_item, null); + final ImageView map = (ImageView) inflater.inflate(R.layout.staticmaps_activity_item, smapsView, false); map.setImageBitmap(image); smapsView.addView(map); } @@ -127,7 +127,7 @@ public class StaticMapsActivity extends AbstractActivity { maps.add(image); } } - } catch (Exception e) { + } catch (final Exception e) { Log.e("StaticMapsActivity.LoadMapsThread.run", e); } } @@ -137,7 +137,7 @@ public class StaticMapsActivity extends AbstractActivity { } loadMapsHandler.sendMessage(Message.obtain()); - } catch (Exception e) { + } catch (final Exception e) { Log.e("StaticMapsActivity.LoadMapsThread.run", e); } } @@ -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..030c379 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; @@ -9,22 +8,30 @@ import cgeo.geocaching.network.Parameters; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.utils.FileUtils; import cgeo.geocaching.utils.Log; +import cgeo.geocaching.utils.RxUtils; 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.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; private static final String PREFIX_PREVIEW = "preview"; private static final String GOOGLE_STATICMAP_URL = "http://maps.google.com/maps/api/staticmap"; + private static final int GOOGLE_MAX_ZOOM = 20; private static final String SATELLITE = "satellite"; private static final String ROADMAP = "roadmap"; private static final String WAYPOINT_PREFIX = "wp"; @@ -34,9 +41,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 +54,85 @@ 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 width, final int height, final Parameters waypoints) { + return Observable.merge(downloadMap(geocode, 20, SATELLITE, markerUrl, prefix + '1', "", latlonMap, width, height, waypoints), + downloadMap(geocode, 18, SATELLITE, markerUrl, prefix + '2', "", latlonMap, width, height, waypoints), + downloadMap(geocode, 16, ROADMAP, markerUrl, prefix + '3', "", latlonMap, width, height, waypoints), + downloadMap(geocode, 14, ROADMAP, markerUrl, prefix + '4', "", latlonMap, width, height, waypoints), + downloadMap(geocode, 11, ROADMAP, markerUrl, prefix + '5', "", latlonMap, width, height, 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); + 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) { + int scale = 1; + if (width > GOOGLE_MAPS_MAX_SIZE) { + scale = 2; } - final HttpResponse httpResponse = Network.getRequest(GOOGLE_STATICMAP_URL, params); + final float aspectRatio = width / (float) height; + final int requestWidth = Math.min(width / scale, GOOGLE_MAPS_MAX_SIZE); + final int requestHeight = (aspectRatio > 1) ? Math.round(requestWidth / aspectRatio) : requestWidth; + final int requestScale = scale; + final int requestZoom = Math.min((scale == 2) ? zoom + 1 : zoom, GOOGLE_MAX_ZOOM); + return Async.fromAction(new Action0() { + @Override + public void call() { + final Parameters params = new Parameters( + "center", latlonMap, + "zoom", String.valueOf(requestZoom), + "size", String.valueOf(requestWidth) + 'x' + String.valueOf(requestHeight), + "scale", String.valueOf(requestScale), + "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, RxUtils.networkScheduler); } - 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(); + // TODO Check if this is also OK, was width -25 + final Point displaySize = Compatibility.getDisplaySize(); + + final List<Observable<String>> downloaders = new LinkedList<>(); if (Settings.isStoreOfflineMaps() && cache.getCoords() != null) { - storeCachePreviewMap(cache); - storeCacheStaticMap(cache, edge, false); + downloaders.add(storeCachePreviewMap(cache)); + downloaders.add(storeCacheStaticMap(cache, displaySize.x, displaySize.y)); } // 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, displaySize.x, displaySize.y)); } } } + + return Observable.merge(downloaders); } /** @@ -123,44 +143,49 @@ 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 width, final int height) { LocalStorage.deleteFilesWithPrefix(cache.getGeocode(), MAP_FILENAME_PREFIX + WAYPOINT_PREFIX); - for (Waypoint waypoint : cache.getWaypoints()) { - storeWaypointStaticMap(cache.getGeocode(), edge, waypoint, false); + final List<Observable<String>> downloaders = new LinkedList<>(); + for (final Waypoint waypoint : cache.getWaypoints()) { + downloaders.add(storeWaypointStaticMap(cache.getGeocode(), width, height, 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) { + // TODO Check if this is also OK, was width -25 + final Point displaySize = Compatibility.getDisplaySize(); + return storeWaypointStaticMap(cache.getGeocode(), displaySize.x, displaySize.y, 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 width, final int height, 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); + final String wpLatlonMap = waypoint.getCoords().format(Format.LAT_LON_DECDEGREE_COMMA); + final 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, width, height, null); } + return Observable.empty(); } - public static void storeCacheStaticMap(final Geocache cache, final boolean waitForResult) { - int edge = guessMaxDisplaySide(); - storeCacheStaticMap(cache, edge, waitForResult); + public static Observable<String> storeCacheStaticMap(final Geocache cache) { + // TODO Check if this is also OK, was width -25 + final Point displaySize = Compatibility.getDisplaySize(); + return storeCacheStaticMap(cache, displaySize.x, displaySize.y); } - private static void storeCacheStaticMap(final Geocache cache, final int edge, final boolean waitForResult) { + private static Observable<String> storeCacheStaticMap(final Geocache cache, final int width, final int height) { final String latlonMap = cache.getCoords().format(Format.LAT_LON_DECDEGREE_COMMA); final Parameters waypoints = new Parameters(); for (final Waypoint waypoint : cache.getWaypoints()) { @@ -172,48 +197,29 @@ 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, width, height, 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); - } - - private static int guessMaxDisplaySide() { - Point displaySize = Compatibility.getDisplaySize(); - return Math.max(displaySize.x, displaySize.y) - 25; + return downloadMap(cache.getGeocode(), 15, ROADMAP, markerUrl, PREFIX_PREVIEW, "shadow:false|", latlonMap, minSize, minSize, null); } - 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 width, final int height, + final Parameters waypoints) { + return downloadDifferentZooms(geocode, markerUrl, prefix, latlonMap, width, height, waypoints); } private static String getCacheMarkerUrl(final Geocache cache) { - StringBuilder url = new StringBuilder(MARKERS_URL); + final StringBuilder url = new StringBuilder(MARKERS_URL); 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"); @@ -221,7 +227,7 @@ public final class StaticMapsProvider { } private static String getWpMarkerUrl(final Waypoint waypoint) { - String type = waypoint.getWaypointType() != null ? waypoint.getWaypointType().id : null; + final String type = waypoint.getWaypointType() != null ? waypoint.getWaypointType().id : null; return MARKERS_URL + "marker_waypoint_" + type + ".png"; } @@ -229,8 +235,8 @@ public final class StaticMapsProvider { if (waypoint == null) { return; } - int waypointId = waypoint.getId(); - int waypointMapHash = waypoint.getStaticMapsHashcode(); + final int waypointId = waypoint.getId(); + final int waypointMapHash = waypoint.getStaticMapsHashcode(); for (int level = 1; level <= MAPS_LEVEL_MAX; level++) { final File mapFile = StaticMapsProvider.getMapFile(geocode, WAYPOINT_PREFIX + waypointId + "_" + waypointMapHash + '_' + level, false); if (!FileUtils.delete(mapFile)) { @@ -251,7 +257,7 @@ public final class StaticMapsProvider { return false; } for (int level = 1; level <= MAPS_LEVEL_MAX; level++) { - File mapFile = StaticMapsProvider.getMapFile(geocode, String.valueOf(level), false); + final File mapFile = StaticMapsProvider.getMapFile(geocode, String.valueOf(level), false); if (mapFile.exists()) { return true; } @@ -267,10 +273,10 @@ public final class StaticMapsProvider { * @return <code>true</code> if at least one map file exists; <code>false</code> otherwise */ public static boolean hasStaticMapForWaypoint(final String geocode, final Waypoint waypoint) { - int waypointId = waypoint.getId(); - int waypointMapHash = waypoint.getStaticMapsHashcode(); + final int waypointId = waypoint.getId(); + final int waypointMapHash = waypoint.getStaticMapsHashcode(); for (int level = 1; level <= MAPS_LEVEL_MAX; level++) { - File mapFile = StaticMapsProvider.getMapFile(geocode, WAYPOINT_PREFIX + waypointId + "_" + waypointMapHash + "_" + level, false); + final File mapFile = StaticMapsProvider.getMapFile(geocode, WAYPOINT_PREFIX + waypointId + "_" + waypointMapHash + "_" + level, false); if (mapFile.exists()) { return true; } @@ -286,11 +292,11 @@ public final class StaticMapsProvider { * @return <code>true</code> if all map files exist; <code>false</code> otherwise */ public static boolean hasAllStaticMapsForWaypoint(final String geocode, final Waypoint waypoint) { - int waypointId = waypoint.getId(); - int waypointMapHash = waypoint.getStaticMapsHashcode(); + final int waypointId = waypoint.getId(); + final int waypointMapHash = waypoint.getStaticMapsHashcode(); for (int level = 1; level <= MAPS_LEVEL_MAX; level++) { - File mapFile = StaticMapsProvider.getMapFile(geocode, WAYPOINT_PREFIX + waypointId + "_" + waypointMapHash + "_" + level, false); - boolean mapExists = mapFile.exists(); + final File mapFile = StaticMapsProvider.getMapFile(geocode, WAYPOINT_PREFIX + waypointId + "_" + waypointMapHash + "_" + level, false); + final boolean mapExists = mapFile.exists(); if (!mapExists) { return false; } @@ -303,8 +309,8 @@ public final class StaticMapsProvider { } public static Bitmap getWaypointMap(final String geocode, final Waypoint waypoint, final int level) { - int waypointId = waypoint.getId(); - int waypointMapHash = waypoint.getStaticMapsHashcode(); + final int waypointId = waypoint.getId(); + final int waypointMapHash = waypoint.getStaticMapsHashcode(); return decodeFile(StaticMapsProvider.getMapFile(geocode, WAYPOINT_PREFIX + waypointId + "_" + waypointMapHash + "_" + level, false)); } diff --git a/main/src/cgeo/geocaching/StatusFragment.java b/main/src/cgeo/geocaching/StatusFragment.java index f8552d7..a228363 100644 --- a/main/src/cgeo/geocaching/StatusFragment.java +++ b/main/src/cgeo/geocaching/StatusFragment.java @@ -1,5 +1,7 @@ package cgeo.geocaching; +import butterknife.ButterKnife; + import cgeo.geocaching.network.StatusUpdater; import cgeo.geocaching.network.StatusUpdater.Status; import cgeo.geocaching.utils.Log; @@ -8,6 +10,7 @@ import rx.Subscription; import rx.android.observables.AndroidObservable; import rx.functions.Action1; import rx.schedulers.Schedulers; +import rx.subscriptions.Subscriptions; import android.content.Intent; import android.content.res.Resources; @@ -23,15 +26,16 @@ import android.widget.TextView; public class StatusFragment extends Fragment { - private Subscription statusSubscription; + private Subscription statusSubscription = Subscriptions.empty(); @Override public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); final ViewGroup statusGroup = (ViewGroup) inflater.inflate(R.layout.status, container, false); - final ImageView statusIcon = (ImageView) statusGroup.findViewById(R.id.status_icon); - final TextView statusMessage = (TextView) statusGroup.findViewById(R.id.status_message); - statusSubscription = AndroidObservable.bindFragment(this, StatusUpdater.latestStatus).subscribe(new Action1<Status>() { + final ImageView statusIcon = ButterKnife.findById(statusGroup, R.id.status_icon); + final TextView statusMessage = ButterKnife.findById(statusGroup, R.id.status_message); + statusSubscription = AndroidObservable.bindFragment(this, StatusUpdater.latestStatus).subscribeOn(Schedulers.io()) + .subscribe(new Action1<Status>() { @Override public void call(final Status status) { if (status == null) { @@ -77,7 +81,7 @@ public class StatusFragment extends Fragment { statusGroup.setClickable(false); } } - }, Schedulers.io()); + }); return statusGroup; } diff --git a/main/src/cgeo/geocaching/Trackable.java b/main/src/cgeo/geocaching/Trackable.java index d532cda..9c2b044 100644 --- a/main/src/cgeo/geocaching/Trackable.java +++ b/main/src/cgeo/geocaching/Trackable.java @@ -35,7 +35,7 @@ public class Trackable implements ILogable { private String goal = null; private String details = null; private String image = null; - private List<LogEntry> logs = new ArrayList<LogEntry>(); + private List<LogEntry> logs = new ArrayList<>(); private String trackingcode = null; public String getUrl() { @@ -215,7 +215,7 @@ public class Trackable implements ILogable { } static public List<LogType> getPossibleLogTypes() { - final List<LogType> logTypes = new ArrayList<LogType>(); + final List<LogType> logTypes = new ArrayList<>(); logTypes.add(LogType.RETRIEVED_IT); logTypes.add(LogType.GRABBED_IT); logTypes.add(LogType.NOTE); diff --git a/main/src/cgeo/geocaching/TrackableActivity.java b/main/src/cgeo/geocaching/TrackableActivity.java index 81d23c9..0586f7c 100644 --- a/main/src/cgeo/geocaching/TrackableActivity.java +++ b/main/src/cgeo/geocaching/TrackableActivity.java @@ -14,10 +14,10 @@ import cgeo.geocaching.network.HtmlImage; import cgeo.geocaching.ui.AbstractCachingPageViewCreator; import cgeo.geocaching.ui.AnchorAwareLinkMovementMethod; import cgeo.geocaching.ui.CacheDetailsCreator; -import cgeo.geocaching.ui.Formatter; import cgeo.geocaching.ui.UserActionsClickListener; import cgeo.geocaching.ui.UserNameClickListener; import cgeo.geocaching.ui.logs.TrackableLogsViewCreator; +import cgeo.geocaching.utils.Formatter; import cgeo.geocaching.utils.HtmlUtils; import cgeo.geocaching.utils.Log; import cgeo.geocaching.utils.UnknownTagsHandler; @@ -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,16 @@ 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.view.ViewGroup; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ScrollView; @@ -74,7 +79,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 +115,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 +217,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 +231,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 +250,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 +260,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 +287,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 +311,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 +337,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,13 +348,13 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi } @Override - protected String getTitle(Page page) { + protected String getTitle(final Page page) { return res.getString(page.resId); } @Override protected Pair<List<? extends Page>, Integer> getOrderedPages() { - final List<Page> pages = new ArrayList<TrackableActivity.Page>(); + final List<Page> pages = new ArrayList<>(); pages.add(Page.DETAILS); if (!trackable.getLogs().isEmpty()) { pages.add(Page.LOGS); @@ -384,21 +373,21 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi @InjectView(R.id.image) protected LinearLayout imageView; @Override - public ScrollView getDispatchedView() { - view = (ScrollView) getLayoutInflater().inflate(R.layout.trackable_details_view, null); + public ScrollView getDispatchedView(final ViewGroup parentView) { + view = (ScrollView) getLayoutInflater().inflate(R.layout.trackable_details_view, parentView, false); ButterKnife.inject(this, view); final CacheDetailsCreator details = new CacheDetailsCreator(TrackableActivity.this, detailsList); // 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 +399,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 +444,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,41 +468,41 @@ 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 if (StringUtils.isNotBlank(HtmlUtils.extractText(trackable.getGoal()))) { goalBox.setVisibility(View.VISIBLE); goalTextView.setVisibility(View.VISIBLE); - goalTextView.setText(Html.fromHtml(trackable.getGoal(), new HtmlImage(geocode, true, 0, false), null), TextView.BufferType.SPANNABLE); + goalTextView.setText(Html.fromHtml(trackable.getGoal(), new HtmlImage(geocode, true, 0, false, goalTextView), null), TextView.BufferType.SPANNABLE); goalTextView.setMovementMethod(AnchorAwareLinkMovementMethod.getInstance()); - registerForContextMenu(goalTextView); + addContextMenu(goalTextView); } // trackable details if (StringUtils.isNotBlank(HtmlUtils.extractText(trackable.getDetails()))) { detailsBox.setVisibility(View.VISIBLE); detailsTextView.setVisibility(View.VISIBLE); - detailsTextView.setText(Html.fromHtml(trackable.getDetails(), new HtmlImage(geocode, true, 0, false), new UnknownTagsHandler()), TextView.BufferType.SPANNABLE); + detailsTextView.setText(Html.fromHtml(trackable.getDetails(), new HtmlImage(geocode, true, 0, false, detailsTextView), new UnknownTagsHandler()), TextView.BufferType.SPANNABLE); detailsTextView.setMovementMethod(AnchorAwareLinkMovementMethod.getInstance()); - registerForContextMenu(detailsTextView); + addContextMenu(detailsTextView); } // trackable image if (StringUtils.isNotBlank(trackable.getImage())) { imageBox.setVisibility(View.VISIBLE); - final ImageView trackableImage = (ImageView) inflater.inflate(R.layout.trackable_image, null); + final ImageView trackableImage = (ImageView) inflater.inflate(R.layout.trackable_image, imageView, false); trackableImage.setImageResource(R.drawable.image_not_loaded); trackableImage.setClickable(true); @@ -538,4 +527,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..a2cdaf7 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; @@ -27,7 +27,7 @@ public class UsefulAppsActivity extends AbstractActivity { @InjectView(R.id.image) protected ImageView image; @InjectView(R.id.description) protected TextView description; - public ViewHolder(View rowView) { + public ViewHolder(final View rowView) { super(rowView); } } @@ -45,7 +45,7 @@ public class UsefulAppsActivity extends AbstractActivity { this.packageName = packageName; } - private void installFromMarket(Activity activity) { + private void installFromMarket(final Activity activity) { try { // allow also opening pure http URLs in addition to market packages final String url = (packageName.startsWith("http:")) ? packageName : "market://details?id=" + packageName; @@ -53,7 +53,7 @@ public class UsefulAppsActivity extends AbstractActivity { marketIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); activity.startActivity(marketIntent); - } catch (RuntimeException e) { + } catch (final RuntimeException e) { // market not available in standard emulator } } @@ -72,17 +72,17 @@ public class UsefulAppsActivity extends AbstractActivity { }; @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState, R.layout.usefulapps_activity); ButterKnife.inject(this); list.setAdapter(new ArrayAdapter<HelperApp>(this, R.layout.usefulapps_item, HELPER_APPS) { @Override - public View getView(int position, View convertView, android.view.ViewGroup parent) { + public View getView(final int position, final View convertView, final android.view.ViewGroup parent) { View rowView = convertView; if (null == rowView) { - rowView = getLayoutInflater().inflate(R.layout.usefulapps_item, null); + rowView = getLayoutInflater().inflate(R.layout.usefulapps_item, parent, false); } ViewHolder holder = (ViewHolder) rowView.getTag(); if (null == holder) { @@ -94,7 +94,7 @@ public class UsefulAppsActivity extends AbstractActivity { return rowView; } - private void fillViewHolder(ViewHolder holder, HelperApp app) { + private void fillViewHolder(final ViewHolder holder, final HelperApp app) { holder.title.setText(res.getString(app.titleId)); holder.image.setImageDrawable(res.getDrawable(app.iconId)); holder.description.setText(Html.fromHtml(res.getString(app.descriptionId))); @@ -104,8 +104,8 @@ public class UsefulAppsActivity extends AbstractActivity { list.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - HelperApp helperApp = HELPER_APPS[position]; + public void onItemClick(final AdapterView<?> parent, final View view, final int position, final long id) { + final HelperApp helperApp = HELPER_APPS[position]; helperApp.installFromMarket(UsefulAppsActivity.this); } }); diff --git a/main/src/cgeo/geocaching/Waypoint.java b/main/src/cgeo/geocaching/Waypoint.java index efedff5..7381aab 100644 --- a/main/src/cgeo/geocaching/Waypoint.java +++ b/main/src/cgeo/geocaching/Waypoint.java @@ -89,7 +89,7 @@ public class Waypoint implements IWaypoint { public static void mergeWayPoints(final List<Waypoint> newPoints, final List<Waypoint> oldPoints, final boolean forceMerge) { // Build a map of new waypoints for faster subsequent lookups - final Map<String, Waypoint> newPrefixes = new HashMap<String, Waypoint>(newPoints.size()); + final Map<String, Waypoint> newPrefixes = new HashMap<>(newPoints.size()); for (final Waypoint waypoint : newPoints) { newPrefixes.put(waypoint.getPrefix(), waypoint); } @@ -122,22 +122,26 @@ public class Waypoint implements IWaypoint { } private int computeOrder() { + // first parking, then trailhead (as start of the journey) + // puzzles, stages, waypoints can all be mixed + // at last the final and the original coordinates of the final switch (waypointType) { case PARKING: return -1; case TRAILHEAD: return 1; - case STAGE: // puzzles and stages with same value - return 2; + case STAGE: case PUZZLE: + case WAYPOINT: return 2; case FINAL: return 3; - case OWN: + case ORIGINAL: return 4; - default: - return 0; + case OWN: + return 5; } + return 0; } private int order() { @@ -151,13 +155,13 @@ public class Waypoint implements IWaypoint { return prefix; } - public void setPrefix(String prefix) { + public void setPrefix(final String prefix) { this.prefix = prefix; cachedOrder = ORDER_UNDEFINED; } 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 @@ -165,7 +169,7 @@ public class Waypoint implements IWaypoint { return id; } - public void setId(int id) { + public void setId(final int id) { this.id = id; } @@ -174,7 +178,7 @@ public class Waypoint implements IWaypoint { return geocode; } - public void setGeocode(String geocode) { + public void setGeocode(final String geocode) { this.geocode = StringUtils.upperCase(geocode); } @@ -187,7 +191,7 @@ public class Waypoint implements IWaypoint { return lookup; } - public void setLookup(String lookup) { + public void setLookup(final String lookup) { this.lookup = lookup; } @@ -196,7 +200,7 @@ public class Waypoint implements IWaypoint { return name; } - public void setName(String name) { + public void setName(final String name) { this.name = name; } @@ -204,7 +208,7 @@ public class Waypoint implements IWaypoint { return latlon; } - public void setLatlon(String latlon) { + public void setLatlon(final String latlon) { this.latlon = latlon; } @@ -213,7 +217,7 @@ public class Waypoint implements IWaypoint { return coords; } - public void setCoords(Geopoint coords) { + public void setCoords(final Geopoint coords) { this.coords = coords; } @@ -221,7 +225,7 @@ public class Waypoint implements IWaypoint { return note; } - public void setNote(String note) { + public void setNote(final String note) { this.note = note; } @@ -244,7 +248,7 @@ public class Waypoint implements IWaypoint { return "waypoint"; } - public void setVisited(boolean visited) { + public void setVisited(final boolean visited) { this.visited = visited; } @@ -267,7 +271,7 @@ public class Waypoint implements IWaypoint { public static final Comparator<? super Waypoint> WAYPOINT_COMPARATOR = new Comparator<Waypoint>() { @Override - public int compare(Waypoint left, Waypoint right) { + public int compare(final Waypoint left, final Waypoint right) { return left.order() - right.order(); } }; @@ -282,7 +286,7 @@ public class Waypoint implements IWaypoint { String gpxId = prefix; if (StringUtils.isNotBlank(geocode)) { - Geocache cache = DataStore.loadCache(geocode, LoadFlags.LOAD_CACHE_OR_DB); + final Geocache cache = DataStore.loadCache(geocode, LoadFlags.LOAD_CACHE_OR_DB); if (cache != null) { gpxId = cache.getWaypointGpxId(prefix); } @@ -298,7 +302,7 @@ public class Waypoint implements IWaypoint { * @return a collection of found waypoints */ public static Collection<Waypoint> parseWaypointsFromNote(@NonNull final String initialNote) { - final List<Waypoint> waypoints = new LinkedList<Waypoint>(); + final List<Waypoint> waypoints = new LinkedList<>(); final Pattern COORDPATTERN = Pattern.compile("\\b[nNsS]{1}\\s*\\d"); // begin of coordinates String note = initialNote; 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..64186a0 100644 --- a/main/src/cgeo/geocaching/activity/AbstractViewPagerActivity.java +++ b/main/src/cgeo/geocaching/activity/AbstractViewPagerActivity.java @@ -5,6 +5,7 @@ import cgeo.geocaching.utils.Log; import com.viewpagerindicator.TitlePageIndicator; import com.viewpagerindicator.TitleProvider; + import org.apache.commons.lang3.tuple.Pair; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; @@ -30,24 +31,24 @@ 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. * * TODO Move to adapter */ - private final List<Page> pageOrder = new ArrayList<Page>(); + private final List<Page> pageOrder = new ArrayList<>(); /** * Instances of all {@link PageViewCreator}. */ - private final Map<Page, PageViewCreator> viewCreators = new HashMap<Page, PageViewCreator>(); + private final Map<Page, PageViewCreator> viewCreators = new HashMap<>(); /** * Store the states of the page views to be able to persist them when destroyed and reinstantiated again */ - private final Map<Page, Bundle> viewStates = new HashMap<Page, Bundle>(); + private final Map<Page, Bundle> viewStates = new HashMap<>(); /** * The {@link ViewPager} for this activity. */ @@ -69,14 +70,14 @@ public abstract class AbstractViewPagerActivity<Page extends Enum<Page>> extends * * @return */ - public View getDispatchedView(); + public View getDispatchedView(final ViewGroup parentView); /** * Returns a (maybe cached) view. * * @return */ - public View getView(); + public View getView(final ViewGroup parentView); /** * Handles changed data-sets. @@ -109,16 +110,17 @@ public abstract class AbstractViewPagerActivity<Page extends Enum<Page>> extends private class ViewPagerAdapter extends PagerAdapter implements TitleProvider { @Override - public void destroyItem(ViewGroup container, int position, Object object) { + public void destroyItem(final ViewGroup container, final int position, final Object object) { if (position >= pageOrder.size()) { return; } final Page page = pageOrder.get(position); // Store the state of the view if the page supports it - PageViewCreator creator = viewCreators.get(page); + final PageViewCreator creator = viewCreators.get(page); if (creator != null) { @Nullable + final Bundle state = creator.getViewState(); if (state != null) { viewStates.put(page, state); @@ -129,7 +131,7 @@ public abstract class AbstractViewPagerActivity<Page extends Enum<Page>> extends } @Override - public void finishUpdate(ViewGroup container) { + public void finishUpdate(final ViewGroup container) { } @Override @@ -138,7 +140,7 @@ public abstract class AbstractViewPagerActivity<Page extends Enum<Page>> extends } @Override - public Object instantiateItem(ViewGroup container, int position) { + public Object instantiateItem(final ViewGroup container, final int position) { final Page page = pageOrder.get(position); @@ -156,29 +158,29 @@ public abstract class AbstractViewPagerActivity<Page extends Enum<Page>> extends if (null != creator) { // Result from getView() is maybe cached, but it should be valid because the // creator should be informed about data-changes with notifyDataSetChanged() - view = creator.getView(); + view = creator.getView(container); // Restore the state of the view if the page supports it - Bundle state = viewStates.get(page); + final Bundle state = viewStates.get(page); if (state != null) { creator.setViewState(state); } container.addView(view, 0); } - } catch (Exception e) { + } catch (final Exception e) { Log.e("ViewPagerAdapter.instantiateItem ", e); } return view; } @Override - public boolean isViewFromObject(View view, Object object) { + public boolean isViewFromObject(final View view, final Object object) { return view == object; } @Override - public void restoreState(Parcelable arg0, ClassLoader arg1) { + public void restoreState(final Parcelable arg0, final ClassLoader arg1) { } @Override @@ -187,18 +189,18 @@ public abstract class AbstractViewPagerActivity<Page extends Enum<Page>> extends } @Override - public void startUpdate(ViewGroup arg0) { + public void startUpdate(final ViewGroup arg0) { } @Override - public int getItemPosition(Object object) { + public int getItemPosition(final Object object) { // We are doing the caching. So pretend that the view is gone. // The ViewPager will get it back in instantiateItem() return POSITION_NONE; } @Override - public String getTitle(int position) { + public String getTitle(final int position) { final Page page = pageOrder.get(position); if (null == page) { return ""; @@ -216,7 +218,7 @@ public abstract class AbstractViewPagerActivity<Page extends Enum<Page>> extends * @param pageSelectedListener * page selection listener or <code>null</code> */ - protected final void createViewPager(int startPageIndex, final OnPageSelectedListener pageSelectedListener) { + protected final void createViewPager(final int startPageIndex, final OnPageSelectedListener pageSelectedListener) { // initialize ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager); viewPagerAdapter = new ViewPagerAdapter(); @@ -227,16 +229,16 @@ public abstract class AbstractViewPagerActivity<Page extends Enum<Page>> extends if (pageSelectedListener != null) { titleIndicator.setOnPageChangeListener(new OnPageChangeListener() { @Override - public void onPageSelected(int position) { + public void onPageSelected(final int position) { pageSelectedListener.onPageSelected(position); } @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + public void onPageScrolled(final int position, final float positionOffset, final int positionOffsetPixels) { } @Override - public void onPageScrollStateChanged(int state) { + public void onPageScrollStateChanged(final int state) { } }); } @@ -267,11 +269,11 @@ public abstract class AbstractViewPagerActivity<Page extends Enum<Page>> extends protected final void reinitializeViewPager() { // notify all creators that the data has changed - for (PageViewCreator creator : viewCreators.values()) { + for (final PageViewCreator creator : viewCreators.values()) { creator.notifyDataSetChanged(); } // reset the stored view states of all pages - for (Bundle state : viewStates.values()) { + for (final Bundle state : viewStates.values()) { state.clear(); } @@ -301,19 +303,19 @@ public abstract class AbstractViewPagerActivity<Page extends Enum<Page>> extends */ protected abstract Pair<List<? extends Page>, Integer> getOrderedPages(); - public final Page getPage(int position) { + public final Page getPage(final int position) { return pageOrder.get(position); } - protected final int getPageIndex(Page page) { + protected final int getPageIndex(final Page page) { return pageOrder.indexOf(page); } - protected final PageViewCreator getViewCreator(Page page) { + protected final PageViewCreator getViewCreator(final Page page) { return viewCreators.get(page); } - protected final boolean isCurrentPage(Page page) { + protected final boolean isCurrentPage(final Page page) { return getCurrentItem() == getPageIndex(page); } 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..b58d3ae 100644 --- a/main/src/cgeo/geocaching/activity/ActivityMixin.java +++ b/main/src/cgeo/geocaching/activity/ActivityMixin.java @@ -1,55 +1,47 @@ 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.os.Build.VERSION; +import android.os.Build.VERSION_CODES; +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,33 +54,53 @@ 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) { + if (Settings.isLightSkin() && VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { return R.style.popup_light; } - return R.style.popup_dark; } + /** + * Show a long toast message to the user. This can be called from any thread. + * + * @param activity the activity the user is facing + * @param resId the message + */ public static void showToast(final Activity activity, final int resId) { ActivityMixin.showToast(activity, activity.getString(resId)); } - public static void showToast(final Activity activity, final String text) { + private static void postShowToast(final Activity activity, final String text, final int toastDuration) { if (StringUtils.isNotBlank(text)) { - Toast toast = Toast.makeText(activity, text, Toast.LENGTH_LONG); - - toast.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM, 0, 100); - toast.show(); + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + final Toast toast = Toast.makeText(activity, text, toastDuration); + toast.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM, 0, 100); + toast.show(); + } + }); } } - public static void showShortToast(final Activity activity, final String text) { - if (StringUtils.isNotBlank(text)) { - Toast toast = Toast.makeText(activity, text, Toast.LENGTH_SHORT); + /** + * Show a long toast message to the user. This can be called from any thread. + * + * @param activity the activity the user is facing + * @param text the message + */ + public static void showToast(final Activity activity, final String text) { + postShowToast(activity, text, Toast.LENGTH_LONG); + } - toast.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM, 0, 100); - toast.show(); - } + /** + * Show a short toast message to the user. This can be called from any thread. + * + * @param activity the activity the user is facing + * @param text the message + */ + public static void showShortToast(final Activity activity, final String text) { + postShowToast(activity, text, Toast.LENGTH_SHORT); } public static void keepScreenOn(final Activity abstractActivity, boolean keepScreenOn) { @@ -98,7 +110,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 +144,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/AbstractLocusApp.java b/main/src/cgeo/geocaching/apps/AbstractLocusApp.java index 8e9181d..baf36a4 100644 --- a/main/src/cgeo/geocaching/apps/AbstractLocusApp.java +++ b/main/src/cgeo/geocaching/apps/AbstractLocusApp.java @@ -77,7 +77,7 @@ public abstract class AbstractLocusApp extends AbstractApp { if (pd.getPoints().size() <= 1000) { DisplayData.sendData(activity, pd, export); } else { - final ArrayList<PointsData> data = new ArrayList<PointsData>(); + final ArrayList<PointsData> data = new ArrayList<>(); data.add(pd); DisplayData.sendDataCursor(activity, data, "content://" + LocusDataStorageProvider.class.getCanonicalName().toLowerCase(Locale.US), @@ -140,7 +140,7 @@ public abstract class AbstractLocusApp extends AbstractApp { pg.found = cache.isFound(); if (withWaypoints && cache.hasWaypoints()) { - pg.waypoints = new ArrayList<PointGeocachingDataWaypoint>(); + pg.waypoints = new ArrayList<>(); for (Waypoint waypoint : cache.getWaypoints()) { if (waypoint == null || waypoint.getCoords() == null) { continue; diff --git a/main/src/cgeo/geocaching/apps/cache/CacheBeaconApp.java b/main/src/cgeo/geocaching/apps/cache/CacheBeaconApp.java deleted file mode 100644 index 34c9074..0000000 --- a/main/src/cgeo/geocaching/apps/cache/CacheBeaconApp.java +++ /dev/null @@ -1,18 +0,0 @@ -package cgeo.geocaching.apps.cache; - -import cgeo.geocaching.Geocache; -import cgeo.geocaching.R; -import cgeo.geocaching.enumerations.CacheAttribute; - -public class CacheBeaconApp extends AbstractGeneralApp { - - public CacheBeaconApp() { - super(getString(R.string.cache_menu_cachebeacon), R.id.cache_app_cache_beacon, "de.fun2code.android.cachebeacon"); - } - - @Override - public boolean isEnabled(Geocache cache) { - return cache.hasAttribute(CacheAttribute.WIRELESSBEACON, true); - } - -} diff --git a/main/src/cgeo/geocaching/apps/cache/GccApp.java b/main/src/cgeo/geocaching/apps/cache/GccApp.java 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/DownloadStaticMapsApp.java b/main/src/cgeo/geocaching/apps/cache/navi/DownloadStaticMapsApp.java index 19b5e02..e9bdb74 100644 --- a/main/src/cgeo/geocaching/apps/cache/navi/DownloadStaticMapsApp.java +++ b/main/src/cgeo/geocaching/apps/cache/navi/DownloadStaticMapsApp.java @@ -13,22 +13,22 @@ class DownloadStaticMapsApp extends AbstractStaticMapsApp { } @Override - public boolean isEnabled(Geocache cache) { - return !cache.hasStaticMap(); + public boolean isEnabled(final Geocache cache) { + return cache.isOffline() && !cache.hasStaticMap(); } @Override - public boolean isEnabled(Waypoint waypoint) { + public boolean isEnabled(final Waypoint waypoint) { return !hasStaticMap(waypoint); } @Override - public void navigate(Activity activity, Geocache cache) { + public void navigate(final Activity activity, final Geocache cache) { invokeStaticMaps(activity, cache, null, true); } @Override - public void navigate(Activity activity, Waypoint waypoint) { + public void navigate(final Activity activity, final Waypoint waypoint) { invokeStaticMaps(activity, null, waypoint, 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..e24c055 100644 --- a/main/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java +++ b/main/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java @@ -7,8 +7,6 @@ import cgeo.geocaching.Waypoint; 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; @@ -69,8 +67,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); @@ -144,7 +140,7 @@ public final class NavigationAppFactory extends AbstractAppFactory { public static void showNavigationMenu(final Activity activity, final Geocache cache, final Waypoint waypoint, final Geopoint destination, final boolean showInternalMap, final boolean showDefaultNavigation) { - final List<NavigationAppsEnum> items = new ArrayList<NavigationAppFactory.NavigationAppsEnum>(); + final List<NavigationAppsEnum> items = new ArrayList<>(); final int defaultNavigationTool = Settings.getDefaultNavigationTool(); for (final NavigationAppsEnum navApp : getInstalledNavigationApps()) { if ((showInternalMap || !(navApp.app instanceof InternalMap)) && @@ -176,13 +172,13 @@ public final class NavigationAppFactory extends AbstractAppFactory { * Using an ArrayAdapter with list of NavigationAppsEnum items avoids * handling between mapping list positions allows us to do dynamic filtering of the list based on use case. */ - final ArrayAdapter<NavigationAppsEnum> adapter = new ArrayAdapter<NavigationAppsEnum>(activity, android.R.layout.select_dialog_item, items); + final ArrayAdapter<NavigationAppsEnum> adapter = new ArrayAdapter<>(activity, android.R.layout.select_dialog_item, items); final AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setTitle(R.string.cache_menu_navigate); builder.setAdapter(adapter, new DialogInterface.OnClickListener() { @Override - public void onClick(DialogInterface dialog, int item) { + public void onClick(final DialogInterface dialog, final int item) { final NavigationAppsEnum selectedItem = adapter.getItem(item); invokeNavigation(activity, cache, waypoint, destination, selectedItem.app); } @@ -197,7 +193,7 @@ public final class NavigationAppFactory extends AbstractAppFactory { * @return */ public static List<NavigationAppsEnum> getInstalledNavigationApps() { - final List<NavigationAppsEnum> installedNavigationApps = new ArrayList<NavigationAppsEnum>(); + final List<NavigationAppsEnum> installedNavigationApps = new ArrayList<>(); for (final NavigationAppsEnum appEnum : NavigationAppsEnum.values()) { if (appEnum.app.isInstalled()) { installedNavigationApps.add(appEnum); @@ -212,7 +208,7 @@ public final class NavigationAppFactory extends AbstractAppFactory { * @return */ public static List<NavigationAppsEnum> getInstalledDefaultNavigationApps() { - final List<NavigationAppsEnum> installedNavigationApps = new ArrayList<NavigationAppsEnum>(); + final List<NavigationAppsEnum> installedNavigationApps = new ArrayList<>(); for (final NavigationAppsEnum appEnum : NavigationAppsEnum.values()) { if (appEnum.app.isInstalled() && appEnum.app.isUsableAsDefaultNavigationApp()) { installedNavigationApps.add(appEnum); @@ -230,27 +226,27 @@ public final class NavigationAppFactory extends AbstractAppFactory { * @param cache * @return */ - public static boolean onMenuItemSelected(final MenuItem item, Activity activity, Geocache cache) { + public static boolean onMenuItemSelected(final MenuItem item, final Activity activity, final Geocache cache) { final App menuItem = getAppFromMenuItem(item); navigateCache(activity, cache, menuItem); return menuItem != null; } - private static void navigateCache(Activity activity, Geocache cache, @Nullable App app) { + private static void navigateCache(final Activity activity, final Geocache cache, @Nullable final App app) { if (app instanceof CacheNavigationApp) { final CacheNavigationApp cacheApp = (CacheNavigationApp) app; cacheApp.navigate(activity, cache); } } - private static void navigateWaypoint(Activity activity, Waypoint waypoint, @Nullable App app) { + private static void navigateWaypoint(final Activity activity, final Waypoint waypoint, @Nullable final App app) { if (app instanceof WaypointNavigationApp) { final WaypointNavigationApp waypointApp = (WaypointNavigationApp) app; waypointApp.navigate(activity, waypoint); } } - private static void navigateGeopoint(Activity activity, Geopoint destination, App app) { + private static void navigateGeopoint(final Activity activity, final Geopoint destination, final App app) { if (app instanceof GeopointNavigationApp) { final GeopointNavigationApp geopointApp = (GeopointNavigationApp) app; geopointApp.navigate(activity, destination); @@ -258,7 +254,7 @@ public final class NavigationAppFactory extends AbstractAppFactory { } @Nullable - private static App getAppFromMenuItem(MenuItem item) { + private static App getAppFromMenuItem(final MenuItem item) { final int id = item.getItemId(); for (final NavigationAppsEnum navApp : NavigationAppsEnum.values()) { if (navApp.id == id) { @@ -276,7 +272,7 @@ public final class NavigationAppFactory extends AbstractAppFactory { * @param activity * @param cache */ - public static void startDefaultNavigationApplication(int defaultNavigation, Activity activity, Geocache cache) { + public static void startDefaultNavigationApplication(final int defaultNavigation, final Activity activity, final Geocache cache) { if (cache == null || cache.getCoords() == null) { ActivityMixin.showToast(activity, CgeoApplication.getInstance().getString(R.string.err_location_unknown)); return; @@ -285,7 +281,7 @@ public final class NavigationAppFactory extends AbstractAppFactory { navigateCache(activity, cache, getDefaultNavigationApplication(defaultNavigation)); } - private static App getDefaultNavigationApplication(int defaultNavigation) { + private static App getDefaultNavigationApplication(final int defaultNavigation) { if (defaultNavigation == 2) { return getNavigationAppForId(Settings.getDefaultNavigationTool2()); } @@ -298,7 +294,7 @@ public final class NavigationAppFactory extends AbstractAppFactory { * @param activity * @param waypoint */ - public static void startDefaultNavigationApplication(int defaultNavigation, Activity activity, Waypoint waypoint) { + public static void startDefaultNavigationApplication(final int defaultNavigation, final Activity activity, final Waypoint waypoint) { if (waypoint == null || waypoint.getCoords() == null) { ActivityMixin.showToast(activity, CgeoApplication.getInstance().getString(R.string.err_location_unknown)); return; @@ -312,7 +308,7 @@ public final class NavigationAppFactory extends AbstractAppFactory { * @param activity * @param destination */ - public static void startDefaultNavigationApplication(int defaultNavigation, Activity activity, final Geopoint destination) { + public static void startDefaultNavigationApplication(final int defaultNavigation, final Activity activity, final Geopoint destination) { if (destination == null) { ActivityMixin.showToast(activity, CgeoApplication.getInstance().getString(R.string.err_location_unknown)); return; 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/apps/cache/navi/RMapsApp.java b/main/src/cgeo/geocaching/apps/cache/navi/RMapsApp.java index 82d144e..4dbb46c 100644 --- a/main/src/cgeo/geocaching/apps/cache/navi/RMapsApp.java +++ b/main/src/cgeo/geocaching/apps/cache/navi/RMapsApp.java @@ -25,7 +25,7 @@ class RMapsApp extends AbstractPointNavigationApp { } private static void navigate(Activity activity, Geopoint coords, String code, String name) { - final ArrayList<String> locations = new ArrayList<String>(); + final ArrayList<String> locations = new ArrayList<>(); locations.add(coords.format(Format.LAT_LON_DECDEGREE_COMMA) + ";" + code + ";" + name); final Intent intent = new Intent(INTENT); intent.putStringArrayListExtra("locations", locations); diff --git a/main/src/cgeo/geocaching/apps/cache/navi/StaticMapApp.java b/main/src/cgeo/geocaching/apps/cache/navi/StaticMapApp.java index 9e1b3f0..5151088 100644 --- a/main/src/cgeo/geocaching/apps/cache/navi/StaticMapApp.java +++ b/main/src/cgeo/geocaching/apps/cache/navi/StaticMapApp.java @@ -13,22 +13,22 @@ class StaticMapApp extends AbstractStaticMapsApp { } @Override - public boolean isEnabled(Geocache cache) { - return cache.hasStaticMap(); + public boolean isEnabled(final Geocache cache) { + return cache.isOffline() && cache.hasStaticMap(); } @Override - public boolean isEnabled(Waypoint waypoint) { + public boolean isEnabled(final Waypoint waypoint) { return hasStaticMap(waypoint); } @Override - public void navigate(Activity activity, Geocache cache) { + public void navigate(final Activity activity, final Geocache cache) { invokeStaticMaps(activity, cache, null, false); } @Override - public void navigate(Activity activity, Waypoint waypoint) { + public void navigate(final Activity activity, final Waypoint waypoint) { invokeStaticMaps(activity, null, waypoint, false); } } diff --git a/main/src/cgeo/geocaching/apps/cachelist/CacheListAppFactory.java b/main/src/cgeo/geocaching/apps/cachelist/CacheListAppFactory.java index 8212111..5886168 100644 --- a/main/src/cgeo/geocaching/apps/cachelist/CacheListAppFactory.java +++ b/main/src/cgeo/geocaching/apps/cachelist/CacheListAppFactory.java @@ -51,7 +51,7 @@ public final class CacheListAppFactory extends AbstractAppFactory { } private static List<CacheListApp> getActiveApps() { - final List<CacheListApp> activeApps = new ArrayList<CacheListApp>(LazyHolder.apps.length); + final List<CacheListApp> activeApps = new ArrayList<>(LazyHolder.apps.length); for (final CacheListApp app : LazyHolder.apps) { if (app.isInstalled()) { activeApps.add(app); 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/concurrent/BlockingThreadPool.java b/main/src/cgeo/geocaching/concurrent/BlockingThreadPool.java index 8e60d44..a6d7e9b 100644 --- a/main/src/cgeo/geocaching/concurrent/BlockingThreadPool.java +++ b/main/src/cgeo/geocaching/concurrent/BlockingThreadPool.java @@ -27,7 +27,7 @@ public class BlockingThreadPool { */ public BlockingThreadPool(int poolSize, int priority) { ThreadFactory threadFactory = new PriorityThreadFactory(priority); - this.queue = new ArrayBlockingQueue<Runnable>(poolSize, true); + this.queue = new ArrayBlockingQueue<>(poolSize, true); this.executor = new ThreadPoolExecutor(0, poolSize, 5, TimeUnit.SECONDS, this.queue); this.executor.setThreadFactory(threadFactory); } diff --git a/main/src/cgeo/geocaching/connector/AbstractConnector.java b/main/src/cgeo/geocaching/connector/AbstractConnector.java index 6d8d79e..7e1ca13 100644 --- a/main/src/cgeo/geocaching/connector/AbstractConnector.java +++ b/main/src/cgeo/geocaching/connector/AbstractConnector.java @@ -160,7 +160,7 @@ public abstract class AbstractConnector implements IConnector { @Override public List<LogType> getPossibleLogTypes(Geocache geocache) { - final List<LogType> logTypes = new ArrayList<LogType>(); + final List<LogType> logTypes = new ArrayList<>(); if (geocache.isEventCache()) { logTypes.add(LogType.WILL_ATTEND); logTypes.add(LogType.ATTENDED); @@ -214,7 +214,7 @@ public abstract class AbstractConnector implements IConnector { @Override public final Collection<String> getCapabilities() { - ArrayList<String> list = new ArrayList<String>(); + ArrayList<String> list = new ArrayList<>(); addCapability(list, ISearchByViewPort.class, R.string.feature_search_live_map); addCapability(list, ISearchByKeyword.class, R.string.feature_search_keyword); addCapability(list, ISearchByCenter.class, R.string.feature_search_center); @@ -281,7 +281,7 @@ public abstract class AbstractConnector implements IConnector { */ static @NonNull public List<UserAction> getDefaultUserActions() { - final ArrayList<UserAction> actions = new ArrayList<UserAction>(); + final ArrayList<UserAction> actions = new ArrayList<>(); if (ContactsAddon.isAvailable()) { actions.add(new UserAction(R.string.user_menu_open_contact, new Action1<UserAction.Context>() { diff --git a/main/src/cgeo/geocaching/connector/ConnectorFactory.java b/main/src/cgeo/geocaching/connector/ConnectorFactory.java index 1f23df8..e6ef829 100644 --- a/main/src/cgeo/geocaching/connector/ConnectorFactory.java +++ b/main/src/cgeo/geocaching/connector/ConnectorFactory.java @@ -13,7 +13,6 @@ import cgeo.geocaching.connector.capability.ISearchByViewPort; import cgeo.geocaching.connector.ec.ECConnector; import cgeo.geocaching.connector.gc.GCConnector; import cgeo.geocaching.connector.gc.MapTokens; -import cgeo.geocaching.connector.oc.OCApiConnector; import cgeo.geocaching.connector.oc.OCApiConnector.ApiSupport; import cgeo.geocaching.connector.oc.OCApiLiveConnector; import cgeo.geocaching.connector.oc.OCConnector; @@ -45,7 +44,9 @@ public final class ConnectorFactory { R.string.oc_de_okapi_consumer_key, R.string.oc_de_okapi_consumer_secret, R.string.pref_connectorOCActive, R.string.pref_ocde_tokenpublic, R.string.pref_ocde_tokensecret, ApiSupport.current), new OCConnector("OpenCaching.CZ", "www.opencaching.cz", "OZ"), - new OCApiConnector("OpenCaching.CO.UK", "www.opencaching.org.uk", "OK", "arU4okouc4GEjMniE2fq", "CC BY-NC-SA 2.5", ApiSupport.oldapi), + new OCApiLiveConnector("opencaching.org.uk", "www.opencaching.org.uk", "OK", "CC BY-NC-SA 2.5", + R.string.oc_uk_okapi_consumer_key, R.string.oc_uk_okapi_consumer_secret, + R.string.pref_connectorOCUKActive, R.string.pref_ocuk_tokenpublic, R.string.pref_ocuk_tokensecret, ApiSupport.oldapi), new OCConnector("OpenCaching.ES", "www.opencachingspain.es", "OC"), new OCConnector("OpenCaching.IT", "www.opencaching.it", "OC"), new OCConnector("OpenCaching.JP", "www.opencaching.jp", "OJ"), @@ -88,7 +89,7 @@ public final class ConnectorFactory { @SuppressWarnings("unchecked") private static <T extends IConnector> Collection<T> getMatchingConnectors(final Class<T> clazz) { - final List<T> matching = new ArrayList<T>(); + final List<T> matching = new ArrayList<>(); for (final IConnector connector : CONNECTORS) { if (clazz.isInstance(connector)) { matching.add((T) connector); @@ -118,7 +119,7 @@ public final class ConnectorFactory { } public static ILogin[] getActiveLiveConnectors() { - final List<ILogin> liveConns = new ArrayList<ILogin>(); + final List<ILogin> liveConns = new ArrayList<>(); for (final IConnector conn : CONNECTORS) { if (conn instanceof ILogin && conn.isActive()) { liveConns.add((ILogin) conn); @@ -143,16 +144,16 @@ public final class ConnectorFactory { } public static @NonNull - IConnector getConnector(ICache cache) { + IConnector getConnector(final ICache cache) { return getConnector(cache.getGeocode()); } - public static TrackableConnector getConnector(Trackable trackable) { + public static TrackableConnector getConnector(final Trackable trackable) { return getTrackableConnector(trackable.getGeocode()); } @NonNull - public static TrackableConnector getTrackableConnector(String geocode) { + public static TrackableConnector getTrackableConnector(final String geocode) { for (final TrackableConnector connector : TRACKABLE_CONNECTORS) { if (connector.canHandleTrackable(geocode)) { return connector; @@ -188,7 +189,7 @@ public final class ConnectorFactory { public static SearchResult searchByViewport(final @NonNull Viewport viewport, final MapTokens tokens) { return SearchResult.parallelCombineActive(searchByViewPortConns, new Func1<ISearchByViewPort, SearchResult>() { @Override - public SearchResult call(ISearchByViewPort connector) { + public SearchResult call(final ISearchByViewPort connector) { return connector.searchByViewport(viewport, tokens); } }); diff --git a/main/src/cgeo/geocaching/connector/ec/ECApi.java b/main/src/cgeo/geocaching/connector/ec/ECApi.java index 702e557..421d112 100644 --- a/main/src/cgeo/geocaching/connector/ec/ECApi.java +++ b/main/src/cgeo/geocaching/connector/ec/ECApi.java @@ -177,7 +177,7 @@ public class ECApi { } final JSONArray json = new JSONArray(data); final int len = json.length(); - final List<Geocache> caches = new ArrayList<Geocache>(len); + final List<Geocache> caches = new ArrayList<>(len); for (int i = 0; i < len; i++) { final Geocache cache = parseCache(json.getJSONObject(i)); if (cache != null) { @@ -205,7 +205,7 @@ public class ECApi { cache.setTerrain((float) response.getDouble("terrain")); cache.setSize(CacheSize.getById(response.getString("size"))); cache.setFound(response.getInt("found") == 1); - DataStore.saveCache(cache, EnumSet.of(SaveFlag.SAVE_CACHE)); + DataStore.saveCache(cache, EnumSet.of(SaveFlag.CACHE)); } catch (final JSONException e) { Log.e("ECApi.parseCache", e); return null; diff --git a/main/src/cgeo/geocaching/connector/ec/ECConnector.java b/main/src/cgeo/geocaching/connector/ec/ECConnector.java index 71716fe..a266eee 100644 --- a/main/src/cgeo/geocaching/connector/ec/ECConnector.java +++ b/main/src/cgeo/geocaching/connector/ec/ECConnector.java @@ -198,7 +198,7 @@ public class ECConnector extends AbstractConnector implements ISearchByGeocode, @Override public List<LogType> getPossibleLogTypes(Geocache geocache) { - final List<LogType> logTypes = new ArrayList<LogType>(); + final List<LogType> logTypes = new ArrayList<>(); if (geocache.isEventCache()) { logTypes.add(LogType.WILL_ATTEND); logTypes.add(LogType.ATTENDED); 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/GCConstants.java b/main/src/cgeo/geocaching/connector/gc/GCConstants.java index 79e570b..7cf43dc 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCConstants.java +++ b/main/src/cgeo/geocaching/connector/gc/GCConstants.java @@ -64,6 +64,7 @@ public final class GCConstants { public final static Pattern PATTERN_INVENTORY = Pattern.compile("<span id=\"ctl00_ContentBody_uxTravelBugList_uxInventoryLabel\">\\W*Inventory[^<]*</span>[^<]*</h3>[^<]*<div class=\"WidgetBody\">([^<]*<ul>(([^<]*<li>[^<]*<a href=\"[^\"]+\"[^>]*>[^<]*<img src=\"[^\"]+\"[^>]*>[^<]*<span>[^<]+<\\/span>[^<]*<\\/a>[^<]*<\\/li>)+)[^<]*<\\/ul>)?"); public final static Pattern PATTERN_INVENTORYINSIDE = Pattern.compile("[^<]*<li>[^<]*<a href=\"[a-z0-9\\-\\_\\.\\?\\/\\:\\@]*\\/track\\/details\\.aspx\\?guid=([0-9a-z\\-]+)[^\"]*\"[^>]*>[^<]*<img src=\"[^\"]+\"[^>]*>[^<]*<span>([^<]+)<\\/span>[^<]*<\\/a>[^<]*<\\/li>"); public final static Pattern PATTERN_WATCHLIST = Pattern.compile(Pattern.quote("watchlist.aspx") + ".{1,50}" + Pattern.quote("action=rem")); + public final static Pattern PATTERN_RELATED_WEB_PAGE = Pattern.compile("id=\"ctl00_ContentBody_uxCacheUrl\" title=\"Related Web Page\" href=\"(.*?)\">"); // Info box top-right public static final Pattern PATTERN_LOGIN_NAME = Pattern.compile("\"SignedInProfileLink\">(.*?)</a>"); diff --git a/main/src/cgeo/geocaching/connector/gc/GCLogin.java b/main/src/cgeo/geocaching/connector/gc/GCLogin.java index 367c721..e84851e 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCLogin.java +++ b/main/src/cgeo/geocaching/connector/gc/GCLogin.java @@ -14,13 +14,18 @@ import cgeo.geocaching.utils.MatcherWrapper; import cgeo.geocaching.utils.TextUtils; import ch.boye.httpclientandroidlib.HttpResponse; + import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; -import android.graphics.drawable.BitmapDrawable; +import rx.Observable; +import rx.functions.Func0; +import rx.util.async.Async; + +import android.graphics.drawable.Drawable; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -50,7 +55,7 @@ public class GCLogin extends AbstractLogin { "dd/MM/yyyy" }; - final Map<String, SimpleDateFormat> map = new HashMap<String, SimpleDateFormat>(); + final Map<String, SimpleDateFormat> map = new HashMap<>(); for (final String format : formats) { map.put(format, new SimpleDateFormat(format, Locale.ENGLISH)); @@ -253,7 +258,7 @@ public class GCLogin extends AbstractLogin { return false; } - public BitmapDrawable downloadAvatarAndGetMemberStatus() { + public Observable<Drawable> downloadAvatarAndGetMemberStatus() { try { final String responseData = StringUtils.defaultString(Network.getResponseData(Network.getRequest("http://www.geocaching.com/my/"))); final String profile = TextUtils.replaceWhitespace(responseData); @@ -267,8 +272,13 @@ public class GCLogin extends AbstractLogin { final String avatarURL = TextUtils.getMatch(profile, GCConstants.PATTERN_AVATAR_IMAGE_PROFILE_PAGE, false, null); if (null != avatarURL) { - final HtmlImage imgGetter = new HtmlImage("", false, 0, false); - return imgGetter.getDrawable(avatarURL.replace("avatar", "user/large")); + return Async.start(new Func0<Drawable>() { + @Override + public Drawable call() { + final HtmlImage imgGetter = new HtmlImage("", false, 0, false); + return imgGetter.getDrawable(avatarURL.replace("avatar", "user/large")); + } + }); } // No match? There may be no avatar set by user. Log.d("No avatar set for user"); diff --git a/main/src/cgeo/geocaching/connector/gc/GCMap.java b/main/src/cgeo/geocaching/connector/gc/GCMap.java index dc2408f..27ce06e 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCMap.java +++ b/main/src/cgeo/geocaching/connector/gc/GCMap.java @@ -15,7 +15,7 @@ import cgeo.geocaching.geopoint.Units; import cgeo.geocaching.geopoint.Viewport; import cgeo.geocaching.network.Parameters; import cgeo.geocaching.settings.Settings; -import cgeo.geocaching.ui.Formatter; +import cgeo.geocaching.utils.Formatter; import cgeo.geocaching.utils.LeastRecentlyUsedMap; import cgeo.geocaching.utils.Log; @@ -25,6 +25,9 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import rx.Observable; +import rx.functions.Func2; + import android.graphics.Bitmap; import java.text.ParseException; @@ -49,7 +52,7 @@ public class GCMap { final Parameters params = new Parameters("i", geocodeList, "_", String.valueOf(System.currentTimeMillis())); params.add("app", "cgeo"); final String referer = GCConstants.URL_LIVE_MAP_DETAILS; - final String data = StringUtils.defaultString(Tile.requestMapInfo(referer, params, referer)); + final String data = StringUtils.defaultString(Tile.requestMapInfo(referer, params, referer).toBlocking().first()); // Example JSON information // {"status":"success", @@ -71,7 +74,7 @@ public class GCMap { throw new JSONException("No data inside JSON"); } - final ArrayList<Geocache> caches = new ArrayList<Geocache>(); + final ArrayList<Geocache> caches = new ArrayList<>(); for (int j = 0; j < dataArray.length(); j++) { final Geocache cache = new Geocache(); @@ -119,7 +122,7 @@ public class GCMap { try { - final LeastRecentlyUsedMap<String, String> nameCache = new LeastRecentlyUsedMap.LruCache<String, String>(2000); // JSON id, cache name + final LeastRecentlyUsedMap<String, String> nameCache = new LeastRecentlyUsedMap.LruCache<>(2000); // JSON id, cache name if (StringUtils.isEmpty(data)) { throw new JSONException("No page given"); @@ -146,33 +149,9 @@ 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 + Map<String, List<UTFGridPosition>> positions = new HashMap<>(); // JSON id as key + Map<String, List<UTFGridPosition>> singlePositions = new HashMap<>(); // JSON id as key for (int i = 1; i < keys.length(); i++) { // index 0 is empty String key = keys.getString(i); @@ -188,9 +167,9 @@ public class GCMap { List<UTFGridPosition> singleListOfPositions = singlePositions.get(id); if (listOfPositions == null) { - listOfPositions = new ArrayList<UTFGridPosition>(); + listOfPositions = new ArrayList<>(); positions.put(id, listOfPositions); - singleListOfPositions = new ArrayList<UTFGridPosition>(); + singleListOfPositions = new ArrayList<>(); singlePositions.put(id, singleListOfPositions); } @@ -203,7 +182,7 @@ public class GCMap { } } - final ArrayList<Geocache> caches = new ArrayList<Geocache>(); + final ArrayList<Geocache> caches = new ArrayList<>(); for (Entry<String, List<UTFGridPosition>> entry : positions.entrySet()) { String id = entry.getKey(); List<UTFGridPosition> pos = entry.getValue(); @@ -289,7 +268,7 @@ public class GCMap { * Strategy for data retrieval and parsing, @see Strategy * @return */ - private static SearchResult searchByViewport(final Viewport viewport, final MapTokens tokens, Strategy strategy) { + private static SearchResult searchByViewport(final Viewport viewport, final MapTokens tokens, final Strategy strategy) { Log.d("GCMap.searchByViewport" + viewport.toString()); final SearchResult searchResult = new SearchResult(); @@ -305,8 +284,7 @@ public class GCMap { searchResult.setUrl(new StringBuilder().append(tiles.iterator().next().getZoomLevel()).append(Formatter.SEPARATOR).append(searchResult.getUrl()).toString()); } - for (Tile tile : tiles) { - + for (final Tile tile : tiles) { if (!Tile.cache.contains(tile)) { final Parameters params = new Parameters( "x", String.valueOf(tile.getX()), @@ -329,34 +307,37 @@ public class GCMap { } // The PNG must be requested first, otherwise the following request would always return with 204 - No Content - Bitmap bitmap = Tile.requestMapTile(params); - - // Check bitmap size - if (bitmap != null && (bitmap.getWidth() != Tile.TILE_SIZE || - bitmap.getHeight() != Tile.TILE_SIZE)) { - bitmap.recycle(); - bitmap = null; - } - - String data = Tile.requestMapInfo(GCConstants.URL_MAP_INFO, params, GCConstants.URL_LIVE_MAP); - if (StringUtils.isEmpty(data)) { - Log.w("GCMap.searchByViewport: No data from server for tile (" + tile.getX() + "/" + tile.getY() + ")"); - } else { - final SearchResult search = GCMap.parseMapJSON(data, tile, bitmap, strategy); - if (CollectionUtils.isEmpty(search.getGeocodes())) { - Log.e("GCMap.searchByViewport: No cache parsed for viewport " + viewport); + final Observable<Bitmap> bitmapObs = Tile.requestMapTile(params); + final Observable<String> dataObs = Tile.requestMapInfo(GCConstants.URL_MAP_INFO, params, GCConstants.URL_LIVE_MAP); + Observable.zip(bitmapObs, dataObs, new Func2<Bitmap, String, Void>() { + @Override + public Void call(final Bitmap bitmap, final String data) { + final boolean validBitmap = bitmap != null && bitmap.getWidth() == Tile.TILE_SIZE && bitmap.getHeight() == Tile.TILE_SIZE; + + if (StringUtils.isEmpty(data)) { + Log.w("GCMap.searchByViewport: No data from server for tile (" + tile.getX() + "/" + tile.getY() + ")"); + } else { + final SearchResult search = GCMap.parseMapJSON(data, tile, validBitmap ? bitmap : null, strategy); + if (CollectionUtils.isEmpty(search.getGeocodes())) { + Log.e("GCMap.searchByViewport: No cache parsed for viewport " + viewport); + } else { + synchronized (searchResult) { + searchResult.addSearchResult(search); + } + } + synchronized (Tile.cache) { + Tile.cache.add(tile); + } + } + + // release native bitmap memory + if (bitmap != null) { + bitmap.recycle(); + } + + return null; } - else { - searchResult.addSearchResult(search); - } - Tile.cache.add(tile); - } - - // release native bitmap memory - if (bitmap != null) { - bitmap.recycle(); - } - + }).toBlocking().single(); } } @@ -397,7 +378,7 @@ public class GCMap { * 8 = mystery, 1858 = whereigo */ private static String getCacheTypeFilter(CacheType typeToDisplay) { - Set<String> filterTypes = new HashSet<String>(); + Set<String> filterTypes = new HashSet<>(); // Put all types in set, remove what should be visible in a second step filterTypes.addAll(Arrays.asList("2", "9", "5", "3", "6", "453", "13", "1304", "4", "11", "137", "8", "1858")); switch (typeToDisplay) { diff --git a/main/src/cgeo/geocaching/connector/gc/GCParser.java b/main/src/cgeo/geocaching/connector/gc/GCParser.java index ec3a4bd..60d7856 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCParser.java +++ b/main/src/cgeo/geocaching/connector/gc/GCParser.java @@ -32,6 +32,7 @@ import cgeo.geocaching.ui.DirectionImage; import cgeo.geocaching.utils.CancellableHandler; import cgeo.geocaching.utils.Log; import cgeo.geocaching.utils.MatcherWrapper; +import cgeo.geocaching.utils.RxUtils; import cgeo.geocaching.utils.SynchronizedDateFormat; import cgeo.geocaching.utils.TextUtils; @@ -47,6 +48,13 @@ 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 android.net.Uri; import android.text.Html; @@ -74,7 +82,7 @@ public abstract class GCParser { return null; } - final List<String> cids = new ArrayList<String>(); + final List<String> cids = new ArrayList<>(); String page = pageContent; final SearchResult searchResult = new SearchResult(); @@ -121,7 +129,7 @@ public abstract class GCParser { final int rows_count = rows.length; int excludedCaches = 0; - final ArrayList<Geocache> caches = new ArrayList<Geocache>(); + final ArrayList<Geocache> caches = new ArrayList<>(); for (int z = 1; z < rows_count; z++) { final Geocache cache = new Geocache(); final String row = rows[z]; @@ -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.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).toBlocking().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 @@ -524,7 +544,12 @@ public abstract class GCParser { cache.setShortDescription(TextUtils.getMatch(page, GCConstants.PATTERN_SHORTDESC, true, "")); // cache description - cache.setDescription(TextUtils.getMatch(page, GCConstants.PATTERN_DESC, true, "")); + final String longDescription = TextUtils.getMatch(page, GCConstants.PATTERN_DESC, true, ""); + String relatedWebPage = TextUtils.getMatch(page, GCConstants.PATTERN_RELATED_WEB_PAGE, true, ""); + if (StringUtils.isNotEmpty(relatedWebPage)) { + relatedWebPage = String.format("<a href=\"%s\"><b>%s</b></a><br/><br/>", relatedWebPage, relatedWebPage); + } + cache.setDescription(relatedWebPage + longDescription); // cache attributes try { @@ -532,7 +557,7 @@ public abstract class GCParser { if (null != attributesPre) { final MatcherWrapper matcherAttributesInside = new MatcherWrapper(GCConstants.PATTERN_ATTRIBUTESINSIDE, attributesPre); - final ArrayList<String> attributes = new ArrayList<String>(); + final ArrayList<String> attributes = new ArrayList<>(); while (matcherAttributesInside.find()) { if (matcherAttributesInside.groupCount() > 1 && !matcherAttributesInside.group(2).equalsIgnoreCase("blank")) { // by default, use the tooltip of the attribute @@ -726,14 +751,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) { @@ -781,7 +803,7 @@ public abstract class GCParser { } // search results don't need to be filtered so load GCVote ratings here - GCVote.loadRatings(new ArrayList<Geocache>(searchResult.getCachesFromSearchResult(LoadFlags.LOAD_CACHE_OR_DB))); + GCVote.loadRatings(new ArrayList<>(searchResult.getCachesFromSearchResult(LoadFlags.LOAD_CACHE_OR_DB))); // save to application search.setError(searchResult.getError()); @@ -985,7 +1007,7 @@ public abstract class GCParser { return Collections.emptyList(); } - List<PocketQueryList> list = new ArrayList<PocketQueryList>(); + List<PocketQueryList> list = new ArrayList<>(); final MatcherWrapper matcherPocket = new MatcherWrapper(GCConstants.PATTERN_LIST_PQ, subPage); @@ -1020,12 +1042,12 @@ public abstract class GCParser { final String log, final List<TrackableLog> trackables) { if (GCLogin.isEmpty(viewstates)) { Log.e("GCParser.postLog: No viewstate given"); - return new ImmutablePair<StatusCode, String>(StatusCode.LOG_POST_ERROR, ""); + return new ImmutablePair<>(StatusCode.LOG_POST_ERROR, ""); } if (StringUtils.isBlank(log)) { Log.e("GCParser.postLog: No log text given"); - return new ImmutablePair<StatusCode, String>(StatusCode.NO_LOG_TEXT, ""); + return new ImmutablePair<>(StatusCode.NO_LOG_TEXT, ""); } final String logInfo = log.replace("\n", "\r\n").trim(); // windows' eol and remove leading and trailing whitespaces @@ -1069,10 +1091,11 @@ public abstract class GCParser { } final String uri = new Uri.Builder().scheme("http").authority("www.geocaching.com").path("/seek/log.aspx").encodedQuery("ID=" + cacheid).build().toString(); - String page = GCLogin.getInstance().postRequestLogged(uri, params); - if (!GCLogin.getInstance().getLoginStatus(page)) { + final GCLogin gcLogin = GCLogin.getInstance(); + String page = gcLogin.postRequestLogged(uri, params); + if (!gcLogin.getLoginStatus(page)) { Log.e("GCParser.postLog: Cannot log in geocaching"); - return new ImmutablePair<StatusCode, String>(StatusCode.NOT_LOGGED_IN, ""); + return new ImmutablePair<>(StatusCode.NOT_LOGGED_IN, ""); } // maintenance, archived needs to be confirmed @@ -1085,7 +1108,7 @@ public abstract class GCParser { if (GCLogin.isEmpty(viewstatesConfirm)) { Log.e("GCParser.postLog: No viewstate for confirm log"); - return new ImmutablePair<StatusCode, String>(StatusCode.LOG_POST_ERROR, ""); + return new ImmutablePair<>(StatusCode.LOG_POST_ERROR, ""); } params.clear(); @@ -1134,22 +1157,22 @@ public abstract class GCParser { DataStore.saveVisitDate(geocode); } - GCLogin.getInstance().getLoginStatus(page); + gcLogin.getLoginStatus(page); // the log-successful-page contains still the old value - if (GCLogin.getInstance().getActualCachesFound() >= 0) { - GCLogin.getInstance().setActualCachesFound(GCLogin.getInstance().getActualCachesFound() + 1); + if (gcLogin.getActualCachesFound() >= 0) { + gcLogin.setActualCachesFound(gcLogin.getActualCachesFound() + 1); } final String logID = TextUtils.getMatch(page, GCConstants.PATTERN_LOG_IMAGE_UPLOAD, ""); - return new ImmutablePair<StatusCode, String>(StatusCode.NO_ERROR, logID); + return new ImmutablePair<>(StatusCode.NO_ERROR, logID); } } catch (final Exception e) { Log.e("GCParser.postLog.check", e); } Log.e("GCParser.postLog: Failed to post log because of unknown error"); - return new ImmutablePair<StatusCode, String>(StatusCode.LOG_POST_ERROR, ""); + return new ImmutablePair<>(StatusCode.LOG_POST_ERROR, ""); } /** @@ -1171,7 +1194,7 @@ public abstract class GCParser { final String page = GCLogin.getInstance().getRequestLogged(uri, null); if (StringUtils.isBlank(page)) { Log.e("GCParser.uploadLogImage: No data from server"); - return new ImmutablePair<StatusCode, String>(StatusCode.UNKNOWN_ERROR, null); + return new ImmutablePair<>(StatusCode.UNKNOWN_ERROR, null); } assert page != null; @@ -1619,116 +1642,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(RxUtils.networkScheduler); + } - 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."); + } + + // 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); + } - return logs; + subscriber.onNext(logDone); + } + } catch (final JSONException e) { + // failed to parse logs + Log.w("GCParser.loadLogsFromDetails: Failed to parse cache logs", e); + } + subscriber.onCompleted(); + } + }); } @NonNull @@ -1737,7 +1786,7 @@ public abstract class GCParser { return Collections.emptyList(); } - final List<LogType> types = new ArrayList<LogType>(); + final List<LogType> types = new ArrayList<>(); final MatcherWrapper typeBoxMatcher = new MatcherWrapper(GCConstants.PATTERN_TYPEBOX, page); if (typeBoxMatcher.find() && typeBoxMatcher.groupCount() > 0) { @@ -1781,7 +1830,7 @@ public abstract class GCParser { return null; } - final List<TrackableLog> trackableLogs = new ArrayList<TrackableLog>(); + final List<TrackableLog> trackableLogs = new ArrayList<>(); final MatcherWrapper trackableMatcher = new MatcherWrapper(GCConstants.PATTERN_TRACKABLE, page); while (trackableMatcher.find()) { @@ -1820,32 +1869,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(RxUtils.computationScheduler); + 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) { @@ -1854,6 +1910,28 @@ public abstract class GCParser { cache.setMyVote(rating.getMyVote()); } } + + // Wait for completion of logs parsing, retrieving and merging + mergedLogs.toBlocking().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/RecaptchaHandler.java b/main/src/cgeo/geocaching/connector/gc/RecaptchaHandler.java index 7cced74..6095514 100644 --- a/main/src/cgeo/geocaching/connector/gc/RecaptchaHandler.java +++ b/main/src/cgeo/geocaching/connector/gc/RecaptchaHandler.java @@ -1,17 +1,21 @@ package cgeo.geocaching.connector.gc; +import butterknife.ButterKnife; + import cgeo.geocaching.R; import cgeo.geocaching.loaders.RecaptchaReceiver; import cgeo.geocaching.network.Network; import cgeo.geocaching.utils.Log; +import cgeo.geocaching.utils.RxUtils; import org.apache.commons.io.IOUtils; + import rx.Observable; import rx.android.observables.AndroidObservable; import rx.functions.Action1; import rx.functions.Func0; -import rx.schedulers.Schedulers; +import android.annotation.SuppressLint; import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; @@ -57,7 +61,7 @@ public class RecaptchaHandler extends Handler { return Observable.empty(); } }); - AndroidObservable.bindActivity(activity, captcha).subscribe(new Action1<Bitmap>() { + AndroidObservable.bindActivity(activity, captcha).subscribeOn(RxUtils.networkScheduler).subscribe(new Action1<Bitmap>() { @Override public void call(final Bitmap bitmap) { imageView.setImageBitmap(bitmap); @@ -67,23 +71,24 @@ public class RecaptchaHandler extends Handler { public void call(final Throwable throwable) { // Do nothing } - }, Schedulers.io()); + }); reloadButton.setEnabled(true); } + @SuppressLint("InflateParams") @Override - public void handleMessage(Message msg) { + public void handleMessage(final Message msg) { if (msg.what == SHOW_CAPTCHA) { final AlertDialog.Builder dlg = new AlertDialog.Builder(activity); - final View view = activity.getLayoutInflater().inflate(R.layout.recaptcha_dialog, null); + final View view = activity.getLayoutInflater().inflate(R.layout.recaptcha_dialog, null, false); - final ImageView imageView = (ImageView) view.findViewById(R.id.image); + final ImageView imageView = ButterKnife.findById(view, R.id.image); - final ImageButton reloadButton = (ImageButton) view.findViewById(R.id.button_recaptcha_refresh); + final ImageButton reloadButton = ButterKnife.findById(view, R.id.button_recaptcha_refresh); reloadButton.setEnabled(false); reloadButton.setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View v) { + public void onClick(final View v) { recaptchaReceiver.fetchChallenge(); loadChallenge(imageView, reloadButton); } @@ -95,8 +100,9 @@ public class RecaptchaHandler extends Handler { dlg.setView(view); dlg.setNeutralButton(activity.getString(R.string.caches_recaptcha_continue), new DialogInterface.OnClickListener() { @Override - public void onClick(DialogInterface dialog, int id) { - final String text = ((EditText) view.findViewById(R.id.text)).getText().toString(); + public void onClick(final DialogInterface dialog, final int id) { + final EditText editText = ButterKnife.findById(view, R.id.text); + final String text = editText.getText().toString(); recaptchaReceiver.setText(text); dialog.cancel(); } diff --git a/main/src/cgeo/geocaching/connector/gc/Tile.java b/main/src/cgeo/geocaching/connector/gc/Tile.java index ca70111..18fe65c 100644 --- a/main/src/cgeo/geocaching/connector/gc/Tile.java +++ b/main/src/cgeo/geocaching/connector/gc/Tile.java @@ -7,10 +7,16 @@ import cgeo.geocaching.network.Network; import cgeo.geocaching.network.Parameters; import cgeo.geocaching.utils.LeastRecentlyUsedSet; import cgeo.geocaching.utils.Log; +import cgeo.geocaching.utils.RxUtils; import ch.boye.httpclientandroidlib.HttpResponse; + import org.eclipse.jdt.annotation.NonNull; +import rx.Observable; +import rx.functions.Func0; +import rx.util.async.Async; + import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -88,10 +94,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 @@ -231,20 +233,39 @@ public class Tile { return toString().hashCode(); } - /** Request JSON informations for a tile */ - public static String requestMapInfo(final String url, final Parameters params, final String referer) { - return Network.getResponseData(Network.getRequest(url, params, new Parameters("Referer", referer))); + /** Request JSON informations for a tile. Return as soon as the request has been made, before the answer has been + * read. + * + * @return An observable with one element, which may be <tt>null</tt>. + */ + public static Observable<String> requestMapInfo(final String url, final Parameters params, final String referer) { + final HttpResponse response = Network.getRequest(url, params, new Parameters("Referer", referer)); + return Async.start(new Func0<String>() { + @Override + public String call() { + return Network.getResponseData(response); + } + }, RxUtils.networkScheduler); } - /** Request .png image for a tile. */ - public static Bitmap requestMapTile(final Parameters params) { + /** Request .png image for a tile. Return as soon as the request has been made, before the answer has been + * read and processed. + * + * @return An observable with one element, which may be <tt>null</tt>. + */ + public static Observable<Bitmap> requestMapTile(final Parameters params) { final HttpResponse response = Network.getRequest(GCConstants.URL_MAP_TILE, params, new Parameters("Referer", GCConstants.URL_LIVE_MAP)); - try { - return response != null ? BitmapFactory.decodeStream(response.getEntity().getContent()) : null; - } catch (IOException e) { - Log.e("Tile.requestMapTile() ", e); - } - return null; + return Async.start(new Func0<Bitmap>() { + @Override + public Bitmap call() { + try { + return response != null ? BitmapFactory.decodeStream(response.getEntity().getContent()) : null; + } catch (IOException e) { + Log.e("Tile.requestMapTile() ", e); + return null; + } + } + }, RxUtils.computationScheduler); } public boolean containsPoint(final @NonNull ICoordinates point) { @@ -277,7 +298,7 @@ public class Tile { * @return */ protected static Set<Tile> getTilesForViewport(final Viewport viewport, final int tilesOnAxis, final int minZoom) { - Set<Tile> tiles = new HashSet<Tile>(); + Set<Tile> tiles = new HashSet<>(); int zoom = Math.max( Math.min(Tile.calcZoomLon(viewport.bottomLeft, viewport.topRight, tilesOnAxis), Tile.calcZoomLat(viewport.bottomLeft, viewport.topRight, tilesOnAxis)), @@ -310,7 +331,7 @@ public class Tile { } public void removeFromTileCache(@NonNull final ICoordinates point) { - for (final Tile tile : new ArrayList<Tile>(this)) { + for (final Tile tile : new ArrayList<>(this)) { if (tile.containsPoint(point)) { remove(tile); } diff --git a/main/src/cgeo/geocaching/utils/UncertainProperty.java b/main/src/cgeo/geocaching/connector/gc/UncertainProperty.java index e8686e3..71adcbd 100644 --- a/main/src/cgeo/geocaching/utils/UncertainProperty.java +++ b/main/src/cgeo/geocaching/connector/gc/UncertainProperty.java @@ -1,6 +1,5 @@ -package cgeo.geocaching.utils; +package cgeo.geocaching.connector.gc; -import cgeo.geocaching.connector.gc.Tile; /** * Property with certainty. When merging properties, the one with higher certainty wins. diff --git a/main/src/cgeo/geocaching/connector/oc/OCApiLiveConnector.java b/main/src/cgeo/geocaching/connector/oc/OCApiLiveConnector.java index 049c633..dd25c5e 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; @@ -34,7 +35,7 @@ public class OCApiLiveConnector extends OCApiConnector implements ISearchByCente private final int tokenSecretPrefKeyId; private UserInfo userInfo = new UserInfo(StringUtils.EMPTY, 0, UserInfoStatus.NOT_RETRIEVED); - public OCApiLiveConnector(String name, String host, String prefix, String licenseString, int cKResId, int cSResId, int isActivePrefKeyId, int tokenPublicPrefKeyId, int tokenSecretPrefKeyId, ApiSupport apiSupport) { + public OCApiLiveConnector(final String name, final String host, final String prefix, final String licenseString, final int cKResId, final int cSResId, final int isActivePrefKeyId, final int tokenPublicPrefKeyId, final int tokenSecretPrefKeyId, final ApiSupport apiSupport) { super(name, host, prefix, CryptUtils.rot13(CgeoApplication.getInstance().getResources().getString(cKResId)), licenseString, apiSupport); cS = CryptUtils.rot13(CgeoApplication.getInstance().getResources().getString(cSResId)); @@ -49,22 +50,22 @@ public class OCApiLiveConnector extends OCApiConnector implements ISearchByCente } @Override - public SearchResult searchByViewport(@NonNull Viewport viewport, MapTokens tokens) { + public SearchResult searchByViewport(@NonNull final Viewport viewport, final MapTokens tokens) { return new SearchResult(OkapiClient.getCachesBBox(viewport, this)); } @Override - public SearchResult searchByCenter(@NonNull Geopoint center, final @NonNull RecaptchaReceiver recaptchaReceiver) { + public SearchResult searchByCenter(@NonNull final Geopoint center, final @NonNull RecaptchaReceiver recaptchaReceiver) { return new SearchResult(OkapiClient.getCachesAround(center, this)); } @Override - public SearchResult searchByOwner(@NonNull String username, final @NonNull RecaptchaReceiver recaptchaReceiver) { + public SearchResult searchByOwner(@NonNull final String username, final @NonNull RecaptchaReceiver recaptchaReceiver) { return new SearchResult(OkapiClient.getCachesByOwner(username, this)); } @Override - public SearchResult searchByFinder(@NonNull String username, final @NonNull RecaptchaReceiver recaptchaReceiver) { + public SearchResult searchByFinder(@NonNull final String username, final @NonNull RecaptchaReceiver recaptchaReceiver) { return new SearchResult(OkapiClient.getCachesByFinder(username, this)); } @@ -94,11 +95,11 @@ public class OCApiLiveConnector extends OCApiConnector implements ISearchByCente @Override public boolean supportsWatchList() { - return true; + return ApiSupport.current == getApiSupport(); } @Override - public boolean addToWatchlist(Geocache cache) { + public boolean addToWatchlist(final Geocache cache) { final boolean added = OkapiClient.setWatchState(cache, true, this); if (added) { @@ -109,7 +110,7 @@ public class OCApiLiveConnector extends OCApiConnector implements ISearchByCente } @Override - public boolean removeFromWatchlist(Geocache cache) { + public boolean removeFromWatchlist(final Geocache cache) { final boolean removed = OkapiClient.setWatchState(cache, false, this); if (removed) { @@ -130,7 +131,7 @@ public class OCApiLiveConnector extends OCApiConnector implements ISearchByCente } @Override - public boolean canLog(Geocache cache) { + public boolean canLog(final Geocache cache) { return true; } @@ -139,7 +140,7 @@ public class OCApiLiveConnector extends OCApiConnector implements ISearchByCente } @Override - public boolean login(Handler handler, Context fromActivity) { + public boolean login(final Handler handler, final Context fromActivity) { if (supportsPersonalization()) { userInfo = OkapiClient.getUserInfo(this); } else { @@ -149,6 +150,11 @@ public class OCApiLiveConnector extends OCApiConnector implements ISearchByCente } @Override + public boolean isOwner(final ICache cache) { + return StringUtils.isNotEmpty(getUserName()) && StringUtils.equals(cache.getOwnerDisplayName(), getUserName()); + } + + @Override public String getUserName() { return userInfo.getName(); } @@ -175,7 +181,7 @@ public class OCApiLiveConnector extends OCApiConnector implements ISearchByCente } @Override - public boolean isSearchForMyCaches(String username) { + public boolean isSearchForMyCaches(final String username) { return StringUtils.equalsIgnoreCase(username, getUserName()); } diff --git a/main/src/cgeo/geocaching/connector/oc/OCAuthParams.java b/main/src/cgeo/geocaching/connector/oc/OCAuthParams.java index 131ddad..a1030f0 100644 --- a/main/src/cgeo/geocaching/connector/oc/OCAuthParams.java +++ b/main/src/cgeo/geocaching/connector/oc/OCAuthParams.java @@ -31,14 +31,18 @@ public class OCAuthParams extends OAuthParameters { R.string.oc_ro_okapi_consumer_key, R.string.oc_ro_okapi_consumer_secret, "callback://www.cgeo.org/opencaching.ro/", R.string.auth_ocro, R.string.pref_ocro_tokenpublic, R.string.pref_ocro_tokensecret, R.string.pref_temp_ocro_token_public, R.string.pref_temp_ocro_token_secret); + public static final OCAuthParams OC_UK_AUTH_PARAMS = new OCAuthParams("www.opencaching.org.uk", false, + R.string.oc_uk_okapi_consumer_key, R.string.oc_uk_okapi_consumer_secret, "callback://www.cgeo.org/opencaching.org.uk/", + R.string.auth_ocuk, R.string.pref_ocuk_tokenpublic, R.string.pref_ocuk_tokensecret, R.string.pref_temp_ocuk_token_public, R.string.pref_temp_ocuk_token_secret); + public final int authTitleResId; public final int tokenPublicPrefKey; public final int tokenSecretPrefKey; public final int tempTokenPublicPrefKey; public final int tempTokenSecretPrefKey; - public OCAuthParams(@NonNull String host, boolean https, int consumerKeyResId, int consumerSecretResId, @NonNull String callback, - int authTitleResId, int tokenPublicPrefKey, int tokenSecretPrefKey, int tempTokePublicPrefKey, int tempTokenSecretPrefKey) { + public OCAuthParams(@NonNull final String host, final boolean https, final int consumerKeyResId, final int consumerSecretResId, @NonNull final String callback, + final int authTitleResId, final int tokenPublicPrefKey, final int tokenSecretPrefKey, final int tempTokePublicPrefKey, final int tempTokenSecretPrefKey) { super(host, "/okapi/services/oauth/request_token", "/okapi/services/oauth/authorize", "/okapi/services/oauth/access_token", @@ -54,7 +58,7 @@ public class OCAuthParams extends OAuthParameters { } @Override - public void setOAuthExtras(Intent intent) { + public void setOAuthExtras(final Intent intent) { super.setOAuthExtras(intent); if (intent != null) { diff --git a/main/src/cgeo/geocaching/connector/oc/OkapiClient.java b/main/src/cgeo/geocaching/connector/oc/OkapiClient.java index 3c93488..3df62aa 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; @@ -79,6 +80,7 @@ final class OkapiClient { private static final String CACHE_VOTES = "rating_votes"; private static final String CACHE_NOTFOUNDS = "notfounds"; private static final String CACHE_FOUNDS = "founds"; + private static final String CACHE_WILLATTENDS = "willattends"; private static final String CACHE_HIDDEN = "date_hidden"; private static final String CACHE_LATEST_LOGS = "latest_logs"; private static final String CACHE_IMAGE_URL = "url"; @@ -98,6 +100,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,11 +119,12 @@ 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_CURRENT_FIELDS = "gc_code|attribution_note|attr_acodes"; - private static final String SERVICE_CACHE_ADDITIONAL_L3_FIELDS = "is_watched|my_notes"; + 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|willattends"; + private static final String SERVICE_CACHE_ADDITIONAL_L3_FIELDS = "my_notes"; + private static final String SERVICE_CACHE_ADDITIONAL_CURRENT_L3_FIELDS = "is_watched"; private static final String METHOD_SEARCH_ALL = "services/caches/search/all"; private static final String METHOD_SEARCH_BBOX = "services/caches/search/bbox"; @@ -143,7 +151,7 @@ final class OkapiClient { public static List<Geocache> getCachesAround(final Geopoint center, final OCApiConnector connector) { final String centerString = GeopointFormatter.format(GeopointFormatter.Format.LAT_DECDEGREE_RAW, center) + SEPARATOR + GeopointFormatter.format(GeopointFormatter.Format.LON_DECDEGREE_RAW, center); final Parameters params = new Parameters("search_method", METHOD_SEARCH_NEAREST); - final Map<String, String> valueMap = new LinkedHashMap<String, String>(); + final Map<String, String> valueMap = new LinkedHashMap<>(); valueMap.put("center", centerString); valueMap.put("limit", "20"); valueMap.put("radius", "200"); @@ -161,7 +169,7 @@ final class OkapiClient { private static List<Geocache> getCachesByUser(final String username, final OCApiConnector connector, final String userRequestParam) { final Parameters params = new Parameters("search_method", METHOD_SEARCH_ALL); - final Map<String, String> valueMap = new LinkedHashMap<String, String>(); + final Map<String, String> valueMap = new LinkedHashMap<>(); final @Nullable String uuid = getUserUUID(connector, username); if (StringUtils.isEmpty(uuid)) { @@ -173,7 +181,7 @@ final class OkapiClient { } public static List<Geocache> getCachesNamed(final Geopoint center, final String namePart, final OCApiConnector connector) { - final Map<String, String> valueMap = new LinkedHashMap<String, String>(); + final Map<String, String> valueMap = new LinkedHashMap<>(); final Parameters params; // search around current position, if there is a position @@ -226,7 +234,7 @@ final class OkapiClient { + SEPARATOR + GeopointFormatter.format(GeopointFormatter.Format.LAT_DECDEGREE_RAW, viewport.topRight) + SEPARATOR + GeopointFormatter.format(GeopointFormatter.Format.LON_DECDEGREE_RAW, viewport.topRight); final Parameters params = new Parameters("search_method", METHOD_SEARCH_BBOX); - final Map<String, String> valueMap = new LinkedHashMap<String, String>(); + final Map<String, String> valueMap = new LinkedHashMap<>(); valueMap.put("bbox", bboxString); return requestCaches(connector, params, valueMap, false); @@ -289,14 +297,15 @@ final class OkapiClient { // Get and iterate result list final JSONObject cachesResponse = response.getJSONObject("results"); if (cachesResponse != null) { - final List<Geocache> caches = new ArrayList<Geocache>(cachesResponse.length()); - @SuppressWarnings("unchecked") - final - Iterator<String> keys = cachesResponse.keys(); + final List<Geocache> caches = new ArrayList<>(cachesResponse.length()); + final Iterator<?> keys = cachesResponse.keys(); while (keys.hasNext()) { - final String key = keys.next(); - final Geocache cache = parseSmallCache(cachesResponse.getJSONObject(key)); - caches.add(cache); + final Object next = keys.next(); + if (next instanceof String) { + final String key = (String) next; + final Geocache cache = parseSmallCache(cachesResponse.getJSONObject(key)); + caches.add(cache); + } } return caches; } @@ -313,7 +322,7 @@ final class OkapiClient { parseCoreCache(response, cache); - DataStore.saveCache(cache, EnumSet.of(SaveFlag.SAVE_CACHE)); + DataStore.saveCache(cache, EnumSet.of(SaveFlag.CACHE)); } catch (final JSONException e) { Log.e("OkapiClient.parseSmallCache", e); } @@ -328,11 +337,16 @@ 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)); + // only current Api + cache.getLogCounts().put(LogType.WILL_ATTEND, response.optInt(CACHE_WILLATTENDS)); if (!response.isNull(CACHE_RATING)) { cache.setRating((float) response.getDouble(CACHE_RATING)); @@ -375,6 +389,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)); } @@ -386,7 +403,7 @@ final class OkapiClient { cache.setDetailedUpdatedNow(); // save full detailed caches - DataStore.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); + DataStore.saveCache(cache, EnumSet.of(SaveFlag.DB)); DataStore.saveLogsWithoutTransaction(cache.getGeocode(), parseLogs(response.getJSONArray(CACHE_LATEST_LOGS))); } catch (final JSONException e) { Log.e("OkapiClient.parseCache", e); @@ -409,6 +426,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)); } @@ -443,7 +462,7 @@ final class OkapiClient { parseLogType(logResponse.getString(LOG_TYPE)), logResponse.getString(LOG_COMMENT).trim()); if (result == null) { - result = new ArrayList<LogEntry>(); + result = new ArrayList<>(); } result.add(log); } catch (final JSONException e) { @@ -467,7 +486,7 @@ final class OkapiClient { wpt.setCoords(pt); } if (result == null) { - result = new ArrayList<Waypoint>(); + result = new ArrayList<>(); } wpt.setPrefix(wpt.getName()); result.add(wpt); @@ -478,6 +497,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<>(); + 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; @@ -559,7 +599,7 @@ final class OkapiClient { private static List<String> parseAttributes(final JSONArray nameList, final JSONArray acodeList) { - final List<String> result = new ArrayList<String>(); + final List<String> result = new ArrayList<>(); for (int i = 0; i < nameList.length(); i++) { try { @@ -593,7 +633,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); } @@ -683,6 +723,9 @@ final class OkapiClient { } if (connector.getApiSupport() == ApiSupport.current) { res.append(SEPARATOR).append(SERVICE_CACHE_ADDITIONAL_CURRENT_FIELDS); + if (connector.getSupportedAuthLevel() == OAuthLevel.Level3) { + res.append(SEPARATOR).append(SERVICE_CACHE_ADDITIONAL_CURRENT_L3_FIELDS); + } } return res.toString(); @@ -702,7 +745,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 +812,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 +835,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 +871,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 +889,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/enumerations/CacheAttribute.java b/main/src/cgeo/geocaching/enumerations/CacheAttribute.java index 0703c3c..1fdb0ac 100644 --- a/main/src/cgeo/geocaching/enumerations/CacheAttribute.java +++ b/main/src/cgeo/geocaching/enumerations/CacheAttribute.java @@ -164,9 +164,9 @@ public enum CacheAttribute { } private final static Map<String, CacheAttribute> FIND_BY_GCRAWNAME; - private final static SparseArray<CacheAttribute> FIND_BY_OCACODE = new SparseArray<CacheAttribute>(); + private final static SparseArray<CacheAttribute> FIND_BY_OCACODE = new SparseArray<>(); static { - final HashMap<String, CacheAttribute> mapGcRawNames = new HashMap<String, CacheAttribute>(); + final HashMap<String, CacheAttribute> mapGcRawNames = new HashMap<>(); for (CacheAttribute attr : values()) { mapGcRawNames.put(attr.rawName, attr); if (attr.ocacode != NO_ID) { diff --git a/main/src/cgeo/geocaching/enumerations/CacheSize.java b/main/src/cgeo/geocaching/enumerations/CacheSize.java index 1255455..c4e2831 100644 --- a/main/src/cgeo/geocaching/enumerations/CacheSize.java +++ b/main/src/cgeo/geocaching/enumerations/CacheSize.java @@ -40,7 +40,7 @@ public enum CacheSize { final private static Map<String, CacheSize> FIND_BY_ID; static { - final HashMap<String, CacheSize> mapping = new HashMap<String, CacheSize>(); + final HashMap<String, CacheSize> mapping = new HashMap<>(); for (final CacheSize cs : values()) { mapping.put(cs.id.toLowerCase(Locale.US), cs); mapping.put(cs.ocSize2.toLowerCase(Locale.US), cs); diff --git a/main/src/cgeo/geocaching/enumerations/CacheType.java b/main/src/cgeo/geocaching/enumerations/CacheType.java index e36f1fd..16677da 100644 --- a/main/src/cgeo/geocaching/enumerations/CacheType.java +++ b/main/src/cgeo/geocaching/enumerations/CacheType.java @@ -60,9 +60,9 @@ public enum CacheType { private final static Map<String, CacheType> FIND_BY_PATTERN; private final static Map<String, CacheType> FIND_BY_GUID; static { - final HashMap<String, CacheType> mappingId = new HashMap<String, CacheType>(); - final HashMap<String, CacheType> mappingPattern = new HashMap<String, CacheType>(); - final HashMap<String, CacheType> mappingGuid = new HashMap<String, CacheType>(); + final HashMap<String, CacheType> mappingId = new HashMap<>(); + final HashMap<String, CacheType> mappingPattern = new HashMap<>(); + final HashMap<String, CacheType> mappingGuid = new HashMap<>(); for (CacheType ct : values()) { mappingId.put(ct.id, ct); mappingPattern.put(ct.pattern.toLowerCase(Locale.US), ct); diff --git a/main/src/cgeo/geocaching/enumerations/LoadFlags.java b/main/src/cgeo/geocaching/enumerations/LoadFlags.java index fb894ac..c781f4b 100644 --- a/main/src/cgeo/geocaching/enumerations/LoadFlags.java +++ b/main/src/cgeo/geocaching/enumerations/LoadFlags.java @@ -8,39 +8,39 @@ import java.util.EnumSet; public interface LoadFlags { public enum LoadFlag { - LOAD_CACHE_BEFORE, // load from CacheCache - LOAD_CACHE_AFTER, // load from CacheCache - LOAD_DB_MINIMAL, // load minimal informations from DataBase - LOAD_ATTRIBUTES, - LOAD_WAYPOINTS, - LOAD_SPOILERS, - LOAD_LOGS, - LOAD_INVENTORY, - LOAD_OFFLINE_LOG + CACHE_BEFORE, // load from CacheCache + CACHE_AFTER, // load from CacheCache + DB_MINIMAL, // load minimal informations from DataBase + ATTRIBUTES, + WAYPOINTS, + SPOILERS, + LOGS, + INVENTORY, + OFFLINE_LOG } /** Retrieve cache from CacheCache only. Do not load from DB */ - public final static EnumSet<LoadFlag> LOAD_CACHE_ONLY = EnumSet.of(LoadFlag.LOAD_CACHE_BEFORE); + public final static EnumSet<LoadFlag> LOAD_CACHE_ONLY = EnumSet.of(LoadFlag.CACHE_BEFORE); /** Retrieve cache from CacheCache first. If not found load from DB */ - public final static EnumSet<LoadFlag> LOAD_CACHE_OR_DB = EnumSet.of(LoadFlag.LOAD_CACHE_BEFORE, LoadFlag.LOAD_DB_MINIMAL, LoadFlag.LOAD_OFFLINE_LOG); + public final static EnumSet<LoadFlag> LOAD_CACHE_OR_DB = EnumSet.of(LoadFlag.CACHE_BEFORE, LoadFlag.DB_MINIMAL, LoadFlag.OFFLINE_LOG); /** Retrieve cache (minimalistic information including waypoints) from DB first. If not found load from CacheCache */ - public final static EnumSet<LoadFlag> LOAD_WAYPOINTS = EnumSet.of(LoadFlag.LOAD_CACHE_AFTER, LoadFlag.LOAD_DB_MINIMAL, LoadFlag.LOAD_WAYPOINTS, LoadFlag.LOAD_OFFLINE_LOG); + public final static EnumSet<LoadFlag> LOAD_WAYPOINTS = EnumSet.of(LoadFlag.CACHE_AFTER, LoadFlag.DB_MINIMAL, LoadFlag.WAYPOINTS, LoadFlag.OFFLINE_LOG); /** Retrieve cache (all stored informations) from DB only. Do not load from CacheCache */ - public final static EnumSet<LoadFlag> LOAD_ALL_DB_ONLY = EnumSet.range(LoadFlag.LOAD_DB_MINIMAL, LoadFlag.LOAD_OFFLINE_LOG); + public final static EnumSet<LoadFlag> LOAD_ALL_DB_ONLY = EnumSet.range(LoadFlag.DB_MINIMAL, LoadFlag.OFFLINE_LOG); public enum SaveFlag { - SAVE_CACHE, // save only to CacheCache - SAVE_DB // include saving to CacheCache + CACHE, // save only to CacheCache + DB // include saving to CacheCache } public final static EnumSet<SaveFlag> SAVE_ALL = EnumSet.allOf(SaveFlag.class); public enum RemoveFlag { - REMOVE_CACHE, // save only to CacheCache - REMOVE_DB, // includes removing from CacheCache - REMOVE_OWN_WAYPOINTS_ONLY_FOR_TESTING // only to be used in unit testing (as we never delete own waypoints) + CACHE, // save only to CacheCache + DB, // includes removing from CacheCache + OWN_WAYPOINTS_ONLY_FOR_TESTING // only to be used in unit testing (as we never delete own waypoints) } - public final static EnumSet<RemoveFlag> REMOVE_ALL = EnumSet.of(RemoveFlag.REMOVE_CACHE, RemoveFlag.REMOVE_DB); + public final static EnumSet<RemoveFlag> REMOVE_ALL = EnumSet.of(RemoveFlag.CACHE, RemoveFlag.DB); }
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/enumerations/LogType.java b/main/src/cgeo/geocaching/enumerations/LogType.java index fa65b71..84ab7b9 100644 --- a/main/src/cgeo/geocaching/enumerations/LogType.java +++ b/main/src/cgeo/geocaching/enumerations/LogType.java @@ -68,8 +68,8 @@ public enum LogType { private final static Map<String, LogType> FIND_BY_ICONNAME; private final static Map<String, LogType> FIND_BY_TYPE; static { - final HashMap<String, LogType> mappingPattern = new HashMap<String, LogType>(); - final HashMap<String, LogType> mappingType = new HashMap<String, LogType>(); + final HashMap<String, LogType> mappingPattern = new HashMap<>(); + final HashMap<String, LogType> mappingType = new HashMap<>(); for (LogType lt : values()) { if (lt.iconName != null) { mappingPattern.put(lt.iconName, lt); diff --git a/main/src/cgeo/geocaching/enumerations/WaypointType.java b/main/src/cgeo/geocaching/enumerations/WaypointType.java index 272b2f2..1805635 100644 --- a/main/src/cgeo/geocaching/enumerations/WaypointType.java +++ b/main/src/cgeo/geocaching/enumerations/WaypointType.java @@ -37,9 +37,9 @@ public enum WaypointType { * non public so that <code>null</code> handling can be handled centrally in the enum type itself */ private static final Map<String, WaypointType> FIND_BY_ID; - public static final Set<WaypointType> ALL_TYPES_EXCEPT_OWN_AND_ORIGINAL = new HashSet<WaypointType>(); + public static final Set<WaypointType> ALL_TYPES_EXCEPT_OWN_AND_ORIGINAL = new HashSet<>(); static { - final HashMap<String, WaypointType> mapping = new HashMap<String, WaypointType>(); + final HashMap<String, WaypointType> mapping = new HashMap<>(); for (WaypointType wt : values()) { mapping.put(wt.id, wt); if (wt != WaypointType.OWN && wt != WaypointType.ORIGINAL) { diff --git a/main/src/cgeo/geocaching/export/ExportFactory.java b/main/src/cgeo/geocaching/export/ExportFactory.java deleted file mode 100644 index e743eb2..0000000 --- a/main/src/cgeo/geocaching/export/ExportFactory.java +++ /dev/null @@ -1,67 +0,0 @@ -package cgeo.geocaching.export; - -import cgeo.geocaching.Geocache; -import cgeo.geocaching.R; -import cgeo.geocaching.utils.Log; - -import android.app.Activity; -import android.app.AlertDialog; -import android.content.DialogInterface; -import android.widget.ArrayAdapter; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Factory to create a dialog with all available exporters. - */ -public abstract class ExportFactory { - - /** - * Contains instances of all available exporter classes. - */ - private static final List<Class<? extends Export>> exporterClasses; - - static { - final ArrayList<Class<? extends Export>> temp = new ArrayList<Class<? extends Export>>(); - temp.add(FieldnoteExport.class); - temp.add(GpxExport.class); - exporterClasses = Collections.unmodifiableList(temp); - } - - /** - * Creates a dialog so that the user can select an exporter. - * - * @param caches - * The {@link List} of {@link cgeo.geocaching.Geocache} to be exported - * @param activity - * The {@link Activity} in whose context the dialog should be shown - */ - public static void showExportMenu(final List<Geocache> caches, final Activity activity) { - final AlertDialog.Builder builder = new AlertDialog.Builder(activity); - builder.setTitle(R.string.export).setIcon(R.drawable.ic_menu_share); - - final ArrayList<Export> export = new ArrayList<Export>(); - for (Class<? extends Export> exporterClass : exporterClasses) { - try { - export.add(exporterClass.newInstance()); - } catch (Exception ex) { - Log.e("showExportMenu", ex); - } - } - - final ArrayAdapter<Export> adapter = new ArrayAdapter<Export>(activity, android.R.layout.select_dialog_item, export); - - builder.setAdapter(adapter, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int item) { - dialog.dismiss(); - final Export selectedExport = adapter.getItem(item); - selectedExport.export(caches, activity); - } - }); - - builder.create().show(); - } -}
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/export/FieldnoteExport.java b/main/src/cgeo/geocaching/export/FieldnoteExport.java index 7d3e07e..c03b848 100644 --- a/main/src/cgeo/geocaching/export/FieldnoteExport.java +++ b/main/src/cgeo/geocaching/export/FieldnoteExport.java @@ -1,5 +1,7 @@ package cgeo.geocaching.export; +import butterknife.ButterKnife; + import cgeo.geocaching.DataStore; import cgeo.geocaching.Geocache; import cgeo.geocaching.LogEntry; @@ -9,14 +11,17 @@ import cgeo.geocaching.connector.ConnectorFactory; import cgeo.geocaching.connector.IConnector; import cgeo.geocaching.connector.capability.FieldNotesCapability; import cgeo.geocaching.settings.Settings; -import cgeo.geocaching.ui.Formatter; import cgeo.geocaching.utils.AsyncTaskWithProgress; +import cgeo.geocaching.utils.Formatter; import cgeo.geocaching.utils.Log; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; +import android.content.Context; import android.content.DialogInterface; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; import android.os.Environment; import android.view.ContextThemeWrapper; import android.view.View; @@ -29,11 +34,11 @@ import java.util.List; * Exports offline logs in the Groundspeak Field Note format. * */ -class FieldnoteExport extends AbstractExport { +public class FieldnoteExport extends AbstractExport { private static final File exportLocation = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/field-notes"); private static int fieldNotesCount = 0; - protected FieldnoteExport() { + public FieldnoteExport() { super(getString(R.string.export_fieldnotes)); } @@ -53,13 +58,18 @@ class FieldnoteExport extends AbstractExport { private Dialog getExportOptionsDialog(final Geocache[] caches, final Activity activity) { final AlertDialog.Builder builder = new AlertDialog.Builder(activity); - // AlertDialog has always dark style, so we have to apply it as well always - final View layout = View.inflate(new ContextThemeWrapper(activity, R.style.dark), R.layout.fieldnote_export_dialog, null); + final Context themedContext; + if (Settings.isLightSkin() && VERSION.SDK_INT < VERSION_CODES.HONEYCOMB) + themedContext = new ContextThemeWrapper(activity, R.style.dark); + else + themedContext = activity; + final View layout = View.inflate(themedContext, R.layout.fieldnote_export_dialog, null); + builder.setView(layout); - final CheckBox uploadOption = (CheckBox) layout.findViewById(R.id.upload); + final CheckBox uploadOption = ButterKnife.findById(layout, R.id.upload); uploadOption.setChecked(Settings.getFieldNoteExportUpload()); - final CheckBox onlyNewOption = (CheckBox) layout.findViewById(R.id.onlynew); + final CheckBox onlyNewOption = ButterKnife.findById(layout, R.id.onlynew); onlyNewOption.setChecked(Settings.getFieldNoteExportOnlyNew()); if (Settings.getFieldnoteExportDate() > 0) { @@ -110,7 +120,7 @@ class FieldnoteExport extends AbstractExport { @Override protected Boolean doInBackgroundInternal(final Geocache[] caches) { // export field notes separately for each connector, so the file can be uploaded to the respective site afterwards - for (IConnector connector : ConnectorFactory.getConnectors()) { + for (final IConnector connector : ConnectorFactory.getConnectors()) { if (connector instanceof FieldNotesCapability) { exportFieldNotes((FieldNotesCapability) connector, caches); } @@ -118,7 +128,7 @@ class FieldnoteExport extends AbstractExport { return true; } - private boolean exportFieldNotes(final FieldNotesCapability connector, Geocache[] caches) { + private boolean exportFieldNotes(final FieldNotesCapability connector, final Geocache[] caches) { final FieldNotes fieldNotes = new FieldNotes(); try { int i = 0; diff --git a/main/src/cgeo/geocaching/export/GpxExport.java b/main/src/cgeo/geocaching/export/GpxExport.java index 08fca0b..61d03bc 100644 --- a/main/src/cgeo/geocaching/export/GpxExport.java +++ b/main/src/cgeo/geocaching/export/GpxExport.java @@ -1,5 +1,7 @@ package cgeo.geocaching.export; +import butterknife.ButterKnife; + import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.Geocache; import cgeo.geocaching.R; @@ -8,15 +10,17 @@ 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; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; +import android.content.Context; import android.content.DialogInterface; -import android.content.Intent; -import android.net.Uri; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; import android.os.Environment; import android.view.ContextThemeWrapper; import android.view.View; @@ -35,9 +39,9 @@ import java.util.Date; import java.util.List; import java.util.Locale; -class GpxExport extends AbstractExport { +public class GpxExport extends AbstractExport { - protected GpxExport() { + public GpxExport() { super(getString(R.string.export_gpx)); } @@ -58,20 +62,25 @@ class GpxExport extends AbstractExport { private Dialog getExportDialog(final String[] geocodes, final Activity activity) { final AlertDialog.Builder builder = new AlertDialog.Builder(activity); - // AlertDialog has always dark style, so we have to apply it as well always - final View layout = View.inflate(new ContextThemeWrapper(activity, R.style.dark), R.layout.gpx_export_dialog, null); + final Context themedContext; + if (Settings.isLightSkin() && VERSION.SDK_INT < VERSION_CODES.HONEYCOMB) + themedContext = new ContextThemeWrapper(activity, R.style.dark); + else + themedContext = activity; + + final View layout = View.inflate(themedContext, R.layout.gpx_export_dialog, null); builder.setView(layout); - final TextView text = (TextView) layout.findViewById(R.id.info); + final TextView text = ButterKnife.findById(layout, R.id.info); text.setText(getString(R.string.export_gpx_info, Settings.getGpxExportDir())); - final CheckBox shareOption = (CheckBox) layout.findViewById(R.id.share); + final CheckBox shareOption = ButterKnife.findById(layout, R.id.share); shareOption.setChecked(Settings.getShareAfterExport()); shareOption.setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View v) { + public void onClick(final View v) { Settings.setShareAfterExport(shareOption.isChecked()); } }); @@ -79,7 +88,7 @@ class GpxExport extends AbstractExport { builder.setPositiveButton(R.string.export, new DialogInterface.OnClickListener() { @Override - public void onClick(DialogInterface dialog, int which) { + public void onClick(final DialogInterface dialog, final int which) { dialog.dismiss(); new ExportTask(activity).execute(geocodes); } @@ -89,7 +98,7 @@ class GpxExport extends AbstractExport { } private static String[] getGeocodes(final List<Geocache> caches) { - final ArrayList<String> allGeocodes = new ArrayList<String>(caches.size()); + final ArrayList<String> allGeocodes = new ArrayList<>(caches.size()); for (final Geocache geocache : caches) { allGeocodes.add(geocache.getGeocode()); } @@ -117,13 +126,13 @@ class GpxExport extends AbstractExport { } @Override - protected File doInBackgroundInternal(String[] geocodes) { + protected File doInBackgroundInternal(final String[] geocodes) { // quick check for being able to write the GPX file if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { return null; } - final List<String> allGeocodes = new ArrayList<String>(Arrays.asList(geocodes)); + final List<String> allGeocodes = new ArrayList<>(Arrays.asList(geocodes)); setMessage(CgeoApplication.getInstance().getResources().getQuantityString(R.plurals.cache_counts, allGeocodes.size(), allGeocodes.size())); @@ -168,11 +177,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/export/GpxSerializer.java b/main/src/cgeo/geocaching/export/GpxSerializer.java index b2587aa..b24eb4c 100644 --- a/main/src/cgeo/geocaching/export/GpxSerializer.java +++ b/main/src/cgeo/geocaching/export/GpxSerializer.java @@ -57,7 +57,7 @@ public final class GpxSerializer { public void writeGPX(List<String> allGeocodesIn, Writer writer, final ProgressListener progressListener) throws IOException { // create a copy of the geocode list, as we need to modify it, but it might be immutable - final ArrayList<String> allGeocodes = new ArrayList<String>(allGeocodesIn); + final ArrayList<String> allGeocodes = new ArrayList<>(allGeocodesIn); this.progressListener = progressListener; gpx.setOutput(writer); @@ -184,8 +184,8 @@ public final class GpxSerializer { private void writeWaypoints(final Geocache cache) throws IOException { final List<Waypoint> waypoints = cache.getWaypoints(); - final List<Waypoint> ownWaypoints = new ArrayList<Waypoint>(waypoints.size()); - final List<Waypoint> originWaypoints = new ArrayList<Waypoint>(waypoints.size()); + final List<Waypoint> ownWaypoints = new ArrayList<>(waypoints.size()); + final List<Waypoint> originWaypoints = new ArrayList<>(waypoints.size()); int maxPrefix = 0; for (final Waypoint wp : cache.getWaypoints()) { diff --git a/main/src/cgeo/geocaching/files/AbstractFileListActivity.java b/main/src/cgeo/geocaching/files/AbstractFileListActivity.java index 07b4fb4..2a05cbc 100644 --- a/main/src/cgeo/geocaching/files/AbstractFileListActivity.java +++ b/main/src/cgeo/geocaching/files/AbstractFileListActivity.java @@ -27,7 +27,7 @@ import java.util.List; public abstract class AbstractFileListActivity<T extends ArrayAdapter<File>> extends AbstractListActivity { private static final int MSG_SEARCH_WHOLE_SD_CARD = 1; - private final List<File> files = new ArrayList<File>(); + private final List<File> files = new ArrayList<>(); private T adapter = null; private ProgressDialog waitDialog = null; private SearchFilesThread searchingThread = null; @@ -51,7 +51,7 @@ public abstract class AbstractFileListActivity<T extends ArrayAdapter<File>> ext } private String getDefaultFolders() { - final ArrayList<String> names = new ArrayList<String>(); + final ArrayList<String> names = new ArrayList<>(); for (File dir : getExistingBaseFolders()) { names.add(dir.getPath()); } @@ -152,7 +152,7 @@ public abstract class AbstractFileListActivity<T extends ArrayAdapter<File>> ext @Override public void run() { - final List<File> list = new ArrayList<File>(); + final List<File> list = new ArrayList<>(); try { if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { @@ -213,7 +213,7 @@ public abstract class AbstractFileListActivity<T extends ArrayAdapter<File>> ext } protected List<File> getExistingBaseFolders() { - ArrayList<File> result = new ArrayList<File>(); + ArrayList<File> result = new ArrayList<>(); for (final File dir : getBaseFolders()) { if (dir.exists() && dir.isDirectory()) { result.add(dir); 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..89ee887 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; @@ -103,12 +105,12 @@ public abstract class GPXParser extends FileParser { private String parentCacheCode = null; private boolean wptVisited = false; private boolean wptUserDefined = false; - private List<LogEntry> logs = new ArrayList<LogEntry>(); + private List<LogEntry> logs = new ArrayList<>(); /** * Parser result. Maps geocode to cache. */ - private final Set<String> result = new HashSet<String>(100); + private final Set<String> result = new HashSet<>(100); private ProgressInputStream progressStream; /** * URL contained in the header of the GPX file. Used to guess where the file is coming from. @@ -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"); @@ -338,11 +340,11 @@ public abstract class GPXParser extends FileParser { // finally store the cache in the database result.add(geocode); - DataStore.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); + DataStore.saveCache(cache, EnumSet.of(SaveFlag.DB)); DataStore.saveLogsWithoutTransaction(cache.getGeocode(), logs); // avoid the cachecache using lots of memory for caches which the user did not actually look at - DataStore.removeCache(geocode, EnumSet.of(RemoveFlag.REMOVE_CACHE)); + DataStore.removeCache(geocode, EnumSet.of(RemoveFlag.CACHE)); showProgressMessage(progressHandler, progressStream.getProgress()); } else if (StringUtils.isNotBlank(cache.getName()) && StringUtils.containsIgnoreCase(type, "waypoint")) { @@ -379,14 +381,14 @@ public abstract class GPXParser extends FileParser { waypoint.setCoords(cache.getCoords()); waypoint.setNote(cache.getDescription()); waypoint.setVisited(wptVisited); - final ArrayList<Waypoint> mergedWayPoints = new ArrayList<Waypoint>(); + final ArrayList<Waypoint> mergedWayPoints = new ArrayList<>(); mergedWayPoints.addAll(cacheForWaypoint.getWaypoints()); - final ArrayList<Waypoint> newPoints = new ArrayList<Waypoint>(); + final ArrayList<Waypoint> newPoints = new ArrayList<>(); newPoints.add(waypoint); Waypoint.mergeWayPoints(newPoints, mergedWayPoints, true); cacheForWaypoint.setWaypoints(newPoints, false); - DataStore.saveCache(cacheForWaypoint, EnumSet.of(SaveFlag.SAVE_DB)); + DataStore.saveCache(cacheForWaypoint, EnumSet.of(SaveFlag.DB)); showProgressMessage(progressHandler, progressStream.getProgress()); } } @@ -814,7 +816,7 @@ public abstract class GPXParser extends FileParser { progressStream = new ProgressInputStream(stream); BufferedReader reader = new BufferedReader(new InputStreamReader(progressStream, CharEncoding.UTF_8)); Xml.parse(new InvalidXMLCharacterFilterReader(reader), root.getContentHandler()); - return DataStore.loadCaches(result, EnumSet.of(LoadFlag.LOAD_DB_MINIMAL)); + return DataStore.loadCaches(result, EnumSet.of(LoadFlag.DB_MINIMAL)); } catch (final SAXException e) { throw new ParserException("Cannot parse .gpx file as GPX " + version + ": could not parse XML", e); } @@ -999,7 +1001,7 @@ public abstract class GPXParser extends FileParser { parentCacheCode = null; wptVisited = false; wptUserDefined = false; - logs = new ArrayList<LogEntry>(); + logs = new ArrayList<>(); cache = new Geocache(this); diff --git a/main/src/cgeo/geocaching/files/LocParser.java b/main/src/cgeo/geocaching/files/LocParser.java index 3d01c1b..2871d77 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; @@ -53,7 +55,7 @@ public final class LocParser extends FileParser { final Map<String, Geocache> cidCoords = parseCoordinates(fileContent); // save found cache coordinates - final HashSet<String> contained = new HashSet<String>(); + final HashSet<String> contained = new HashSet<>(); for (String geocode : searchResult.getGeocodes()) { if (cidCoords.containsKey(geocode)) { contained.add(geocode); @@ -80,7 +82,7 @@ public final class LocParser extends FileParser { } static Map<String, Geocache> parseCoordinates(final String fileContent) { - final Map<String, Geocache> coords = new HashMap<String, Geocache>(); + final Map<String, Geocache> coords = new HashMap<>(); if (StringUtils.isBlank(fileContent)) { return coords; } @@ -116,11 +118,11 @@ 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>(); + final List<Geocache> caches = new ArrayList<>(); for (Entry<String, Geocache> entry : coords.entrySet()) { Geocache coord = entry.getValue(); if (StringUtils.isBlank(coord.getGeocode()) || StringUtils.isBlank(coord.getName())) { @@ -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/files/LocalStorage.java b/main/src/cgeo/geocaching/files/LocalStorage.java index 01c090b..63a1844 100644 --- a/main/src/cgeo/geocaching/files/LocalStorage.java +++ b/main/src/cgeo/geocaching/files/LocalStorage.java @@ -431,7 +431,7 @@ public final class LocalStorage { public static List<File> getStorages() { String extStorage = Environment.getExternalStorageDirectory().getAbsolutePath(); - List<File> storages = new ArrayList<File>(); + List<File> storages = new ArrayList<>(); storages.add(new File(extStorage)); File file = new File(FILE_SYSTEM_TABLE_PATH); if (file.canRead()) { diff --git a/main/src/cgeo/geocaching/files/SimpleDirChooser.java b/main/src/cgeo/geocaching/files/SimpleDirChooser.java index 1e1296a..2aadf16 100644 --- a/main/src/cgeo/geocaching/files/SimpleDirChooser.java +++ b/main/src/cgeo/geocaching/files/SimpleDirChooser.java @@ -1,5 +1,7 @@ package cgeo.geocaching.files; +import butterknife.ButterKnife; + import cgeo.geocaching.Intents; import cgeo.geocaching.R; import cgeo.geocaching.activity.AbstractListActivity; @@ -45,7 +47,7 @@ public class SimpleDirChooser extends AbstractListActivity { private boolean chooseForWriting = false; @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); final Bundle extras = getIntent().getExtras(); currentDir = dirContaining(extras.getString(Intents.EXTRA_START_DIR)); @@ -60,27 +62,27 @@ public class SimpleDirChooser extends AbstractListActivity { resetOkButton(); okButton.setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View v) { + public void onClick(final View v) { setResult(RESULT_OK, new Intent() .setData(Uri.fromFile(new File(currentDir, adapter.getItem(lastPosition).getName())))); finish(); } }); - Button cancelButton = (Button) findViewById(R.id.simple_dir_chooser_cancel); + final Button cancelButton = (Button) findViewById(R.id.simple_dir_chooser_cancel); cancelButton.setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View v) { - Intent intent = new Intent(); + public void onClick(final View v) { + final Intent intent = new Intent(); setResult(RESULT_CANCELED, intent); finish(); } }); - EditText pathField = (EditText) findViewById(R.id.simple_dir_chooser_path); + final EditText pathField = (EditText) findViewById(R.id.simple_dir_chooser_path); pathField.setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View v) { + public void onClick(final View v) { editPath(); } @@ -88,7 +90,7 @@ public class SimpleDirChooser extends AbstractListActivity { } public void editPath() { - AlertDialog.Builder builder = new AlertDialog.Builder(this); + final AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.simple_dir_chooser_current_path); final EditText input = new EditText(this); input.setInputType(InputType.TYPE_CLASS_TEXT); @@ -96,9 +98,9 @@ public class SimpleDirChooser extends AbstractListActivity { builder.setView(input); builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override - public void onClick(DialogInterface dialog, int which) { - String pathText = input.getText().toString(); - File newPathDir = new File(pathText); + public void onClick(final DialogInterface dialog, final int which) { + final String pathText = input.getText().toString(); + final File newPathDir = new File(pathText); if (newPathDir.exists() && newPathDir.isDirectory()) { currentDir = newPathDir; fill(currentDir); @@ -109,7 +111,7 @@ public class SimpleDirChooser extends AbstractListActivity { }); builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override - public void onClick(DialogInterface dialog, int which) { + public void onClick(final DialogInterface dialog, final int which) { dialog.cancel(); } }); @@ -129,18 +131,18 @@ public class SimpleDirChooser extends AbstractListActivity { Environment.getExternalStorageDirectory(); } - private void fill(File dir) { + private void fill(final File dir) { lastPosition = -1; resetOkButton(); - EditText path = (EditText) findViewById(R.id.simple_dir_chooser_path); + final EditText path = (EditText) findViewById(R.id.simple_dir_chooser_path); path.setText(this.getResources().getString(R.string.simple_dir_chooser_current_path) + " " + dir.getAbsolutePath()); final File[] dirs = dir.listFiles(new DirOnlyFilenameFilter()); - List<Option> listDirs = new ArrayList<Option>(); + final List<Option> listDirs = new ArrayList<>(); try { - for (File currentDir : dirs) { + for (final File currentDir : dirs) { listDirs.add(new Option(currentDir.getName(), currentDir.getAbsolutePath(), currentDir.canWrite())); } - } catch (RuntimeException e) { + } catch (final RuntimeException e) { } Collections.sort(listDirs, Option.NAME_COMPARATOR); if (dir.getParent() != null) { @@ -159,11 +161,11 @@ public class SimpleDirChooser extends AbstractListActivity { public class FileArrayAdapter extends ArrayAdapter<Option> { - private Context context; - private int id; - private List<Option> items; + private final Context context; + private final int id; + private final List<Option> items; - public FileArrayAdapter(Context context, int simpleDirItemResId, List<Option> objects) { + public FileArrayAdapter(final Context context, final int simpleDirItemResId, final List<Option> objects) { super(context, simpleDirItemResId, objects); this.context = context; this.id = simpleDirItemResId; @@ -171,26 +173,26 @@ public class SimpleDirChooser extends AbstractListActivity { } @Override - public Option getItem(int index) { + public Option getItem(final int index) { return items.get(index); } @Override - public View getView(int position, View convertView, ViewGroup parent) { + public View getView(final int position, final View convertView, final ViewGroup parent) { View v = convertView; if (v == null) { - LayoutInflater vi = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + final LayoutInflater vi = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); v = vi.inflate(id, null); } final Option option = items.get(position); if (option != null) { - TextView t1 = (TextView) v.findViewById(R.id.TextView01); + final TextView t1 = ButterKnife.findById(v, R.id.TextView01); if (t1 != null) { t1.setOnClickListener(new OnTextViewClickListener(position)); t1.setText(option.getName()); } - CheckBox check = (CheckBox) v.findViewById(R.id.CheckBox); + final CheckBox check = ButterKnife.findById(v, R.id.CheckBox); if (check != null) { if (!chooseForWriting || option.isWriteable()) { check.setOnClickListener(new OnCheckBoxClickListener(position)); @@ -206,20 +208,20 @@ public class SimpleDirChooser extends AbstractListActivity { } public class OnTextViewClickListener implements OnClickListener { - private int position; + private final int position; - OnTextViewClickListener(int position) { + OnTextViewClickListener(final int position) { this.position = position; } @Override - public void onClick(View arg0) { - Option option = adapter.getItem(position); + public void onClick(final View arg0) { + final Option option = adapter.getItem(position); if (option.getName().equals(PARENT_DIR)) { currentDir = new File(option.getPath()); fill(currentDir); } else { - File dir = new File(option.getPath()); + final File dir = new File(option.getPath()); if (dir.list(new DirOnlyFilenameFilter()).length > 0) { currentDir = dir; fill(currentDir); @@ -229,16 +231,16 @@ public class SimpleDirChooser extends AbstractListActivity { } public class OnCheckBoxClickListener implements OnClickListener { - private int position; + private final int position; - OnCheckBoxClickListener(int position) { + OnCheckBoxClickListener(final int position) { this.position = position; } @Override - public void onClick(View arg0) { - Option lastOption = (lastPosition > -1) ? adapter.getItem(lastPosition) : null; - Option currentOption = adapter.getItem(position); + public void onClick(final View arg0) { + final Option lastOption = (lastPosition > -1) ? adapter.getItem(lastPosition) : null; + final Option currentOption = adapter.getItem(position); if (lastOption != null) { lastOption.setChecked(false); } @@ -264,13 +266,13 @@ public class SimpleDirChooser extends AbstractListActivity { private static Comparator<Option> NAME_COMPARATOR = new Comparator<SimpleDirChooser.Option>() { @Override - public int compare(Option lhs, Option rhs) { + public int compare(final Option lhs, final Option rhs) { return String.CASE_INSENSITIVE_ORDER.compare(lhs.name, rhs.name); } }; - public Option(String name, String path, boolean writeable) { + public Option(final String name, final String path, final boolean writeable) { this.name = name; this.path = path; this.writeable = writeable; @@ -288,7 +290,7 @@ public class SimpleDirChooser extends AbstractListActivity { return this.checked; } - public void setChecked(boolean checked) { + public void setChecked(final boolean checked) { this.checked = checked; } @@ -300,8 +302,8 @@ public class SimpleDirChooser extends AbstractListActivity { public static class DirOnlyFilenameFilter implements FilenameFilter { @Override - public boolean accept(File dir, String filename) { - File file = new File(dir, filename); + public boolean accept(final File dir, final String filename) { + final File file = new File(dir, filename); return file.isDirectory() && file.canRead(); } diff --git a/main/src/cgeo/geocaching/filter/AbstractFilter.java b/main/src/cgeo/geocaching/filter/AbstractFilter.java index 6bd8587..e602b0f 100644 --- a/main/src/cgeo/geocaching/filter/AbstractFilter.java +++ b/main/src/cgeo/geocaching/filter/AbstractFilter.java @@ -14,7 +14,7 @@ abstract class AbstractFilter implements IFilter { @Override public void filter(final List<Geocache> list) { - final List<Geocache> itemsToRemove = new ArrayList<Geocache>(); + final List<Geocache> itemsToRemove = new ArrayList<>(); for (Geocache item : list) { if (!accepts(item)) { itemsToRemove.add(item); diff --git a/main/src/cgeo/geocaching/filter/AttributeFilter.java b/main/src/cgeo/geocaching/filter/AttributeFilter.java index 552a48c..b59ab29 100644 --- a/main/src/cgeo/geocaching/filter/AttributeFilter.java +++ b/main/src/cgeo/geocaching/filter/AttributeFilter.java @@ -36,7 +36,7 @@ class AttributeFilter extends AbstractFilter { final String packageName = CgeoApplication.getInstance().getBaseContext().getPackageName(); final Resources res = CgeoApplication.getInstance().getResources(); - final List<IFilter> filters = new LinkedList<IFilter>(); + final List<IFilter> filters = new LinkedList<>(); for (final String id: res.getStringArray(R.array.attribute_ids)) { filters.add(new AttributeFilter(getName("attribute_" + id, res, packageName), id)); } diff --git a/main/src/cgeo/geocaching/filter/DifficultyFilter.java b/main/src/cgeo/geocaching/filter/DifficultyFilter.java index 438c25a..175ad75 100644 --- a/main/src/cgeo/geocaching/filter/DifficultyFilter.java +++ b/main/src/cgeo/geocaching/filter/DifficultyFilter.java @@ -25,7 +25,7 @@ class DifficultyFilter extends AbstractRangeFilter { @Override public List<IFilter> getFilters() { - final ArrayList<IFilter> filters = new ArrayList<IFilter>(DIFFICULTY_MAX); + final ArrayList<IFilter> filters = new ArrayList<>(DIFFICULTY_MAX); for (int difficulty = DIFFICULTY_MIN; difficulty <= DIFFICULTY_MAX; difficulty++) { filters.add(new DifficultyFilter(difficulty)); } diff --git a/main/src/cgeo/geocaching/filter/DistanceFilter.java b/main/src/cgeo/geocaching/filter/DistanceFilter.java index c217e7c..3328c72 100644 --- a/main/src/cgeo/geocaching/filter/DistanceFilter.java +++ b/main/src/cgeo/geocaching/filter/DistanceFilter.java @@ -40,7 +40,7 @@ class DistanceFilter extends AbstractFilter { @Override public List<IFilter> getFilters() { - final List<IFilter> filters = new ArrayList<IFilter>(KILOMETERS.length); + final List<IFilter> filters = new ArrayList<>(KILOMETERS.length); for (int i = 0; i < KILOMETERS.length; i++) { final int minRange = KILOMETERS[i]; final int maxRange; diff --git a/main/src/cgeo/geocaching/filter/FilterUserInterface.java b/main/src/cgeo/geocaching/filter/FilterUserInterface.java index b6eaa78..9f1d563 100644 --- a/main/src/cgeo/geocaching/filter/FilterUserInterface.java +++ b/main/src/cgeo/geocaching/filter/FilterUserInterface.java @@ -44,7 +44,7 @@ public final class FilterUserInterface { this.activity = activity; this.res = CgeoApplication.getInstance().getResources(); - registry = new ArrayList<FactoryEntry>(); + registry = new ArrayList<>(); if (Settings.getCacheType() == CacheType.ALL) { register(R.string.caches_filter_type, TypeFilter.Factory.class); } @@ -82,7 +82,7 @@ public final class FilterUserInterface { final AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setTitle(R.string.caches_filter_title); - final ArrayAdapter<FactoryEntry> adapter = new ArrayAdapter<FactoryEntry>(activity, android.R.layout.select_dialog_item, registry); + final ArrayAdapter<FactoryEntry> adapter = new ArrayAdapter<>(activity, android.R.layout.select_dialog_item, registry); builder.setAdapter(adapter, new DialogInterface.OnClickListener() { @Override @@ -116,7 +116,7 @@ public final class FilterUserInterface { final AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setTitle(menuTitle); - final ArrayAdapter<IFilter> adapter = new ArrayAdapter<IFilter>(activity, android.R.layout.select_dialog_item, filters); + final ArrayAdapter<IFilter> adapter = new ArrayAdapter<>(activity, android.R.layout.select_dialog_item, filters); builder.setAdapter(adapter, new DialogInterface.OnClickListener() { @Override public void onClick(final DialogInterface dialog, final int item) { diff --git a/main/src/cgeo/geocaching/filter/OriginFilter.java b/main/src/cgeo/geocaching/filter/OriginFilter.java index 8c54a4c..99d1c05 100644 --- a/main/src/cgeo/geocaching/filter/OriginFilter.java +++ b/main/src/cgeo/geocaching/filter/OriginFilter.java @@ -27,7 +27,7 @@ public class OriginFilter extends AbstractFilter { @Override public List<OriginFilter> getFilters() { - final ArrayList<OriginFilter> filters = new ArrayList<OriginFilter>(); + final ArrayList<OriginFilter> filters = new ArrayList<>(); for (IConnector connector : ConnectorFactory.getConnectors()) { filters.add(new OriginFilter(connector)); } diff --git a/main/src/cgeo/geocaching/filter/PopularityFilter.java b/main/src/cgeo/geocaching/filter/PopularityFilter.java index d4f54ef..a0244b9 100644 --- a/main/src/cgeo/geocaching/filter/PopularityFilter.java +++ b/main/src/cgeo/geocaching/filter/PopularityFilter.java @@ -28,7 +28,7 @@ class PopularityFilter extends AbstractFilter { @Override public List<IFilter> getFilters() { - final List<IFilter> filters = new ArrayList<IFilter>(FAVORITES.length); + final List<IFilter> filters = new ArrayList<>(FAVORITES.length); for (int i = 0; i < FAVORITES.length; i++) { final int minRange = FAVORITES[i]; final int maxRange = Integer.MAX_VALUE; diff --git a/main/src/cgeo/geocaching/filter/PopularityRatioFilter.java b/main/src/cgeo/geocaching/filter/PopularityRatioFilter.java index 2d7207a..a04f219 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) { @@ -59,7 +52,7 @@ class PopularityRatioFilter extends AbstractFilter { @Override public List<IFilter> getFilters() { - final List<IFilter> filters = new ArrayList<IFilter>(RATIOS.length); + final List<IFilter> filters = new ArrayList<>(RATIOS.length); for (int i = 0; i < RATIOS.length; i++) { final int minRange = RATIOS[i]; final int maxRange = Integer.MAX_VALUE; diff --git a/main/src/cgeo/geocaching/filter/SizeFilter.java b/main/src/cgeo/geocaching/filter/SizeFilter.java index 13c1d87..f02874c 100644 --- a/main/src/cgeo/geocaching/filter/SizeFilter.java +++ b/main/src/cgeo/geocaching/filter/SizeFilter.java @@ -29,7 +29,7 @@ class SizeFilter extends AbstractFilter { @Override public List<IFilter> getFilters() { final CacheSize[] cacheSizes = CacheSize.values(); - final List<IFilter> filters = new LinkedList<IFilter>(); + final List<IFilter> filters = new LinkedList<>(); for (CacheSize cacheSize : cacheSizes) { if (cacheSize != CacheSize.UNKNOWN) { filters.add(new SizeFilter(cacheSize)); diff --git a/main/src/cgeo/geocaching/filter/StateFilter.java b/main/src/cgeo/geocaching/filter/StateFilter.java index f452259..ebe133c 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)); @@ -113,8 +126,9 @@ abstract class StateFilter extends AbstractFilter { @Override public List<StateFilter> getFilters() { - final List<StateFilter> filters = new ArrayList<StateFilter>(6); + final List<StateFilter> filters = new ArrayList<>(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/filter/TerrainFilter.java b/main/src/cgeo/geocaching/filter/TerrainFilter.java index f14313c..7da6a19 100644 --- a/main/src/cgeo/geocaching/filter/TerrainFilter.java +++ b/main/src/cgeo/geocaching/filter/TerrainFilter.java @@ -24,7 +24,7 @@ class TerrainFilter extends AbstractRangeFilter { @Override public List<IFilter> getFilters() { - final ArrayList<IFilter> filters = new ArrayList<IFilter>(TERRAIN_MAX); + final ArrayList<IFilter> filters = new ArrayList<>(TERRAIN_MAX); for (int terrain = TERRAIN_MIN; terrain <= TERRAIN_MAX; terrain++) { filters.add(new TerrainFilter(terrain)); } diff --git a/main/src/cgeo/geocaching/filter/TypeFilter.java b/main/src/cgeo/geocaching/filter/TypeFilter.java index ea0ccff..d363d39 100644 --- a/main/src/cgeo/geocaching/filter/TypeFilter.java +++ b/main/src/cgeo/geocaching/filter/TypeFilter.java @@ -29,7 +29,7 @@ class TypeFilter extends AbstractFilter { @Override public List<IFilter> getFilters() { final CacheType[] types = CacheType.values(); - final List<IFilter> filters = new LinkedList<IFilter>(); + final List<IFilter> filters = new LinkedList<>(); for (CacheType cacheType : types) { if (cacheType != CacheType.ALL) { filters.add(new TypeFilter(cacheType)); diff --git a/main/src/cgeo/geocaching/gcvote/GCVote.java b/main/src/cgeo/geocaching/gcvote/GCVote.java index 0ab1fe3..8de3edc 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; @@ -33,7 +35,7 @@ public final class GCVote { private static final Pattern PATTERN_VOTE_ELEMENT = Pattern.compile("<vote ([^>]+)>", Pattern.CASE_INSENSITIVE); private static final int MAX_CACHED_RATINGS = 1000; - private static final LeastRecentlyUsedMap<String, GCVoteRating> RATINGS_CACHE = new LeastRecentlyUsedMap.LruCache<String, GCVoteRating>(MAX_CACHED_RATINGS); + private static final LeastRecentlyUsedMap<String, GCVoteRating> RATINGS_CACHE = new LeastRecentlyUsedMap.LruCache<>(MAX_CACHED_RATINGS); private static final float MIN_RATING = 1; private static final float MAX_RATING = 5; @@ -74,7 +76,7 @@ public final class GCVote { return null; } - final Map<String, GCVoteRating> ratings = new HashMap<String, GCVoteRating>(); + final Map<String, GCVoteRating> ratings = new HashMap<>(); try { final Parameters params = new Parameters(); @@ -253,13 +255,13 @@ 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 */ private static @NonNull ArrayList<String> getVotableGeocodes(final @NonNull Collection<Geocache> caches) { - final ArrayList<String> geocodes = new ArrayList<String>(caches.size()); + final ArrayList<String> geocodes = new ArrayList<>(caches.size()); for (final Geocache cache : caches) { String geocode = cache.getGeocode(); if (StringUtils.isNotBlank(geocode) && cache.supportsGCVote()) { @@ -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..018216d 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) { @@ -26,7 +28,7 @@ public class Units { units = "m"; } } - return new ImmutablePair<Double, String>(distance, units); + return new ImmutablePair<>(distance, units); } public static String getDistanceFromKilometers(final Float 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..9b57b3a 100644 --- a/main/src/cgeo/geocaching/list/AbstractList.java +++ b/main/src/cgeo/geocaching/list/AbstractList.java @@ -8,7 +8,7 @@ public abstract class AbstractList { public final int id; public final String title; - private static SparseArray<AbstractList> LISTS = new SparseArray<AbstractList>(); + private static SparseArray<AbstractList> LISTS = new SparseArray<>(); public AbstractList(final int id, final String title) { this.id = id; @@ -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..53632a0 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); app = CgeoApplication.getInstance(); res = app.getResources(); } @@ -77,33 +78,16 @@ 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>(); + final List<CharSequence> listsTitle = new ArrayList<>(); for (AbstractList list : lists) { listsTitle.add(list.getTitleAndCount()); } 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<>(); + 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..2ca0cfd 100644 --- a/main/src/cgeo/geocaching/maps/CGeoMap.java +++ b/main/src/cgeo/geocaching/maps/CGeoMap.java @@ -1,5 +1,7 @@ package cgeo.geocaching.maps; +import butterknife.ButterKnife; + import cgeo.geocaching.CacheListActivity; import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.DataStore; @@ -38,19 +40,19 @@ import cgeo.geocaching.utils.AngleUtils; import cgeo.geocaching.utils.CancellableHandler; import cgeo.geocaching.utils.LeastRecentlyUsedSet; import cgeo.geocaching.utils.Log; +import cgeo.geocaching.utils.MapUtils; 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 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,18 +63,20 @@ 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; -import android.util.SparseArray; import android.view.Menu; 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,11 +96,14 @@ 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; - private CompositeSubscription resumeSubscription; + /** + * initialization with an empty subscription to make static code analysis tools more happy + */ + private Subscription resumeSubscription = Subscriptions.empty(); /** Controls the behavior of the map */ public enum MapMode { @@ -133,11 +140,17 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto private static final String BUNDLE_LIVE_ENABLED = "liveEnabled"; private static final String BUNDLE_TRAIL_HISTORY = "trailHistory"; - private Resources res = null; - private MapItemFactory mapItemFactory = null; - private Activity activity = null; - private MapViewImpl mapView = null; - private CgeoApplication app = null; + // Those are initialized in onCreate() and will never be null afterwards + private Resources res; + private Activity activity; + private CgeoApplication app; + private MapItemFactory mapItemFactory; + private String mapTitle; + private LeastRecentlyUsedSet<Geocache> caches; + private MapViewImpl mapView; + private CachesOverlay overlayCaches; + private PositionAndScaleOverlay overlayPositionAndScale; + final private GeoDirHandler geoDirUpdate; private SearchResult searchIntent = null; private String geocodeIntent = null; @@ -151,8 +164,6 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto private boolean noMapTokenShowed = false; // map status data private boolean followMyLocation = false; - private Viewport viewport = null; - private int zoom = -100; // threads private Subscription loadTimer; private LoadDetails loadDetailsThread = null; @@ -160,33 +171,20 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto private volatile long loadThreadRun = 0L; //Interthread communication flag private volatile boolean downloaded = false; - // overlays - private CachesOverlay overlayCaches = null; - private PositionAndScaleOverlay overlayPositionAndScale = null; - // data for overlays - private static final int[][] INSET_RELIABLE = { { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } }; // center, 33x40 / 45x51 / 60x68 - private static final int[][] INSET_TYPE = { { 5, 8, 6, 10 }, { 4, 4, 5, 11 }, { 4, 4, 5, 11 } }; // center, 22x22 / 36x36 - private static final int[][] INSET_OWN = { { 21, 0, 0, 26 }, { 25, 0, 0, 35 }, { 40, 0, 0, 48 } }; // top right, 12x12 / 16x16 / 20x20 - private static final int[][] INSET_FOUND = { { 0, 0, 21, 28 }, { 0, 0, 25, 35 }, { 0, 0, 40, 48 } }; // top left, 12x12 / 16x16 / 20x20 - 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>(); + /** Count of caches currently visible */ private int cachesCnt = 0; - /** List of caches in the viewport */ - private LeastRecentlyUsedSet<Geocache> caches = null; /** List of waypoints in the viewport */ - private final LeastRecentlyUsedSet<Waypoint> waypoints = new LeastRecentlyUsedSet<Waypoint>(MAX_CACHES); + private final LeastRecentlyUsedSet<Waypoint> waypoints = new LeastRecentlyUsedSet<>(MAX_CACHES); // storing for offline private ProgressDialog waitDialog = null; private int detailTotal = 0; private int detailProgress = 0; private long detailProgressTime = 0L; - // views - private ImageSwitcher myLocSwitch = null; - /** Controls the map behaviour */ + // views + private CheckBox myLocSwitch = null; + /** Controls the map behavior */ private MapMode mapMode = null; /** Live mode enabled for map. **/ private boolean isLiveEnabled; @@ -194,90 +192,147 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto private boolean markersInvalidated = false; // previous state for loadTimer private boolean centered = false; // if map is already centered private boolean alreadyCentered = false; // -""- for setting my location - private static final Set<String> dirtyCaches = new HashSet<String>(); + private static final Set<String> dirtyCaches = new HashSet<>(); + /** * if live map is enabled, this is the minimum zoom level, independent of the stored setting */ private static final int MIN_LIVEMAP_ZOOM = 12; - // Thread pooling - private static BlockingQueue<Runnable> displayQueue = new ArrayBlockingQueue<Runnable>(1); + private static BlockingQueue<Runnable> displayQueue = new ArrayBlockingQueue<>(1); private static ThreadPoolExecutor displayExecutor = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, displayQueue, new ThreadPoolExecutor.DiscardOldestPolicy()); - private static BlockingQueue<Runnable> downloadQueue = new ArrayBlockingQueue<Runnable>(1); + private static BlockingQueue<Runnable> downloadQueue = new ArrayBlockingQueue<>(1); private static ThreadPoolExecutor downloadExecutor = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, downloadQueue, new ThreadPoolExecutor.DiscardOldestPolicy()); - private static BlockingQueue<Runnable> loadQueue = new ArrayBlockingQueue<Runnable>(1); - private static ThreadPoolExecutor loadExecutor = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, loadQueue, new ThreadPoolExecutor.DiscardOldestPolicy()); + private static BlockingQueue<Runnable> loadQueue = new ArrayBlockingQueue<>(1); + private static ThreadPoolExecutor loadExecutor = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, loadQueue, new ThreadPoolExecutor.DiscardOldestPolicy()); // 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<>(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.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 = ButterKnife.findById(activity, 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<>(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,8 +344,8 @@ 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; - waitDialog.setMessage(res.getString(R.string.caches_downloading) + " " + minsRemaining + " " + res.getQuantityString(R.plurals.caches_eta_mins, minsRemaining)); + final int minsRemaining = secondsRemaining / 60; + waitDialog.setMessage(res.getString(R.string.caches_downloading) + " " + res.getQuantityString(R.plurals.caches_eta_mins, minsRemaining, minsRemaining)); } } } else if (msg.what == FINISHED_LOADING_DETAILS) { @@ -300,35 +355,19 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } } } - @Override public void handleCancel(final Object extra) { if (loadDetailsThread != null) { loadDetailsThread.stopIt(); } } - } - final private Handler noMapTokenHandler = new Handler() { - - @Override - public void handleMessage(Message msg) { - if (!noMapTokenShowed) { - ActivityMixin.showToast(activity, res.getString(R.string.map_token_err)); - - noMapTokenShowed = true; - } - } - }; - /** - * calling activities can set the map title via extras - */ - private String mapTitle; + } /* Current source id */ private int currentSourceId; - public CGeoMap(MapActivityImpl activity) { + public CGeoMap(final MapActivityImpl activity) { super(activity); geoDirUpdate = new UpdateLoc(this); } @@ -356,13 +395,12 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto outState.putInt(BUNDLE_MAP_SOURCE, currentSourceId); outState.putIntArray(BUNDLE_MAP_STATE, currentMapState()); outState.putBoolean(BUNDLE_LIVE_ENABLED, isLiveEnabled); - if (overlayPositionAndScale != null) { - outState.putParcelableArrayList(BUNDLE_TRAIL_HISTORY, overlayPositionAndScale.getHistory()); - } + outState.putParcelableArrayList(BUNDLE_TRAIL_HISTORY, overlayPositionAndScale.getHistory()); } + @TargetApi(Build.VERSION_CODES.HONEYCOMB) @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); // class init @@ -370,8 +408,8 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto activity = this.getActivity(); app = (CgeoApplication) activity.getApplication(); - int countBubbleCnt = DataStore.getAllCachesCount(); - caches = new LeastRecentlyUsedSet<Geocache>(MAX_CACHES + countBubbleCnt); + final int countBubbleCnt = DataStore.getAllCachesCount(); + caches = new LeastRecentlyUsedSet<>(MAX_CACHES + countBubbleCnt); final MapProvider mapProvider = Settings.getMapProvider(); mapItemFactory = mapProvider.getMapItemFactory(); @@ -418,10 +456,14 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto ActivityMixin.keepScreenOn(activity, true); + // set layout ActivityMixin.setTheme(activity); + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB) { + 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,20 +471,15 @@ 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(); - if (overlayCaches == null) { - overlayCaches = mapView.createAddMapOverlay(mapView.getContext(), getResources().getDrawable(R.drawable.marker)); - } - - if (overlayPositionAndScale == null) { - overlayPositionAndScale = mapView.createAddPositionAndScaleOverlay(activity); - if (trailHistory != null) { - overlayPositionAndScale.setHistory(trailHistory); - } + overlayCaches = mapView.createAddMapOverlay(mapView.getContext(), getResources().getDrawable(R.drawable.marker)); + overlayPositionAndScale = mapView.createAddPositionAndScaleOverlay(); + if (trailHistory != null) { + overlayPositionAndScale.setHistory(trailHistory); } mapView.repaintRequired(null); @@ -462,14 +499,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 = ButterKnife.findById(activity, R.id.my_position); + if (locSwitch!=null) { + initMyLocationSwitchButton(locSwitch); + } prepareFilterBar(); if (!app.isLiveMapHintShownInThisSession() && !Settings.getHideLiveMapHint() && Settings.getLiveMapHintShowCount() <= 3) { @@ -477,6 +511,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,8 +533,9 @@ 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(); - ((TextView) activity.findViewById(R.id.filter_text)).setText(cacheType); + final String cacheType = Settings.getCacheType().getL10n(); + final TextView filterTitleView = ButterKnife.findById(activity, R.id.filter_text); + filterTitleView.setText(cacheType); activity.findViewById(R.id.filter_bar).setVisibility(View.VISIBLE); } else { activity.findViewById(R.id.filter_bar).setVisibility(View.GONE); @@ -503,8 +548,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 +559,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } dirtyCaches.clear(); // Update display - displayExecutor.execute(new DisplayRunnable(mapView.getViewport())); + displayExecutor.execute(new DisplayRunnable(this)); } } @@ -523,17 +568,16 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto resumeSubscription.unsubscribe(); savePrefs(); - if (mapView != null) { - mapView.destroyDrawingCache(); - } + mapView.destroyDrawingCache(); - overlaysCache.clear(); + MapUtils.clearCachedItems(); 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 +585,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 +623,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.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 +651,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 +659,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); @@ -635,7 +683,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto case R.id.menu_store_caches: if (!isLoading()) { final Set<String> geocodesInViewport = getGeocodesForCachesInViewport(); - final List<String> geocodes = new ArrayList<String>(); + final List<String> geocodes = new ArrayList<>(); for (final String geocode : geocodesInViewport) { if (!DataStore.isOffline(geocode, null)) { @@ -667,10 +715,6 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } return true; case R.id.menu_circle_mode: - if (overlayCaches == null) { - return false; - } - overlayCaches.switchCircles(); mapView.repaintRequired(overlayCaches); ActivityMixin.invalidateOptionsMenu(activity); @@ -726,16 +770,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<>(); 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 +788,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 +796,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) { @@ -773,7 +817,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto * @return a non-null Set of geocodes corresponding to the caches that are shown on screen. */ private Set<String> getGeocodesForCachesInViewport() { - final Set<String> geocodes = new HashSet<String>(); + final Set<String> geocodes = new HashSet<>(); final List<Geocache> cachesProtected = caches.getAsList(); final Viewport viewport = mapView.getViewport(); @@ -802,7 +846,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto if (restartRequired) { mapRestart(); - } else if (mapView != null) { + } else if (mapView != null) { // changeMapSource can be called by onCreate() mapView.setMapSource(); ActivityMixin.invalidateOptionsMenu(activity); } @@ -818,7 +862,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); @@ -845,10 +889,6 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto * @return the current map state as an array of int, or null if no map state is available */ private int[] currentMapState() { - if (mapView == null) { - return mapStateIntent; - } - final GeoPointImpl mapCenter = mapView.getMapViewCenter(); return new int[] { mapCenter.getLatitudeE6(), @@ -860,10 +900,6 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } private void savePrefs() { - if (mapView == null) { - return; - } - Settings.setMapZoom(mapView.getMapZoomLevel()); Settings.setMapCenter(mapView.getMapViewCenter()); } @@ -894,10 +930,10 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto /** * weak reference to the outer class */ - private final WeakReference<CGeoMap> map; + private final WeakReference<CGeoMap> mapRef; - public UpdateLoc(CGeoMap map) { - this.map = new WeakReference<CGeoMap>(map); + public UpdateLoc(final CGeoMap map) { + mapRef = new WeakReference<>(map); } @Override @@ -922,41 +958,35 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto timeLastPositionOverlayCalculation = currentTimeMillis; try { - CGeoMap cgeoMapRef = map.get(); - if (cgeoMapRef != null) { - if (cgeoMapRef.mapView != null) { - if (cgeoMapRef.overlayPositionAndScale == null) { - cgeoMapRef.overlayPositionAndScale = cgeoMapRef.mapView.createAddPositionAndScaleOverlay(cgeoMapRef.activity); - } - - boolean needsRepaintForDistance = needsRepaintForDistance(); - boolean needsRepaintForHeading = needsRepaintForHeading(); - - if (needsRepaintForDistance) { - if (cgeoMapRef.followMyLocation) { - cgeoMapRef.centerMap(new Geopoint(currentLocation)); - } + final CGeoMap map = mapRef.get(); + if (map != null) { + final boolean needsRepaintForDistance = needsRepaintForDistance(); + final boolean needsRepaintForHeading = needsRepaintForHeading(); + + if (needsRepaintForDistance) { + if (map.followMyLocation) { + map.centerMap(new Geopoint(currentLocation)); } + } - if (needsRepaintForDistance || needsRepaintForHeading) { - cgeoMapRef.overlayPositionAndScale.setCoordinates(currentLocation); - cgeoMapRef.overlayPositionAndScale.setHeading(currentHeading); - cgeoMapRef.mapView.repaintRequired(cgeoMapRef.overlayPositionAndScale); - } + if (needsRepaintForDistance || needsRepaintForHeading) { + map.overlayPositionAndScale.setCoordinates(currentLocation); + map.overlayPositionAndScale.setHeading(currentHeading); + map.mapView.repaintRequired(map.overlayPositionAndScale); } } - } catch (RuntimeException e) { + } catch (final RuntimeException e) { Log.w("Failed to update location."); } } } boolean needsRepaintForHeading() { - final CGeoMap cgeoMapRef = map.get(); - if (cgeoMapRef == null) { + final CGeoMap map = mapRef.get(); + if (map == null) { return false; } - return Math.abs(AngleUtils.difference(currentHeading, cgeoMapRef.overlayPositionAndScale.getHeading())) > MIN_HEADING_DELTA; + return Math.abs(AngleUtils.difference(currentHeading, map.overlayPositionAndScale.getHeading())) > MIN_HEADING_DELTA; } boolean needsRepaintForDistance() { @@ -964,11 +994,11 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto return false; } - final CGeoMap cgeoMapRef = map.get(); - if (cgeoMapRef == null) { + final CGeoMap map = mapRef.get(); + if (map == null) { return false; } - final Location lastLocation = cgeoMapRef.overlayPositionAndScale.getCoordinates(); + final Location lastLocation = map.overlayPositionAndScale.getCoordinates(); float dist = Float.MAX_VALUE; if (lastLocation != null) { @@ -976,11 +1006,11 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } final float[] mapDimension = new float[1]; - if (cgeoMapRef.mapView.getWidth() < cgeoMapRef.mapView.getHeight()) { - final double span = cgeoMapRef.mapView.getLongitudeSpan() / 1e6; + if (map.mapView.getWidth() < map.mapView.getHeight()) { + final double span = map.mapView.getLongitudeSpan() / 1e6; Location.distanceBetween(currentLocation.getLatitude(), currentLocation.getLongitude(), currentLocation.getLatitude(), currentLocation.getLongitude() + span, mapDimension); } else { - final double span = cgeoMapRef.mapView.getLatitudeSpan() / 1e6; + final double span = map.mapView.getLatitudeSpan() / 1e6; Location.distanceBetween(currentLocation.getLatitude(), currentLocation.getLongitude(), currentLocation.getLatitude() + span, currentLocation.getLongitude(), mapDimension); } @@ -1003,50 +1033,62 @@ 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; + private int previousZoom = -100; + private Viewport previousViewport; - long currentTime = System.currentTimeMillis(); + public LoadTimerAction(@NonNull final CGeoMap map) { + this.mapRef = new WeakReference<>(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 { + // 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) || (previousViewport == null) || zoomNow != previousZoom || + (mapMoved(previousViewport, viewportNow) && (map.cachesCnt <= 0 || CollectionUtils.isEmpty(map.caches) || !previousViewport.includes(viewportNow))); + + // update title on any change + if (moved || !viewportNow.equals(previousViewport)) { + map.displayHandler.sendEmptyMessage(UPDATE_TITLE); + } + previousZoom = zoomNow; - } catch (Exception e) { - Log.w("CGeoMap.startLoadtimer.start", e); + // save new values + if (moved) { + map.markersInvalidated = false; + + final long currentTime = System.currentTimeMillis(); + + if (1000 < (currentTime - map.loadThreadRun)) { + previousViewport = 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), 0, 250, TimeUnit.MILLISECONDS); } /** @@ -1066,79 +1108,84 @@ 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())); - } + public void runWithMap(final CGeoMap map) { + 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(mapView.getViewport(), 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(mapView.getViewport(), 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(mapView.getViewport(), 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 +1193,119 @@ 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); + public void runWithMap(final CGeoMap map) { + map.doDownloadRun(); + } + } + + private void doDownloadRun() { + try { + showProgressHandler.sendEmptyMessage(SHOW_PROGRESS); // show progress + if (Settings.isGCConnectorActive()) { + if (tokens == null) { + tokens = GCLogin.getInstance().getMapTokens(); + if (StringUtils.isEmpty(tokens.getUserSession()) || StringUtils.isEmpty(tokens.getSessionToken())) { + tokens = null; + if (!noMapTokenShowed) { + ActivityMixin.showToast(activity, res.getString(R.string.map_token_err)); + noMapTokenShowed = true; } } } - 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(mapView.getViewport().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.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(); - } + public void runWithMap(final CGeoMap map) { + map.doDisplayRun(); + } + } - // display caches - final List<Geocache> cachesToDisplay = caches.getAsList(); - final List<Waypoint> waypointsToDisplay = new ArrayList<Waypoint>(waypoints); - final List<CachesOverlayItemImpl> itemsToDisplay = new ArrayList<CachesOverlayItemImpl>(); + private void doDisplayRun() { + try { + showProgressHandler.sendEmptyMessage(SHOW_PROGRESS); - 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) { + // display caches + final List<Geocache> cachesToDisplay = caches.getAsList(); + final List<Waypoint> waypointsToDisplay = new ArrayList<>(waypoints); + final List<CachesOverlayItemImpl> itemsToDisplay = new ArrayList<>(); - if (waypoint == null || waypoint.getCoords() == null) { - continue; - } + 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) { - itemsToDisplay.add(getWaypointItem(waypoint)); - } - } - for (Geocache cache : cachesToDisplay) { - - 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,11 +1323,21 @@ 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(final Viewport viewport) { - this.viewport = viewport; + protected DoRunnable(@NonNull final CGeoMap map) { + mapRef = new WeakReference<>(map); } + + @Override + final public void run() { + final CGeoMap map = mapRef.get(); + if (map != null) { + runWithMap(map); + } + } + + abstract protected void runWithMap(final CGeoMap map); } /** @@ -1281,7 +1346,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,23 +1357,23 @@ 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 { - waitDialog.setMessage(res.getString(R.string.caches_downloading) + " " + roundedEta + " " + res.getQuantityString(R.plurals.caches_eta_mins, roundedEta)); + waitDialog.setMessage(res.getString(R.string.caches_downloading) + " " + res.getQuantityString(R.plurals.caches_eta_mins, roundedEta, roundedEta)); } waitDialog.show(); @@ -1353,7 +1418,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 +1432,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<>(); + for (final Geocache cache : caches) { + if ((excludeMine && cache.isFound()) || (excludeMine && cache.isOwner()) || (excludeDisabled && cache.isDisabled()) || (excludeDisabled && cache.isArchived())) { removeList.add(cache); } } @@ -1392,9 +1457,6 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto if (coords == null) { return; } - if (mapView == null) { - return; - } final MapControllerImpl mapController = mapView.getMapController(); final GeoPointImpl target = makeGeoPoint(coords); @@ -1410,14 +1472,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 +1503,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 +1512,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 +1523,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<>(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<>(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 +1582,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; @@ -1556,88 +1641,13 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto private CachesOverlayItemImpl getCacheItem(final Geocache cache) { final CachesOverlayItemImpl item = mapItemFactory.getCachesOverlayItem(cache, cache.applyDistanceRule()); - - final int hashcode = new HashCodeBuilder() - .append(cache.isReliableLatLon()) - .append(cache.getType().id) - .append(cache.isDisabled() || cache.isArchived()) - .append(cache.getMapMarkerId()) - .append(cache.isOwner()) - .append(cache.isFound()) - .append(cache.hasUserModifiedCoords()) - .append(cache.getPersonalNote()) - .append(cache.isLogOffline()) - .append(cache.getListId() > 0) - .toHashCode(); - - LayerDrawable drawable = overlaysCache.get(hashcode); - if (drawable == null) { - drawable = createCacheItem(cache, hashcode); - } - item.setMarker(drawable); + item.setMarker(MapUtils.getCacheItem(getResources(), cache)); return item; } - private LayerDrawable createCacheItem(final Geocache cache, final int hashcode) { - // Set initial capacities to the maximum of layers and insets to avoid dynamic reallocation - final ArrayList<Drawable> layers = new ArrayList<Drawable>(9); - final ArrayList<int[]> insets = new ArrayList<int[]>(8); - - // background: disabled or not - final Drawable marker = getResources().getDrawable(cache.getMapMarkerId()); - layers.add(marker); - final int resolution = marker.getIntrinsicWidth() > 40 ? (marker.getIntrinsicWidth() > 50 ? 2 : 1) : 0; - // reliable or not - if (!cache.isReliableLatLon()) { - insets.add(INSET_RELIABLE[resolution]); - layers.add(getResources().getDrawable(R.drawable.marker_notreliable)); - } - // cache type - layers.add(getResources().getDrawable(cache.getType().markerId)); - insets.add(INSET_TYPE[resolution]); - // own - if (cache.isOwner()) { - layers.add(getResources().getDrawable(R.drawable.marker_own)); - insets.add(INSET_OWN[resolution]); - // if not, checked if stored - } else if (cache.getListId() > 0) { - layers.add(getResources().getDrawable(R.drawable.marker_stored)); - insets.add(INSET_OWN[resolution]); - } - // found - if (cache.isFound()) { - layers.add(getResources().getDrawable(R.drawable.marker_found)); - insets.add(INSET_FOUND[resolution]); - // if not, perhaps logged offline - } else if (cache.isLogOffline()) { - layers.add(getResources().getDrawable(R.drawable.marker_found_offline)); - insets.add(INSET_FOUND[resolution]); - } - // user modified coords - if (cache.hasUserModifiedCoords()) { - layers.add(getResources().getDrawable(R.drawable.marker_usermodifiedcoords)); - insets.add(INSET_USERMODIFIEDCOORDS[resolution]); - } - // personal note - if (cache.getPersonalNote() != null) { - layers.add(getResources().getDrawable(R.drawable.marker_personalnote)); - insets.add(INSET_PERSONALNOTE[resolution]); - } - - final LayerDrawable ld = new LayerDrawable(layers.toArray(new Drawable[layers.size()])); - - int index = 1; - for (final int[] inset : insets) { - ld.setLayerInset(index++, inset[0], inset[1], inset[2], inset[3]); - } - - overlaysCache.put(hashcode, ld); - return ld; - } - 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..3c6109e 100644 --- a/main/src/cgeo/geocaching/maps/CachesOverlay.java +++ b/main/src/cgeo/geocaching/maps/CachesOverlay.java @@ -40,7 +40,7 @@ import java.util.List; public class CachesOverlay extends AbstractItemizedOverlay { - private List<CachesOverlayItemImpl> items = new ArrayList<CachesOverlayItemImpl>(); + private List<CachesOverlayItemImpl> items = new ArrayList<>(); private Context context = null; private boolean displayCircles = false; private Progress progress = new Progress(); @@ -61,7 +61,7 @@ public class CachesOverlay extends AbstractItemizedOverlay { } void updateItems(CachesOverlayItemImpl item) { - List<CachesOverlayItemImpl> itemsPre = new ArrayList<CachesOverlayItemImpl>(); + List<CachesOverlayItemImpl> itemsPre = new ArrayList<>(); itemsPre.add(item); updateItems(itemsPre); @@ -79,7 +79,7 @@ public class CachesOverlay extends AbstractItemizedOverlay { // ensure no interference between the draw and content changing routines getOverlayImpl().lock(); try { - items = new ArrayList<CachesOverlayItemImpl>(itemsPre); + items = new ArrayList<>(itemsPre); setLastFocusedItemIndex(-1); // to reset tap during data change populate(); @@ -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/MapProviderFactory.java b/main/src/cgeo/geocaching/maps/MapProviderFactory.java index 890274d..dd4ff0f 100644 --- a/main/src/cgeo/geocaching/maps/MapProviderFactory.java +++ b/main/src/cgeo/geocaching/maps/MapProviderFactory.java @@ -2,7 +2,7 @@ package cgeo.geocaching.maps; import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.R; -import cgeo.geocaching.maps.google.GoogleMapProvider; +import cgeo.geocaching.maps.google.v1.GoogleMapProvider; import cgeo.geocaching.maps.interfaces.MapProvider; import cgeo.geocaching.maps.interfaces.MapSource; import cgeo.geocaching.maps.mapsforge.MapsforgeMapProvider; @@ -21,7 +21,7 @@ import java.util.List; public class MapProviderFactory { - private final static ArrayList<MapSource> mapSources = new ArrayList<MapSource>(); + private final static ArrayList<MapSource> mapSources = new ArrayList<>(); static { // add GoogleMapProvider only if google api is available in order to support x86 android emulator @@ -108,7 +108,7 @@ public class MapProviderFactory { * remove offline map sources after changes of the settings */ public static void deleteOfflineMapSources() { - final ArrayList<MapSource> deletion = new ArrayList<MapSource>(); + final ArrayList<MapSource> deletion = new ArrayList<>(); for (MapSource mapSource : mapSources) { if (mapSource instanceof MapsforgeMapProvider.OfflineMapSource) { deletion.add(mapSource); 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..08244ef 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(); } @@ -105,7 +103,7 @@ public class PositionDrawer { if (Settings.isMapTrail()) { // always add current position to drawn history to have a closed connection - final ArrayList<Location> paintHistory = new ArrayList<Location>(positionHistory.getHistory()); + final ArrayList<Location> paintHistory = new ArrayList<>(positionHistory.getHistory()); paintHistory.add(coordinates); int size = paintHistory.size(); @@ -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/PositionHistory.java b/main/src/cgeo/geocaching/maps/PositionHistory.java index bc6779e..af13740 100644 --- a/main/src/cgeo/geocaching/maps/PositionHistory.java +++ b/main/src/cgeo/geocaching/maps/PositionHistory.java @@ -19,7 +19,7 @@ public class PositionHistory { */ private static final int MAX_POSITIONS = 700; - private ArrayList<Location> history = new ArrayList<Location>(); + private ArrayList<Location> history = new ArrayList<>(); /** * Adds the current position to the trail history to be able to show the trail on the map. 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/GoogleCacheOverlay.java b/main/src/cgeo/geocaching/maps/google/v1/GoogleCacheOverlay.java index d14c687..9b18c2d 100644 --- a/main/src/cgeo/geocaching/maps/google/GoogleCacheOverlay.java +++ b/main/src/cgeo/geocaching/maps/google/v1/GoogleCacheOverlay.java @@ -1,4 +1,4 @@ -package cgeo.geocaching.maps.google; +package cgeo.geocaching.maps.google.v1; import cgeo.geocaching.maps.CachesOverlay; import cgeo.geocaching.maps.interfaces.ItemizedOverlayImpl; diff --git a/main/src/cgeo/geocaching/maps/google/GoogleCacheOverlayItem.java b/main/src/cgeo/geocaching/maps/google/v1/GoogleCacheOverlayItem.java index b26654a..463aae9 100644 --- a/main/src/cgeo/geocaching/maps/google/GoogleCacheOverlayItem.java +++ b/main/src/cgeo/geocaching/maps/google/v1/GoogleCacheOverlayItem.java @@ -1,4 +1,4 @@ -package cgeo.geocaching.maps.google; +package cgeo.geocaching.maps.google.v1; import cgeo.geocaching.IWaypoint; import cgeo.geocaching.maps.interfaces.CachesOverlayItemImpl; diff --git a/main/src/cgeo/geocaching/maps/google/GoogleGeoPoint.java b/main/src/cgeo/geocaching/maps/google/v1/GoogleGeoPoint.java index d5f6385..2f540ad 100644 --- a/main/src/cgeo/geocaching/maps/google/GoogleGeoPoint.java +++ b/main/src/cgeo/geocaching/maps/google/v1/GoogleGeoPoint.java @@ -1,4 +1,4 @@ -package cgeo.geocaching.maps.google; +package cgeo.geocaching.maps.google.v1; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.maps.interfaces.GeoPointImpl; diff --git a/main/src/cgeo/geocaching/maps/google/GoogleMapActivity.java b/main/src/cgeo/geocaching/maps/google/v1/GoogleMapActivity.java index a98241f..374e7b0 100644 --- a/main/src/cgeo/geocaching/maps/google/GoogleMapActivity.java +++ b/main/src/cgeo/geocaching/maps/google/v1/GoogleMapActivity.java @@ -1,5 +1,6 @@ -package cgeo.geocaching.maps.google; +package cgeo.geocaching.maps.google.v1; +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/GoogleMapController.java b/main/src/cgeo/geocaching/maps/google/v1/GoogleMapController.java index 096cd61..ea95676 100644 --- a/main/src/cgeo/geocaching/maps/google/GoogleMapController.java +++ b/main/src/cgeo/geocaching/maps/google/v1/GoogleMapController.java @@ -1,4 +1,4 @@ -package cgeo.geocaching.maps.google; +package cgeo.geocaching.maps.google.v1; import cgeo.geocaching.maps.interfaces.GeoPointImpl; import cgeo.geocaching.maps.interfaces.MapControllerImpl; diff --git a/main/src/cgeo/geocaching/maps/google/GoogleMapItemFactory.java b/main/src/cgeo/geocaching/maps/google/v1/GoogleMapItemFactory.java index c708dc5..d7e9380 100644 --- a/main/src/cgeo/geocaching/maps/google/GoogleMapItemFactory.java +++ b/main/src/cgeo/geocaching/maps/google/v1/GoogleMapItemFactory.java @@ -1,4 +1,4 @@ -package cgeo.geocaching.maps.google; +package cgeo.geocaching.maps.google.v1; import cgeo.geocaching.IWaypoint; import cgeo.geocaching.geopoint.Geopoint; diff --git a/main/src/cgeo/geocaching/maps/google/GoogleMapProjection.java b/main/src/cgeo/geocaching/maps/google/v1/GoogleMapProjection.java index dc694b8..901a369 100644 --- a/main/src/cgeo/geocaching/maps/google/GoogleMapProjection.java +++ b/main/src/cgeo/geocaching/maps/google/v1/GoogleMapProjection.java @@ -1,4 +1,4 @@ -package cgeo.geocaching.maps.google; +package cgeo.geocaching.maps.google.v1; import cgeo.geocaching.maps.interfaces.GeoPointImpl; import cgeo.geocaching.maps.interfaces.MapProjectionImpl; diff --git a/main/src/cgeo/geocaching/maps/google/GoogleMapProvider.java b/main/src/cgeo/geocaching/maps/google/v1/GoogleMapProvider.java index 38d7d96..884e076 100644 --- a/main/src/cgeo/geocaching/maps/google/GoogleMapProvider.java +++ b/main/src/cgeo/geocaching/maps/google/v1/GoogleMapProvider.java @@ -1,4 +1,4 @@ -package cgeo.geocaching.maps.google; +package cgeo.geocaching.maps.google.v1; import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.R; diff --git a/main/src/cgeo/geocaching/maps/google/GoogleMapView.java b/main/src/cgeo/geocaching/maps/google/v1/GoogleMapView.java index 610dbe1..c611790 100644 --- a/main/src/cgeo/geocaching/maps/google/GoogleMapView.java +++ b/main/src/cgeo/geocaching/maps/google/v1/GoogleMapView.java @@ -1,4 +1,4 @@ -package cgeo.geocaching.maps.google; +package cgeo.geocaching.maps.google.v1; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; @@ -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/v1/GoogleOverlay.java index 0a5cf69..40a5539 100644 --- a/main/src/cgeo/geocaching/maps/google/GoogleOverlay.java +++ b/main/src/cgeo/geocaching/maps/google/v1/GoogleOverlay.java @@ -1,4 +1,4 @@ -package cgeo.geocaching.maps.google; +package cgeo.geocaching.maps.google.v1; import cgeo.geocaching.maps.PositionAndScaleOverlay; import cgeo.geocaching.maps.interfaces.GeneralOverlay; @@ -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/MapsforgeMapProvider.java b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapProvider.java index 9f09991..01b10ec 100644 --- a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapProvider.java +++ b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapProvider.java @@ -59,7 +59,7 @@ public final class MapsforgeMapProvider extends AbstractMapProvider { File directory = new File(directoryPath); if (directory.isDirectory()) { try { - ArrayList<String> mapFileList = new ArrayList<String>(); + ArrayList<String> mapFileList = new ArrayList<>(); final File[] files = directory.listFiles(); if (ArrayUtils.isNotEmpty(files)) { for (File file : files) { 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..31edc9f 100644 --- a/main/src/cgeo/geocaching/network/HtmlImage.java +++ b/main/src/cgeo/geocaching/network/HtmlImage.java @@ -9,25 +9,25 @@ import cgeo.geocaching.list.StoredList; import cgeo.geocaching.utils.CancellableHandler; import cgeo.geocaching.utils.FileUtils; import cgeo.geocaching.utils.ImageUtils; +import cgeo.geocaching.utils.ImageUtils.ContainerDrawable; 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; @@ -38,15 +38,13 @@ import android.graphics.Point; import android.graphics.drawable.BitmapDrawable; import android.net.Uri; import android.text.Html; +import android.widget.TextView; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.util.Date; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; public class HtmlImage implements Html.ImageGetter { @@ -59,7 +57,9 @@ public class HtmlImage implements Html.ImageGetter { // given URL will be returned. This observable will emit the local copy of the image if it is present, // regardless of its freshness, then if needed an updated fresher copy after retrieving it from the network. // - If onlySave is false and the instance is used as an ImageGetter, only the final version of the - // image will be returned. + // image will be returned, unless a view has been provided. If it has, then a dummy drawable is returned + // and is updated when the image is available, possibly several times if we had a stale copy of the image + // and then got a new one from the network. private static final String[] BLOCKED = new String[] { "gccounter.de", @@ -89,26 +89,55 @@ public class HtmlImage implements Html.ImageGetter { final private int maxWidth; final private int maxHeight; final private Resources resources; + final private TextView view; // 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>())); - public HtmlImage(final String geocode, final boolean returnErrorImage, final int listId, final boolean onlySave) { + /** + * Create a new HtmlImage object with different behaviours depending on <tt>onlySave</tt> and <tt>view</tt> values. + * + * @param geocode the geocode of the item for which we are requesting the image + * @param returnErrorImage set to <tt>true</tt> if an error image should be returned in case of a problem, + * <tt>false</tt> to get a transparent 1x1 image instead + * @param listId the list this cache belongs to, used to determine if an older image for the offline case can be used or not + * @param onlySave if set to <tt>true</tt>, {@link #getDrawable(String)} will only fetch and store the image, not return it + * @param view if non-null, {@link #getDrawable(String)} will return an initially empty drawable which will be redrawn when + * the image is ready through an invalidation of the given view + */ + public HtmlImage(final String geocode, final boolean returnErrorImage, final int listId, final boolean onlySave, final TextView view) { this.geocode = geocode; this.returnErrorImage = returnErrorImage; this.listId = listId; this.onlySave = onlySave; + this.view = view; - Point displaySize = Compatibility.getDisplaySize(); + final Point displaySize = Compatibility.getDisplaySize(); this.maxWidth = displaySize.x - 25; this.maxHeight = displaySize.y - 25; this.resources = CgeoApplication.getInstance().getResources(); } + /** + * Create a new HtmlImage object with different behaviours depending on <tt>onlySave</tt> value. No view object + * will be tied to this HtmlImage. + * + * For documentation, see {@link #HtmlImage(String, boolean, int, boolean, TextView)}. + */ + public HtmlImage(final String geocode, final boolean returnErrorImage, final int listId, final boolean onlySave) { + this(geocode, returnErrorImage, listId, onlySave, null); + } + + /** + * Retrieve and optionally display an image. + * See {@link #HtmlImage(String, boolean, int, boolean, TextView)} for the various behaviours. + * + * @param url + * the URL to fetch from cache or network + * @return a drawable containing the image, or <tt>null</tt> if <tt>onlySave</tt> is <tt>true</tt> + */ @Nullable @Override public BitmapDrawable getDrawable(final String url) { @@ -122,7 +151,10 @@ public class HtmlImage implements Html.ImageGetter { })); return null; } - return drawable.toBlockingObservable().lastOrDefault(null); + if (view == null) { + return drawable.toBlocking().lastOrDefault(null); + } + return new ContainerDrawable(view, drawable); } // Caches are loaded from disk on a computation scheduler to avoid using more threads than cores while decoding @@ -130,7 +162,7 @@ public class HtmlImage implements Html.ImageGetter { public Observable<BitmapDrawable> fetchDrawable(final String url) { if (StringUtils.isBlank(url) || ImageUtils.containsPattern(url, BLOCKED)) { - return Observable.from(getTransparent1x1Image(resources)); + return Observable.from(ImageUtils.getTransparent1x1Drawable(resources)); } // Explicit local file URLs are loaded from the filesystem regardless of their age. The IO part is short @@ -152,9 +184,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 +197,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) { + RxUtils.networkScheduler.createWorker().schedule(new Action0() { + @Override public void call() { downloadAndSave(subscriber); } - })); + }); } })); } @@ -178,7 +209,7 @@ public class HtmlImage implements Html.ImageGetter { private Pair<BitmapDrawable, Boolean> loadFromDisk() { final Pair<Bitmap, Boolean> loadResult = loadImageFromStorage(url, pseudoGeocode, shared); final Bitmap bitmap = loadResult.getLeft(); - return new ImmutablePair<BitmapDrawable, Boolean>(bitmap != null ? + return new ImmutablePair<>(bitmap != null ? ImageUtils.scaleBitmapToFitDisplay(bitmap) : null, loadResult.getRight() @@ -195,19 +226,17 @@ public class HtmlImage implements Html.ImageGetter { subscriber.onCompleted(); return; } - } else { - if (subscriber.isUnsubscribed() || downloadOrRefreshCopy(url, file)) { + } else if (subscriber.isUnsubscribed() || downloadOrRefreshCopy(url, file)) { // The existing copy was fresh enough or we were unsubscribed earlier. subscriber.onCompleted(); return; - } } 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) { @@ -215,7 +244,7 @@ public class HtmlImage implements Html.ImageGetter { } else { subscriber.onNext(returnErrorImage ? new BitmapDrawable(resources, BitmapFactory.decodeResource(resources, R.drawable.image_not_loaded)) : - getTransparent1x1Image(resources)); + ImageUtils.getTransparent1x1Drawable(resources)); } subscriber.onCompleted(); } @@ -225,12 +254,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; } /** @@ -257,7 +286,7 @@ public class HtmlImage implements Html.ImageGetter { return true; } } - } catch (Exception e) { + } catch (final Exception e) { Log.e("HtmlImage.downloadOrRefreshCopy", e); } } @@ -284,10 +313,6 @@ public class HtmlImage implements Html.ImageGetter { } } - private BitmapDrawable getTransparent1x1Image(final Resources res) { - return new BitmapDrawable(res, BitmapFactory.decodeResource(resources, R.drawable.image_no_placement)); - } - /** * Load an image from primary or secondary storage. * @@ -306,10 +331,10 @@ public class HtmlImage implements Html.ImageGetter { } final File fileSec = LocalStorage.getStorageSecFile(pseudoGeocode, url, true); return loadCachedImage(fileSec, forceKeep); - } catch (Exception e) { + } catch (final Exception e) { Log.w("HtmlImage.loadImageFromStorage", e); } - return new ImmutablePair<Bitmap, Boolean>(null, false); + return new ImmutablePair<>(null, false); } @Nullable @@ -351,7 +376,7 @@ public class HtmlImage implements Html.ImageGetter { if (file.exists()) { final boolean freshEnough = listId >= StoredList.STANDARD_LIST_ID || file.lastModified() > (new Date().getTime() - (24 * 60 * 60 * 1000)) || forceKeep; if (onlySave) { - return new ImmutablePair<Bitmap, Boolean>(null, true); + return new ImmutablePair<>(null, true); } final BitmapFactory.Options bfOptions = new BitmapFactory.Options(); bfOptions.inTempStorage = new byte[16 * 1024]; @@ -360,24 +385,24 @@ public class HtmlImage implements Html.ImageGetter { final Bitmap image = BitmapFactory.decodeFile(file.getPath(), bfOptions); if (image == null) { Log.e("Cannot decode bitmap from " + file.getPath()); - return new ImmutablePair<Bitmap, Boolean>(null, false); + return new ImmutablePair<>(null, false); } - return new ImmutablePair<Bitmap, Boolean>(image, + return new ImmutablePair<>(image, freshEnough); } - return new ImmutablePair<Bitmap, Boolean>(null, false); + return new ImmutablePair<>(null, false); } private void setSampleSize(final File file, final BitmapFactory.Options bfOptions) { //Decode image size only - BitmapFactory.Options options = new BitmapFactory.Options(); + final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BufferedInputStream stream = null; try { stream = new BufferedInputStream(new FileInputStream(file)); BitmapFactory.decodeStream(stream, null, options); - } catch (FileNotFoundException e) { + } catch (final FileNotFoundException e) { Log.e("HtmlImage.setSampleSize", e); } finally { IOUtils.closeQuietly(stream); diff --git a/main/src/cgeo/geocaching/network/OAuth.java b/main/src/cgeo/geocaching/network/OAuth.java index fa376af..cfc62fc 100644 --- a/main/src/cgeo/geocaching/network/OAuth.java +++ b/main/src/cgeo/geocaching/network/OAuth.java @@ -31,7 +31,7 @@ public class OAuth { "oauth_version", "1.0"); params.sort(); - final List<String> paramsEncoded = new ArrayList<String>(); + final List<String> paramsEncoded = new ArrayList<>(); for (final NameValuePair nameValue : params) { paramsEncoded.add(nameValue.getName() + "=" + OAuth.percentEncode(nameValue.getValue())); } 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..82650d1 100644 --- a/main/src/cgeo/geocaching/network/StatusUpdater.java +++ b/main/src/cgeo/geocaching/network/StatusUpdater.java @@ -1,14 +1,14 @@ package cgeo.geocaching.network; import cgeo.geocaching.CgeoApplication; +import cgeo.geocaching.utils.RxUtils; import cgeo.geocaching.utils.Version; import org.json.JSONException; import org.json.JSONObject; -import rx.Scheduler; -import rx.schedulers.Schedulers; + +import rx.functions.Action0; import rx.subjects.BehaviorSubject; -import rx.functions.Action1; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; @@ -52,9 +52,9 @@ public class StatusUpdater { final static public BehaviorSubject<Status> latestStatus = BehaviorSubject.create(Status.defaultStatus(null)); static { - Schedulers.io().schedulePeriodically(new Action1<Scheduler.Inner>() { + RxUtils.networkScheduler.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..a4799cb 100644 --- a/main/src/cgeo/geocaching/sensors/GeoDataProvider.java +++ b/main/src/cgeo/geocaching/sensors/GeoDataProvider.java @@ -1,13 +1,12 @@ 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; @@ -24,6 +23,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; @@ -96,11 +96,11 @@ public class GeoDataProvider implements OnSubscribe<IGeoData> { final private Listener gpsListener = new Listener(LocationManager.GPS_PROVIDER, gpsLocation); @Override - public Subscription connect() { + public void connect(Action1<? super Subscription> connection) { 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 +118,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 + ")"); @@ -135,7 +135,7 @@ public class GeoDataProvider implements OnSubscribe<IGeoData> { })); } }); - return subscription; + connection.call(subscription); } }; diff --git a/main/src/cgeo/geocaching/settings/AbstractCheckCredentialsPreference.java b/main/src/cgeo/geocaching/settings/AbstractCheckCredentialsPreference.java index 917c9c4..2f83028 100644 --- a/main/src/cgeo/geocaching/settings/AbstractCheckCredentialsPreference.java +++ b/main/src/cgeo/geocaching/settings/AbstractCheckCredentialsPreference.java @@ -5,13 +5,15 @@ import cgeo.geocaching.activity.ActivityMixin; import cgeo.geocaching.enumerations.StatusCode; import cgeo.geocaching.network.Cookies; import cgeo.geocaching.ui.dialog.Dialogs; +import cgeo.geocaching.utils.RxUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; + +import rx.Observable; import rx.android.observables.AndroidObservable; import rx.functions.Action1; import rx.functions.Func0; -import rx.schedulers.Schedulers; import rx.util.async.Async; import android.app.ProgressDialog; @@ -38,55 +40,62 @@ public abstract class AbstractCheckCredentialsPreference extends AbstractClickab protected abstract ImmutablePair<String, String> getCredentials(); - protected abstract ImmutablePair<StatusCode, ? extends Drawable> login(); + /** + * Try to login. + * + * @return A pair containing the status code, and, if the status code is + * <tt>NO_ERROR</tt>, an observable (or <tt>null</tt>) wihch may emit + * the avatar for the user (every drawable will be shown in place of the previous one). + */ + protected abstract ImmutablePair<StatusCode, Observable<Drawable>> login(); private class LoginCheckClickListener implements OnPreferenceClickListener { - final private SettingsActivity activity; + final private SettingsActivity settingsActivity; LoginCheckClickListener(final SettingsActivity activity) { - this.activity = activity; + this.settingsActivity = activity; } @Override public boolean onPreferenceClick(Preference preference) { - final Resources res = activity.getResources(); + final Resources res = settingsActivity.getResources(); final ImmutablePair<String, String> credentials = getCredentials(); // check credentials for validity if (StringUtils.isBlank(credentials.getLeft()) || StringUtils.isBlank(credentials.getRight())) { - ActivityMixin.showToast(activity, R.string.err_missing_auth); + ActivityMixin.showToast(settingsActivity, R.string.err_missing_auth); return false; } - final ProgressDialog loginDialog = ProgressDialog.show(activity, + final ProgressDialog loginDialog = ProgressDialog.show(settingsActivity, res.getString(R.string.init_login_popup), res.getString(R.string.init_login_popup_working), true); loginDialog.setCancelable(false); Cookies.clearCookies(); - AndroidObservable.bindActivity(activity, Async.start(new Func0<ImmutablePair<StatusCode, ? extends Drawable>>() { + AndroidObservable.bindActivity(settingsActivity, Async.start(new Func0<ImmutablePair<StatusCode, Observable<Drawable>>>() { @Override - public ImmutablePair<StatusCode, ? extends Drawable> call() { + public ImmutablePair<StatusCode, Observable<Drawable>> call() { return login(); } - })).subscribe(new Action1<ImmutablePair<StatusCode, ? extends Drawable>>() { + })).subscribeOn(RxUtils.networkScheduler).subscribe(new Action1<ImmutablePair<StatusCode, Observable<Drawable>>>() { @Override - public void call(final ImmutablePair<StatusCode, ? extends Drawable> loginInfo) { + public void call(final ImmutablePair<StatusCode, Observable<Drawable>> loginInfo) { loginDialog.dismiss(); if (loginInfo.getLeft() == StatusCode.NO_ERROR) { - Dialogs.message(activity, R.string.init_login_popup, R.string.init_login_popup_ok, loginInfo.getRight()); + Dialogs.message(settingsActivity, R.string.init_login_popup, R.string.init_login_popup_ok, loginInfo.getRight()); } else { - Dialogs.message(activity, R.string.init_login_popup, + Dialogs.message(settingsActivity, R.string.init_login_popup, res.getString(R.string.init_login_popup_failed_reason) + " " + loginInfo.getLeft().getErrorString(res) + "." ); } - activity.initBasicMemberPreferences(); + settingsActivity.initBasicMemberPreferences(); } - }, Schedulers.io()); + }); return false; // no shared preference has to be changed } diff --git a/main/src/cgeo/geocaching/settings/CheckECCredentialsPreference.java b/main/src/cgeo/geocaching/settings/CheckECCredentialsPreference.java index c1cf740..f5d9ab5 100644 --- a/main/src/cgeo/geocaching/settings/CheckECCredentialsPreference.java +++ b/main/src/cgeo/geocaching/settings/CheckECCredentialsPreference.java @@ -6,6 +6,8 @@ import cgeo.geocaching.enumerations.StatusCode; import org.apache.commons.lang3.tuple.ImmutablePair; +import rx.Observable; + import android.content.Context; import android.graphics.drawable.Drawable; import android.util.AttributeSet; @@ -26,7 +28,7 @@ public class CheckECCredentialsPreference extends AbstractCheckCredentialsPrefer } @Override - protected ImmutablePair<StatusCode, Drawable> login() { - return new ImmutablePair<StatusCode, Drawable>(ECLogin.getInstance().login(), null); + protected ImmutablePair<StatusCode, Observable<Drawable>> login() { + return new ImmutablePair<>(ECLogin.getInstance().login(), null); } } diff --git a/main/src/cgeo/geocaching/settings/CheckGcCredentialsPreference.java b/main/src/cgeo/geocaching/settings/CheckGcCredentialsPreference.java index 2a05f47..0269f3b 100644 --- a/main/src/cgeo/geocaching/settings/CheckGcCredentialsPreference.java +++ b/main/src/cgeo/geocaching/settings/CheckGcCredentialsPreference.java @@ -5,6 +5,8 @@ import cgeo.geocaching.enumerations.StatusCode; import org.apache.commons.lang3.tuple.ImmutablePair; +import rx.Observable; + import android.content.Context; import android.graphics.drawable.Drawable; import android.util.AttributeSet; @@ -25,7 +27,7 @@ public class CheckGcCredentialsPreference extends AbstractCheckCredentialsPrefer } @Override - protected ImmutablePair<StatusCode, ? extends Drawable> login() { + protected ImmutablePair<StatusCode, Observable<Drawable>> login() { final StatusCode loginResult = GCLogin.getInstance().login(); switch (loginResult) { case NO_ERROR: diff --git a/main/src/cgeo/geocaching/settings/OAuthPreference.java b/main/src/cgeo/geocaching/settings/OAuthPreference.java index 54f8023..54bad6d 100644 --- a/main/src/cgeo/geocaching/settings/OAuthPreference.java +++ b/main/src/cgeo/geocaching/settings/OAuthPreference.java @@ -23,13 +23,14 @@ public class OAuthPreference extends AbstractClickablePreference { OCNL(R.string.pref_fakekey_ocnl_authorization, OCAuthorizationActivity.class, OCAuthParams.OC_NL_AUTH_PARAMS), OCUS(R.string.pref_fakekey_ocus_authorization, OCAuthorizationActivity.class, OCAuthParams.OC_US_AUTH_PARAMS), OCRO(R.string.pref_fakekey_ocro_authorization, OCAuthorizationActivity.class, OCAuthParams.OC_RO_AUTH_PARAMS), + OCUK(R.string.pref_fakekey_ocuk_authorization, OCAuthorizationActivity.class, OCAuthParams.OC_UK_AUTH_PARAMS), TWITTER(R.string.pref_fakekey_twitter_authorization, TwitterAuthorizationActivity.class, TwitterAuthorizationActivity.TWITTER_OAUTH_PARAMS); public final int prefKeyId; public final Class<?> authActivity; public final OAuthParameters authParams; - OAuthActivityMapping(int prefKeyId, Class<?> authActivity, OAuthParameters authParams) { + OAuthActivityMapping(final int prefKeyId, final Class<?> authActivity, final OAuthParameters authParams) { this.prefKeyId = prefKeyId; this.authActivity = authActivity; this.authParams = authParams; @@ -40,7 +41,7 @@ public class OAuthPreference extends AbstractClickablePreference { private OAuthActivityMapping getAuthorization() { final String prefKey = getKey(); - for (OAuthActivityMapping auth : OAuthActivityMapping.values()) { + for (final OAuthActivityMapping auth : OAuthActivityMapping.values()) { if (auth.prefKeyId != NO_KEY && prefKey.equals(CgeoApplication.getInstance().getString(auth.prefKeyId))) { return auth; } @@ -48,12 +49,12 @@ public class OAuthPreference extends AbstractClickablePreference { return OAuthActivityMapping.NONE; } - public OAuthPreference(Context context, AttributeSet attrs) { + public OAuthPreference(final Context context, final AttributeSet attrs) { super(context, attrs); this.oAuthMapping = getAuthorization(); } - public OAuthPreference(Context context, AttributeSet attrs, int defStyle) { + public OAuthPreference(final Context context, final AttributeSet attrs, final int defStyle) { super(context, attrs, defStyle); this.oAuthMapping = getAuthorization(); } @@ -63,9 +64,9 @@ public class OAuthPreference extends AbstractClickablePreference { activity.setAuthTitle(oAuthMapping.prefKeyId); return new OnPreferenceClickListener() { @Override - public boolean onPreferenceClick(Preference preference) { + public boolean onPreferenceClick(final Preference preference) { if (oAuthMapping.authActivity != null && oAuthMapping.authParams != null) { - Intent authIntent = new Intent(preference.getContext(), + final Intent authIntent = new Intent(preference.getContext(), oAuthMapping.authActivity); oAuthMapping.authParams.setOAuthExtras(authIntent); activity.startActivityForResult(authIntent, diff --git a/main/src/cgeo/geocaching/settings/OCPreferenceKeys.java b/main/src/cgeo/geocaching/settings/OCPreferenceKeys.java index e3c5aca..d8983e8 100644 --- a/main/src/cgeo/geocaching/settings/OCPreferenceKeys.java +++ b/main/src/cgeo/geocaching/settings/OCPreferenceKeys.java @@ -26,11 +26,14 @@ public enum OCPreferenceKeys { R.string.pref_ocnl_tokenpublic, R.string.pref_ocnl_tokensecret, OCAuthParams.OC_NL_AUTH_PARAMS), OC_RO("oc.ro", R.string.pref_connectorOCROActive, R.string.preference_screen_ocro, R.string.pref_fakekey_ocro_authorization, R.string.pref_fakekey_ocro_website, - R.string.pref_ocro_tokenpublic, R.string.pref_ocro_tokensecret, OCAuthParams.OC_RO_AUTH_PARAMS); + R.string.pref_ocro_tokenpublic, R.string.pref_ocro_tokensecret, OCAuthParams.OC_RO_AUTH_PARAMS), + OC_UK("oc.uk", R.string.pref_connectorOCUKActive, R.string.preference_screen_ocuk, + R.string.pref_fakekey_ocuk_authorization, R.string.pref_fakekey_ocuk_website, + R.string.pref_ocuk_tokenpublic, R.string.pref_ocuk_tokensecret, OCAuthParams.OC_UK_AUTH_PARAMS); - private OCPreferenceKeys(final String siteId, final int isActivePrefId, final int prefScreenId, final int authPrefId, - final int websitePrefId, final int publicTokenPrefId, final int privateTokenPrefId, final OCAuthParams authParams) { + OCPreferenceKeys(final String siteId, final int isActivePrefId, final int prefScreenId, final int authPrefId, + final int websitePrefId, final int publicTokenPrefId, final int privateTokenPrefId, final OCAuthParams authParams) { this.siteId = siteId; this.isActivePrefId = isActivePrefId; this.prefScreenId = prefScreenId; @@ -46,10 +49,10 @@ public enum OCPreferenceKeys { private static final SparseArray<OCPreferenceKeys> FIND_BY_AUTH_PREF_ID; static { - FIND_BY_ISACTIVE_ID = new SparseArray<OCPreferenceKeys>(values().length); - FIND_BY_AUTH_PREF_ID = new SparseArray<OCPreferenceKeys>(values().length); - Map<String, OCPreferenceKeys> byIsactiveKey = new HashMap<String, OCPreferenceKeys>(); - for (OCPreferenceKeys key : values()) { + FIND_BY_ISACTIVE_ID = new SparseArray<>(values().length); + FIND_BY_AUTH_PREF_ID = new SparseArray<>(values().length); + final Map<String, OCPreferenceKeys> byIsactiveKey = new HashMap<>(); + for (final OCPreferenceKeys key : values()) { FIND_BY_ISACTIVE_ID.put(key.isActivePrefId, key); FIND_BY_AUTH_PREF_ID.put(key.authPrefId, key); byIsactiveKey.put(CgeoApplication.getInstance().getString(key.isActivePrefId), key); diff --git a/main/src/cgeo/geocaching/settings/RegisterSend2CgeoPreference.java b/main/src/cgeo/geocaching/settings/RegisterSend2CgeoPreference.java index cc2de9f..84c343a 100644 --- a/main/src/cgeo/geocaching/settings/RegisterSend2CgeoPreference.java +++ b/main/src/cgeo/geocaching/settings/RegisterSend2CgeoPreference.java @@ -6,14 +6,16 @@ import cgeo.geocaching.network.Network; import cgeo.geocaching.network.Parameters; import cgeo.geocaching.ui.dialog.Dialogs; import cgeo.geocaching.utils.Log; +import cgeo.geocaching.utils.RxUtils; import ch.boye.httpclientandroidlib.HttpResponse; + 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.app.ProgressDialog; import android.content.Context; @@ -75,7 +77,7 @@ public class RegisterSend2CgeoPreference extends AbstractClickablePreference { return Observable.empty(); } - }).firstOrDefault(0)).subscribe(new Action1<Integer>() { + }).firstOrDefault(0)).subscribeOn(RxUtils.networkScheduler).subscribe(new Action1<Integer>() { @Override public void call(final Integer pin) { progressDialog.dismiss(); @@ -87,7 +89,7 @@ public class RegisterSend2CgeoPreference extends AbstractClickablePreference { Dialogs.message(activity, R.string.init_sendToCgeo, R.string.init_sendToCgeo_register_fail); } } - }, Schedulers.io()); + }); return true; } diff --git a/main/src/cgeo/geocaching/settings/Settings.java b/main/src/cgeo/geocaching/settings/Settings.java index 7a4dfdd..01ebd6f 100644 --- a/main/src/cgeo/geocaching/settings/Settings.java +++ b/main/src/cgeo/geocaching/settings/Settings.java @@ -13,7 +13,7 @@ import cgeo.geocaching.enumerations.LogType; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.list.StoredList; import cgeo.geocaching.maps.MapProviderFactory; -import cgeo.geocaching.maps.google.GoogleMapProvider; +import cgeo.geocaching.maps.google.v1.GoogleMapProvider; import cgeo.geocaching.maps.interfaces.GeoPointImpl; import cgeo.geocaching.maps.interfaces.MapProvider; import cgeo.geocaching.maps.interfaces.MapSource; @@ -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; @@ -76,7 +78,9 @@ public class Settings { .getDefaultSharedPreferences(CgeoApplication.getInstance().getBaseContext()); static { migrateSettings(); - Log.setDebug(sharedPrefs.getBoolean(getKey(R.string.pref_debug), false)); + final boolean isDebug = sharedPrefs.getBoolean(getKey(R.string.pref_debug), false); + Log.setDebug(isDebug); + CgeoApplication.dumpOnOutOfMemory(isDebug); } /** @@ -91,7 +95,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 +177,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 +262,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(); @@ -291,10 +295,10 @@ public class Settings { final String password = getString(connector.getPasswordPreferenceKey(), null); if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) { - return new ImmutablePair<String, String>(StringUtils.EMPTY, StringUtils.EMPTY); + return new ImmutablePair<>(StringUtils.EMPTY, StringUtils.EMPTY); } - return new ImmutablePair<String, String>(username, password); + return new ImmutablePair<>(username, password); } public static String getUsername() { @@ -330,7 +334,7 @@ public class Settings { } public static ImmutablePair<String, String> getTokenPair(final int tokenPublicPrefKey, final int tokenSecretPrefKey) { - return new ImmutablePair<String, String>(getString(tokenPublicPrefKey, null), getString(tokenSecretPrefKey, null)); + return new ImmutablePair<>(getString(tokenPublicPrefKey, null), getString(tokenSecretPrefKey, null)); } public static void setTokens(final int tokenPublicPrefKey, @Nullable final String tokenPublic, final int tokenSecretPrefKey, @Nullable final String tokenSecret) { @@ -346,11 +350,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, "")); } @@ -370,11 +374,11 @@ public class Settings { return null; } - return new ImmutablePair<String, String>(username, password); + return new ImmutablePair<>(username, password); } 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 +428,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 +448,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; } @@ -624,6 +628,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 +681,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; @@ -709,6 +714,10 @@ public class Settings { return getString(R.string.pref_webDeviceCode, null); } + public static boolean isRegisteredForSend2cgeo() { + return getWebDeviceCode() != null; + } + public static String getWebDeviceName() { return getString(R.string.pref_webDeviceName, android.os.Build.MODEL); } @@ -760,7 +769,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,9 +786,9 @@ 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); - return new ImmutablePair<String, String>(tokenPublic, tokenSecret); + 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<>(tokenPublic, tokenSecret); } public static int getVersion() { @@ -889,8 +898,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<>(); FileUtils.listDir(result, directory, new ExtensionsBasedFileSelector(new String[] { "xml" }), null); return result.toArray(new File[result.size()]); @@ -898,13 +907,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 +979,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 +988,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 +1001,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 +1013,25 @@ public class Settings { return getString(R.string.pref_ec_icons, "1"); } + /* Store last checksum of changelog for changelog display */ + public static long getLastChangelogChecksum() { + return getLong(R.string.pref_changelog_last_checksum, 0); + } + + public static void setLastChangelogChecksum(final long checksum) { + putLong(R.string.pref_changelog_last_checksum, checksum); + } + + 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<>(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..bf73370 100644 --- a/main/src/cgeo/geocaching/settings/SettingsActivity.java +++ b/main/src/cgeo/geocaching/settings/SettingsActivity.java @@ -9,11 +9,11 @@ import cgeo.geocaching.activity.ActivityMixin; import cgeo.geocaching.apps.cache.navi.NavigationAppFactory; import cgeo.geocaching.apps.cache.navi.NavigationAppFactory.NavigationAppsEnum; import cgeo.geocaching.connector.gc.GCConnector; -import cgeo.geocaching.connector.gc.GCLogin; 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; @@ -90,21 +90,21 @@ public class SettingsActivity extends PreferenceActivity { SettingsActivity.addPreferencesFromResource(this, R.xml.preferences); initPreferences(); - Intent intent = getIntent(); + final Intent intent = getIntent(); openInitialScreen(intent.getIntExtra(INTENT_OPEN_SCREEN, 0)); } - private void openInitialScreen(int initialScreen) { + private void openInitialScreen(final int initialScreen) { if (initialScreen == 0) { return; } - PreferenceScreen screen = (PreferenceScreen) getPreference(initialScreen); + final PreferenceScreen screen = (PreferenceScreen) getPreference(initialScreen); if (screen == null) { return; } try { setPreferenceScreen(screen); - } catch (RuntimeException e) { + } catch (final RuntimeException e) { Log.e("could not open preferences " + initialScreen, e); } } @@ -129,7 +129,7 @@ public class SettingsActivity extends PreferenceActivity { initNavigationMenuPreferences(); initMaintenanceButtons(); - for (int k : new int[] { R.string.pref_username, R.string.pref_password, + for (final int k : new int[] { R.string.pref_username, R.string.pref_password, R.string.pref_pass_vote, R.string.pref_signature, R.string.pref_mapsource, R.string.pref_renderthemepath, R.string.pref_gpxExportDir, R.string.pref_gpxImportDir, @@ -143,7 +143,7 @@ public class SettingsActivity extends PreferenceActivity { } private void initNavigationMenuPreferences() { - for (NavigationAppsEnum appEnum : NavigationAppsEnum.values()) { + for (final NavigationAppsEnum appEnum : NavigationAppsEnum.values()) { if (appEnum.app.isInstalled()) { getPreference(appEnum.preferenceKey).setEnabled(true); } @@ -154,26 +154,33 @@ public class SettingsActivity extends PreferenceActivity { } private void initServicePreferences() { - for (OCPreferenceKeys key : OCPreferenceKeys.values()) { + for (final OCPreferenceKeys key : OCPreferenceKeys.values()) { getPreference(key.isActivePrefId).setOnPreferenceChangeListener(VALUE_CHANGE_LISTENER); setWebsite(key.websitePrefId, key.authParams.host); - setServiceScreenSummary(getPreferenceManager(), key.isActivePrefId); + getPreference(key.prefScreenId).setSummary(getServiceSummary(Settings.isOCConnectorActive(key.isActivePrefId))); } getPreference(R.string.pref_connectorGCActive).setOnPreferenceChangeListener(VALUE_CHANGE_LISTENER); - getPreference(R.string.pref_connectorOXActive).setOnPreferenceChangeListener(VALUE_CHANGE_LISTENER); - getPreference(R.string.pref_connectorECActive).setOnPreferenceChangeListener(VALUE_CHANGE_LISTENER); setWebsite(R.string.pref_fakekey_gc_website, GCConnector.getInstance().getHost()); + getPreference(R.string.preference_screen_gc).setSummary(getServiceSummary(Settings.isGCConnectorActive())); + + getPreference(R.string.pref_connectorOXActive).setOnPreferenceChangeListener(VALUE_CHANGE_LISTENER); setWebsite(R.string.pref_fakekey_ox_website, "opencaching.com"); + getPreference(R.string.preference_screen_ox).setSummary(getServiceSummary(Settings.isOXConnectorActive())); + + getPreference(R.string.pref_connectorECActive).setOnPreferenceChangeListener(VALUE_CHANGE_LISTENER); setWebsite(R.string.pref_fakekey_ec_website, "extremcaching.com"); + getPreference(R.string.preference_screen_ec).setSummary(getServiceSummary(Settings.isECConnectorActive())); + + getPreference(R.string.pref_ratingwanted).setOnPreferenceChangeListener(VALUE_CHANGE_LISTENER); setWebsite(R.string.pref_fakekey_gcvote_website, "gcvote.com"); + getPreference(R.string.preference_screen_gcvote).setSummary(getServiceSummary(Settings.isRatingWanted())); + setWebsite(R.string.pref_fakekey_sendtocgeo_website, "send2.cgeo.org"); - setServiceScreenSummary(getPreferenceManager(), R.string.pref_connectorGCActive); - setServiceScreenSummary(getPreferenceManager(), R.string.pref_connectorOXActive); - setServiceScreenSummary(getPreferenceManager(), R.string.pref_connectorECActive); + getPreference(R.string.preference_screen_sendtocgeo).setSummary(getServiceSummary(Settings.isRegisteredForSend2cgeo())); } private void setWebsite(final int preferenceKey, final String host) { - Preference preference = getPreference(preferenceKey); + final Preference preference = getPreference(preferenceKey); preference.setOnPreferenceClickListener(new OnPreferenceClickListener() { @Override public boolean onPreferenceClick(final Preference preference) { @@ -183,36 +190,10 @@ public class SettingsActivity extends PreferenceActivity { }); } - private static String getServiceSummary(boolean status) { + private static String getServiceSummary(final boolean status) { return status ? CgeoApplication.getInstance().getString(R.string.settings_service_active) : StringUtils.EMPTY; } - private static void setServiceScreenSummary(PreferenceManager preferenceManager, final int preferenceKey) { - - String summary = StringUtils.EMPTY; - - switch (preferenceKey) { - case R.string.pref_connectorGCActive: - summary = getServiceSummary(Settings.isGCConnectorActive()); - preferenceManager.findPreference(getKey(R.string.preference_screen_gc)).setSummary(summary); - break; - case R.string.pref_connectorOXActive: - summary = getServiceSummary(Settings.isOXConnectorActive()); - preferenceManager.findPreference(getKey(R.string.preference_screen_ox)).setSummary(summary); - break; - case R.string.pref_connectorECActive: - summary = getServiceSummary(Settings.isECConnectorActive()); - preferenceManager.findPreference(getKey(R.string.preference_screen_ec)).setSummary(summary); - break; - default: - if (OCPreferenceKeys.isOCPreference(preferenceKey)) { - OCPreferenceKeys prefKey = OCPreferenceKeys.getById(preferenceKey); - summary = getServiceSummary(Settings.isOCConnectorActive(prefKey.isActivePrefId)); - preferenceManager.findPreference(getKey(prefKey.prefScreenId)).setSummary(summary); - } - } - } - private static String getKey(final int prefKeyId) { return CgeoApplication.getInstance().getString(prefKeyId); } @@ -225,11 +206,11 @@ public class SettingsActivity extends PreferenceActivity { * Fill the choice list for map sources. */ private void initMapSourcePreference() { - ListPreference pref = (ListPreference) getPreference(R.string.pref_mapsource); + final ListPreference pref = (ListPreference) getPreference(R.string.pref_mapsource); - List<MapSource> mapSources = MapProviderFactory.getMapSources(); - CharSequence[] entries = new CharSequence[mapSources.size()]; - CharSequence[] values = new CharSequence[mapSources.size()]; + final List<MapSource> mapSources = MapProviderFactory.getMapSources(); + final CharSequence[] entries = new CharSequence[mapSources.size()]; + final CharSequence[] values = new CharSequence[mapSources.size()]; for (int i = 0; i < mapSources.size(); ++i) { entries[i] = mapSources.get(i).getName(); values[i] = String.valueOf(mapSources.get(i).getNumericalId()); @@ -245,8 +226,8 @@ public class SettingsActivity extends PreferenceActivity { final List<NavigationAppsEnum> apps = NavigationAppFactory.getInstalledDefaultNavigationApps(); - CharSequence[] entries = new CharSequence[apps.size()]; - CharSequence[] values = new CharSequence[apps.size()]; + final CharSequence[] entries = new CharSequence[apps.size()]; + final CharSequence[] values = new CharSequence[apps.size()]; for (int i = 0; i < apps.size(); ++i) { entries[i] = apps.get(i).toString(); values[i] = String.valueOf(apps.get(i).id); @@ -277,7 +258,7 @@ public class SettingsActivity extends PreferenceActivity { new OnPreferenceClickListener() { @Override public boolean onPreferenceClick(final Preference preference) { - Intent i = new Intent(SettingsActivity.this, + final Intent i = new Intent(SettingsActivity.this, SelectMapfileActivity.class); startActivityForResult(i, R.string.pref_mapDirectory); return false; @@ -307,7 +288,7 @@ public class SettingsActivity extends PreferenceActivity { dirChooser.putExtra(FileManagerIntents.EXTRA_BUTTON_TEXT, getString(android.R.string.ok)); startActivityForResult(dirChooser, dct.requestCode); - } catch (android.content.ActivityNotFoundException ex) { + } catch (final android.content.ActivityNotFoundException ex) { // OI file manager not available final Intent dirChooser = new Intent(this, SimpleDirChooser.class); dirChooser.putExtra(Intents.EXTRA_START_DIR, startDirectory); @@ -319,7 +300,7 @@ public class SettingsActivity extends PreferenceActivity { private void setChosenDirectory(final DirChooserType dct, final Intent data) { final String directory = new File(data.getData().getPath()).getAbsolutePath(); if (StringUtils.isNotBlank(directory)) { - Preference p = getPreference(dct.keyId); + final Preference p = getPreference(dct.keyId); if (p == null) { return; } @@ -329,7 +310,7 @@ public class SettingsActivity extends PreferenceActivity { } public void initBackupButtons() { - Preference backup = getPreference(R.string.pref_fakekey_preference_backup); + final Preference backup = getPreference(R.string.pref_fakekey_preference_backup); backup.setOnPreferenceClickListener(new OnPreferenceClickListener() { @Override public boolean onPreferenceClick(final Preference preference) { @@ -343,7 +324,7 @@ public class SettingsActivity extends PreferenceActivity { } }); - Preference restore = getPreference(R.string.pref_fakekey_preference_restore); + final Preference restore = getPreference(R.string.pref_fakekey_preference_restore); restore.setOnPreferenceClickListener(new OnPreferenceClickListener() { @Override public boolean onPreferenceClick(final Preference preference) { @@ -354,14 +335,14 @@ public class SettingsActivity extends PreferenceActivity { } public void initMaintenanceButtons() { - Preference dirMaintenance = getPreference(R.string.pref_fakekey_preference_maintenance_directories); + final Preference dirMaintenance = getPreference(R.string.pref_fakekey_preference_maintenance_directories); dirMaintenance.setOnPreferenceClickListener(new OnPreferenceClickListener() { @Override public boolean onPreferenceClick(final Preference preference) { // disable the button, as the cleanup runs in background and should not be invoked a second time preference.setEnabled(false); - Resources res = getResources(); + final Resources res = getResources(); final SettingsActivity activity = SettingsActivity.this; final ProgressDialog dialog = ProgressDialog.show(activity, res.getString(R.string.init_maintenance), res.getString(R.string.init_maintenance_directories), true, false); new Thread() { @@ -380,10 +361,19 @@ public class SettingsActivity extends PreferenceActivity { return true; } }); + final Preference memoryDumpPref = getPreference(R.string.pref_memory_dump); + memoryDumpPref + .setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override public boolean onPreferenceClick( + final Preference preference) { + DebugUtils.createMemoryDump(SettingsActivity.this); + return true; + } + }); } private void initDbLocationPreference() { - Preference p = getPreference(R.string.pref_dbonsdcard); + final Preference p = getPreference(R.string.pref_dbonsdcard); p.setPersistent(false); p.setOnPreferenceClickListener(new OnPreferenceClickListener() { @Override @@ -396,11 +386,13 @@ public class SettingsActivity extends PreferenceActivity { } private void initDebugPreference() { - Preference p = getPreference(R.string.pref_debug); + final Preference p = getPreference(R.string.pref_debug); p.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(final Preference preference, final Object newValue) { - Log.setDebug((Boolean) newValue); + final boolean isDebug = (Boolean) newValue; + Log.setDebug(isDebug); + CgeoApplication.dumpOnOutOfMemory(isDebug); return true; } }); @@ -434,7 +426,7 @@ public class SettingsActivity extends PreferenceActivity { return; } final PreferenceScreen screen = (PreferenceScreen) preference; - ListAdapter adapter = screen.getRootAdapter(); + final ListAdapter adapter = screen.getRootAdapter(); if (adapter instanceof BaseAdapter) { ((BaseAdapter) adapter).notifyDataSetChanged(); } @@ -444,13 +436,14 @@ public class SettingsActivity extends PreferenceActivity { Settings.putString(R.string.pref_webDeviceName, Settings.getWebDeviceName()); } - public void setAuthTitle(int prefKeyId) { + public void setAuthTitle(final int prefKeyId) { switch (prefKeyId) { case R.string.pref_fakekey_ocde_authorization: case R.string.pref_fakekey_ocpl_authorization: case R.string.pref_fakekey_ocnl_authorization: case R.string.pref_fakekey_ocus_authorization: case R.string.pref_fakekey_ocro_authorization: + case R.string.pref_fakekey_ocuk_authorization: setOCAuthTitle(OCPreferenceKeys.getByAuthId(prefKeyId)); break; case R.string.pref_fakekey_twitter_authorization: @@ -490,7 +483,7 @@ public class SettingsActivity extends PreferenceActivity { return; } - for (DirChooserType dct : DirChooserType.values()) { + for (final DirChooserType dct : DirChooserType.values()) { if (requestCode == dct.requestCode) { setChosenDirectory(dct, data); return; @@ -501,7 +494,7 @@ public class SettingsActivity extends PreferenceActivity { case R.string.pref_mapDirectory: if (data.hasExtra(Intents.EXTRA_MAP_FILE)) { final String mapFile = data.getStringExtra(Intents.EXTRA_MAP_FILE); - File file = new File(mapFile); + final File file = new File(mapFile); if (!file.isDirectory()) { Settings.setMapFile(mapFile); if (!Settings.isValidMapFile(Settings.getMapFile())) { @@ -509,8 +502,8 @@ public class SettingsActivity extends PreferenceActivity { } else { // Ensure map source preference is updated accordingly. // TODO: There should be a better way to find and select the map source for a map file - Integer mapSourceId = mapFile.hashCode(); - ListPreference mapSource = (ListPreference) getPreference(R.string.pref_mapsource); + final Integer mapSourceId = mapFile.hashCode(); + final ListPreference mapSource = (ListPreference) getPreference(R.string.pref_mapsource); mapSource.setValue(mapSourceId.toString()); VALUE_CHANGE_LISTENER.onPreferenceChange(mapSource, mapSourceId); } @@ -526,7 +519,8 @@ public class SettingsActivity extends PreferenceActivity { case R.string.pref_fakekey_ocnl_authorization: case R.string.pref_fakekey_ocus_authorization: case R.string.pref_fakekey_ocro_authorization: - OCPreferenceKeys key = OCPreferenceKeys.getByAuthId(requestCode); + case R.string.pref_fakekey_ocuk_authorization: + final OCPreferenceKeys key = OCPreferenceKeys.getByAuthId(requestCode); if (key != null) { setOCAuthTitle(key); redrawScreen(key.prefScreenId); @@ -546,9 +540,13 @@ public class SettingsActivity extends PreferenceActivity { * to reflect its new value. */ private static final Preference.OnPreferenceChangeListener VALUE_CHANGE_LISTENER = new Preference.OnPreferenceChangeListener() { + + private PreferenceManager preferenceManager; + @Override public boolean onPreferenceChange(final Preference preference, final Object value) { - String stringValue = value.toString(); + preferenceManager = preference.getPreferenceManager(); + final String stringValue = value.toString(); if (isPreference(preference, R.string.pref_mapsource)) { // reset the cached map source @@ -578,14 +576,15 @@ public class SettingsActivity extends PreferenceActivity { || isPreference(preference, R.string.pref_connectorOCNLActive) || isPreference(preference, R.string.pref_connectorOCUSActive) || isPreference(preference, R.string.pref_connectorOCROActive) + || isPreference(preference, R.string.pref_connectorOCUKActive) || isPreference(preference, R.string.pref_connectorGCActive) || isPreference(preference, R.string.pref_connectorOXActive) || isPreference(preference, R.string.pref_connectorECActive)) { // update summary - boolean boolVal = ((Boolean) value).booleanValue(); - String summary = getServiceSummary(boolVal); + final boolean boolVal = (Boolean) value; + final String summary = getServiceSummary(boolVal); if (OCPreferenceKeys.isOCPreference(preference.getKey())) { - OCPreferenceKeys prefKey = OCPreferenceKeys.getByKey(preference.getKey()); + final OCPreferenceKeys prefKey = OCPreferenceKeys.getByKey(preference.getKey()); preference.getPreferenceManager().findPreference(getKey(prefKey.prefScreenId)).setSummary(summary); } else if (isPreference(preference, R.string.pref_connectorGCActive)) { preference.getPreferenceManager().findPreference(getKey(R.string.preference_screen_gc)).setSummary(summary); @@ -600,8 +599,8 @@ public class SettingsActivity extends PreferenceActivity { } else if (preference instanceof ListPreference) { // For list preferences, look up the correct display value in // the preference's 'entries' list. - ListPreference listPreference = (ListPreference) preference; - int index = listPreference.findIndexOfValue(stringValue); + final ListPreference listPreference = (ListPreference) preference; + final int index = listPreference.findIndexOfValue(stringValue); // Set the summary to reflect the new value. preference.setSummary( @@ -617,6 +616,9 @@ public class SettingsActivity extends PreferenceActivity { text = preference.getContext().getString(R.string.init_backup_last_no); } preference.setSummary(text); + } else if (isPreference(preference, R.string.pref_ratingwanted)) { + findPreference(R.string.preference_screen_gcvote).setSummary(getServiceSummary((Boolean) value)); + redrawScreen(findPreference(R.string.preference_screen_services)); } else { // For all other preferences, set the summary to the value's // simple string representation. @@ -625,13 +627,15 @@ public class SettingsActivity extends PreferenceActivity { // TODO: do not special case geocaching.com here if ((isPreference(preference, R.string.pref_username) && !stringValue.equals(Settings.getUsername())) || (isPreference(preference, R.string.pref_password) && !stringValue.equals(Settings.getGcCredentials().getRight()))) { // reset log-in if gc user or password is changed - if (GCLogin.getInstance().isActualLoginStatus()) { - GCLogin.getInstance().logout(); - } CgeoApplication.getInstance().forceRelog(); } return true; } + + private Preference findPreference(final int preferenceKeyResourceId) { + return preferenceManager.findPreference(getKey(preferenceKeyResourceId)); + } + }; /** @@ -662,12 +666,12 @@ public class SettingsActivity extends PreferenceActivity { */ private void bindSummaryToStringValue(final int key) { - Preference pref = getPreference(key); + final Preference pref = getPreference(key); if (pref == null) { return; } - String value = PreferenceManager + final String value = PreferenceManager .getDefaultSharedPreferences(pref.getContext()) .getString(pref.getKey(), ""); @@ -686,7 +690,7 @@ public class SettingsActivity extends PreferenceActivity { @SuppressWarnings("deprecation") @Override - public void setPreferenceScreen(PreferenceScreen preferenceScreen) { + public void setPreferenceScreen(final PreferenceScreen preferenceScreen) { // TODO replace with fragment based code super.setPreferenceScreen(preferenceScreen); } @@ -698,7 +702,7 @@ public class SettingsActivity extends PreferenceActivity { return super.getPreferenceManager(); } - private static boolean isPreference(final Preference preference, int preferenceKeyId) { + private static boolean isPreference(final Preference preference, final int preferenceKeyId) { return getKey(preferenceKeyId).equals(preference.getKey()); } } diff --git a/main/src/cgeo/geocaching/settings/TemplateTextPreference.java b/main/src/cgeo/geocaching/settings/TemplateTextPreference.java index 667b02b..a33f09d 100644 --- a/main/src/cgeo/geocaching/settings/TemplateTextPreference.java +++ b/main/src/cgeo/geocaching/settings/TemplateTextPreference.java @@ -1,5 +1,7 @@ package cgeo.geocaching.settings; +import butterknife.ButterKnife; + import cgeo.geocaching.R; import cgeo.geocaching.activity.ActivityMixin; import cgeo.geocaching.ui.dialog.Dialogs; @@ -30,12 +32,12 @@ public class TemplateTextPreference extends DialogPreference { private EditText editText; private String initialValue; - public TemplateTextPreference(Context context, AttributeSet attrs) { + public TemplateTextPreference(final Context context, final AttributeSet attrs) { super(context, attrs); init(); } - public TemplateTextPreference(Context context, AttributeSet attrs, int defStyle) { + public TemplateTextPreference(final Context context, final AttributeSet attrs, final int defStyle) { super(context, attrs, defStyle); init(); } @@ -45,28 +47,28 @@ public class TemplateTextPreference extends DialogPreference { } @Override - protected void onBindDialogView(View view) { + protected void onBindDialogView(final View view) { settingsActivity = (SettingsActivity) this.getContext(); - editText = (EditText) view.findViewById(R.id.signature_dialog_text); + editText = ButterKnife.findById(view, R.id.signature_dialog_text); editText.setText(getPersistedString(initialValue != null ? initialValue : StringUtils.EMPTY)); Dialogs.moveCursorToEnd(editText); - Button button = (Button) view.findViewById(R.id.signature_templates); + final Button button = ButterKnife.findById(view, R.id.signature_templates); button.setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View button) { - AlertDialog.Builder alert = new AlertDialog.Builder(TemplateTextPreference.this.getContext()); + public void onClick(final View button) { + final AlertDialog.Builder alert = new AlertDialog.Builder(TemplateTextPreference.this.getContext()); alert.setTitle(R.string.init_signature_template_button); - final ArrayList<LogTemplate> templates = LogTemplateProvider.getTemplates(); - String[] items = new String[templates.size()]; + final ArrayList<LogTemplate> templates = LogTemplateProvider.getTemplatesWithoutSignature(); + final String[] items = new String[templates.size()]; for (int i = 0; i < templates.size(); i++) { items[i] = settingsActivity.getResources().getString(templates.get(i).getResourceId()); } alert.setItems(items, new DialogInterface.OnClickListener() { @Override - public void onClick(DialogInterface dialog, int position) { + public void onClick(final DialogInterface dialog, final int position) { dialog.dismiss(); final LogTemplate template = templates.get(position); insertSignatureTemplate(template); @@ -80,14 +82,14 @@ public class TemplateTextPreference extends DialogPreference { } private void insertSignatureTemplate(final LogTemplate template) { - String insertText = "[" + template.getTemplateString() + "]"; + final String insertText = "[" + template.getTemplateString() + "]"; ActivityMixin.insertAtPosition(editText, insertText, true); } @Override - protected void onDialogClosed(boolean positiveResult) { + protected void onDialogClosed(final boolean positiveResult) { if (positiveResult) { - String text = editText.getText().toString(); + final String text = editText.getText().toString(); persistString(text); callChangeListener(text); } @@ -95,7 +97,7 @@ public class TemplateTextPreference extends DialogPreference { } @Override - protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) { + protected void onSetInitialValue(final boolean restorePersistedValue, final Object defaultValue) { if (restorePersistedValue) { // Restore existing state initialValue = this.getPersistedString(DEFAULT_VALUE); @@ -107,7 +109,7 @@ public class TemplateTextPreference extends DialogPreference { } @Override - protected Object onGetDefaultValue(TypedArray array, int index) { + protected Object onGetDefaultValue(final TypedArray array, final int index) { return array.getString(index); } } diff --git a/main/src/cgeo/geocaching/settings/TextPreference.java b/main/src/cgeo/geocaching/settings/TextPreference.java index eecf4cc..b3de59a 100644 --- a/main/src/cgeo/geocaching/settings/TextPreference.java +++ b/main/src/cgeo/geocaching/settings/TextPreference.java @@ -1,5 +1,7 @@ package cgeo.geocaching.settings; +import butterknife.ButterKnife; + import cgeo.geocaching.R; import android.content.Context; @@ -29,15 +31,15 @@ public class TextPreference extends AbstractAttributeBasedPrefence { private TextView summaryView; private CharSequence summaryText; - public TextPreference(Context context) { + public TextPreference(final Context context) { super(context); } - public TextPreference(Context context, AttributeSet attrs) { + public TextPreference(final Context context, final AttributeSet attrs) { super(context, attrs); } - public TextPreference(Context context, AttributeSet attrs, int defStyle) { + public TextPreference(final Context context, final AttributeSet attrs, final int defStyle) { super(context, attrs, defStyle); } @@ -47,27 +49,27 @@ public class TextPreference extends AbstractAttributeBasedPrefence { } @Override - protected void processAttributeValues(TypedArray values) { + protected void processAttributeValues(final TypedArray values) { this.text = values.getString(0); } @Override - protected View onCreateView(ViewGroup parent) { + protected View onCreateView(final ViewGroup parent) { this.setSelectable(false); - View v = super.onCreateView(parent); + final View v = super.onCreateView(parent); - TextView text = (TextView) v.findViewById(R.id.textPreferenceText); + final TextView text = ButterKnife.findById(v, R.id.textPreferenceText); text.setText(this.text); - summaryView = (TextView) v.findViewById(R.id.textPreferenceSummary); + summaryView = ButterKnife.findById(v, R.id.textPreferenceSummary); setSummary(null); // show saved summary text return v; } @Override - public void setSummary(CharSequence summaryText) { + public void setSummary(final CharSequence summaryText) { // the layout hasn't been inflated yet, save the summaryText for later use if (summaryView == null) { this.summaryText = summaryText; diff --git a/main/src/cgeo/geocaching/settings/WpThresholdPreference.java b/main/src/cgeo/geocaching/settings/WpThresholdPreference.java index 4c43acf..37edafa 100644 --- a/main/src/cgeo/geocaching/settings/WpThresholdPreference.java +++ b/main/src/cgeo/geocaching/settings/WpThresholdPreference.java @@ -1,5 +1,7 @@ package cgeo.geocaching.settings; +import butterknife.ButterKnife; + import cgeo.geocaching.R; import android.content.Context; @@ -15,17 +17,17 @@ public class WpThresholdPreference extends Preference { private TextView valueView; - public WpThresholdPreference(Context context) { + public WpThresholdPreference(final Context context) { super(context); init(); } - public WpThresholdPreference(Context context, AttributeSet attrs) { + public WpThresholdPreference(final Context context, final AttributeSet attrs) { super(context, attrs); init(); } - public WpThresholdPreference(Context context, AttributeSet attrs, int defStyle) { + public WpThresholdPreference(final Context context, final AttributeSet attrs, final int defStyle) { super(context, attrs, defStyle); init(); } @@ -35,33 +37,33 @@ public class WpThresholdPreference extends Preference { } @Override - protected View onCreateView(ViewGroup parent) { - View v = super.onCreateView(parent); + protected View onCreateView(final ViewGroup parent) { + final View v = super.onCreateView(parent); // get views - SeekBar seekBar = (SeekBar) v.findViewById(R.id.wp_threshold_seekbar); - valueView = (TextView) v.findViewById(R.id.wp_threshold_value_view); + final SeekBar seekBar = ButterKnife.findById(v, R.id.wp_threshold_seekbar); + valueView = ButterKnife.findById(v, R.id.wp_threshold_value_view); // init seekbar seekBar.setMax(Settings.SHOW_WP_THRESHOLD_MAX); // set initial value - int threshold = Settings.getWayPointsThreshold(); + final int threshold = Settings.getWayPointsThreshold(); valueView.setText(String.valueOf(threshold)); seekBar.setProgress(threshold); seekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + public void onProgressChanged(final SeekBar seekBar, final int progress, final boolean fromUser) { if (fromUser) { valueView.setText(String.valueOf(progress)); } } @Override - public void onStartTrackingTouch(SeekBar seekBar) { + public void onStartTrackingTouch(final SeekBar seekBar) { } @Override - public void onStopTrackingTouch(SeekBar seekBar) { + public void onStopTrackingTouch(final SeekBar seekBar) { Settings.setShowWaypointsThreshold(seekBar.getProgress()); } }); diff --git a/main/src/cgeo/geocaching/sorting/ComparatorUserInterface.java b/main/src/cgeo/geocaching/sorting/ComparatorUserInterface.java deleted file mode 100644 index 7f10353..0000000 --- a/main/src/cgeo/geocaching/sorting/ComparatorUserInterface.java +++ /dev/null @@ -1,122 +0,0 @@ -package cgeo.geocaching.sorting; - -import cgeo.geocaching.R; -import cgeo.geocaching.utils.Log; - -import rx.functions.Action1; - -import android.app.Activity; -import android.app.AlertDialog; -import android.content.DialogInterface; -import android.content.res.Resources; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; - -public class ComparatorUserInterface { - private final Activity activity; - private final ArrayList<ComparatorEntry> registry; - private final Resources res; - - private static final class ComparatorEntry { - private final String name; - private final Class<? extends CacheComparator> cacheComparator; - - public ComparatorEntry(final String name, final Class<? extends CacheComparator> cacheComparator) { - this.name = name; - this.cacheComparator = cacheComparator; - } - - @Override - public String toString() { - return name; - } - } - - public ComparatorUserInterface(final Activity activity) { - this.activity = activity; - res = activity.getResources(); - - registry = new ArrayList<ComparatorUserInterface.ComparatorEntry>(20); - - register(R.string.caches_sort_distance, null); - register(R.string.caches_sort_date_hidden, DateComparator.class); - register(R.string.caches_sort_difficulty, DifficultyComparator.class); - register(R.string.caches_sort_finds, FindsComparator.class); - register(R.string.caches_sort_geocode, GeocodeComparator.class); - register(R.string.caches_sort_inventory, InventoryComparator.class); - register(R.string.caches_sort_name, NameComparator.class); - register(R.string.caches_sort_favorites, PopularityComparator.class); - register(R.string.caches_sort_favorites_ratio, PopularityRatioComparator.class); - register(R.string.caches_sort_rating, RatingComparator.class); - register(R.string.caches_sort_size, SizeComparator.class); - register(R.string.caches_sort_state, StateComparator.class); - register(R.string.caches_sort_storage, StorageTimeComparator.class); - register(R.string.caches_sort_terrain, TerrainComparator.class); - register(R.string.caches_sort_date_logged, VisitComparator.class); - register(R.string.caches_sort_vote, VoteComparator.class); - - // sort the menu labels alphabetically for easier reading - Collections.sort(registry, new Comparator<ComparatorEntry>() { - - @Override - public int compare(ComparatorEntry lhs, ComparatorEntry rhs) { - return lhs.name.compareToIgnoreCase(rhs.name); - } - }); - } - - private void register(final int resourceId, Class<? extends CacheComparator> comparatorClass) { - registry.add(new ComparatorEntry(res.getString(resourceId), comparatorClass)); - } - - public void selectComparator(final CacheComparator current, final Action1<CacheComparator> runAfterwards) { - final AlertDialog.Builder builder = new AlertDialog.Builder(activity); - builder.setTitle(R.string.caches_sort_title); - - // adapter doesn't work correctly here, therefore using the string array based method - final String[] items = new String[registry.size()]; - for (int i = 0; i < items.length; i++) { - items[i] = registry.get(i).name; - } - builder.setSingleChoiceItems(items, getCurrentIndex(current), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int itemIndex) { - ComparatorEntry entry = registry.get(itemIndex); - try { - if (entry.cacheComparator == null) { - runAfterwards.call(null); - } - else { - CacheComparator comparator = entry.cacheComparator.newInstance(); - runAfterwards.call(comparator); - } - } catch (InstantiationException e) { - Log.e("selectComparator", e); - } catch (IllegalAccessException e) { - Log.e("selectComparator", e); - } - dialog.dismiss(); - } - }); - - builder.create().show(); - } - - private int getCurrentIndex(final CacheComparator current) { - for (int index = 0; index < registry.size(); index++) { - final ComparatorEntry entry = registry.get(index); - if (current == null) { - if (entry.cacheComparator == null) { - return index; - } - } - else if (current.getClass().equals(entry.cacheComparator)) { - return index; - } - } - return -1; - } - -} diff --git a/main/src/cgeo/geocaching/sorting/DateComparator.java b/main/src/cgeo/geocaching/sorting/DateComparator.java index 9df70f9..7913941 100644 --- a/main/src/cgeo/geocaching/sorting/DateComparator.java +++ b/main/src/cgeo/geocaching/sorting/DateComparator.java @@ -19,7 +19,7 @@ public class DateComparator extends AbstractCacheComparator { final int dateDifference = date1.compareTo(date2); // for equal dates, sort by distance if (dateDifference == 0) { - final ArrayList<Geocache> list = new ArrayList<Geocache>(); + final ArrayList<Geocache> list = new ArrayList<>(); list.add(cache1); list.add(cache2); final DistanceComparator distanceComparator = new DistanceComparator(CgeoApplication.getInstance().currentGeo().getCoords(), list); 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/sorting/SortActionProvider.java b/main/src/cgeo/geocaching/sorting/SortActionProvider.java new file mode 100644 index 0000000..e9e65a0 --- /dev/null +++ b/main/src/cgeo/geocaching/sorting/SortActionProvider.java @@ -0,0 +1,153 @@ +package cgeo.geocaching.sorting; + +import cgeo.geocaching.R; +import cgeo.geocaching.utils.Log; + +import org.eclipse.jdt.annotation.NonNull; + +import rx.functions.Action1; + +import android.content.Context; +import android.support.v4.view.ActionProvider; +import android.view.MenuItem; +import android.view.MenuItem.OnMenuItemClickListener; +import android.view.SubMenu; +import android.view.View; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; + +/** + * Provides a sub menu for sorting caches. Register your listener in the onCreateOptionsMenu of the containing activity. + * + */ +public class SortActionProvider extends ActionProvider implements OnMenuItemClickListener { + + private static final int MENU_GROUP = 1; + private final Context mContext; + private final ArrayList<ComparatorEntry> registry = new ArrayList<>(20); + /** + * Callback triggered on selecting a new sort order. + */ + private Action1<CacheComparator> onClickListener; + /** + * Currently selected filter. Used for radio button indication. + */ + private CacheComparator selection; + + private static final class ComparatorEntry { + private final String name; + private final Class<? extends CacheComparator> cacheComparator; + + public ComparatorEntry(final String name, final Class<? extends CacheComparator> cacheComparator) { + this.name = name; + this.cacheComparator = cacheComparator; + } + + @Override + public String toString() { + return name; + } + } + + public SortActionProvider(final Context context) { + super(context); + mContext = context; + registerComparators(); + } + + private void register(final int resourceId, final Class<? extends CacheComparator> comparatorClass) { + registry.add(new ComparatorEntry(mContext.getString(resourceId), comparatorClass)); + } + + private void registerComparators() { + register(R.string.caches_sort_distance, null); + register(R.string.caches_sort_date_hidden, DateComparator.class); + register(R.string.caches_sort_difficulty, DifficultyComparator.class); + register(R.string.caches_sort_finds, FindsComparator.class); + register(R.string.caches_sort_geocode, GeocodeComparator.class); + register(R.string.caches_sort_inventory, InventoryComparator.class); + register(R.string.caches_sort_name, NameComparator.class); + register(R.string.caches_sort_favorites, PopularityComparator.class); + register(R.string.caches_sort_favorites_ratio, PopularityRatioComparator.class); + register(R.string.caches_sort_rating, RatingComparator.class); + register(R.string.caches_sort_size, SizeComparator.class); + register(R.string.caches_sort_state, StateComparator.class); + register(R.string.caches_sort_storage, StorageTimeComparator.class); + register(R.string.caches_sort_terrain, TerrainComparator.class); + register(R.string.caches_sort_date_logged, VisitComparator.class); + register(R.string.caches_sort_vote, VoteComparator.class); + + // sort the menu labels alphabetically for easier reading + Collections.sort(registry, new Comparator<ComparatorEntry>() { + + @Override + public int compare(final ComparatorEntry lhs, final ComparatorEntry rhs) { + return lhs.name.compareToIgnoreCase(rhs.name); + } + }); + } + + @Override + public View onCreateActionView(){ + // must return null, otherwise onPrepareSubMenu is not called + return null; + } + + @Override + public boolean hasSubMenu(){ + return true; + } + + @Override + public void onPrepareSubMenu(final SubMenu subMenu){ + subMenu.clear(); + for (int i = 0; i < registry.size(); i++) { + final ComparatorEntry comparatorEntry = registry.get(i); + final MenuItem menuItem = subMenu.add(MENU_GROUP, i, i, comparatorEntry.name); + menuItem.setOnMenuItemClickListener(this).setCheckable(true); + if (selection == null) { + if (comparatorEntry.cacheComparator == null) { + menuItem.setChecked(true); + } + } + else { + if (selection.getClass().equals(comparatorEntry.cacheComparator)) { + menuItem.setChecked(true); + } + } + } + subMenu.setGroupCheckable(MENU_GROUP, true, true); + } + + @Override + public boolean onMenuItemClick(final MenuItem item){ + callListener(registry.get(item.getItemId()).cacheComparator); + return true; + } + + private void callListener(final Class<? extends CacheComparator> cacheComparator) { + try { + if (cacheComparator == null) { + onClickListener.call(null); + } + else { + final CacheComparator comparator = cacheComparator.newInstance(); + onClickListener.call(comparator); + } + } catch (final InstantiationException e) { + Log.e("selectComparator", e); + } catch (final IllegalAccessException e) { + Log.e("selectComparator", e); + } + } + + public void setClickListener(final @NonNull Action1<CacheComparator> onClickListener) { + this.onClickListener = onClickListener; + } + + public void setSelection(final CacheComparator selection) { + this.selection = selection; + } +}
\ No newline at end of file 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..306c686 100644 --- a/main/src/cgeo/geocaching/ui/AbstractCachingPageViewCreator.java +++ b/main/src/cgeo/geocaching/ui/AbstractCachingPageViewCreator.java @@ -3,11 +3,13 @@ package cgeo.geocaching.ui; import cgeo.geocaching.activity.AbstractViewPagerActivity.PageViewCreator; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import android.os.Bundle; import android.view.View; +import android.view.ViewGroup; /** * View creator which destroys the created view on every {@link #notifyDataSetChanged()}. @@ -24,9 +26,9 @@ public abstract class AbstractCachingPageViewCreator<ViewClass extends View> imp } @Override - public final View getView() { + public final View getView(final ViewGroup parentView) { if (view == null) { - view = getDispatchedView(); + view = getDispatchedView(parentView); } return view; @@ -34,7 +36,7 @@ public abstract class AbstractCachingPageViewCreator<ViewClass extends View> imp @Override @SuppressFBWarnings("USM_USELESS_ABSTRACT_METHOD") - public abstract ViewClass getDispatchedView(); + public abstract ViewClass getDispatchedView(final ViewGroup parentView); /** * Gets the state of the view but returns an empty state if not overridden @@ -51,7 +53,6 @@ public abstract class AbstractCachingPageViewCreator<ViewClass extends View> imp * Restores the state of the view but just returns if not overridden. */ @Override - public void setViewState(@NonNull Bundle state) { - return; + public void setViewState(@NonNull final Bundle state) { } } diff --git a/main/src/cgeo/geocaching/ui/AbstractUserClickListener.java b/main/src/cgeo/geocaching/ui/AbstractUserClickListener.java index 2a78f07..f26cb53 100644 --- a/main/src/cgeo/geocaching/ui/AbstractUserClickListener.java +++ b/main/src/cgeo/geocaching/ui/AbstractUserClickListener.java @@ -45,7 +45,7 @@ abstract class AbstractUserClickListener implements View.OnClickListener { final AbstractActivity activity = (AbstractActivity) view.getContext(); final Resources res = activity.getResources(); - ArrayList<String> labels = new ArrayList<String>(userActions.size()); + ArrayList<String> labels = new ArrayList<>(userActions.size()); for (UserAction action : userActions) { labels.add(res.getString(action.displayResourceId)); } diff --git a/main/src/cgeo/geocaching/ui/AddressListAdapter.java b/main/src/cgeo/geocaching/ui/AddressListAdapter.java index 8134235..81b6c23 100644 --- a/main/src/cgeo/geocaching/ui/AddressListAdapter.java +++ b/main/src/cgeo/geocaching/ui/AddressListAdapter.java @@ -29,7 +29,7 @@ public class AddressListAdapter extends ArrayAdapter<Address> { @InjectView(R.id.label) protected TextView label; @InjectView(R.id.distance) protected TextView distance; - public ViewHolder(View view) { + public ViewHolder(final View view) { super(view); } } @@ -49,7 +49,7 @@ public class AddressListAdapter extends ArrayAdapter<Address> { // holder pattern implementation final ViewHolder holder; if (view == null) { - view = inflater.inflate(R.layout.addresslist_item, null); + view = inflater.inflate(R.layout.addresslist_item, parent, false); holder = new ViewHolder(view); } else { holder = (ViewHolder) view.getTag(); @@ -81,7 +81,7 @@ public class AddressListAdapter extends ArrayAdapter<Address> { private static CharSequence getAddressText(final Address address) { final int maxIndex = address.getMaxAddressLineIndex(); - final ArrayList<String> lines = new ArrayList<String>(); + final ArrayList<String> lines = new ArrayList<>(); for (int i = 0; i <= maxIndex; i++) { final String line = address.getAddressLine(i); if (StringUtils.isNotBlank(line)) { diff --git a/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java b/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java index 5d8ebef..78e1dec 100644 --- a/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java +++ b/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java @@ -1,5 +1,7 @@ package cgeo.geocaching.ui; +import butterknife.ButterKnife; + import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.Geocache; import cgeo.geocaching.R; @@ -7,10 +9,12 @@ import cgeo.geocaching.Waypoint; import cgeo.geocaching.connector.ConnectorFactory; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.Units; +import cgeo.geocaching.utils.Formatter; import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.NonNull; +import android.annotation.SuppressLint; import android.app.Activity; import android.content.res.Resources; import android.text.format.DateUtils; @@ -26,6 +30,9 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; +// TODO The suppression of this lint finding is bad. But to fix it, someone needs to rework the layout of the cache +// details also, not just only change the code here. +@SuppressLint("InflateParams") public final class CacheDetailsCreator { private final Activity activity; private final ViewGroup parentView; @@ -45,10 +52,10 @@ public final class CacheDetailsCreator { * @return the view containing the displayed string (i.e. the right side one from the pair of "label": "value") */ public TextView add(final int nameId, final CharSequence value) { - final RelativeLayout layout = (RelativeLayout) activity.getLayoutInflater().inflate(R.layout.cache_information_item, null); - final TextView nameView = (TextView) layout.findViewById(R.id.name); + final RelativeLayout layout = (RelativeLayout) activity.getLayoutInflater().inflate(R.layout.cache_information_item, null, false); + final TextView nameView = ButterKnife.findById(layout, R.id.name); nameView.setText(res.getString(nameId)); - lastValueView = (TextView) layout.findViewById(R.id.value); + lastValueView = ButterKnife.findById(layout, R.id.value); lastValueView.setText(value); parentView.addView(layout); return lastValueView; @@ -63,10 +70,10 @@ public final class CacheDetailsCreator { } public RelativeLayout addStars(final int nameId, final float value, final int max) { - final RelativeLayout layout = (RelativeLayout) activity.getLayoutInflater().inflate(R.layout.cache_information_item, null); - final TextView nameView = (TextView) layout.findViewById(R.id.name); - lastValueView = (TextView) layout.findViewById(R.id.value); - final LinearLayout layoutStars = (LinearLayout) layout.findViewById(R.id.stars); + final RelativeLayout layout = (RelativeLayout) activity.getLayoutInflater().inflate(R.layout.cache_information_item, null, false); + final TextView nameView = ButterKnife.findById(layout, R.id.name); + lastValueView = ButterKnife.findById(layout, R.id.value); + final LinearLayout layoutStars = ButterKnife.findById(layout, R.id.stars); nameView.setText(activity.getResources().getString(nameId)); lastValueView.setText(String.format("%.1f", value) + ' ' + activity.getResources().getString(R.string.cache_rating_of) + " " + String.format("%d", max)); @@ -81,7 +88,7 @@ public final class CacheDetailsCreator { final LayoutInflater inflater = LayoutInflater.from(activity); for (int i = 0; i < max; i++) { - ImageView star = (ImageView) inflater.inflate(R.layout.star_image, null); + final ImageView star = (ImageView) inflater.inflate(R.layout.star_image, starsContainer, false); if (value - i >= 0.75) { star.setImageResource(R.drawable.star_on); } else if (value - i >= 0.25) { @@ -93,9 +100,9 @@ public final class CacheDetailsCreator { } } - public void addCacheState(Geocache cache) { + public void addCacheState(final Geocache cache) { if (cache.isLogOffline() || cache.isArchived() || cache.isDisabled() || cache.isPremiumMembersOnly() || cache.isFound()) { - final List<String> states = new ArrayList<String>(5); + final List<String> states = new ArrayList<>(5); if (cache.isLogOffline()) { states.add(res.getString(R.string.cache_status_offline_log)); } @@ -115,30 +122,30 @@ public final class CacheDetailsCreator { } } - public void addRating(Geocache cache) { + public void addRating(final Geocache cache) { if (cache.getRating() > 0) { final RelativeLayout itemLayout = addStars(R.string.cache_rating, cache.getRating()); if (cache.getVotes() > 0) { - final TextView itemAddition = (TextView) itemLayout.findViewById(R.id.addition); + final TextView itemAddition = ButterKnife.findById(itemLayout, R.id.addition); itemAddition.setText("(" + cache.getVotes() + ")"); itemAddition.setVisibility(View.VISIBLE); } } } - public void addSize(Geocache cache) { + public void addSize(final Geocache cache) { if (null != cache.getSize() && cache.showSize()) { add(R.string.cache_size, cache.getSize().getL10n()); } } - public void addDifficulty(Geocache cache) { + public void addDifficulty(final Geocache cache) { if (cache.getDifficulty() > 0) { addStars(R.string.cache_difficulty, cache.getDifficulty()); } } - public void addTerrain(Geocache cache) { + public void addTerrain(final Geocache cache) { if (cache.getTerrain() > 0) { addStars(R.string.cache_terrain, cache.getTerrain(), ConnectorFactory.getConnector(cache).getMaxTerrain()); } @@ -189,7 +196,7 @@ public final class CacheDetailsCreator { add(R.string.cache_distance, text); } - public void addEventDate(@NonNull Geocache cache) { + public void addEventDate(@NonNull final Geocache cache) { if (!cache.isEventCache()) { return; } diff --git a/main/src/cgeo/geocaching/ui/CacheListAdapter.java b/main/src/cgeo/geocaching/ui/CacheListAdapter.java index 0d90d9f..b879e54 100644 --- a/main/src/cgeo/geocaching/ui/CacheListAdapter.java +++ b/main/src/cgeo/geocaching/ui/CacheListAdapter.java @@ -19,6 +19,7 @@ import cgeo.geocaching.sorting.InverseComparator; import cgeo.geocaching.sorting.VisitComparator; import cgeo.geocaching.utils.AngleUtils; import cgeo.geocaching.utils.DateUtils; +import cgeo.geocaching.utils.Formatter; import cgeo.geocaching.utils.Log; import org.apache.commons.collections4.CollectionUtils; @@ -26,6 +27,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.eclipse.jdt.annotation.NonNull; +import android.annotation.SuppressLint; import android.app.Activity; import android.content.res.Resources; import android.graphics.drawable.Drawable; @@ -45,6 +47,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; @@ -62,10 +65,10 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> { private boolean selectMode = false; private IFilter currentFilter = null; private List<Geocache> originalList = null; - private boolean isLiveList = Settings.isLiveList(); + private final boolean isLiveList = Settings.isLiveList(); - final private Set<CompassMiniView> compasses = new LinkedHashSet<CompassMiniView>(); - final private Set<DistanceView> distances = new LinkedHashSet<DistanceView>(); + final private Set<CompassMiniView> compasses = new LinkedHashSet<>(); + final private Set<DistanceView> distances = new LinkedHashSet<>(); final private CacheListType cacheListType; final private Resources res; /** Resulting list of caches */ @@ -75,7 +78,7 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> { private static final int SWIPE_MIN_DISTANCE = 60; private static final int SWIPE_MAX_OFF_PATH = 100; - private static final SparseArray<Drawable> gcIconDrawables = new SparseArray<Drawable>(); + private static final SparseArray<Drawable> gcIconDrawables = new SparseArray<>(); /** * time in milliseconds after which the list may be resorted due to position updates */ @@ -109,12 +112,12 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> { @InjectView(R.id.direction) protected CompassMiniView direction; @InjectView(R.id.dirimg) protected ImageView dirImg; - public ViewHolder(View view) { + public ViewHolder(final View view) { super(view); } } - public CacheListAdapter(final Activity activity, final List<Geocache> list, CacheListType cacheListType) { + public CacheListAdapter(final Activity activity, final List<Geocache> list, final CacheListType cacheListType) { super(activity, 0, list); final IGeoData currentGeo = CgeoApplication.getInstance().currentGeo(); if (currentGeo != null) { @@ -132,10 +135,10 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> { gcIconDrawables.put(hashCode, activity.getResources().getDrawable(cacheType.markerId)); // icon with flag for user modified coordinates hashCode = getIconHashCode(cacheType, true); - Drawable[] layers = new Drawable[2]; + final Drawable[] layers = new Drawable[2]; layers[0] = activity.getResources().getDrawable(cacheType.markerId); layers[1] = modifiedCoordinatesMarker; - LayerDrawable ld = new LayerDrawable(layers); + final LayerDrawable ld = new LayerDrawable(layers); ld.setLayerInset(1, layers[0].getIntrinsicWidth() - layers[1].getIntrinsicWidth(), layers[0].getIntrinsicHeight() - layers[1].getIntrinsicHeight(), @@ -183,7 +186,7 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> { return cacheListType == CacheListType.HISTORY; } - public Geocache findCacheByGeocode(String geocode) { + public Geocache findCacheByGeocode(final String geocode) { for (int i = 0; i < getCount(); i++) { if (getItem(i).getGeocode().equalsIgnoreCase(geocode)) { return getItem(i); @@ -198,7 +201,7 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> { public void reFilter() { if (currentFilter != null) { // Back up the list again - originalList = new ArrayList<Geocache>(list); + originalList = new ArrayList<>(list); currentFilter.filter(list); } @@ -210,7 +213,7 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> { public void setFilter(final IFilter filter) { // Backup current caches list if it isn't backed up yet if (originalList == null) { - originalList = new ArrayList<Geocache>(list); + originalList = new ArrayList<>(list); } // If there is already a filter in place, this is a request to change or clear the filter, so we have to @@ -239,7 +242,7 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> { public int getCheckedCount() { int checked = 0; - for (Geocache cache : list) { + for (final Geocache cache : list) { if (cache.isStatusChecked()) { checked++; } @@ -267,7 +270,7 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> { } public void invertSelection() { - for (Geocache cache : list) { + for (final Geocache cache : list) { cache.setStatusChecked(!cache.isStatusChecked()); } notifyDataSetChanged(); @@ -317,7 +320,7 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> { if (coords == null) { return; } - final ArrayList<Geocache> oldList = new ArrayList<Geocache>(list); + final ArrayList<Geocache> oldList = new ArrayList<>(list); Collections.sort(list, getPotentialInversion(new DistanceComparator(coords, list))); // avoid an update if the list has not changed due to location update @@ -368,7 +371,7 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> { final ViewHolder holder; if (v == null) { - v = inflater.inflate(R.layout.cacheslist_item, null); + v = inflater.inflate(R.layout.cacheslist_item, parent, false); holder = new ViewHolder(v); } else { @@ -377,7 +380,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); @@ -471,24 +474,13 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> { } else { favoriteBack = R.drawable.favorite_background_dark; } - final float myVote = cache.getMyVote(); - if (myVote > 0) { // use my own rating for display, if I have voted - if (myVote >= 4) { - favoriteBack = RATING_BACKGROUND[2]; - } else if (myVote >= 3) { - favoriteBack = RATING_BACKGROUND[1]; - } else if (myVote > 0) { - favoriteBack = RATING_BACKGROUND[0]; - } - } else { - final float rating = cache.getRating(); - if (rating >= 3.5) { - favoriteBack = RATING_BACKGROUND[2]; - } else if (rating >= 2.1) { - favoriteBack = RATING_BACKGROUND[1]; - } else if (rating > 0.0) { - favoriteBack = RATING_BACKGROUND[0]; - } + final float rating = cache.getRating(); + if (rating >= 3.5) { + favoriteBack = RATING_BACKGROUND[2]; + } else if (rating >= 2.1) { + favoriteBack = RATING_BACKGROUND[1]; + } else if (rating > 0.0) { + favoriteBack = RATING_BACKGROUND[0]; } holder.favorite.setBackgroundResource(favoriteBack); @@ -501,7 +493,7 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> { return v; } - private static Drawable getCacheIcon(Geocache cache) { + private static Drawable getCacheIcon(final Geocache cache) { int hashCode = getIconHashCode(cache.getType(), cache.hasUserModifiedCoords() || cache.hasFinalDefined()); final Drawable drawable = gcIconDrawables.get(hashCode); if (drawable != null) { @@ -524,36 +516,42 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> { private final Geocache cache; - public SelectionCheckBoxListener(Geocache cache) { + public SelectionCheckBoxListener(final Geocache cache) { this.cache = cache; } @Override - public void onClick(View view) { + public void onClick(final View view) { assert view instanceof CheckBox; final boolean checkNow = ((CheckBox) view).isChecked(); cache.setStatusChecked(checkNow); } } - 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<>(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()); } } @@ -565,6 +563,7 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> { } // Swipe on item + @SuppressLint("ClickableViewAccessibility") @Override public boolean onTouch(final View view, final MotionEvent event) { return gestureDetector.onTouchEvent(event); @@ -572,25 +571,31 @@ 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<>(adapter); } @Override - public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + public boolean onFling(final MotionEvent e1, final MotionEvent e2, final float velocityX, final float velocityY) { try { 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,12 +603,12 @@ 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; } - } catch (Exception e) { + } catch (final Exception e) { Log.w("CacheListAdapter.FlingGesture.onFling", e); } @@ -616,8 +621,8 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> { } public List<Geocache> getCheckedCaches() { - final ArrayList<Geocache> result = new ArrayList<Geocache>(); - for (Geocache cache : list) { + final ArrayList<Geocache> result = new ArrayList<>(); + for (final Geocache cache : list) { if (cache.isStatusChecked()) { result.add(cache); } @@ -630,7 +635,7 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> { if (!result.isEmpty()) { return result; } - return new ArrayList<Geocache>(list); + return new ArrayList<>(list); } public int getCheckedOrAllCount() { diff --git a/main/src/cgeo/geocaching/ui/CompassView.java b/main/src/cgeo/geocaching/ui/CompassView.java index f7111f7..240afcf 100644 --- a/main/src/cgeo/geocaching/ui/CompassView.java +++ b/main/src/cgeo/geocaching/ui/CompassView.java @@ -3,10 +3,10 @@ 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 rx.subscriptions.Subscriptions; import android.content.Context; import android.content.res.Resources; @@ -18,6 +18,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 { @@ -54,14 +55,43 @@ public class CompassView extends View { private int compassOverlayWidth = 0; private int compassOverlayHeight = 0; private boolean initialDisplay; - private Subscription periodicUpdate; + private Subscription periodicUpdate = Subscriptions.empty(); - public CompassView(Context contextIn) { + private static final class UpdateAction implements Action0 { + + private final WeakReference<CompassView> compassViewRef; + + private UpdateAction(final CompassView view) { + this.compassViewRef = new WeakReference<>(view); + } + + @Override + public void call() { + final CompassView compassView = compassViewRef.get(); + if (compassView == null) { + return; + } + compassView.updateGraphics(); + } + } + + public CompassView(final Context contextIn) { super(contextIn); context = contextIn; } - public CompassView(Context contextIn, AttributeSet attrs) { + 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(final Context contextIn, final AttributeSet attrs) { super(contextIn, attrs); context = contextIn; } @@ -88,24 +118,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) { @@ -156,7 +175,7 @@ public class CompassView extends View { * the actual value * @return the new value */ - static protected float smoothUpdate(float goal, float actual) { + static protected float smoothUpdate(final float goal, final float actual) { final double diff = AngleUtils.difference(actual, goal); double offset = 0; @@ -173,7 +192,7 @@ public class CompassView extends View { } @Override - protected void onDraw(Canvas canvas) { + protected void onDraw(final Canvas canvas) { final float azimuthTemp = azimuthShown; final float azimuthRelative = AngleUtils.normalize(azimuthTemp - cacheHeadingShown); @@ -218,11 +237,11 @@ public class CompassView extends View { } @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec)); } - private int measureWidth(int measureSpec) { + private int measureWidth(final int measureSpec) { final int specMode = MeasureSpec.getMode(measureSpec); final int specSize = MeasureSpec.getSize(measureSpec); @@ -238,7 +257,7 @@ public class CompassView extends View { return desired; } - private int measureHeight(int measureSpec) { + private int measureHeight(final int measureSpec) { // The duplicated code in measureHeight and measureWidth cannot be avoided. // Those methods must be efficient, therefore we cannot extract the code differences and unify the remainder. final int specMode = MeasureSpec.getMode(measureSpec); diff --git a/main/src/cgeo/geocaching/ui/EditNoteDialog.java b/main/src/cgeo/geocaching/ui/EditNoteDialog.java index 63f06fc..013fdff 100644 --- a/main/src/cgeo/geocaching/ui/EditNoteDialog.java +++ b/main/src/cgeo/geocaching/ui/EditNoteDialog.java @@ -1,14 +1,20 @@ package cgeo.geocaching.ui; +import butterknife.ButterKnife; + import cgeo.geocaching.R; import cgeo.geocaching.activity.Keyboard; +import cgeo.geocaching.settings.Settings; import cgeo.geocaching.ui.dialog.Dialogs; import org.eclipse.jdt.annotation.NonNull; import android.app.AlertDialog; import android.app.Dialog; +import android.content.Context; import android.content.DialogInterface; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentActivity; @@ -27,10 +33,10 @@ public class EditNoteDialog extends DialogFragment { private EditText mEditText; private EditNoteDialogListener listener; - public static EditNoteDialog newInstance(final String initialNote, EditNoteDialogListener listener) { - EditNoteDialog dialog = new EditNoteDialog(); + public static EditNoteDialog newInstance(final String initialNote, final EditNoteDialogListener listener) { + final EditNoteDialog dialog = new EditNoteDialog(); - Bundle arguments = new Bundle(); + final Bundle arguments = new Bundle(); arguments.putString(EditNoteDialog.ARGUMENT_INITIAL_NOTE, initialNote); dialog.setArguments(arguments); dialog.listener = listener; @@ -39,24 +45,31 @@ public class EditNoteDialog extends DialogFragment { } @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { + public Dialog onCreateDialog(final Bundle savedInstanceState) { final @NonNull FragmentActivity activity = getActivity(); - View view = View.inflate(new ContextThemeWrapper(activity, R.style.dark), R.layout.fragment_edit_note, null); - mEditText = (EditText) view.findViewById(R.id.note); - String initialNote = getArguments().getString(ARGUMENT_INITIAL_NOTE); + + final Context themedContext; + if (Settings.isLightSkin() && VERSION.SDK_INT < VERSION_CODES.HONEYCOMB) + themedContext = new ContextThemeWrapper(activity, R.style.dark); + else + themedContext = activity; + + final View view = View.inflate(themedContext, R.layout.fragment_edit_note, null); + mEditText = ButterKnife.findById(view, R.id.note); + final String initialNote = getArguments().getString(ARGUMENT_INITIAL_NOTE); if (initialNote != null) { mEditText.setText(initialNote); Dialogs.moveCursorToEnd(mEditText); getArguments().remove(ARGUMENT_INITIAL_NOTE); } - AlertDialog.Builder builder = new AlertDialog.Builder(activity); + final AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setTitle(R.string.cache_personal_note); builder.setView(view); builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override - public void onClick(DialogInterface dialog, int whichButton) { + public void onClick(final DialogInterface dialog, final int whichButton) { listener.onFinishEditNoteDialog(mEditText.getText().toString()); dialog.dismiss(); } @@ -64,7 +77,7 @@ public class EditNoteDialog extends DialogFragment { builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override - public void onClick(DialogInterface dialog, int whichButton) { + public void onClick(final DialogInterface dialog, final int whichButton) { dialog.dismiss(); } }); diff --git a/main/src/cgeo/geocaching/ui/FileSelectionListAdapter.java b/main/src/cgeo/geocaching/ui/FileSelectionListAdapter.java index c325f50..e07bbc3 100644 --- a/main/src/cgeo/geocaching/ui/FileSelectionListAdapter.java +++ b/main/src/cgeo/geocaching/ui/FileSelectionListAdapter.java @@ -22,7 +22,7 @@ public class FileSelectionListAdapter extends ArrayAdapter<File> { private final IFileSelectionView parentView; private final LayoutInflater inflater; - public FileSelectionListAdapter(IFileSelectionView parentIn, List<File> listIn) { + public FileSelectionListAdapter(final IFileSelectionView parentIn, final List<File> listIn) { super(parentIn.getContext(), 0, listIn); parentView = parentIn; @@ -36,19 +36,19 @@ public class FileSelectionListAdapter extends ArrayAdapter<File> { return null; } - File file = getItem(position); + final File file = getItem(position); View v = rowView; ViewHolder holder; if (v == null) { - v = inflater.inflate(R.layout.mapfile_item, null); + v = inflater.inflate(R.layout.mapfile_item, parent, false); holder = new ViewHolder(v); } else { holder = (ViewHolder) v.getTag(); } - String currentFile = parentView.getCurrentFile(); + final String currentFile = parentView.getCurrentFile(); if (currentFile != null && file.equals(new File(currentFile))) { holder.filename.setTypeface(holder.filename.getTypeface(), Typeface.BOLD); } else { @@ -67,13 +67,13 @@ public class FileSelectionListAdapter extends ArrayAdapter<File> { private class TouchListener implements View.OnClickListener { private File file = null; - public TouchListener(File fileIn) { + public TouchListener(final File fileIn) { file = fileIn; } // tap on item @Override - public void onClick(View view) { + public void onClick(final View view) { parentView.setCurrentFile(file.toString()); parentView.close(); } @@ -83,7 +83,7 @@ public class FileSelectionListAdapter extends ArrayAdapter<File> { @InjectView(R.id.mapfilepath) protected TextView filepath; @InjectView(R.id.mapfilename) protected TextView filename; - public ViewHolder(View view) { + public ViewHolder(final View view) { super(view); } } diff --git a/main/src/cgeo/geocaching/ui/GPXListAdapter.java b/main/src/cgeo/geocaching/ui/GPXListAdapter.java index ae18ab4..5db103b 100644 --- a/main/src/cgeo/geocaching/ui/GPXListAdapter.java +++ b/main/src/cgeo/geocaching/ui/GPXListAdapter.java @@ -28,12 +28,12 @@ public class GPXListAdapter extends ArrayAdapter<File> { @InjectView(R.id.filepath) protected TextView filepath; @InjectView(R.id.filename) protected TextView filename; - public ViewHolder(View view) { + public ViewHolder(final View view) { super(view); } } - public GPXListAdapter(GpxFileListActivity parentIn, List<File> listIn) { + public GPXListAdapter(final GpxFileListActivity parentIn, final List<File> listIn) { super(parentIn, 0, listIn); activity = parentIn; @@ -53,7 +53,7 @@ public class GPXListAdapter extends ArrayAdapter<File> { final ViewHolder holder; if (view == null) { - view = inflater.inflate(R.layout.gpx_item, null); + view = inflater.inflate(R.layout.gpx_item, parent, false); holder = new ViewHolder(view); } else { holder = (ViewHolder) view.getTag(); @@ -62,7 +62,7 @@ public class GPXListAdapter extends ArrayAdapter<File> { view.setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View v) { + public void onClick(final View v) { (new GPXImporter(activity, activity.getListId(), null)).importGPX(file); } }); @@ -73,10 +73,10 @@ public class GPXListAdapter extends ArrayAdapter<File> { view.setOnLongClickListener(new View.OnLongClickListener() { @Override - public boolean onLongClick(View v) { + public boolean onLongClick(final View v) { Dialogs.confirmYesNo(activity, R.string.gpx_import_delete_title, activity.getString(R.string.gpx_import_delete_message, file.getName()), new DialogInterface.OnClickListener() { @Override - public void onClick(DialogInterface dialog, int id) { + public void onClick(final DialogInterface dialog, final int id) { FileUtils.deleteIgnoringFailure(file); GPXListAdapter.this.remove(file); } diff --git a/main/src/cgeo/geocaching/ui/HtmlImageCounter.java b/main/src/cgeo/geocaching/ui/HtmlImageCounter.java deleted file mode 100644 index 24b70ea..0000000 --- a/main/src/cgeo/geocaching/ui/HtmlImageCounter.java +++ /dev/null @@ -1,19 +0,0 @@ -package cgeo.geocaching.ui; - -import android.graphics.drawable.Drawable; -import android.text.Html; - -public class HtmlImageCounter implements Html.ImageGetter { - - private int imageCount = 0; - - @Override - public Drawable getDrawable(String url) { - imageCount++; - return null; - } - - public int getImageCount() { - return imageCount; - } -}
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/ui/ImagesList.java b/main/src/cgeo/geocaching/ui/ImagesList.java index 5727971..8bd4ac2 100644 --- a/main/src/cgeo/geocaching/ui/ImagesList.java +++ b/main/src/cgeo/geocaching/ui/ImagesList.java @@ -1,5 +1,7 @@ package cgeo.geocaching.ui; +import butterknife.ButterKnife; + import cgeo.geocaching.Image; import cgeo.geocaching.R; import cgeo.geocaching.files.LocalStorage; @@ -9,6 +11,7 @@ import cgeo.geocaching.utils.Log; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; + import rx.Subscription; import rx.android.observables.AndroidObservable; import rx.functions.Action0; @@ -65,11 +68,11 @@ public class ImagesList { private LayoutInflater inflater = null; private final Activity activity; // We could use a Set here, but we will insert no duplicates, so there is no need to check for uniqueness. - private final Collection<Bitmap> bitmaps = new LinkedList<Bitmap>(); + private final Collection<Bitmap> bitmaps = new LinkedList<>(); /** * map image view id to image */ - private final SparseArray<Image> images = new SparseArray<Image>(); + private final SparseArray<Image> images = new SparseArray<>(); private final String geocode; private LinearLayout imagesView; @@ -97,26 +100,27 @@ public class ImagesList { } })); - imagesView = (LinearLayout) parentView.findViewById(R.id.spoiler_list); + imagesView = ButterKnife.findById(parentView, R.id.spoiler_list); final HtmlImage imgGetter = new HtmlImage(geocode, true, offline ? StoredList.STANDARD_LIST_ID : StoredList.TEMPORARY_LIST_ID, false); for (final Image img : images) { - final LinearLayout rowView = (LinearLayout) inflater.inflate(R.layout.cache_image_item, null); + final LinearLayout rowView = (LinearLayout) inflater.inflate(R.layout.cache_image_item, imagesView, false); assert(rowView != null); if (StringUtils.isNotBlank(img.getTitle())) { - ((TextView) rowView.findViewById(R.id.title)).setText(Html.fromHtml(img.getTitle())); + final TextView titleView = ButterKnife.findById(rowView, R.id.title); + titleView.setText(Html.fromHtml(img.getTitle())); rowView.findViewById(R.id.titleLayout).setVisibility(View.VISIBLE); } if (StringUtils.isNotBlank(img.getDescription())) { - final TextView descView = (TextView) rowView.findViewById(R.id.description); + final TextView descView = ButterKnife.findById(rowView, R.id.description); descView.setText(Html.fromHtml(img.getDescription()), TextView.BufferType.SPANNABLE); descView.setVisibility(View.VISIBLE); } - final ImageView imageView = (ImageView) inflater.inflate(R.layout.image_item, null); + final ImageView imageView = (ImageView) inflater.inflate(R.layout.image_item, rowView, false); assert(imageView != null); subscriptions.add(AndroidObservable.bindActivity(activity, imgGetter.fetchDrawable(img.getUrl())).subscribe(new Action1<BitmapDrawable>() { @Override @@ -141,7 +145,7 @@ public class ImagesList { imageView.setClickable(true); imageView.setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View arg0) { + public void onClick(final View arg0) { viewImageInStandardApp(img, image); } }); @@ -169,7 +173,7 @@ public class ImagesList { imagesView.removeAllViews(); } - public void onCreateContextMenu(ContextMenu menu, View v) { + public void onCreateContextMenu(final ContextMenu menu, final View v) { assert v instanceof ImageView; activity.getMenuInflater().inflate(R.menu.images_list_context, menu); final Resources res = activity.getResources(); @@ -179,7 +183,7 @@ public class ImagesList { currentImage = images.get(view.getId()); } - public boolean onContextItemSelected(MenuItem item) { + public boolean onContextItemSelected(final MenuItem item) { switch (item.getItemId()) { case R.id.image_open_file: viewImageInStandardApp(currentImage, currentDrawable); @@ -221,7 +225,7 @@ public class ImagesList { intent.setDataAndType(Uri.fromFile(saveToTemporaryJPGFile(image)), "image/jpeg"); } activity.startActivity(intent); - } catch (Exception e) { + } catch (final Exception e) { Log.e("ImagesList.viewImageInStandardApp", e); } } diff --git a/main/src/cgeo/geocaching/ui/LoggingUI.java b/main/src/cgeo/geocaching/ui/LoggingUI.java index d5c5fae..8454474 100644 --- a/main/src/cgeo/geocaching/ui/LoggingUI.java +++ b/main/src/cgeo/geocaching/ui/LoggingUI.java @@ -78,7 +78,7 @@ public class LoggingUI extends AbstractUIFactory { final LogType currentLogType = currentLog == null ? null : currentLog.type; final List<LogType> logTypes = cache.getPossibleLogTypes(); - final ArrayList<LogTypeEntry> list = new ArrayList<LogTypeEntry>(); + final ArrayList<LogTypeEntry> list = new ArrayList<>(); for (LogType logType : logTypes) { list.add(new LogTypeEntry(logType, null, logType == currentLogType)); } @@ -90,7 +90,7 @@ public class LoggingUI extends AbstractUIFactory { final AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setTitle(R.string.cache_menu_visit_offline); - final ArrayAdapter<LogTypeEntry> adapter = new ArrayAdapter<LogTypeEntry>(activity, android.R.layout.select_dialog_item, list); + final ArrayAdapter<LogTypeEntry> adapter = new ArrayAdapter<>(activity, android.R.layout.select_dialog_item, list); builder.setAdapter(adapter, new DialogInterface.OnClickListener() { @Override diff --git a/main/src/cgeo/geocaching/ui/WeakReferenceHandler.java b/main/src/cgeo/geocaching/ui/WeakReferenceHandler.java index 4724466..d51e697 100644 --- a/main/src/cgeo/geocaching/ui/WeakReferenceHandler.java +++ b/main/src/cgeo/geocaching/ui/WeakReferenceHandler.java @@ -18,7 +18,7 @@ public abstract class WeakReferenceHandler<ActivityType extends Activity> extend private final WeakReference<ActivityType> activityRef; protected WeakReferenceHandler(final ActivityType activity) { - this.activityRef = new WeakReference<ActivityType>(activity); + this.activityRef = new WeakReference<>(activity); } protected ActivityType getActivity() { diff --git a/main/src/cgeo/geocaching/ui/dialog/CoordinatesInputDialog.java b/main/src/cgeo/geocaching/ui/dialog/CoordinatesInputDialog.java index b2ce11a..ca3e3a4 100644 --- a/main/src/cgeo/geocaching/ui/dialog/CoordinatesInputDialog.java +++ b/main/src/cgeo/geocaching/ui/dialog/CoordinatesInputDialog.java @@ -1,22 +1,28 @@ package cgeo.geocaching.ui.dialog; +import butterknife.ButterKnife; + 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; import org.apache.commons.lang3.StringUtils; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; 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 +31,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 +44,70 @@ 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) { + + final 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()); + } + + final CoordinatesInputDialog cid = new CoordinatesInputDialog(); + cid.setArguments(args); + return cid; } @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); + gp = getArguments().getParcelable(GEOPOINT_ARG); + gpinitial = getArguments().getParcelable(GEOPOINT_INTIAL_ARG); + cacheCoords = getArguments().getParcelable(CACHECOORDS_ARG); + + if (savedInstanceState != null && savedInstanceState.getParcelable(GEOPOINT_ARG)!=null) { + gp = savedInstanceState.getParcelable(GEOPOINT_ARG); + } - setContentView(R.layout.coordinatesinput_dialog); + if (VERSION.SDK_INT < VERSION_CODES.HONEYCOMB && Settings.isLightSkin()) { + setStyle(STYLE_NORMAL, R.style.DialogFixGingerbread); + } + } + + @Override + public void onSaveInstanceState(final 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); - final Spinner spinner = (Spinner) findViewById(R.id.spinnerCoordinateFormats); + final View v = inflater.inflate(R.layout.coordinatesinput_dialog, container, false); + final Spinner spinner = ButterKnife.findById(v, 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 +115,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 = ButterKnife.findById(v, R.id.ButtonLat); + eLat = ButterKnife.findById(v, R.id.latitude); + eLatDeg = ButterKnife.findById(v, R.id.EditTextLatDeg); + eLatMin = ButterKnife.findById(v, R.id.EditTextLatMin); + eLatSec = ButterKnife.findById(v, R.id.EditTextLatSec); + eLatSub = ButterKnife.findById(v, R.id.EditTextLatSecFrac); + tLatSep1 = ButterKnife.findById(v, R.id.LatSeparator1); + tLatSep2 = ButterKnife.findById(v, R.id.LatSeparator2); + tLatSep3 = ButterKnife.findById(v, R.id.LatSeparator3); + + bLon = ButterKnife.findById(v, R.id.ButtonLon); + eLon = ButterKnife.findById(v, R.id.longitude); + eLonDeg = ButterKnife.findById(v, R.id.EditTextLonDeg); + eLonMin = ButterKnife.findById(v, R.id.EditTextLonMin); + eLonSec = ButterKnife.findById(v, R.id.EditTextLonSec); + eLonSub = ButterKnife.findById(v, R.id.EditTextLonSecFrac); + tLonSep1 = ButterKnife.findById(v, R.id.LonSeparator1); + tLonSep2 = ButterKnife.findById(v, R.id.LonSeparator2); + tLonSep3 = ButterKnife.findById(v, R.id.LonSeparator3); eLatDeg.addTextChangedListener(new TextChanged(eLatDeg)); eLatMin.addTextChangedListener(new TextChanged(eLatMin)); @@ -115,18 +156,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 = ButterKnife.findById(v, R.id.current); buttonCurrent.setOnClickListener(new CurrentListener()); - final Button buttonCache = (Button) findViewById(R.id.cache); - if (cache != null) { + final Button buttonCache = ButterKnife.findById(v, 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 = ButterKnife.findById(v, R.id.done); buttonDone.setOnClickListener(new InputDoneListener()); + + return v; } + + private void updateGUI() { if (gp == null) { return; @@ -137,14 +184,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 +212,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 +237,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); @@ -226,7 +273,7 @@ public class CoordinatesInputDialog extends NoTitleDialog { private class ButtonClickListener implements View.OnClickListener { @Override - public void onClick(View view) { + public void onClick(final View view) { assert view instanceof Button; final Button button = (Button) view; final CharSequence text = button.getText(); @@ -259,12 +306,12 @@ public class CoordinatesInputDialog extends NoTitleDialog { private final EditText editText; - public TextChanged(EditText editText) { + public TextChanged(final EditText editText) { this.editText = editText; } @Override - public void afterTextChanged(Editable s) { + public void afterTextChanged(final Editable s) { /* * Max lengths, depending on currentFormat * @@ -318,11 +365,11 @@ public class CoordinatesInputDialog extends NoTitleDialog { } @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { + public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) { } @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { + public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { } } @@ -371,7 +418,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; } @@ -392,15 +440,15 @@ public class CoordinatesInputDialog extends NoTitleDialog { private class CoordinateFormatListener implements OnItemSelectedListener { @Override - public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) { + public void onItemSelected(final AdapterView<?> parent, final View view, final int pos, final long id) { // Ignore first call, which comes from onCreate() if (currentFormat != null) { // 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; } @@ -413,7 +461,7 @@ public class CoordinatesInputDialog extends NoTitleDialog { } @Override - public void onNothingSelected(AdapterView<?> arg0) { + public void onNothingSelected(final AdapterView<?> arg0) { } } @@ -421,13 +469,14 @@ public class CoordinatesInputDialog extends NoTitleDialog { private class CurrentListener implements View.OnClickListener { @Override - public void onClick(View v) { - if (geo == null || geo.getCoords() == null) { - context.showToast(context.getResources().getString(R.string.err_point_unknown_position)); + public void onClick(final View v) { + 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(); } } @@ -435,37 +484,35 @@ public class CoordinatesInputDialog extends NoTitleDialog { private class CacheListener implements View.OnClickListener { @Override - public void onClick(View v) { - if (cache == null || cache.getCoords() == null) { - context.showToast(context.getResources().getString(R.string.err_location_unknown)); + public void onClick(final View v) { + 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 - public void onClick(View v) { + public void onClick(final View v) { if (!areCurrentCoordinatesValid(true)) { 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/CustomProgressDialog.java b/main/src/cgeo/geocaching/ui/dialog/CustomProgressDialog.java index 97c5c29..db21f70 100644 --- a/main/src/cgeo/geocaching/ui/dialog/CustomProgressDialog.java +++ b/main/src/cgeo/geocaching/ui/dialog/CustomProgressDialog.java @@ -7,44 +7,29 @@ import android.app.ProgressDialog; import android.content.Context; import android.os.Bundle; import android.view.View; -import android.widget.TextView; import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; /** - * Modified progress dialog class which allows hiding the absolute numbers + * Modified progress dialog class which allows hiding the absolute numbers. * */ public class CustomProgressDialog extends ProgressDialog { - public CustomProgressDialog(Context context) { + public CustomProgressDialog(final Context context) { super(context, ActivityMixin.getDialogTheme()); } @Override - protected void onCreate(Bundle savedInstanceState) { + protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); - try { - Method method = TextView.class.getMethod("setVisibility", Integer.TYPE); - - Field[] fields = this.getClass().getSuperclass().getDeclaredFields(); - - for (Field field : fields) { - if (field.getName().equalsIgnoreCase("mProgressNumber")) { - field.setAccessible(true); - TextView textView = (TextView) field.get(this); - method.invoke(textView, View.GONE); - } - } - } catch (NoSuchMethodException e) { - Log.e("Failed to invoke the progressDialog method 'setVisibility' and set 'mProgressNumber' to GONE.", e); - } catch (IllegalAccessException e) { - Log.e("Failed to invoke the progressDialog method 'setVisibility' and set 'mProgressNumber' to GONE.", e); - } catch (InvocationTargetException e) { - Log.e("Failed to invoke the progressDialog method 'setVisibility' and set 'mProgressNumber' to GONE.", e); + // Field is private, make it accessible through reflection before hiding it. + final Field field = getClass().getSuperclass().getDeclaredField("mProgressNumber"); + field.setAccessible(true); + ((View) field.get(this)).setVisibility(View.GONE); + } catch (NoSuchFieldException | IllegalAccessException e) { + Log.e("Failed to find the progressDialog field 'mProgressNumber'", e); } } }
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/ui/dialog/DateDialog.java b/main/src/cgeo/geocaching/ui/dialog/DateDialog.java index 18f8e2e..1046f81 100644 --- a/main/src/cgeo/geocaching/ui/dialog/DateDialog.java +++ b/main/src/cgeo/geocaching/ui/dialog/DateDialog.java @@ -1,49 +1,59 @@ package cgeo.geocaching.ui.dialog; +import butterknife.ButterKnife; + 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; - - public DateDialog(Activity contextIn, DateDialogParent parentIn, Calendar dateIn) { - super(contextIn); + private Calendar date; - // init - this.date = dateIn; - this.parent = parentIn; + public static DateDialog getInstance(final Calendar date) { + final DateDialog dd = new DateDialog(); + final 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); + final 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) { + final View v = inflater.inflate(R.layout.date, container, false); - final DatePicker picker = (DatePicker) findViewById(R.id.picker); + final DatePicker picker = ButterKnife.findById(v, 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); + public void onDateChanged(final DatePicker picker, final int year, final int month, final int 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..21e1a82 100644 --- a/main/src/cgeo/geocaching/ui/dialog/Dialogs.java +++ b/main/src/cgeo/geocaching/ui/dialog/Dialogs.java @@ -2,10 +2,14 @@ package cgeo.geocaching.ui.dialog; import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.R; +import cgeo.geocaching.settings.Settings; +import cgeo.geocaching.utils.ImageUtils; import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.Nullable; +import rx.Observable; +import rx.android.schedulers.AndroidSchedulers; import rx.functions.Action1; import android.app.Activity; @@ -15,6 +19,8 @@ import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.graphics.drawable.Drawable; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; import android.text.Editable; import android.text.InputType; import android.text.TextWatcher; @@ -24,7 +30,7 @@ import android.widget.EditText; /** * Wrapper for {@link AlertDialog}. If you want to show a simple text, use one of the - * {@link #message(Activity, String, String, Drawable)} variants. If you want the user to confirm using Okay/Cancel or + * {@link #message(Activity, String, String)} variants. If you want the user to confirm using Okay/Cancel or * Yes/No, select one of the {@link #confirm(Activity, String, String, String, OnClickListener)} or * {@link #confirmYesNo(Activity, String, String, OnClickListener)} variants. * @@ -49,8 +55,8 @@ public final class Dialogs { * listener of the positive button */ public static AlertDialog.Builder confirm(final Activity context, final String title, final String msg, final String positiveButton, final OnClickListener okayListener) { - AlertDialog.Builder builder = new AlertDialog.Builder(context); - AlertDialog dialog = builder.setTitle(title) + final AlertDialog.Builder builder = new AlertDialog.Builder(context); + final AlertDialog dialog = builder.setTitle(title) .setCancelable(true) .setMessage(msg) .setPositiveButton(positiveButton, okayListener) @@ -92,8 +98,8 @@ public final class Dialogs { * listener of the positive button */ public static AlertDialog.Builder confirmYesNo(final Activity context, final String title, final String msg, final OnClickListener yesListener) { - AlertDialog.Builder builder = new AlertDialog.Builder(context); - AlertDialog dialog = builder.setTitle(title) + final AlertDialog.Builder builder = new AlertDialog.Builder(context); + final AlertDialog dialog = builder.setTitle(title) .setCancelable(true) .setMessage(msg) .setPositiveButton(android.R.string.yes, yesListener) @@ -200,7 +206,7 @@ public final class Dialogs { return confirm(context, getString(title), getString(msg), okayListener); } - private static String getString(int resourceId) { + private static String getString(final int resourceId) { return CgeoApplication.getInstance().getString(resourceId); } @@ -221,6 +227,18 @@ public final class Dialogs { * * @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 * message dialog title * @param message @@ -231,7 +249,7 @@ public final class Dialogs { } /** - * Show a message dialog with a single "OK" button and an icon. + * Show a message dialog with a single "OK" button and an eventual icon. * * @param context * activity owning the dialog @@ -239,21 +257,29 @@ public final class Dialogs { * message dialog title * @param message * message dialog content - * @param icon - * message dialog title icon + * @param iconObservable + * observable (may be <tt>null</tt>) containing the icon(s) to set */ - public static void message(final Activity context, final @Nullable String title, final String message, final @Nullable Drawable icon) { - Builder builder = new AlertDialog.Builder(context) + public static void message(final Activity context, final @Nullable String title, final String message, final @Nullable Observable<Drawable> iconObservable) { + final Builder builder = new AlertDialog.Builder(context) .setMessage(message) .setCancelable(true) .setPositiveButton(getString(android.R.string.ok), null); if (title != null) { builder.setTitle(title); } - if (icon != null) { - builder.setIcon(icon); + builder.setIcon(ImageUtils.getTransparent1x1Drawable(context.getResources())); + + final AlertDialog dialog = builder.create(); + if (iconObservable != null) { + iconObservable.observeOn(AndroidSchedulers.mainThread()).subscribe(new Action1<Drawable>() { + @Override + public void call(final Drawable drawable) { + dialog.setIcon(drawable); + } + }); } - builder.create().show(); + dialog.show(); } /** @@ -293,11 +319,11 @@ public final class Dialogs { * message dialog title * @param message * message dialog content - * @param icon + * @param iconObservable * message dialog title icon */ - public static void message(final Activity context, final int title, final int message, final @Nullable Drawable icon) { - message(context, getString(title), getString(message), icon); + public static void message(final Activity context, final int title, final int message, final Observable<Drawable> iconObservable) { + message(context, getString(title), getString(message), iconObservable); } /** @@ -315,7 +341,14 @@ public final class Dialogs { * listener to be run on okay */ public static void input(final Activity context, final int title, final String defaultValue, final int buttonTitle, final Action1<String> okayListener) { - final Context themedContext = new ContextThemeWrapper(context, R.style.dark); + final Context themedContext; + + if (Settings.isLightSkin() && VERSION.SDK_INT < VERSION_CODES.HONEYCOMB) { + themedContext = new ContextThemeWrapper(context, R.style.dark); + } else { + themedContext = context; + } + final EditText input = new EditText(themedContext); input.setInputType(InputType.TYPE_TEXT_FLAG_CAP_SENTENCES | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_CLASS_TEXT); input.setText(defaultValue); @@ -326,13 +359,13 @@ public final class Dialogs { builder.setPositiveButton(buttonTitle, new OnClickListener() { @Override - public void onClick(DialogInterface dialog, int which) { + public void onClick(final DialogInterface dialog, final int which) { okayListener.call(input.getText().toString()); } }); builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override - public void onClick(DialogInterface dialog, int whichButton) { + public void onClick(final DialogInterface dialog, final int whichButton) { dialog.dismiss(); } }); @@ -341,17 +374,17 @@ public final class Dialogs { input.addTextChangedListener(new TextWatcher() { @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { + public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { // empty } @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { + public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) { // empty } @Override - public void afterTextChanged(Editable editable) { + public void afterTextChanged(final Editable editable) { enableDialogButtonIfNotEmpty(dialog, editable.toString()); } }); diff --git a/main/src/cgeo/geocaching/ui/dialog/LiveMapInfoDialogBuilder.java b/main/src/cgeo/geocaching/ui/dialog/LiveMapInfoDialogBuilder.java index c29f549..9858c28 100644 --- a/main/src/cgeo/geocaching/ui/dialog/LiveMapInfoDialogBuilder.java +++ b/main/src/cgeo/geocaching/ui/dialog/LiveMapInfoDialogBuilder.java @@ -6,7 +6,10 @@ import cgeo.geocaching.settings.Settings; import android.app.Activity; import android.app.AlertDialog; +import android.content.Context; import android.content.DialogInterface; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; import android.view.ContextThemeWrapper; import android.view.View; @@ -15,8 +18,12 @@ public class LiveMapInfoDialogBuilder { public static AlertDialog create(Activity activity) { final AlertDialog.Builder builder = new AlertDialog.Builder(activity); - // AlertDialog has always dark style, so we have to apply it as well always - final View layout = View.inflate(new ContextThemeWrapper(activity, R.style.dark), R.layout.livemapinfo, null); + final Context themedContext; + if (Settings.isLightSkin() && VERSION.SDK_INT < VERSION_CODES.HONEYCOMB) + themedContext = new ContextThemeWrapper(activity, R.style.dark); + else + themedContext = activity; + final View layout = View.inflate(themedContext, R.layout.livemapinfo, null); builder.setView(layout); final int showCount = Settings.getLiveMapHintShowCount(); 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/ui/logs/CacheLogsViewCreator.java b/main/src/cgeo/geocaching/ui/logs/CacheLogsViewCreator.java index 6311476..38a219e 100644 --- a/main/src/cgeo/geocaching/ui/logs/CacheLogsViewCreator.java +++ b/main/src/cgeo/geocaching/ui/logs/CacheLogsViewCreator.java @@ -53,7 +53,7 @@ public class CacheLogsViewCreator extends LogsViewCreator { // adds the log counts final Map<LogType, Integer> logCounts = getCache().getLogCounts(); if (logCounts != null) { - final List<Entry<LogType, Integer>> sortedLogCounts = new ArrayList<Entry<LogType, Integer>>(logCounts.size()); + final List<Entry<LogType, Integer>> sortedLogCounts = new ArrayList<>(logCounts.size()); for (final Entry<LogType, Integer> entry : logCounts.entrySet()) { // it may happen that the label is unknown -> then avoid any output for this type if (entry.getKey() != LogType.PUBLISH_LISTING && entry.getKey().getL10n() != null) { @@ -71,7 +71,7 @@ public class CacheLogsViewCreator extends LogsViewCreator { } }); - final ArrayList<String> labels = new ArrayList<String>(sortedLogCounts.size()); + final ArrayList<String> labels = new ArrayList<>(sortedLogCounts.size()); for (final Entry<LogType, Integer> pair : sortedLogCounts) { labels.add(pair.getValue() + "× " + pair.getKey().getL10n()); } diff --git a/main/src/cgeo/geocaching/ui/logs/LogsViewCreator.java b/main/src/cgeo/geocaching/ui/logs/LogsViewCreator.java index 6590d22..9532946 100644 --- a/main/src/cgeo/geocaching/ui/logs/LogsViewCreator.java +++ b/main/src/cgeo/geocaching/ui/logs/LogsViewCreator.java @@ -1,28 +1,24 @@ package cgeo.geocaching.ui.logs; -import cgeo.geocaching.Image; import cgeo.geocaching.ImagesActivity; import cgeo.geocaching.LogEntry; import cgeo.geocaching.R; import cgeo.geocaching.activity.AbstractActivity; -import cgeo.geocaching.activity.Progress; import cgeo.geocaching.list.StoredList; import cgeo.geocaching.network.HtmlImage; import cgeo.geocaching.ui.AbstractCachingListViewPageViewCreator; import cgeo.geocaching.ui.AnchorAwareLinkMovementMethod; import cgeo.geocaching.ui.DecryptTextClickListener; -import cgeo.geocaching.ui.Formatter; -import cgeo.geocaching.ui.HtmlImageCounter; import cgeo.geocaching.ui.UserActionsClickListener; +import cgeo.geocaching.utils.Formatter; import cgeo.geocaching.utils.TextUtils; import cgeo.geocaching.utils.UnknownTagsHandler; import org.apache.commons.lang3.StringEscapeUtils; -import android.os.AsyncTask; import android.text.Html; -import android.text.Spanned; import android.view.View; +import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.TextView; @@ -34,19 +30,19 @@ public abstract class LogsViewCreator extends AbstractCachingListViewPageViewCre protected final AbstractActivity activity; - public LogsViewCreator(AbstractActivity activity) { + public LogsViewCreator(final AbstractActivity activity) { this.activity = activity; } @Override - public ListView getDispatchedView() { + public ListView getDispatchedView(final ViewGroup parentView) { if (!isValid()) { return null; } final List<LogEntry> logs = getLogs(); - view = (ListView) activity.getLayoutInflater().inflate(R.layout.logs_page, null); + view = (ListView) activity.getLayoutInflater().inflate(R.layout.logs_page, parentView, false); addHeaderView(); view.setAdapter(new ArrayAdapter<LogEntry>(activity, R.layout.logs_item, logs) { @@ -54,7 +50,7 @@ public abstract class LogsViewCreator extends AbstractCachingListViewPageViewCre public View getView(final int position, final View convertView, final android.view.ViewGroup parent) { View rowView = convertView; if (null == rowView) { - rowView = activity.getLayoutInflater().inflate(R.layout.logs_item, null); + rowView = activity.getLayoutInflater().inflate(R.layout.logs_item, parent, false); } LogViewHolder holder = (LogViewHolder) rowView.getTag(); if (null == holder) { @@ -71,7 +67,7 @@ public abstract class LogsViewCreator extends AbstractCachingListViewPageViewCre return view; } - protected void fillViewHolder(final View convertView, LogViewHolder holder, final LogEntry log) { + protected void fillViewHolder(final View convertView, final LogViewHolder holder, final LogEntry log) { if (log.date > 0) { holder.date.setText(Formatter.formatShortDateVerbally(log.date)); holder.date.setVisibility(View.VISIBLE); @@ -88,17 +84,10 @@ public abstract class LogsViewCreator extends AbstractCachingListViewPageViewCre String logText = log.log; if (TextUtils.containsHtml(logText)) { logText = log.getDisplayText(); - // Fast preview: parse only HTML without loading any images - final HtmlImageCounter imageCounter = new HtmlImageCounter(); final UnknownTagsHandler unknownTagsHandler = new UnknownTagsHandler(); - holder.text.setText(Html.fromHtml(logText, imageCounter, unknownTagsHandler), TextView.BufferType.SPANNABLE); - if (imageCounter.getImageCount() > 0) { - // Complete view: parse again with loading images - if necessary ! If there are any images causing problems the user can see at least the preview - final LogImageLoader loader = new LogImageLoader(holder); - loader.execute(logText); - } - } - else { + holder.text.setText(Html.fromHtml(logText, new HtmlImage(getGeocode(), false, StoredList.STANDARD_LIST_ID, false, holder.text), + unknownTagsHandler), TextView.BufferType.SPANNABLE); + } else { holder.text.setText(logText, TextView.BufferType.SPANNABLE); } @@ -108,8 +97,8 @@ public abstract class LogsViewCreator extends AbstractCachingListViewPageViewCre holder.images.setVisibility(View.VISIBLE); holder.images.setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View v) { - ImagesActivity.startActivityLogImages(activity, getGeocode(), new ArrayList<Image>(log.getLogImages())); + public void onClick(final View v) { + ImagesActivity.startActivityLogImages(activity, getGeocode(), new ArrayList<>(log.getLogImages())); } }); } else { @@ -146,30 +135,4 @@ public abstract class LogsViewCreator extends AbstractCachingListViewPageViewCre abstract protected boolean isValid(); - /** Loads the Log Images outside the UI thread. */ - - private class LogImageLoader extends AsyncTask<String, Progress, Spanned> { - final private LogViewHolder holder; - final private int position; - - public LogImageLoader(LogViewHolder holder) { - this.holder = holder; - this.position = holder.getPosition(); - } - - @Override - protected Spanned doInBackground(String... logtext) { - return Html.fromHtml(logtext[0], new HtmlImage(getGeocode(), false, StoredList.STANDARD_LIST_ID, false), null); //, TextView.BufferType.SPANNABLE) - } - - @Override - protected void onPostExecute(Spanned result) { - // Ensure that this holder and its view still references the right item before updating the text. - if (position == holder.getPosition()) { - holder.text.setText(result); - } - } - - } - } diff --git a/main/src/cgeo/geocaching/utils/AsyncTaskWithProgress.java b/main/src/cgeo/geocaching/utils/AsyncTaskWithProgress.java index 7526d92..3d2b2b1 100644 --- a/main/src/cgeo/geocaching/utils/AsyncTaskWithProgress.java +++ b/main/src/cgeo/geocaching/utils/AsyncTaskWithProgress.java @@ -13,7 +13,7 @@ import android.os.AsyncTask; * If no style is given, the progress dialog uses "determinate" style with known maximum. The progress maximum is * automatically derived from the number of {@code Params} given to the task in {@link #execute(Object...)}. * </p> - * + * * @param <Params> * @param <Result> */ @@ -53,7 +53,7 @@ public abstract class AsyncTaskWithProgress<Params, Result> extends AsyncTask<Pa * @param progressTitle * @param progressMessage */ - public AsyncTaskWithProgress(final Activity activity, final String progressTitle, final String progressMessage, boolean indeterminate) { + public AsyncTaskWithProgress(final Activity activity, final String progressTitle, final String progressMessage, final boolean indeterminate) { this.activity = activity; this.progressTitle = progressTitle; this.progressMessage = progressMessage; @@ -66,7 +66,7 @@ public abstract class AsyncTaskWithProgress<Params, Result> extends AsyncTask<Pa * @param activity * @param progressTitle */ - public AsyncTaskWithProgress(final Activity activity, final String progressTitle, boolean indeterminate) { + public AsyncTaskWithProgress(final Activity activity, final String progressTitle, final boolean indeterminate) { this(activity, progressTitle, null, indeterminate); } @@ -91,7 +91,7 @@ public abstract class AsyncTaskWithProgress<Params, Result> extends AsyncTask<Pa } @Override - protected final void onPostExecute(Result result) { + protected final void onPostExecute(final Result result) { onPostExecuteInternal(result); if (null != activity) { progress.dismiss(); @@ -103,12 +103,12 @@ public abstract class AsyncTaskWithProgress<Params, Result> extends AsyncTask<Pa * * @param result */ - protected void onPostExecuteInternal(Result result) { + protected void onPostExecuteInternal(final Result result) { // empty by default } @Override - protected final void onProgressUpdate(Integer... status) { + protected final void onProgressUpdate(final Integer... status) { final int progressValue = status[0]; if (null != activity && progressValue >= 0) { progress.setProgress(progressValue); @@ -119,7 +119,7 @@ public abstract class AsyncTaskWithProgress<Params, Result> extends AsyncTask<Pa /** * This method should by overridden by sub classes instead of {@link #onProgressUpdate(Integer...)}. */ - protected void onProgressUpdateInternal(@SuppressWarnings("unused") int progress) { + protected void onProgressUpdateInternal(@SuppressWarnings("unused") final int progress) { // empty by default } @@ -127,8 +127,9 @@ public abstract class AsyncTaskWithProgress<Params, Result> extends AsyncTask<Pa progress.setMessage(message); } + @SuppressWarnings("unchecked") @Override - protected final Result doInBackground(Params... params) { + protected final Result doInBackground(final Params... params) { if (params != null) { progress.setMaxProgressAndReset(params.length); } diff --git a/main/src/cgeo/geocaching/utils/DatabaseBackupUtils.java b/main/src/cgeo/geocaching/utils/DatabaseBackupUtils.java index 4ce2e0c..d8aff74 100644 --- a/main/src/cgeo/geocaching/utils/DatabaseBackupUtils.java +++ b/main/src/cgeo/geocaching/utils/DatabaseBackupUtils.java @@ -3,14 +3,12 @@ package cgeo.geocaching.utils; import cgeo.geocaching.DataStore; import cgeo.geocaching.MainActivity; import cgeo.geocaching.R; -import cgeo.geocaching.ui.Formatter; import cgeo.geocaching.ui.dialog.Dialogs; 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 +51,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 +58,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 +72,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/ui/Formatter.java b/main/src/cgeo/geocaching/utils/Formatter.java index 9242b9a..3068cd4 100644 --- a/main/src/cgeo/geocaching/ui/Formatter.java +++ b/main/src/cgeo/geocaching/utils/Formatter.java @@ -1,4 +1,4 @@ -package cgeo.geocaching.ui; +package cgeo.geocaching.utils; import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.Geocache; @@ -121,7 +121,7 @@ public abstract class Formatter { } public static String formatCacheInfoLong(Geocache cache, CacheListType cacheListType) { - final ArrayList<String> infos = new ArrayList<String>(); + final ArrayList<String> infos = new ArrayList<>(); if (StringUtils.isNotBlank(cache.getGeocode())) { infos.add(cache.getGeocode()); } @@ -138,7 +138,7 @@ public abstract class Formatter { } public static String formatCacheInfoShort(Geocache cache) { - final ArrayList<String> infos = new ArrayList<String>(); + final ArrayList<String> infos = new ArrayList<>(); addShortInfos(cache, infos); return StringUtils.join(infos, Formatter.SEPARATOR); } @@ -163,7 +163,7 @@ public abstract class Formatter { } public static String formatCacheInfoHistory(Geocache cache) { - final ArrayList<String> infos = new ArrayList<String>(3); + final ArrayList<String> infos = new ArrayList<>(3); infos.add(StringUtils.upperCase(cache.getGeocode())); infos.add(Formatter.formatDate(cache.getVisitedDate())); infos.add(Formatter.formatTime(cache.getVisitedDate())); @@ -171,7 +171,7 @@ public abstract class Formatter { } public static String formatWaypointInfo(Waypoint waypoint) { - final List<String> infos = new ArrayList<String>(3); + final List<String> infos = new ArrayList<>(3); WaypointType waypointType = waypoint.getWaypointType(); if (waypointType != WaypointType.OWN && waypointType != null) { infos.add(waypointType.getL10n()); diff --git a/main/src/cgeo/geocaching/utils/HtmlUtils.java b/main/src/cgeo/geocaching/utils/HtmlUtils.java index 37e20ec..51c4d6e 100644 --- a/main/src/cgeo/geocaching/utils/HtmlUtils.java +++ b/main/src/cgeo/geocaching/utils/HtmlUtils.java @@ -34,7 +34,7 @@ public final class HtmlUtils { if (html instanceof Spanned) { Spanned text = (Spanned) html; Object[] styles = text.getSpans(0, text.length(), Object.class); - ArrayList<Pair<Integer, Integer>> removals = new ArrayList<Pair<Integer, Integer>>(); + ArrayList<Pair<Integer, Integer>> removals = new ArrayList<>(); for (Object style : styles) { if (style instanceof ImageSpan) { int start = text.getSpanStart(style); diff --git a/main/src/cgeo/geocaching/utils/ImageUtils.java b/main/src/cgeo/geocaching/utils/ImageUtils.java index ffb7bf3..739ecc4 100644 --- a/main/src/cgeo/geocaching/utils/ImageUtils.java +++ b/main/src/cgeo/geocaching/utils/ImageUtils.java @@ -1,6 +1,7 @@ package cgeo.geocaching.utils; import cgeo.geocaching.CgeoApplication; +import cgeo.geocaching.R; import cgeo.geocaching.compatibility.Compatibility; import org.apache.commons.io.IOUtils; @@ -8,17 +9,25 @@ import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; +import rx.Observable; +import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Action1; + +import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; import android.media.ExifInterface; import android.net.Uri; import android.os.Environment; import android.util.Base64; import android.util.Base64InputStream; +import android.widget.TextView; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; @@ -288,4 +297,48 @@ public final class ImageUtils { IOUtils.closeQuietly(in); } } + + public static BitmapDrawable getTransparent1x1Drawable(final Resources res) { + return new BitmapDrawable(res, BitmapFactory.decodeResource(res, R.drawable.image_no_placement)); + } + + /** + * Container which can hold a drawable (initially an empty one) and get a newer version when it + * becomes available. It also invalidates the view the container belongs to, so that it is + * redrawn properly. + */ + @SuppressWarnings("deprecation") + public final static class ContainerDrawable extends BitmapDrawable implements Action1<Drawable> { + private Drawable drawable; + final private TextView view; + + public ContainerDrawable(@NonNull final TextView view) { + this.view = view; + drawable = null; + setBounds(0, 0, 0, 0); + } + + public ContainerDrawable(@NonNull final TextView view, final Observable<? extends Drawable> drawableObservable) { + this(view); + updateFrom(drawableObservable); + } + + @Override + public void draw(final Canvas canvas) { + if (drawable != null) { + drawable.draw(canvas); + } + } + + @Override + public void call(final Drawable newDrawable) { + setBounds(0, 0, newDrawable.getIntrinsicWidth(), newDrawable.getIntrinsicHeight()); + drawable = newDrawable; + view.setText(view.getText()); + } + + public void updateFrom(final Observable<? extends Drawable> drawableObservable) { + drawableObservable.observeOn(AndroidSchedulers.mainThread()).subscribe(this); + } + } } diff --git a/main/src/cgeo/geocaching/utils/LazyInitializedList.java b/main/src/cgeo/geocaching/utils/LazyInitializedList.java index dedc96e..b0e2e46 100644 --- a/main/src/cgeo/geocaching/utils/LazyInitializedList.java +++ b/main/src/cgeo/geocaching/utils/LazyInitializedList.java @@ -65,7 +65,7 @@ public abstract class LazyInitializedList<ElementType> extends AbstractList<Elem @Override public void clear() { - list = new ArrayList<ElementType>(); + list = new ArrayList<>(); } } diff --git a/main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java b/main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java index 259e94a..a69f427 100644 --- a/main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java +++ b/main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java @@ -31,11 +31,11 @@ public class LeastRecentlyUsedSet<E> extends AbstractSet<E> public LeastRecentlyUsedSet(int maxEntries, int initialCapacity, float loadFactor) { // because we don't use any Map.get() methods from the Set, BOUNDED and LRU_CACHE have the exact same Behaviour // So we use LRU_CACHE mode because it should perform a bit better (as it doesn't re-add explicitly) - map = new LeastRecentlyUsedMap.LruCache<E, Object>(maxEntries, initialCapacity, loadFactor); + map = new LeastRecentlyUsedMap.LruCache<>(maxEntries, initialCapacity, loadFactor); } public LeastRecentlyUsedSet(int maxEntries) { - map = new LeastRecentlyUsedMap.LruCache<E, Object>(maxEntries); + map = new LeastRecentlyUsedMap.LruCache<>(maxEntries); } /** @@ -157,7 +157,7 @@ public class LeastRecentlyUsedSet<E> extends AbstractSet<E> * @return List based clone of the set */ public synchronized List<E> getAsList() { - return new ArrayList<E>(this); + return new ArrayList<>(this); } /** @@ -200,7 +200,7 @@ public class LeastRecentlyUsedSet<E> extends AbstractSet<E> final float loadFactor = s.readFloat(); final int maxEntries = s.readInt(); - map = new LeastRecentlyUsedMap.LruCache<E, Object>(maxEntries, capacity, loadFactor); + map = new LeastRecentlyUsedMap.LruCache<>(maxEntries, capacity, loadFactor); // Read in size final int size = s.readInt(); diff --git a/main/src/cgeo/geocaching/utils/Log.java b/main/src/cgeo/geocaching/utils/Log.java index 8f96f10..f338a8e 100644 --- a/main/src/cgeo/geocaching/utils/Log.java +++ b/main/src/cgeo/geocaching/utils/Log.java @@ -16,6 +16,10 @@ public final class Log { private static final String TAG = "cgeo"; + private static final class StackTraceDebug extends RuntimeException { + final static private long serialVersionUID = 27058374L; + } + /** * The debug flag is cached here so that we don't need to access the settings every time we have to evaluate it. */ @@ -119,4 +123,17 @@ public final class Log { IOUtils.closeQuietly(writer); } } + + /** + * Log a debug message with the actual stack trace. + * + * @param msg the debug message + */ + public static void logStackTrace(final String msg) { + try { + throw new StackTraceDebug(); + } catch (final StackTraceDebug dbg) { + Log.d(msg, dbg); + } + } } diff --git a/main/src/cgeo/geocaching/utils/LogTemplateProvider.java b/main/src/cgeo/geocaching/utils/LogTemplateProvider.java index 5fa0982..ff4013c 100644 --- a/main/src/cgeo/geocaching/utils/LogTemplateProvider.java +++ b/main/src/cgeo/geocaching/utils/LogTemplateProvider.java @@ -8,7 +8,6 @@ import cgeo.geocaching.connector.ConnectorFactory; import cgeo.geocaching.connector.IConnector; import cgeo.geocaching.connector.capability.ILogin; import cgeo.geocaching.settings.Settings; -import cgeo.geocaching.ui.Formatter; import org.apache.commons.lang3.StringUtils; @@ -36,9 +35,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 +46,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 +103,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<>(); templates.add(new LogTemplate("DATE", R.string.init_signature_template_date) { @Override @@ -132,6 +134,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 +180,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 +193,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 +208,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 +222,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 +233,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 +265,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/MapUtils.java b/main/src/cgeo/geocaching/utils/MapUtils.java new file mode 100644 index 0000000..948df77 --- /dev/null +++ b/main/src/cgeo/geocaching/utils/MapUtils.java @@ -0,0 +1,126 @@ +package cgeo.geocaching.utils; + +import cgeo.geocaching.Geocache; +import cgeo.geocaching.R; + +import org.apache.commons.lang3.builder.HashCodeBuilder; + +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.util.SparseArray; + +import java.util.ArrayList; + +public final class MapUtils { + + // data for overlays + private static final int[][] INSET_RELIABLE = { { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } }; // center, 33x40 / 45x51 / 60x68 + private static final int[][] INSET_TYPE = { { 5, 8, 6, 10 }, { 4, 4, 5, 11 }, { 4, 4, 5, 11 } }; // center, 22x22 / 36x36 + private static final int[][] INSET_OWN = { { 21, 0, 0, 26 }, { 25, 0, 0, 35 }, { 40, 0, 0, 48 } }; // top right, 12x12 / 16x16 / 20x20 + private static final int[][] INSET_FOUND = { { 0, 0, 21, 28 }, { 0, 0, 25, 35 }, { 0, 0, 40, 48 } }; // top left, 12x12 / 16x16 / 20x20 + 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 static final SparseArray<LayerDrawable> overlaysCache = new SparseArray<>(); + + private MapUtils() { + // Do not instantiate + } + + /** + * Build the drawable for a given cache. + * + * @param res the resources to use + * @param cache the cache to build the drawable for + * @return a drawable representing the current cache status + */ + public static LayerDrawable getCacheItem(final Resources res, final Geocache cache) { + final int hashcode = new HashCodeBuilder() + .append(cache.isReliableLatLon()) + .append(cache.getType().id) + .append(cache.isDisabled() || cache.isArchived()) + .append(cache.getMapMarkerId()) + .append(cache.isOwner()) + .append(cache.isFound()) + .append(cache.hasUserModifiedCoords()) + .append(cache.getPersonalNote()) + .append(cache.isLogOffline()) + .append(cache.getListId() > 0) + .toHashCode(); + + synchronized (overlaysCache) { + LayerDrawable drawable = overlaysCache.get(hashcode); + if (drawable == null) { + drawable = MapUtils.createCacheItem(res, cache); + overlaysCache.put(hashcode, drawable); + } + return drawable; + } + } + + /** + * Clear the cache of drawable items. + */ + public static void clearCachedItems() { + synchronized (overlaysCache) { + overlaysCache.clear(); + } + } + + private static LayerDrawable createCacheItem(final Resources res, final Geocache cache) { + // Set initial capacities to the maximum of layers and insets to avoid dynamic reallocation + final ArrayList<Drawable> layers = new ArrayList<>(9); + final ArrayList<int[]> insets = new ArrayList<>(8); + + // background: disabled or not + final Drawable marker = res.getDrawable(cache.getMapMarkerId()); + layers.add(marker); + final int resolution = marker.getIntrinsicWidth() > 40 ? (marker.getIntrinsicWidth() > 50 ? 2 : 1) : 0; + // reliable or not + if (!cache.isReliableLatLon()) { + insets.add(INSET_RELIABLE[resolution]); + layers.add(res.getDrawable(R.drawable.marker_notreliable)); + } + // cache type + layers.add(res.getDrawable(cache.getType().markerId)); + insets.add(INSET_TYPE[resolution]); + // own + if (cache.isOwner()) { + layers.add(res.getDrawable(R.drawable.marker_own)); + insets.add(INSET_OWN[resolution]); + // if not, checked if stored + } else if (cache.getListId() > 0) { + layers.add(res.getDrawable(R.drawable.marker_stored)); + insets.add(INSET_OWN[resolution]); + } + // found + if (cache.isFound()) { + layers.add(res.getDrawable(R.drawable.marker_found)); + insets.add(INSET_FOUND[resolution]); + // if not, perhaps logged offline + } else if (cache.isLogOffline()) { + layers.add(res.getDrawable(R.drawable.marker_found_offline)); + insets.add(INSET_FOUND[resolution]); + } + // user modified coords + if (cache.hasUserModifiedCoords()) { + layers.add(res.getDrawable(R.drawable.marker_usermodifiedcoords)); + insets.add(INSET_USERMODIFIEDCOORDS[resolution]); + } + // personal note + if (cache.getPersonalNote() != null) { + layers.add(res.getDrawable(R.drawable.marker_personalnote)); + insets.add(INSET_PERSONALNOTE[resolution]); + } + + final LayerDrawable ld = new LayerDrawable(layers.toArray(new Drawable[layers.size()])); + + int index = 1; + for (final int[] inset : insets) { + ld.setLayerInset(index++, inset[0], inset[1], inset[2], inset[3]); + } + + return ld; + } +} diff --git a/main/src/cgeo/geocaching/utils/OOMDumpingUncaughtExceptionHandler.java b/main/src/cgeo/geocaching/utils/OOMDumpingUncaughtExceptionHandler.java new file mode 100644 index 0000000..1401542 --- /dev/null +++ b/main/src/cgeo/geocaching/utils/OOMDumpingUncaughtExceptionHandler.java @@ -0,0 +1,79 @@ +package cgeo.geocaching.utils; + +import android.os.Environment; + +import java.io.IOException; +import java.lang.Thread.UncaughtExceptionHandler; + +public class OOMDumpingUncaughtExceptionHandler implements UncaughtExceptionHandler { + + private UncaughtExceptionHandler defaultHandler = null; + private boolean defaultReplaced = false; + + public static boolean activateHandler() { + + final OOMDumpingUncaughtExceptionHandler handler = new OOMDumpingUncaughtExceptionHandler(); + + return handler.activate(); + } + + private boolean activate() { + + defaultHandler = Thread.getDefaultUncaughtExceptionHandler(); + + // replace default handler if that has not been done already + if (!(defaultHandler instanceof OOMDumpingUncaughtExceptionHandler)) { + Thread.setDefaultUncaughtExceptionHandler(this); + defaultReplaced = true; + } else { + defaultHandler = null; + defaultReplaced = false; + } + + return defaultReplaced; + } + + public static boolean resetToDefault() { + + boolean defaultResetted = false; + + final UncaughtExceptionHandler unspecificHandler = Thread.getDefaultUncaughtExceptionHandler(); + + if (unspecificHandler instanceof OOMDumpingUncaughtExceptionHandler) { + final OOMDumpingUncaughtExceptionHandler handler = (OOMDumpingUncaughtExceptionHandler) unspecificHandler; + defaultResetted = handler.reset(); + } + + return defaultResetted; + } + + private boolean reset() { + + final boolean resetted = defaultReplaced; + + if (defaultReplaced) { + Thread.setDefaultUncaughtExceptionHandler(defaultHandler); + defaultReplaced = false; + } + + return resetted; + } + + @Override + public void uncaughtException(final Thread thread, final 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 (final IOException e) { + Log.e("Error writing dump", e); + } + } + defaultHandler.uncaughtException(thread, ex); + } +} diff --git a/main/src/cgeo/geocaching/utils/RxUtils.java b/main/src/cgeo/geocaching/utils/RxUtils.java index 8e7864c..241ba78 100644 --- a/main/src/cgeo/geocaching/utils/RxUtils.java +++ b/main/src/cgeo/geocaching/utils/RxUtils.java @@ -1,6 +1,8 @@ package cgeo.geocaching.utils; +import rx.Observable; import rx.Scheduler; +import rx.observables.BlockingObservable; import rx.schedulers.Schedulers; import java.util.concurrent.LinkedBlockingQueue; @@ -12,7 +14,15 @@ 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 final Scheduler networkScheduler = Schedulers.from(new ThreadPoolExecutor(10, 10, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>())); + + public static <T> void waitForCompletion(final BlockingObservable<T> observable) { + observable.lastOrDefault(null); + } + + public static void waitForCompletion(final Observable<?>... observables) { + waitForCompletion(Observable.merge(observables).toBlocking()); + } } 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/SimpleCancellableHandler.java b/main/src/cgeo/geocaching/utils/SimpleCancellableHandler.java index 22cd4d7..eee71ba 100644 --- a/main/src/cgeo/geocaching/utils/SimpleCancellableHandler.java +++ b/main/src/cgeo/geocaching/utils/SimpleCancellableHandler.java @@ -15,8 +15,8 @@ public class SimpleCancellableHandler extends CancellableHandler { protected final WeakReference<Progress> progressDialogRef; public SimpleCancellableHandler(final AbstractActivity activity, final Progress progress) { - this.activityRef = new WeakReference<AbstractActivity>(activity); - this.progressDialogRef = new WeakReference<Progress>(progress); + this.activityRef = new WeakReference<>(activity); + this.progressDialogRef = new WeakReference<>(progress); } @Override diff --git a/main/src/cgeo/geocaching/utils/SimpleHandler.java b/main/src/cgeo/geocaching/utils/SimpleHandler.java index 8e0a479..7c5ac43 100644 --- a/main/src/cgeo/geocaching/utils/SimpleHandler.java +++ b/main/src/cgeo/geocaching/utils/SimpleHandler.java @@ -14,8 +14,8 @@ public abstract class SimpleHandler extends Handler { protected final WeakReference<Progress> progressDialogRef; public SimpleHandler(final AbstractActivity activity, final Progress progress) { - activityRef = new WeakReference<AbstractActivity>(activity); - progressDialogRef = new WeakReference<Progress>(progress); + activityRef = new WeakReference<>(activity); + progressDialogRef = new WeakReference<>(progress); } @Override diff --git a/main/src/cgeo/geocaching/utils/TextUtils.java b/main/src/cgeo/geocaching/utils/TextUtils.java index ef09f32..77aa167 100644 --- a/main/src/cgeo/geocaching/utils/TextUtils.java +++ b/main/src/cgeo/geocaching/utils/TextUtils.java @@ -4,11 +4,13 @@ package cgeo.geocaching.utils; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + import org.eclipse.jdt.annotation.Nullable; import java.nio.charset.Charset; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.zip.CRC32; /** * Misc. utils. All methods don't use Android specific stuff to use these methods in plain JUnit tests. @@ -52,7 +54,7 @@ public final class TextUtils { result = matcher.group(group); } if (null != result) { - Matcher remover = PATTERN_REMOVE_NONPRINTABLE.matcher(result); + final Matcher remover = PATTERN_REMOVE_NONPRINTABLE.matcher(result); result = remover.replaceAll(" "); return trim ? new String(result).trim() : new String(result); @@ -134,7 +136,7 @@ public final class TextUtils { data.getChars(0, length, chars, 0); int resultSize = 0; boolean lastWasWhitespace = true; - for (char c : chars) { + for (final char c : chars) { if (c == ' ' || c == '\n' || c == '\r' || c == '\t') { if (!lastWasWhitespace) { chars[resultSize++] = ' '; @@ -167,8 +169,20 @@ public final class TextUtils { * @return */ public static String removeControlCharacters(final String input) { - Matcher remover = PATTERN_REMOVE_NONPRINTABLE.matcher(input); + final Matcher remover = PATTERN_REMOVE_NONPRINTABLE.matcher(input); return remover.replaceAll(" ").trim(); } + /** + * Calculate a simple checksum for change-checking (not usable for security/cryptography!) + * + * @param input + * String to check + * @return resulting checksum + */ + public static long checksum(final String input) { + final CRC32 checksum = new CRC32(); + checksum.update(input.getBytes()); + return checksum.getValue(); + } } 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)))); } } |