Compare commits

...

1 commit

Author SHA1 Message Date
John O'Reilly
ee1fd9915a graphql client 2022-01-25 18:52:23 +00:00
53 changed files with 996 additions and 591 deletions

View file

@ -1,14 +1,15 @@
package com.surrus.peopleinspace
import com.surrus.common.remote.Assignment
import com.surrus.common.remote.IssPosition
import com.surrus.common.model.Assignment
import com.surrus.common.model.IssPosition
import com.surrus.common.repository.PeopleInSpaceRepositoryInterface
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
class PeopleInSpaceRepositoryFake: PeopleInSpaceRepositoryInterface {
val peopleList = listOf(Assignment("Apollo 11", "Neil Armstrong"),
Assignment("Apollo 11", "Buzz Aldrin"))
Assignment("Apollo 11", "Buzz Aldrin")
)
val issPosition = IssPosition(53.2743394, -9.0514163)

View file

@ -16,7 +16,7 @@ import androidx.glance.layout.padding
import androidx.glance.text.FontWeight
import androidx.glance.text.TextStyle
import androidx.glance.unit.ColorProvider
import com.surrus.common.remote.Assignment
import com.surrus.common.model.Assignment
import com.surrus.common.repository.PeopleInSpaceRepositoryInterface
import com.surrus.peopleinspace.glance.util.BaseGlanceAppWidget
import kotlinx.coroutines.flow.first

View file

@ -17,7 +17,7 @@ import androidx.compose.ui.semantics.SemanticsPropertyKey
import androidx.compose.ui.semantics.SemanticsPropertyReceiver
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.viewinterop.AndroidView
import com.surrus.common.remote.IssPosition
import com.surrus.common.model.IssPosition
import com.surrus.peopleinspace.util.collectAsStateWithLifecycle
import org.koin.androidx.compose.getViewModel
import org.osmdroid.util.GeoPoint

View file

@ -22,7 +22,7 @@ import androidx.navigation.compose.currentBackStackEntryAsState
import com.google.accompanist.navigation.animation.AnimatedNavHost
import com.google.accompanist.navigation.animation.composable
import com.google.accompanist.navigation.animation.rememberAnimatedNavController
import com.surrus.common.remote.Assignment
import com.surrus.common.model.Assignment
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {

View file

@ -2,7 +2,7 @@ package com.surrus.peopleinspace.ui
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.surrus.common.remote.Assignment
import com.surrus.common.model.Assignment
import com.surrus.common.repository.PeopleInSpaceRepositoryInterface
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.stateIn

View file

@ -1,7 +1,7 @@
package com.surrus.peopleinspace.ui
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.surrus.common.remote.Assignment
import com.surrus.common.model.Assignment
class PersonProvider : CollectionPreviewParameterProvider<Assignment>(
listOf(

View file

@ -18,15 +18,15 @@ import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.rememberImagePainter
import com.surrus.common.remote.Assignment
import com.surrus.common.model.Assignment
import org.koin.androidx.compose.getViewModel
const val PersonListTag = "PersonList"
@Composable
fun PersonListScreen(paddingValues: PaddingValues = PaddingValues(),
personSelected: (person: Assignment) -> Unit,
peopleInSpaceViewModel: PeopleInSpaceViewModel = getViewModel()
personSelected: (person: Assignment) -> Unit,
peopleInSpaceViewModel: PeopleInSpaceViewModel = getViewModel()
) {
val peopleState = peopleInSpaceViewModel.peopleInSpace.collectAsState()

1
backend/.gitignore vendored
View file

@ -1 +0,0 @@
/build

View file

@ -1,37 +0,0 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("kotlin-platform-jvm")
application
kotlin("plugin.serialization")
id("com.github.johnrengelman.shadow")
}
dependencies {
with(Deps.Kotlinx) {
implementation(serializationCore) // JVM dependency
implementation(coroutinesCore)
}
with(Deps.Ktor) {
implementation(serverCore)
implementation(serverNetty)
implementation(websockets)
implementation(serverContentNegotiation)
implementation(json)
}
with(Deps.Log) {
implementation(logback)
}
implementation(project(":common"))
}
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
}
application {
mainClass.set("ServerKt")
}

View file

@ -1,2 +0,0 @@
runtime: java11
entrypoint: java -Xmx64m -jar server.jar

View file

@ -1,80 +0,0 @@
val personImages = mapOf(
"Chris Cassidy" to "https://www.nasa.gov/sites/default/files/styles/side_image/public/thumbnails/image/9368855148_f79942efb7_o.jpg?itok=-w5yoryN",
"Anatoly Ivanishin" to "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e0/Anatoli_Ivanishin_2011.jpg/440px-Anatoli_Ivanishin_2011.jpg",
"Ivan Vagner" to "http://www.spacefacts.de/more/cosmonauts/photo/vagner_ivan_3.jpg",
"Sergey Ryzhikov" to "https://spaceflight101.com/iss-expedition-50/wp-content/uploads/sites/118/2016/11/jsc2016e105228.jpg",
"Kate Rubins" to "https://spaceflight101.com/iss-expedition-49/wp-content/uploads/sites/110/2016/09/26720141242_be992e9a20_o-768x1152.jpg",
"Sergey Kud-Sverchkov" to "https://www.esa.int/var/esa/storage/images/esa_multimedia/images/2014/08/sergey_kud-sverchkov/14716838-1-eng-GB/Sergey_Kud-Sverchkov_pillars.jpg",
"Mike Hopkins" to "https://pbs.twimg.com/media/Em5EbQOVEAAdZ0h?format=jpg&name=medium",
"Victor Glover" to "https://pbs.twimg.com/media/Em5EbSnUYAEAgyl?format=jpg&name=medium",
"Shannon Walker" to "https://pbs.twimg.com/media/Em5EbQPVoAATIx8?format=jpg&name=medium",
"Soichi Noguchi" to "https://pbs.twimg.com/media/Em5EbSoVcAA3R2F?format=jpg&name=medium",
"Mark Vande Hei" to "https://www.esa.int/var/esa/storage/images/esa_multimedia/images/2016/09/mark_vande_hei/16121862-1-eng-GB/Mark_Vande_Hei_pillars.jpg",
"Oleg Novitskiy" to "https://spaceflight101.com/iss-expedition-50/wp-content/uploads/sites/118/2016/11/jsc2016e165868.jpg",
"Pyotr Dubrov" to "https://www.nasa.gov/sites/default/files/styles/full_width_feature/public/thumbnails/image/jsc2021e010288.jpg",
"Shane Kimbrough" to "https://www.nasa.gov/sites/default/files/styles/full_width_feature/public/thumbnails/image/jsc2021e010824.jpg",
"Megan McArthur" to "https://www.nasa.gov/sites/default/files/styles/full_width_feature/public/thumbnails/image/jsc2021e010823.jpg",
"Akihiko Hoshide" to "https://www.nasa.gov/sites/default/files/styles/full_width_feature/public/thumbnails/image/jsc2021e010825.jpg",
"Thomas Pesquet" to "https://www.nasa.gov/sites/default/files/styles/full_width_feature/public/thumbnails/image/jsc2021e010826.jpg",
"Nie Haisheng" to "http://www.spacefacts.de/more/taikonauts/photo/nie_haisheng_1.jpg",
"Liu Boming" to "http://www.april12.eu/chinaastron/photo/liuboming.jpg",
"Tang Hongbo" to "https://upload.wikimedia.org/wikipedia/commons/3/35/Tang_Hongbo.png",
"Chris Sembroski" to "https://assets.newatlas.com/dims4/default/9fd3579/2147483647/strip/true/crop/828x1037+0+0/resize/767x960!/quality/90/?url=http%3A%2F%2Fnewatlas-brightspot.s3.amazonaws.com%2F20%2F75%2F205f2e594adf9492c0c0275e2ec6%2Fi4-chris-s-129a9147.jpg",
"Hayley Arceneaux" to "https://pbs.twimg.com/media/E_RKiXIXEAECmiP.jpg",
"Sian Procto" to "https://pbs.twimg.com/media/E_VcnA2XsAgp4r-.jpg",
"Jared Isaacman" to "https://pbs.twimg.com/media/E_OTuR-XsAEhsln.jpg",
"Anton Shkaplerov" to "https://alchetron.com/cdn/anton-shkaplerov-799e7545-54ae-4145-83d1-18f906d22b1-resize-750.jpeg",
"Klim Shipenko" to "https://upload.wikimedia.org/wikipedia/pt/f/f9/Shipenko_klim.jpg",
"Yulia Pereslid" to "https://metro.co.uk/wp-content/uploads/2021/10/PRI_203475964.jpg?quality=90&strip=all&zoom=1&resize=540%2C810",
"Zhai Zhigang" to "https://dingyue.ws.126.net/2021/0701/aaa7e1e7j00qvkk13001hc000hs00lzg.jpg",
"Wang Yaping" to "https://alchetron.com/cdn/wang-yaping-2f869150-f66f-4884-b638-62ff678084a-resize-750.jpeg",
"Ye Guangfu" to "https://upload.wikimedia.org/wikipedia/commons/a/a2/Ye_Guangfu_in_2021.jpg",
"Raja Chari" to "https://www.spacex.com/static/images/crew-3/portraits/SPACEX_Crew-3_RajaChari.jpg",
"Tom Marshburn" to "https://www.spacex.com/static/images/crew-3/portraits/SPACEX_Crew-3_TomMarshburn.jpg",
"Kayla Barron" to "https://www.spacex.com/static/images/crew-3/portraits/SPACEX_Crew-3_KaylaBarron.jpg",
"Matthias Maurer" to "https://www.spacex.com/static/images/crew-3/portraits/SPACEX_Crew-3_MathiasMaurer.jpg",
"Alexander Misurkin" to "https://spaceflight101.com/iss/wp-content/uploads/sites/37/2017/08/36468940815_70ef48a8b6_o-768x1152.jpg",
"Yusaku Maezawa" to "https://pbs.twimg.com/media/FD599ihaUAAyjC7?format=jpg",
"Yozo Hirano" to "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS1lYvHXPvl-eG3yX5MWAtvTaxkoiISJ5KXrg&usqp=CAU",
)
val personBios = mapOf(
"Chris Cassidy" to "Christopher John \"Chris\" Cassidy (born January 4, 1970, in Salem, Massachusetts) is a NASA astronaut and United States Navy SEAL. Chris Cassidy achieved the rank of captain in the U.S. Navy. He was the Chief of the Astronaut Office at NASA from July 2015 until June 2017.",
"Anatoly Ivanishin" to "Anatoli Alekseyevich Ivanishin (Russian: Анатолий Алексеевич Иванишин; born 15 January 1969) is a Russian cosmonaut. His first visit to space was to the International Space Station on board the Soyuz TMA-22 spacecraft as an Expedition 29 / Expedition 30 crew member, launching in November 2011 and returning in April 2012. Ivanishin was the Commander of the International Space Station for Expedition 49.",
"Ivan Vagner" to "Ivan Viktorovich Vagner (born 10 July 1985) is a Russian engineer and cosmonaut who was selected in October 2010. He graduated from the Baltic State Technical University in 2008, before working as an engineer for RKK Energia.\n\nHe began his first spaceflight in April 2020 as a Flight Engineer on Soyuz MS-16 and Expedition 62/63.",
"Sergey Ryzhikov" to "Sergey Nikolayevich Ryzhikov (Russian: Сергей Николаевич Рыжиков; born on August 19, 1974), lieutenant colonel of Russian Air Force, is a Russian cosmonaut, selected in 2006. Ryzhikov launched on his first spaceflight on board the Soyuz MS-02 spacecraft. He spent approximately six months on board the International Space Station taking part in Expedition 49/50, returning to Earth on April 10, 2017",
"Kate Rubins" to "Kathleen Hallisey \"Kate\" Rubins (born October 14, 1978) is a NASA astronaut. She became the 60th woman to fly in space when she launched on a Soyuz spacecraft to the International Space Station on July 7, 2016. She returned to Earth on October 30, 2016 aboard a Soyuz. She was a crew member of Expedition 48 and Expedition 49 of the International Space Station.",
"Sergey Kud-Sverchkov" to "Sergey Vladimirovich Kud-Sverchkov was born on August 23, 1983 at the Baikonur Cosmodrome in the Kazakh Soviet Socialist Republic. Sergey Kud-Sverchkov is married and father of one daughter. Since April 2010, he is a Russian Cosmonaut of the Russian Space Agency Roscosmos. He is currently in space.",
"Mike Hopkins" to "Michael Scott Hopkins was born on December 28, 1968 in Lebanon, Missouri but grew up on a farm in Richland, Missouri in a United Methodist family. After graduating from the School of the Osage High School in Lake of the Ozarks, Missouri, in 1987, he entered the University of Illinois at Urbana-Champaign. While there, he played defensive back for the Illinois Fighting Illini football team. He graduated in 1991 with a Bachelor of Science degree in aerospace engineering. He followed his undergraduate studies with a Master of Science degree in aerospace engineering from Stanford University, which he earned in 1992.",
"Victor Glover" to "Victor Jerome Glover (born April 30, 1976) is a NASA astronaut of the class of 2013 and Pilot on the first operational flight of the SpaceX Crew Dragon to the International Space Station. Glover is a commander in the U.S. Navy where he pilots an F/A-18, and a graduate of the U.S. Air Force Test Pilot School.",
"Shannon Walker" to "Shannon Walker (born 4 June 1965 in Houston, Texas) is an American physicist and a NASA astronaut selected in 2004. She launched on her first mission into space on 25 June 2010 onboard Soyuz TMA-19 and spent over 163 days in space.\n\nShe returned to space for her second long duration mission on 15 November 2020, onboard SpaceX Crew-1, the first operational flight of SpaceX's Crew Dragon spacecraft.",
"Soichi Noguchi" to "Soichi Noguchi (野口 聡一, Noguchi Sōichi, born 15 April 1965 in Yokohama, Japan) is a Japanese aeronautical engineer and JAXA astronaut. His first spaceflight was as a Mission Specialist aboard STS-114 on 26 July 2005 for NASA's first \"return to flight\" Space Shuttle mission after the Columbia disaster. He was also in space as part of the Soyuz TMA-17 crew and Expedition 22 to the International Space Station (ISS), returning to Earth on 2 June 2010. He is the fifth Japanese astronaut to fly in space and the fourth to fly on the space shuttle. His third flight is onboard the Dragon 2 capsule for the SpaceX Crew-1 mission which launched successfully on November 15, 2020. This makes him one of only three astronauts to fly on three different launch systems.",
"Mark Vande Hei" to "Mark Thomas Vande Hei (born November 10, 1966) is a retired United States Army officer and NASA astronaut who served as a flight Engineer for Expedition 53 and 54 on the International Space Station.",
"Oleg Novitskiy" to "Oleg Viktorovich Novitskiy (Russian: Олег Викторович Новицкий; born October 12, 1971 in Červień, Belarus) is a former Lieutenant Colonel in the Russian Air Force who logged over 700 hours of flight time and was awarded for bravery. He is currently serving as a Russian cosmonaut with Roskosmos and has participated in multiple expeditions, during which he has spent over 340 days in space.",
"Pyotr Dubrov" to "Pyotr Valerievich Dubrov (Russian: Пётр Валерьевич Дубров; born 30 January 1978) is a Russian engineer and cosmonaut selected by Roscosmos in 2012.",
"Shane Kimbrough" to "Robert Shane Kimbrough (born June 4, 1967) is a retired United States Army officer, and a NASA astronaut. He was part of the first group of candidates selected for NASA astronaut training following the Space Shuttle Columbia disaster. Kimbrough is a veteran of two spaceflights, the first being a Space Shuttle flight, and the second being a six-month mission to the ISS on board a Russian Soyuz craft. He was the commander of the International Space Station for Expedition 50, and returned to Earth in April 2017.",
"Megan McArthur" to "Katherine Megan McArthur (born August 30, 1971) is an American oceanographer, engineer, and a National Aeronautics and Space Administration (NASA) astronaut. She has served as a Capsule Communicator (CAPCOM) for both the space shuttle and space station. Megan McArthur has flown one space shuttle mission, STS-125. She is known as the last person to be hands on with the Hubble Space Telescope via the Canadarm. McArthur has served in a number of positions including working in the Shuttle Avionics Laboratory (SAIL).",
"Akihiko Hoshide" to "Akihiko Hoshide (星出 彰彦, Hoshide Akihiko, born December 28, 1968) is a Japanese engineer and JAXA astronaut. On August 30, 2012, Hoshide became the third Japanese astronaut to walk in space.",
"Thomas Pesquet" to "Thomas Gautier Pesquet (French pronunciation: \u200B[tɔma gotje pɛskɛ]; born 27 February 1978 in Rouen) is a French aerospace engineer, pilot, and European Space Agency astronaut. Pesquet was selected by ESA as a candidate in May 2009, and he successfully completed his basic training in November 2010. From November 2016 to June 2017, Pesquet was part of Expedition 50 and Expedition 51 as a flight engineer.Pesquet returned to space in April 2021 on board the SpaceX Crew Dragon for a second six-month stay on the ISS.",
"Nie Haisheng" to "Nie Haisheng (simplified Chinese: 聂海胜; traditional Chinese: 聶海勝; pinyin: Niè Hǎishèng; born 13 October 1964) is a Chinese military pilot and CNSA astronaut.",
"Liu Boming" to "Liu Boming (simplified Chinese: 刘伯明; traditional Chinese: 劉伯明; pinyin: Liú Bómíng; born September 1966) is a Chinese pilot selected as part of the Shenzhou program. A fighter pilot in the People's Liberation Army Air Force, he was selected to be an CNSA member in 1998.",
"Tang Hongbo" to "Tang Hongbo (Chinese: 汤洪波; born October 1975) is a Chinese pilot selected as part of the Shenzhou program.",
"Chris Sembroski" to "Christopher Sembroski (born August 28, 1979) is an American data engineer, Air Force veteran, and commercial astronaut, currently living in Everett, Washington, United States. He is a Lockheed Martin employee and private astronaut for the Inspiration4 mission.The position was given to Sembroski after a friend had declined the prize, transferring it to Sembroski.Sembroski has long had an interest in space, being an amateur astronomer and rocketeer.",
"Hayley Arceneaux" to "Hayley Arceneaux is a St. Jude Children's Research Hospital employee, bone cancer survivor and private astronaut who is now a physician assistant; she joined billionaire Jared Isaacman on SpaceX's first private spaceflight Inspiration4 launched on September 15, 2021. At age 29, Arceneaux became the youngest American in space.",
"Sian Procto" to "Sian Hayley Proctor is an American geology professor, science communicator, and commercial astronaut. She was selected as the pilot for the Inspiration4 private orbital spaceflight conducted on 15th September 2021, aboard a SpaceX-operated Crew Dragon space capsule.She is a geology professor at South Mountain Community College in Arizona.She is also a major in the Civil Air Patrol where she serves as the aerospace education officer for its Arizona Wing.",
"Jared Isaacman" to "Jared Isaacman (born February 11, 1983) is an American billionaire businessman, pilot and amateur astronaut. He is the founder and CEO of Shift4 Payments, a payment processor. Isaacman served as commander of the SpaceX flight Inspiration4, launched September 15, 2021",
"Anton Shkaplerov" to "Anton Nikolaevich Shkaplerov (Russian: Антон Николаевич Шкаплеров; born 20 February 1972) is a Russian cosmonaut. He is a veteran of four spaceflights and is a former Commander of the International Space Station.",
"Klim Shipenko" to "Klim Alekseevich Shipenko (Russian: Клим Алексеевич Шипенко; born 16 June 1983) is a Russian film director, screenwriter, actor and producer. In 2021, Shipenko is planning to shoot portions of a science fiction film aboard the International Space Station. It is to be the second narrative film shot in space, and first feature film shot in space.",
"Yulia Pereslid" to "Yulia Sergeevna Peresild (Russian: Ю́лия Серге́евна Переси́льд; born 5 September 1984) is a Russian stage and film actress. ",
"Zhai Zhigang" to "Zhai Zhigang (born October 10, 1966) is a major general of the People's Liberation Army Strategic Support Force (PLASSF) in active service as a People's Liberation Army Astronaut Corps (PLAAC) taikonaut. During the Shenzhou 7 mission in 2008, he became the first Chinese citizen to carry out a spacewalk. He was a People's Liberation Army Air Force (PLAAF) fighter pilot.",
"Wang Yaping" to "Colonel Wang Yaping (born 27 January 1980) is a Chinese military pilot and astronaut. Wang was the second female astronaut selected to the People's Liberation Army Astronaut Corps, and the second Chinese woman in space.",
"Ye Guangfu" to "Colonel Ye Guangfu (Chinese: 叶光富; born 1 September 1980[1]) is a Chinese People's Liberation Army Astronaut Corps (PLAAC) astronaut selected as part of the Shenzhou program.",
"Raja Chari" to "Raja Jon Vurputoor \"Grinder\" Chari (born June 24, 1977; Colonel, United States Air Force) is an American test pilot and NASA astronaut. He is a graduate of the U.S. Air Force Academy, Massachusetts Institute of Technology, and U.S. Naval Test Pilot School, and has over 2,000 flying hours.",
"Tom Marshburn" to "Thomas Henry \"Tom\" Marshburn (born August 29, 1960) is an American physician and a NASA astronaut. He is a veteran of two spaceflights to the International Space Station.",
"Kayla Barron" to "Kayla Jane Barron (born September 19, 1987; LCDR, USN) is an American submarine warfare officer, engineer and NASA astronaut.",
"Matthias Maurer" to "Matthias Josef Maurer (born 18 March 1970 in St. Wendel, Saarland) is a German European Space Agency astronaut and materials scientist, who was selected in 2015 to take part in space training.",
"Alexander Misurkin" to "Alexander Alexanderovich Misurkin (Russian: Aлександрександрович Мисуркин) (born September 23, 1977), a major in the Russian Air Force, is a Russian cosmonaut, selected in 2006. He flew aboard Soyuz TMA-08M on 28 March 2013 as his first space mission, and launched on Soyuz MS-06 as his second flight, in 2017. He was Commander of the International Space Station for Expedition 54.",
"Yusaku Maezawa" to "Yusaku Maezawa (前澤 友作, Maezawa Yūsaku, born 22 November 1975) is a Japanese billionaire entrepreneur and art collector. He founded Start Today in 1998 and launched the online fashion retail website Zozotown in 2004, now Japan's largest. Most recently, Maezawa introduced a custom-fit apparel brand ZOZO and at-home measurement system, the ZOZOSUIT, in 2018. As of July 2021, he is estimated by Forbes to have a net worth of $1.9 billion.",
"Yozo Hirano" to "Yozo Hirano (Japanese: 平野 陽三, 1985- ) born in Imabari, Ehime Prefecture, Japan, is a Japanese spaceflight participant. He is scheduled to fly on Soyuz MS-20.\n\n"
+ "He is foreseen to fly with Yusaku Maezawa, who will pay for both seats; his function will be as production assistant of Maezawa and to document the flight."
)

View file

@ -1,54 +0,0 @@
import com.surrus.common.di.initKoin
import com.surrus.common.remote.Assignment
import com.surrus.common.remote.AstroResult
import com.surrus.common.remote.PeopleInSpaceApi
import io.ktor.server.application.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.plugins.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
fun main() {
val koin = initKoin(enableNetworkLogs = true).koin
val peopleInSpaceApi = koin.get<PeopleInSpaceApi>()
peopleInSpaceApi.baseUrl = "http://api.open-notify.org"
val port = System.getenv().getOrDefault("PORT", "8080").toInt()
embeddedServer(Netty, port) {
install(ContentNegotiation) {
json()
}
routing {
get("/astros.json") {
val ar = peopleInSpaceApi.fetchPeople()
val result = AstroResult(ar.message, ar.number, ar.people.map {
val personImageUrl = personImages[it.name]
val personBio = personBios[it.name]
Assignment(it.craft, it.name, personImageUrl, personBio)
})
call.respond(result)
}
get("/iss-now.json") {
val result = peopleInSpaceApi.fetchISSPosition()
call.respond(result)
}
get("/astros_local.json") {
val result = AstroResult(
"success", 3,
listOf(
Assignment("ISS", "Chris Cassidy"),
Assignment("ISS", "Anatoly Ivanishin"),
Assignment("ISS", "Ivan Vagner")
)
)
call.respond(result)
}
}
}.start(wait = true)
}

View file

@ -11,16 +11,16 @@ buildscript {
dependencies {
// keeping this here to allow AS to automatically update
classpath("com.android.tools.build:gradle:7.0.4")
classpath("com.android.tools.build:gradle:7.1.0")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
classpath("org.jetbrains.kotlin:kotlin-serialization:${kotlinVersion}")
with(Deps.Gradle) {
classpath(sqlDelight)
classpath(shadow)
classpath(kotlinter)
classpath(gradleVersionsPlugin)
classpath("com.rickclephas.kmp:kmp-nativecoroutines-gradle-plugin:${Versions.kmpNativeCoroutinesVersion}")
classpath("com.apollographql.apollo3:apollo-gradle-plugin:${Versions.apollo}")
}
}
}

View file

@ -5,10 +5,11 @@ object Versions {
const val kotlinCoroutines = "1.6.0"
const val koin = "3.1.4"
const val ktor = "2.0.0-beta-1"
const val kotlinxSerialization = "1.3.2"
const val kotlinxHtmlJs = "0.7.3"
const val apollo = "3.0.0"
const val kmpNativeCoroutinesVersion = "0.11.1-new-mm"
const val compose = "1.1.0-rc01"
@ -25,7 +26,6 @@ object Versions {
const val mockito = "3.11.2"
const val robolectric = "4.6.1"
const val sqlDelight = "1.5.3"
const val shadow = "7.0.0"
const val kotlinterGradle = "3.4.5"
@ -52,7 +52,6 @@ object Deps {
object Gradle {
const val kotlinter = "org.jmailen.gradle:kotlinter-gradle:${Versions.kotlinterGradle}"
const val shadow = "gradle.plugin.com.github.jengelman.gradle.plugins:shadow:${Versions.shadow}"
const val sqlDelight = "com.squareup.sqldelight:gradle-plugin:${Versions.sqlDelight}"
const val gradleVersionsPlugin = "com.github.ben-manes:gradle-versions-plugin:${Versions.gradleVersionsPlugin}"
}
@ -74,6 +73,14 @@ object Deps {
const val activityCompose = "androidx.activity:activity-compose:${Versions.activityCompose}"
}
object Apollo {
const val apolloRuntime = "com.apollographql.apollo3:apollo-runtime:${Versions.apollo}"
const val apolloNormalizedCacheInMemory = "com.apollographql.apollo3:apollo-normalized-cache:${Versions.apollo}"
const val apolloNormalizedCacheSqlite = "com.apollographql.apollo3:apollo-normalized-cache-sqlite:${Versions.apollo}"
const val apolloMockServer = "com.apollographql.apollo3:apollo-mockserver:${Versions.apollo}"
const val apolloTestingSupport = "com.apollographql.apollo3:apollo-testing-support:${Versions.apollo}"
}
object Test {
const val junit = "junit:junit:${Versions.junit}"
const val androidXTestJUnit = "androidx.test.ext:junit:${Versions.androidXTestJUnit}"
@ -111,34 +118,6 @@ object Deps {
const val compose = "io.insert-koin:koin-androidx-compose:${Versions.koin}"
}
object Ktor {
const val serverCore = "io.ktor:ktor-server-core:${Versions.ktor}"
const val serverNetty = "io.ktor:ktor-server-netty:${Versions.ktor}"
const val contentNegotiation = "io.ktor:ktor-client-content-negotiation:${Versions.ktor}"
const val json = "io.ktor:ktor-serialization-kotlinx-json:${Versions.ktor}"
const val serverContentNegotiation = "io.ktor:ktor-server-content-negotiation:${Versions.ktor}"
const val websockets = "io.ktor:ktor-websockets:${Versions.ktor}"
const val clientCore = "io.ktor:ktor-client-core:${Versions.ktor}"
const val clientJson = "io.ktor:ktor-client-json:${Versions.ktor}"
const val clientLogging = "io.ktor:ktor-client-logging:${Versions.ktor}"
const val clientSerialization = "io.ktor:ktor-client-serialization:${Versions.ktor}"
const val clientAndroid = "io.ktor:ktor-client-android:${Versions.ktor}"
const val clientJava = "io.ktor:ktor-client-java:${Versions.ktor}"
const val clientIos = "io.ktor:ktor-client-ios:${Versions.ktor}"
const val clientJs = "io.ktor:ktor-client-js:${Versions.ktor}"
}
object SqlDelight {
const val runtime = "com.squareup.sqldelight:runtime:${Versions.sqlDelight}"
const val coroutineExtensions = "com.squareup.sqldelight:coroutines-extensions:${Versions.sqlDelight}"
const val androidDriver = "com.squareup.sqldelight:android-driver:${Versions.sqlDelight}"
const val nativeDriver = "com.squareup.sqldelight:native-driver:${Versions.sqlDelight}"
const val nativeDriverMacos = "com.squareup.sqldelight:native-driver-macosx64:${Versions.sqlDelight}"
const val sqliteDriver = "com.squareup.sqldelight:sqlite-driver:${Versions.sqlDelight}"
}
object React {
const val react = "org.jetbrains:kotlin-react:${Versions.kotlinReact}"
const val dom = "org.jetbrains:kotlin-react-dom:${Versions.kotlinReactDom}"

View file

@ -6,7 +6,7 @@ plugins {
id("kotlinx-serialization")
id("com.android.library")
id("org.jetbrains.kotlin.native.cocoapods")
id("com.squareup.sqldelight")
id("com.apollographql.apollo3")
id("com.rickclephas.kmp.nativecoroutines")
id("com.chromaticnoise.multiplatform-swiftpackage") version "2.0.3"
}
@ -62,22 +62,14 @@ kotlin {
sourceSets {
sourceSets["commonMain"].dependencies {
with(Deps.Ktor) {
implementation(clientCore)
implementation(clientJson)
implementation(clientLogging)
implementation(contentNegotiation)
implementation(json)
}
with(Deps.Kotlinx) {
implementation(coroutinesCore)
implementation(serializationCore)
}
with(Deps.SqlDelight) {
implementation(runtime)
implementation(coroutineExtensions)
with(Deps.Apollo) {
implementation(apolloRuntime)
implementation(apolloNormalizedCacheInMemory)
}
with(Deps.Koin) {
@ -97,38 +89,31 @@ kotlin {
}
sourceSets["androidMain"].dependencies {
implementation(Deps.Ktor.clientAndroid)
implementation(Deps.SqlDelight.androidDriver)
implementation(Deps.Apollo.apolloNormalizedCacheSqlite)
}
sourceSets["androidTest"].dependencies {
implementation(Deps.Test.junit)
}
sourceSets["jvmMain"].dependencies {
implementation(Deps.Ktor.clientJava)
implementation(Deps.SqlDelight.sqliteDriver)
implementation(Deps.Log.slf4j)
implementation(Deps.Apollo.apolloNormalizedCacheSqlite)
}
sourceSets["iOSMain"].dependencies {
implementation(Deps.Ktor.clientIos)
implementation(Deps.SqlDelight.nativeDriver)
implementation(Deps.Apollo.apolloNormalizedCacheSqlite)
}
sourceSets["iOSTest"].dependencies {
}
sourceSets["watchMain"].dependencies {
implementation(Deps.Ktor.clientIos)
implementation(Deps.SqlDelight.nativeDriver)
}
sourceSets["macOSMain"].dependencies {
implementation(Deps.Ktor.clientIos)
implementation(Deps.SqlDelight.nativeDriverMacos)
implementation(Deps.Apollo.apolloNormalizedCacheSqlite)
}
sourceSets["jsMain"].dependencies {
implementation(Deps.Ktor.clientJs)
}
}
}
@ -139,17 +124,19 @@ tasks.withType<KotlinCompile> {
}
}
sqldelight {
database("PeopleInSpaceDatabase") {
packageName = "com.surrus.peopleinspace.db"
sourceFolders = listOf("sqldelight")
}
apollo {
packageName.set("com.surrus.common")
codegenModels.set("operationBased")
generateSchema.set(true)
generateTestBuilders.set(true)
}
multiplatformSwiftPackage {
packageName("PeopleInSpace")
swiftToolsVersion("5.3")
targetPlatforms {
iOS { v("13") }
}
}
}

View file

@ -1,19 +1,11 @@
package com.surrus.common.repository
import com.squareup.sqldelight.android.AndroidSqliteDriver
import com.surrus.common.di.PeopleInSpaceDatabaseWrapper
import com.surrus.peopleinspace.db.PeopleInSpaceDatabase
import io.ktor.client.engine.android.*
import com.apollographql.apollo3.cache.normalized.api.NormalizedCacheFactory
import com.apollographql.apollo3.cache.normalized.sql.SqlNormalizedCacheFactory
import org.koin.dsl.module
actual fun platformModule() = module {
single {
val driver =
AndroidSqliteDriver(PeopleInSpaceDatabase.Schema, get(), "peopleinspace.db")
PeopleInSpaceDatabaseWrapper(PeopleInSpaceDatabase(driver))
}
single { Android.create() }
single<NormalizedCacheFactory> { SqlNormalizedCacheFactory(get(), "swapi.db") }
}

View file

@ -0,0 +1,15 @@
query GetPeople {
people {
name
craft
personBio
personImageUrl
}
}
subscription IssPosition {
issPosition {
latitude
longitude
}
}

View file

@ -0,0 +1,850 @@
{
"__schema": {
"queryType": {
"name": "Query"
},
"subscriptionType": {
"name": "Subscription"
},
"types": [
{
"kind": "SCALAR",
"name": "Boolean",
"description": "Built-in Boolean"
},
{
"kind": "SCALAR",
"name": "Float",
"description": "Built-in Float"
},
{
"kind": "OBJECT",
"name": "Person",
"fields": [
{
"name": "craft",
"isDeprecated": false,
"type": {
"kind": "NON_NULL",
"ofType": {
"kind": "SCALAR",
"name": "String"
}
},
"args": []
},
{
"name": "name",
"isDeprecated": false,
"type": {
"kind": "NON_NULL",
"ofType": {
"kind": "SCALAR",
"name": "String"
}
},
"args": []
},
{
"name": "personBio",
"isDeprecated": false,
"type": {
"kind": "SCALAR",
"name": "String"
},
"args": []
},
{
"name": "personImageUrl",
"isDeprecated": false,
"type": {
"kind": "SCALAR",
"name": "String"
},
"args": []
}
]
},
{
"kind": "OBJECT",
"name": "Position",
"fields": [
{
"name": "latitude",
"isDeprecated": false,
"type": {
"kind": "NON_NULL",
"ofType": {
"kind": "SCALAR",
"name": "Float"
}
},
"args": []
},
{
"name": "longitude",
"isDeprecated": false,
"type": {
"kind": "NON_NULL",
"ofType": {
"kind": "SCALAR",
"name": "Float"
}
},
"args": []
}
]
},
{
"kind": "OBJECT",
"name": "Query",
"fields": [
{
"name": "people",
"isDeprecated": false,
"type": {
"kind": "NON_NULL",
"ofType": {
"kind": "LIST",
"ofType": {
"kind": "NON_NULL",
"ofType": {
"kind": "OBJECT",
"name": "Person"
}
}
}
},
"args": []
}
]
},
{
"kind": "SCALAR",
"name": "String",
"description": "Built-in String"
},
{
"kind": "OBJECT",
"name": "Subscription",
"fields": [
{
"name": "issPosition",
"isDeprecated": false,
"type": {
"kind": "NON_NULL",
"ofType": {
"kind": "OBJECT",
"name": "Position"
}
},
"args": []
}
]
},
{
"kind": "OBJECT",
"name": "__Directive",
"fields": [
{
"name": "name",
"description": "The __Directive type represents a Directive that a server supports.",
"isDeprecated": false,
"type": {
"kind": "NON_NULL",
"ofType": {
"kind": "SCALAR",
"name": "String"
}
},
"args": []
},
{
"name": "description",
"isDeprecated": false,
"type": {
"kind": "SCALAR",
"name": "String"
},
"args": []
},
{
"name": "isRepeatable",
"isDeprecated": false,
"type": {
"kind": "NON_NULL",
"ofType": {
"kind": "SCALAR",
"name": "Boolean"
}
},
"args": []
},
{
"name": "locations",
"isDeprecated": false,
"type": {
"kind": "NON_NULL",
"ofType": {
"kind": "LIST",
"ofType": {
"kind": "NON_NULL",
"ofType": {
"kind": "ENUM",
"name": "__DirectiveLocation"
}
}
}
},
"args": []
},
{
"name": "args",
"isDeprecated": false,
"type": {
"kind": "NON_NULL",
"ofType": {
"kind": "LIST",
"ofType": {
"kind": "NON_NULL",
"ofType": {
"kind": "OBJECT",
"name": "__InputValue"
}
}
}
},
"args": []
},
{
"name": "onOperation",
"isDeprecated": true,
"deprecationReason": "Use `locations`.",
"type": {
"kind": "SCALAR",
"name": "Boolean"
},
"args": []
},
{
"name": "onFragment",
"isDeprecated": true,
"deprecationReason": "Use `locations`.",
"type": {
"kind": "SCALAR",
"name": "Boolean"
},
"args": []
},
{
"name": "onField",
"isDeprecated": true,
"deprecationReason": "Use `locations`.",
"type": {
"kind": "SCALAR",
"name": "Boolean"
},
"args": []
}
]
},
{
"kind": "ENUM",
"name": "__DirectiveLocation",
"description": "An enum describing valid locations where a directive can be placed",
"enumValues": [
{
"name": "QUERY",
"description": "Indicates the directive is valid on queries.",
"isDeprecated": false
},
{
"name": "MUTATION",
"description": "Indicates the directive is valid on mutations.",
"isDeprecated": false
},
{
"name": "SUBSCRIPTION",
"description": "Indicates the directive is valid on subscriptions.",
"isDeprecated": false
},
{
"name": "FIELD",
"description": "Indicates the directive is valid on fields.",
"isDeprecated": false
},
{
"name": "FRAGMENT_DEFINITION",
"description": "Indicates the directive is valid on fragment definitions.",
"isDeprecated": false
},
{
"name": "FRAGMENT_SPREAD",
"description": "Indicates the directive is valid on fragment spreads.",
"isDeprecated": false
},
{
"name": "INLINE_FRAGMENT",
"description": "Indicates the directive is valid on inline fragments.",
"isDeprecated": false
},
{
"name": "VARIABLE_DEFINITION",
"description": "Indicates the directive is valid on variable definitions.",
"isDeprecated": false
},
{
"name": "SCHEMA",
"description": "Indicates the directive is valid on a schema SDL definition.",
"isDeprecated": false
},
{
"name": "SCALAR",
"description": "Indicates the directive is valid on a scalar SDL definition.",
"isDeprecated": false
},
{
"name": "OBJECT",
"description": "Indicates the directive is valid on an object SDL definition.",
"isDeprecated": false
},
{
"name": "FIELD_DEFINITION",
"description": "Indicates the directive is valid on a field SDL definition.",
"isDeprecated": false
},
{
"name": "ARGUMENT_DEFINITION",
"description": "Indicates the directive is valid on a field argument SDL definition.",
"isDeprecated": false
},
{
"name": "INTERFACE",
"description": "Indicates the directive is valid on an interface SDL definition.",
"isDeprecated": false
},
{
"name": "UNION",
"description": "Indicates the directive is valid on an union SDL definition.",
"isDeprecated": false
},
{
"name": "ENUM",
"description": "Indicates the directive is valid on an enum SDL definition.",
"isDeprecated": false
},
{
"name": "ENUM_VALUE",
"description": "Indicates the directive is valid on an enum value SDL definition.",
"isDeprecated": false
},
{
"name": "INPUT_OBJECT",
"description": "Indicates the directive is valid on an input object SDL definition.",
"isDeprecated": false
},
{
"name": "INPUT_FIELD_DEFINITION",
"description": "Indicates the directive is valid on an input object field SDL definition.",
"isDeprecated": false
}
]
},
{
"kind": "OBJECT",
"name": "__EnumValue",
"fields": [
{
"name": "name",
"isDeprecated": false,
"type": {
"kind": "NON_NULL",
"ofType": {
"kind": "SCALAR",
"name": "String"
}
},
"args": []
},
{
"name": "description",
"isDeprecated": false,
"type": {
"kind": "SCALAR",
"name": "String"
},
"args": []
},
{
"name": "isDeprecated",
"isDeprecated": false,
"type": {
"kind": "NON_NULL",
"ofType": {
"kind": "SCALAR",
"name": "Boolean"
}
},
"args": []
},
{
"name": "deprecationReason",
"isDeprecated": false,
"type": {
"kind": "SCALAR",
"name": "String"
},
"args": []
}
]
},
{
"kind": "OBJECT",
"name": "__Field",
"fields": [
{
"name": "name",
"isDeprecated": false,
"type": {
"kind": "NON_NULL",
"ofType": {
"kind": "SCALAR",
"name": "String"
}
},
"args": []
},
{
"name": "description",
"isDeprecated": false,
"type": {
"kind": "SCALAR",
"name": "String"
},
"args": []
},
{
"name": "args",
"isDeprecated": false,
"type": {
"kind": "NON_NULL",
"ofType": {
"kind": "LIST",
"ofType": {
"kind": "NON_NULL",
"ofType": {
"kind": "OBJECT",
"name": "__InputValue"
}
}
}
},
"args": [
{
"name": "includeDeprecated",
"isDeprecated": false,
"type": {
"kind": "SCALAR",
"name": "Boolean"
},
"defaultValue": "false"
}
]
},
{
"name": "type",
"isDeprecated": false,
"type": {
"kind": "NON_NULL",
"ofType": {
"kind": "OBJECT",
"name": "__Type"
}
},
"args": []
},
{
"name": "isDeprecated",
"isDeprecated": false,
"type": {
"kind": "NON_NULL",
"ofType": {
"kind": "SCALAR",
"name": "Boolean"
}
},
"args": []
},
{
"name": "deprecationReason",
"isDeprecated": false,
"type": {
"kind": "SCALAR",
"name": "String"
},
"args": []
}
]
},
{
"kind": "OBJECT",
"name": "__InputValue",
"fields": [
{
"name": "name",
"isDeprecated": false,
"type": {
"kind": "NON_NULL",
"ofType": {
"kind": "SCALAR",
"name": "String"
}
},
"args": []
},
{
"name": "description",
"isDeprecated": false,
"type": {
"kind": "SCALAR",
"name": "String"
},
"args": []
},
{
"name": "type",
"isDeprecated": false,
"type": {
"kind": "NON_NULL",
"ofType": {
"kind": "OBJECT",
"name": "__Type"
}
},
"args": []
},
{
"name": "defaultValue",
"isDeprecated": false,
"type": {
"kind": "SCALAR",
"name": "String"
},
"args": []
},
{
"name": "isDeprecated",
"isDeprecated": false,
"type": {
"kind": "SCALAR",
"name": "Boolean"
},
"args": []
},
{
"name": "deprecationReason",
"isDeprecated": false,
"type": {
"kind": "SCALAR",
"name": "String"
},
"args": []
}
]
},
{
"kind": "OBJECT",
"name": "__Schema",
"description": "A GraphQL Introspection defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, the entry points for query, mutation, and subscription operations.",
"fields": [
{
"name": "description",
"isDeprecated": false,
"type": {
"kind": "SCALAR",
"name": "String"
},
"args": []
},
{
"name": "types",
"description": "A list of all types supported by this server.",
"isDeprecated": false,
"type": {
"kind": "NON_NULL",
"ofType": {
"kind": "LIST",
"ofType": {
"kind": "NON_NULL",
"ofType": {
"kind": "OBJECT",
"name": "__Type"
}
}
}
},
"args": []
},
{
"name": "queryType",
"description": "The type that query operations will be rooted at.",
"isDeprecated": false,
"type": {
"kind": "NON_NULL",
"ofType": {
"kind": "OBJECT",
"name": "__Type"
}
},
"args": []
},
{
"name": "mutationType",
"description": "If this server supports mutation, the type that mutation operations will be rooted at.",
"isDeprecated": false,
"type": {
"kind": "OBJECT",
"name": "__Type"
},
"args": []
},
{
"name": "directives",
"description": "'A list of all directives supported by this server.",
"isDeprecated": false,
"type": {
"kind": "NON_NULL",
"ofType": {
"kind": "LIST",
"ofType": {
"kind": "NON_NULL",
"ofType": {
"kind": "OBJECT",
"name": "__Directive"
}
}
}
},
"args": []
},
{
"name": "subscriptionType",
"description": "'If this server support subscription, the type that subscription operations will be rooted at.",
"isDeprecated": false,
"type": {
"kind": "OBJECT",
"name": "__Type"
},
"args": []
}
]
},
{
"kind": "OBJECT",
"name": "__Type",
"fields": [
{
"name": "kind",
"isDeprecated": false,
"type": {
"kind": "NON_NULL",
"ofType": {
"kind": "ENUM",
"name": "__TypeKind"
}
},
"args": []
},
{
"name": "name",
"isDeprecated": false,
"type": {
"kind": "SCALAR",
"name": "String"
},
"args": []
},
{
"name": "description",
"isDeprecated": false,
"type": {
"kind": "SCALAR",
"name": "String"
},
"args": []
},
{
"name": "fields",
"isDeprecated": false,
"type": {
"kind": "LIST",
"ofType": {
"kind": "NON_NULL",
"ofType": {
"kind": "OBJECT",
"name": "__Field"
}
}
},
"args": [
{
"name": "includeDeprecated",
"isDeprecated": false,
"type": {
"kind": "SCALAR",
"name": "Boolean"
},
"defaultValue": "false"
}
]
},
{
"name": "interfaces",
"isDeprecated": false,
"type": {
"kind": "LIST",
"ofType": {
"kind": "NON_NULL",
"ofType": {
"kind": "OBJECT",
"name": "__Type"
}
}
},
"args": []
},
{
"name": "possibleTypes",
"isDeprecated": false,
"type": {
"kind": "LIST",
"ofType": {
"kind": "NON_NULL",
"ofType": {
"kind": "OBJECT",
"name": "__Type"
}
}
},
"args": []
},
{
"name": "enumValues",
"isDeprecated": false,
"type": {
"kind": "LIST",
"ofType": {
"kind": "NON_NULL",
"ofType": {
"kind": "OBJECT",
"name": "__EnumValue"
}
}
},
"args": [
{
"name": "includeDeprecated",
"isDeprecated": false,
"type": {
"kind": "SCALAR",
"name": "Boolean"
},
"defaultValue": "false"
}
]
},
{
"name": "inputFields",
"isDeprecated": false,
"type": {
"kind": "LIST",
"ofType": {
"kind": "NON_NULL",
"ofType": {
"kind": "OBJECT",
"name": "__InputValue"
}
}
},
"args": [
{
"name": "includeDeprecated",
"isDeprecated": false,
"type": {
"kind": "SCALAR",
"name": "Boolean"
},
"defaultValue": "false"
}
]
},
{
"name": "ofType",
"isDeprecated": false,
"type": {
"kind": "OBJECT",
"name": "__Type"
},
"args": []
},
{
"name": "specifiedByUrl",
"isDeprecated": false,
"type": {
"kind": "SCALAR",
"name": "String"
},
"args": []
}
]
},
{
"kind": "ENUM",
"name": "__TypeKind",
"description": "An enum describing what kind of type a given __Type is",
"enumValues": [
{
"name": "SCALAR",
"description": "Indicates this type is a scalar. 'specifiedByUrl' is a valid field",
"isDeprecated": false
},
{
"name": "OBJECT",
"description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.",
"isDeprecated": false
},
{
"name": "INTERFACE",
"description": "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.",
"isDeprecated": false
},
{
"name": "UNION",
"description": "Indicates this type is a union. `possibleTypes` is a valid field.",
"isDeprecated": false
},
{
"name": "ENUM",
"description": "Indicates this type is an enum. `enumValues` is a valid field.",
"isDeprecated": false
},
{
"name": "INPUT_OBJECT",
"description": "Indicates this type is an input object. `inputFields` is a valid field.",
"isDeprecated": false
},
{
"name": "LIST",
"description": "Indicates this type is a list. `ofType` is a valid field.",
"isDeprecated": false
},
{
"name": "NON_NULL",
"description": "Indicates this type is a non-null. `ofType` is a valid field.",
"isDeprecated": false
}
]
}
]
}
}

View file

@ -1,18 +1,15 @@
package com.surrus.common.di
import com.surrus.common.remote.PeopleInSpaceApi
import com.apollographql.apollo3.ApolloClient
import com.apollographql.apollo3.cache.normalized.api.MemoryCacheFactory
import com.apollographql.apollo3.cache.normalized.api.NormalizedCacheFactory
import com.apollographql.apollo3.cache.normalized.normalizedCache
import com.surrus.common.repository.PeopleInSpaceRepository
import com.surrus.common.repository.PeopleInSpaceRepositoryInterface
import com.surrus.common.repository.platformModule
import io.ktor.client.*
import io.ktor.client.engine.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.logging.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.serialization.json.Json
import org.koin.core.context.startKoin
import org.koin.dsl.KoinAppDeclaration
import org.koin.dsl.module
@ -27,27 +24,23 @@ fun initKoin(enableNetworkLogs: Boolean = false, appDeclaration: KoinAppDeclarat
fun initKoin() = initKoin(enableNetworkLogs = false) {}
fun commonModule(enableNetworkLogs: Boolean) = module {
single { createJson() }
single { createHttpClient(get(), get(), enableNetworkLogs = enableNetworkLogs) }
single { CoroutineScope(Dispatchers.Default + SupervisorJob() ) }
single<PeopleInSpaceRepositoryInterface> { PeopleInSpaceRepository() }
single { PeopleInSpaceApi(get()) }
single { createApolloClient(get()) }
}
fun createJson() = Json { isLenient = true; ignoreUnknownKeys = true }
fun createHttpClient(httpClientEngine: HttpClientEngine, json: Json, enableNetworkLogs: Boolean) = HttpClient(httpClientEngine) {
install(ContentNegotiation) {
json(json)
}
if (enableNetworkLogs) {
install(Logging) {
logger = Logger.DEFAULT
level = LogLevel.INFO
}
}
fun createApolloClient(sqlNormalizedCacheFactory: NormalizedCacheFactory): ApolloClient {
val memoryFirstThenSqlCacheFactory = MemoryCacheFactory(10 * 1024 * 1024)
.chain(sqlNormalizedCacheFactory)
return ApolloClient.Builder()
.serverUrl("https://peopleinspace-graphql-guhrsfr7ka-uc.a.run.app/graphql")
.webSocketServerUrl("wss://peopleinspace-graphql-guhrsfr7ka-uc.a.run.app/subscriptions")
.normalizedCache(memoryFirstThenSqlCacheFactory, writeToCacheAsynchronously = true)
.build()
}

View file

@ -1,5 +0,0 @@
package com.surrus.common.di
import com.surrus.peopleinspace.db.PeopleInSpaceDatabase
class PeopleInSpaceDatabaseWrapper(val instance: PeopleInSpaceDatabase?)

View file

@ -0,0 +1,9 @@
package com.surrus.common.model
import kotlinx.serialization.Serializable
@Serializable
data class Assignment(val craft: String, val name: String, var personImageUrl: String? = "", var personBio: String? = "")
@Serializable
data class IssPosition(val latitude: Double, val longitude: Double)

View file

@ -1,27 +0,0 @@
package com.surrus.common.remote
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.request.*
import kotlinx.serialization.Serializable
import org.koin.core.component.KoinComponent
@Serializable
data class AstroResult(val message: String, val number: Int, val people: List<Assignment>)
@Serializable
data class Assignment(val craft: String, val name: String, var personImageUrl: String? = "", var personBio: String? = "")
@Serializable
data class IssPosition(val latitude: Double, val longitude: Double)
@Serializable
data class IssResponse(val message: String, val iss_position: IssPosition, val timestamp: Long)
class PeopleInSpaceApi(
private val client: HttpClient,
var baseUrl: String = "https://people-in-space-proxy.ew.r.appspot.com",
) : KoinComponent {
suspend fun fetchPeople() = client.get("$baseUrl/astros.json").body<AstroResult>()
suspend fun fetchISSPosition() = client.get("$baseUrl/iss-now.json").body<IssResponse>()
}

View file

@ -1,18 +1,16 @@
package com.surrus.common.repository
import co.touchlab.kermit.Logger
import com.rickclephas.kmp.nativecoroutines.NativeCoroutineScope
import com.squareup.sqldelight.runtime.coroutines.asFlow
import com.squareup.sqldelight.runtime.coroutines.mapToList
import com.surrus.common.di.PeopleInSpaceDatabaseWrapper
import com.surrus.common.remote.Assignment
import com.surrus.common.remote.IssPosition
import com.surrus.common.remote.PeopleInSpaceApi
import kotlinx.coroutines.*
import com.apollographql.apollo3.ApolloClient
import com.apollographql.apollo3.cache.normalized.watch
import com.surrus.common.GetPeopleQuery
import com.surrus.common.IssPositionSubscription
import com.surrus.common.model.Assignment
import com.surrus.common.model.IssPosition
import kotlinx.coroutines.flow.*
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
interface PeopleInSpaceRepositoryInterface {
fun fetchPeopleAsFlow(): Flow<List<Assignment>>
fun pollISSPosition(): Flow<IssPosition>
@ -20,72 +18,30 @@ interface PeopleInSpaceRepositoryInterface {
suspend fun fetchAndStorePeople()
}
fun GetPeopleQuery.Person.mapToAssignment() = Assignment(craft, name, personImageUrl, personBio)
class PeopleInSpaceRepository : KoinComponent, PeopleInSpaceRepositoryInterface {
private val peopleInSpaceApi: PeopleInSpaceApi by inject()
private val apolloClient: ApolloClient by inject()
@NativeCoroutineScope
private val coroutineScope: CoroutineScope = MainScope()
private val peopleInSpaceDatabase: PeopleInSpaceDatabaseWrapper by inject()
private val peopleInSpaceQueries = peopleInSpaceDatabase.instance?.peopleInSpaceQueries
val logger = Logger.withTag("PeopleInSpaceRepository")
init {
coroutineScope.launch {
fetchAndStorePeople()
override fun fetchPeopleAsFlow() =
apolloClient.query(GetPeopleQuery()).watch().map {
it.dataAssertNoErrors.people.map { it.mapToAssignment() }
}
}
override fun fetchPeopleAsFlow(): Flow<List<Assignment>> {
// the main reason we need to do this check is that sqldelight isn't currently
// setup for javascript client
return peopleInSpaceQueries?.selectAll(
mapper = { name, craft, personImageUrl, personBio ->
Assignment(name = name, craft = craft, personImageUrl = personImageUrl, personBio = personBio)
}
)?.asFlow()?.mapToList() ?: flowOf(emptyList())
override suspend fun fetchPeople(): List<Assignment> {
val response = apolloClient.query(GetPeopleQuery()).execute()
return response.dataAssertNoErrors.people.map {
it.mapToAssignment()
}
}
override suspend fun fetchAndStorePeople() {
logger.d { "fetchAndStorePeople" }
try {
val result = peopleInSpaceApi.fetchPeople()
fetchPeople()
}
// this is very basic implementation for now that removes all existing rows
// in db and then inserts results from api request
// using "transaction" accelerate the batch of queries, especially inserting
peopleInSpaceQueries?.transaction {
peopleInSpaceQueries.deleteAll()
result.people.forEach {
peopleInSpaceQueries.insertItem(
it.name,
it.craft,
it.personImageUrl,
it.personBio
)
}
}
} catch (e: Exception) {
// TODO report error up to UI
logger.w(e) { "Exception during fetchAndStorePeople: $e" }
override fun pollISSPosition() =
apolloClient.subscription(IssPositionSubscription()).toFlow().map {
val result = it.dataAssertNoErrors.issPosition
IssPosition(result.latitude, result.longitude)
}
}
// Used by web and apple clients atm
override suspend fun fetchPeople(): List<Assignment> = peopleInSpaceApi.fetchPeople().people
override fun pollISSPosition(): Flow<IssPosition> {
return flow {
while (true) {
val position = peopleInSpaceApi.fetchISSPosition().iss_position
emit(position)
logger.d { position.toString() }
delay(POLL_INTERVAL)
}
}
}
companion object {
private const val POLL_INTERVAL = 10000L
}
}

View file

@ -1,2 +0,0 @@
ALTER TABLE People ADD COLUMN personImageUrl TEXT;
ALTER TABLE People ADD COLUMN personBio TEXT;

View file

@ -1,16 +0,0 @@
CREATE TABLE People(
name TEXT NOT NULL PRIMARY KEY,
craft TEXT NOT NULL,
personImageUrl TEXT,
personBio TEXT
);
insertItem:
INSERT OR REPLACE INTO People(name, craft, personImageUrl, personBio)VALUES(?,?,?,?);
selectAll:
SELECT * FROM People;
deleteAll:
DELETE FROM People;

View file

@ -1,6 +1,6 @@
package com.surrus.peopleinspace
import com.surrus.common.di.PeopleInSpaceDatabaseWrapper
import com.apollographql.apollo3.ApolloClient
import com.surrus.common.di.commonModule
import com.surrus.common.repository.PeopleInSpaceRepositoryInterface
import com.surrus.common.repository.platformModule
@ -28,7 +28,7 @@ class PeopleInSpaceTest: KoinTest {
commonModule(true),
platformModule(),
module {
single { PeopleInSpaceDatabaseWrapper(null) }
single { createMockApolloClient("https://peopleinspace-graphql-guhrsfr7ka-uc.a.run.app/graphql") }
}
)
}
@ -40,4 +40,11 @@ class PeopleInSpaceTest: KoinTest {
println(result)
assertTrue(result.isNotEmpty())
}
private fun createMockApolloClient(url: String): ApolloClient {
println("createMockApolloClient, ur = $url")
return ApolloClient.Builder()
.serverUrl(url)
.build()
}
}

View file

@ -1,16 +1,11 @@
package com.surrus.common.repository
import com.apollographql.apollo3.cache.normalized.api.NormalizedCacheFactory
import com.apollographql.apollo3.cache.normalized.sql.SqlNormalizedCacheFactory
import com.squareup.sqldelight.drivers.native.NativeSqliteDriver
import com.surrus.common.di.PeopleInSpaceDatabaseWrapper
import com.surrus.peopleinspace.db.PeopleInSpaceDatabase
import io.ktor.client.engine.ios.*
import org.koin.dsl.module
actual fun platformModule() = module {
single {
val driver = NativeSqliteDriver(PeopleInSpaceDatabase.Schema, "peopleinspace.db")
PeopleInSpaceDatabaseWrapper(PeopleInSpaceDatabase(driver))
}
single { Ios.create() }
single<NormalizedCacheFactory> { SqlNormalizedCacheFactory("swapi.db") }
}

View file

@ -1,12 +1,9 @@
package com.surrus.common.repository
import com.surrus.common.di.PeopleInSpaceDatabaseWrapper
import io.ktor.client.engine.js.*
import com.apollographql.apollo3.cache.normalized.api.MemoryCacheFactory
import com.apollographql.apollo3.cache.normalized.api.NormalizedCacheFactory
import org.koin.dsl.module
actual fun platformModule() = module {
single {
PeopleInSpaceDatabaseWrapper(null)
}
single { Js.create() }
single<NormalizedCacheFactory> { MemoryCacheFactory() }
}

View file

@ -1,13 +0,0 @@
package com.surrus
import com.surrus.common.di.initKoin
import com.surrus.common.remote.PeopleInSpaceApi
import kotlinx.coroutines.runBlocking
fun main() {
runBlocking {
val koin = initKoin(enableNetworkLogs = true).koin
val api = koin.get<PeopleInSpaceApi>()
println(api.fetchPeople())
}
}

View file

@ -1,16 +1,9 @@
package com.surrus.common.repository
import com.squareup.sqldelight.sqlite.driver.JdbcSqliteDriver
import com.surrus.common.di.PeopleInSpaceDatabaseWrapper
import com.surrus.peopleinspace.db.PeopleInSpaceDatabase
import io.ktor.client.engine.java.*
import com.apollographql.apollo3.cache.normalized.api.NormalizedCacheFactory
import com.apollographql.apollo3.cache.normalized.sql.SqlNormalizedCacheFactory
import org.koin.dsl.module
actual fun platformModule() = module {
single {
val driver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY)
.also { PeopleInSpaceDatabase.Schema.create(it) }
PeopleInSpaceDatabaseWrapper(PeopleInSpaceDatabase(driver))
}
single { Java.create() }
single<NormalizedCacheFactory> { SqlNormalizedCacheFactory("jdbc:sqlite:swapi.db") }
}

View file

@ -1,15 +1,10 @@
package com.surrus.common.repository
import com.apollographql.apollo3.cache.normalized.api.NormalizedCacheFactory
import com.apollographql.apollo3.cache.normalized.sql.SqlNormalizedCacheFactory
import com.squareup.sqldelight.drivers.native.NativeSqliteDriver
import com.surrus.common.di.PeopleInSpaceDatabaseWrapper
import com.surrus.peopleinspace.db.PeopleInSpaceDatabase
import io.ktor.client.engine.ios.*
import org.koin.dsl.module
actual fun platformModule() = module {
single {
val driver = NativeSqliteDriver(PeopleInSpaceDatabase.Schema, "peopleinspace.db")
PeopleInSpaceDatabaseWrapper(PeopleInSpaceDatabase(driver))
}
single { Ios.create() }
single<NormalizedCacheFactory> { SqlNormalizedCacheFactory("swapi.db") }
}

View file

@ -19,8 +19,9 @@ import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import androidx.compose.ui.window.rememberWindowState
import com.surrus.common.di.initKoin
import com.surrus.common.remote.Assignment
import com.surrus.common.remote.PeopleInSpaceApi
import com.surrus.common.repository.PeopleInSpaceRepositoryInterface
import com.surrus.common.model.IssPosition
import com.surrus.common.model.Assignment
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.jetbrains.skia.Image.Companion.makeFromEncoded
@ -39,10 +40,10 @@ fun main() = application {
var peopleState by remember { mutableStateOf(emptyList<Assignment>()) }
var selectedPerson by remember { mutableStateOf<Assignment?>(null) }
val peopleInSpaceApi = koin.get<PeopleInSpaceApi>()
val repository = koin.get<PeopleInSpaceRepositoryInterface>()
LaunchedEffect(true) {
peopleState = peopleInSpaceApi.fetchPeople().people
peopleState = repository.fetchPeople()
selectedPerson = peopleState.first()
}

View file

@ -1,8 +1,7 @@
import androidx.compose.runtime.*
import com.surrus.common.di.initKoin
import com.surrus.common.remote.Assignment
import com.surrus.common.remote.IssPosition
import com.surrus.common.repository.PeopleInSpaceRepository
import com.surrus.common.model.Assignment
import com.surrus.common.model.IssPosition
import com.surrus.common.repository.PeopleInSpaceRepositoryInterface
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.flow.collect

View file

@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip

View file

@ -1 +0,0 @@
build

View file

@ -1,41 +0,0 @@
plugins {
id("kotlin-platform-jvm")
id("org.jetbrains.kotlin.plugin.spring") version("1.6.10")
id("org.jetbrains.kotlin.plugin.serialization")
id("org.springframework.boot") version("2.5.6")
id("com.google.cloud.tools.appengine") version("2.4.2")
id("com.github.johnrengelman.shadow")
}
dependencies {
implementation("com.expediagroup:graphql-kotlin-spring-server:5.3.0")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.3.1")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.1")
testImplementation("com.squareup.okhttp3:okhttp:4.9.3")
with(Deps.Log) {
implementation(logback)
}
implementation(project(":common"))
}
kotlin {
sourceSets.all {
languageSettings {
optIn("kotlin.RequiresOptIn")
}
}
}
appengine {
stage {
setArtifact(tasks.named("bootJar").flatMap { (it as Jar).archiveFile })
}
deploy {
projectId = "peopleinspace-graphql"
version = "GCLOUD_CONFIG"
}
}

View file

@ -1,3 +0,0 @@
runtime: java11
entrypoint: java -Xmx64m -jar graphql-server.jar

View file

@ -1,20 +0,0 @@
package com.surrus.peopleinspace
import com.surrus.common.di.initKoin
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.ConfigurableApplicationContext
val koin = initKoin(enableNetworkLogs = true).koin
@SpringBootApplication
class DefaultApplication {
}
fun runServer(): ConfigurableApplicationContext {
return runApplication<DefaultApplication>()
}

View file

@ -1,37 +0,0 @@
package com.surrus.peopleinspace
import com.expediagroup.graphql.server.operations.Subscription
import com.surrus.common.remote.IssPosition
import com.surrus.common.remote.PeopleInSpaceApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.reactive.asPublisher
import org.reactivestreams.Publisher
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Component
@Component
class IssPositionSubscription : Subscription {
private val logger: Logger = LoggerFactory.getLogger(IssPositionSubscription::class.java)
private var peopleInSpaceApi: PeopleInSpaceApi = koin.get()
fun issPosition(): Publisher<IssPosition> {
return flow {
while (true) {
val position = peopleInSpaceApi.fetchISSPosition().iss_position
logger.info("ISS position = $position")
emit(position)
delay(POLL_INTERVAL)
}
}.asPublisher()
}
companion object {
private const val POLL_INTERVAL = 10000L
}
}

View file

@ -1,15 +0,0 @@
package com.surrus.peopleinspace
import com.expediagroup.graphql.server.operations.Query
import com.surrus.common.remote.PeopleInSpaceApi
import com.surrus.common.remote.Assignment
import org.springframework.stereotype.Component
data class People(val people: List<Assignment>)
@Component
class RootQuery : Query {
private var peopleInSpaceApi: PeopleInSpaceApi = koin.get()
suspend fun allPeople(): People = People(peopleInSpaceApi.fetchPeople().people)
}

View file

@ -1,6 +0,0 @@
package com.surrus.peopleinspace
fun main(args: Array<String>) {
runServer()
}

View file

@ -1,2 +0,0 @@
graphql:
packages: "com.surrus"

View file

@ -19,5 +19,3 @@ include(":app", ":common", ":compose-desktop")
include(":wearApp")
include(":web")
include(":compose-web")
include(":backend")
include(":graphql-server")

View file

@ -6,7 +6,7 @@ import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.onParent
import com.surrus.common.remote.Assignment
import com.surrus.common.model.Assignment
import com.surrus.peopleinspace.PersonListScreen
import com.surrus.peopleinspace.PersonListTag
import org.junit.Rule

View file

@ -14,7 +14,7 @@ import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.viewinterop.AndroidView
import androidx.wear.compose.material.MaterialTheme
import androidx.wear.compose.material.Scaffold
import com.surrus.common.remote.IssPosition
import com.surrus.common.model.IssPosition
import org.koin.androidx.compose.getViewModel
import org.osmdroid.tileprovider.tilesource.TileSourceFactory
import org.osmdroid.util.GeoPoint

View file

@ -2,7 +2,7 @@ package com.surrus.peopleinspace
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.surrus.common.remote.Assignment
import com.surrus.common.model.Assignment
import com.surrus.common.repository.PeopleInSpaceRepositoryInterface
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.stateIn

View file

@ -34,7 +34,7 @@ import androidx.wear.compose.material.Scaffold
import androidx.wear.compose.material.Text
import androidx.wear.compose.material.Vignette
import androidx.wear.compose.material.VignettePosition
import com.surrus.common.remote.Assignment
import com.surrus.common.model.Assignment
import org.koin.androidx.compose.getViewModel
@Composable

View file

@ -47,7 +47,7 @@ import androidx.wear.compose.material.VignettePosition
import androidx.wear.compose.material.rememberScalingLazyListState
import coil.annotation.ExperimentalCoilApi
import coil.compose.rememberImagePainter
import com.surrus.common.remote.Assignment
import com.surrus.common.model.Assignment
import org.koin.androidx.compose.getViewModel
const val PersonListTag = "PersonList"

View file

@ -13,7 +13,7 @@ import androidx.glance.layout.padding
import androidx.glance.text.FontWeight
import androidx.glance.text.TextStyle
import androidx.glance.unit.ColorProvider
import com.surrus.common.remote.Assignment
import com.surrus.common.model.Assignment
import com.surrus.common.repository.PeopleInSpaceRepositoryInterface
import com.surrus.peopleinspace.tile.util.BaseGlanceTileService
import kotlinx.coroutines.flow.first

View file

@ -1,5 +1,5 @@
import com.surrus.common.remote.Assignment
import com.surrus.common.remote.IssPosition
import com.surrus.common.model.Assignment
import com.surrus.common.model.IssPosition
import components.*
import components.materialui.AppBar
import components.materialui.Card

View file

@ -1,6 +1,6 @@
package components
import com.surrus.common.remote.IssPosition
import com.surrus.common.model.IssPosition
import components.pigeonmaps.Map
import components.pigeonmaps.Marker
import react.RBuilder

View file

@ -1,6 +1,6 @@
package components
import com.surrus.common.remote.Assignment
import com.surrus.common.model.Assignment
import components.materialui.Avatar
import components.materialui.ListItem
import components.materialui.ListItemAvatar

View file

@ -1,6 +1,6 @@
package components
import com.surrus.common.remote.Assignment
import com.surrus.common.model.Assignment
import kotlinx.css.Align
import kotlinx.css.ObjectFit
import kotlinx.css.alignSelf