Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit af1f86fd authored by Mitul Sheth's avatar Mitul Sheth
Browse files

Merge branch 'Fix_removing_places_from_the_favorites-ai-review' into 'main'

fix(favorites): removing places from the favorites

See merge request !64
parents d4bec252 dd36f2b7
Loading
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ package earth.maps.cardinal.data
import kotlin.math.abs

data class GeocodeResult(
    val geocodeId: String,
    val latitude: Double,
    val longitude: Double,
    val displayName: String,
+2 −0
Original line number Diff line number Diff line
@@ -464,7 +464,9 @@ class LocationRepository @Inject constructor(

    fun createSearchResultPlace(result: GeocodeResult): Place {
        val openingHours = result.properties["opening_hours"]

        return Place(
            id = result.geocodeId,
            name = result.displayName,
            description = mapOsmTagsToDescription(result.properties),
            icon = "search",
+89 −0
Original line number Diff line number Diff line
/*
 *     Cardinal Maps
 *     Copyright (C) 2026 Cardinal Maps Authors
 *
 *     This program is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package earth.maps.cardinal.data

import java.nio.ByteBuffer
import java.security.MessageDigest

/**
 * A utility for generating deterministic, stable identifiers for geographical places.
 *
 * This generator uses a combination of coordinates and the place's display name to create
 * a SHA-256 hash. Using raw byte buffers for coordinates ensures that the ID remains
 * consistent across different device locales (avoiding decimal separator issues like '.' vs ',').
 *
 * Including the [name] in the generation process prevents ID collisions in scenarios where
 * multiple distinct points of interest (POIs) exist at the exact same coordinates.
 */
object PlaceIdGenerator {
    /**
     * The number of bytes required to store two Double values (Latitude and Longitude).
     */
    private const val COORD_BYTE_SIZE = 16

    /**
     * Generates a stable hex ID based on the provided location and name.
     *
     * @param latitude The latitude of the place.
     * @param longitude The longitude of the place.
     * @param name The display name or title of the place.
     * @return A deterministic SHA-256 hash string in hexadecimal format.
     */
    fun generateId(latitude: Double, longitude: Double, name: String): String {
        val nameBytes = name.toByteArray(Charsets.UTF_8)

        // Allocate space for coordinates (16 bytes) + name bytes
        val buffer = ByteBuffer.allocate(COORD_BYTE_SIZE + nameBytes.size)
        buffer.putDouble(latitude)
        buffer.putDouble(longitude)
        buffer.put(nameBytes)

        return hashBytesToHex(buffer.array())
    }

    /**
     * Returns a stable identifier for the given [place].
     *
     * If the [place] already contains a valid ID (e.g., from an online provider like Pelias),
     * that ID is returned. If the ID is null or blank (common with offline geocoding results),
     * a deterministic ID is generated based on the place's coordinates and name.
     *
     * @param place The place object to get or generate an ID for.
     * @return The existing ID if present, otherwise a generated SHA-256 hex string.
     */
    fun generateId(place: Place): String {
        return if (place.id.isNullOrBlank()) generateId(
            latitude = place.latLng.latitude,
            longitude = place.latLng.longitude,
            name = place.name
        ) else place.id
    }

    /**
     * Hashes a byte array using SHA-256 and converts it to a hex string.
     *
     * Implementation note: Uses [joinToString] for linear-time string construction
     * to avoid excessive memory allocations.
     */
    private fun hashBytesToHex(bytes: ByteArray): String {
        return MessageDigest.getInstance("SHA-256")
            .digest(bytes)
            .joinToString("") { "%02x".format(it) }
    }
}
 No newline at end of file
+2 −2
Original line number Diff line number Diff line
@@ -21,7 +21,7 @@ package earth.maps.cardinal.data.room
import androidx.room.Entity
import androidx.room.PrimaryKey
import earth.maps.cardinal.data.Place
import java.util.UUID
import earth.maps.cardinal.data.PlaceIdGenerator

@Entity(tableName = "recent_searches")
data class RecentSearch(
@@ -46,7 +46,7 @@ data class RecentSearch(
            val timestamp = System.currentTimeMillis()

            return RecentSearch(
                id = UUID.randomUUID().toString(),
                id = PlaceIdGenerator.generateId(place),
                name = place.name,
                description = place.description,
                icon = place.icon,
+2 −3
Original line number Diff line number Diff line
@@ -21,7 +21,7 @@ package earth.maps.cardinal.data.room
import androidx.room.Entity
import androidx.room.PrimaryKey
import earth.maps.cardinal.data.Place
import java.util.UUID
import earth.maps.cardinal.data.PlaceIdGenerator

@Entity(tableName = "saved_places")
data class SavedPlace(
@@ -53,9 +53,8 @@ data class SavedPlace(
    companion object {
        fun fromPlace(place: Place): SavedPlace {
            val timestamp = System.currentTimeMillis()

            return SavedPlace(
                id = UUID.randomUUID().toString(),
                id = PlaceIdGenerator.generateId(place),
                placeId = 0,
                customName = null,
                customDescription = null,
Loading