Author: Adam Pioterek <adam.pioterek@protonmail.ch>
favourites: adding, deleting splitting
%!v(PANIC=String method: strings: negative Repeat count)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 907ec3e2fdfe60bc23b6a159134f22cc61a30d06..76deb8315581a75e1235f7196a8a1cc9da3300fe 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -34,7 +34,9 @@<service android:name=".VmClient" - android:exported="false"></service> + android:exported="false" /> + + <activity android:name=".activities.EditFavouriteActivity"></activity> </application> </manifest> \ No newline at end of file diff --git a/app/src/main/java/ml/adamsprogs/bimba/TimetableDownloader.kt b/app/src/main/java/ml/adamsprogs/bimba/TimetableDownloader.kt index b6be3d8580f718b0ba98bb18736c8e94b93f4bff..fad8a2b196fa98dd6cd2127896847e578a146206 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/TimetableDownloader.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/TimetableDownloader.kt @@ -27,7 +27,7 @@ if (!isNetworkAvailable(this)) { sendResult("no connectivity") return } - val metadataUrl = URL("https://adamsprogs.ml/w/_media/programmes/bimba/timetable.db.meta") + val metadataUrl = URL("https://adamsprogs.ml/w/_media/programmes/bimba/timetable.meta") var httpCon = metadataUrl.openConnection() as HttpURLConnection if (httpCon.responseCode != HttpURLConnection.HTTP_OK) throw Exception("Failed to connect") @@ -36,7 +36,13 @@ val reader = BufferedReader(InputStreamReader(httpCon.inputStream)) val lastModified = reader.readLine() val checksum = reader.readLine() size = Integer.parseInt(reader.readLine()) / 1024 - val currentLastModified = prefs.getString("timetableLastModified", "1979-10-12T00:00") + val dbVersion = reader.readLine() + if (Integer.parseInt(dbVersion.split(".")[0]) > 1) { //todo version to const + sendResult("version mismatch") + return + } + val dbFilename = reader.readLine() + val currentLastModified = prefs.getString("timetableLastModified", "19791012") if (lastModified <= currentLastModified && !intent.getBooleanExtra("force", false)) { sendResult("up-to-date") return @@ -45,7 +51,7 @@ Log.i("Downloader", "timetable is newer ($lastModified > $currentLastModified)") notify(0) - val xzDbUrl = URL("https://adamsprogs.ml/w/_media/programmes/bimba/timetable.db.xz") + val xzDbUrl = URL("https://adamsprogs.ml/w/_media/programmes/bimba/$dbFilename") httpCon = xzDbUrl.openConnection() as HttpURLConnection if (httpCon.responseCode != HttpURLConnection.HTTP_OK) throw Exception("Failed to connect") diff --git a/app/src/main/java/ml/adamsprogs/bimba/VmClient.kt b/app/src/main/java/ml/adamsprogs/bimba/VmClient.kt index 0592a803d2eff1eff334b3e9c5b15505809a9178..77a60772f12835221c39d198b292738d3f4ffe00 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/VmClient.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/VmClient.kt @@ -15,10 +15,10 @@ override fun onHandleIntent(intent: Intent?) { if (intent != null) { val stopId = intent.getStringExtra("stopId") if (!isNetworkAvailable(this)) { - sendResult(createDepartures(this, stopId)) + sendResult(createDepartures(stopId)) } else { val stopSymbol = intent.getStringExtra("stopSymbol") - val departures = createDepartures(this, stopId) + val departures = createDepartures(stopId) val client = OkHttpClient() val url = "http://www.peka.poznan.pl/vm/method.vm?ts=${Calendar.getInstance().timeInMillis}" diff --git a/app/src/main/java/ml/adamsprogs/bimba/activities/DashActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/activities/DashActivity.kt index 5e7e894d5d329e49b287b8c16c8a34790b64e7aa..6f068964f6951e6093281d7a1e485bb823890c97 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/activities/DashActivity.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/activities/DashActivity.kt @@ -15,12 +15,10 @@ import android.support.v4.widget.* import android.support.v7.widget.* import android.util.Log import android.view.inputmethod.InputMethodManager -import com.google.gson.Gson -import com.google.gson.JsonObject import ml.adamsprogs.bimba.* //todo refresh every 15s -class DashActivity : AppCompatActivity(), MessageReceiver.OnTimetableDownloadListener, SwipeRefreshLayout.OnRefreshListener { +class DashActivity : AppCompatActivity(), MessageReceiver.OnTimetableDownloadListener, SwipeRefreshLayout.OnRefreshListener, FavouritesAdapter.OnMenuItemClickListener { val context: Context = this val receiver = MessageReceiver() lateinit var timetable: Timetable @@ -28,6 +26,7 @@ var stops: ArrayList ? = null lateinit var swipeRefreshLayout: SwipeRefreshLayout lateinit var favouritesList: RecyclerView lateinit var searchView: FloatingSearchView + lateinit var favourites: FavouriteStorage override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -49,6 +48,7 @@ searchView.setOnFocusChangeListener(object : FloatingSearchView.OnFocusChangeListener { override fun onFocus() { swipeRefreshLayout.isEnabled = false favouritesList.visibility = View.GONE + //todo show suggestions } override fun onFocusCleared() { @@ -100,32 +100,15 @@ //todo searchView.attachNavigationDrawerToMenuButton(mDrawerLayout) } private fun prepareFavourites() { + favourites = FavouriteStorage(context) val layoutManager = LinearLayoutManager(context) favouritesList = findViewById(R.id.favouritesList) as RecyclerView - favouritesList.adapter = FavouritesAdapter(context, getFavourites()) + favouritesList.adapter = FavouritesAdapter(context, favourites.favouritesList, this) favouritesList.layoutManager = layoutManager } - private fun getFavourites(): ArrayList<Favourite> { - val preferences = context.getSharedPreferences("ml.adamsprogs.bimba.prefs", Context.MODE_PRIVATE) - val favouritesString = preferences.getString("favourites", "{}") - val favouritesMap = Gson().fromJson(favouritesString, JsonObject::class.java) - val favourites = ArrayList<Favourite>() - for ((name, jsonTimetables) in favouritesMap.entrySet()) { - val timetables = ArrayList<HashMap<String, String>>() - for (jsonTimetable in jsonTimetables.asJsonArray) { - val timetable = HashMap<String, String>() - timetable["stop"] = jsonTimetable.asJsonObject["stop"].asString - timetable["line"] = jsonTimetable.asJsonObject["line"].asString - timetables.add(timetable) - } - favourites.add(Favourite(name, timetables, context)) - } - return favourites - } - private fun getStops() { - timetable = Timetable(this) + timetable = getTimetable(this) stops = timetable.getStops() } @@ -161,7 +144,8 @@ } override fun onResume() { super.onResume() - favouritesList.adapter = FavouritesAdapter(context, getFavourites()) + favourites.refresh() + favouritesList.adapter = FavouritesAdapter(context, favourites.favouritesList, this) favouritesList.adapter.notifyDataSetChanged() } @@ -169,7 +153,6 @@ override fun onDestroy() { super.onDestroy() receiver.removeOnTimetableDownloadListener(context as MessageReceiver.OnTimetableDownloadListener) unregisterReceiver(receiver) - timetable.close() } fun deAccent(str: String): String { @@ -199,5 +182,22 @@ timetable.refresh() stops = timetable.getStops() Snackbar.make(swipeRefreshLayout, message, Snackbar.LENGTH_LONG).show() swipeRefreshLayout.isRefreshing = false + } + + override fun edit(name: String): Boolean { + val intent = Intent(this, EditFavouriteActivity::class.java) + intent.putExtra("favourite", favourites.favourites[name]) + startActivity(intent) + favourites.refresh() + (favouritesList.adapter as FavouritesAdapter).favourites = favourites.favouritesList + favouritesList.adapter.notifyDataSetChanged() + return true + } + + override fun delete(name: String): Boolean { + favourites.delete(name) + (favouritesList.adapter as FavouritesAdapter).favourites = favourites.favouritesList + favouritesList.adapter.notifyDataSetChanged() + return true } } diff --git a/app/src/main/java/ml/adamsprogs/bimba/activities/EditFavouriteActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/activities/EditFavouriteActivity.kt new file mode 100644 index 0000000000000000000000000000000000000000..b8380a37e4ce6a65d4b9f79882aa9f9139cf7c99 --- /dev/null +++ b/app/src/main/java/ml/adamsprogs/bimba/activities/EditFavouriteActivity.kt @@ -0,0 +1,35 @@ +package ml.adamsprogs.bimba.activities + +import android.support.v7.app.AppCompatActivity +import android.os.Bundle +import android.support.v7.widget.DividerItemDecoration +import android.support.v7.widget.LinearLayoutManager +import android.support.v7.widget.RecyclerView +import android.support.v7.widget.Toolbar +import android.widget.EditText +import ml.adamsprogs.bimba.R +import ml.adamsprogs.bimba.models.Favourite +import ml.adamsprogs.bimba.models.FavouriteEditRowAdapter + +class EditFavouriteActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_edit_favourite) + + val favourite = intent.getParcelableExtra<Favourite>("favourite") + + val recyclerView = findViewById(R.id.favourite_edit_list) as RecyclerView? + val layoutManager = LinearLayoutManager(this) + recyclerView!!.layoutManager = layoutManager + val dividerItemDecoration = DividerItemDecoration(this, layoutManager.orientation) + recyclerView.addItemDecoration(dividerItemDecoration) + recyclerView.adapter = FavouriteEditRowAdapter(this, favourite) + val toolbar = findViewById(R.id.toolbar) as Toolbar + setSupportActionBar(toolbar) + supportActionBar?.title = getString(R.string.edit_favourite_title, favourite.name) + + val nameEdit = findViewById(R.id.favourite_name_edit) as EditText + nameEdit.setText(favourite.name) + } +} diff --git a/app/src/main/java/ml/adamsprogs/bimba/activities/NoDbActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/activities/NoDbActivity.kt index 01d759ab8d045afdaa50fc9d544e0b5c641e7dea..84182ce84b7908f81cd6836fdadcd6745b47f68b 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/activities/NoDbActivity.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/activities/NoDbActivity.kt @@ -17,8 +17,7 @@ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_nodb) - var filter: IntentFilter - filter = IntentFilter("ml.adamsprogs.bimba.timetableDownloaded") + var filter: IntentFilter = IntentFilter("ml.adamsprogs.bimba.timetableDownloaded") filter.addCategory(Intent.CATEGORY_DEFAULT) registerReceiver(timetableDownloadReceiver, filter) timetableDownloadReceiver.addOnTimetableDownloadListener(this) @@ -30,6 +29,21 @@ filter = IntentFilter("android.net.conn.CONNECTIVITY_CHANGE") registerReceiver(networkStateReceiver, filter) networkStateReceiver.addOnConnectivityChangeListener(this) } else + downloadTimetable() + } + + override fun onResume() { + super.onResume() + var filter: IntentFilter = IntentFilter("ml.adamsprogs.bimba.timetableDownloaded") + filter.addCategory(Intent.CATEGORY_DEFAULT) + registerReceiver(timetableDownloadReceiver, filter) + 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 if (!serviceRunning) downloadTimetable() } diff --git a/app/src/main/java/ml/adamsprogs/bimba/activities/SplashActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/activities/SplashActivity.kt index d1c341f365785ba288f0dcb13ab2f1025de2fb29..91f260811253e612ba7d26f2d14570747932690a 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/activities/SplashActivity.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/activities/SplashActivity.kt @@ -3,19 +3,20 @@ import android.support.v7.app.AppCompatActivity import android.os.Bundle import android.content.Intent -import ml.adamsprogs.bimba.models.Timetable +import android.database.sqlite.SQLiteCantOpenDatabaseException +import ml.adamsprogs.bimba.models.getTimetable class SplashActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val timetable = Timetable(this) - if(timetable.isDatabaseHealthy()) + try { + getTimetable(this) startActivity(Intent(this, DashActivity::class.java)) - else + } catch(e: SQLiteCantOpenDatabaseException) { 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 index 6fc79e3fde4c096a6231ef495ba2caf8543db131..ebdd6f4d74c1f8a06ebe87a7e5fc5f1d3ccab37a 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/activities/StopActivity.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/activities/StopActivity.kt @@ -12,7 +12,7 @@ import android.support.v7.widget.* import android.support.v4.app.* import android.support.v4.view.* import android.support.v4.content.res.ResourcesCompat -import com.google.gson.* +import android.util.Log import ml.adamsprogs.bimba.models.* import ml.adamsprogs.bimba.* @@ -49,13 +49,13 @@ createTimerTask() prepareOnDownloadListener() - timetable = Timetable(this) + timetable = getTimetable() 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)) + sectionsPagerAdapter = SectionsPagerAdapter(supportFragmentManager, createDepartures(stopId)) viewPager!!.adapter = sectionsPagerAdapter viewPager!!.addOnPageChangeListener(TabLayout.TabLayoutOnPageChangeListener(tabLayout)) @@ -67,26 +67,26 @@ scheduleRefresh() val fab = findViewById(R.id.fab) as FloatingActionButton - var favourites = Gson().fromJson(sharedPreferences.getString("favourites", "{}"), JsonObject::class.java) - if (favourites[stopSymbol] == null) { + val favourites = FavouriteStorage(context) + if (!favourites.has(stopSymbol)) { fab.setImageDrawable(ResourcesCompat.getDrawable(context.resources, R.drawable.ic_favourite_empty, this.theme)) } fab.setOnClickListener { - favourites = Gson().fromJson(sharedPreferences.getString("favourites", "{}"), JsonObject::class.java) - if (favourites[stopSymbol] == null) { - val items = JsonArray() + Log.i("FAB", "Click") + if (!favourites.has(stopSymbol)) { + Log.i("FAB", "Add") + val items = ArrayList<HashMap<String, String>>() timetable.getLines(stopId)?.forEach { - val o = JsonObject() - o.addProperty("stop", stopId) - o.addProperty("line", it) + val o = HashMap<String, String>() + o["stop"] = stopId + o["line"] = it items.add(o) } - favourites.add(stopSymbol,items) - val favouritesString = Gson().toJson(favourites) - val editor = sharedPreferences.edit() - editor.putString("favourites", favouritesString) - editor.apply() + Log.i("FAB", "Say") + favourites.add(stopSymbol, items) + Log.i("FAB", "Change") + fab.setImageDrawable(ResourcesCompat.getDrawable(context.resources, R.drawable.ic_favourite, this.theme)) } else { Snackbar.make(it, getString(R.string.stop_already_fav), Snackbar.LENGTH_LONG) .setAction("Action", null).show() @@ -151,7 +151,7 @@ timer.cancel() } else { timetableType = "departure" item.icon = (ResourcesCompat.getDrawable(resources, R.drawable.ic_timetable_full, this.theme)) - sectionsPagerAdapter?.departures = createDepartures(this, stopId) + sectionsPagerAdapter?.departures = createDepartures(stopId) sectionsPagerAdapter?.relativeTime = true sectionsPagerAdapter?.notifyDataSetChanged() scheduleRefresh() @@ -167,7 +167,6 @@ super.onDestroy() receiver.removeOnVmListener(context as MessageReceiver.OnVmListener) unregisterReceiver(receiver) timer.cancel() - timetable.close() } class PlaceholderFragment : Fragment() { 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 a8b0c8e50301ad5526416091723a16c271574ed5..c60f6c604ea2beafda6575d042f6e3943e82fa9c 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/models/DeparturesAdapter.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/models/DeparturesAdapter.kt @@ -33,8 +33,8 @@ } return filtered } -fun createDepartures(context: Context, stopId: String): HashMap<String, ArrayList<Departure>> { - val timetable = Timetable(context) +fun createDepartures(stopId: String): HashMap<String, ArrayList<Departure>> { + val timetable = getTimetable() val departures = timetable.getStopDepartures(stopId) val moreDepartures = timetable.getStopDepartures(stopId) val rolledDepartures = HashMap<String, ArrayList<Departure>>() @@ -48,8 +48,6 @@ rolledDepartures[mode] = (departures[mode] as ArrayList + moreDepartures[mode] as ArrayList<Departure>) as ArrayList<Departure> rolledDepartures[mode] = filterDepartures(rolledDepartures[mode]) } - - timetable.close() return rolledDepartures } diff --git a/app/src/main/java/ml/adamsprogs/bimba/models/Favourite.kt b/app/src/main/java/ml/adamsprogs/bimba/models/Favourite.kt index 7e89bd7184783c96401ef4cc490302041ae0a8d9..ed1e8b3182e8b86962679cf74c973054473e09c9 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/models/Favourite.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/models/Favourite.kt @@ -1,10 +1,52 @@ package ml.adamsprogs.bimba.models import android.content.Context +import android.os.Parcel +import android.os.Parcelable +import android.util.Log import java.util.* +import kotlin.collections.ArrayList +import kotlin.collections.HashMap -class Favourite(var name: String, var timetables: ArrayList<HashMap<String, String>>, context: Context) { - val timetable = Timetable(context) +class Favourite : Parcelable { + lateinit var name:String + lateinit var timetables: ArrayList<HashMap<String, String>> + lateinit var context: Context + + private constructor() + + constructor(parcel: Parcel) { + val array = ArrayList<String>() + parcel.readStringList(array) + val timetables = ArrayList<HashMap<String, String>>() + for (row in array) { + val element = HashMap<String, String>() + element["stop"] = row.split("|")[0] + element["line"] = row.split("|")[1] + timetables.add(element) + } + this.name = parcel.readString() + this.timetables = timetables + } + + constructor(name: String, timetables: ArrayList<HashMap<String, String>>) { + this.name = name + this.timetables = timetables + } + + override fun describeContents(): Int { + return 105 + } + + override fun writeToParcel(dest: Parcel?, flags: Int) { + val parcel = timetables.map { "${it["stop"]}|${it["line"]}" } + dest?.writeStringList(parcel) + dest?.writeString(name) + } + + val timetable = getTimetable() + val size: Int + get() = timetables.size var nextDeparture: Departure? = null get() { @@ -37,4 +79,24 @@ return minDeparture } private set + + fun delete(stop: String, line: String) { + Log.i("ROW", "Favourite deleting $stop, $line") + val element = HashMap<String, String>() + element["stop"] = stop + element["line"] = line + val b = timetables.remove(element) + Log.i("ROW", "$b") + Log.i("ROW", timetables.toString()) + } + + companion object CREATOR : Parcelable.Creator<Favourite> { + override fun createFromParcel(parcel: Parcel): Favourite { + return Favourite(parcel) + } + + override fun newArray(size: Int): Array<Favourite?> { + return arrayOfNulls(size) + } + } } diff --git a/app/src/main/java/ml/adamsprogs/bimba/models/FavouriteEditRowAdapter.kt b/app/src/main/java/ml/adamsprogs/bimba/models/FavouriteEditRowAdapter.kt new file mode 100644 index 0000000000000000000000000000000000000000..a1666423bf48e79f9154afa02d545a9d2d8b19b3 --- /dev/null +++ b/app/src/main/java/ml/adamsprogs/bimba/models/FavouriteEditRowAdapter.kt @@ -0,0 +1,53 @@ +package ml.adamsprogs.bimba.models + +import android.content.Context +import android.support.v7.widget.RecyclerView +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import ml.adamsprogs.bimba.R + +class FavouriteEditRowAdapter(val context: Context, var favourite: Favourite) : + RecyclerView.Adapter<FavouriteEditRowAdapter.ViewHolder>() { + override fun getItemCount(): Int { + return favourite.size + } + + override fun onBindViewHolder(holder: ViewHolder?, position: Int) { + val timetable = getTimetable() + val favourites = FavouriteStorage(context) + val favouriteElement = timetable.getFavouriteElement(favourite.timetables[position]["stop"]!!, + favourite.timetables[position]["line"]!!) + holder?.rowTextView?.text = favouriteElement + holder?.splitButton?.setOnClickListener { + favourites.detach(favourite.name, favourite.timetables[position]["stop"]!!, + favourite.timetables[position]["line"]!!, favouriteElement!!) + favourite = favourites.favourites[favourite.name]!! + notifyDataSetChanged() + } + holder?.deleteButton?.setOnClickListener { + favourites.delete(favourite.name, favourite.timetables[position]["stop"]!!, + favourite.timetables[position]["line"]!!) + favourite = favourites.favourites[favourite.name]!! + notifyDataSetChanged() + } + } + + override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): ViewHolder { + val context = parent?.context + val inflater = LayoutInflater.from(context) + + val rowView = inflater.inflate(R.layout.row_favourite_edit, parent, false) + val viewHolder = ViewHolder(rowView) + return viewHolder + } + + inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val rowTextView = itemView.findViewById(R.id.favourite_edit_row) as TextView + val splitButton = itemView.findViewById(R.id.favourite_edit_split) as ImageView + val deleteButton = itemView.findViewById(R.id.favourite_edit_delete) as ImageView + } +} \ No newline at end of file diff --git a/app/src/main/java/ml/adamsprogs/bimba/models/FavouriteStorage.kt b/app/src/main/java/ml/adamsprogs/bimba/models/FavouriteStorage.kt new file mode 100644 index 0000000000000000000000000000000000000000..970e2ad54c03a8a60f601be937903c1ed3016f0f --- /dev/null +++ b/app/src/main/java/ml/adamsprogs/bimba/models/FavouriteStorage.kt @@ -0,0 +1,88 @@ +package ml.adamsprogs.bimba.models + +import android.content.Context +import android.content.SharedPreferences +import android.util.Log +import com.google.gson.Gson +import com.google.gson.JsonArray +import com.google.gson.JsonObject + +class FavouriteStorage(val context: Context) { + val favourites = HashMap<String, Favourite>() + val preferences: SharedPreferences = context.getSharedPreferences("ml.adamsprogs.bimba.prefs", Context.MODE_PRIVATE) + val favouritesList: List<Favourite> + get() { + return favourites.values.toList() + } + + init { + refresh() + } + + fun refresh() { + val favouritesString = preferences.getString("favourites", "{}") + val favouritesMap = Gson().fromJson(favouritesString, JsonObject::class.java) + for ((name, jsonTimetables) in favouritesMap.entrySet()) { + val timetables = ArrayList<HashMap<String, String>>() + for (jsonTimetable in jsonTimetables.asJsonArray) { + val timetable = HashMap<String, String>() + timetable["stop"] = jsonTimetable.asJsonObject["stop"].asString + timetable["line"] = jsonTimetable.asJsonObject["line"].asString + timetables.add(timetable) + } + favourites[name] = Favourite(name, timetables) + } + } + + fun has(name: String): Boolean = favourites.contains(name) + + fun add(name: String, timetables: ArrayList<HashMap<String, String>>) { + if (favourites[name] == null) { + favourites[name] = Favourite(name, timetables) + serialize() + } + } + + fun delete(name: String) { + favourites.remove(name) + serialize() + } + + fun delete(name: String, stop: String, line: String) { + Log.i("ROW", "delete $name, $stop, $line") + favourites[name]?.delete(stop, line) + //todo check empty + serialize() + } + + fun serialize() { + val rootObject = JsonObject() + for ((name, favourite) in favourites) { + val timetables = JsonArray() + for (timetable in favourite.timetables) { + val element = JsonObject() + element.addProperty("stop", timetable["stop"]) + element.addProperty("line", timetable["line"]) + timetables.add(element) + } + rootObject.add(name, timetables) + } + val favouritesString = Gson().toJson(rootObject) + Log.i("FAB", favouritesString) + val editor = preferences.edit() + editor.putString("favourites", favouritesString) + editor.apply() + } + + fun detach(name: String, stop: String, line: String, newName: String) { + val element = HashMap<String, String>() + element["stop"] = stop + element["line"] = line + val array = ArrayList<HashMap<String, String>>() + array.add(element) + favourites[newName] = Favourite(newName, array) + serialize() + + delete(name, stop, line) + } +} \ No newline at end of file diff --git a/app/src/main/java/ml/adamsprogs/bimba/models/FavouritesAdapter.kt b/app/src/main/java/ml/adamsprogs/bimba/models/FavouritesAdapter.kt index d97e13817f530006d3a3d4537557e9bedb0f374a..b0df901aa1f92774af921db31a166d13fbd16508 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/models/FavouritesAdapter.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/models/FavouritesAdapter.kt @@ -12,7 +12,7 @@ import android.view.LayoutInflater import java.util.* -class FavouritesAdapter(val context: Context, val favourites: List<Favourite>) : +class FavouritesAdapter(val context: Context, var favourites: List<Favourite>, val onMenuItemClickListener: FavouritesAdapter.OnMenuItemClickListener) : RecyclerView.Adapter<FavouritesAdapter.ViewHolder>() { override fun getItemCount(): Int { return favourites.size @@ -37,8 +37,8 @@ val popup = PopupMenu(context, it) val inflater = popup.menuInflater popup.setOnMenuItemClickListener { when (it.itemId) { - R.id.favourite_edit -> editFavourite(favourite.name) - R.id.favourite_delete -> deleteFavourite(favourite.name) + R.id.favourite_edit -> onMenuItemClickListener.edit(favourite.name) + R.id.favourite_delete -> onMenuItemClickListener.delete(favourite.name) else -> false } } @@ -47,16 +47,6 @@ popup.show() } } - private fun editFavourite(name: String): Boolean { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - return true - } - - private fun deleteFavourite(name: String): Boolean { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - return true - } - override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): ViewHolder { val context = parent?.context val inflater = LayoutInflater.from(context) @@ -71,5 +61,10 @@ val nameTextView = itemView.findViewById(R.id.favourite_name) as TextView val timeTextView = itemView.findViewById(R.id.favourite_time) as TextView val lineTextView = itemView.findViewById(R.id.favourite_line) as TextView val moreButton = itemView.findViewById(R.id.favourite_more_button) as ImageView + } + + interface OnMenuItemClickListener { + fun edit(name: String): Boolean + fun delete(name: String): Boolean } } \ No newline at end of file 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 2117cdd3dbd5a679c3f0ffb51d6b55e37a610802..bf8d21faf6cccb40a3bbf0ac8155448e0dbaffd9 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/models/Timetable.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/models/Timetable.kt @@ -5,18 +5,39 @@ import android.database.Cursor import android.database.sqlite.SQLiteCantOpenDatabaseException import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabaseCorruptException -import android.util.Log import java.io.File -class Timetable(var context: Context) { - var db: SQLiteDatabase? = null +private var timetable: Timetable? = null + +fun getTimetable(context: Context? = null, force: Boolean = false): Timetable { + if (timetable == null || force) + if (context != null) { + val db: SQLiteDatabase? + try { + db = SQLiteDatabase.openDatabase(File(context.filesDir, "timetable.db").path, + null, SQLiteDatabase.OPEN_READONLY) + } catch(e: NoSuchFileException) { + throw SQLiteCantOpenDatabaseException("no such file") + } catch(e: SQLiteCantOpenDatabaseException) { + throw SQLiteCantOpenDatabaseException("cannot open db") + } catch(e: SQLiteDatabaseCorruptException) { + throw SQLiteCantOpenDatabaseException("db corrupt") + } + timetable = Timetable() + timetable!!.db = db + return timetable as Timetable + } + else + throw IllegalArgumentException("new timetable requested and no context given") + else + return timetable as Timetable +} + +class Timetable internal constructor() { + lateinit var db: SQLiteDatabase init { readDbFile() - } - - fun isDatabaseHealthy(): Boolean { - return db != null } fun refresh() { @@ -24,29 +45,17 @@ readDbFile() } private fun readDbFile() { - try { - db = SQLiteDatabase.openDatabase(File(context.filesDir, "timetable.db").path, - null, SQLiteDatabase.OPEN_READONLY) - } catch(e: SQLiteCantOpenDatabaseException) { - Log.e("Timetable", "Cannot open database") - db = null - } catch(e: SQLiteDatabaseCorruptException) { - Log.e("Timetable", "Database is corrupted") - db = null - } } fun getStops(): ArrayList<StopSuggestion>? { - if (db == null) - return null val stops = ArrayList<StopSuggestion>() - var cursor : Cursor? = null + var cursor: Cursor? = null try { - cursor = db!!.rawQuery("select name ||char(10)|| headsigns as suggestion, id, stops.symbol || number as stopSymbol from stops" + + cursor = db.rawQuery("select name ||char(10)|| headsigns as suggestion, id, stops.symbol || number as stopSymbol from stops" + " join nodes on(stops.symbol = nodes.symbol) order by name, id;", null) while (cursor.moveToNext()) stops.add(StopSuggestion(cursor.getString(0), cursor.getString(1), cursor.getString(2))) - }catch (e: SQLiteDatabaseCorruptException) { + } catch (e: SQLiteDatabaseCorruptException) { cursor?.close() return null } finally { @@ -56,9 +65,7 @@ return stops } fun getStopName(stopId: String): String? { - if (db == null) - return null - val cursor = db!!.rawQuery("select name from nodes join stops on(stops.symbol = nodes.symbol) where id = ?;", + val cursor = db.rawQuery("select name from nodes join stops on(stops.symbol = nodes.symbol) where id = ?;", listOf(stopId).toTypedArray()) val name: String cursor.moveToNext() @@ -68,14 +75,12 @@ return name } fun getStopDepartures(stopId: String, lineId: String? = null): HashMap<String, ArrayList<Departure>>? { - if (db == null) - return null - val andLine:String + val andLine: String if (lineId == null) andLine = "" else andLine = "and line_id = '$lineId'" - val cursor = db!!.rawQuery("select lines.number, mode, substr('0'||hour, -2) || ':' || " + + val cursor = db.rawQuery("select lines.number, mode, substr('0'||hour, -2) || ':' || " + "substr('0'||minute, -2) as time, lowFloor, modification, headsign from departures join " + "timetables on(timetable_id = timetables.id) join lines on(line_id = lines.id) where " + "stop_id = ? $andLine order by mode, time;", listOf(stopId).toTypedArray()) @@ -93,9 +98,7 @@ return departures } fun getLines(stopId: String?): ArrayList<String>? { - if (db == null) - return null - val cursor = db!!.rawQuery(" select distinct line_id from timetables join " + + val cursor = db.rawQuery(" select distinct line_id from timetables join " + "stops on(stop_id = stops.id) where stops.id = ?;", listOf(stopId).toTypedArray()) val lines = ArrayList<String>() @@ -106,7 +109,16 @@ cursor.close() return lines } - fun close() { - db?.close() + fun getFavouriteElement(stop: String, line: String): String? { + val cursor = db.rawQuery("select name || ' (' || stops.symbol || stops.number || '): \n' " + + "|| lines.number || ' → ' || headsign from timetables join stops on (stops.id = stop_id) " + + "join lines on(lines.id = line_id) join nodes on(nodes.symbol = stops.symbol) where " + + "stop_id = ? and line_id = ?", + listOf(stop, line).toTypedArray()) + val element: String + cursor.moveToNext() + element = cursor.getString(0) + cursor.close() + return element } } diff --git a/app/src/main/res/drawable/ic_delete.xml b/app/src/main/res/drawable/ic_delete.xml new file mode 100644 index 0000000000000000000000000000000000000000..39e64d6980a8c1d7330ec2eb8172779cffd483af --- /dev/null +++ b/app/src/main/res/drawable/ic_delete.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/> +</vector> diff --git a/app/src/main/res/drawable/ic_split.xml b/app/src/main/res/drawable/ic_split.xml new file mode 100644 index 0000000000000000000000000000000000000000..327e1d2f554eac9ed106caf94f45a9421e15fd22 --- /dev/null +++ b/app/src/main/res/drawable/ic_split.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M14,4l2.29,2.29 -2.88,2.88 1.42,1.42 2.88,-2.88L20,10L20,4zM10,4L4,4v6l2.29,-2.29 4.71,4.7L11,20h2v-8.41l-5.29,-5.3z"/> +</vector> diff --git a/app/src/main/res/layout/activity_edit_favourite.xml b/app/src/main/res/layout/activity_edit_favourite.xml new file mode 100644 index 0000000000000000000000000000000000000000..b93a989607cf125b465c9631cbb173bec3502693 --- /dev/null +++ b/app/src/main/res/layout/activity_edit_favourite.xml @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="utf-8"?> +<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/dialog_favourite" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <android.support.design.widget.AppBarLayout + android:id="@+id/appbar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="@dimen/appbar_padding_top" + android:theme="@style/AppTheme.AppBarOverlay"> + + <android.support.v7.widget.Toolbar + android:id="@+id/toolbar" + android:layout_width="match_parent" + android:layout_height="?attr/actionBarSize" + android:layout_weight="1" + android:background="@color/colorPrimary" + app:layout_scrollFlags="scroll|enterAlways" + app:popupTheme="@style/AppTheme.PopupOverlay" /> + </android.support.design.widget.AppBarLayout> + + <TextView + android:id="@+id/name_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginTop="8dp" + android:labelFor="@id/favourite_name_edit" + android:text="@string/favourite_name" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/appbar" /> + + <EditText + android:id="@+id/favourite_name_edit" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:ems="10" + android:inputType="text" + android:text="" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/name_label" + app:layout_constraintEnd_toEndOf="parent" + android:layout_marginEnd="8dp" /> + + <android.support.v7.widget.RecyclerView + android:id="@+id/favourite_edit_list" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_marginBottom="8dp" + android:layout_marginEnd="8dp" + android:layout_marginStart="8dp" + android:layout_marginTop="8dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/favourite_name_edit"> + + </android.support.v7.widget.RecyclerView> + +</android.support.constraint.ConstraintLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/row_favourite_edit.xml b/app/src/main/res/layout/row_favourite_edit.xml new file mode 100644 index 0000000000000000000000000000000000000000..5128233f1048e216c7a8a55629cd5935e0b31f2f --- /dev/null +++ b/app/src/main/res/layout/row_favourite_edit.xml @@ -0,0 +1,54 @@ +<?xml version="1.0" encoding="utf-8"?> +<android.support.constraint.ConstraintLayout 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" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + + <TextView + android:id="@+id/favourite_edit_row" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginBottom="16dp" + android:layout_marginEnd="16dp" + android:layout_marginStart="16dp" + android:layout_marginTop="16dp" + android:gravity="center" + android:text="" + android:textAlignment="viewStart" + android:textAppearance="@style/TextAppearance.AppCompat" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@+id/favourite_edit_split" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <ImageView + android:id="@+id/favourite_edit_delete" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="16dp" + android:layout_marginStart="9dp" + android:layout_marginTop="16dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toEndOf="@+id/favourite_edit_split" + app:layout_constraintTop_toTopOf="parent" + app:srcCompat="@drawable/ic_delete" + tools:layout_editor_absoluteX="335dp" + tools:layout_editor_absoluteY="16dp" /> + + <ImageView + android:id="@+id/favourite_edit_split" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="16dp" + android:layout_marginEnd="58dp" + android:layout_marginTop="16dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:srcCompat="@drawable/ic_split" + tools:layout_editor_absoluteX="302dp" + tools:layout_editor_absoluteY="16dp" /> +</android.support.constraint.ConstraintLayout> \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 59eab5f8398508474e4978a95a5c2ea60d22b81b..2192e1f29a34a3a9c7a67018d632e24446c9052c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -26,4 +26,8 @@ This stop is already in favourites <string name="favourite_row_more_button" translatable="false">favourite row more button</string> <string name="action_edit">Edit</string> <string name="action_delete">Delete</string> + <string name="done">Done</string> + <string name="favourite_edit">Edit favourite</string> + <string name="favourite_name">Favourite name</string> + <string name="edit_favourite_title">Edit ‘%1$s’</string> </resources> diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index d95d96583cbfd31bc307f6f239448f4967e10343..776858ee5161cc4c8e03b8cedf732ab4ed6a4e8a 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -22,4 +22,6 @@Ten przystanek już jest pośród ulubionych <string name="departure_to_line">%1$s → %2$s</string> <string name="action_delete">Usuń</string> <string name="action_edit">Edytuj</string> + <string name="done">Zakończ</string> + <string name="favourite_edit">Edytuj ulubiony</string> </resources> \ No newline at end of file