refactoring hltb example to use navigation
This commit is contained in:
parent
33f9786d39
commit
ec81c2a24f
|
@ -3,7 +3,7 @@ package be.kuleuven.howlongtobeat.model.room
|
|||
import androidx.room.Room
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import be.kuleuven.howlongtobeat.model.Todo
|
||||
import be.kuleuven.howlongtobeat.model.Game
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
|
@ -11,15 +11,15 @@ import org.junit.Test
|
|||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class TodoPersistenceTests {
|
||||
class GamePersistenceTests {
|
||||
|
||||
private lateinit var db: TodoDatabase
|
||||
private lateinit var dao: TodoDao
|
||||
private lateinit var db: GameDatabase
|
||||
private lateinit var dao: GameDao
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
db = Room.inMemoryDatabaseBuilder(appContext, TodoDatabase::class.java)
|
||||
db = Room.inMemoryDatabaseBuilder(appContext, GameDatabase::class.java)
|
||||
.setQueryCallback(LogQueryCallBack(), CurrentThreadExecutor())
|
||||
.build()
|
||||
db.clearAllTables()
|
||||
|
@ -33,7 +33,7 @@ class TodoPersistenceTests {
|
|||
|
||||
@Test
|
||||
fun todoItemCanBePersisted() {
|
||||
val item = Todo("brush my little pony", false)
|
||||
val item = Game("brush my little pony", false)
|
||||
dao.insert(arrayListOf(item))
|
||||
|
||||
val refreshedItem = dao.query().single()
|
||||
|
@ -46,7 +46,7 @@ class TodoPersistenceTests {
|
|||
|
||||
@Test
|
||||
fun updateUpdatesTodoPropertiesInDb() {
|
||||
var todo = Todo("git good at Hollow Knight", false)
|
||||
var todo = Game("git good at Hollow Knight", false)
|
||||
dao.insert(arrayListOf(todo))
|
||||
todo = dao.query().single() // refresh to get the ID, otherwise update() will update where ID = 0
|
||||
|
||||
|
@ -61,7 +61,7 @@ class TodoPersistenceTests {
|
|||
}
|
||||
}
|
||||
|
||||
private fun finallyFinishHollowKnight(item: Todo) {
|
||||
private fun finallyFinishHollowKnight(item: Game) {
|
||||
println("Congrats! On to Demon Souls?")
|
||||
item.check()
|
||||
}
|
|
@ -3,6 +3,7 @@
|
|||
package="be.kuleuven.howlongtobeat">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
|
|
|
@ -6,21 +6,18 @@ import android.view.ViewGroup
|
|||
import android.widget.CheckBox
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import be.kuleuven.howlongtobeat.model.Todo
|
||||
import be.kuleuven.howlongtobeat.model.Game
|
||||
|
||||
class TodoAdapter(val items: List<Todo>) : RecyclerView.Adapter<TodoAdapter.TodoViewHolder>() {
|
||||
class GameListAdapter(val items: List<Game>) : RecyclerView.Adapter<GameListAdapter.GameListViewHolder>() {
|
||||
|
||||
inner class TodoViewHolder(currentItemView: View) : RecyclerView.ViewHolder(currentItemView)
|
||||
inner class GameListViewHolder(currentItemView: View) : RecyclerView.ViewHolder(currentItemView)
|
||||
|
||||
// this creates the needed ViewHolder class that links our layout XML to our viewHolder
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TodoViewHolder {
|
||||
// don't forget to set attachToRoot to false, otherwise it will crash!
|
||||
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_todo, parent, false)
|
||||
return TodoViewHolder(view)
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameListViewHolder {
|
||||
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_game, parent, false)
|
||||
return GameListViewHolder(view)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: TodoViewHolder, position: Int) {
|
||||
// bind the data to our items: set the todo text view text and checked state accordingly
|
||||
override fun onBindViewHolder(holder: GameListViewHolder, position: Int) {
|
||||
val currentTodoItem = items[position]
|
||||
holder.itemView.apply {
|
||||
val checkBoxTodo = findViewById<CheckBox>(R.id.chkTodoDone)
|
|
@ -0,0 +1,75 @@
|
|||
package be.kuleuven.howlongtobeat
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import be.kuleuven.howlongtobeat.databinding.FragmentGamelistBinding
|
||||
import be.kuleuven.howlongtobeat.model.Game
|
||||
import be.kuleuven.howlongtobeat.model.GameRepository
|
||||
import be.kuleuven.howlongtobeat.model.room.GameRepositoryRoomImpl
|
||||
|
||||
class GameListFragment : Fragment(R.layout.fragment_gamelist) {
|
||||
|
||||
private val gameList = arrayListOf<Game>()
|
||||
|
||||
private lateinit var binding: FragmentGamelistBinding
|
||||
private lateinit var main: MainActivity
|
||||
private lateinit var adapter: GameListAdapter
|
||||
private lateinit var gameRepository: GameRepository
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
binding = FragmentGamelistBinding.inflate(layoutInflater)
|
||||
main = activity as MainActivity
|
||||
gameRepository = GameRepositoryRoomImpl(main.applicationContext)
|
||||
loadGames()
|
||||
|
||||
adapter = GameListAdapter(gameList)
|
||||
binding.rvGameList.adapter = adapter
|
||||
binding.rvGameList.layoutManager = LinearLayoutManager(this.context)
|
||||
|
||||
binding.btnAddTodo.setOnClickListener(this::addNewTotoItem)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
private fun loadGames() {
|
||||
gameList.addAll(gameRepository.load())
|
||||
if(!gameList.any()) {
|
||||
gameList.add(Game.NONE_YET)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addNewTotoItem(it: View) {
|
||||
findNavController().navigate(R.id.action_gameListFragment_to_loadingFragment)
|
||||
}
|
||||
|
||||
/*
|
||||
fun onHltbGamesRetrieved(games: List<HowLongToBeatResult>) {
|
||||
gameList.clear()
|
||||
gameList.addAll(games.map { Game("${it.title} (${it.howlong})", false) })
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun clearAllItems() {
|
||||
gameList.clear()
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun clearLatestItem() {
|
||||
if(gameList.size >= 1) {
|
||||
gameList.removeAt(gameList.size - 1)
|
||||
adapter.notifyItemRemoved(gameList.size - 1)
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package be.kuleuven.howlongtobeat
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import be.kuleuven.howlongtobeat.hltb.HowLongToBeatResult
|
||||
|
||||
class HltbResultsAdapter(private val items: List<HowLongToBeatResult>) : RecyclerView.Adapter<HltbResultsAdapter.HltbResultsViewHolder>() {
|
||||
|
||||
inner class HltbResultsViewHolder(currentItemView: View) : RecyclerView.ViewHolder(currentItemView)
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HltbResultsViewHolder {
|
||||
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_hltbresult, parent, false)
|
||||
return HltbResultsViewHolder(view)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: HltbResultsViewHolder, position: Int) {
|
||||
val currentResult = items[position]
|
||||
holder.itemView.apply {
|
||||
val txtHltbItemResult = findViewById<TextView>(R.id.txtHltbItemResult)
|
||||
txtHltbItemResult.text = currentResult.title
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = items.size
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package be.kuleuven.howlongtobeat
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import be.kuleuven.howlongtobeat.databinding.FragmentHltbresultsBinding
|
||||
import be.kuleuven.howlongtobeat.hltb.HowLongToBeatResult
|
||||
|
||||
class HltbResultsFragment : Fragment(R.layout.fragment_hltbresults) {
|
||||
private lateinit var binding: FragmentHltbresultsBinding
|
||||
private lateinit var adapter: HltbResultsAdapter
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
binding = FragmentHltbresultsBinding.inflate(layoutInflater)
|
||||
|
||||
val resultFromLoadingFragment = arguments?.getSerializable(HowLongToBeatResult.RESULT) as List<HowLongToBeatResult>
|
||||
|
||||
adapter = HltbResultsAdapter(resultFromLoadingFragment)
|
||||
binding.rvHltbResult.adapter = adapter
|
||||
binding.rvHltbResult.layoutManager = LinearLayoutManager(this.context)
|
||||
|
||||
return binding.root
|
||||
}
|
||||
}
|
|
@ -1,5 +1,127 @@
|
|||
package be.kuleuven.howlongtobeat
|
||||
|
||||
import android.Manifest
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import be.kuleuven.howlongtobeat.cartridges.Cartridge
|
||||
import be.kuleuven.howlongtobeat.cartridges.CartridgesRepository
|
||||
import be.kuleuven.howlongtobeat.databinding.FragmentLoadingBinding
|
||||
import be.kuleuven.howlongtobeat.google.GoogleVisionClient
|
||||
import be.kuleuven.howlongtobeat.hltb.HLTBClient
|
||||
import be.kuleuven.howlongtobeat.hltb.HowLongToBeatResult
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class LoadingFragment : Fragment(R.layout.fragment_loading)
|
||||
class LoadingFragment : Fragment(R.layout.fragment_loading) {
|
||||
|
||||
private lateinit var hltbClient: HLTBClient
|
||||
private lateinit var cartRepo: CartridgesRepository
|
||||
private lateinit var visionClient: GoogleVisionClient
|
||||
|
||||
private lateinit var cameraPermissionActivityResult: ActivityResultLauncher<String>
|
||||
private lateinit var cameraActivityResult: ActivityResultLauncher<Void>
|
||||
private lateinit var main: MainActivity
|
||||
private lateinit var binding: FragmentLoadingBinding
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
binding = FragmentLoadingBinding.inflate(layoutInflater)
|
||||
main = activity as MainActivity
|
||||
|
||||
cartRepo = CartridgesRepository.fromAsset(main.applicationContext)
|
||||
visionClient = GoogleVisionClient()
|
||||
hltbClient = HLTBClient(main.applicationContext)
|
||||
|
||||
cameraActivityResult = registerForActivityResult(ActivityResultContracts.TakePicturePreview(), this::cameraSnapTaken)
|
||||
cameraPermissionActivityResult = registerForActivityResult(ActivityResultContracts.RequestPermission(), this::cameraPermissionAcceptedOrDenied)
|
||||
tryToMakeCameraSnap()
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
private fun cameraSnapTaken(pic: Bitmap) {
|
||||
MainScope().launch{
|
||||
findGameBasedOnCameraSnap(pic)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun findGameBasedOnCameraSnap(pic: Bitmap) {
|
||||
progress("Unleashing Google Vision on the pic...")
|
||||
// TODO remove in future
|
||||
val dummypic = BitmapFactory.decodeResource(resources, R.drawable.supermarioland2)
|
||||
val cartCode = visionClient.findCartCodeViaGoogleVision(dummypic)
|
||||
|
||||
if (cartCode == null) {
|
||||
errorInProgress("Unable to find a code in your pic. Retry?")
|
||||
return
|
||||
}
|
||||
|
||||
progress("Found cart code $cartCode, looking in DB...")
|
||||
val foundCart = cartRepo.find(cartCode)
|
||||
|
||||
if (foundCart == Cartridge.UNKNOWN_CART) {
|
||||
errorInProgress("$cartCode is an unknown game cartridge. Retry?")
|
||||
return
|
||||
}
|
||||
|
||||
progress("Valid cart code $cartCode, looking in HLTB...")
|
||||
hltbClient.find(foundCart.title) {
|
||||
Snackbar.make(requireView(), "Found ${it.size} game(s) for cart $cartCode", Snackbar.LENGTH_LONG).show()
|
||||
|
||||
// TODO wat als geen hltb results gevonden?
|
||||
val bundle = bundleOf(HowLongToBeatResult.RESULT to it, HowLongToBeatResult.CODE to cartCode)
|
||||
findNavController().navigate(R.id.action_loadingFragment_to_hltbResultsFragment, bundle)
|
||||
}
|
||||
}
|
||||
|
||||
private fun cameraPermissionAcceptedOrDenied(succeeded: Boolean) {
|
||||
if(succeeded) {
|
||||
makeCameraSnap()
|
||||
} else {
|
||||
progress("Camera permission required!")
|
||||
}
|
||||
}
|
||||
|
||||
private fun tryToMakeCameraSnap() {
|
||||
progress("Making snapshot with camera...")
|
||||
if(ContextCompat.checkSelfPermission(main, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
|
||||
cameraPermissionActivityResult.launch(Manifest.permission.CAMERA)
|
||||
}
|
||||
makeCameraSnap()
|
||||
}
|
||||
|
||||
private fun makeCameraSnap() {
|
||||
cameraActivityResult.launch(null)
|
||||
}
|
||||
|
||||
private fun progress(msg: String) {
|
||||
if(!binding.indeterminateBar.isAnimating) {
|
||||
binding.indeterminateBar.animate()
|
||||
}
|
||||
binding.txtLoading.text = msg
|
||||
}
|
||||
|
||||
private fun errorInProgress(msg: String) {
|
||||
progress(msg)
|
||||
binding.indeterminateBar.clearAnimation()
|
||||
Snackbar.make(requireView(), msg, Snackbar.LENGTH_LONG).show()
|
||||
// todo show retry button or something?
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,77 +1,33 @@
|
|||
package be.kuleuven.howlongtobeat
|
||||
|
||||
import android.graphics.BitmapFactory
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.appcompat.app.ActionBarDrawerToggle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import be.kuleuven.howlongtobeat.cartridges.CartridgesRepository
|
||||
import be.kuleuven.howlongtobeat.databinding.ActivityMainBinding
|
||||
import be.kuleuven.howlongtobeat.google.GoogleVisionClient
|
||||
import be.kuleuven.howlongtobeat.hltb.HLTBClient
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
private lateinit var menuBarToggle: ActionBarDrawerToggle
|
||||
private var todoFragment = TodoFragment()
|
||||
private var loadingFragment = LoadingFragment()
|
||||
private lateinit var hltbClient: HLTBClient
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
|
||||
setCurrentFragment(loadingFragment)
|
||||
setupMenuDrawer()
|
||||
|
||||
hltbClient = HLTBClient(applicationContext) {
|
||||
todoFragment.onHltbGamesRetrieved(it)
|
||||
}
|
||||
|
||||
val cartRepo = CartridgesRepository.fromAsset(applicationContext)
|
||||
val sml2SampleData = BitmapFactory.decodeResource(resources, R.drawable.supermarioland2)
|
||||
MainScope().launch{
|
||||
val cartCode = GoogleVisionClient().findCartCodeViaGoogleVision(sml2SampleData)
|
||||
val foundCart = cartRepo.find(cartCode)
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
Snackbar.make(binding.root, "Found cart: ${foundCart.title}!", Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
setCurrentFragment(todoFragment)
|
||||
hltbClient.triggerFind(foundCart.title)
|
||||
}
|
||||
|
||||
setContentView(binding.root)
|
||||
}
|
||||
|
||||
private fun setCurrentFragment(fragment: Fragment) {
|
||||
supportFragmentManager.beginTransaction().apply {
|
||||
replace(R.id.frmTodoContainer, fragment)
|
||||
commit()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupMenuDrawer() {
|
||||
menuBarToggle = ActionBarDrawerToggle(this, binding.drawerLayout, R.string.menu_open, R.string.menu_close)
|
||||
binding.drawerLayout.addDrawerListener(menuBarToggle)
|
||||
// it's now ready to be used
|
||||
menuBarToggle.syncState()
|
||||
|
||||
// when the menu drawer opens, the toggle button moves to a "back" button and it will close again.
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
// handle menu drawer item clicks.
|
||||
// since these are all events that influence the fragment list, delegate their actions!
|
||||
binding.navView.setNavigationItemSelectedListener {
|
||||
when (it.itemId) {
|
||||
R.id.mnuClear -> clearAllItems()
|
||||
|
@ -83,27 +39,21 @@ class MainActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
private fun clearAllItems() {
|
||||
todoFragment.clearAllItems()
|
||||
//todoFragment.clearAllItems()
|
||||
}
|
||||
|
||||
private fun clearLatestItem() {
|
||||
todoFragment.clearLatestItem()
|
||||
//todoFragment.clearLatestItem()
|
||||
}
|
||||
|
||||
private fun resetItems() {
|
||||
todoFragment.resetItems()
|
||||
//todoFragment.resetItems()
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
// we need to do this to respond correctly to clicks on menu items, otherwise it won't be caught
|
||||
if(menuBarToggle.onOptionsItemSelected(item)) {
|
||||
return true
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
fun hideKeyboard(view: View) {
|
||||
val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imm.hideSoftInputFromWindow(view.windowToken, 0)
|
||||
}
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
package be.kuleuven.howlongtobeat
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import be.kuleuven.howlongtobeat.databinding.FragmentTodolistBinding
|
||||
import be.kuleuven.howlongtobeat.hltb.Game
|
||||
import be.kuleuven.howlongtobeat.hltb.HLTBClient
|
||||
import be.kuleuven.howlongtobeat.model.Todo
|
||||
|
||||
class TodoFragment : Fragment(R.layout.fragment_todolist) {
|
||||
|
||||
private val todoList = arrayListOf<Todo>()
|
||||
private lateinit var hltbClient: HLTBClient
|
||||
|
||||
private lateinit var binding: FragmentTodolistBinding
|
||||
private lateinit var main: MainActivity
|
||||
private lateinit var adapter: TodoAdapter
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
binding = FragmentTodolistBinding.inflate(layoutInflater)
|
||||
main = activity as MainActivity
|
||||
|
||||
|
||||
adapter = TodoAdapter(todoList)
|
||||
binding.rvwTodo.adapter = adapter
|
||||
// If we don't supply a layout manager, the recyclerview will not be displayed
|
||||
// there are three options here: a simple LinearLayoutManager (1-dimensional), a GridLayoutManager (2D) or a StaggeredGridLayoutManager
|
||||
binding.rvwTodo.layoutManager = LinearLayoutManager(this.context)
|
||||
|
||||
binding.btnAddTodo.setOnClickListener(this::addNewTotoItem)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
fun onHltbGamesRetrieved(games: List<Game>) {
|
||||
todoList.clear()
|
||||
todoList.addAll(games.map { Todo("${it.title} (${it.howlong})", false) })
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
private fun addNewTotoItem(it: View) {
|
||||
val newTodoTitle = binding.edtTodo.text.toString()
|
||||
// this will not automatically updat the view!
|
||||
todoList.add(Todo(newTodoTitle, false))
|
||||
adapter.notifyItemInserted(todoList.size - 1)
|
||||
// adapter.notifyDatasetChanged() also works but will update EVERYTHING, which is not too efficient.
|
||||
binding.edtTodo.text.clear()
|
||||
binding.edtTodo.clearFocus()
|
||||
|
||||
main.hideKeyboard(it)
|
||||
}
|
||||
|
||||
fun clearAllItems() {
|
||||
todoList.clear()
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun clearLatestItem() {
|
||||
if(todoList.size >= 1) {
|
||||
todoList.removeAt(todoList.size - 1)
|
||||
adapter.notifyItemRemoved(todoList.size - 1)
|
||||
}
|
||||
}
|
||||
|
||||
fun resetItems() {
|
||||
todoList.clear()
|
||||
todoList.addAll(Todo.defaults())
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
}
|
|
@ -24,10 +24,8 @@ class GoogleVisionClient {
|
|||
suspend fun findCartCodeViaGoogleVision(cameraSnap: Bitmap): String? {
|
||||
var response: BatchAnnotateImagesResponse
|
||||
withContext(Dispatchers.IO) {
|
||||
println("Encoding image...")
|
||||
val sml2Data = cameraSnap.asEncodedGoogleVisionImage()
|
||||
|
||||
println("Done, uploading image...")
|
||||
val req = AnnotateImageRequest().apply {
|
||||
features = listOf(Feature().apply {
|
||||
type = "TEXT_DETECTION"
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
package be.kuleuven.howlongtobeat.hltb
|
||||
|
||||
data class Game(val title: String, val howlong: Double, val finished: Boolean = false) {
|
||||
}
|
|
@ -5,7 +5,7 @@ import com.android.volley.Response
|
|||
import com.android.volley.toolbox.StringRequest
|
||||
import com.android.volley.toolbox.Volley
|
||||
|
||||
class HLTBClient(val context: Context, val onResponseFetched: (List<Game>) -> Unit) {
|
||||
class HLTBClient(val context: Context) {
|
||||
|
||||
// Inspired by https://www.npmjs.com/package/howlongtobeat
|
||||
// The API is abysmal, but hey, it works...
|
||||
|
@ -37,7 +37,7 @@ class HLTBClient(val context: Context, val onResponseFetched: (List<Game>) -> Un
|
|||
}
|
||||
}
|
||||
|
||||
fun triggerFind(query: String) {
|
||||
fun find(query: String, onResponseFetched: (List<HowLongToBeatResult>) -> Unit) {
|
||||
val queue = Volley.newRequestQueue(context)
|
||||
val req = HLTBRequest(query) {
|
||||
onResponseFetched(HowLongToBeatResultParser.parse(it))
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
package be.kuleuven.howlongtobeat.hltb
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class HowLongToBeatResult(val title: String, val howlong: Double, val finished: Boolean = false) : java.io.Serializable {
|
||||
companion object {
|
||||
const val RESULT = "HowLongToBeatResult"
|
||||
const val CODE = "CartCode"
|
||||
}
|
||||
}
|
|
@ -5,8 +5,8 @@ object HowLongToBeatResultParser {
|
|||
private val titleMatcher = """<a class=".+" title="(.+)" href=""".toRegex()
|
||||
private val hourMatcher = """<div class=".+">(.+) Hours""".toRegex()
|
||||
|
||||
fun parse(html: String): List<Game> {
|
||||
val result = arrayListOf<Game>()
|
||||
fun parse(html: String): List<HowLongToBeatResult> {
|
||||
val result = arrayListOf<HowLongToBeatResult>()
|
||||
val rows = html.split("\n")
|
||||
for(i in 0..rows.size - 1) {
|
||||
val matched = titleMatcher.find(rows[i])
|
||||
|
@ -14,7 +14,7 @@ object HowLongToBeatResultParser {
|
|||
val (title) = matched.destructured
|
||||
val hour = parseHoursFromRow(i, rows)
|
||||
|
||||
result.add(Game(title, hour))
|
||||
result.add(HowLongToBeatResult(title, hour))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package be.kuleuven.howlongtobeat.model
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity
|
||||
data class Game(
|
||||
@ColumnInfo(name = "title") val title: String,
|
||||
@ColumnInfo(name = "is_done") var isDone: Boolean = false,
|
||||
@PrimaryKey(autoGenerate = true) var id: Int = 0) : java.io.Serializable {
|
||||
|
||||
fun check() {
|
||||
isDone = true
|
||||
}
|
||||
|
||||
companion object {
|
||||
val NONE_YET = Game("No entries yet, add one!")
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package be.kuleuven.howlongtobeat.model
|
||||
|
||||
interface GameRepository {
|
||||
|
||||
fun load(): List<Game>
|
||||
|
||||
fun save(items: List<Game>)
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
package be.kuleuven.howlongtobeat.model
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
// In practice, you should NOT mix seralizable and entity
|
||||
// This is just an example to show you both Room and ObjectOutputStream's implementations.
|
||||
@Serializable
|
||||
@Entity
|
||||
data class Todo(
|
||||
@ColumnInfo(name = "title") val title: String,
|
||||
@ColumnInfo(name = "is_done") var isDone: Boolean,
|
||||
@PrimaryKey(autoGenerate = true) var id: Int = 0) : java.io.Serializable {
|
||||
|
||||
fun check() {
|
||||
isDone = true
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun defaults(): List<Todo> = arrayListOf(
|
||||
Todo("Get graded", false),
|
||||
Todo("Pay attention", true),
|
||||
Todo("Get good at Android dev", false),
|
||||
Todo("Refactor Java projects", false)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
package be.kuleuven.howlongtobeat.model
|
||||
|
||||
interface TodoRepository {
|
||||
|
||||
fun load(): List<Todo>
|
||||
|
||||
fun save(items: List<Todo>)
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
package be.kuleuven.howlongtobeat.model.file
|
||||
|
||||
import android.content.Context
|
||||
import be.kuleuven.howlongtobeat.model.Todo
|
||||
import be.kuleuven.howlongtobeat.model.TodoRepository
|
||||
import java.io.EOFException
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.ObjectInputStream
|
||||
import java.io.ObjectOutputStream
|
||||
|
||||
class TodoFileRepository(val context: Context) : TodoRepository {
|
||||
|
||||
override fun load(): List<Todo> {
|
||||
try {
|
||||
val openFileInput = context.openFileInput("todoitems.txt") ?: return Todo.defaults()
|
||||
ObjectInputStream(openFileInput).use {
|
||||
return it.readObject() as ArrayList<Todo>
|
||||
}
|
||||
} catch(fileNotFound: FileNotFoundException) {
|
||||
// no file yet, revert to defaults.
|
||||
} catch(prematureEndOfFile: EOFException) {
|
||||
// also ignore this: file incomplete/corrupt, revert to defaults.
|
||||
}
|
||||
return Todo.defaults()
|
||||
}
|
||||
|
||||
override fun save(items: List<Todo>) {
|
||||
val openFileOutput = context.openFileOutput("todoitems.txt", Context.MODE_PRIVATE) ?: return
|
||||
ObjectOutputStream(openFileOutput).use {
|
||||
it.writeObject(items)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package be.kuleuven.howlongtobeat.model.room
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import androidx.room.Update
|
||||
import be.kuleuven.howlongtobeat.model.Game
|
||||
|
||||
@Dao
|
||||
interface GameDao {
|
||||
|
||||
@Query("SELECT * FROM Game")
|
||||
fun query(): List<Game>
|
||||
|
||||
@Update
|
||||
fun update(items: List<Game>)
|
||||
|
||||
@Query("DELETE FROM Game")
|
||||
fun deleteAll()
|
||||
|
||||
@Insert
|
||||
fun insert(items: List<Game>)
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package be.kuleuven.howlongtobeat.model.room
|
||||
|
||||
import androidx.room.Database
|
||||
import androidx.room.RoomDatabase
|
||||
import be.kuleuven.howlongtobeat.model.Game
|
||||
|
||||
@Database(entities = arrayOf(Game::class), version = 1)
|
||||
abstract class GameDatabase : RoomDatabase() {
|
||||
abstract fun todoDao() : GameDao
|
||||
}
|
|
@ -2,24 +2,25 @@ package be.kuleuven.howlongtobeat.model.room
|
|||
|
||||
import android.content.Context
|
||||
import androidx.room.Room
|
||||
import be.kuleuven.howlongtobeat.model.Todo
|
||||
import be.kuleuven.howlongtobeat.model.TodoRepository
|
||||
import be.kuleuven.howlongtobeat.model.Game
|
||||
import be.kuleuven.howlongtobeat.model.GameRepository
|
||||
|
||||
class TodoRoomRepository(appContext: Context) : TodoRepository {
|
||||
class GameRepositoryRoomImpl(appContext: Context) :
|
||||
GameRepository {
|
||||
|
||||
private val db: TodoDatabase
|
||||
private val dao: TodoDao
|
||||
private val db: GameDatabase
|
||||
private val dao: GameDao
|
||||
|
||||
init {
|
||||
db = Room.databaseBuilder(appContext, TodoDatabase::class.java, "todo-db")
|
||||
db = Room.databaseBuilder(appContext, GameDatabase::class.java, "todo-db")
|
||||
.allowMainThreadQueries()
|
||||
.build()
|
||||
dao = db.todoDao()
|
||||
}
|
||||
|
||||
override fun load(): List<Todo> = dao.query()
|
||||
override fun load(): List<Game> = dao.query()
|
||||
|
||||
override fun save(items: List<Todo>) {
|
||||
override fun save(items: List<Game>) {
|
||||
// You'll learn more about transactions in the database course in the 3rd academic year.
|
||||
db.runInTransaction {
|
||||
dao.deleteAll()
|
|
@ -1,23 +0,0 @@
|
|||
package be.kuleuven.howlongtobeat.model.room
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import androidx.room.Update
|
||||
import be.kuleuven.howlongtobeat.model.Todo
|
||||
|
||||
@Dao
|
||||
interface TodoDao {
|
||||
|
||||
@Query("SELECT * FROM Todo")
|
||||
fun query(): List<Todo>
|
||||
|
||||
@Update
|
||||
fun update(items: List<Todo>)
|
||||
|
||||
@Query("DELETE FROM Todo")
|
||||
fun deleteAll()
|
||||
|
||||
@Insert
|
||||
fun insert(items: List<Todo>)
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
package be.kuleuven.howlongtobeat.model.room
|
||||
|
||||
import androidx.room.Database
|
||||
import androidx.room.RoomDatabase
|
||||
import be.kuleuven.howlongtobeat.model.Todo
|
||||
|
||||
@Database(entities = arrayOf(Todo::class), version = 1)
|
||||
abstract class TodoDatabase : RoomDatabase() {
|
||||
abstract fun todoDao() : TodoDao
|
||||
}
|
|
@ -7,10 +7,17 @@
|
|||
android:id="@+id/drawerLayout"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/frmTodoContainer" />
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/nav_host_fragment"
|
||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:navGraph="@navigation/nav_graph"
|
||||
app:defaultNavHost="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.navigation.NavigationView
|
||||
android:id="@+id/navView"
|
||||
|
|
|
@ -1,33 +1,29 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.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="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rvwTodo"
|
||||
android:id="@+id/rvGameList"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/edtTodo"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/edtTodo"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="new TODO"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/btnAddTodo"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<Button
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/btnAddTodo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Add"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:src="@android:drawable/ic_menu_add"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:contentDescription="Add New Game"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rvHltbResult"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -19,6 +19,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Please wait, fetching..."
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/indeterminateBar"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtHltbItemResult"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Some Result"
|
||||
android:textSize="24sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/nav_graph"
|
||||
app:startDestination="@id/gameListFragment">
|
||||
|
||||
<fragment
|
||||
android:id="@+id/gameListFragment"
|
||||
android:name="be.kuleuven.howlongtobeat.GameListFragment"
|
||||
android:label="GameListFragment" >
|
||||
<action
|
||||
android:id="@+id/action_gameListFragment_to_loadingFragment"
|
||||
app:destination="@id/loadingFragment" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/loadingFragment"
|
||||
android:name="be.kuleuven.howlongtobeat.LoadingFragment"
|
||||
android:label="LoadingFragment" >
|
||||
<action
|
||||
android:id="@+id/action_loadingFragment_to_hltbResultsFragment"
|
||||
app:destination="@id/hltbResultsFragment" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/hltbResultsFragment"
|
||||
android:name="be.kuleuven.howlongtobeat.HltbResultsFragment"
|
||||
android:label="HltbResultsFragment" />
|
||||
</navigation>
|
Loading…
Reference in New Issue