diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -13,10 +13,12 @@ + + @@ -35,6 +37,9 @@ android:networkSecurityConfig="@xml/networks_security_config" android:roundIcon="@mipmap/ic_launcher_round" android:theme="@style/AppTheme.NoActionBar"> + @@ -102,8 +107,7 @@ android:authorities="it.reyboz.bustorino.provider" android:enabled="true" android:exported="false"> - - + diff --git a/app/src/main/java/it/reyboz/bustorino/ActivityAbout.java b/app/src/main/java/it/reyboz/bustorino/ActivityAbout.java --- a/app/src/main/java/it/reyboz/bustorino/ActivityAbout.java +++ b/app/src/main/java/it/reyboz/bustorino/ActivityAbout.java @@ -32,6 +32,7 @@ import android.widget.TextView; import android.widget.Toast; +import it.reyboz.bustorino.backend.utils; import it.reyboz.bustorino.middleware.BarcodeScanUtils; public class ActivityAbout extends AppCompatActivity { @@ -40,7 +41,7 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_about); - Spanned htmlText = Html.fromHtml(getResources().getString( + Spanned htmlText = utils.convertHtml(getResources().getString( R.string.about_history)); TextView aboutTextView = findViewById(R.id.aboutTextView); assert aboutTextView != null; 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 @@ -36,7 +36,7 @@ ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { - actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setDisplayHomeAsUpEnabled(false); actionBar.setIcon(R.drawable.ic_launcher); } if (savedInstanceState==null) { @@ -48,13 +48,14 @@ LinesDetailFragment.Companion.makeArgs("gtt:4U")) */ - .add(R.id.fragment_container_view, LinesGridShowingFragment.class, null) - .commit(); + //.add(R.id.fragment_container_view, LinesGridShowingFragment.class, null) + //.add(R.id.fragment_container_view, IntroFragment.class, IntroFragment.makeArguments(0)) + //.commit(); //.add(R.id.fragment_container_view, LinesDetailFragment.class, // LinesDetailFragment.Companion.makeArgs("gtt:4U")) - //.add(R.id.fragment_container_view, TestRealtimeGtfsFragment.class, null) - //.commit(); + .add(R.id.fragment_container_view, TestRealtimeGtfsFragment.class, null) + .commit(); } } diff --git a/app/src/main/java/it/reyboz/bustorino/ActivityIntro.kt b/app/src/main/java/it/reyboz/bustorino/ActivityIntro.kt new file mode 100644 --- /dev/null +++ b/app/src/main/java/it/reyboz/bustorino/ActivityIntro.kt @@ -0,0 +1,107 @@ +package it.reyboz.bustorino + +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.view.View +import android.widget.ImageButton +import androidx.appcompat.app.AppCompatActivity +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 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) + + 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) + } + + 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 + } + } + + }) + } + + + + /** + * 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/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 @@ -82,26 +82,8 @@ super.onCreate(savedInstanceState); Log.d(DEBUG_TAG, "onCreate, savedInstanceState is: "+savedInstanceState); setContentView(R.layout.activity_principal); - final SharedPreferences theShPr = getMainSharedPreferences(); boolean showingArrivalsFromIntent = false; - //database check - GtfsDatabase gtfsDB = GtfsDatabase.Companion.getGtfsDatabase(this); - - final int db_version = gtfsDB.getOpenHelper().getReadableDatabase().getVersion(); - boolean dataUpdateRequested = false; - final int old_version = PreferencesHolder.getGtfsDBVersion(theShPr); - Log.d(DEBUG_TAG, "GTFS Database: old version is "+old_version+ ", new version is "+db_version); - if (old_version < db_version){ - //decide update conditions in the future - if(old_version < 2 && db_version >= 2) { - dataUpdateRequested = true; - DatabaseUpdate.requestDBUpdateWithWork(this, true, true); - } - PreferencesHolder.setGtfsDBVersion(theShPr, db_version); - } - - Toolbar mToolbar = findViewById(R.id.default_toolbar); setSupportActionBar(mToolbar); if (getSupportActionBar()!=null) @@ -200,6 +182,23 @@ requestArrivalsForStopID(busStopID); //this shows the fragment, too showingArrivalsFromIntent = true; } + //database check + GtfsDatabase gtfsDB = GtfsDatabase.Companion.getGtfsDatabase(this); + + final int db_version = gtfsDB.getOpenHelper().getReadableDatabase().getVersion(); + boolean dataUpdateRequested = false; + final SharedPreferences theShPr = getMainSharedPreferences(); + + final int old_version = PreferencesHolder.getGtfsDBVersion(theShPr); + Log.d(DEBUG_TAG, "GTFS Database: old version is "+old_version+ ", new version is "+db_version); + if (old_version < db_version){ + //decide update conditions in the future + if(old_version < 2 && db_version >= 2) { + dataUpdateRequested = true; + DatabaseUpdate.requestDBUpdateWithWork(this, true, true); + } + PreferencesHolder.setGtfsDBVersion(theShPr, db_version); + } //Try (hopefully) database update if(!dataUpdateRequested) DatabaseUpdate.requestDBUpdateWithWork(this, false, false); @@ -259,6 +258,12 @@ //last but not least, set the good default values manageDefaultValuesForSettings(); + + //check if first run activity (IntroActivity) has been started once or not + boolean hasIntroRun = theShPr.getBoolean(PreferencesHolder.PREF_INTRO_ACTIVITY_RUN,false); + if(!hasIntroRun){ + startIntroductionActivity(); + } } private ActionBarDrawerToggle setupDrawerToggle(Toolbar toolbar) { // NOTE: Make sure you pass in a valid toolbar reference. ActionBarDrawToggle() does not require it @@ -703,6 +708,12 @@ ft.commit(); } + void startIntroductionActivity(){ + Intent intent = new Intent(ActivityPrincipal.this, ActivityIntro.class); + intent.putExtra(ActivityIntro.RESTART_MAIN, false); + startActivity(intent); + } + class ToolbarItemClickListener implements Toolbar.OnMenuItemClickListener{ private final Context activityContext; @@ -712,23 +723,27 @@ @Override public boolean onMenuItemClick(MenuItem item) { - switch (item.getItemId()) { - case R.id.action_about: - startActivity(new Intent(ActivityPrincipal.this, ActivityAbout.class)); - return true; - case R.id.action_hack: - openIceweasel(getString(R.string.hack_url), activityContext); - return true; - case R.id.action_source: - openIceweasel("https://gitpull.it/source/libre-busto/", activityContext); - return true; - case R.id.action_licence: - openIceweasel("https://www.gnu.org/licenses/gpl-3.0.html", activityContext); - return true; - case R.id.action_experiments: - startActivity(new Intent(ActivityPrincipal.this, ActivityExperiments.class)); - default: + final int id = item.getItemId(); + if(id == R.id.action_about){ + startActivity(new Intent(ActivityPrincipal.this, ActivityAbout.class)); + return true; + } else if (id == R.id.action_hack) { + openIceweasel(getString(R.string.hack_url), activityContext); + return true; + } else if (id == R.id.action_source){ + openIceweasel("https://gitpull.it/source/libre-busto/", activityContext); + return true; + } else if (id == R.id.action_licence){ + openIceweasel("https://www.gnu.org/licenses/gpl-3.0.html", activityContext); + return true; + } else if (id == R.id.action_experiments) { + startActivity(new Intent(ActivityPrincipal.this, ActivityExperiments.class)); + return true; + } else if (id == R.id.action_tutorial) { + startIntroductionActivity(); + return true; } + return false; } } diff --git a/app/src/main/java/it/reyboz/bustorino/BustoApp.java b/app/src/main/java/it/reyboz/bustorino/BustoApp.java --- a/app/src/main/java/it/reyboz/bustorino/BustoApp.java +++ b/app/src/main/java/it/reyboz/bustorino/BustoApp.java @@ -56,4 +56,5 @@ ACRA.init(this, builder); } + } diff --git a/app/src/main/java/it/reyboz/bustorino/backend/utils.java b/app/src/main/java/it/reyboz/bustorino/backend/utils.java --- a/app/src/main/java/it/reyboz/bustorino/backend/utils.java +++ b/app/src/main/java/it/reyboz/bustorino/backend/utils.java @@ -20,9 +20,13 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.content.res.Resources; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; +import android.os.Build; +import android.text.Html; +import android.text.Spanned; import android.util.Log; import android.util.TypedValue; import androidx.annotation.Nullable; @@ -91,7 +95,10 @@ } public static float convertDipToPixels(Context con, float dp){ - return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,con.getResources().getDisplayMetrics()); + return convertDipToPixels(con.getResources(), dp); + } + public static float convertDipToPixels(Resources res, float dp){ + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,res.getDisplayMetrics()); } /* @@ -372,4 +379,12 @@ } return sb.toString(); } + + public static Spanned convertHtml(String text) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + return Html.fromHtml(text, Html.FROM_HTML_MODE_COMPACT); + } else { + return Html.fromHtml(text); + } + } } diff --git a/app/src/main/java/it/reyboz/bustorino/data/PreferencesHolder.java b/app/src/main/java/it/reyboz/bustorino/data/PreferencesHolder.java --- a/app/src/main/java/it/reyboz/bustorino/data/PreferencesHolder.java +++ b/app/src/main/java/it/reyboz/bustorino/data/PreferencesHolder.java @@ -31,6 +31,7 @@ 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 SharedPreferences getMainSharedPreferences(Context context){ return context.getSharedPreferences(context.getString(R.string.mainSharedPreferences), MODE_PRIVATE); @@ -48,4 +49,14 @@ 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); + } } diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/IntroFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/IntroFragment.kt new file mode 100644 --- /dev/null +++ b/app/src/main/java/it/reyboz/bustorino/fragments/IntroFragment.kt @@ -0,0 +1,151 @@ +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.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) + val closeButton = root.findViewById