Skip to content

Commit

Permalink
Add ability to import path from CSV
Browse files Browse the repository at this point in the history
  • Loading branch information
kylecorry31 committed Jul 10, 2024
1 parent 0fa4157 commit 934d174
Show file tree
Hide file tree
Showing 9 changed files with 119 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,16 @@ class CsvIOService(private val uriPicker: UriPicker, private val uriService: Uri
}

override suspend fun import(): List<List<String>>? = onIO {
val uri = uriPicker.open(listOf("text/csv")) ?: return@onIO null
val uri =
uriPicker.open(
listOf(
"text/csv",
"application/csv",
"text/comma-separated-values",
"text/plain"
)
)
?: return@onIO null
val stream = uriService.inputStream(uri) ?: return@onIO null
CSVConvert.parse(stream.readText())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ interface IPackRepo {

suspend fun addPack(pack: Pack): Long

suspend fun addItem(item: PackItem)
suspend fun addItem(item: PackItem): Long

suspend fun deleteAll()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.kylecorry.trail_sense.tools.packs.infrastructure

import android.content.Context
import com.kylecorry.andromeda.fragments.AndromedaFragment
import com.kylecorry.luna.text.toDoubleCompat
import com.kylecorry.sol.units.Weight
Expand Down Expand Up @@ -34,15 +35,16 @@ class LighterPackIOService(uriPicker: UriPicker, uriService: UriService) :

private fun toCsv(pack: List<PackItem>): List<List<String>> {
val headers = listOf(
"item name",
"category",
"desc",
"qty",
"weight",
"unit",
"packed qty",
"desired qty"
HEADER_ITEM_NAME,
HEADER_CATEGORY,
HEADER_DESCRIPTION,
HEADER_QUANTITY,
HEADER_WEIGHT_AMOUNT,
HEADER_WEIGHT_UNIT,
HEADER_PACKED_QUANTITY,
HEADER_DESIRED_QUANTITY
)

val items = pack.map {
listOf(
it.name,
Expand All @@ -63,13 +65,23 @@ class LighterPackIOService(uriPicker: UriPicker, uriService: UriService) :
return null
}

val headerRow = data.first().map { it.lowercase() }
val nameIdx = headerRow.indexOf(HEADER_ITEM_NAME.lowercase())
val categoryIdx = headerRow.indexOf(HEADER_CATEGORY.lowercase())
val weightAmountIdx = headerRow.indexOf(HEADER_WEIGHT_AMOUNT.lowercase())
val weightUnitIdx = headerRow.indexOf(HEADER_WEIGHT_UNIT.lowercase())
val qtyIdx = headerRow.indexOf(HEADER_QUANTITY.lowercase())
val packedQtyIdx = headerRow.indexOf(HEADER_PACKED_QUANTITY.lowercase())
val desiredQtyIdx = headerRow.indexOf(HEADER_DESIRED_QUANTITY.lowercase())

return data.drop(1).map { it ->
val name = it.getOrNull(0) ?: ""
val category = parseCategoryString(it.getOrNull(1) ?: "")
val weight = it.getOrNull(4)?.toFloatOrNull()
val unit = it.getOrNull(5)?.let { parseWeightUnit(it) } ?: WeightUnits.Grams
val packedQty = it.getOrNull(6)?.toDoubleCompat() ?: 0.0
val desiredQty = it.getOrNull(7)?.toDoubleCompat() ?: 0.0
val name = it.getOrNull(nameIdx) ?: ""
val category = parseCategoryString(it.getOrNull(categoryIdx) ?: "")
val weight = it.getOrNull(weightAmountIdx)?.toFloatOrNull()
val unit = it.getOrNull(weightUnitIdx)?.let { parseWeightUnit(it) } ?: WeightUnits.Grams
val packedQty = it.getOrNull(packedQtyIdx)?.toDoubleCompat() ?: 0.0
val desiredQty = it.getOrNull(desiredQtyIdx)?.toDoubleCompat() ?: it.getOrNull(qtyIdx)
?.toDoubleCompat() ?: 0.0
PackItem(
0,
0,
Expand Down Expand Up @@ -108,6 +120,16 @@ class LighterPackIOService(uriPicker: UriPicker, uriService: UriService) :
}

companion object {

private const val HEADER_ITEM_NAME = "item name"
private const val HEADER_CATEGORY = "category"
private const val HEADER_DESCRIPTION = "desc"
private const val HEADER_QUANTITY = "qty"
private const val HEADER_WEIGHT_AMOUNT = "weight"
private const val HEADER_WEIGHT_UNIT = "unit"
private const val HEADER_PACKED_QUANTITY = "packed qty"
private const val HEADER_DESIRED_QUANTITY = "desired qty"

fun create(fragment: AndromedaFragment): LighterPackIOService {
return LighterPackIOService(
FragmentUriPicker(fragment),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ class PackRepo private constructor(context: Context) : IPackRepo {
packDao.delete(mapper.mapToPackEntity(pack))
}

override suspend fun addPack(pack: Pack): Long {
return if (pack.id == 0L) {
override suspend fun addPack(pack: Pack): Long = onIO {
if (pack.id == 0L) {
packDao.insert(mapper.mapToPackEntity(pack))
} else {
packDao.update(mapper.mapToPackEntity(pack))
Expand All @@ -73,9 +73,10 @@ class PackRepo private constructor(context: Context) : IPackRepo {
return newId
}

override suspend fun addItem(item: PackItem) {
override suspend fun addItem(item: PackItem) = onIO {
if (item.id != 0L) {
inventoryItemDao.update(mapper.mapToItemEntity(item))
item.id
} else {
inventoryItemDao.insert(mapper.mapToItemEntity(item))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import com.kylecorry.trail_sense.databinding.FragmentPackListBinding
import com.kylecorry.trail_sense.tools.packs.domain.Pack
import com.kylecorry.trail_sense.tools.packs.infrastructure.PackRepo
import com.kylecorry.trail_sense.tools.packs.ui.commands.ExportPackingListCommand
import com.kylecorry.trail_sense.tools.packs.ui.commands.ImportPackingListCommand
import com.kylecorry.trail_sense.tools.packs.ui.mappers.PackAction
import com.kylecorry.trail_sense.tools.packs.ui.mappers.PackListItemMapper
import kotlinx.coroutines.Dispatchers
Expand Down Expand Up @@ -60,6 +61,11 @@ class PackListFragment : BoundFragment<FragmentPackListBinding>() {
}

binding.addBtn.setOnClickListener { createPack() }

// TODO: Temporary
binding.packListTitle.rightButton.setOnClickListener {
ImportPackingListCommand(this).execute()
}
}

private fun renamePack(pack: Pack) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,14 @@ import com.kylecorry.andromeda.fragments.inBackground
import com.kylecorry.luna.text.slugify
import com.kylecorry.trail_sense.R
import com.kylecorry.trail_sense.shared.commands.generic.Command
import com.kylecorry.trail_sense.shared.io.ExternalUriService
import com.kylecorry.trail_sense.shared.io.FragmentUriPicker
import com.kylecorry.trail_sense.tools.packs.domain.Pack
import com.kylecorry.trail_sense.tools.packs.domain.PackItem
import com.kylecorry.trail_sense.tools.packs.infrastructure.LighterPackIOService
import com.kylecorry.trail_sense.tools.packs.infrastructure.PackRepo

class ExportPackingListCommand(private val fragment: AndromedaFragment) : Command<Pack> {

private val exportService = LighterPackIOService(
FragmentUriPicker(fragment),
ExternalUriService(fragment.requireContext())
)
private val exportService = LighterPackIOService.create(fragment)

private val repo = PackRepo.getInstance(fragment.requireContext())

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.kylecorry.trail_sense.tools.packs.ui.commands

import androidx.core.os.bundleOf
import androidx.navigation.fragment.findNavController
import com.kylecorry.andromeda.alerts.Alerts
import com.kylecorry.andromeda.alerts.toast
import com.kylecorry.andromeda.core.coroutines.BackgroundMinimumState
import com.kylecorry.andromeda.fragments.AndromedaFragment
import com.kylecorry.andromeda.fragments.inBackground
import com.kylecorry.andromeda.pickers.CoroutinePickers
import com.kylecorry.trail_sense.R
import com.kylecorry.trail_sense.shared.commands.Command
import com.kylecorry.trail_sense.shared.navigateWithAnimation
import com.kylecorry.trail_sense.tools.packs.domain.Pack
import com.kylecorry.trail_sense.tools.packs.domain.PackItem
import com.kylecorry.trail_sense.tools.packs.infrastructure.LighterPackIOService
import com.kylecorry.trail_sense.tools.packs.infrastructure.PackRepo

class ImportPackingListCommand(private val fragment: AndromedaFragment) : Command {

private val importService = LighterPackIOService.create(fragment)
private val repo = PackRepo.getInstance(fragment.requireContext())

override fun execute() {
fragment.inBackground(BackgroundMinimumState.Created) {
var items: List<PackItem>? = null
Alerts.withLoading(fragment.requireContext(), fragment.getString(R.string.loading)) {
items = importService.import()
}

// If items are null or empty, show a toast and return
if (items.isNullOrEmpty()) {
fragment.toast(fragment.getString(R.string.no_items_found))
return@inBackground
}

// Ask the user for the pack name
// TODO: Default to the file name
val name = CoroutinePickers.text(
fragment.requireContext(),
fragment.getString(R.string.name)
) ?: return@inBackground

// Create the pack
var packId = 0L
Alerts.withLoading(fragment.requireContext(), fragment.getString(R.string.loading)) {
packId = repo.addPack(Pack(0, name))
for (item in items!!) {
val newItem = item.copy(packId = packId)
repo.addItem(newItem)
}
}

// Open the pack
val bundle = bundleOf("pack_id" to packId)
fragment.findNavController().navigateWithAnimation(R.id.packItemListFragment, bundle)
}
}
}
1 change: 1 addition & 0 deletions app/src/main/res/layout/fragment_pack_list.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:rightButtonIcon="@drawable/ic_import_export"
app:showSubtitle="false"
app:title="@string/packing_lists" />

Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1438,4 +1438,5 @@
<string name="bottom_navigation_slot">Bottom navigation slot %d</string>
<string name="pref_weather_forecast_source" translatable="false">pref_weather_forecast_source</string>
<string name="forecast_algorithm">Forecast algorithm</string>
<string name="no_items_found">No items found</string>
</resources>

0 comments on commit 934d174

Please sign in to comment.