Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 17 additions & 9 deletions app/src/main/java/ch/comparis/jetpackhomefinder/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,31 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.ui.Modifier
import ch.comparis.jetpackhomefinder.ui.RealEstateApp
import ch.comparis.jetpackhomefinder.ui.theme.JetpackHomefinderTheme
import ch.comparis.jetpackhomefinder.viewmodel.RealEstateViewModel

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
JetpackHomefinderTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
RealEstateApp(modifier = Modifier.padding(innerPadding))
}
}

private val viewModel: RealEstateViewModel by viewModels()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
JetpackHomefinderTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
RealEstateApp(
modifier = Modifier.padding(innerPadding),
vm = viewModel,
)
}
}
}
}
}
52 changes: 37 additions & 15 deletions app/src/main/java/ch/comparis/jetpackhomefinder/model/Models.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,51 @@ import kotlinx.serialization.Serializable
@OptIn(kotlinx.serialization.InternalSerializationApi::class)
@Serializable
data class Listing(
val id: Int,
val title: String,
val rooms: Double,
val area: Int,
@SerialName("pricePerMonth") val price: Int,
val zipCode: Int,
val city: String,
val address: String,
@SerialName("image") val imageUrl: String
val id: Int,
val title: String,
val rooms: Double,
val area: Int,
@SerialName("pricePerMonth") val price: Int,
val zipCode: Int,
val city: String,
val address: String,
@SerialName("image") val imageUrl: String
)

data class GroupedListings(
val filters: List<FilterUIModel>,
val listings: List<Listing>,
)

sealed interface FilterUIModel {
data class PriceRange(
val range: IntRange,
val firstRange: Boolean,
val lastRange: Boolean,
) : FilterUIModel

data class ZipCode(val zipCode: Int) : FilterUIModel
data class City(val city: String) : FilterUIModel
}

sealed interface GroupingFilter {
data class PriceRange(val ranges: List<IntRange>) : GroupingFilter
data object ZipCode : GroupingFilter
data object City : GroupingFilter
}

@OptIn(kotlinx.serialization.InternalSerializationApi::class)
@Serializable
data class ListingsMetadata(
val page: Int,
val pageSize: Int,
val totalItems: Int,
val pageCount: Int
val page: Int,
val pageSize: Int,
val totalItems: Int,
val pageCount: Int
)

@OptIn(kotlinx.serialization.InternalSerializationApi::class)
@Serializable
data class ListingsResponse(
@SerialName("data") val listings: List<Listing>,
val metadata: ListingsMetadata
@SerialName("data") val listings: List<Listing>,
val metadata: ListingsMetadata
)
Original file line number Diff line number Diff line change
@@ -1,28 +1,86 @@
package ch.comparis.jetpackhomefinder.repository

import ch.comparis.jetpackhomefinder.model.FilterUIModel
import ch.comparis.jetpackhomefinder.model.GroupedListings
import ch.comparis.jetpackhomefinder.model.GroupingFilter
import ch.comparis.jetpackhomefinder.model.Listing
import ch.comparis.jetpackhomefinder.model.ListingsResponse
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import okhttp3.OkHttpClient
import okhttp3.Request

class RealEstateRepository(
private val client: OkHttpClient = OkHttpClient(),
private val json: Json = Json { ignoreUnknownKeys = true }
private val client: OkHttpClient = OkHttpClient(),
private val json: Json = Json { ignoreUnknownKeys = true }
) {
private val url = "https://cmp-resources-prd.b-cdn.net/real-estate-listings.json"
private val url = "https://cmp-resources-prd.b-cdn.net/real-estate-listings.json"

fun fetchListings(): Result<List<Listing>> {
return try {
val request = Request.Builder().url(url).build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) return Result.failure(Exception("HTTP ${'$'}{response.code}"))
val bodyStr = response.body?.string() ?: return Result.failure(Exception("Empty body"))
val parsed = json.decodeFromString<ListingsResponse>(bodyStr)
Result.success(parsed.listings)
}
} catch (e: Exception) {
Result.failure(e)
suspend fun fetchListings(): Result<List<Listing>> {
return try {
withContext(Dispatchers.IO) {
val request = Request.Builder().url(url).build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) return@withContext Result.failure(Exception("HTTP ${'$'}{response.code}"))
val bodyStr = response.body?.string() ?: return@withContext Result.failure(Exception("Empty body"))
val parsed = json.decodeFromString<ListingsResponse>(bodyStr)
Result.success(parsed.listings)
}
}
} catch (e: Exception) {
Result.failure(e)
}
}

suspend fun fetchGroupedListings(
filters: List<GroupingFilter>
): Result<List<GroupedListings>> {
println(">>> Filters: $filters")
return fetchListings()
.fold(
onFailure = { Result.failure(it) },
onSuccess = { listings ->
// 0 - 999 -> 0
// 1000 - 1999-> 1
// 2000 - 2999-> 2
val grouped = listings
.sortedBy { it.price }
.groupBy { listing ->
filters.map { filter ->
when (filter) {
is GroupingFilter.PriceRange -> {
val range = filter.ranges.first { range ->
listing.price in range
}
val index = filter.ranges.indexOf(range)
FilterUIModel.PriceRange(
range = range,
firstRange = index == 0,
lastRange = index == filter.ranges.size - 1,
)
}

GroupingFilter.City -> {
FilterUIModel.City(listing.city)
}

GroupingFilter.ZipCode -> {
FilterUIModel.ZipCode(listing.zipCode)
}
}
}

}
.entries
.map { (filters, listings) ->
GroupedListings(
filters = filters,
listings = listings,
)
}
Result.success(grouped)
}
)
}
}
Loading