diff --git a/app/src/main/java/it/reyboz/bustorino/ActivityExperiments.java b/app/src/main/java/it/reyboz/bustorino/ActivityExperiments.java --- a/app/src/main/java/it/reyboz/bustorino/ActivityExperiments.java +++ b/app/src/main/java/it/reyboz/bustorino/ActivityExperiments.java @@ -76,9 +76,20 @@ } @Override - public void showMapCenteredOnStop(Stop stop) { + public void showMapCenteredOnStop(@Nullable Stop stop) { } + + @Override + public void openLinesFragment() { + Log.d(DEBUG_TAG, "Asked to open lines grid fragment"); + } + + @Override + public void openFavoritesFragment() { + + } + @Override public void openLineFromStop(String routeGtfsId, @Nullable String stopIDFrom){ @@ -101,4 +112,9 @@ tr.commit(); } + @Override + public void openNearbyStopsFragment() { + Log.d(DEBUG_TAG, "Requested to open nearby stops fragment"); + } + } \ No newline at end of file diff --git a/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java b/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java --- a/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java +++ b/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java @@ -242,21 +242,21 @@ Fragment f = getSupportFragmentManager().findFragmentById(R.id.mainActContentFrame); Log.d(DEBUG_TAG, "OnCreate the fragment is "+f); String vl = PreferenceManager.getDefaultSharedPreferences(this).getString(SettingsFragment.PREF_KEY_STARTUP_SCREEN, ""); - //if (vl.length() == 0 || vl.equals("arrivals")) { - // showMainFragment(); + Log.d(DEBUG_TAG, "The default screen to open is: "+vl); if (showingArrivalsFromIntent){ //do nothing but exclude a case }else if (savedInstanceState==null) { + var framan = getSupportFragmentManager(); //we are not restarting the activity from nothing if (vl.equals("map")) { requestMapFragment(false); } else if (vl.equals("favorites")) { - checkAndShowFavoritesFragment(getSupportFragmentManager(), false); + checkAndShowFavoritesFragment(framan, false); } else if (vl.equals("lines")) { - showLinesFragment(getSupportFragmentManager(), false, null); + showLinesFragment(framan, false, null); } else { - showMainFragment(false); + showMainFragmentFromClick(false); } } onCreateComplete = true; @@ -355,7 +355,7 @@ return true; } else if(menuItem.getItemId() == R.id.nav_arrivals){ closeDrawerIfOpen(); - showMainFragment(true); + showMainFragmentFromClick(true); return true; } else if(menuItem.getItemId() == R.id.nav_map_item){ closeDrawerIfOpen(); @@ -529,7 +529,7 @@ } /** - * Show the fragment by adding it to the backstack + * Show the actual fragment by adding it to the backstack * @param fraMan the fragmentManager * @param fragment the fragment */ @@ -548,11 +548,12 @@ ft.commit(); } /** - * Show the fragment by adding it to the backstack + * Create a new MainFragment for the arguments provided and show it in the layout * @param fraMan the fragmentManager * @param arguments args for the fragment */ private static void createShowMainFragment(FragmentManager fraMan,@Nullable Bundle arguments, boolean addToBackStack){ + //var frag = MainScreenFragment.newInstance(); FragmentTransaction ft = fraMan.beginTransaction() .replace(R.id.mainActContentFrame, MainScreenFragment.class, arguments, MainScreenFragment.FRAGMENT_TAG) .setReorderingAllowed(false) @@ -566,6 +567,27 @@ if (addToBackStack) ft.addToBackStack(null); ft.commit(); } + private void showMainFragmentFromClick(@Nullable Bundle argsToCreate, boolean addToBackStack){ + FragmentManager fraMan = getSupportFragmentManager(); + Fragment fragment = fraMan.findFragmentByTag(MainScreenFragment.FRAGMENT_TAG); + final MainScreenFragment mainScreenFragment; + if (fragment==null | !(fragment instanceof MainScreenFragment)){ + createShowMainFragment(fraMan, argsToCreate, addToBackStack); + } + else if(!fragment.isVisible()){ + + + mainScreenFragment = (MainScreenFragment) fragment; + showMainFragment(fraMan, mainScreenFragment, addToBackStack); + Log.d(DEBUG_TAG, "Found the main fragment"); + } else{ + mainScreenFragment = (MainScreenFragment) fragment; + } + } + + private void showMainFragmentFromClick(boolean addToBackStack){ + showMainFragmentFromClick(MainScreenFragment.makeArgsButtonsScreen(), addToBackStack); + } private void requestMapFragment(final boolean allowReturn){ // starting from Android 11, we don't need to have the STORAGE permission anymore for the map cache @@ -630,24 +652,6 @@ .commit(); } - private void showMainFragment(boolean addToBackStack){ - FragmentManager fraMan = getSupportFragmentManager(); - Fragment fragment = fraMan.findFragmentByTag(MainScreenFragment.FRAGMENT_TAG); - final MainScreenFragment mainScreenFragment; - if (fragment==null | !(fragment instanceof MainScreenFragment)){ - createShowMainFragment(fraMan, null, addToBackStack); - } - else if(!fragment.isVisible()){ - - - mainScreenFragment = (MainScreenFragment) fragment; - showMainFragment(fraMan, mainScreenFragment, addToBackStack); - Log.d(DEBUG_TAG, "Found the main fragment"); - } else{ - mainScreenFragment = (MainScreenFragment) fragment; - } - //return mainScreenFragment; - } @Nullable private MainScreenFragment getMainFragmentIfVisible(){ FragmentManager fraMan = getSupportFragmentManager(); @@ -661,6 +665,7 @@ public void showFloatingActionButton(boolean yes) { //TODO } + /* public void setDrawerSelectedItem(String fragmentTag){ switch (fragmentTag){ @@ -737,9 +742,8 @@ probableFragment.requestArrivalsForStopID(ID); } else { // we have no fragment - final Bundle args = new Bundle(); - args.putString(MainScreenFragment.PENDING_STOP_SEARCH, ID); //if onCreate is complete, then we are not asking for the first showing fragment + final Bundle args = MainScreenFragment.makeArgsArrivals(ID); boolean addtobackstack = onCreateComplete; createShowMainFragment(fraMan, args ,addtobackstack); } @@ -773,6 +777,32 @@ tr.commit(); } + @Override + public void openNearbyStopsFragment() { + FragmentManager fraMan = getSupportFragmentManager(); + var fragment = fraMan.findFragmentByTag(MainScreenFragment.FRAGMENT_TAG); + if(fragment instanceof MainScreenFragment mainFrag){ + if(!mainFrag.isVisible()){ + showMainFragment(fraMan, mainFrag, false); + } + mainFrag.openNearbyStopsFragment(); + } else{ + // there is no fragment and it is not visible + // add to back stack the main fragment, as the NearbyStopsFragment will not be added + createShowMainFragment(fraMan, MainScreenFragment.makeArgsNearby(), true); + } + } + + @Override + public void openLinesFragment() { + showLinesFragment(getSupportFragmentManager(), true, null); + } + + @Override + public void openFavoritesFragment() { + checkAndShowFavoritesFragment(getSupportFragmentManager(), true); + } + @Override public void toggleSpinner(boolean state) { MainScreenFragment probableFragment = getMainFragmentIfVisible(); @@ -791,10 +821,12 @@ @Override - public void showMapCenteredOnStop(Stop stop) { + public void showMapCenteredOnStop(@Nullable Stop stop) { createAndShowMapFragment(stop, true); } + + //Map Fragment stuff void createAndShowMapFragment(@Nullable Stop stop, boolean addToBackStack){ final FragmentManager fm = getSupportFragmentManager(); diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/ArrivalsFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/ArrivalsFragment.kt --- a/app/src/main/java/it/reyboz/bustorino/fragments/ArrivalsFragment.kt +++ b/app/src/main/java/it/reyboz/bustorino/fragments/ArrivalsFragment.kt @@ -758,11 +758,15 @@ return null } - fun isFragmentForTheSameStop(p: Palina): Boolean { - return if (tag != null) tag == getFragmentTag(p) + fun isFragmentForTheSameStop(stopID: String) : Boolean{ + return if (tag != null) tag == getFragmentTag(stopID) else false } + fun isFragmentForTheSameStop(p: Palina): Boolean { + return isFragmentForTheSameStop(p.ID) + } + /** * Request arrivals in the fragment @@ -812,10 +816,13 @@ return fragment } + + //return "palina_" + p.ID + @JvmStatic - fun getFragmentTag(p: Palina): String { - return "palina_" + p.ID - } + fun getFragmentTag(stopID: String) = "palina_$stopID" + @JvmStatic + fun getFragmentTag(p: Palina) = getFragmentTag(p.ID) @JvmStatic fun getArrivalsWorkID(stopID: String) = "arrivals_search_$stopID" diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/BarcodeFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/BarcodeFragment.kt new file mode 100644 --- /dev/null +++ b/app/src/main/java/it/reyboz/bustorino/fragments/BarcodeFragment.kt @@ -0,0 +1,52 @@ +package it.reyboz.bustorino.fragments + +import android.net.Uri +import android.util.Log +import android.widget.Toast +import androidx.activity.result.ActivityResultCallback +import androidx.core.net.toUri +import it.reyboz.bustorino.R +import it.reyboz.bustorino.backend.utils +import it.reyboz.bustorino.middleware.BarcodeScanContract +import it.reyboz.bustorino.middleware.BarcodeScanOptions +import it.reyboz.bustorino.middleware.BarcodeScanUtils + +//TODO: This might be probably implemented as interface +abstract class BarcodeFragment : ScreenBaseFragment(){ + + private val barcodeLauncher = registerForActivityResult(BarcodeScanContract(), ActivityResultCallback { + result -> + if (result != null && result.contents != null) { + //Toast.makeText(MyActivity.this, "Cancelled", Toast.LENGTH_LONG).show(); + val uri: Uri + try { + uri = result.contents.toUri() // this apparently prevents NullPointerException. Somehow. + } catch (e: Exception) { + Log.w("BusTO-BarcodeFragment","Cannot read QR code",e) + if (context != null) Toast.makeText( + requireContext(), + R.string.no_qrcode, Toast.LENGTH_SHORT + ).show() + return@ActivityResultCallback + } + val busStopID = utils.getBusStopIDFromUri(uri) + onQrScanSuccess(busStopID) + } else { + if (context != null) Toast.makeText( + requireContext(), R.string.no_qrcode, Toast.LENGTH_SHORT + ).show() + } + }) + + abstract fun onQrScanSuccess(busIDToSearch: String) + + protected fun launchBarcodeScan() { + val scanOptions = BarcodeScanOptions() + val intent = scanOptions.createScanIntent() + if (!BarcodeScanUtils.checkTargetPackageExists(getContext(), intent)) { + BarcodeScanUtils.showDownloadDialog(null, this) + } else { + barcodeLauncher.launch(scanOptions) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/ButtonsFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/ButtonsFragment.kt new file mode 100644 --- /dev/null +++ b/app/src/main/java/it/reyboz/bustorino/fragments/ButtonsFragment.kt @@ -0,0 +1,155 @@ +package it.reyboz.bustorino.fragments + +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.GridLayout +import android.widget.ImageView +import android.widget.TextView +import androidx.fragment.app.Fragment +import it.reyboz.bustorino.ActivitySettings +import it.reyboz.bustorino.R + +/** + * A simple [Fragment] subclass. + * Use the [ButtonsFragment.newInstance] factory method to + * create an instance of this fragment. + */ +class ButtonsFragment : BarcodeFragment() { + + private lateinit var gridLayout: GridLayout + + private var listener: CommonFragmentListener? = null + private lateinit var items: List + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + arguments?.let { + + } + if(listener is FragmentListenerMain){ + val ll = listener as FragmentListenerMain + ll.enableRefreshLayout(false) + } + } + private val marginHoriz = 30 + private val marginVer = 25 + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + // Inflate the layout for this fragment + val root = inflater.inflate(R.layout.fragment_buttons, container, false) + items = listOf( + CardMenuItem(CardAction.NEARBY, getString(R.string.nearby_me), R.drawable.compass_3_fill), + CardMenuItem(CardAction.MAP, getString(R.string.map), R.drawable.map), + CardMenuItem(CardAction.FAVORITES_STOPS, getString(R.string.action_favorites), R.drawable.ic_star_filled_white), + CardMenuItem(CardAction.LINES, getString(R.string.lines), R.drawable.ic_moving_emph), + CardMenuItem(CardAction.SETTINGS, getString(R.string.action_settings), R.drawable.ic_baseline_settings_24), + CardMenuItem(CardAction.QR_SCAN, getString(R.string.scan_qr_code_stop), R.drawable.qr_code_scan) + ) + gridLayout = root.findViewById(R.id.homeGridLayout) + + items.forEach { item -> + // Inflate base layout + val cardView = LayoutInflater.from(requireContext()) + .inflate(R.layout.item_card_button, gridLayout, false) + + // Popola icona e testo + cardView.findViewById(R.id.cardIcon).setImageResource(item.iconRes) + cardView.findViewById(R.id.cardLabel).text = item.label + // Parametri griglia: colonna flessibile + margini + cardView.layoutParams = GridLayout.LayoutParams().apply { + width = 0 + height = GridLayout.LayoutParams.WRAP_CONTENT + columnSpec = GridLayout.spec(GridLayout.UNDEFINED, 1f) + setMargins(marginHoriz, marginVer, marginHoriz, marginVer) // margini tra le card + } + + // Click + cardView.setOnClickListener { onCardClicked(item) } + + gridLayout.addView(cardView) + } + return root + } + + + private fun onCardClicked(item: CardMenuItem) { + Log.d(DEBUG_TAG, "onCardClicked - item: ${item}, listener: ${listener}") + // reagisci al tap + val list = listener + if(list == null){ + Log.w(DEBUG_TAG, "onCardClicked - listener is null") + } else + when(item.action) { + CardAction.NEARBY -> { + list.openNearbyStopsFragment() + } + CardAction.MAP -> { list.showMapCenteredOnStop(null)} + CardAction.LINES -> { list.openLinesFragment();} + CardAction.SETTINGS -> { startActivity(Intent(requireContext(), ActivitySettings::class.java)) } + CardAction.FAVORITES_STOPS -> { list.openFavoritesFragment() } + CardAction.QR_SCAN -> { + launchBarcodeScan() + } + } + } + + override fun onQrScanSuccess(busIDToSearch: String) { + listener?.let { + it.requestArrivalsForStopID(busIDToSearch) + } ?: Log.d(DEBUG_TAG, "onQrScanSuccess - listener is null") + } + + override fun getBaseViewForSnackBar(): View? { + return null + } + + override fun onAttach(context: Context) { + super.onAttach(context) + if (context is CommonFragmentListener) { + listener = context + Log.d(DEBUG_TAG, "onAttach") + } else{ + throw RuntimeException("$context must implement CommonFragmentListener") + } + } + + override fun onDetach() { + listener = null + Log.d(DEBUG_TAG, "onDetach") + super.onDetach() + } + + companion object { + /** + * @return A new instance of fragment ButtonsFragment. + */ + @JvmStatic + fun newInstance() = + ButtonsFragment().apply { + arguments = Bundle().apply { + } + } + const val DEBUG_TAG = "BusTO-ButtonsFragment" + + enum class CardAction { + NEARBY, MAP, FAVORITES_STOPS, LINES, SETTINGS, QR_SCAN + } + + data class CardMenuItem( + val action: CardAction, + val label: String, + val iconRes: Int + ) + + const val FRAGMENT_TAG = "HomeButtonsFragment" + } +} \ No newline at end of file diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/CommonFragmentListener.java b/app/src/main/java/it/reyboz/bustorino/fragments/CommonFragmentListener.java --- a/app/src/main/java/it/reyboz/bustorino/fragments/CommonFragmentListener.java +++ b/app/src/main/java/it/reyboz/bustorino/fragments/CommonFragmentListener.java @@ -35,7 +35,7 @@ * We want to open the map on the specified stop * @param stop needs to have location data (latitude, longitude) */ - void showMapCenteredOnStop(Stop stop); + void showMapCenteredOnStop(@Nullable Stop stop); /** * We want to show the line in detail for route coming from a stop @@ -50,4 +50,16 @@ * @param args extra arguments given as Bundle */ void openLineFromVehicle(String routeGtfsId, @Nullable String optionalPatternId, @Nullable Bundle args); + + /** + * Show the nearby stops fragment + */ + void openNearbyStopsFragment(); + + /** + * Show the lines + */ + void openLinesFragment(); + + void openFavoritesFragment(); } diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/FragmentHelper.java b/app/src/main/java/it/reyboz/bustorino/fragments/FragmentHelper.java --- a/app/src/main/java/it/reyboz/bustorino/fragments/FragmentHelper.java +++ b/app/src/main/java/it/reyboz/bustorino/fragments/FragmentHelper.java @@ -20,11 +20,12 @@ import android.content.Context; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; -import android.os.AsyncTask; import android.util.Log; import android.widget.Toast; @@ -50,7 +51,6 @@ public static final int NO_FRAME = -3; private static final String DEBUG_TAG = "BusTO FragmHelper"; private final StopSearcher stopSearcher; - private boolean shouldHaltAllActivities=false; public FragmentHelper(FragmentListenerMain listener, FragmentManager framan, Context context, int mainFrame) { @@ -84,48 +84,43 @@ * Called when you need to create a fragment for a specified Palina * @param p the Stop that needs to be displayed */ - public void createOrUpdateStopFragment(Palina p, boolean addToBackStack){ - boolean sameFragment; + public void showArrivalsFragmentForStop(@NonNull Palina p, boolean addToBackStack){ + boolean sameFragment = false; ArrivalsFragment arrivalsFragment = null; + final FragmentManager fm = managerWeakRef.get(); + if(fm == null) return; - if(managerWeakRef.get()==null || shouldHaltAllActivities) { - //SOMETHING WENT VERY WRONG - Log.e(DEBUG_TAG, "We are asked for a new stop but we can't show anything"); - return; - } - - FragmentManager fm = managerWeakRef.get(); - - if(fm.findFragmentById(primaryFrameLayout) instanceof ArrivalsFragment) { - arrivalsFragment = (ArrivalsFragment) fm.findFragmentById(primaryFrameLayout); - //Log.d(DEBUG_TAG, "Arrivals are for fragment with same stop?"); - if (arrivalsFragment == null) sameFragment = false; - else sameFragment = arrivalsFragment.isFragmentForTheSameStop(p); - } else { - sameFragment = false; - Log.d(DEBUG_TAG, "We aren't showing an ArrivalsFragment"); + if(fm.findFragmentById(primaryFrameLayout) instanceof ArrivalsFragment frag) { + sameFragment = frag.isFragmentForTheSameStop(p); + if(sameFragment) { + arrivalsFragment = frag; + Log.d("BusTO", "Same bus stop, accessing existing fragment"); + } } - setLastSuccessfullySearchedBusStop(p); - if (sameFragment){ - Log.d("BusTO", "Same bus stop, accessing existing fragment"); - arrivalsFragment = (ArrivalsFragment) fm.findFragmentById(primaryFrameLayout); - if (arrivalsFragment == null) sameFragment = false; - } - if(!sameFragment) { - //set the String to be displayed on the fragment - String displayName = p.getStopDisplayName(); - if (displayName != null && displayName.length() > 0) { - arrivalsFragment = ArrivalsFragment.newInstance(p.ID,displayName); - } else { - arrivalsFragment = ArrivalsFragment.newInstance(p.ID); + if(!sameFragment) { + // get old fragment + var frag = fm.findFragmentByTag(ArrivalsFragment.getFragmentTag(p)); + if(frag instanceof ArrivalsFragment) { + attachFragmentToContainer(fm, frag, null, true, addToBackStack); + arrivalsFragment = (ArrivalsFragment) frag; + } else { // create new fragment + //set the String to be displayed on the fragment + String displayName = p.getStopDisplayName(); + if (displayName != null && !displayName.isEmpty()) { + arrivalsFragment = ArrivalsFragment.newInstance(p.ID, displayName); + } else { + arrivalsFragment = ArrivalsFragment.newInstance(p.ID); + } + String probableTag = ArrivalsFragment.getFragmentTag(p); + attachFragmentToContainer(fm, arrivalsFragment, probableTag, true, addToBackStack); } - String probableTag = ArrivalsFragment.getFragmentTag(p); - attachFragmentToContainer(fm,arrivalsFragment,new AttachParameters(probableTag, true, addToBackStack)); } - // DO NOT CALL `setListAdapter` ever on arrivals fragment - arrivalsFragment.updateFragmentData(p); + setLastSuccessfullySearchedBusStop(p); + // update the data only if I have information about the passaggi + if(p.getTotalNumberOfPassages() > 0) + arrivalsFragment.updateFragmentData(p); // enable fragment auto refresh arrivalsFragment.setReloadOnResume(true); @@ -141,13 +136,13 @@ public void createStopListFragment(List resultList, String query, boolean addToBackStack){ listenerMain.hideKeyboard(); StopListFragment listfragment = StopListFragment.newInstance(query); - if(managerWeakRef.get()==null || shouldHaltAllActivities) { + if(managerWeakRef.get()==null) { //SOMETHING WENT VERY WRONG Log.e(DEBUG_TAG, "We are asked for a new stop but we can't show anything"); return; } - attachFragmentToContainer(managerWeakRef.get(),listfragment, - new AttachParameters("search_"+query, false,addToBackStack)); + attachFragmentToContainer(managerWeakRef.get(), + listfragment, "search_"+query, false, addToBackStack); listfragment.setStopList(resultList); //listenerMain.readyGUIfor(FragmentKind.STOPS); toggleSpinner(false); @@ -163,37 +158,35 @@ } /** - * Attach a new fragment to a cointainer + * Attach a new fragment to the appropriate container * @param fm the FragmentManager * @param fragment the Fragment - * @param parameters attach parameters + * @param tagAttach attach tag (can be null, the fragment's own tag has preference) + * @param addToBackStack if the transaction is to be added to the stack + * @param toSecondaryFrame if the fragment goes to the secondary frame */ - protected void attachFragmentToContainer(FragmentManager fm,Fragment fragment, AttachParameters parameters){ - if(shouldHaltAllActivities) //nothing to do - return; + protected void attachFragmentToContainer(FragmentManager fm, Fragment fragment, @Nullable String tagAttach, boolean toSecondaryFrame, boolean addToBackStack){ + FragmentTransaction ft = fm.beginTransaction(); int frameID; - if(parameters.attachToSecondaryFrame && secondaryFrameLayout!=NO_FRAME) - // ft.replace(secondaryFrameLayout,fragment,tag); + if(toSecondaryFrame && secondaryFrameLayout!=NO_FRAME) frameID = secondaryFrameLayout; - else frameID = primaryFrameLayout; - switch (parameters.transaction){ - case REPLACE: - ft.replace(frameID,fragment,parameters.tag); - - } - if (parameters.addToBackStack) - ft.addToBackStack("state_"+parameters.tag); + else + frameID = primaryFrameLayout; + var tag = fragment.getTag(); + if(tag == null) tag = tagAttach; + // there is only one case + //switch (pars.transaction){ + // case REPLACE: + ft.replace(frameID,fragment,tag); + //} + if (addToBackStack) + ft.addToBackStack("state_"+tag); ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE); - if(!fm.isDestroyed() && !shouldHaltAllActivities) - ft.commit(); + ft.commit(); //fm.executePendingTransactions(); } - public synchronized void setBlockAllActivities(boolean shouldI) { - this.shouldHaltAllActivities = shouldI; - } - public void stopLastRequestIfNeeded(){ /*if(lastTaskRef == null) return; AsyncTask task = lastTaskRef.get(); @@ -260,11 +253,12 @@ private void showShortToast(int messageID){ showToastMessage(messageID, true); } - + /* + // 18/05/2026: Commenting, do not remove, might be useful later enum Transaction{ REPLACE, } - static final class AttachParameters { + private static final class AttachParameters { String tag; boolean attachToSecondaryFrame; Transaction transaction; @@ -284,4 +278,6 @@ this.transaction = Transaction.REPLACE; } } + + */ } diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java --- a/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java +++ b/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java @@ -36,9 +36,9 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; +import androidx.lifecycle.ViewModelProvider; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; -import android.os.Handler; import android.util.Log; import android.view.LayoutInflater; import android.view.MenuItem; @@ -53,16 +53,16 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton; -import java.util.List; import java.util.Map; import it.reyboz.bustorino.R; import it.reyboz.bustorino.backend.*; -import it.reyboz.bustorino.data.PreferencesHolder; import it.reyboz.bustorino.middleware.BarcodeScanContract; import it.reyboz.bustorino.middleware.BarcodeScanOptions; import it.reyboz.bustorino.middleware.BarcodeScanUtils; import it.reyboz.bustorino.util.Permissions; +import it.reyboz.bustorino.viewmodels.IntroViewModel; +import org.jetbrains.annotations.NotNull; import static it.reyboz.bustorino.backend.utils.getBusStopIDFromUri; import static it.reyboz.bustorino.util.Permissions.LOCATION_PERMISSIONS; @@ -73,7 +73,7 @@ * Use the {@link MainScreenFragment#newInstance} factory method to * create an instance of this fragment. */ -public class MainScreenFragment extends ScreenBaseFragment implements FragmentListenerMain{ +public class MainScreenFragment extends BarcodeFragment implements FragmentListenerMain{ private static final String SAVED_FRAGMENT="saved_fragment"; @@ -82,8 +82,28 @@ public static final String PENDING_STOP_SEARCH="PendingStopSearch"; + public static final String ARG_INITIAL_CONTENT = "initial_content"; + public static final String ARG_STOP_ID = "pending_stop_id"; + public static final String ARG_SEARCH_QUERY = "pending_search_query"; + public final static String FRAGMENT_TAG = "MainScreenFragment"; + public enum InitialScreen { + HOME_BUTTONS(0), + NEARBY_STOPS(1), + ARRIVALS(2), + STOP_SEARCH(3); + + public final int code; + InitialScreen(int code) { this.code = code; } + + @Nullable + public static InitialScreen fromCode(int code) { + for (InitialScreen c : values()) if (c.code == code) return c; + return null; + } + } + private FragmentHelper fragmentHelper; private SwipeRefreshLayout swipeRefreshLayout; private EditText busStopSearchByIDEditText; @@ -107,7 +127,7 @@ //private ImageButton addToFavorites; //// HIDDEN BUT IMPORTANT ELEMENTS //// private FragmentManager childFragMan; - + private IntroViewModel introViewModel; private void refreshStop() { if(getContext() == null){ Log.w(DEBUG_TAG,"Asked to refresh stop but context is null"); @@ -126,38 +146,12 @@ Log.w(DEBUG_TAG, "Asked to refresh stop when there is no fragment"); } } - // - private final ActivityResultLauncher barcodeLauncher = registerForActivityResult(new BarcodeScanContract(), - result -> { - if(result!=null && result.getContents()!=null) { - //Toast.makeText(MyActivity.this, "Cancelled", Toast.LENGTH_LONG).show(); - Uri uri; - try { - uri = Uri.parse(result.getContents()); // this apparently prevents NullPointerException. Somehow. - } catch (NullPointerException e) { - if (getContext()!=null) - Toast.makeText(getContext().getApplicationContext(), - R.string.no_qrcode, Toast.LENGTH_SHORT).show(); - return; - } - String busStopID = getBusStopIDFromUri(uri); - busStopSearchByIDEditText.setText(busStopID); - requestArrivalsForStopID(busStopID); - - } else { - //Toast.makeText(MyActivity.this, "Scanned: " + result.getContents(), Toast.LENGTH_LONG).show(); - if (getContext()!=null) - Toast.makeText(getContext().getApplicationContext(), - R.string.no_qrcode, Toast.LENGTH_SHORT).show(); - - } - }); - /// LOCATION STUFF /// boolean pendingIntroRun = false; boolean pendingNearbyStopsFragmentRequest = false; + boolean pendingNearbyAddToBackStack = false; boolean locationPermissionGranted, locationPermissionAsked = false; //AppLocationManager locationManager; private final ActivityResultLauncher requestPermissionLauncher = @@ -186,7 +180,7 @@ //showNearbyStopsFragment(); Log.d(DEBUG_TAG, "We have location permission"); if (pendingNearbyStopsFragmentRequest) { - showNearbyFragmentIfPossible(); + showNearbyFragmentIfPossible(pendingNearbyAddToBackStack); pendingNearbyStopsFragmentRequest = false; } } @@ -199,6 +193,8 @@ private CommonFragmentListener mListener; private String pendingStopID = null; + private String pendingSearchQuery = null; + private InitialScreen initialScreen = InitialScreen.HOME_BUTTONS; private CoordinatorLayout coordLayout; public MainScreenFragment() { @@ -206,19 +202,71 @@ } - public static MainScreenFragment newInstance() { - return new MainScreenFragment(); + public static MainScreenFragment newInstance(@NonNull InitialScreen kind, + @Nullable String stopId, + @Nullable String query) { + MainScreenFragment f = new MainScreenFragment(); + f.setArguments(makeArgs(kind, stopId, query)); + return f; + } + public static MainScreenFragment newInstance(@NonNull InitialScreen kind, @Nullable Bundle args){ + MainScreenFragment f = new MainScreenFragment(); + if (args != null) { + f.setArguments(args); + } + return f; } + /** + * Create the bundle for the arguments of the fragment + * @param kind the kind of initial screen + * @param stopId + * @param query + * @return + */ + public static Bundle makeArgs(@NonNull InitialScreen kind, @Nullable String stopId, @Nullable String query) { + Bundle b = new Bundle(); + b.putInt(ARG_INITIAL_CONTENT, kind.code); + if (stopId != null) b.putString(ARG_STOP_ID, stopId); + if (query != null) b.putString(ARG_SEARCH_QUERY, query); + return b; + } + public static Bundle makeArgsArrivals(@NonNull String stopID){ + return makeArgs(InitialScreen.ARRIVALS, stopID, null); + } + public static Bundle makeArgsStops(@NonNull String query){ + return makeArgs(InitialScreen.STOP_SEARCH, query, null); + } + public static Bundle makeArgsNearby(){ + return makeArgs(InitialScreen.NEARBY_STOPS, null, null); + } + public static Bundle makeArgsButtonsScreen(){ + return makeArgs(InitialScreen.HOME_BUTTONS, null, null); + } + + + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (getArguments() != null) { - //do nothing - Log.d(DEBUG_TAG, "ARGS ARE NOT NULL: "+getArguments()); - if (getArguments().getString(PENDING_STOP_SEARCH)!=null) - pendingStopID = getArguments().getString(PENDING_STOP_SEARCH); + Bundle args = getArguments(); + if (args != null) { + Log.d(DEBUG_TAG, "ARGS ARE NOT NULL: "+ args); + + // Legacy key, kept for back-compat + String legacyStop = args.getString(PENDING_STOP_SEARCH); + if (legacyStop != null) pendingStopID = legacyStop; + + if (args.containsKey(ARG_INITIAL_CONTENT)) { + int code = args.getInt(ARG_INITIAL_CONTENT, InitialScreen.HOME_BUTTONS.code); + InitialScreen parsed = InitialScreen.fromCode(code); + initialScreen = (parsed != null) ? parsed : InitialScreen.HOME_BUTTONS; + } + String stopId = args.getString(ARG_STOP_ID); + if (stopId != null) pendingStopID = stopId; + pendingSearchQuery = args.getString(ARG_SEARCH_QUERY); } + } @Override @@ -283,6 +331,10 @@ cr.setPowerRequirement(Criteria.NO_REQUIREMENT); */ //locationManager = AppLocationManager.getInstance(requireContext()); + introViewModel = new ViewModelProvider(requireActivity()).get(IntroViewModel.class); + introViewModel.getIntroIsRunning().observe(getViewLifecycleOwner(), isRunning -> { + pendingIntroRun = isRunning; + }); Log.d(DEBUG_TAG, "OnCreateView, savedInstanceState null: "+(savedInstanceState==null)); @@ -307,8 +359,38 @@ */ if (getChildFragmentManager().findFragmentById(R.id.resultFrame)!= null){ swipeRefreshLayout.setVisibility(View.VISIBLE); + // The child FragmentManager has restored its content — don't dispatch again + return; } + if (savedInstanceState != null) return; + + dispatchInitialContent(); + } + + /** + * Installs the initial child fragment based on the arguments supplied as arguments + */ + private void dispatchInitialContent() { + switch (initialScreen) { + case NEARBY_STOPS: + showNearbyStopsFragmentChecking(false); + break; + case ARRIVALS: + // pendingStopID is consumed in onResume → requestArrivalsForStopID + break; + case STOP_SEARCH: + if (pendingSearchQuery != null && pendingSearchQuery.length() >= 2) { + fragmentHelper.requestStopSearch(pendingSearchQuery); + } else { + showButtonsFragment(true); + } + pendingSearchQuery = null; + break; + case HOME_BUTTONS: + default: + showButtonsFragment(true); + } } @Override @@ -318,7 +400,7 @@ Fragment fragment = getChildFragmentManager().findFragmentById(R.id.resultFrame); if (fragment!=null) getChildFragmentManager().putFragment(outState, SAVED_FRAGMENT, fragment); - if (fragmentHelper!=null) fragmentHelper.setBlockAllActivities(true); + //if (fragmentHelper!=null) fragmentHelper.setBlockAllActivities(true); } @@ -361,7 +443,6 @@ throw new RuntimeException(context + " must implement CommonFragmentListener"); } - } @Override public void onDetach() { @@ -377,18 +458,9 @@ if (setupOnStart) { if (pendingStopID==null){ - if(PreferencesHolder.hasIntroFinishedOneShot(requireContext())){ - Log.d(DEBUG_TAG, "Showing nearby stops"); - if(!checkLocationPermission()){ - requestLocationPermission(); - pendingNearbyStopsFragmentRequest = true; - } - else { - showNearbyFragmentIfPossible(); - } - } else { - //The Introductory Activity is about to be started, hence pause the request and show later - pendingIntroRun = true; + if(!pendingIntroRun){ + //show the fragment + //showButtonsFragment(); } } @@ -400,48 +472,64 @@ } } + private void showButtonsFragment(boolean addInsteadOfReplace){ + + swipeRefreshLayout.setVisibility(View.VISIBLE); + var ft = childFragMan.beginTransaction(); + var frag = ButtonsFragment.newInstance(); + if(addInsteadOfReplace) + ft.add(R.id.resultFrame,frag, ButtonsFragment.FRAGMENT_TAG); + else{ + ft.replace(R.id.resultFrame, frag, ButtonsFragment.FRAGMENT_TAG); + ft.addToBackStack(null); + } + ft.commit(); + } + + private void showNearbyStopsFragmentChecking(boolean addToBackStack){ + if(!checkLocationPermission()){ + requestLocationPermission(); + pendingNearbyStopsFragmentRequest = true; + pendingNearbyAddToBackStack = addToBackStack; + Log.d(DEBUG_TAG, "requesting location permission for nearby fragment"); + } + else { + Log.d(DEBUG_TAG, "Showing nearby stops fragment"); + showNearbyFragmentIfPossible(addToBackStack); + } + } + @Override public void onResume() { super.onResume(); final Context con = requireContext(); Log.w(DEBUG_TAG, "OnResume called, setupOnStart: "+ setupOnStart); - //if (locationManager == null) - // locationManager = AppLocationManager.getInstance(con); //recheck the introduction activity has been run - if(pendingIntroRun && PreferencesHolder.hasIntroFinishedOneShot(con)){ - //request position permission if needed - if(!checkLocationPermission()){ - requestLocationPermission(); - pendingNearbyStopsFragmentRequest = true; - } - else { - showNearbyFragmentIfPossible(); - } - //deactivate flag - pendingIntroRun = false; - } if(Permissions.bothLocationPermissionsGranted(con)){ Log.d(DEBUG_TAG, "Location permission OK"); - //if(!locationManager.isRequesterRegistered(requester)) - // locationManager.addLocationRequestFor(requester); + } //don't request permission // if we have a pending stopID request, do it Log.d(DEBUG_TAG, "Pending stop ID for arrivals: "+pendingStopID); //this is the second time we are attaching this fragment -> Log.d(DEBUG_TAG, "Waiting for new stop request: "+ suppressArrivalsReload); - //TODO: if we come back to this from another fragment, and the user has given again the permission - // for the Location, we should show the Nearby Stops + if(!suppressArrivalsReload && pendingStopID==null){ //none of the following cases are true // check if we are showing any fragment + /* + //TODO: check if this is needed final Fragment fragment = getChildFragmentManager().findFragmentById(R.id.resultFrame); + if(fragment==null || swipeRefreshLayout.getVisibility() != View.VISIBLE){ //we are not showing anything if(Permissions.anyLocationPermissionsGranted(getContext())){ showNearbyFragmentIfPossible(); } } + + */ } if (suppressArrivalsReload){ // we have to suppress the reloading of the (possible) ArrivalsFragment @@ -462,7 +550,7 @@ } mListener.readyGUIfor(FragmentKind.MAIN_SCREEN_FRAGMENT); - fragmentHelper.setBlockAllActivities(false); + //fragmentHelper.setBlockAllActivities(false); } @@ -470,31 +558,29 @@ public void onPause() { //mainHandler = null; //locationManager.removeLocationRequestFor(requester); - fragmentHelper.setBlockAllActivities(true); + //fragmentHelper.setBlockAllActivities(true); fragmentHelper.stopLastRequestIfNeeded(); super.onPause(); } - - /* GUI METHODS */ + + @Override + public void onQrScanSuccess(@NotNull String busIDToSearch) { + busStopSearchByIDEditText.setText(busIDToSearch); + requestArrivalsForStopID(busIDToSearch); + } + /** * QR scan button clicked * * @param v View QRButton clicked */ public void onQRButtonClick(View v) { - - BarcodeScanOptions scanOptions = new BarcodeScanOptions(); - Intent intent = scanOptions.createScanIntent(); - if(!BarcodeScanUtils.checkTargetPackageExists(getContext(), intent)){ - BarcodeScanUtils.showDownloadDialog(null, this); - }else { - barcodeLauncher.launch(scanOptions); - } + launchBarcodeScan(); } /** @@ -613,25 +699,6 @@ //actionHelpMenuItem.setVisible(false); } - private void actuallyShowNearbyStopsFragment(){ - swipeRefreshLayout.setVisibility(View.VISIBLE); - final Fragment existingFrag = childFragMan.findFragmentById(R.id.resultFrame); - // fragment; - if (!(existingFrag instanceof NearbyStopsFragment)){ - Log.d(DEBUG_TAG, "actually showing Nearby Stops Fragment"); - //there is no fragment showing - final NearbyStopsFragment fragment = NearbyStopsFragment.newInstance(NearbyStopsFragment.FragType.STOPS); - - FragmentTransaction ft = childFragMan.beginTransaction(); - - ft.replace(R.id.resultFrame, fragment, NearbyStopsFragment.FRAGMENT_TAG); - if (getActivity()!=null && !getActivity().isFinishing()) - ft.commit(); - else Log.e(DEBUG_TAG, "Not showing nearby fragment because activity null or is finishing"); - } - } - - @Override public void showFloatingActionButton(boolean yes) { mListener.showFloatingActionButton(yes); @@ -678,12 +745,27 @@ @Override public void openLineFromStop(String routeGtfsId, @Nullable String stopIDFrom) { //pass to activity - mListener.openLineFromStop(routeGtfsId, stopIDFrom); + if(mListener!=null) mListener.openLineFromStop(routeGtfsId, stopIDFrom); } @Override public void openLineFromVehicle(String routeGtfsId, @Nullable String optionalPatternId, @Nullable Bundle args) { - mListener.openLineFromVehicle(routeGtfsId, optionalPatternId, args); + if(mListener!=null) mListener.openLineFromVehicle(routeGtfsId, optionalPatternId, args); + } + + @Override + public void openNearbyStopsFragment() { + showNearbyStopsFragmentChecking(true); + } + + @Override + public void openLinesFragment() { + if(mListener!=null) mListener.openLinesFragment(); + } + + @Override + public void openFavoritesFragment() { + if(mListener!=null) mListener.openFavoritesFragment(); } @Override @@ -709,29 +791,28 @@ Log.e(DEBUG_TAG, "Asked for arrivals with null context"); return; } - ArrivalsFetcher[] fetchers = utils.getDefaultArrivalsFetchers(getContext()).toArray(new ArrivalsFetcher[0]); - if (ID == null || ID.length() <= 0) { + if (ID == null || ID.isEmpty()) { // we're still in UI thread, no need to mess with Progress showToastMessage(R.string.insert_bus_stop_number_error, true); toggleSpinner(false); - } else if (framan.findFragmentById(R.id.resultFrame) instanceof ArrivalsFragment) { - ArrivalsFragment fragment = (ArrivalsFragment) framan.findFragmentById(R.id.resultFrame); - if (fragment != null && fragment.getStopID() != null && fragment.getStopID().equals(ID)){ - // Run with previous fetchers - //fragment.getCurrentFetchers().toArray() - fragment.requestArrivalsForTheFragment(); - } else{ - //SHOW NEW ARRIVALS FRAGMENT - //new AsyncArrivalsSearcher(fragmentHelper, fetchers, getContext()).execute(ID); - fragmentHelper.createOrUpdateStopFragment(new Palina(ID), true); + } else{ + var palinaTrial = new Palina(ID); + if (framan.findFragmentById(R.id.resultFrame) instanceof ArrivalsFragment fragment) { + if (fragment.isFragmentForTheSameStop(palinaTrial)){ + // Run with previous fetchers + //fragment.getCurrentFetchers().toArray() + fragment.requestArrivalsForTheFragment(); + } else{ + // The rest of the case is handled by the fragment Helper + fragmentHelper.showArrivalsFragmentForStop(palinaTrial, true); + } } - } - else { - Log.d(DEBUG_TAG, "This is probably the first arrivals search, preparing GUI"); - //prepareGUIForArrivals(); - //new AsyncArrivalsSearcher(fragmentHelper,fetchers, getContext()).execute(ID); - fragmentHelper.createOrUpdateStopFragment(new Palina(ID), true); + else { + // this is not needed any more + //prepareGUIForArrivals(); + fragmentHelper.showArrivalsFragmentForStop(palinaTrial, true); + } } } @@ -739,21 +820,22 @@ final Context context = getContext(); if(context==null) return false; - final boolean isOldVersion = Build.VERSION.SDK_INT < Build.VERSION_CODES.M; - final boolean noPermission = ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && - ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED; + final boolean noPermission = ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED; - return isOldVersion || !noPermission; + return !noPermission; } private void requestLocationPermission(){ + if(shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)){ + makeToast(R.string.enable_position_message_nearby); + } requestPermissionLauncher.launch(LOCATION_PERMISSIONS); } - private void showNearbyFragmentIfPossible() { + private void showNearbyFragmentIfPossible(boolean addToBackStack) { if (isNearbyFragmentShown()) { //nothing to do - Log.w(DEBUG_TAG, "Asked to show nearby fragment but we already are showing it"); + Log.d(DEBUG_TAG, "Asked to show nearby fragment but we already are showing it"); return; } if (getContext() == null) { @@ -761,13 +843,26 @@ return; } - if (fragmentHelper.getLastSuccessfullySearchedBusStop() == null - && !childFragMan.isDestroyed()) { + if (!childFragMan.isDestroyed()) { //Go ahead with the request - - actuallyShowNearbyStopsFragment(); + swipeRefreshLayout.setVisibility(View.VISIBLE); + final Fragment existingFrag = childFragMan.findFragmentById(R.id.resultFrame); + // fragment; + if (!(existingFrag instanceof NearbyStopsFragment)){ + Log.d(DEBUG_TAG, "actually showing Nearby Stops Fragment"); + //there is no fragment showing + final NearbyStopsFragment fragment = NearbyStopsFragment.newInstance(NearbyStopsFragment.FragType.STOPS); + + FragmentTransaction ft = childFragMan.beginTransaction(); + + ft.replace(R.id.resultFrame, fragment, NearbyStopsFragment.FRAGMENT_TAG); + if(addToBackStack) ft.addToBackStack(null); + if (getActivity()!=null && !getActivity().isFinishing()) + ft.commit(); + else Log.e(DEBUG_TAG, "Not showing nearby fragment because activity null or is finishing"); + } pendingNearbyStopsFragmentRequest = false; } } } \ No newline at end of file diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/ScreenBaseFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/ScreenBaseFragment.java --- a/app/src/main/java/it/reyboz/bustorino/fragments/ScreenBaseFragment.java +++ b/app/src/main/java/it/reyboz/bustorino/fragments/ScreenBaseFragment.java @@ -2,11 +2,7 @@ import android.Manifest; import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; import android.content.SharedPreferences; -import android.net.Uri; -import android.provider.Settings; import android.util.Log; import android.view.View; import android.view.inputmethod.InputMethodManager; @@ -15,7 +11,6 @@ import androidx.activity.result.ActivityResultCallback; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; @@ -44,12 +39,18 @@ return getOption(mContext, optionName, optDefault); } - protected void showToastMessage(int messageID, boolean short_lenght) { - final int length = short_lenght ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG; + protected void showToastMessage(int messageID, boolean shortT) { + final int length = shortT ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG; final Context context = getContext(); if(context!=null) Toast.makeText(context, messageID, length).show(); } + protected void makeToast(String message){ + Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show(); + } + protected void makeToast(int messageID){ + Toast.makeText(getContext(), messageID, Toast.LENGTH_SHORT).show(); + } public void hideKeyboard() { if (getActivity()==null) return; diff --git a/app/src/main/java/it/reyboz/bustorino/middleware/StopSearcher.kt b/app/src/main/java/it/reyboz/bustorino/middleware/StopSearcher.kt --- a/app/src/main/java/it/reyboz/bustorino/middleware/StopSearcher.kt +++ b/app/src/main/java/it/reyboz/bustorino/middleware/StopSearcher.kt @@ -1,6 +1,5 @@ package it.reyboz.bustorino.middleware -import android.content.Context import android.util.Log import it.reyboz.bustorino.backend.Fetcher import it.reyboz.bustorino.backend.FiveTStopsFetcher @@ -11,8 +10,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.lang.ref.WeakReference diff --git a/app/src/main/java/it/reyboz/bustorino/viewmodels/FavoritesViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/FavoritesViewModel.kt --- a/app/src/main/java/it/reyboz/bustorino/viewmodels/FavoritesViewModel.kt +++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/FavoritesViewModel.kt @@ -1,21 +1,15 @@ package it.reyboz.bustorino.viewmodels import android.app.Application -import android.util.Log import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.MediatorLiveData -import androidx.lifecycle.application import androidx.lifecycle.map import androidx.lifecycle.switchMap -import androidx.lifecycle.viewModelScope import androidx.work.WorkInfo import it.reyboz.bustorino.backend.Stop import it.reyboz.bustorino.backend.StopFavoritesData import it.reyboz.bustorino.data.DBUpdateWorker.Companion.getWorkInfoLiveData -import it.reyboz.bustorino.data.FavoritesLiveData import it.reyboz.bustorino.data.OldDataRepository -import it.reyboz.bustorino.data.QueryLiveData -import kotlinx.coroutines.launch import java.util.concurrent.Executors class FavoritesViewModel(application: Application) : AndroidViewModel(application) { diff --git a/app/src/main/java/it/reyboz/bustorino/viewmodels/IntroViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/IntroViewModel.kt new file mode 100644 --- /dev/null +++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/IntroViewModel.kt @@ -0,0 +1,11 @@ +package it.reyboz.bustorino.viewmodels + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel + +class IntroViewModel: ViewModel() { + + + var introIsRunning = MutableLiveData(false) + +} \ No newline at end of file diff --git a/app/src/main/java/it/reyboz/bustorino/viewmodels/LinesViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/LinesViewModel.kt --- a/app/src/main/java/it/reyboz/bustorino/viewmodels/LinesViewModel.kt +++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/LinesViewModel.kt @@ -3,12 +3,9 @@ import android.app.Application import android.util.Log import androidx.lifecycle.* -import it.reyboz.bustorino.backend.Result import it.reyboz.bustorino.backend.Stop import it.reyboz.bustorino.data.GtfsRepository -import it.reyboz.bustorino.data.NextGenDB import it.reyboz.bustorino.data.OldDataRepository -import it.reyboz.bustorino.data.gtfs.GtfsDatabase import it.reyboz.bustorino.data.gtfs.GtfsRoute import it.reyboz.bustorino.data.gtfs.MatoPatternWithStops import it.reyboz.bustorino.data.gtfs.PatternStop diff --git a/app/src/main/res/drawable-hdpi/ic_star.png b/app/src/main/res/drawable-hdpi/ic_star.png deleted file mode 100644 index 7337fed6ef223388e380ab5eb665da0d3f3abd02..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@^#mtppu=b3qT=9!WD=p(%VazM^! zA30CNPhKBe3D#w51}Z=$tLZZZ99WskObP{3r7WpnrJR{G0t6I*UH)R%>N1l)v;2$M zOpLxg*XsyaPjwmv$S=?s4^#gy02@sNY@|Jfiokvo0sBBP&C6(6QZs_ZlO;8cqdx(B zr8V||i9}~0;!k%~(!~hvE=jr)Q=f8ej`CSa4NpGBU2T?h>5WxXS_axnvG%9IE(%l|sXGPKnNk1ihl(glXtFf9%us7zNx2#r8V`K;9f^To|{QiKWn}q6S+3rX|b;V0v3V1l|`#-hMh#NAML<%*nq?_umw!l`R*F< z&f3(&q0d|FAd#5{1U>uF)EY<0_42(3hzabGL_DvW|NeFmuogG<9 zOtxzS0>*ICVjNk_XFiELdjfn0=DRs?gl1&kViN4jhm~Hn&I)mymO>&5zAXxbX%Ky6OwybMOGh!GVPbAZ{wb zU8m2b#s)4>djGKSB~6pI=X|H9=TBjc6p&PsR7#^GN@+vOj+J?!FR8x@VkA}ckBX$? zp1>Fb&~%oXcCE16?`j%5Azp|AZ{JSXx+&5NqXZ9(iXy+(Y%HCU8yG5FzYk27y)p0000E>F2H^KoTC{j>Q;0>4CEAg5bEMMK#Bwr15TC z0!M(2F~)5KzU!JGxDwUXqEDNdrI8Uj?g4K6ZwZL|HH~yw>h$dUPVbupw;z-=)ahVY zN@7%uG5wOtKn2(XY)+utX5b5;BB?B?KZI^gV?f+>Cutq9+h_M%V9@&u1AhIX@A)_Q zISHNsH9!9gECMfqWxu`-`~loIv!%H4sA&-NHq~C>1E364frG%8Yb5K=0yls;;2H1& zxNT;0aoxNu>7u#^7kx>_OM||a47dcG&Z>150L#E-URlGyA*UowPOM>4=YsgIkgYGT#SCsz_Rhq40#HzD6~CgoHjH+YAdXJ97^hlk~KK zk~RMx*y3(pB1D+qTWj#_r;DEJ_Rf|5oh%{7oQnTJAX^#aL&wjnc2N| zT-4LtJu|BU=l$7He3}Ojh~k2zO6VS4WLZ>P#v9~?fIE_Yhy=z?1>BYEqb^ff8$Tmy zpQQDnXFY~Dte5n4X7xZMvRj4|K-M~t*39QYW`P0)3KZxzyaw8)I0MPWRjL30002ov JPDHLkV1nl#^7;S( diff --git a/app/src/main/res/drawable-xxhdpi/ic_star.png b/app/src/main/res/drawable-xxhdpi/ic_star.png deleted file mode 100644 index bc99ac86917c0ce1c8ba594eea8caafb9db1a380..0000000000000000000000000000000000000000 GIT binary patch literal 0 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 P00000NkvXXu0mjfSA22Y diff --git a/app/src/main/res/drawable/compass_3_fill.xml b/app/src/main/res/drawable/compass_3_fill.xml new file mode 100644 --- /dev/null +++ b/app/src/main/res/drawable/compass_3_fill.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_moving_emph.xml b/app/src/main/res/drawable/ic_moving_emph.xml --- a/app/src/main/res/drawable/ic_moving_emph.xml +++ b/app/src/main/res/drawable/ic_moving_emph.xml @@ -4,6 +4,6 @@ android:viewportWidth="48" android:viewportHeight="48"> diff --git a/app/src/main/res/drawable/ic_star_filled_white.xml b/app/src/main/res/drawable/ic_star_filled_white.xml --- a/app/src/main/res/drawable/ic_star_filled_white.xml +++ b/app/src/main/res/drawable/ic_star_filled_white.xml @@ -4,6 +4,6 @@ android:viewportWidth="24" android:viewportHeight="24"> diff --git a/app/src/main/res/drawable/qr_code_scan.xml b/app/src/main/res/drawable/qr_code_scan.xml new file mode 100644 --- /dev/null +++ b/app/src/main/res/drawable/qr_code_scan.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_buttons.xml b/app/src/main/res/layout/fragment_buttons.xml new file mode 100644 --- /dev/null +++ b/app/src/main/res/layout/fragment_buttons.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_main_screen.xml b/app/src/main/res/layout/fragment_main_screen.xml --- a/app/src/main/res/layout/fragment_main_screen.xml +++ b/app/src/main/res/layout/fragment_main_screen.xml @@ -1,79 +1,84 @@ - - > + android:contentDescription="@string/scan_qr_code_stop" + android:scaleType="fitCenter" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintStart_toStartOf="parent" + /> + - - - - - + @@ -83,30 +88,32 @@ style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="8dp" - android:layout_alignParentEnd="true" - android:layout_alignParentStart="true" android:layout_below="@+id/QRButton" android:layout_marginTop="3dp" android:layout_marginEnd="10dp" android:layout_marginStart="10dp" android:indeterminateOnly="true" android:minWidth="10dp" - android:visibility="gone" /> - - + android:visibility="gone" + app:layout_constraintTop_toBottomOf="@id/searchButton" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + /> - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/item_card_button.xml b/app/src/main/res/layout/item_card_button.xml new file mode 100644 --- /dev/null +++ b/app/src/main/res/layout/item_card_button.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -74,6 +74,7 @@ Visualizza sulla mappa Non trovo un\'applicazione dove mostrarla Posizione della fermata non trovata + Vicino a me Fermate vicine Ricerca della posizione Nessuna fermata nei dintorni diff --git a/app/src/main/res/values-v35/styles.xml b/app/src/main/res/values-v35/styles.xml --- a/app/src/main/res/values-v35/styles.xml +++ b/app/src/main/res/values-v35/styles.xml @@ -1,15 +1,4 @@ - - - \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -51,6 +51,7 @@ Vehicle %1$s No timetable found No QR code found, try using another app to scan + Scan QR code at the stop Unexpected internal error, cannot extract data from GTT/5T website Help About the app @@ -151,7 +152,8 @@ ListFragment - BusTO it.reyboz.bustorino.preferences db_is_updating - + + Nearby me Nearby stops Nearby connections App version