SwiftUI updates + move image url retrieval to shared code
This commit is contained in:
parent
78179b36ef
commit
4f3448b867
8 changed files with 92 additions and 24 deletions
|
@ -64,13 +64,12 @@ sealed class Routing {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun mainLayout(peopleInSpaceViewModel: PeopleInSpaceViewModel, defaultRouting: Routing) {
|
fun mainLayout(peopleInSpaceViewModel: PeopleInSpaceViewModel, defaultRouting: Routing) {
|
||||||
val peopleState = peopleInSpaceViewModel.peopleInSpace.observeAsState(emptyList())
|
|
||||||
|
|
||||||
PeopleInSpaceTheme {
|
PeopleInSpaceTheme {
|
||||||
Router(defaultRouting) { backStack ->
|
Router(defaultRouting) { backStack ->
|
||||||
when (val routing = backStack.last()) {
|
when (val routing = backStack.last()) {
|
||||||
is Routing.PeopleList -> PersonList(
|
is Routing.PeopleList -> PersonList(
|
||||||
peopleState = peopleState,
|
peopleInSpaceViewModel = peopleInSpaceViewModel,
|
||||||
personSelected = {
|
personSelected = {
|
||||||
backStack.push(Routing.PersonDetails(it))
|
backStack.push(Routing.PersonDetails(it))
|
||||||
}
|
}
|
||||||
|
@ -82,30 +81,35 @@ fun mainLayout(peopleInSpaceViewModel: PeopleInSpaceViewModel, defaultRouting: R
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PersonList(peopleState: State<List<Assignment>>, personSelected : (person : Assignment) -> Unit) {
|
fun PersonList(peopleInSpaceViewModel: PeopleInSpaceViewModel, personSelected : (person : Assignment) -> Unit) {
|
||||||
|
val peopleState = peopleInSpaceViewModel.peopleInSpace.observeAsState(emptyList())
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(title = { Text("People In Space") })
|
TopAppBar(title = { Text("People In Space") })
|
||||||
},
|
},
|
||||||
bodyContent = {
|
bodyContent = {
|
||||||
LazyColumnFor(items = peopleState.value, itemContent = { person ->
|
LazyColumnFor(items = peopleState.value, itemContent = { person ->
|
||||||
PersonView(person, personSelected)
|
val personImageUrl = peopleInSpaceViewModel.getPersonImage(person.name)
|
||||||
|
PersonView(personImageUrl, person, personSelected)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PersonView(person: Assignment, personSelected : (person : Assignment) -> Unit) {
|
fun PersonView(personImageUrl: String, person: Assignment, personSelected : (person : Assignment) -> Unit) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.padding(16.dp) + Modifier.fillMaxWidth()
|
modifier = Modifier.padding(16.dp) + Modifier.fillMaxWidth()
|
||||||
+ Modifier.clickable(onClick = { personSelected(person) }),
|
+ Modifier.clickable(onClick = { personSelected(person) }),
|
||||||
verticalGravity = Alignment.CenterVertically
|
verticalGravity = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
|
|
||||||
personImages[person.name]?.let { imageUrl ->
|
if (personImageUrl.isNotEmpty()) {
|
||||||
CoilImage(data = imageUrl, modifier = Modifier.preferredSize(60.dp))
|
CoilImage(data = personImageUrl, modifier = Modifier.preferredSize(60.dp))
|
||||||
} ?: Spacer(modifier = Modifier.preferredSize(60.dp))
|
} else {
|
||||||
|
Spacer(modifier = Modifier.preferredSize(60.dp))
|
||||||
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.preferredSize(12.dp))
|
Spacer(modifier = Modifier.preferredSize(12.dp))
|
||||||
|
|
||||||
|
@ -137,10 +141,10 @@ fun PersonDetailsView(peopleInSpaceViewModel: PeopleInSpaceViewModel, person: As
|
||||||
Text(person.name, style = MaterialTheme.typography.h4)
|
Text(person.name, style = MaterialTheme.typography.h4)
|
||||||
Spacer(modifier = Modifier.preferredSize(12.dp))
|
Spacer(modifier = Modifier.preferredSize(12.dp))
|
||||||
|
|
||||||
personImages[person.name]?.let { imageUrl ->
|
val imageUrl = peopleInSpaceViewModel.getPersonImage(person.name)
|
||||||
|
if (imageUrl.isNotEmpty()) {
|
||||||
CoilImage(data = imageUrl, modifier = Modifier.preferredSize(240.dp))
|
CoilImage(data = imageUrl, modifier = Modifier.preferredSize(240.dp))
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.preferredSize(24.dp))
|
Spacer(modifier = Modifier.preferredSize(24.dp))
|
||||||
|
|
||||||
val bio = peopleInSpaceViewModel.getPersonBio(person.name)
|
val bio = peopleInSpaceViewModel.getPersonBio(person.name)
|
||||||
|
@ -155,6 +159,6 @@ fun PersonDetailsView(peopleInSpaceViewModel: PeopleInSpaceViewModel, person: As
|
||||||
@Composable
|
@Composable
|
||||||
fun DefaultPreview(@PreviewParameter(PersonProvider::class) person: Assignment) {
|
fun DefaultPreview(@PreviewParameter(PersonProvider::class) person: Assignment) {
|
||||||
MaterialTheme {
|
MaterialTheme {
|
||||||
PersonView(person, personSelected = {})
|
PersonView("", person, personSelected = {})
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -22,4 +22,8 @@ class PeopleInSpaceViewModel(private val peopleInSpaceRepository: PeopleInSpaceR
|
||||||
fun getPersonBio(personName: String): String {
|
fun getPersonBio(personName: String): String {
|
||||||
return peopleInSpaceRepository.getPersonBio(personName)
|
return peopleInSpaceRepository.getPersonBio(personName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getPersonImage(personName: String): String {
|
||||||
|
return peopleInSpaceRepository.getPersonImage(personName)
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -3,12 +3,6 @@ package com.surrus.peopleinspace.ui
|
||||||
import androidx.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
import androidx.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||||
import com.surrus.common.remote.Assignment
|
import com.surrus.common.remote.Assignment
|
||||||
|
|
||||||
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 "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c1/Ivan_Vagner_%28Jsc2020e014992%29.jpg/440px-Ivan_Vagner_%28Jsc2020e014992%29.jpg",
|
|
||||||
)
|
|
||||||
|
|
||||||
class PersonProvider : CollectionPreviewParameterProvider<Assignment>(
|
class PersonProvider : CollectionPreviewParameterProvider<Assignment>(
|
||||||
listOf(
|
listOf(
|
||||||
Assignment("ISS", "Chris Cassidy"),
|
Assignment("ISS", "Chris Cassidy"),
|
||||||
|
|
|
@ -57,6 +57,10 @@ class PeopleInSpaceRepository() : KoinComponent {
|
||||||
return personBios[personName] ?: ""
|
return personBios[personName] ?: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getPersonImage(personName: String): String {
|
||||||
|
return personImages[personName] ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
// called from Kotlin/Native clients
|
// called from Kotlin/Native clients
|
||||||
fun fetchPeople(success: (List<Assignment>) -> Unit) {
|
fun fetchPeople(success: (List<Assignment>) -> Unit) {
|
||||||
GlobalScope.launch(Dispatchers.Main) {
|
GlobalScope.launch(Dispatchers.Main) {
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
1A40FE1923DB5C35008428A6 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A40FE1823DB5C35008428A6 /* libsqlite3.tbd */; };
|
1A40FE1923DB5C35008428A6 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A40FE1823DB5C35008428A6 /* libsqlite3.tbd */; };
|
||||||
|
1AA82EED25052E1E00193051 /* ImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AA82EEC25052E1E00193051 /* ImageView.swift */; };
|
||||||
1ABD44FA23B00008008387E3 /* ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ABD44F923B00008008387E3 /* ViewModel.swift */; };
|
1ABD44FA23B00008008387E3 /* ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ABD44F923B00008008387E3 /* ViewModel.swift */; };
|
||||||
1ABFB8BE23AFF5CC003D807E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ABFB8BD23AFF5CC003D807E /* AppDelegate.swift */; };
|
1ABFB8BE23AFF5CC003D807E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ABFB8BD23AFF5CC003D807E /* AppDelegate.swift */; };
|
||||||
1ABFB8C023AFF5CC003D807E /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ABFB8BF23AFF5CC003D807E /* SceneDelegate.swift */; };
|
1ABFB8C023AFF5CC003D807E /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ABFB8BF23AFF5CC003D807E /* SceneDelegate.swift */; };
|
||||||
|
@ -20,6 +21,7 @@
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
1A40FE1823DB5C35008428A6 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; };
|
1A40FE1823DB5C35008428A6 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; };
|
||||||
|
1AA82EEC25052E1E00193051 /* ImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageView.swift; sourceTree = "<group>"; };
|
||||||
1ABD44F923B00008008387E3 /* ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModel.swift; sourceTree = "<group>"; };
|
1ABD44F923B00008008387E3 /* ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModel.swift; sourceTree = "<group>"; };
|
||||||
1ABFB8BA23AFF5CC003D807E /* PeopleInSpaceSwiftUI.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PeopleInSpaceSwiftUI.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
1ABFB8BA23AFF5CC003D807E /* PeopleInSpaceSwiftUI.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PeopleInSpaceSwiftUI.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
1ABFB8BD23AFF5CC003D807E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
1ABFB8BD23AFF5CC003D807E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
@ -85,6 +87,7 @@
|
||||||
1ABFB8CB23AFF5CE003D807E /* Info.plist */,
|
1ABFB8CB23AFF5CE003D807E /* Info.plist */,
|
||||||
1ABFB8C523AFF5CE003D807E /* Preview Content */,
|
1ABFB8C523AFF5CE003D807E /* Preview Content */,
|
||||||
1ABD44F923B00008008387E3 /* ViewModel.swift */,
|
1ABD44F923B00008008387E3 /* ViewModel.swift */,
|
||||||
|
1AA82EEC25052E1E00193051 /* ImageView.swift */,
|
||||||
);
|
);
|
||||||
path = PeopleInSpaceSwiftUI;
|
path = PeopleInSpaceSwiftUI;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -203,6 +206,7 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
1AA82EED25052E1E00193051 /* ImageView.swift in Sources */,
|
||||||
1ABFB8BE23AFF5CC003D807E /* AppDelegate.swift in Sources */,
|
1ABFB8BE23AFF5CC003D807E /* AppDelegate.swift in Sources */,
|
||||||
1ABFB8C023AFF5CC003D807E /* SceneDelegate.swift in Sources */,
|
1ABFB8C023AFF5CC003D807E /* SceneDelegate.swift in Sources */,
|
||||||
1ABD44FA23B00008008387E3 /* ViewModel.swift in Sources */,
|
1ABD44FA23B00008008387E3 /* ViewModel.swift in Sources */,
|
||||||
|
|
|
@ -8,11 +8,9 @@ struct ContentView: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
List(peopleInSpaceViewModel.people, id: \.name) { person in
|
List(peopleInSpaceViewModel.people, id: \.name) { person in
|
||||||
|
|
||||||
NavigationLink(destination: PersonDetailsView(peopleInSpaceViewModel: self.peopleInSpaceViewModel, person: person)) {
|
NavigationLink(destination: PersonDetailsView(peopleInSpaceViewModel: self.peopleInSpaceViewModel, person: person)) {
|
||||||
PersonView(person: person)
|
PersonView(peopleInSpaceViewModel: self.peopleInSpaceViewModel, person: person)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
.navigationBarTitle(Text("PeopleInSpace"), displayMode: .large)
|
.navigationBarTitle(Text("PeopleInSpace"), displayMode: .large)
|
||||||
.onAppear(perform: {
|
.onAppear(perform: {
|
||||||
|
@ -23,10 +21,12 @@ struct ContentView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PersonView: View {
|
struct PersonView: View {
|
||||||
|
var peopleInSpaceViewModel: PeopleInSpaceViewModel
|
||||||
var person: Assignment
|
var person: Assignment
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack {
|
HStack {
|
||||||
|
ImageView(withURL: peopleInSpaceViewModel.getPersonImage(personName: person.name), width: 64, height: 64)
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
Text(person.name).font(.headline)
|
Text(person.name).font(.headline)
|
||||||
Text(person.craft).font(.subheadline)
|
Text(person.craft).font(.subheadline)
|
||||||
|
@ -41,12 +41,15 @@ struct PersonDetailsView: View {
|
||||||
var person: Assignment
|
var person: Assignment
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .center, spacing: 10) {
|
VStack(alignment: .center, spacing: 32) {
|
||||||
Text(person.name).font(.headline)
|
Text(person.name).font(.title)
|
||||||
|
|
||||||
|
ImageView(withURL: peopleInSpaceViewModel.getPersonImage(personName: person.name), width: 240, height: 240)
|
||||||
|
|
||||||
Text(peopleInSpaceViewModel.getPersonBio(personName: person.name)).font(.body)
|
Text(peopleInSpaceViewModel.getPersonBio(personName: person.name)).font(.body)
|
||||||
|
Spacer()
|
||||||
}
|
}
|
||||||
.padding(.leading)
|
.padding()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
import SwiftUI
|
||||||
|
import Foundation
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
struct ImageView: View {
|
||||||
|
@ObservedObject var imageLoader:ImageLoader
|
||||||
|
@State var image:UIImage = UIImage()
|
||||||
|
var width: CGFloat
|
||||||
|
var height: CGFloat
|
||||||
|
|
||||||
|
init(withURL url:String, width: CGFloat, height: CGFloat) {
|
||||||
|
imageLoader = ImageLoader(urlString:url)
|
||||||
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
Image(uiImage: image)
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fit)
|
||||||
|
.frame(width: width, height: height)
|
||||||
|
}.onReceive(imageLoader.didChange) { data in
|
||||||
|
self.image = UIImage(data: data) ?? UIImage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ImageLoader: ObservableObject {
|
||||||
|
var didChange = PassthroughSubject<Data, Never>()
|
||||||
|
var data = Data() {
|
||||||
|
didSet {
|
||||||
|
didChange.send(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init(urlString:String) {
|
||||||
|
guard let url = URL(string: urlString) else { return }
|
||||||
|
let task = URLSession.shared.dataTask(with: url) { data, response, error in
|
||||||
|
guard let data = data else { return }
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.data = data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
task.resume()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -19,5 +19,10 @@ class PeopleInSpaceViewModel: ObservableObject {
|
||||||
func getPersonBio(personName: String) -> String {
|
func getPersonBio(personName: String) -> String {
|
||||||
return repository.getPersonBio(personName: personName)
|
return repository.getPersonBio(personName: personName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getPersonImage(personName: String) -> String {
|
||||||
|
return repository.getPersonImage(personName: personName)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue