Skip to content

Commit

Permalink
Merge pull request #51 from private-yusuke/make-recently-checked-in-v…
Browse files Browse the repository at this point in the history
…enues-selectable

Make recently checked-in venues selectable
  • Loading branch information
private-yusuke authored Jan 22, 2023
2 parents 37b9408 + 9bc8245 commit 987206e
Show file tree
Hide file tree
Showing 22 changed files with 363 additions and 14 deletions.
13 changes: 13 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ android {
}

resConfigs "en", "ja"

javaCompileOptions {
annotationProcessorOptions {
arguments += ["room.schemaLocation": "$projectDir/schemas"]
}
}
}

buildTypes {
Expand Down Expand Up @@ -117,4 +123,11 @@ dependencies {
// For encrypting and decrypting credentials
implementation 'com.google.crypto.tink:tink-android:1.7.0'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.4.1"

// Room
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-paging:$room_version"
implementation "androidx.room:room-ktx:$room_version"
implementation 'com.github.anboralabs:spatia-room:0.2.4'
}
64 changes: 64 additions & 0 deletions app/schemas/pub.yusuke.interscheckin.AppDatabase/1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "e1ce587d7ca33db324d2788059075aa5",
"entities": [
{
"tableName": "visited_venues",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `categoriesString` TEXT, `location` BLOB NOT NULL, `icon_name` TEXT, `icon_url` TEXT, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "categoriesString",
"columnName": "categoriesString",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "location",
"columnName": "location",
"affinity": "BLOB",
"notNull": true
},
{
"fieldPath": "iconName",
"columnName": "icon_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "iconUrl",
"columnName": "icon_url",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'e1ce587d7ca33db324d2788059075aa5')"
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import pub.yusuke.interscheckin.repositories.foursquarecheckins.FoursquarePlaces
import pub.yusuke.interscheckin.repositories.settings.FakeSettingsRepository
import pub.yusuke.interscheckin.repositories.settings.SettingsRepository
import pub.yusuke.interscheckin.repositories.userpreferences.FakeUserPreferencesRepository
import pub.yusuke.interscheckin.repositories.visitedvenues.FakeVisitedVenueDao
import pub.yusuke.interscheckin.repositories.visitedvenues.VisitedVenueDao
import javax.inject.Singleton

@Module
Expand Down Expand Up @@ -41,4 +43,10 @@ interface FakeMainApplicationModule {
fun bindFoursquarePlacesRepository(
foursquarePlacesRepository: FakeFoursquarePlacesRepository
): FoursquarePlacesRepository

@Singleton
@Binds
fun bindVisitedVenueDao(
visitedVenueDao: FakeVisitedVenueDao
): VisitedVenueDao
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ class FakeFoursquareCheckinsRepository @Inject constructor() : FoursquareCheckin
name = "good venue",
location = Checkin.V2Venue.Location(
state = "茨城県",
city = "つくば市"
city = "つくば市",
lat = 36.107565,
lng = 140.104733
),
categories = listOf(
Checkin.V2Venue.Category(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package pub.yusuke.interscheckin.repositories.visitedvenues

import co.anbora.labs.spatia.geometry.Point
import javax.inject.Inject

class FakeVisitedVenueDao @Inject constructor() : VisitedVenueDao {
override suspend fun findByLatLong(
latitude: Double,
longitude: Double,
nameLike: String,
limit: Long
): List<VisitedVenue> = listOf(
VisitedVenue(
id = "venue_id",
name = "An visited Venue",
categoriesString = "Intersection",
location = Point(0.0, 0.0),
iconName = "Intersection",
iconUrl = "https://example.com/visited_venue.png"
)
)

override suspend fun insertVisitedVenues(visitedVenues: List<VisitedVenue>) {}

override suspend fun deleteAll() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import androidx.compose.ui.semantics.SemanticsProperties
import androidx.compose.ui.test.assertIsEnabled
import androidx.compose.ui.test.assertIsNotEnabled
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.longClick
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTextInput
import androidx.compose.ui.test.performTouchInput
import androidx.navigation.NavController
import androidx.test.espresso.action.ViewActions.longClick
import com.google.android.gms.location.FusedLocationProviderClient
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
Expand Down
20 changes: 20 additions & 0 deletions app/src/main/java/pub/yusuke/interscheckin/AppDatabase.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package pub.yusuke.interscheckin

import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import co.anbora.labs.spatia.geometry.GeometryConverters
import pub.yusuke.interscheckin.repositories.visitedvenues.VisitedVenue
import pub.yusuke.interscheckin.repositories.visitedvenues.VisitedVenueDao

@Database(
entities = [
VisitedVenue::class
],
version = 1,
exportSchema = true
)
@TypeConverters(GeometryConverters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun visitedVenueDao(): VisitedVenueDao
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import android.content.Context
import android.os.VibratorManager
import androidx.datastore.core.DataStore
import androidx.datastore.core.DataStoreFactory
import androidx.room.RoomDatabase
import androidx.sqlite.db.SupportSQLiteDatabase
import co.anbora.labs.spatia.builder.SpatiaRoom
import com.google.android.gms.location.LocationServices
import com.google.crypto.tink.Aead
import com.google.crypto.tink.KeyTemplates
Expand All @@ -28,6 +31,7 @@ import pub.yusuke.interscheckin.repositories.settings.SettingsPreferencesSeriali
import pub.yusuke.interscheckin.repositories.settings.SettingsRepository
import pub.yusuke.interscheckin.repositories.settings.SettingsRepositoryImpl
import pub.yusuke.interscheckin.repositories.userpreferences.UserPreferencesRepositoryImpl
import pub.yusuke.interscheckin.repositories.visitedvenues.VisitedVenue
import java.io.File
import javax.inject.Singleton

Expand Down Expand Up @@ -123,4 +127,31 @@ class MainApplicationModule {
fun provideFusedLocationProviderClient(
@ApplicationContext context: Context
) = LocationServices.getFusedLocationProviderClient(context)

@Singleton
@Provides
fun provideAppDatabase(
@ApplicationContext context: Context
) = SpatiaRoom.databaseBuilder(
context,
AppDatabase::class.java,
"interscheckin"
).addCallback(object : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
db.query(
"""
select
RecoverGeometryColumn('visited_venues', 'location', ${VisitedVenue.SRID}, 'POINT', 'XY');
"""
).moveToNext()
db.query("select CreateSpatialIndex('visited_venues', 'location');").moveToNext()
}
}).build()

@Singleton
@Provides
fun provideVisitedVenueDao(
appDatabase: AppDatabase
) = appDatabase.visitedVenueDao()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package pub.yusuke.interscheckin.repositories.visitedvenues

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import co.anbora.labs.spatia.geometry.Point

@Entity(tableName = VisitedVenue.TABLE_NAME)
data class VisitedVenue(
@PrimaryKey
val id: String,
val name: String,
val categoriesString: String? = null,
val location: Point,
@ColumnInfo(name = "icon_name")
val iconName: String?,
@ColumnInfo(name = "icon_url")
val iconUrl: String?
) {
companion object {
const val TABLE_NAME = "visited_venues"
const val SRID = 4326
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package pub.yusuke.interscheckin.repositories.visitedvenues

import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.SkipQueryVerification

@Dao
interface VisitedVenueDao {
@Query(
"""
select
*
from
${VisitedVenue.TABLE_NAME}
where
name like '%' || :nameLike || '%'
order by
distance(
setsrid(location, ${VisitedVenue.SRID}),
makepoint(:latitude, :longitude, ${VisitedVenue.SRID})
) asc
limit :limit
"""
)
@SkipQueryVerification
suspend fun findByLatLong(
latitude: Double,
longitude: Double,
nameLike: String = "",
limit: Long = 10
): List<VisitedVenue>

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertVisitedVenues(visitedVenues: List<VisitedVenue>)

@Query("delete from ${VisitedVenue.TABLE_NAME}")
suspend fun deleteAll()
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
package pub.yusuke.interscheckin.ui.histories

import co.anbora.labs.spatia.geometry.Point
import pub.yusuke.foursquareclient.models.Checkin
import pub.yusuke.foursquareclient.models.url
import pub.yusuke.interscheckin.repositories.foursquarecheckins.FoursquareCheckinsRepository
import pub.yusuke.interscheckin.repositories.visitedvenues.VisitedVenue
import pub.yusuke.interscheckin.repositories.visitedvenues.VisitedVenueDao
import javax.inject.Inject

class HistoriesPaging @Inject constructor(
private val foursquareCheckinsRepository: FoursquareCheckinsRepository
private val foursquareCheckinsRepository: FoursquareCheckinsRepository,
private val visitedVenueDao: VisitedVenueDao
) : HistoriesContract.Paging {
override suspend fun load(
userId: Long?,
beforeTimestamp: Long?,
offset: Long?,
perPage: Long
): List<HistoriesContract.Checkin> =
foursquareCheckinsRepository.getCheckins(
): List<HistoriesContract.Checkin> {
val checkins = foursquareCheckinsRepository.getCheckins(
userId = userId,
offset = offset,
beforeTimestamp = beforeTimestamp,
limit = perPage
).translateToCheckins()
)
visitedVenueDao.insertVisitedVenues(checkins.translateToVisitedVenues())

return checkins.translateToCheckins()
}

private fun List<Checkin>.translateToCheckins(): List<HistoriesContract.Checkin> =
this.map { it.translateToCheckin() }
Expand All @@ -37,7 +45,7 @@ class HistoriesPaging @Inject constructor(
HistoriesContract.Checkin.Venue(
id = this.id,
name = this.name,
address = this.location?.let {
address = this.location.let {
listOfNotNull(it.state, it.city).joinToString(" ")
},
categoriesString = this.categories.joinToString(", ") { it.name },
Expand All @@ -49,4 +57,20 @@ class HistoriesPaging @Inject constructor(
name = this.name,
url = this.icon.url()
)

private fun List<Checkin>.translateToVisitedVenues(): List<VisitedVenue> =
map { it.translateToVisitedVenue() }

private fun Checkin.translateToVisitedVenue(): VisitedVenue =
VisitedVenue(
id = this.venue.id,
name = this.venue.name,
categoriesString = this.venue.categories.joinToString(", ") { it.name },
location = Point(
this.venue.location.lat,
this.venue.location.lng
),
iconName = this.venue.categories.firstOrNull()?.name,
iconUrl = this.venue.categories.firstOrNull()?.icon?.url()
)
}
Loading

0 comments on commit 987206e

Please sign in to comment.