Author: Adam Pioterek <adam.pioterek@protonmail.ch>
refreshing timetable every 15s
app/src/main/AndroidManifest.xml | 12 | 6 app/src/main/java/ml/adamsprogs/bimba/MessageReceiver.kt | 35 | 3 | 6 | 115 app/src/main/java/ml/adamsprogs/bimba/VmClient.kt | 31 app/src/main/java/ml/adamsprogs/bimba/models/DeparturesAdapter.kt | 47 app/src/main/java/ml/adamsprogs/bimba/models/Timetable.kt | 4 app/src/main/res/layout/activity_main.xml | 2 app/src/main/res/layout/activity_stop.xml | 2 app/src/main/res/layout/fragment_stop.xml | 2 app/src/main/res/menu/menu_stop.xml | 2
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 84217dda388216e606314352638831a0d2d80baa..d39599789d29518ca69c703776b72dba55903f21 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,18 +11,18 @@ android:icon="@drawable/logo" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> - <activity android:name=".MainActivity" /> + <activity android:name=".activities.MainActivity" /> <service android:name=".TimetableDownloader" android:exported="false" /> <activity - android:name=".StopActivity" + android:name=".activities.StopActivity" android:label="@string/title_activity_stop" android:theme="@style/AppTheme" /> <activity - android:name=".SplashActivity" + android:name=".activities.SplashActivity" android:theme="@style/SplashTheme"> <intent-filter> <action android:name="android.intent.action.MAIN" /> @@ -30,7 +30,11 @@ <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> - <activity android:name=".NoDbActivity" /> + <activity android:name=".activities.NoDbActivity" /> + + <service + android:name=".VmClient" + android:exported="false"></service> </application> </manifest> \ No newline at end of file diff --git a/app/src/main/java/ml/adamsprogs/bimba/MainActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/MainActivity.kt deleted file mode 100644 index bfc9c3d3ae32ab816dbcf8aa000f8bfd6e43bd5f..0000000000000000000000000000000000000000 --- a/app/src/main/java/ml/adamsprogs/bimba/MainActivity.kt +++ /dev/null @@ -1,144 +0,0 @@ -package ml.adamsprogs.bimba - -import android.content.Intent -import android.content.IntentFilter -import android.os.* -import android.support.design.widget.Snackbar -import android.support.v7.app.* -import android.text.Html -import android.view.View -import com.arlib.floatingsearchview.FloatingSearchView -import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion -import ml.adamsprogs.bimba.models.* -import kotlin.concurrent.thread -import android.app.Activity -import android.content.Context -import android.support.v4.widget.SwipeRefreshLayout -import android.util.Log -import android.view.inputmethod.InputMethodManager - - -class MainActivity : AppCompatActivity(), MessageReceiver.OnTimetableDownloadListener, SwipeRefreshLayout.OnRefreshListener { - val context: Context = this - val receiver = MessageReceiver() - lateinit var timetable: Timetable - var stops: ArrayList<StopSuggestion>? = null - lateinit var swipeRefreshLayout: SwipeRefreshLayout - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO) - - prepareSwipeLayout() - - prepareOnDownloadListener() - startDownloaderService() - - getStops() - - val searchView = findViewById(R.id.search_view) as FloatingSearchView - - searchView.setOnQueryChangeListener({ _, newQuery -> - thread { - val newStops = stops!!.filter { deAccent(it.body.split("\n")[0]).contains(newQuery, true) } - runOnUiThread { searchView.swapSuggestions(newStops) } - } - }) - - searchView.setOnSearchListener(object : FloatingSearchView.OnSearchListener { - override fun onSuggestionClicked(searchSuggestion: SearchSuggestion) { - val imm = context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager - var view = (context as MainActivity).currentFocus - if (view == null) { - view = View(context) - } - imm.hideSoftInputFromWindow(view.windowToken, 0) - intent = Intent(context, StopActivity::class.java) - intent.putExtra("stop", (searchSuggestion as StopSuggestion).id) - startActivity(intent) - } - - override fun onSearchAction(query: String) { - } - }) - - searchView.setOnBindSuggestionCallback { _, _, textView, item, _ -> - val suggestion = item as StopSuggestion - val text = suggestion.body.split("\n") - val t = "<small><font color=\"#a0a0a0\">" + text[1] + "</font></small>" - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - textView.text = Html.fromHtml(text[0] + "<br/>" + t, Html.FROM_HTML_MODE_LEGACY) - } else { - @Suppress("DEPRECATION") - textView.text = Html.fromHtml(text[0] + "<br/>" + t) - } - } - - //todo searchView.attachNavigationDrawerToMenuButton(mDrawerLayout) - } - - private fun getStops() { - timetable = Timetable(this) - stops = timetable.getStops() - } - - private fun prepareOnDownloadListener() { - val filter = IntentFilter("ml.adamsprogs.bimba.timetableDownloaded") - filter.addCategory(Intent.CATEGORY_DEFAULT) - registerReceiver(receiver, filter) - receiver.addOnTimetableDownloadListener(context as MessageReceiver.OnTimetableDownloadListener) - } - - private fun startDownloaderService() { - startService(Intent(context, TimetableDownloader::class.java)) - } - - private fun prepareSwipeLayout() { - swipeRefreshLayout = findViewById(R.id.swipeRefreshLayout) as SwipeRefreshLayout - swipeRefreshLayout.isEnabled = true - swipeRefreshLayout.setOnRefreshListener(this) - swipeRefreshLayout.setColorSchemeResources(R.color.colorAccent, R.color.colorPrimary) - } - - override fun onRefresh() { - swipeRefreshLayout.isRefreshing = true - Log.i("Refresh", "Downloading") - startDownloaderService() - } - - override fun onDestroy() { - super.onDestroy() - receiver.removeOnTimetableDownloadListener(context as MessageReceiver.OnTimetableDownloadListener) - unregisterReceiver(receiver) - } - - fun deAccent(str: String): String { - var result = str.replace('ę', 'e') - result = result.replace('ó', 'o') - result = result.replace('ą', 'a') - result = result.replace('ś', 's') - result = result.replace('ł', 'l') - result = result.replace('ż', 'ż') - result = result.replace('ź', 'ź') - result = result.replace('ć', 'ć') - result = result.replace('ń', 'n') - return result - } - - override fun onTimetableDownload(result: String?) { - Log.i("Refresh", "downloaded: $result") - val message: String - when (result) { - "downloaded" -> message = getString(R.string.timetable_downloaded) - "no connectivity" -> message = getString(R.string.no_connectivity) - "up-to-date" -> message = getString(R.string.timetable_up_to_date) - "validity failed" -> message = getString(R.string.validity_failed) - else -> message = getString(R.string.error_try_later) - } - timetable.refresh() - stops = timetable.getStops() - Snackbar.make(swipeRefreshLayout, message, Snackbar.LENGTH_LONG).show() - swipeRefreshLayout.isRefreshing = false - } -} diff --git a/app/src/main/java/ml/adamsprogs/bimba/MessageReceiver.kt b/app/src/main/java/ml/adamsprogs/bimba/MessageReceiver.kt index 0203994b9671acb43fbc21958b3f3800de22dd4a..bc53ddb2579afcedda4f27b10e3ba572a7c31ec5 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/MessageReceiver.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/MessageReceiver.kt @@ -3,14 +3,31 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import ml.adamsprogs.bimba.models.Departure +import ml.adamsprogs.bimba.models.fromString class MessageReceiver: BroadcastReceiver() { val onTimetableDownloadListeners: HashSet<OnTimetableDownloadListener> = HashSet() + val onVmListeners: HashSet<OnVmListener> = HashSet() override fun onReceive(context: Context?, intent: Intent?) { - val result = intent?.getStringExtra("result") - for (listener in onTimetableDownloadListeners) { - listener.onTimetableDownload(result) + if (intent?.action == "ml.adamsprogs.bimba.timetableDownloaded") { + val result = intent.getStringExtra("result") + for (listener in onTimetableDownloadListeners) { + listener.onTimetableDownload(result) + } + } + if (intent?.action == "ml.adamsprogs.bimba.departuresCreated") { + val workdays = intent.getStringArrayListExtra("workdays").map { fromString(it)} as ArrayList<Departure> + val saturdays = intent.getStringArrayListExtra("saturdays").map { fromString(it)} as ArrayList<Departure> + val sundays = intent.getStringArrayListExtra("sundays").map { fromString(it)} as ArrayList<Departure> + val departures = HashMap<String, ArrayList<Departure>>() + departures["workdays"] = workdays + departures["saturdays"] = saturdays + departures["sundays"] = sundays + for (listener in onVmListeners) { + listener.onVm(departures) + } } } @@ -22,7 +39,19 @@ fun removeOnTimetableDownloadListener(listener: OnTimetableDownloadListener) { onTimetableDownloadListeners.remove(listener) } + fun addOnVmListener(listener: OnVmListener) { + onVmListeners.add(listener) + } + + fun removeOnVmListener(listener: OnVmListener) { + onVmListeners.remove(listener) + } + interface OnTimetableDownloadListener { fun onTimetableDownload(result: String?) + } + + interface OnVmListener { + fun onVm(departures: HashMap<String, ArrayList<Departure>>) } } \ No newline at end of file diff --git a/app/src/main/java/ml/adamsprogs/bimba/NoDbActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/NoDbActivity.kt deleted file mode 100644 index 8b31c19d7a91d7e5552a76bbb774cff692b123f1..0000000000000000000000000000000000000000 --- a/app/src/main/java/ml/adamsprogs/bimba/NoDbActivity.kt +++ /dev/null @@ -1,68 +0,0 @@ -package ml.adamsprogs.bimba - -import android.content.Intent -import android.support.v7.app.AppCompatActivity -import android.os.Bundle -import android.content.IntentFilter -import android.widget.TextView - - -class NoDbActivity : AppCompatActivity(), NetworkStateReceiver.OnConnectivityChangeListener, MessageReceiver.OnTimetableDownloadListener { - val networkStateReceiver = NetworkStateReceiver() - val timetableDownloadReceiver = MessageReceiver() - var serviceRunning = false - var askedForNetwork = false - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_nodb) - var filter: IntentFilter - filter = IntentFilter("ml.adamsprogs.bimba.timetableDownloaded") - filter.addCategory(Intent.CATEGORY_DEFAULT) - registerReceiver(timetableDownloadReceiver, filter) - timetableDownloadReceiver.addOnTimetableDownloadListener(this) - - if (!isNetworkAvailable(this)) { - askedForNetwork = true - (findViewById(R.id.noDbCaption) as TextView).text = getString(R.string.no_db_connect) - filter = IntentFilter("android.net.conn.CONNECTIVITY_CHANGE") - registerReceiver(networkStateReceiver, filter) - networkStateReceiver.addOnConnectivityChangeListener(this) - } else - downloadTimetable() - } - - fun downloadTimetable() { - (findViewById(R.id.noDbCaption) as TextView).text = getString(R.string.no_db_downloading) - serviceRunning = true - intent = Intent(this, TimetableDownloader::class.java) - intent.putExtra("force", true) - startService(intent) - } - - override fun onConnectivityChange(connected: Boolean) { - if (connected && !serviceRunning) - downloadTimetable() - /*if (!connected) - serviceRunning = false*/ - } - - override fun onTimetableDownload(result: String?) { - when (result) { - "downloaded" -> { - timetableDownloadReceiver.removeOnTimetableDownloadListener(this) - networkStateReceiver.removeOnConnectivityChangeListener(this) - startActivity(Intent(this, MainActivity::class.java)) - finish() - } - else -> (findViewById(R.id.noDbCaption) as TextView).text = getString(R.string.error_try_later) - } - } - - override fun onPause() { - super.onPause() - unregisterReceiver(timetableDownloadReceiver) - if (askedForNetwork) - unregisterReceiver(networkStateReceiver) - } -} diff --git a/app/src/main/java/ml/adamsprogs/bimba/SplashActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/SplashActivity.kt deleted file mode 100644 index 19f61cc3f8a4243ae8923ddc580b6b1085c5e2b0..0000000000000000000000000000000000000000 --- a/app/src/main/java/ml/adamsprogs/bimba/SplashActivity.kt +++ /dev/null @@ -1,19 +0,0 @@ -package ml.adamsprogs.bimba - -import android.support.v7.app.AppCompatActivity -import android.os.Bundle -import android.content.Intent -import ml.adamsprogs.bimba.models.Timetable - - -class SplashActivity : AppCompatActivity() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - if(Timetable(this).isDatabaseHealthy()) - startActivity(Intent(this, MainActivity::class.java)) - else - startActivity(Intent(this, NoDbActivity::class.java)) - finish() - } -} diff --git a/app/src/main/java/ml/adamsprogs/bimba/StopActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/StopActivity.kt deleted file mode 100644 index 5e222f87419a924e16dcb24c9a3a083865c658fb..0000000000000000000000000000000000000000 --- a/app/src/main/java/ml/adamsprogs/bimba/StopActivity.kt +++ /dev/null @@ -1,194 +0,0 @@ -package ml.adamsprogs.bimba - -import android.support.design.widget.* -import android.support.v7.app.AppCompatActivity -import android.support.v7.widget.Toolbar - -import android.support.v4.app.* -import android.support.v4.view.ViewPager -import android.os.Bundle -import android.support.v4.content.res.ResourcesCompat -import android.support.v4.view.PagerAdapter -import android.view.* - -import ml.adamsprogs.bimba.models.* -import android.support.v7.widget.* -import java.util.* -import kotlin.collections.ArrayList -import kotlin.collections.HashMap - - -class StopActivity : AppCompatActivity() { //todo refresh - - private lateinit var stopId: String - private var timetableType = "departure" - private var sectionsPagerAdapter: SectionsPagerAdapter? = null - private var viewPager: ViewPager? = null - private lateinit var timetable: Timetable - private val today = Calendar.getInstance() - private lateinit var tabLayout: TabLayout - - override fun onCreate(savedInstanceState: Bundle?) { //todo select current mode - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_stop) - stopId = intent.getStringExtra("stop") - - val toolbar = findViewById(R.id.toolbar) as Toolbar - setSupportActionBar(toolbar) - - /*todo when Internet connection - exists -> download vm - else -> timetable - */ - timetable = Timetable(this) - supportActionBar?.title = timetable.getStopName(stopId) ?: "Stop" - - viewPager = findViewById(R.id.container) as ViewPager - tabLayout = findViewById(R.id.tabs) as TabLayout - - sectionsPagerAdapter = SectionsPagerAdapter(supportFragmentManager, createDepartures()) - - viewPager!!.adapter = sectionsPagerAdapter - viewPager!!.addOnPageChangeListener(TabLayout.TabLayoutOnPageChangeListener(tabLayout)) - tabLayout.addOnTabSelectedListener(TabLayout.ViewPagerOnTabSelectedListener(viewPager)) - - selectTodayPage() - - val fab = findViewById(R.id.fab) as FloatingActionButton - fab.setOnClickListener { view -> - Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) - .setAction("Action", null).show() - //todo favourites - } - } - - private fun createDepartures(): HashMap<String, ArrayList<Departure>> { - val departures = timetable.getStopDepartures(stopId) - val moreDepartures = timetable.getStopDepartures(stopId) - val rolledDepartures = HashMap<String, ArrayList<Departure>>() - - for ((_, tomorrowDepartures) in moreDepartures!!) { - tomorrowDepartures.forEach{it.tomorrow = true} - } - - for ((mode, _) in departures!!) { - rolledDepartures[mode] = (departures[mode] as ArrayList<Departure> + - moreDepartures[mode] as ArrayList<Departure>) as ArrayList<Departure> - rolledDepartures[mode] = filterDepartures(rolledDepartures[mode]) - } - - return rolledDepartures - } - - private fun filterDepartures(departures: List<Departure>?): ArrayList<Departure> { - val filtered = ArrayList<Departure>() - val lines = HashMap<String, Int>() - val now = Calendar.getInstance() - for (departure in departures!!) { - val time = Calendar.getInstance() - time.set(Calendar.HOUR_OF_DAY, Integer.parseInt(departure.time.split(":")[0])) - time.set(Calendar.MINUTE, Integer.parseInt(departure.time.split(":")[1])) - time.set(Calendar.SECOND, 0) - time.set(Calendar.MILLISECOND, 0) - if (departure.tomorrow) - time.add(Calendar.DAY_OF_MONTH, 1) - var lineExistedTimes = lines[departure.line] - if (now.before(time) && lineExistedTimes ?: 0 < 3) { - lineExistedTimes = (lineExistedTimes ?: 0) + 1 - lines[departure.line] = lineExistedTimes - filtered.add(departure) - } - } - return filtered - } - - private fun selectTodayPage() { - when (today.get(Calendar.DAY_OF_WEEK)) { - Calendar.SATURDAY -> tabLayout.getTabAt(1)?.select() - Calendar.SUNDAY -> tabLayout.getTabAt(2)?.select() - else -> tabLayout.getTabAt(0)?.select() - } - } - - override fun onCreateOptionsMenu(menu: Menu): Boolean { - menuInflater.inflate(R.menu.menu_stop, menu) - return true - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - val id = item.itemId - - if (id == R.id.action_change_type) { - if (timetableType == "departure") { - timetableType = "full" - item.icon = (ResourcesCompat.getDrawable(resources, R.drawable.ic_timetable_departure, this.theme)) - sectionsPagerAdapter?.changeDepartures(timetable.getStopDepartures(stopId)!!) - sectionsPagerAdapter?.notifyDataSetChanged() - } else { - timetableType = "departure" - item.icon = (ResourcesCompat.getDrawable(resources, R.drawable.ic_timetable_full, this.theme)) - sectionsPagerAdapter?.changeDepartures(createDepartures()) - sectionsPagerAdapter?.notifyDataSetChanged() - } - return true - } - - return super.onOptionsItemSelected(item) - } - - class PlaceholderFragment : Fragment() { - - override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, - savedInstanceState: Bundle?): View? { - val rootView = inflater!!.inflate(R.layout.fragment_stop, container, false) - - val layoutManager = LinearLayoutManager(activity) - val departuresList: RecyclerView = rootView.findViewById(R.id.departuresList) as RecyclerView - val dividerItemDecoration = DividerItemDecoration(departuresList.context, layoutManager.orientation) - departuresList.addItemDecoration(dividerItemDecoration) - val adapter = DeparturesAdapter(activity, arguments.getStringArrayList("departures").map { fromString(it) }) - departuresList.adapter = adapter - departuresList.layoutManager = layoutManager - return rootView - } - - companion object { - private val ARG_SECTION_NUMBER = "section_number" - - fun newInstance(sectionNumber: Int, stopId: String, departures: ArrayList<Departure>?): PlaceholderFragment { - val fragment = PlaceholderFragment() - val args = Bundle() - args.putInt(ARG_SECTION_NUMBER, sectionNumber) - args.putString("stop", stopId) - val d = ArrayList<String>() - departures?.mapTo(d) { it.toString() } - args.putStringArrayList("departures", d) - fragment.arguments = args - return fragment - } - } - } - - inner class SectionsPagerAdapter(fm: FragmentManager, var departures: HashMap<String, ArrayList<Departure>>) : FragmentStatePagerAdapter(fm) { - - override fun getItemPosition(obj: Any?): Int { - return PagerAdapter.POSITION_NONE - } - - override fun getItem(position: Int): Fragment { - var mode: String? = null - when (position) { - 0 -> mode = "workdays" - 1 -> mode = "saturdays" - 2 -> mode = "sundays" - } - return PlaceholderFragment.newInstance(position + 1, stopId, departures[mode]) - } - - fun changeDepartures(departures: HashMap<String, ArrayList<Departure>>) { - this.departures = departures - } - - override fun getCount() = 3 - } -} diff --git a/app/src/main/java/ml/adamsprogs/bimba/VmClient.kt b/app/src/main/java/ml/adamsprogs/bimba/VmClient.kt new file mode 100644 index 0000000000000000000000000000000000000000..ce9266da34b1b9f694a2546ad9f7ce3f8c910399 --- /dev/null +++ b/app/src/main/java/ml/adamsprogs/bimba/VmClient.kt @@ -0,0 +1,31 @@ +package ml.adamsprogs.bimba + +import android.app.IntentService +import android.content.Intent +import ml.adamsprogs.bimba.models.Departure + +import ml.adamsprogs.bimba.models.createDepartures + +class VmClient : IntentService("VmClient") { + + override fun onHandleIntent(intent: Intent?) { + if (intent != null) { + val stopId = intent.getStringExtra("stopId") + if (!isNetworkAvailable(this)) { + sendResult(createDepartures(this, stopId)) + } else { + //todo download vm + } + } + } + + private fun sendResult(departures: HashMap<String, ArrayList<Departure>>) { + val broadcastIntent = Intent() + broadcastIntent.action = "ml.adamsprogs.bimba.departuresCreated" + broadcastIntent.addCategory(Intent.CATEGORY_DEFAULT) + broadcastIntent.putStringArrayListExtra("workdays", departures["workdays"]?.map{it.toString()} as java.util.ArrayList<String>) + broadcastIntent.putStringArrayListExtra("saturdays", departures["saturdays"]?.map{it.toString()} as java.util.ArrayList<String>) + broadcastIntent.putStringArrayListExtra("sundays", departures["sundays"]?.map{it.toString()} as java.util.ArrayList<String>) + sendBroadcast(broadcastIntent) + } +} diff --git a/app/src/main/java/ml/adamsprogs/bimba/activities/MainActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/activities/MainActivity.kt new file mode 100644 index 0000000000000000000000000000000000000000..c058e131c46d25dc808a521e0ec206937829d2d5 --- /dev/null +++ b/app/src/main/java/ml/adamsprogs/bimba/activities/MainActivity.kt @@ -0,0 +1,148 @@ +package ml.adamsprogs.bimba.activities + +import android.content.Intent +import android.content.IntentFilter +import android.os.* +import android.support.design.widget.Snackbar +import android.support.v7.app.* +import android.text.Html +import android.view.View +import com.arlib.floatingsearchview.FloatingSearchView +import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion +import ml.adamsprogs.bimba.models.* +import kotlin.concurrent.thread +import android.app.Activity +import android.content.Context +import android.support.v4.widget.SwipeRefreshLayout +import android.util.Log +import android.view.inputmethod.InputMethodManager +import ml.adamsprogs.bimba.MessageReceiver +import ml.adamsprogs.bimba.R +import ml.adamsprogs.bimba.TimetableDownloader + + +class MainActivity : AppCompatActivity(), MessageReceiver.OnTimetableDownloadListener, SwipeRefreshLayout.OnRefreshListener { + val context: Context = this + val receiver = MessageReceiver() + lateinit var timetable: Timetable + var stops: ArrayList<StopSuggestion>? = null + lateinit var swipeRefreshLayout: SwipeRefreshLayout + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO) + + prepareSwipeLayout() + + prepareOnDownloadListener() + startDownloaderService() + + getStops() + + val searchView = findViewById(R.id.search_view) as FloatingSearchView + + searchView.setOnQueryChangeListener({ _, newQuery -> + thread { + val newStops = stops!!.filter { deAccent(it.body.split("\n")[0]).contains(newQuery, true) } + runOnUiThread { searchView.swapSuggestions(newStops) } + } + }) + + searchView.setOnSearchListener(object : FloatingSearchView.OnSearchListener { + override fun onSuggestionClicked(searchSuggestion: SearchSuggestion) { + val imm = context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager + var view = (context as MainActivity).currentFocus + if (view == null) { + view = View(context) + } + imm.hideSoftInputFromWindow(view.windowToken, 0) + intent = Intent(context, StopActivity::class.java) + intent.putExtra("stop", (searchSuggestion as StopSuggestion).id) + startActivity(intent) + } + + override fun onSearchAction(query: String) { + } + }) + + searchView.setOnBindSuggestionCallback { _, _, textView, item, _ -> + val suggestion = item as StopSuggestion + val text = suggestion.body.split("\n") + val t = "<small><font color=\"#a0a0a0\">" + text[1] + "</font></small>" + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + textView.text = Html.fromHtml(text[0] + "<br/>" + t, Html.FROM_HTML_MODE_LEGACY) + } else { + @Suppress("DEPRECATION") + textView.text = Html.fromHtml(text[0] + "<br/>" + t) + } + } + + //todo searchView.attachNavigationDrawerToMenuButton(mDrawerLayout) + } + + private fun getStops() { + timetable = Timetable(this) + stops = timetable.getStops() + } + + private fun prepareOnDownloadListener() { + val filter = IntentFilter("ml.adamsprogs.bimba.timetableDownloaded") + filter.addCategory(Intent.CATEGORY_DEFAULT) + registerReceiver(receiver, filter) + receiver.addOnTimetableDownloadListener(context as MessageReceiver.OnTimetableDownloadListener) + } + + private fun startDownloaderService() { + startService(Intent(context, TimetableDownloader::class.java)) + } + + private fun prepareSwipeLayout() { + swipeRefreshLayout = findViewById(R.id.swipeRefreshLayout) as SwipeRefreshLayout + swipeRefreshLayout.isEnabled = true + swipeRefreshLayout.setOnRefreshListener(this) + swipeRefreshLayout.setColorSchemeResources(R.color.colorAccent, R.color.colorPrimary) + } + + override fun onRefresh() { + swipeRefreshLayout.isRefreshing = true + Log.i("Refresh", "Downloading") + startDownloaderService() + } + + override fun onDestroy() { + super.onDestroy() + receiver.removeOnTimetableDownloadListener(context as MessageReceiver.OnTimetableDownloadListener) + unregisterReceiver(receiver) + timetable.close() + } + + fun deAccent(str: String): String { + var result = str.replace('ę', 'e') + result = result.replace('ó', 'o') + result = result.replace('ą', 'a') + result = result.replace('ś', 's') + result = result.replace('ł', 'l') + result = result.replace('ż', 'ż') + result = result.replace('ź', 'ź') + result = result.replace('ć', 'ć') + result = result.replace('ń', 'n') + return result + } + + override fun onTimetableDownload(result: String?) { + Log.i("Refresh", "downloaded: $result") + val message: String + when (result) { + "downloaded" -> message = getString(R.string.timetable_downloaded) + "no connectivity" -> message = getString(R.string.no_connectivity) + "up-to-date" -> message = getString(R.string.timetable_up_to_date) + "validity failed" -> message = getString(R.string.validity_failed) + else -> message = getString(R.string.error_try_later) + } + timetable.refresh() + stops = timetable.getStops() + Snackbar.make(swipeRefreshLayout, message, Snackbar.LENGTH_LONG).show() + swipeRefreshLayout.isRefreshing = false + } +} diff --git a/app/src/main/java/ml/adamsprogs/bimba/activities/NoDbActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/activities/NoDbActivity.kt new file mode 100644 index 0000000000000000000000000000000000000000..6e89218c7fd22a9565f0f86a86e19f5eac2a622f --- /dev/null +++ b/app/src/main/java/ml/adamsprogs/bimba/activities/NoDbActivity.kt @@ -0,0 +1,69 @@ +package ml.adamsprogs.bimba.activities + +import android.content.Intent +import android.support.v7.app.AppCompatActivity +import android.os.Bundle +import android.content.IntentFilter +import android.widget.TextView +import ml.adamsprogs.bimba.* + + +class NoDbActivity : AppCompatActivity(), NetworkStateReceiver.OnConnectivityChangeListener, MessageReceiver.OnTimetableDownloadListener { + val networkStateReceiver = NetworkStateReceiver() + val timetableDownloadReceiver = MessageReceiver() + var serviceRunning = false + var askedForNetwork = false + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_nodb) + var filter: IntentFilter + filter = IntentFilter("ml.adamsprogs.bimba.timetableDownloaded") + filter.addCategory(Intent.CATEGORY_DEFAULT) + registerReceiver(timetableDownloadReceiver, filter) + timetableDownloadReceiver.addOnTimetableDownloadListener(this) + + if (!isNetworkAvailable(this)) { + askedForNetwork = true + (findViewById(R.id.noDbCaption) as TextView).text = getString(R.string.no_db_connect) + filter = IntentFilter("android.net.conn.CONNECTIVITY_CHANGE") + registerReceiver(networkStateReceiver, filter) + networkStateReceiver.addOnConnectivityChangeListener(this) + } else + downloadTimetable() + } + + fun downloadTimetable() { + (findViewById(R.id.noDbCaption) as TextView).text = getString(R.string.no_db_downloading) + serviceRunning = true + intent = Intent(this, TimetableDownloader::class.java) + intent.putExtra("force", true) + startService(intent) + } + + override fun onConnectivityChange(connected: Boolean) { + if (connected && !serviceRunning) + downloadTimetable() + /*if (!connected) + serviceRunning = false*/ + } + + override fun onTimetableDownload(result: String?) { + when (result) { + "downloaded" -> { + timetableDownloadReceiver.removeOnTimetableDownloadListener(this) + networkStateReceiver.removeOnConnectivityChangeListener(this) + startActivity(Intent(this, MainActivity::class.java)) + finish() + } + else -> (findViewById(R.id.noDbCaption) as TextView).text = getString(R.string.error_try_later) + } + } + + override fun onPause() { + super.onPause() + unregisterReceiver(timetableDownloadReceiver) + if (askedForNetwork) + unregisterReceiver(networkStateReceiver) + } +} diff --git a/app/src/main/java/ml/adamsprogs/bimba/activities/SplashActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/activities/SplashActivity.kt new file mode 100644 index 0000000000000000000000000000000000000000..98a0fed99b714a63fb103c20389d8563598e7847 --- /dev/null +++ b/app/src/main/java/ml/adamsprogs/bimba/activities/SplashActivity.kt @@ -0,0 +1,21 @@ +package ml.adamsprogs.bimba.activities + +import android.support.v7.app.AppCompatActivity +import android.os.Bundle +import android.content.Intent +import ml.adamsprogs.bimba.models.Timetable + + +class SplashActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val timetable = Timetable(this) + if(timetable.isDatabaseHealthy()) + startActivity(Intent(this, MainActivity::class.java)) + else + startActivity(Intent(this, NoDbActivity::class.java)) + timetable.close() + finish() + } +} diff --git a/app/src/main/java/ml/adamsprogs/bimba/activities/StopActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/activities/StopActivity.kt new file mode 100644 index 0000000000000000000000000000000000000000..46594bcd3f3a2780eb383b1a3957acb401f6c61b --- /dev/null +++ b/app/src/main/java/ml/adamsprogs/bimba/activities/StopActivity.kt @@ -0,0 +1,207 @@ +package ml.adamsprogs.bimba.activities + +import android.content.Intent +import android.content.IntentFilter +import android.support.design.widget.* +import android.support.v7.app.AppCompatActivity +import android.support.v7.widget.Toolbar + +import android.support.v4.app.* +import android.support.v4.view.ViewPager +import android.os.Bundle +import android.support.v4.content.res.ResourcesCompat +import android.support.v4.view.PagerAdapter +import android.view.* + +import ml.adamsprogs.bimba.models.* +import android.support.v7.widget.* +import ml.adamsprogs.bimba.MessageReceiver +import ml.adamsprogs.bimba.R +import ml.adamsprogs.bimba.VmClient +import java.util.* +import kotlin.collections.ArrayList +import kotlin.collections.HashMap + + +class StopActivity : AppCompatActivity(), MessageReceiver.OnVmListener { + + private lateinit var stopId: String + private var timetableType = "departure" + private var sectionsPagerAdapter: SectionsPagerAdapter? = null + private var viewPager: ViewPager? = null + private lateinit var timetable: Timetable + private val today = Calendar.getInstance() + private lateinit var tabLayout: TabLayout + private var timer = Timer() + private lateinit var timerTask:TimerTask + private val context = this + val receiver = MessageReceiver() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_stop) + stopId = intent.getStringExtra("stop") + + val toolbar = findViewById(R.id.toolbar) as Toolbar + setSupportActionBar(toolbar) + + createTimerTask() + + prepareOnDownloadListener() + + timetable = Timetable(this) + supportActionBar?.title = timetable.getStopName(stopId) ?: "Stop" + + viewPager = findViewById(R.id.container) as ViewPager + tabLayout = findViewById(R.id.tabs) as TabLayout + + sectionsPagerAdapter = SectionsPagerAdapter(supportFragmentManager, createDepartures(this, stopId)) + + viewPager!!.adapter = sectionsPagerAdapter + viewPager!!.addOnPageChangeListener(TabLayout.TabLayoutOnPageChangeListener(tabLayout)) + tabLayout.addOnTabSelectedListener(TabLayout.ViewPagerOnTabSelectedListener(viewPager)) + + selectTodayPage() + + scheduleRefresh() + + val fab = findViewById(R.id.fab) as FloatingActionButton + fab.setOnClickListener { view -> + Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) + .setAction("Action", null).show() + //todo favourites + } + } + + private fun createTimerTask() { + timerTask = object : TimerTask() { + override fun run() { + val vmIntent = Intent(context, VmClient::class.java) + vmIntent.putExtra("stopId", stopId) + startService(vmIntent) + } + } + } + + private fun prepareOnDownloadListener() { + val filter = IntentFilter("ml.adamsprogs.bimba.departuresCreated") + filter.addCategory(Intent.CATEGORY_DEFAULT) + registerReceiver(receiver, filter) + receiver.addOnVmListener(context as MessageReceiver.OnVmListener) + } + + override fun onVm(departures: HashMap<String, ArrayList<Departure>>) { + sectionsPagerAdapter?.departures = departures + sectionsPagerAdapter?.notifyDataSetChanged() + } + + private fun selectTodayPage() { + when (today.get(Calendar.DAY_OF_WEEK)) { + Calendar.SATURDAY -> tabLayout.getTabAt(1)?.select() + Calendar.SUNDAY -> tabLayout.getTabAt(2)?.select() + else -> tabLayout.getTabAt(0)?.select() + } + } + + private fun scheduleRefresh() { + timer.cancel() + timer = Timer() + createTimerTask() + timer.scheduleAtFixedRate(timerTask, 0, 15000) + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.menu_stop, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + val id = item.itemId + + if (id == R.id.action_change_type) { + if (timetableType == "departure") { + timetableType = "full" + item.icon = (ResourcesCompat.getDrawable(resources, R.drawable.ic_timetable_departure, this.theme)) + sectionsPagerAdapter?.departures = timetable.getStopDepartures(stopId)!! + sectionsPagerAdapter?.relativeTime = false + sectionsPagerAdapter?.notifyDataSetChanged() + timer.cancel() + } else { + timetableType = "departure" + item.icon = (ResourcesCompat.getDrawable(resources, R.drawable.ic_timetable_full, this.theme)) + sectionsPagerAdapter?.departures = createDepartures(this, stopId) + sectionsPagerAdapter?.relativeTime = true + sectionsPagerAdapter?.notifyDataSetChanged() + scheduleRefresh() + } + return true + } + + return super.onOptionsItemSelected(item) + } + + override fun onDestroy() { + super.onDestroy() + receiver.removeOnVmListener(context as MessageReceiver.OnVmListener) + unregisterReceiver(receiver) + timer.cancel() + timetable.close() + } + + class PlaceholderFragment : Fragment() { + + override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + val rootView = inflater!!.inflate(R.layout.fragment_stop, container, false) + + val layoutManager = LinearLayoutManager(activity) + val departuresList: RecyclerView = rootView.findViewById(R.id.departuresList) as RecyclerView + val dividerItemDecoration = DividerItemDecoration(departuresList.context, layoutManager.orientation) + departuresList.addItemDecoration(dividerItemDecoration) + val adapter = DeparturesAdapter(activity, arguments.getStringArrayList("departures").map { fromString(it) }, + arguments["relativeTime"] as Boolean) + departuresList.adapter = adapter + departuresList.layoutManager = layoutManager + return rootView + } + + companion object { + private val ARG_SECTION_NUMBER = "section_number" + + fun newInstance(sectionNumber: Int, stopId: String, departures: ArrayList<Departure>?, relativeTime: Boolean): + PlaceholderFragment { + val fragment = PlaceholderFragment() + val args = Bundle() + args.putInt(ARG_SECTION_NUMBER, sectionNumber) + args.putString("stop", stopId) + val d = ArrayList<String>() + departures?.mapTo(d) { it.toString() } + args.putStringArrayList("departures", d) + args.putBoolean("relativeTime", relativeTime) + fragment.arguments = args + return fragment + } + } + } + + inner class SectionsPagerAdapter(fm: FragmentManager, var departures: HashMap<String, ArrayList<Departure>>) : FragmentStatePagerAdapter(fm) { + + var relativeTime = true + + override fun getItemPosition(obj: Any?): Int { + return PagerAdapter.POSITION_NONE + } + + override fun getItem(position: Int): Fragment { + var mode: String? = null + when (position) { + 0 -> mode = "workdays" + 1 -> mode = "saturdays" + 2 -> mode = "sundays" + } + return PlaceholderFragment.newInstance(position + 1, stopId, departures[mode], relativeTime) + } + + override fun getCount() = 3 + } +} diff --git a/app/src/main/java/ml/adamsprogs/bimba/models/DeparturesAdapter.kt b/app/src/main/java/ml/adamsprogs/bimba/models/DeparturesAdapter.kt index e899fc8cb3079e7e3c13c5be30c5beb1f787383b..834fbf4f0583568d1b4b85e4879676aa49f70412 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/models/DeparturesAdapter.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/models/DeparturesAdapter.kt @@ -11,7 +11,50 @@ import ml.adamsprogs.bimba.R import android.view.LayoutInflater import java.util.* -class DeparturesAdapter(val context: Context, val departures: List<Departure>) : +fun filterDepartures(departures: List<Departure>?): ArrayList<Departure> { + val filtered = ArrayList<Departure>() + val lines = HashMap<String, Int>() + val now = Calendar.getInstance() + for (departure in departures!!) { + val time = Calendar.getInstance() + time.set(Calendar.HOUR_OF_DAY, Integer.parseInt(departure.time.split(":")[0])) + time.set(Calendar.MINUTE, Integer.parseInt(departure.time.split(":")[1])) + time.set(Calendar.SECOND, 0) + time.set(Calendar.MILLISECOND, 0) + if (departure.tomorrow) + time.add(Calendar.DAY_OF_MONTH, 1) + var lineExistedTimes = lines[departure.line] + if (now.before(time) && lineExistedTimes ?: 0 < 3) { + lineExistedTimes = (lineExistedTimes ?: 0) + 1 + lines[departure.line] = lineExistedTimes + filtered.add(departure) + } + } + return filtered +} + +fun createDepartures(context: Context, stopId: String): HashMap<String, ArrayList<Departure>> { + val timetable = Timetable(context) + val departures = timetable.getStopDepartures(stopId) + val moreDepartures = timetable.getStopDepartures(stopId) + val rolledDepartures = HashMap<String, ArrayList<Departure>>() + + for ((_, tomorrowDepartures) in moreDepartures!!) { + tomorrowDepartures.forEach { it.tomorrow = true } + } + + for ((mode, _) in departures!!) { + rolledDepartures[mode] = (departures[mode] as ArrayList<Departure> + + moreDepartures[mode] as ArrayList<Departure>) as ArrayList<Departure> + rolledDepartures[mode] = filterDepartures(rolledDepartures[mode]) + } + + timetable.close() + + return rolledDepartures +} + +class DeparturesAdapter(val context: Context, val departures: List<Departure>, val relativeTime: Boolean) : RecyclerView.Adapter<DeparturesAdapter.ViewHolder>() { override fun getItemCount(): Int { return departures.size @@ -29,7 +72,7 @@ val departureIn = (departureTime.timeInMillis - now.timeInMillis) / (1000 * 60) val timeString: String - if (departureIn > 60 || departureIn < 0) + if (departureIn > 60 || departureIn < 0 || !relativeTime) timeString = context.getString(R.string.departure_at, departure.time) else if (departureIn > 0) timeString = context.getString(R.string.departure_in, departureIn.toString()) diff --git a/app/src/main/java/ml/adamsprogs/bimba/models/Timetable.kt b/app/src/main/java/ml/adamsprogs/bimba/models/Timetable.kt index 9e08ecd35ab476075222b2f4fc731c6e8949fdba..05e2a6ee4a6096c3bcecfb53edf441d5cd1882ba 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/models/Timetable.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/models/Timetable.kt @@ -86,4 +86,8 @@ } cursor.close() return departures } + + fun close() { + db?.close() + } } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 50db680552ef62dca20d5cf36e3e3e65aa3101db..f8601b3ceeeacd90fea031f2d262ae969d0b8d68 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -5,7 +5,7 @@ xmlns:tools="http://schemas.android.com/tools" android:id="@+id/swipeRefreshLayout" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context="ml.adamsprogs.bimba.MainActivity"> + tools:context="ml.adamsprogs.bimba.activities.MainActivity"> <android.support.constraint.ConstraintLayout android:id="@+id/main_layout" diff --git a/app/src/main/res/layout/activity_stop.xml b/app/src/main/res/layout/activity_stop.xml index 072d70cd1e87f953e74d7d4434544967ed4ea652..86a729521afcd4b934b2a58541fba5bba648d932 100644 --- a/app/src/main/res/layout/activity_stop.xml +++ b/app/src/main/res/layout/activity_stop.xml @@ -7,7 +7,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" android:animateLayoutChanges="true" - tools:context="ml.adamsprogs.bimba.StopActivity"> + tools:context="ml.adamsprogs.bimba.activities.StopActivity"> <android.support.design.widget.AppBarLayout android:id="@+id/appbar" diff --git a/app/src/main/res/layout/fragment_stop.xml b/app/src/main/res/layout/fragment_stop.xml index e8ba3100581354aadfbc60adb32a8cfe0da82c3f..0e24c01dde58f17ff54d130b461c68c002d28298 100644 --- a/app/src/main/res/layout/fragment_stop.xml +++ b/app/src/main/res/layout/fragment_stop.xml @@ -4,7 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:id="@+id/constraintLayout" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context="ml.adamsprogs.bimba.StopActivity$PlaceholderFragment"> + tools:context="ml.adamsprogs.bimba.activities.StopActivity$PlaceholderFragment"> <android.support.v7.widget.RecyclerView android:id="@+id/departuresList" diff --git a/app/src/main/res/menu/menu_stop.xml b/app/src/main/res/menu/menu_stop.xml index b6a44d35ffee8bde31c57b994a4b54b58eeca51a..f5eeafa161d8425f01c97b62b4816477ecc797e0 100644 --- a/app/src/main/res/menu/menu_stop.xml +++ b/app/src/main/res/menu/menu_stop.xml @@ -1,7 +1,7 @@ <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" - tools:context="ml.adamsprogs.bimba.StopActivity"> + tools:context="ml.adamsprogs.bimba.activities.StopActivity"> <item android:id="@+id/action_change_type" android:orderInCategory="100"