From b4d77cf2b3fac29432f8dde0f745723c9faaac86 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Sun, 9 Dec 2018 15:45:45 -0200 Subject: [PATCH] Directions Form: Move overflow menu items into UI --- .../transportr/settings/SettingsManager.kt | 5 - .../trips/search/DirectionsFragment.java | 372 ------------------ .../trips/search/DirectionsFragment.kt | 294 ++++++++++++++ .../trips/search/DirectionsViewModel.java | 5 + ...ic_action_navigation_unfold_less_white.xml | 9 + ...ic_action_navigation_unfold_more_white.xml | 9 + app/src/main/res/drawable/ic_trip_arrival.xml | 9 + .../main/res/drawable/ic_trip_departure.xml | 9 + .../res/layout/fragment_directions_form.xml | 144 ++++--- app/src/main/res/menu/directions.xml | 41 +- artwork/ic_trip_arrival.svg | 58 +++ artwork/ic_trip_departure.svg | 60 +++ 12 files changed, 550 insertions(+), 465 deletions(-) delete mode 100644 app/src/main/java/de/grobox/transportr/trips/search/DirectionsFragment.java create mode 100644 app/src/main/java/de/grobox/transportr/trips/search/DirectionsFragment.kt create mode 100644 app/src/main/res/drawable/ic_action_navigation_unfold_less_white.xml create mode 100644 app/src/main/res/drawable/ic_action_navigation_unfold_more_white.xml create mode 100644 app/src/main/res/drawable/ic_trip_arrival.xml create mode 100644 app/src/main/res/drawable/ic_trip_departure.xml create mode 100644 artwork/ic_trip_arrival.svg create mode 100644 artwork/ic_trip_departure.svg diff --git a/app/src/main/java/de/grobox/transportr/settings/SettingsManager.kt b/app/src/main/java/de/grobox/transportr/settings/SettingsManager.kt index d3e35a10c..e930f08f0 100644 --- a/app/src/main/java/de/grobox/transportr/settings/SettingsManager.kt +++ b/app/src/main/java/de/grobox/transportr/settings/SettingsManager.kt @@ -96,11 +96,6 @@ class SettingsManager @Inject constructor(private val context: Context) { settings.edit().putBoolean(TRIP_DETAIL_ONBOARDING, false).apply() } - fun showDirectionsOnboarding(): Boolean = settings.getBoolean(DIRECTIONS_ONBOARDING, true) - fun directionsOnboardingShown() { - settings.edit().putBoolean(DIRECTIONS_ONBOARDING, false).apply() - } - fun getNetworkId(i: Int): NetworkId? { var networkSettingsStr = NETWORK_ID_1 if (i == 2) networkSettingsStr = NETWORK_ID_2 diff --git a/app/src/main/java/de/grobox/transportr/trips/search/DirectionsFragment.java b/app/src/main/java/de/grobox/transportr/trips/search/DirectionsFragment.java deleted file mode 100644 index 90fce7e56..000000000 --- a/app/src/main/java/de/grobox/transportr/trips/search/DirectionsFragment.java +++ /dev/null @@ -1,372 +0,0 @@ -/* - * Transportr - * - * Copyright (c) 2013 - 2018 Torsten Grote - * - * This program is Free Software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package de.grobox.transportr.trips.search; - -import android.graphics.PorterDuff; -import android.graphics.drawable.Drawable; -import androidx.lifecycle.ViewModelProvider; -import androidx.lifecycle.ViewModelProviders; -import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; -import androidx.cardview.widget.CardView; -import androidx.appcompat.widget.Toolbar; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.View.OnLongClickListener; -import android.view.ViewGroup; -import android.view.animation.Animation; -import android.view.animation.TranslateAnimation; -import android.widget.ImageView; -import android.widget.TextView; - -import java.util.Calendar; - -import javax.annotation.ParametersAreNonnullByDefault; -import javax.inject.Inject; - -import de.grobox.transportr.R; -import de.grobox.transportr.TransportrFragment; -import de.grobox.transportr.data.locations.FavoriteLocation.FavLocationType; -import de.grobox.transportr.locations.LocationGpsView; -import de.grobox.transportr.locations.LocationView; -import de.grobox.transportr.locations.WrapLocation; -import de.grobox.transportr.networks.TransportNetwork; -import de.grobox.transportr.settings.SettingsManager; -import de.grobox.transportr.ui.TimeDateFragment; -import de.grobox.transportr.utils.DateUtils; -import de.grobox.transportr.utils.IconOnboardingBuilder; - -import static android.Manifest.permission.ACCESS_FINE_LOCATION; -import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static android.view.View.GONE; -import static android.view.View.VISIBLE; -import static android.view.animation.Animation.RELATIVE_TO_SELF; -import static de.grobox.transportr.data.locations.FavoriteLocation.FavLocationType.FROM; -import static de.grobox.transportr.data.locations.FavoriteLocation.FavLocationType.TO; -import static de.grobox.transportr.data.locations.FavoriteLocation.FavLocationType.VIA; -import static de.grobox.transportr.utils.Constants.DATE; -import static de.grobox.transportr.utils.Constants.DEPARTURE; -import static de.grobox.transportr.utils.Constants.EXPANDED; -import static de.grobox.transportr.utils.DateUtils.getDate; -import static de.grobox.transportr.utils.DateUtils.getTime; -import static de.grobox.transportr.utils.DateUtils.isNow; -import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_DISMISSED; -import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_FOCAL_PRESSED; - -@ParametersAreNonnullByDefault -public class DirectionsFragment extends TransportrFragment { - - @Inject SettingsManager settingsManager; - @Inject ViewModelProvider.Factory viewModelFactory; - - private Toolbar toolbar; - private @Nullable Menu menu; - private ImageView favIcon, timeIcon; - private TextView date, time; - private LocationGpsView from; - private LocationView via, to; - private CardView fromCard, viaCard, toCard; - - private DirectionsViewModel viewModel; - - @Override - public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View v = inflater.inflate(R.layout.fragment_directions_form, container, false); - getComponent().inject(this); - - setHasOptionsMenu(true); - toolbar = v.findViewById(R.id.toolbar); - favIcon = toolbar.findViewById(R.id.favIcon); - timeIcon = toolbar.findViewById(R.id.timeIcon); - date = toolbar.findViewById(R.id.date); - time = toolbar.findViewById(R.id.time); - - fromCard = v.findViewById(R.id.fromCard); - viaCard = v.findViewById(R.id.viaCard); - toCard = v.findViewById(R.id.toCard); - from = v.findViewById(R.id.fromLocation); - via = v.findViewById(R.id.viaLocation); - to = v.findViewById(R.id.toLocation); - - setUpToolbar(toolbar); - - viewModel = new ViewModelProvider(requireActivity(), viewModelFactory).get(DirectionsViewModel.class); - TransportNetwork network = viewModel.getTransportNetwork().getValue(); - if (network == null) throw new IllegalStateException(); - from.setTransportNetwork(network); - via.setTransportNetwork(network); - to.setTransportNetwork(network); - - from.setType(FROM); - via.setType(VIA); - to.setType(TO); - - viewModel.getHome().observe(getViewLifecycleOwner(), homeLocation -> { - from.setHomeLocation(homeLocation); - via.setHomeLocation(homeLocation); - to.setHomeLocation(homeLocation); - }); - viewModel.getWork().observe(getViewLifecycleOwner(), workLocation -> { - from.setWorkLocation(workLocation); - via.setWorkLocation(workLocation); - to.setWorkLocation(workLocation); - }); - viewModel.getLocations().observe(getViewLifecycleOwner(), favoriteLocations -> { - if (favoriteLocations == null) return; - from.setFavoriteLocations(favoriteLocations); - via.setFavoriteLocations(favoriteLocations); - to.setFavoriteLocations(favoriteLocations); - }); - viewModel.getFromLocation().observe(getViewLifecycleOwner(), location -> { - from.setLocation(location); - if (location != null) to.requestFocus(); - }); - viewModel.getViaLocation().observe(getViewLifecycleOwner(), location -> via.setLocation(location)); - viewModel.getToLocation().observe(getViewLifecycleOwner(), location -> to.setLocation(location)); - viewModel.getCalendar().observe(getViewLifecycleOwner(), this::onCalendarUpdated); - viewModel.findGpsLocation.observe(getViewLifecycleOwner(), this::onFindGpsLocation); - viewModel.isFavTrip().observe(getViewLifecycleOwner(), this::onFavStatusChanged); - - setupClickListeners(); - - return v; - } - - @Override - @SuppressWarnings("ConstantConditions") - public void onSaveInstanceState(@NonNull Bundle outState) { - super.onSaveInstanceState(outState); - outState.putSerializable(DATE, viewModel.getCalendar().getValue()); - outState.putBoolean(EXPANDED, viewModel.getIsExpanded().getValue()); - outState.putBoolean(DEPARTURE, viewModel.getIsDeparture().getValue()); - } - - @Override - public void onViewStateRestored(@Nullable Bundle savedInstanceState) { - super.onViewStateRestored(savedInstanceState); - if (savedInstanceState != null && viewModel.getTrips().getValue() == null) { - viewModel.setIsExpanded(savedInstanceState.getBoolean(EXPANDED, false)); - viewModel.setIsDeparture(savedInstanceState.getBoolean(DEPARTURE, true)); - viewModel.setFromLocation(from.getLocation()); - viewModel.setViaLocation(via.getLocation()); - viewModel.setToLocation(to.getLocation()); - viewModel.onTimeAndDateSet((Calendar) savedInstanceState.getSerializable(DATE)); - } - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - inflater.inflate(R.menu.directions, menu); - this.menu = menu; - viewModel.getIsDeparture().observe(this, this::onIsDepartureChanged); - viewModel.getIsExpanded().observe(this, this::onViaVisibleChanged); - super.onCreateOptionsMenu(menu, inflater); - - // onboarding for overflow menu - if (getActivity() != null && settingsManager.showDirectionsOnboarding()) { - new IconOnboardingBuilder(getActivity()) - .setTarget(toolbar.getChildAt(toolbar.getChildCount() - 1)) - .setPrimaryText(R.string.onboarding_directions_title) - .setSecondaryText(R.string.onboarding_directions_message) - .setIcon(R.drawable.ic_more_vert) - .setPromptStateChangeListener((prompt, state) -> { - if (state == STATE_DISMISSED || state == STATE_FOCAL_PRESSED) { - settingsManager.directionsOnboardingShown(); - } - }) - .show(); - } - - Drawable drawable = menu.findItem(R.id.action_swap_locations).getIcon(); - if (drawable != null) { - drawable.mutate(); - drawable.setColorFilter(0xffffffff, PorterDuff.Mode.SRC_ATOP); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.action_swap_locations: - swapLocations(); - return true; - case R.id.action_departure: - viewModel.setIsDeparture(true); - return true; - case R.id.action_arrival: - viewModel.setIsDeparture(false); - return true; - case R.id.action_navigation_expand: - viewModel.setIsExpanded(!item.isChecked()); - return true; - case R.id.action_choose_products: - new ProductDialogFragment().show(getActivity().getSupportFragmentManager(), ProductDialogFragment.TAG); - default: - return super.onOptionsItemSelected(item); - } - } - - private void setupClickListeners() { - favIcon.setVisibility(VISIBLE); - favIcon.setOnClickListener(view -> viewModel.toggleFavTrip()); - - OnClickListener onTimeClickListener = view -> { - if (viewModel.getCalendar().getValue() == null) throw new IllegalStateException(); - TimeDateFragment fragment = TimeDateFragment.newInstance(viewModel.getCalendar().getValue()); - fragment.setTimeDateListener(viewModel); - fragment.show(getActivity().getSupportFragmentManager(), TimeDateFragment.TAG); - }; - OnLongClickListener onTimeLongClickListener = view -> { - viewModel.resetCalender(); - return true; - }; - - timeIcon.setOnClickListener(onTimeClickListener); - date.setOnClickListener(onTimeClickListener); - time.setOnClickListener(onTimeClickListener); - - timeIcon.setOnLongClickListener(onTimeLongClickListener); - date.setOnLongClickListener(onTimeLongClickListener); - time.setOnLongClickListener(onTimeLongClickListener); - - from.setLocationViewListener(viewModel); - via.setLocationViewListener(viewModel); - to.setLocationViewListener(viewModel); - } - - private void onCalendarUpdated(@Nullable Calendar calendar) { - if (calendar == null) return; - if (isNow(calendar)) { - time.setText(R.string.now); - date.setVisibility(GONE); - } else if (DateUtils.isToday(calendar)) { - time.setText(getTime(getContext(), calendar.getTime())); - date.setVisibility(GONE); - } else { - time.setText(getTime(getContext(), calendar.getTime())); - date.setText(getDate(getContext(), calendar.getTime())); - date.setVisibility(VISIBLE); - } - } - - private void swapLocations() { - float toToY = fromCard.getY() - toCard.getY(); - Animation slideUp = new TranslateAnimation(RELATIVE_TO_SELF, 0.0f, RELATIVE_TO_SELF, 0.0f, RELATIVE_TO_SELF, - 0.0f, Animation.ABSOLUTE, toToY); - slideUp.setDuration(400); - slideUp.setFillAfter(true); - slideUp.setFillEnabled(true); - - float fromToY = toCard.getY() - fromCard.getY(); - Animation slideDown = new TranslateAnimation(RELATIVE_TO_SELF, 0.0f, RELATIVE_TO_SELF, 0.0f, RELATIVE_TO_SELF, - 0.0f, Animation.ABSOLUTE, fromToY); - slideDown.setDuration(400); - slideDown.setFillAfter(true); - slideDown.setFillEnabled(true); - - fromCard.startAnimation(slideDown); - toCard.startAnimation(slideUp); - - slideUp.setAnimationListener(new Animation.AnimationListener() { - @Override - public void onAnimationStart(Animation animation) { - } - - @Override - public void onAnimationRepeat(Animation animation) { - } - - @Override - public void onAnimationEnd(Animation animation) { - // swap location objects - WrapLocation tmp = to.getLocation(); - if (from.isSearching()) { - viewModel.findGpsLocation.setValue(null); - // TODO: GPS currently only supports from location, so don't swap it for now - viewModel.setToLocation(null); - } else { - viewModel.setToLocation(from.getLocation()); - } - viewModel.setFromLocation(tmp); - - fromCard.clearAnimation(); - toCard.clearAnimation(); - - viewModel.search(); - } - }); - } - - private void onIsDepartureChanged(boolean isDeparture) { - if (menu == null) throw new IllegalStateException("Menu is null"); - if (isDeparture) { - MenuItem departureItem = menu.findItem(R.id.action_departure); - departureItem.setChecked(true); - } else { - MenuItem arrivalItem = menu.findItem(R.id.action_arrival); - arrivalItem.setChecked(true); - } - } - - private void onViaVisibleChanged(boolean viaVisible) { - if (menu == null) throw new IllegalStateException("Menu is null"); - MenuItem viaItem = menu.findItem(R.id.action_navigation_expand); - viaItem.setChecked(viaVisible); - viaCard.setVisibility(viaVisible ? VISIBLE : GONE); - } - - private void onFindGpsLocation(@Nullable FavLocationType type) { - if (type == null) { - viewModel.locationLiveData.removeObservers(DirectionsFragment.this); - from.clearSearching(); - return; - } - if (ContextCompat.checkSelfPermission(getContext(), ACCESS_FINE_LOCATION) != PERMISSION_GRANTED) { - return; - } - from.setSearching(); - to.requestFocus(); - viewModel.locationLiveData.observe(this, location -> { - viewModel.setFromLocation(location); - viewModel.search(); - viewModel.locationLiveData.removeObservers(DirectionsFragment.this); - }); - } - - private void onFavStatusChanged(@Nullable Boolean isFav) { - if (isFav == null) { - favIcon.setVisibility(GONE); - } else { - favIcon.setVisibility(VISIBLE); - if (isFav) { - favIcon.setImageResource(R.drawable.ic_action_star); - } else { - favIcon.setImageResource(R.drawable.ic_action_star_empty); - } - } - } - -} diff --git a/app/src/main/java/de/grobox/transportr/trips/search/DirectionsFragment.kt b/app/src/main/java/de/grobox/transportr/trips/search/DirectionsFragment.kt new file mode 100644 index 000000000..a7b2ef78e --- /dev/null +++ b/app/src/main/java/de/grobox/transportr/trips/search/DirectionsFragment.kt @@ -0,0 +1,294 @@ +/* + * Transportr + * + * Copyright (c) 2013 - 2018 Torsten Grote + * + * This program is Free Software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.grobox.transportr.trips.search + +import android.Manifest.permission.ACCESS_FINE_LOCATION +import android.content.pm.PackageManager.PERMISSION_GRANTED +import android.os.Bundle +import android.view.* +import android.view.View.* +import android.view.animation.Animation +import android.view.animation.Animation.RELATIVE_TO_SELF +import android.view.animation.TranslateAnimation +import androidx.core.content.ContextCompat +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.ViewModelProviders +import de.grobox.transportr.R +import de.grobox.transportr.TransportrFragment +import de.grobox.transportr.data.locations.FavoriteLocation.FavLocationType +import de.grobox.transportr.data.locations.FavoriteLocation.FavLocationType.FROM +import de.grobox.transportr.data.locations.FavoriteLocation.FavLocationType.TO +import de.grobox.transportr.data.locations.FavoriteLocation.FavLocationType.VIA +import de.grobox.transportr.settings.SettingsManager +import de.grobox.transportr.ui.TimeDateFragment +import de.grobox.transportr.utils.Constants.DATE +import de.grobox.transportr.utils.Constants.DEPARTURE +import de.grobox.transportr.utils.Constants.EXPANDED +import de.grobox.transportr.utils.DateUtils +import de.grobox.transportr.utils.DateUtils.* +import kotlinx.android.synthetic.main.fragment_directions_form.* +import java.util.* +import javax.annotation.ParametersAreNonnullByDefault +import javax.inject.Inject + +@ParametersAreNonnullByDefault +class DirectionsFragment : TransportrFragment() { + + @Inject + internal lateinit var settingsManager: SettingsManager + @Inject + internal lateinit var viewModelFactory: ViewModelProvider.Factory + + private lateinit var viewModel: DirectionsViewModel + private var expandItem: MenuItem? = null + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + val v = inflater.inflate(R.layout.fragment_directions_form, container, false) + component.inject(this) + + setHasOptionsMenu(true) + + viewModel = ViewModelProviders.of(activity!!, viewModelFactory).get(DirectionsViewModel::class.java) + + return v + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setUpToolbar(toolbar) + + val network = viewModel.transportNetwork.value ?: throw IllegalStateException() + fromLocation.setTransportNetwork(network) + viaLocation.setTransportNetwork(network) + toLocation.setTransportNetwork(network) + + fromLocation.type = FROM + viaLocation.type = VIA + toLocation.type = TO + + fromLocation.setLocationViewListener(viewModel) + viaLocation.setLocationViewListener(viewModel) + toLocation.setLocationViewListener(viewModel) + + viewModel.home.observe(this, Observer { homeLocation -> + fromLocation.setHomeLocation(homeLocation) + viaLocation.setHomeLocation(homeLocation) + toLocation.setHomeLocation(homeLocation) + }) + viewModel.work.observe(this, Observer { workLocation -> + fromLocation.setWorkLocation(workLocation) + viaLocation.setWorkLocation(workLocation) + toLocation.setWorkLocation(workLocation) + }) + viewModel.locations.observe(this, Observer { favoriteLocations -> + if (favoriteLocations == null) return@Observer + fromLocation.setFavoriteLocations(favoriteLocations) + viaLocation.setFavoriteLocations(favoriteLocations) + toLocation.setFavoriteLocations(favoriteLocations) + }) + viewModel.fromLocation.observe(this, Observer { location -> + fromLocation.setLocation(location) + if (location != null) toLocation.requestFocus() + }) + viewModel.viaLocation.observe(this, Observer { location -> viaLocation.setLocation(location) }) + viewModel.toLocation.observe(this, Observer { location -> toLocation.setLocation(location) }) + viewModel.isDeparture.observe(this, Observer { this.onIsDepartureChanged(it) }) + viewModel.isExpanded.observe(this, Observer { this.onViaVisibleChanged(it) }) + viewModel.calendar.observe(this, Observer { this.onCalendarUpdated(it) }) + viewModel.findGpsLocation.observe(this, Observer { this.onFindGpsLocation(it) }) + viewModel.isFavTrip.observe(this, Observer { this.onFavStatusChanged(it) }) + + favIcon.visibility = VISIBLE + favIcon.setOnClickListener { viewModel.toggleFavTrip() } + + departureIcon.setOnClickListener { viewModel.toggleDeparture() } + + val onTimeClickListener = OnClickListener { + if (viewModel.calendar.value == null) throw IllegalStateException() + val fragment = TimeDateFragment.newInstance(viewModel.calendar.value!!) + fragment.setTimeDateListener(viewModel) + fragment.show(activity!!.supportFragmentManager, TimeDateFragment.TAG) + } + val onTimeLongClickListener = OnLongClickListener { + viewModel.resetCalender() + true + } + + timeIcon.setOnClickListener(onTimeClickListener) + date.setOnClickListener(onTimeClickListener) + time.setOnClickListener(onTimeClickListener) + + timeIcon.setOnLongClickListener(onTimeLongClickListener) + date.setOnLongClickListener(onTimeLongClickListener) + time.setOnLongClickListener(onTimeLongClickListener) + + productsIcon.setOnClickListener { + activity?.let { a -> + ProductDialogFragment().show(a.supportFragmentManager, ProductDialogFragment.TAG) + } + } + swapIcon.setOnClickListener { swapLocations() } + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putSerializable(DATE, viewModel.calendar.value) + outState.putBoolean(EXPANDED, viewModel.isExpanded.value ?: false) + outState.putBoolean(DEPARTURE, viewModel.isDeparture.value ?: true) + } + + override fun onViewStateRestored(savedInstanceState: Bundle?) { + super.onViewStateRestored(savedInstanceState) + if (savedInstanceState != null && viewModel.trips.value == null) { + viewModel.setIsExpanded(savedInstanceState.getBoolean(EXPANDED, false)) + viewModel.setIsDeparture(savedInstanceState.getBoolean(DEPARTURE, true)) + viewModel.setFromLocation(fromLocation.getLocation()) + viewModel.setViaLocation(viaLocation.getLocation()) + viewModel.setToLocation(toLocation.getLocation()) + viewModel.onTimeAndDateSet(savedInstanceState.getSerializable(DATE) as Calendar) + } + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.directions, menu) + expandItem = menu.findItem(R.id.action_navigation_expand) + super.onCreateOptionsMenu(menu, inflater) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.action_navigation_expand -> { + viewModel.setIsExpanded(!item.isChecked) + true + } + else -> super.onOptionsItemSelected(item) + } + } + + private fun onCalendarUpdated(calendar: Calendar?) { + if (calendar == null) return + when { + isNow(calendar) -> { + time.setText(R.string.now) + date.visibility = GONE + } + DateUtils.isToday(calendar) -> { + time.text = getTime(context, calendar.time) + date.visibility = GONE + } + else -> { + time.text = getTime(context, calendar.time) + date.text = getDate(context, calendar.time) + date.visibility = VISIBLE + } + } + } + + private fun swapLocations() { + val toToY = fromCard.y - toCard.y + val slideUp = TranslateAnimation( + RELATIVE_TO_SELF, 0.0f, RELATIVE_TO_SELF, 0.0f, RELATIVE_TO_SELF, + 0.0f, Animation.ABSOLUTE, toToY + ) + slideUp.duration = 400 + slideUp.fillAfter = true + slideUp.isFillEnabled = true + + val fromToY = toCard.y - fromCard.y + val slideDown = TranslateAnimation( + RELATIVE_TO_SELF, 0.0f, RELATIVE_TO_SELF, 0.0f, RELATIVE_TO_SELF, + 0.0f, Animation.ABSOLUTE, fromToY + ) + slideDown.duration = 400 + slideDown.fillAfter = true + slideDown.isFillEnabled = true + + fromCard.startAnimation(slideDown) + toCard.startAnimation(slideUp) + + slideUp.setAnimationListener(object : Animation.AnimationListener { + override fun onAnimationStart(animation: Animation) {} + + override fun onAnimationRepeat(animation: Animation) {} + + override fun onAnimationEnd(animation: Animation) { + // swap location objects + val tmp = toLocation.getLocation() + if (fromLocation.isSearching) { + viewModel.findGpsLocation.setValue(null) + // TODO: GPS currently only supports from location, so don't swap it for now + viewModel.setToLocation(null) + } else { + viewModel.setToLocation(fromLocation.getLocation()) + } + viewModel.setFromLocation(tmp) + + fromCard.clearAnimation() + toCard.clearAnimation() + + viewModel.search() + } + }) + } + + private fun onIsDepartureChanged(isDeparture: Boolean) { + departureIcon.setImageResource(if (isDeparture) R.drawable.ic_trip_departure else R.drawable.ic_trip_arrival) + } + + private fun onViaVisibleChanged(viaVisible: Boolean) { + expandItem?.isChecked = viaVisible + expandItem?.setIcon(if (viaVisible) R.drawable.ic_action_navigation_unfold_less_white else R.drawable.ic_action_navigation_unfold_more_white) + viaCard.visibility = if (viaVisible) VISIBLE else GONE + } + + private fun onFindGpsLocation(type: FavLocationType?) { + if (type == null) { + viewModel.locationLiveData.removeObservers(this@DirectionsFragment) + fromLocation.clearSearching() + return + } + if (ContextCompat.checkSelfPermission(context, ACCESS_FINE_LOCATION) != PERMISSION_GRANTED) { + return + } + fromLocation.setSearching() + toLocation.requestFocus() + viewModel.locationLiveData.observe(this, Observer { location -> + viewModel.setFromLocation(location) + viewModel.search() + viewModel.locationLiveData.removeObservers(this@DirectionsFragment) + }) + } + + private fun onFavStatusChanged(isFav: Boolean?) { + if (isFav == null) { + favIcon.visibility = INVISIBLE + } else { + favIcon.visibility = VISIBLE + if (isFav) { + favIcon.setImageResource(R.drawable.ic_action_star) + } else { + favIcon.setImageResource(R.drawable.ic_action_star_empty) + } + } + } + +} diff --git a/app/src/main/java/de/grobox/transportr/trips/search/DirectionsViewModel.java b/app/src/main/java/de/grobox/transportr/trips/search/DirectionsViewModel.java index 3b0acd524..fb5dcee7f 100644 --- a/app/src/main/java/de/grobox/transportr/trips/search/DirectionsViewModel.java +++ b/app/src/main/java/de/grobox/transportr/trips/search/DirectionsViewModel.java @@ -164,6 +164,11 @@ LiveData getIsDeparture() { return isDeparture; } + void toggleDeparture() { + isDeparture.setValue(!(isDeparture.getValue() == null || isDeparture.getValue())); + search(); + } + void setIsDeparture(boolean departure) { isDeparture.setValue(departure); search(); diff --git a/app/src/main/res/drawable/ic_action_navigation_unfold_less_white.xml b/app/src/main/res/drawable/ic_action_navigation_unfold_less_white.xml new file mode 100644 index 000000000..6160ae372 --- /dev/null +++ b/app/src/main/res/drawable/ic_action_navigation_unfold_less_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_action_navigation_unfold_more_white.xml b/app/src/main/res/drawable/ic_action_navigation_unfold_more_white.xml new file mode 100644 index 000000000..46ba28c6d --- /dev/null +++ b/app/src/main/res/drawable/ic_action_navigation_unfold_more_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_trip_arrival.xml b/app/src/main/res/drawable/ic_trip_arrival.xml new file mode 100644 index 000000000..a7b88c3cf --- /dev/null +++ b/app/src/main/res/drawable/ic_trip_arrival.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_trip_departure.xml b/app/src/main/res/drawable/ic_trip_departure.xml new file mode 100644 index 000000000..0cd27a357 --- /dev/null +++ b/app/src/main/res/drawable/ic_trip_departure.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/fragment_directions_form.xml b/app/src/main/res/layout/fragment_directions_form.xml index 706190b11..9f32c3f2e 100644 --- a/app/src/main/res/layout/fragment_directions_form.xml +++ b/app/src/main/res/layout/fragment_directions_form.xml @@ -7,6 +7,7 @@ android:layout_height="match_parent" android:animateLayoutChanges="true" android:paddingBottom="8dp" + tools:context=".trips.search.DirectionsActivity" tools:showIn="@layout/activity_directions"> - - - - - - - - - + @@ -142,4 +96,90 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/menu/directions.xml b/app/src/main/res/menu/directions.xml index fcde4807c..b4a3ff6b6 100644 --- a/app/src/main/res/menu/directions.xml +++ b/app/src/main/res/menu/directions.xml @@ -1,44 +1,13 @@ - - - - - - - - - - - - - - + - - + app:showAsAction="always"/> diff --git a/artwork/ic_trip_arrival.svg b/artwork/ic_trip_arrival.svg new file mode 100644 index 000000000..bf2e9019c --- /dev/null +++ b/artwork/ic_trip_arrival.svg @@ -0,0 +1,58 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/artwork/ic_trip_departure.svg b/artwork/ic_trip_departure.svg new file mode 100644 index 000000000..f4563d1a8 --- /dev/null +++ b/artwork/ic_trip_departure.svg @@ -0,0 +1,60 @@ + + + + + + image/svg+xml + + + + + + + + + +