diff --git a/app/src/main/java/it/reyboz/bustorino/ActivityIntro.kt b/app/src/main/java/it/reyboz/bustorino/ActivityIntro.kt index 237d070..3320c94 100644 --- a/app/src/main/java/it/reyboz/bustorino/ActivityIntro.kt +++ b/app/src/main/java/it/reyboz/bustorino/ActivityIntro.kt @@ -1,107 +1,135 @@ package it.reyboz.bustorino import android.content.Intent import android.os.Bundle import android.util.Log +import android.util.TypedValue import android.view.View import android.widget.ImageButton import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.res.ResourcesCompat import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.widget.ViewPager2 import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayoutMediator import it.reyboz.bustorino.data.PreferencesHolder import it.reyboz.bustorino.fragments.IntroFragment class ActivityIntro : AppCompatActivity(), IntroFragment.IntroListener { private lateinit var viewPager : ViewPager2 private lateinit var btnForward: ImageButton private lateinit var btnBackward: ImageButton + private lateinit var closeBottomButton: ImageButton private var restartMain = true + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_intro) viewPager = findViewById(R.id.viewPager) btnBackward = findViewById(R.id.btnPrevious) btnForward = findViewById(R.id.btnNext) + closeBottomButton = findViewById(R.id.btnCompactClose) val extras = intent.extras if(extras!=null){ restartMain = extras.getBoolean(RESTART_MAIN) } val adapter = IntroPagerAdapter(this) viewPager.adapter = adapter val tabLayout = findViewById(R.id.tab_layout) val tabLayoutMediator = TabLayoutMediator(tabLayout, viewPager) { tab, pos -> Log.d(DEBUG_TAG, "tabview on position $pos") } tabLayoutMediator.attach() btnForward.setOnClickListener { viewPager.setCurrentItem(viewPager.currentItem+1,true) } btnBackward.setOnClickListener { viewPager.setCurrentItem(viewPager.currentItem-1, true) } + /*closeBottomButton.setOnClickListener { + closeIntroduction() + } + + */ viewPager.registerOnPageChangeCallback(object : OnPageChangeCallback() { override fun onPageSelected(position: Int) { if(position == 0){ btnBackward.visibility = View.INVISIBLE } else{ btnBackward.visibility = View.VISIBLE } if(position == NUM_ITEMS-1){ btnForward.visibility = View.INVISIBLE - } else{ - btnForward.visibility = View.VISIBLE + closeBottomButton.visibility = View.VISIBLE + }else if(position == NUM_ITEMS-2){ + if(closeBottomButton.visibility == View.VISIBLE) { + closeBottomButton.visibility = View.INVISIBLE + btnForward.visibility = View.VISIBLE + } + //btnForward.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.arrow_forward_white, null)) + //btnForward.setBackgroundColor(ResourcesCompat.getColor(resources,R.attr.colorAccent, theme)) + /*val + GET THE COLOR VALUE OF THE THEMER + colo = TypedValue() + theme.resolveAttribute(R.attr.colorAccent,colo, true) + btnForward.backgroundTintList //(colo.data) + + */ } } + }) + + closeBottomButton.setOnClickListener { + closeIntroduction() + } } /** * A simple pager adapter that represents 5 ScreenSlidePageFragment objects, in * sequence. */ private inner class IntroPagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) { override fun getItemCount(): Int = NUM_ITEMS override fun createFragment(position: Int): Fragment = IntroFragment.newInstance(position) } companion object{ const private val DEBUG_TAG = "BusTO-IntroActivity" const val RESTART_MAIN = "restartMainActivity" const val NUM_ITEMS = 7 } override fun closeIntroduction() { if(restartMain) startActivity(Intent(this, ActivityPrincipal::class.java)) val pref = PreferencesHolder.getMainSharedPreferences(this) val editor = pref.edit() editor.putBoolean(PreferencesHolder.PREF_INTRO_ACTIVITY_RUN, true) //use commit so we don't "lose" info editor.commit() finish() } } \ No newline at end of file diff --git a/app/src/main/java/it/reyboz/bustorino/adapters/RouteAdapter.kt b/app/src/main/java/it/reyboz/bustorino/adapters/RouteAdapter.kt index 8577422..0a7a4f4 100644 --- a/app/src/main/java/it/reyboz/bustorino/adapters/RouteAdapter.kt +++ b/app/src/main/java/it/reyboz/bustorino/adapters/RouteAdapter.kt @@ -1,59 +1,59 @@ package it.reyboz.bustorino.adapters import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView import androidx.cardview.widget.CardView import androidx.recyclerview.widget.RecyclerView import it.reyboz.bustorino.R import it.reyboz.bustorino.data.gtfs.GtfsRoute import java.lang.ref.WeakReference class RouteAdapter(val routes: List, - click: onItemClick, - private val layoutId: Int = R.layout.line_title_header) : + click: ItemClicker, + private val layoutId: Int = R.layout.line_title_header) : RecyclerView.Adapter() { - val clickreference: WeakReference + val clickreference: WeakReference init { clickreference = WeakReference(click) } class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { val descrptionTextView: TextView val nameTextView : TextView val innerCardView : CardView? init { // Define click listener for the ViewHolder's View nameTextView = view.findViewById(R.id.lineShortNameTextView) descrptionTextView = view.findViewById(R.id.lineDirectionTextView) innerCardView = view.findViewById(R.id.innerCardView) } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val view = LayoutInflater.from(parent.context) .inflate(layoutId, parent, false) return ViewHolder(view) } override fun getItemCount() = routes.size override fun onBindViewHolder(holder: ViewHolder, position: Int) { // Get element from your dataset at this position and replace the // contents of the view with that element val route = routes[position] holder.nameTextView.text = route.shortName holder.descrptionTextView.text = route.longName holder.itemView.setOnClickListener{ clickreference.get()?.onRouteItemClicked(route) } } - fun interface onItemClick{ + fun interface ItemClicker{ fun onRouteItemClicked(gtfsRoute: GtfsRoute) } } \ No newline at end of file diff --git a/app/src/main/java/it/reyboz/bustorino/adapters/RouteOnlyLineAdapter.kt b/app/src/main/java/it/reyboz/bustorino/adapters/RouteOnlyLineAdapter.kt index c780291..bce4356 100644 --- a/app/src/main/java/it/reyboz/bustorino/adapters/RouteOnlyLineAdapter.kt +++ b/app/src/main/java/it/reyboz/bustorino/adapters/RouteOnlyLineAdapter.kt @@ -1,48 +1,62 @@ package it.reyboz.bustorino.adapters import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView import androidx.recyclerview.widget.RecyclerView import it.reyboz.bustorino.R import it.reyboz.bustorino.backend.Palina +import java.lang.ref.WeakReference -class RouteOnlyLineAdapter (val routeNames: List) : +class RouteOnlyLineAdapter (val routeNames: List, + onItemClick: OnClick?) : RecyclerView.Adapter() { + + private val clickreference: WeakReference? + init { + clickreference = if(onItemClick!=null) WeakReference(onItemClick) else null + } + /** * Provide a reference to the type of views that you are using * (custom ViewHolder) */ class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { val textView: TextView init { // Define click listener for the ViewHolder's View textView = view.findViewById(R.id.routeBallID) } } - constructor(palina: Palina, showOnlyEmpty: Boolean): this(palina.routesNamesWithNoPassages) + constructor(palina: Palina, showOnlyEmpty: Boolean): this(palina.routesNamesWithNoPassages, null) // Create new views (invoked by the layout manager) override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder { // Create a new view, which defines the UI of the list item val view = LayoutInflater.from(viewGroup.context) .inflate(R.layout.round_line_header, viewGroup, false) return ViewHolder(view) } // Replace the contents of a view (invoked by the layout manager) override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) { // Get element from your dataset at this position and replace the // contents of the view with that element viewHolder.textView.text = routeNames[position] + viewHolder.itemView.setOnClickListener{ + clickreference?.get()?.onItemClick(position, routeNames[position]) + } } // Return the size of your dataset (invoked by the layout manager) override fun getItemCount() = routeNames.size + fun interface OnClick{ + fun onItemClick(index: Int, name: String) + } } diff --git a/app/src/main/java/it/reyboz/bustorino/data/GtfsRepository.kt b/app/src/main/java/it/reyboz/bustorino/data/GtfsRepository.kt index bba97c0..e41b57b 100644 --- a/app/src/main/java/it/reyboz/bustorino/data/GtfsRepository.kt +++ b/app/src/main/java/it/reyboz/bustorino/data/GtfsRepository.kt @@ -1,38 +1,42 @@ package it.reyboz.bustorino.data import android.content.Context import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import it.reyboz.bustorino.data.gtfs.* class GtfsRepository( val gtfsDao: GtfsDBDao ) { constructor(context: Context) : this(GtfsDatabase.getGtfsDatabase(context).gtfsDao()) fun getLinesLiveDataForFeed(feed: String): LiveData>{ //return withContext(Dispatchers.IO){ return gtfsDao.getRoutesForFeed(feed) //} } fun getPatternsForRouteID(routeID: String): LiveData>{ return if(routeID.isNotEmpty()) gtfsDao.getPatternsLiveDataByRouteID(routeID) else MutableLiveData(listOf()) } /** * Get the patterns with the stops lists (gtfsIDs only) */ fun getPatternsWithStopsForRouteID(routeID: String): LiveData>{ return if(routeID.isNotEmpty()) gtfsDao.getPatternsWithStopsByRouteID(routeID) else MutableLiveData(listOf()) } fun getAllRoutes(): LiveData>{ return gtfsDao.getAllRoutes() } + + fun getRouteFromGtfsId(gtfsId: String): LiveData{ + return gtfsDao.getRouteByGtfsID(gtfsId) + } } \ No newline at end of file diff --git a/app/src/main/java/it/reyboz/bustorino/data/PreferencesHolder.java b/app/src/main/java/it/reyboz/bustorino/data/PreferencesHolder.java index 477ec9b..3581adc 100644 --- a/app/src/main/java/it/reyboz/bustorino/data/PreferencesHolder.java +++ b/app/src/main/java/it/reyboz/bustorino/data/PreferencesHolder.java @@ -1,62 +1,91 @@ /* BusTO - Data components Copyright (C) 2021 Fabio Mazza 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 it.reyboz.bustorino.data; import android.content.Context; import android.content.SharedPreferences; +import android.util.Log; import it.reyboz.bustorino.R; import static android.content.Context.MODE_PRIVATE; import androidx.preference.PreferenceManager; +import java.util.HashSet; +import java.util.Set; + /** * Static class for commonly used SharedPreference operations */ public abstract class PreferencesHolder { public static final String PREF_GTFS_DB_VERSION = "gtfs_db_version"; public static final String PREF_INTRO_ACTIVITY_RUN ="pref_intro_activity_run"; + public static final String PREF_FAVORITE_LINES = "pref_favorite_lines"; + public static SharedPreferences getMainSharedPreferences(Context context){ return context.getSharedPreferences(context.getString(R.string.mainSharedPreferences), MODE_PRIVATE); } public static SharedPreferences getAppPreferences(Context con){ return PreferenceManager.getDefaultSharedPreferences(con); } public static int getGtfsDBVersion(SharedPreferences pref){ return pref.getInt(PREF_GTFS_DB_VERSION,-1); } public static void setGtfsDBVersion(SharedPreferences pref,int version){ SharedPreferences.Editor ed = pref.edit(); ed.putInt(PREF_GTFS_DB_VERSION,version); ed.apply(); } /** * Check if the introduction activity has been run at least one * @param con the context needed * @return true if it has been run */ public static boolean hasIntroFinishedOneShot(Context con){ final SharedPreferences pref = getMainSharedPreferences(con); return pref.getBoolean(PREF_INTRO_ACTIVITY_RUN, false); } + + public static boolean addOrRemoveLineToFavorites(Context con, String gtfsLineId, boolean addToFavorites){ + final SharedPreferences pref = getMainSharedPreferences(con); + final HashSet favorites = new HashSet<>(pref.getStringSet(PREF_FAVORITE_LINES, new HashSet<>())); + boolean modified = true; + if(addToFavorites) + favorites.add(gtfsLineId); + else if(favorites.contains(gtfsLineId)) + favorites.remove(gtfsLineId); + else + modified = false; // we are not changing anything + if(modified) { + final SharedPreferences.Editor editor = pref.edit(); + editor.putStringSet(PREF_FAVORITE_LINES, favorites); + editor.apply(); + } + return modified; + } + + public static HashSet getFavoritesLinesGtfsIDs(Context con){ + final SharedPreferences pref = getMainSharedPreferences(con); + return new HashSet<>(pref.getStringSet(PREF_FAVORITE_LINES, new HashSet<>())); + } } diff --git a/app/src/main/java/it/reyboz/bustorino/data/gtfs/GtfsDBDao.kt b/app/src/main/java/it/reyboz/bustorino/data/gtfs/GtfsDBDao.kt index b1c02c6..f36f360 100644 --- a/app/src/main/java/it/reyboz/bustorino/data/gtfs/GtfsDBDao.kt +++ b/app/src/main/java/it/reyboz/bustorino/data/gtfs/GtfsDBDao.kt @@ -1,156 +1,156 @@ /* BusTO - Data components Copyright (C) 2021 Fabio Mazza 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 it.reyboz.bustorino.data.gtfs import androidx.lifecycle.LiveData import androidx.room.* @Dao interface GtfsDBDao { // get queries @Query("SELECT * FROM "+GtfsRoute.DB_TABLE) fun getAllRoutes() : LiveData> - @Query("SELECT * FROM ${GtfsRoute.DB_TABLE} WHERE ${GtfsRoute.COL_ROUTE_ID} IN (:routeGtfsIds)") - fun getRoutesByIDs(routeGtfsIds: List): LiveData> + @Query("SELECT * FROM ${GtfsRoute.DB_TABLE} WHERE ${GtfsRoute.COL_ROUTE_ID} LIKE :gtfsId") + fun getRouteByGtfsID(gtfsId: String) : LiveData @Query("SELECT "+GtfsTrip.COL_TRIP_ID+" FROM "+GtfsTrip.DB_TABLE) fun getAllTripsIDs() : List @Query("SELECT "+GtfsStop.COL_STOP_ID+" FROM "+GtfsStop.DB_TABLE) fun getAllStopsIDs() : List @Query("SELECT * FROM "+GtfsStop.DB_TABLE+" WHERE "+GtfsStop.COL_STOP_CODE+" LIKE :queryID") fun getStopByStopID(queryID: String): LiveData> @Query("SELECT * FROM "+GtfsShape.DB_TABLE+ " WHERE "+GtfsShape.COL_SHAPE_ID+" LIKE :shapeID"+ " ORDER BY "+GtfsShape.COL_POINT_SEQ+ " ASC" ) fun getShapeByID(shapeID: String) : LiveData> @Query("SELECT * FROM ${GtfsRoute.DB_TABLE} WHERE ${GtfsRoute.COL_AGENCY_ID} LIKE :agencyID") fun getRoutesByAgency(agencyID:String) : LiveData> @Query("SELECT ${MatoPattern.COL_CODE} FROM ${MatoPattern.TABLE_NAME} ") fun getPatternsCodes(): List @Query("SELECT * FROM ${MatoPattern.TABLE_NAME} WHERE ${MatoPattern.COL_ROUTE_ID} LIKE :routeID") fun getPatternsLiveDataByRouteID(routeID: String): LiveData> @Query("SELECT * FROM "+MatoPattern.TABLE_NAME+" WHERE ${MatoPattern.COL_ROUTE_ID} LIKE :routeGtfsId") fun getPatternsForRouteID(routeGtfsId: String) : List @Query("SELECT * FROM ${PatternStop.TABLE_NAME} WHERE ${PatternStop.COL_PATTERN_ID} LIKE :patternGtfsID") fun getStopsByPatternID(patternGtfsID: String): LiveData> @Transaction @Query("SELECT * FROM ${MatoPattern.TABLE_NAME} WHERE ${MatoPattern.COL_ROUTE_ID} LIKE :routeID") fun getPatternsWithStopsByRouteID(routeID: String): LiveData> @Transaction @Query("SELECT * FROM ${MatoPattern.TABLE_NAME} WHERE ${MatoPattern.COL_CODE} IN (:patternGtfsIDs)") fun getPatternsWithStopsFromIDs(patternGtfsIDs: List) : LiveData> @Query("SELECT ${MatoPattern.COL_CODE} FROM ${MatoPattern.TABLE_NAME} WHERE ${MatoPattern.COL_CODE} IN (:codes)") fun getPatternsCodesInTheDB(codes: List): List @Transaction @Query("SELECT * FROM ${GtfsTrip.DB_TABLE} WHERE ${GtfsTrip.COL_TRIP_ID} IN (:tripsIds)") fun getTripsFromIDs(tripsIds: List) : List @Transaction @Query("SELECT * FROM ${GtfsTrip.DB_TABLE} WHERE ${GtfsTrip.COL_TRIP_ID} IN (:trips)") fun getTripPatternStops(trips: List): LiveData> fun getRoutesForFeed(feed:String): LiveData>{ val agencyID = "${feed}:%" return getRoutesByAgency(agencyID) } @Transaction fun clearAndInsertRoutes(routes: List){ deleteAllRoutes() insertRoutes(routes) } @Transaction @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertRoutes(routes: List) @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertStops(stops: List) @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertCalendarServices(services: List) @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertShapes(shapes: List) @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertDates(dates: List) @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertServices(services: List) @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertTrips(trips: List) @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertStopTimes(stopTimes: List) @Query("DELETE FROM "+GtfsRoute.DB_TABLE) fun deleteAllRoutes() @Query("DELETE FROM "+GtfsStop.DB_TABLE) fun deleteAllStops() @Query("DELETE FROM "+GtfsTrip.DB_TABLE) fun deleteAllTrips() @Query("DELETE FROM ${MatoPattern.TABLE_NAME} WHERE ${MatoPattern.COL_CODE} IN (:codes)") fun deletePatternsWithCodes(codes: List): Int @Update(onConflict = OnConflictStrategy.REPLACE) fun updateShapes(shapes: List) : Int @Transaction fun updateAllStops(stops: List){ deleteAllStops() insertStops(stops) } @Query("DELETE FROM "+GtfsStopTime.DB_TABLE) fun deleteAllStopTimes() @Query("DELETE FROM "+GtfsService.DB_TABLE) fun deleteAllServices() @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertFeeds(feeds: List) @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertAgencies(agencies: List) @Transaction fun insertAgenciesWithFeeds(feeds: List, agencies: List){ insertFeeds(feeds) insertAgencies(agencies) } //patterns @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertPatterns(patterns: List) @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertPatternStops(patternStops: List) } \ No newline at end of file diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/ArrivalsFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/ArrivalsFragment.java index 74333ce..fca2a6c 100644 --- a/app/src/main/java/it/reyboz/bustorino/fragments/ArrivalsFragment.java +++ b/app/src/main/java/it/reyboz/bustorino/fragments/ArrivalsFragment.java @@ -1,693 +1,693 @@ /* BusTO - Fragments components Copyright (C) 2018 Fabio Mazza 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 it.reyboz.bustorino.fragments; import android.annotation.SuppressLint; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.widget.*; import androidx.annotation.Nullable; import androidx.annotation.NonNull; import androidx.core.widget.NestedScrollView; import androidx.loader.app.LoaderManager; import androidx.loader.content.CursorLoader; import androidx.loader.content.Loader; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import java.util.ArrayList; import java.util.Collections; import java.util.List; import androidx.recyclerview.widget.DividerItemDecoration; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import it.reyboz.bustorino.R; import it.reyboz.bustorino.adapters.AdapterClickListener; import it.reyboz.bustorino.adapters.PalinaAdapter; import it.reyboz.bustorino.adapters.RouteOnlyLineAdapter; import it.reyboz.bustorino.backend.ArrivalsFetcher; import it.reyboz.bustorino.backend.DBStatusManager; import it.reyboz.bustorino.backend.Fetcher; import it.reyboz.bustorino.backend.FiveTNormalizer; import it.reyboz.bustorino.backend.Palina; import it.reyboz.bustorino.backend.Passaggio; import it.reyboz.bustorino.backend.Route; import it.reyboz.bustorino.backend.Stop; import it.reyboz.bustorino.backend.utils; import it.reyboz.bustorino.data.AppDataProvider; import it.reyboz.bustorino.data.NextGenDB; import it.reyboz.bustorino.data.UserDB; import it.reyboz.bustorino.middleware.AsyncStopFavoriteAction; import it.reyboz.bustorino.util.LinesNameSorter; import it.reyboz.bustorino.util.ViewUtils; import static it.reyboz.bustorino.fragments.ScreenBaseFragment.setOption; public class ArrivalsFragment extends ResultBaseFragment implements LoaderManager.LoaderCallbacks { private static final String OPTION_SHOW_LEGEND = "show_legend"; private final static String KEY_STOP_ID = "stopid"; private final static String KEY_STOP_NAME = "stopname"; private final static String DEBUG_TAG_ALL = "BUSTOArrivalsFragment"; private String DEBUG_TAG = DEBUG_TAG_ALL; private final static int loaderFavId = 2; private final static int loaderStopId = 1; static final String STOP_TITLE = "messageExtra"; private final static String SOURCES_TEXT="sources_textview_message"; private @Nullable String stopID,stopName; private DBStatusManager prefs; private DBStatusManager.OnDBUpdateStatusChangeListener listener; private boolean justCreated = false; private Palina lastUpdatedPalina = null; private boolean needUpdateOnAttach = false; private boolean fetchersChangeRequestPending = false; private boolean stopIsInFavorites = false; //Views protected ImageButton addToFavorites; protected TextView timesSourceTextView; protected TextView messageTextView; protected RecyclerView arrivalsRecyclerView; private PalinaAdapter mListAdapter = null; private TextView howDoesItWorkTextView; private Button hideHintButton; //private NestedScrollView theScrollView; protected RecyclerView noArrivalsRecyclerView; private RouteOnlyLineAdapter noArrivalsAdapter; private TextView noArrivalsTitleView; private GridLayoutManager layoutManager; //private View canaryEndView; private List fetchers = null; //new ArrayList<>(Arrays.asList(utils.getDefaultArrivalsFetchers())); private boolean reloadOnResume = true; private final AdapterClickListener mRouteClickListener = route -> { String routeName; routeName = FiveTNormalizer.routeInternalToDisplay(route.getNameForDisplay()); if (routeName == null) { routeName = route.getNameForDisplay(); } if(getContext()==null) Log.e(DEBUG_TAG, "Touched on a route but Context is null"); else if (route.destinazione == null || route.destinazione.length() == 0) { Toast.makeText(getContext(), getString(R.string.route_towards_unknown, routeName), Toast.LENGTH_SHORT).show(); } else { Toast.makeText(getContext(), getString(R.string.route_towards_destination, routeName, route.destinazione), Toast.LENGTH_SHORT).show(); } }; public static ArrivalsFragment newInstance(String stopID){ return newInstance(stopID, null); } public static ArrivalsFragment newInstance(@NonNull String stopID, @Nullable String stopName){ ArrivalsFragment fragment = new ArrivalsFragment(); Bundle args = new Bundle(); args.putString(KEY_STOP_ID,stopID); //parameter for ResultListFragmentrequestArrivalsForStopID //args.putSerializable(LIST_TYPE,FragmentKind.ARRIVALS); if (stopName != null){ args.putString(KEY_STOP_NAME,stopName); } fragment.setArguments(args); return fragment; } public static String getFragmentTag(Palina p) { return "palina_"+p.ID; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); stopID = getArguments().getString(KEY_STOP_ID); DEBUG_TAG = DEBUG_TAG_ALL+" "+stopID; //this might really be null stopName = getArguments().getString(KEY_STOP_NAME); final ArrivalsFragment arrivalsFragment = this; listener = new DBStatusManager.OnDBUpdateStatusChangeListener() { @Override public void onDBStatusChanged(boolean updating) { if(!updating){ getLoaderManager().restartLoader(loaderFavId,getArguments(),arrivalsFragment); } else { final LoaderManager lm = getLoaderManager(); lm.destroyLoader(loaderFavId); lm.destroyLoader(loaderStopId); } } @Override public boolean defaultStatusValue() { return true; } }; prefs = new DBStatusManager(getContext().getApplicationContext(),listener); justCreated = true; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View root = inflater.inflate(R.layout.fragment_arrivals, container, false); messageTextView = root.findViewById(R.id.messageTextView); addToFavorites = root.findViewById(R.id.addToFavorites); // "How does it work part" howDoesItWorkTextView = root.findViewById(R.id.howDoesItWorkTextView); hideHintButton = root.findViewById(R.id.hideHintButton); hideHintButton.setOnClickListener(this::onHideHint); //theScrollView = root.findViewById(R.id.arrivalsScrollView); // recyclerview holding the arrival times arrivalsRecyclerView = root.findViewById(R.id.arrivalsRecyclerView); final LinearLayoutManager manager = new LinearLayoutManager(getContext()); arrivalsRecyclerView.setLayoutManager(manager); final DividerItemDecoration mDividerItemDecoration = new DividerItemDecoration(arrivalsRecyclerView.getContext(), manager.getOrientation()); arrivalsRecyclerView.addItemDecoration(mDividerItemDecoration); timesSourceTextView = root.findViewById(R.id.timesSourceTextView); timesSourceTextView.setOnLongClickListener(view -> { if(!fetchersChangeRequestPending){ rotateFetchers(); //Show we are changing provider timesSourceTextView.setText(R.string.arrival_source_changing); mListener.requestArrivalsForStopID(stopID); fetchersChangeRequestPending = true; return true; } return false; }); timesSourceTextView.setOnClickListener(view -> { Toast.makeText(getContext(), R.string.change_arrivals_source_message, Toast.LENGTH_SHORT) .show(); }); //Button addToFavorites.setClickable(true); addToFavorites.setOnClickListener(v -> { // add/remove the stop in the favorites toggleLastStopToFavorites(); }); String displayName = getArguments().getString(STOP_TITLE); if(displayName!=null) setTextViewMessage(String.format( getString(R.string.passages), displayName)); String probablemessage = getArguments().getString(MESSAGE_TEXT_VIEW); if (probablemessage != null) { //Log.d("BusTO fragment " + this.getTag(), "We have a possible message here in the savedInstaceState: " + probablemessage); messageTextView.setText(probablemessage); messageTextView.setVisibility(View.VISIBLE); } //no arrivals stuff noArrivalsRecyclerView = root.findViewById(R.id.noArrivalsRecyclerView); layoutManager = new GridLayoutManager(getContext(),60); layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { return 12; } }); noArrivalsRecyclerView.setLayoutManager(layoutManager); noArrivalsTitleView = root.findViewById(R.id.noArrivalsMessageTextView); //canaryEndView = root.findViewById(R.id.canaryEndView); /*String sourcesTextViewData = getArguments().getString(SOURCES_TEXT); if (sourcesTextViewData!=null){ timesSourceTextView.setText(sourcesTextViewData); }*/ //need to do this when we recreate the fragment but we haven't updated the arrival times if (lastUpdatedPalina!=null) showArrivalsSources(lastUpdatedPalina); return root; } @Override public void onResume() { super.onResume(); LoaderManager loaderManager = getLoaderManager(); Log.d(DEBUG_TAG, "OnResume, justCreated "+justCreated+", lastUpdatedPalina is: "+lastUpdatedPalina); /*if(needUpdateOnAttach){ updateFragmentData(null); needUpdateOnAttach=false; }*/ /*if(lastUpdatedPalina!=null){ updateFragmentData(null); showArrivalsSources(lastUpdatedPalina); }*/ mListener.readyGUIfor(FragmentKind.ARRIVALS); if (mListAdapter!=null) resetListAdapter(mListAdapter); if(noArrivalsAdapter!=null){ noArrivalsRecyclerView.setAdapter(noArrivalsAdapter); } if(stopID!=null){ if(!justCreated){ fetchers = utils.getDefaultArrivalsFetchers(getContext()); adjustFetchersToSource(); if (reloadOnResume) mListener.requestArrivalsForStopID(stopID); } else justCreated = false; //start the loader if(prefs.isDBUpdating(true)){ prefs.registerListener(); } else { Log.d(DEBUG_TAG, "Restarting loader for stop"); loaderManager.restartLoader(loaderFavId, getArguments(), this); } updateMessage(); } if (ScreenBaseFragment.getOption(requireContext(),OPTION_SHOW_LEGEND, true)) { showHints(); } } @Override public void onStart() { super.onStart(); if (needUpdateOnAttach){ updateFragmentData(null); needUpdateOnAttach = false; } } @Override public void onPause() { if(listener!=null) prefs.unregisterListener(); super.onPause(); LoaderManager loaderManager = getLoaderManager(); Log.d(DEBUG_TAG, "onPause, have running loaders: "+loaderManager.hasRunningLoaders()); loaderManager.destroyLoader(loaderFavId); } @Override public void onAttach(@NonNull Context context) { super.onAttach(context); //get fetchers fetchers = utils.getDefaultArrivalsFetchers(context); } @Nullable public String getStopID() { return stopID; } public boolean reloadsOnResume() { return reloadOnResume; } public void setReloadOnResume(boolean reloadOnResume) { this.reloadOnResume = reloadOnResume; } // HINT "HOW TO USE" private void showHints() { howDoesItWorkTextView.setVisibility(View.VISIBLE); hideHintButton.setVisibility(View.VISIBLE); //actionHelpMenuItem.setVisible(false); } private void hideHints() { howDoesItWorkTextView.setVisibility(View.GONE); hideHintButton.setVisibility(View.GONE); //actionHelpMenuItem.setVisible(true); } public void onHideHint(View v) { hideHints(); setOption(requireContext(),OPTION_SHOW_LEGEND, false); } /** * Give the fetchers * @return the list of the fetchers */ public ArrayList getCurrentFetchers(){ return new ArrayList<>(this.fetchers); } public ArrivalsFetcher[] getCurrentFetchersAsArray(){ ArrivalsFetcher[] arr = new ArrivalsFetcher[fetchers.size()]; fetchers.toArray(arr); return arr; } private void rotateFetchers(){ Log.d(DEBUG_TAG, "Rotating fetchers, before: "+fetchers); Collections.rotate(fetchers, -1); Log.d(DEBUG_TAG, "Rotating fetchers, afterwards: "+fetchers); } /** * Update the UI with the new data * @param p the full Palina */ public void updateFragmentData(@Nullable Palina p){ if (p!=null) lastUpdatedPalina = p; if (!isAdded()){ //defer update at next show if (p==null) Log.w(DEBUG_TAG, "Asked to update the data, but we're not attached and the data is null"); else needUpdateOnAttach = true; } else { final PalinaAdapter adapter = new PalinaAdapter(getContext(), lastUpdatedPalina, mRouteClickListener, true); showArrivalsSources(lastUpdatedPalina); resetListAdapter(adapter); final ArrayList routesWithNoPassages = lastUpdatedPalina.getRoutesNamesWithNoPassages(); Collections.sort(routesWithNoPassages, new LinesNameSorter()); - noArrivalsAdapter = new RouteOnlyLineAdapter(routesWithNoPassages); + noArrivalsAdapter = new RouteOnlyLineAdapter(routesWithNoPassages, null); if(noArrivalsRecyclerView!=null){ noArrivalsRecyclerView.setAdapter(noArrivalsAdapter); //hide the views if there are no empty routes if(routesWithNoPassages.isEmpty()){ noArrivalsRecyclerView.setVisibility(View.GONE); noArrivalsTitleView.setVisibility(View.GONE); } else { noArrivalsRecyclerView.setVisibility(View.VISIBLE); noArrivalsTitleView.setVisibility(View.VISIBLE); } } //canaryEndView.setVisibility(View.VISIBLE); //check if canaryEndView is visible //boolean isCanaryVisibile = ViewUtils.Companion.isViewPartiallyVisibleInScroll(canaryEndView, theScrollView); //Log.d(DEBUG_TAG, "Canary view fully visibile: "+isCanaryVisibile); } } /** * Set the message of the arrival times source * @param p Palina with the arrival times */ protected void showArrivalsSources(Palina p){ final Passaggio.Source source = p.getPassaggiSourceIfAny(); if (source == null){ Log.e(DEBUG_TAG, "NULL SOURCE"); return; } String source_txt; switch (source){ case GTTJSON: source_txt = getString(R.string.gttjsonfetcher); break; case FiveTAPI: source_txt = getString(R.string.fivetapifetcher); break; case FiveTScraper: source_txt = getString(R.string.fivetscraper); break; case MatoAPI: source_txt = getString(R.string.source_mato); break; case UNDETERMINED: //Don't show the view source_txt = getString(R.string.undetermined_source); break; default: throw new IllegalStateException("Unexpected value: " + source); } // final boolean updatedFetchers = adjustFetchersToSource(source); if(!updatedFetchers) Log.w(DEBUG_TAG, "Tried to update the source fetcher but it didn't work"); final String base_message = getString(R.string.times_source_fmt, source_txt); timesSourceTextView.setText(base_message); timesSourceTextView.setVisibility(View.VISIBLE); if (p.getTotalNumberOfPassages() > 0) { timesSourceTextView.setVisibility(View.VISIBLE); } else { timesSourceTextView.setVisibility(View.INVISIBLE); } fetchersChangeRequestPending = false; } protected boolean adjustFetchersToSource(Passaggio.Source source){ if (source == null) return false; int count = 0; if (source!= Passaggio.Source.UNDETERMINED) while (source != fetchers.get(0).getSourceForFetcher() && count < 200){ //we need to update the fetcher that is requested rotateFetchers(); count++; } return count < 200; } protected boolean adjustFetchersToSource(){ if (lastUpdatedPalina == null) return false; final Passaggio.Source source = lastUpdatedPalina.getPassaggiSourceIfAny(); return adjustFetchersToSource(source); } /** * Update the message in the fragment * * It may eventually change the "Add to Favorite" icon */ private void updateMessage(){ String message = null; if (stopName != null && stopID != null && stopName.length() > 0) { message = (stopID.concat(" - ").concat(stopName)); } else if(stopID!=null) { message = stopID; } else { Log.e("ArrivalsFragm"+getTag(),"NO ID FOR THIS FRAGMENT - something went horribly wrong"); } if(message!=null) { setTextViewMessage(getString(R.string.passages,message)); } // whatever is the case, update the star icon //updateStarIconFromLastBusStop(); } @NonNull @Override public Loader onCreateLoader(int id, Bundle args) { if(args.getString(KEY_STOP_ID)==null) return null; final String stopID = args.getString(KEY_STOP_ID); final Uri.Builder builder = AppDataProvider.getUriBuilderToComplete(); CursorLoader cl; switch (id){ case loaderFavId: builder.appendPath("favorites").appendPath(stopID); cl = new CursorLoader(getContext(),builder.build(),UserDB.getFavoritesColumnNamesAsArray,null,null,null); break; case loaderStopId: builder.appendPath("stop").appendPath(stopID); cl = new CursorLoader(getContext(),builder.build(),new String[]{NextGenDB.Contract.StopsTable.COL_NAME}, null,null,null); break; default: return null; } cl.setUpdateThrottle(500); return cl; } @Override public void onLoadFinished(Loader loader, Cursor data) { switch (loader.getId()){ case loaderFavId: final int colUserName = data.getColumnIndex(UserDB.getFavoritesColumnNamesAsArray[1]); if(data.getCount()>0){ // IT'S IN FAVORITES data.moveToFirst(); final String probableName = data.getString(colUserName); stopIsInFavorites = true; stopName = probableName; //update the message in the textview updateMessage(); } else { stopIsInFavorites =false; } updateStarIcon(); if(stopName == null){ //stop is not inside the favorites and wasn't provided Log.d("ArrivalsFragment"+getTag(),"Stop wasn't in the favorites and has no name, looking in the DB"); getLoaderManager().restartLoader(loaderStopId,getArguments(),this); } break; case loaderStopId: if(data.getCount()>0){ data.moveToFirst(); int index = data.getColumnIndex( NextGenDB.Contract.StopsTable.COL_NAME ); if (index == -1){ Log.e(DEBUG_TAG, "Index is -1, column not present. App may explode now..."); } stopName = data.getString(index); updateMessage(); } else { Log.w("ArrivalsFragment"+getTag(),"Stop is not inside the database... CLOISTER BELL"); } } } @Override public void onLoaderReset(Loader loader) { //NOTHING TO DO } protected void resetListAdapter(PalinaAdapter adapter) { mListAdapter = adapter; if (arrivalsRecyclerView != null) { arrivalsRecyclerView.setAdapter(adapter); arrivalsRecyclerView.setVisibility(View.VISIBLE); } } /** * Set the message textView * @param message the whole message to write in the textView */ public void setTextViewMessage(String message) { messageTextView.setText(message); messageTextView.setVisibility(View.VISIBLE); } public void toggleLastStopToFavorites() { Stop stop = lastUpdatedPalina; if (stop != null) { // toggle the status in background new AsyncStopFavoriteAction(getContext().getApplicationContext(), AsyncStopFavoriteAction.Action.TOGGLE, v->updateStarIconFromLastBusStop(v)).execute(stop); } else { // this case have no sense, but just immediately update the favorite icon updateStarIconFromLastBusStop(true); } } /** * Update the star "Add to favorite" icon */ public void updateStarIconFromLastBusStop(Boolean toggleDone) { if (stopIsInFavorites) stopIsInFavorites = !toggleDone; else stopIsInFavorites = toggleDone; updateStarIcon(); // check if there is a last Stop /* if (stopID == null) { addToFavorites.setVisibility(View.INVISIBLE); } else { // filled or outline? if (isStopInFavorites(stopID)) { addToFavorites.setImageResource(R.drawable.ic_star_filled); } else { addToFavorites.setImageResource(R.drawable.ic_star_outline); } addToFavorites.setVisibility(View.VISIBLE); } */ } /** * Update the star icon according to `stopIsInFavorites` */ public void updateStarIcon() { // no favorites no party! // check if there is a last Stop if (stopID == null) { addToFavorites.setVisibility(View.INVISIBLE); } else { // filled or outline? if (stopIsInFavorites) { addToFavorites.setImageResource(R.drawable.ic_star_filled); } else { addToFavorites.setImageResource(R.drawable.ic_star_outline); } addToFavorites.setVisibility(View.VISIBLE); } } @Override public void onDestroyView() { arrivalsRecyclerView = null; if(getArguments()!=null) { getArguments().putString(SOURCES_TEXT, timesSourceTextView.getText().toString()); getArguments().putString(MESSAGE_TEXT_VIEW, messageTextView.getText().toString()); } super.onDestroyView(); } public boolean isFragmentForTheSameStop(Palina p) { if (getTag() != null) return getTag().equals(getFragmentTag(p)); else return false; } } diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/IntroFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/IntroFragment.kt index 5006d75..7cd18a3 100644 --- a/app/src/main/java/it/reyboz/bustorino/fragments/IntroFragment.kt +++ b/app/src/main/java/it/reyboz/bustorino/fragments/IntroFragment.kt @@ -1,151 +1,154 @@ package it.reyboz.bustorino.fragments import android.content.Context import android.graphics.BitmapFactory import android.graphics.text.LineBreaker import android.os.Build import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Button +import android.widget.ImageButton import android.widget.ImageView import android.widget.TextView import androidx.fragment.app.Fragment import it.reyboz.bustorino.R import it.reyboz.bustorino.backend.utils import java.lang.IllegalStateException // TODO: Rename parameter arguments, choose names that match // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER private const val SCREEN_INDEX = "screenindex" /** * A simple [Fragment] subclass. * Use the [IntroFragment.newInstance] factory method to * create an instance of this fragment. */ class IntroFragment : Fragment() { // TODO: Rename and change types of parameters private var screenIndex = 1 private lateinit var imageHolder: ImageView private lateinit var textView: TextView + private lateinit var listener: IntroListener override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) arguments?.let { screenIndex = it.getInt(SCREEN_INDEX) } } override fun onAttach(context: Context) { super.onAttach(context) if(context !is IntroListener){ throw IllegalStateException("Context must implement IntroListener") } listener = context } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { // Inflate the layout for this fragment val root= inflater.inflate(R.layout.fragment_intro, container, false) imageHolder = root.findViewById(R.id.image_tutorial) textView = root.findViewById(R.id.tutorialTextView) when(screenIndex){ 0 -> { setImageBitmap(imageHolder, R.drawable.tuto_busto, 300f) textView.text = utils.convertHtml(getString(R.string.tutorial_first)) } 1->{ setImageBitmap(imageHolder, R.drawable.tuto_search) setTextHtmlDescription(R.string.tutorial_search) } 2 ->{ setImageBitmap(imageHolder, R.drawable.tuto_arrivals) textView.text = utils.convertHtml(getString(R.string.tutorial_arrivals)) } 3 ->{ setImageBitmap(imageHolder, R.drawable.tuto_stops) textView.text = utils.convertHtml(getString(R.string.tutorial_stops)) } 4 ->{ setImageBitmap(imageHolder, R.drawable.tuto_map) textView.text = utils.convertHtml(getString(R.string.tutorial_map)) } 5 ->{ setImageBitmap(imageHolder, R.drawable.tuto_line_det) textView.text = utils.convertHtml(getString(R.string.tutorial_line)) } 6-> { setImageBitmap(imageHolder,R.drawable.tuto_menu) setTextHtmlDescription(R.string.tutorial_menu) + //this is the cheapest trick ever lol val closeButton = root.findViewById