Compare commits

...

No commits in common. "main" and "gh-pages" have entirely different histories.

384 changed files with 84 additions and 18986 deletions

View file

@ -1,62 +0,0 @@
name: Android CI
on: [push, pull_request]
jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
- name: Build android app
run: ./gradlew assembleDebug
- name: Run Unit Tests
run: ./gradlew test
- name: Build web app
run: ./gradlew :web:assemble
- name: Build iOS shared code
run: ./gradlew :common:compileKotlinIOS
- name: Build macOS shared code
run: ./gradlew :common:compileKotlinMacOS
# If main branch update, deploy to gh-pages
- name: Deploy
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main'
uses: JamesIves/github-pages-deploy-action@3.7.1
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH: gh-pages # The branch the action should deploy to.
FOLDER: web/build/distributions # The folder the action should deploy.
CLEAN: true # Automatically remove deleted files from the deploy branch
androidTest:
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
- name: Wear Instrumentation Tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 28
target: android-wear
script: ./gradlew wear:connectedAndroidTest
- name: Android Instrumentation Tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 29
target: google_apis
script: ./gradlew app:connectedAndroidTest

391
.gitignore vendored
View file

@ -1,391 +0,0 @@
# Ignore Gradle GUI config
gradle-app.setting
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar
# Cache of project
.gradletasknamecache
# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
# gradle/wrapper/gradle-wrapper.properties
.idea
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
/build
/captures
*.xcworkspacedata
*.xcuserstate
*.xcscheme
xcschememanagement.plist
*.xcbkptlist
### Kotlin template
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
### Gradle template
**/build/
!src/**/build/
# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
# gradle/wrapper/gradle-wrapper.properties
### Windows template
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
### Android template
# Built application files
*.apk
*.aar
*.ap_
*.aab
# Files for the ART/Dalvik VM
*.dex
# Java class files
# Generated files
bin/
gen/
out/
# Uncomment the following line in case you need and you don't have the release build type files in your app
# release/
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/
# IntelliJ
*.iml
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
.idea/assetWizardSettings.xml
.idea/dictionaries
.idea/libraries
# Android Studio 3 in .gitignore file.
.idea/caches
.idea/modules.xml
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
.idea/navEditor.xml
# Keystore files
# Uncomment the following lines if you do not want to check your keystore files in.
#*.jks
#*.keystore
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
.cxx/
# Google Services (e.g. APIs or Firebase)
# google-services.json
# Freeline
freeline.py
freeline/
freeline_project_description.json
# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md
# Version control
vcs.xml
# lint
lint/intermediates/
lint/generated/
lint/outputs/
lint/tmp/
# lint/reports/
# Android Profiling
*.hprof
### Xcode template
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## User settings
xcuserdata/
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
*.xcscmblueprint
*.xccheckout
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
DerivedData/
*.moved-aside
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
## Gcc Patch
/*.gcno
### macOS template
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### Swift template
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## User settings
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
## Obj-C/Swift specific
*.hmap
## App packaging
*.ipa
*.dSYM.zip
*.dSYM
## Playgrounds
timeline.xctimeline
playground.xcworkspace
# Swift Package Manager
#
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
# Packages/
# Package.pins
# Package.resolved
# *.xcodeproj
#
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
# hence it is not needed unless you have added a package configuration file to your project
# .swiftpm
.build/
# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# Pods/
#
# Add this line if you want to avoid checking in source code from the Xcode workspace
# *.xcworkspace
# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts
Carthage/Build/
# Accio dependency management
Dependencies/
.accio/
# fastlane
#
# It is recommended to not store the screenshots in the git repo.
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control
fastlane/screenshots/**/*.png
# Code Injection
#
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode
iOSInjectionProject/
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### Linux template
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*

203
LICENSE
View file

@ -1,203 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

144
README.md
View file

@ -1,144 +0,0 @@
# PeopleInSpace
Minimal **Kotlin Multiplatform** project with SwiftUI, Jetpack Compose, Compose for Wear OS, Compose for Desktop, Compose for Web, and Kotlin/JS + React clients along with Ktor backend. Currently running on
* Android (Jetpack Compose)
* Android App Widget (Compose based Glance API - contributed by https://github.com/yschimke)
* Wear OS (Compose for Wear OS - primarily developed by https://github.com/yschimke)
* iOS (SwiftUI)
* iOS App Widget (SwiftUI)
* watchOS (SwiftUI) (contributed by https://github.com/nealsanche)
* macOS (SwiftUI)
* Desktop (Compose for Desktop)
* Web (Compose for Web)
* Web (Kotlin/JS + React Wrapper) (contributed by https://github.com/PatilShreyas)
* JVM (small Ktor back end service + `Main.kt` in `common` module)
It makes use of [Open Notify PeopleInSpace API](http://open-notify.org/Open-Notify-API/People-In-Space/) to show list of people currently in
space and also the position of the International Space Station (inspired by https://kousenit.org/2019/12/19/a-few-astronomical-examples-in-kotlin/)!
The project is included as sample in the official [Kotlin Multiplatform Mobile docs](https://kotlinlang.org/docs/mobile/samples.html#peopleinspace) and also the [Google Dev Library](https://devlibrary.withgoogle.com/products/android)
Related posts:
* [Minimal Kotlin Multiplatform project using Compose and SwiftUI](https://johnoreilly.dev/posts/minimal-kotlin-platform-compose-swiftui/)
* [Adding some Storage (to) Space](https://johnoreilly.dev/posts/adding-sqldelight-to-peopleinspace/)
* [Kotlin Multiplatform running on macOS](https://johnoreilly.dev/posts/kotlinmultiplatform-macos/)
* [PeopleInSpace hits the web with Kotlin/JS and React](https://johnoreilly.dev/posts/peopleinspace-kotlinjs/)
* [Using Koin in a Kotlin Multiplatform Project](https://johnoreilly.dev/posts/kotlinmultiplatform-koin/)
* [Jetpack Compose for the Desktop!](https://johnoreilly.dev/posts/jetpack-compose-desktop-copy/)
* [Comparing use of LiveData and StateFlow in a Jetpack Compose project](https://johnoreilly.dev/posts/jetpack-compose-stateflow-livedata/)
* [Wrapping Kotlin Flow with Swift Combine Publisher in a Kotlin Multiplatform project](https://johnoreilly.dev/posts/kotlinmultiplatform-swift-combine_publisher-flow/)
* [Using Swift Packages in a Kotlin Multiplatform project](https://johnoreilly.dev/posts/kotlinmultiplatform-swift-package/)
* [Using Swift's new async/await when invoking Kotlin Multiplatform code](https://johnoreilly.dev/posts/swift_async_await_kotlin_coroutines/)
* [Exploring new AWS SDK for Kotlin](https://johnoreilly.dev/posts/aws-sdk-kotlin/)
Note that this repository very much errs on the side of minimalism to help more clearly illustrate key moving parts of a Kotlin
Multiplatform project and also to hopefully help someone just starting to explore KMP to get up and running for first time (and is of course
primarily focused on use of Jetpack Compose and SwiftUI). If you're at the stage of moving
beyond this then I'd definitely recommend checking out [KaMPKit](https://github.com/touchlab/KaMPKit) from Touchlab.
I also have the following samples that demonstrate the use of a variety of Kotlin Multiplatform libraries (and also use Jetpack Compose and SwiftUI).
* [BikeShare](https://github.com/joreilly/BikeShare)
* [GalwayBus](https://github.com/joreilly/GalwayBus)
* [MortyComposeKMM](https://github.com/joreilly/MortyComposeKMM)
* [FantasyPremierLeague](https://github.com/joreilly/FantasyPremierLeague)
* [StarWars](https://github.com/joreilly/StarWars)
* [Chip-8](https://github.com/joreilly/chip-8)
### Building
You need to use Android Studio Arctic Fox (**note: Java 11 is now the minimum version required**). Requires XCode 13.2 or later (due to use of new Swift 5.5 concurrnecy APIs).
When opening iOS/watchOS/macOS projects remember to open `.xcworkspace` file (and not `.xcodeproj` one).
To exercise (React based) web client run `./gradlew :web:browserDevelopmentRun`.
To run backend you can either run `./gradlew :backend:run` or run `Server.kt` directly from Android Studio. After doing that you should then for example be able to open `http://localhost:9090/astros_local.json` in a browser.
### Compose for Web client
The Compose for Web client resides in the `compose-web` module and can be run by
invoking `./gradlew :compose-web:jsBrowserDevelopmentRun`
### Compose for Desktop client
This client is available in `compose-desktop` module. Note that you need to use appropriate version of JVM when running (works for example with Java 11)
### Backend code
Have tested this out in Google App Engine deployment. Using shadowJar plugin to create an "uber" jar and then deploying it as shown below. Should be possible to deploy this jar to other services as well.
```
./gradlew :backend:shadowJar
gcloud app deploy backend/build/libs/backend-all.jar
```
### GraphQL backend
There's a GraphQL module (`graphql-server`) which can be run locally using `./gradlew :graphql-server:bootRun` with "playground" then available at http://localhost:8080/playground
### Screenshots
**iOS (SwiftUI)**
<br/>
<img width="546" alt="Screenshot 2021-02-27 at 12 09 02" src="https://user-images.githubusercontent.com/6302/109386736-ac1f0700-78f4-11eb-812e-4bf971a8c2a7.png">
**Android (Jetpack Compose)**
<br/>
<img width="555" alt="Screenshot 2021-03-07 at 17 03 46" src="https://user-images.githubusercontent.com/6302/110248059-2ab81c00-7f67-11eb-9b3a-2b04d1be43ef.png">
**watchOS (SwiftUI)**
<br/>
<img width="250" alt="watchOS Screenshot 1" src="https://user-images.githubusercontent.com/6302/139499100-dc5112b0-04b9-4bdc-9c30-9975f3608eb3.png">
<img width="250" alt="watch0S Screenshot 2" src="https://user-images.githubusercontent.com/6302/139499115-944b241d-8e92-428b-b86c-f599b456c4bf.png">
**Wear OS (Wear Compose)**
<br/>
<img width="250" alt="Wear Compose Screenshot 1" src="https://user-images.githubusercontent.com/6302/137623548-ac51ca72-572e-4009-8b34-315defdf93a5.png">
<img width="250" alt="Wear Compose Screenshot 2" src="https://user-images.githubusercontent.com/6302/137640396-851489bb-e41d-47ef-badb-e2d22454eee4.png">
<img width="250" alt="Wear Compose Screenshot 3" src="https://user-images.githubusercontent.com/6302/139468900-16ad4e95-41dc-427f-977c-b893b1751c78.png">
**macOS (SwiftUI)**
<br/>
<img width="937" alt="Screenshot 2021-06-01 at 20 02 31" src="https://user-images.githubusercontent.com/6302/120376983-6ec37e80-c314-11eb-8279-7acc0c2d5206.png">
**Compose for Desktop**
<br/>
<img width="912" alt="Screenshot 2021-10-01 at 16 45 06" src="https://user-images.githubusercontent.com/6302/135652185-4ce9d8e3-f06e-4e9d-9930-3e900267f8bd.png">
**Compose for Web**
<br/>
<img width="564" alt="Screenshot 2021-05-31 at 21 29 53" src="https://user-images.githubusercontent.com/6302/120240074-9dc7ea80-c257-11eb-9884-5870a3f4ef95.png">
**Web App (Kotlin/JS + React)**
<br/>
<img width="612" alt="Screenshot 2021-06-06 at 23 50 00" src="https://user-images.githubusercontent.com/19620536/120935764-eda82500-c721-11eb-9042-f15ade7473f7.png">
### Languages, libraries and tools used
* [Kotlin](https://kotlinlang.org/)
* [Kotlin Coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html)
* [Kotlinx Serialization](https://github.com/Kotlin/kotlinx.serialization)
* [Ktor client library](https://github.com/ktorio/ktor)
* [Android Architecture Components](https://developer.android.com/topic/libraries/architecture/index.html)
* [Koin](https://github.com/InsertKoinIO/koin)
* [SQLDelight](https://github.com/cashapp/sqldelight)
* [Jetpack Compose](https://developer.android.com/jetpack/compose)
* [SwiftUI](https://developer.apple.com/documentation/swiftui)
* [KMP-NativeCoroutines](https://github.com/rickclephas/KMP-NativeCoroutines)

2
app/.gitignore vendored
View file

@ -1,2 +0,0 @@
/build
*.iml

View file

@ -1,105 +0,0 @@
plugins {
id("com.android.application")
kotlin("android")
id("com.github.ben-manes.versions")
}
android {
compileSdk = Versions.androidCompileSdk
defaultConfig {
applicationId = "com.surrus.peopleinspace"
minSdk = Versions.androidMinSdk
targetSdk = Versions.androidTargetSdk
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = Versions.composeCompiler
}
buildTypes {
getByName("release") {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
packagingOptions {
resources.excludes.add("META-INF/licenses/**")
resources.excludes.add("META-INF/AL2.0")
resources.excludes.add("META-INF/LGPL2.1")
}
}
dependencies {
with(Deps.Android) {
implementation(material)
implementation(osmdroidAndroid)
}
with(Deps.AndroidX) {
implementation(lifecycleRuntimeKtx)
implementation(lifecycleViewmodelKtx)
implementation(activityCompose)
}
with(Deps.Glance) {
implementation(appwidget)
}
with(Deps.Compose) {
implementation(compiler)
implementation(ui)
implementation(uiGraphics)
implementation(foundationLayout)
implementation(material)
implementation(navigation)
implementation(coilCompose)
implementation(accompanistNavigationAnimation)
implementation(uiTooling)
}
with(Deps.Koin) {
implementation(core)
implementation(android)
implementation(compose)
testImplementation(test)
testImplementation(testJUnit4)
}
with(Deps.Test) {
testImplementation(junit)
androidTestImplementation(androidXTestJUnit)
testImplementation(testCore)
testImplementation(robolectric)
testImplementation(mockito)
// Compose testing dependencies
androidTestImplementation(composeUiTest)
androidTestImplementation(composeUiTestJUnit)
debugImplementation(composeUiTestManifest)
}
implementation(project(":common"))
}

View file

@ -1,21 +0,0 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View file

@ -1,29 +0,0 @@
package com.surrus.peopleinspace
import com.surrus.common.remote.Assignment
import com.surrus.common.remote.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"))
val issPosition = IssPosition(53.2743394, -9.0514163)
override fun fetchPeopleAsFlow(): Flow<List<Assignment>> {
return flowOf(peopleList)
}
override fun pollISSPosition(): Flow<IssPosition> {
return flowOf(issPosition)
}
override suspend fun fetchPeople(): List<Assignment> {
return emptyList()
}
override suspend fun fetchAndStorePeople() {
}
}

View file

@ -1,50 +0,0 @@
package com.surrus.peopleinspace
import androidx.compose.ui.test.*
import androidx.compose.ui.test.junit4.createComposeRule
import com.surrus.peopleinspace.ui.*
import org.junit.Rule
import org.junit.Test
class PeopleInSpaceTest {
@get:Rule
val composeTestRule = createComposeRule()
private val peopleInSpaceRepository = PeopleInSpaceRepositoryFake()
private val peopleInSpaceViewModel = PeopleInSpaceViewModel(peopleInSpaceRepository)
@Test
fun testPeopleListScreen() {
composeTestRule.setContent {
PersonListScreen(personSelected = {},
peopleInSpaceViewModel = peopleInSpaceViewModel
)
}
val peopleList = peopleInSpaceRepository.peopleList
val personListNode = composeTestRule.onNodeWithTag(PersonListTag)
personListNode.assertIsDisplayed()
.onChildren().assertCountEquals(peopleList.size)
peopleList.forEachIndexed { index, person ->
val rowNode = personListNode.onChildAt(index)
rowNode.assertTextContains(person.name)
rowNode.assertTextContains(person.craft)
}
}
@Test
fun testISSPositionScreen() {
composeTestRule.setContent {
ISSPositionScreen(peopleInSpaceViewModel = peopleInSpaceViewModel)
}
composeTestRule.onNodeWithTag(ISSPositionMapTag).assertIsDisplayed()
val expectedIssPosition = peopleInSpaceRepository.issPosition
composeTestRule
.onNode(SemanticsMatcher.expectValue(IssPositionKey, expectedIssPosition))
.assertExists()
}
}

View file

@ -1,53 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.surrus.peopleinspace">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".PeopleInSpaceApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:usesCleartextTraffic="true"
android:theme="@style/Theme.PeopleInSpace">
<activity
android:name=".ui.MainActivity"
android:exported="true"
android:theme="@style/Theme.PeopleInSpace.NoActionBar"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name="com.surrus.peopleinspace.glance.PeopleInSpaceWidgetReceiver"
android:label="People in Space"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_info" />
</receiver>
<receiver android:name="com.surrus.peopleinspace.glance.ISSMapWidgetReceiver"
android:label="ISS Map"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/iss_widget_info" />
</receiver>
</application>
</manifest>

View file

@ -1,33 +0,0 @@
package com.surrus.peopleinspace
import android.app.Application
import co.touchlab.kermit.Logger
import com.surrus.common.di.initKoin
import com.surrus.peopleinspace.di.appModule
import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger
import org.koin.core.logger.Level
import org.osmdroid.config.Configuration
import java.io.File
class PeopleInSpaceApplication : Application() {
override fun onCreate() {
super.onCreate()
// needed for osmandroid
Configuration.getInstance().userAgentValue = BuildConfig.APPLICATION_ID
Configuration.getInstance().osmdroidTileCache = File(cacheDir, "osm").also {
it.mkdir()
}
initKoin {
// https://github.com/InsertKoinIO/koin/issues/1188
androidLogger(if (BuildConfig.DEBUG) Level.ERROR else Level.NONE)
androidContext(this@PeopleInSpaceApplication)
modules(appModule)
}
Logger.d { "PeopleInSpaceApplication" }
}
}

View file

@ -1,9 +0,0 @@
package com.surrus.peopleinspace.di
import com.surrus.peopleinspace.ui.PeopleInSpaceViewModel
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module
val appModule = module {
viewModel { PeopleInSpaceViewModel(get()) }
}

View file

@ -1,106 +0,0 @@
package com.surrus.peopleinspace.glance
import android.graphics.Bitmap
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.ExperimentalUnitApi
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
import androidx.glance.GlanceModifier
import androidx.glance.Image
import androidx.glance.ImageProvider
import androidx.glance.action.actionStartActivity
import androidx.glance.action.clickable
import androidx.glance.background
import androidx.glance.layout.Box
import androidx.glance.layout.fillMaxSize
import androidx.glance.text.Text
import androidx.glance.text.TextStyle
import androidx.glance.unit.ColorProvider
import com.surrus.common.repository.PeopleInSpaceRepositoryInterface
import com.surrus.peopleinspace.R
import com.surrus.peopleinspace.glance.util.BaseGlanceAppWidget
import com.surrus.peopleinspace.ui.MainActivity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.core.component.inject
import org.osmdroid.tileprovider.MapTileProviderBasic
import org.osmdroid.tileprovider.tilesource.TileSourceFactory
import org.osmdroid.util.GeoPoint
import org.osmdroid.views.Projection
import org.osmdroid.views.drawing.MapSnapshot
import org.osmdroid.views.overlay.IconOverlay
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
class ISSMapWidget : BaseGlanceAppWidget<ISSMapWidget.Data>() {
val repository: PeopleInSpaceRepositoryInterface by inject()
data class Data(val bitmap: Bitmap?)
override suspend fun loadData(): Data {
val issPosition = repository.pollISSPosition().first()
val issPositionPoint = GeoPoint(issPosition.latitude, issPosition.longitude)
val stationMarker = IconOverlay(
issPositionPoint,
context.resources.getDrawable(R.drawable.ic_iss, context.theme)
)
val source = TileSourceFactory.DEFAULT_TILE_SOURCE
val projection = Projection(5.0, 480, 240, issPositionPoint, 0f, true, false, 0, 0)
val bitmap = withContext(Dispatchers.Main) {
suspendCoroutine<Bitmap> { cont ->
val mapSnapshot = MapSnapshot(
{
if (it.status == MapSnapshot.Status.CANVAS_OK) {
val bitmap = Bitmap.createBitmap(it.bitmap)
cont.resume(bitmap)
}
},
MapSnapshot.INCLUDE_FLAG_UPTODATE or MapSnapshot.INCLUDE_FLAG_SCALED,
MapTileProviderBasic(context, source, null),
listOf(stationMarker),
projection
)
launch(Dispatchers.IO) {
mapSnapshot.run()
}
}
}
return Data(bitmap)
}
@OptIn(ExperimentalUnitApi::class)
@Composable
override fun Content(data: Data?) {
Box(
modifier = GlanceModifier.background(Color.DarkGray).fillMaxSize().clickable(
actionStartActivity<MainActivity>()
)
) {
val bitmap = data?.bitmap
if (bitmap != null) {
Image(
modifier = GlanceModifier.fillMaxSize(),
provider = ImageProvider(bitmap),
contentDescription = "ISS Location"
)
} else {
Text(
"Loading ISS Map...",
style = TextStyle(
color = ColorProvider(Color.White),
fontSize = TextUnit(20f, TextUnitType.Sp)
)
)
}
}
}
}

View file

@ -1,7 +0,0 @@
package com.surrus.peopleinspace.glance
import com.surrus.peopleinspace.glance.util.BaseGlanceAppWidgetReceiver
class ISSMapWidgetReceiver : BaseGlanceAppWidgetReceiver<ISSMapWidget>() {
override fun createWidget(): ISSMapWidget = ISSMapWidget()
}

View file

@ -1,65 +0,0 @@
package com.surrus.peopleinspace.glance
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.ExperimentalUnitApi
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.dp
import androidx.glance.GlanceModifier
import androidx.glance.appwidget.lazy.LazyColumn
import androidx.glance.background
import androidx.glance.layout.Row
import androidx.glance.layout.padding
import androidx.glance.text.FontWeight
import androidx.glance.text.Text
import androidx.glance.text.TextStyle
import androidx.glance.unit.ColorProvider
import com.surrus.common.remote.Assignment
import com.surrus.common.repository.PeopleInSpaceRepositoryInterface
import com.surrus.peopleinspace.glance.util.BaseGlanceAppWidget
import kotlinx.coroutines.flow.first
import org.koin.core.component.inject
class PeopleInSpaceWidget : BaseGlanceAppWidget<PeopleInSpaceWidget.Data>() {
val repository: PeopleInSpaceRepositoryInterface by inject()
data class Data(val people: List<Assignment>)
override suspend fun loadData(): Data {
return Data(repository.fetchPeopleAsFlow().first())
}
@OptIn(ExperimentalUnitApi::class)
@Composable
override fun Content(data: Data?) {
LazyColumn(
modifier = GlanceModifier.background(Color.DarkGray).padding(horizontal = 8.dp)
) {
item {
Text(
modifier = GlanceModifier.padding(bottom = 8.dp),
text = "People in Space",
style = TextStyle(
color = ColorProvider(Color.White),
fontSize = TextUnit(12f, TextUnitType.Sp),
fontWeight = FontWeight.Bold
)
)
}
if (data != null) {
items(data.people.size) {
Row {
Text(
text = data.people[it].name,
style = TextStyle(
color = ColorProvider(Color.White),
fontSize = TextUnit(10f, TextUnitType.Sp)
)
)
}
}
}
}
}
}

View file

@ -1,7 +0,0 @@
package com.surrus.peopleinspace.glance
import com.surrus.peopleinspace.glance.util.BaseGlanceAppWidgetReceiver
class PeopleInSpaceWidgetReceiver : BaseGlanceAppWidgetReceiver<PeopleInSpaceWidget>() {
override fun createWidget(): PeopleInSpaceWidget = PeopleInSpaceWidget()
}

View file

@ -1,54 +0,0 @@
package com.surrus.peopleinspace.glance.util
import android.content.Context
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.unit.DpSize
import androidx.glance.GlanceId
import androidx.glance.LocalGlanceId
import androidx.glance.LocalSize
import androidx.glance.appwidget.GlanceAppWidget
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
abstract class BaseGlanceAppWidget<T>(initialData: T? = null) : GlanceAppWidget(), KoinComponent {
val context: Context by inject()
var glanceId by mutableStateOf<GlanceId?>(null)
var size by mutableStateOf<DpSize?>(null)
var data by mutableStateOf<T?>(initialData)
private val coroutineScope = MainScope()
abstract suspend fun loadData(): T
fun initiateLoad() {
coroutineScope.launch {
data = loadData()
val currentGlanceId = snapshotFlow { glanceId }.filterNotNull().firstOrNull()
if (currentGlanceId != null) {
update(context, currentGlanceId)
}
}
}
@Composable
override fun Content() {
glanceId = LocalGlanceId.current
size = LocalSize.current
Content(data)
}
@Composable
abstract fun Content(data: T?)
}

View file

@ -1,17 +0,0 @@
package com.surrus.peopleinspace.glance.util
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.GlanceAppWidgetReceiver
import org.koin.core.component.KoinComponent
abstract class BaseGlanceAppWidgetReceiver<T : BaseGlanceAppWidget<*>> : GlanceAppWidgetReceiver(),
KoinComponent {
override val glanceAppWidget: GlanceAppWidget
get() {
return createWidget().apply {
this.initiateLoad()
}
}
abstract fun createWidget(): T
}

View file

@ -1,11 +0,0 @@
package com.surrus.peopleinspace.ui
import androidx.compose.ui.graphics.Color
val maroon200 = Color(0xFFb73d2a)
val maroon500 = Color(0xFF800000)
val maroon700 = Color(0xFF4f0000)
val teal200 = Color(0xFF03DAC5)
val lowAvailabilityColor = Color(0xFFFF8C00)
val highAvailabilityColor = Color(0xFF008000)

View file

@ -1,74 +0,0 @@
package com.surrus.peopleinspace.ui
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.platform.testTag
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.peopleinspace.util.collectAsStateWithLifecycle
import org.koin.androidx.compose.getViewModel
import org.osmdroid.util.GeoPoint
import org.osmdroid.views.CustomZoomButtonsController
import org.osmdroid.views.MapView
import org.osmdroid.views.overlay.Marker
const val ISSPositionMapTag = "ISSPositionMap"
val IssPositionKey = SemanticsPropertyKey<IssPosition>("IssPosition")
var SemanticsPropertyReceiver.observedIssPosition by IssPositionKey
@Composable
fun ISSPositionScreen(peopleInSpaceViewModel: PeopleInSpaceViewModel = getViewModel()) {
val lifecycleOwner = LocalLifecycleOwner.current
val issPosition by peopleInSpaceViewModel.issPosition
.collectAsStateWithLifecycle(lifecycleOwner, IssPosition(0.0, 0.0))
val context = LocalContext.current
val map = remember { MapView(context) }
Scaffold(
topBar = {
TopAppBar(title = { Text("ISS Position") })
}
) {
Column {
Box {
AndroidView({ map }, modifier = Modifier
.fillMaxHeight().testTag(ISSPositionMapTag)
.semantics { observedIssPosition = issPosition }
){ map ->
map.zoomController.setVisibility(CustomZoomButtonsController.Visibility.SHOW_AND_FADEOUT)
map.setMultiTouchControls(true)
val mapController = map.controller
mapController.setZoom(5.0)
val issPositionPoint = GeoPoint(issPosition.latitude, issPosition.longitude)
mapController.setCenter(issPositionPoint)
map.overlays.clear()
val stationMarker = Marker(map)
stationMarker.position = issPositionPoint
stationMarker.title = "ISS"
map.overlays.add(stationMarker)
}
}
}
}
}

View file

@ -1,142 +0,0 @@
package com.surrus.peopleinspace.ui
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.*
import androidx.compose.animation.core.tween
import androidx.compose.material.BottomNavigation
import androidx.compose.material.BottomNavigationItem
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.LocationOn
import androidx.compose.material.icons.filled.Person
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
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
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MainLayout()
}
}
}
sealed class Screen(val title: String) {
object PersonList : Screen("PersonList")
object PersonDetails : Screen("PersonDetails")
object ISSPositionScreen : Screen("ISSPosition")
}
data class BottomNavigationitem(
val route: String,
val icon: ImageVector,
val iconContentDescription: String
)
val bottomNavigationItems = listOf(
BottomNavigationitem(
Screen.PersonList.title,
Icons.Default.Person,
"People"
),
BottomNavigationitem(
Screen.ISSPositionScreen.title,
Icons.Filled.LocationOn,
"ISS Position"
)
)
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun MainLayout() {
val navController = rememberAnimatedNavController()
PeopleInSpaceTheme {
Scaffold(
bottomBar = {
BottomNavigation {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
bottomNavigationItems.forEach { bottomNavigationitem ->
BottomNavigationItem(
icon = {
Icon(
bottomNavigationitem.icon,
contentDescription = bottomNavigationitem.iconContentDescription
)
},
selected = currentRoute == bottomNavigationitem.route,
onClick = {
navController.navigate(bottomNavigationitem.route) {
popUpTo(navController.graph.id)
launchSingleTop = true
}
}
)
}
}
}
) { paddingValues ->
AnimatedNavHost(navController, startDestination = Screen.PersonList.title) {
composable(
route = Screen.PersonList.title,
exitTransition = {
slideOutHorizontally() +
fadeOut(animationSpec = tween(1000))
},
popEnterTransition = {
slideInHorizontally()
}
) {
PersonListScreen(
paddingValues = paddingValues,
personSelected = {
navController.navigate(Screen.PersonDetails.title + "/${it.name}")
}
)
}
composable(
route = Screen.PersonDetails.title + "/{person}",
enterTransition = {
slideInHorizontally() +
fadeIn(animationSpec = tween(1000))
},
popExitTransition = {
slideOutHorizontally()
}
) { backStackEntry ->
PersonDetailsScreen(
backStackEntry.arguments?.get("person") as String,
popBack = { navController.popBackStack() }
)
}
composable(Screen.ISSPositionScreen.title) {
ISSPositionScreen()
}
}
}
}
}
@Preview
@Composable
fun DefaultPreview(@PreviewParameter(PersonProvider::class) person: Assignment) {
MaterialTheme {
PersonView(person, personSelected = {})
}
}

View file

@ -1,22 +0,0 @@
package com.surrus.peopleinspace.ui
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.surrus.common.remote.Assignment
import com.surrus.common.repository.PeopleInSpaceRepositoryInterface
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.stateIn
class PeopleInSpaceViewModel(
private val peopleInSpaceRepository: PeopleInSpaceRepositoryInterface
) : ViewModel() {
val peopleInSpace = peopleInSpaceRepository.fetchPeopleAsFlow()
.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
val issPosition = peopleInSpaceRepository.pollISSPosition()
fun getPerson(personName: String): Assignment? {
return peopleInSpace.value.find { it.name == personName }
}
}

View file

@ -1,11 +0,0 @@
package com.surrus.peopleinspace.ui
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.surrus.common.remote.Assignment
class PersonProvider : CollectionPreviewParameterProvider<Assignment>(
listOf(
Assignment("ISS", "Chris Cassidy"),
Assignment("ISS", "Anatoli Ivanishin")
)
)

View file

@ -1,56 +0,0 @@
package com.surrus.peopleinspace.ui
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import coil.compose.rememberImagePainter
import org.koin.androidx.compose.getViewModel
@Composable
fun PersonDetailsScreen(personName: String, popBack: () -> Unit) {
val peopleInSpaceViewModel = getViewModel<PeopleInSpaceViewModel>()
Scaffold(
topBar = {
TopAppBar(
title = { Text(personName) },
navigationIcon = {
IconButton(onClick = { popBack() }) {
Icon(Icons.Filled.ArrowBack, contentDescription = "Back")
}
}
)
}
) {
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
val person = peopleInSpaceViewModel.getPerson(personName)
person?.let {
Text(person.name, style = MaterialTheme.typography.h4)
Spacer(modifier = Modifier.size(12.dp))
val imageUrl = person.personImageUrl ?: ""
if (imageUrl.isNotEmpty()) {
Image(
painter = rememberImagePainter(imageUrl),
modifier = Modifier.size(240.dp), contentDescription = person.name
)
}
Spacer(modifier = Modifier.size(24.dp))
val bio = person.personBio ?: ""
Text(bio, style = MaterialTheme.typography.body1)
}
}
}
}

View file

@ -1,73 +0,0 @@
package com.surrus.peopleinspace.ui
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.testTag
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 org.koin.androidx.compose.getViewModel
const val PersonListTag = "PersonList"
@Composable
fun PersonListScreen(paddingValues: PaddingValues = PaddingValues(),
personSelected: (person: Assignment) -> Unit,
peopleInSpaceViewModel: PeopleInSpaceViewModel = getViewModel()
) {
val peopleState = peopleInSpaceViewModel.peopleInSpace.collectAsState()
Scaffold(
topBar = {
TopAppBar(title = { Text("People In Space") })
}
) {
LazyColumn(contentPadding = paddingValues, modifier = Modifier.testTag(PersonListTag)) {
items(peopleState.value) { person ->
PersonView(person, personSelected)
}
}
}
}
@Composable
fun PersonView(person: Assignment, personSelected: (person: Assignment) -> Unit) {
Row(modifier = Modifier
.fillMaxWidth()
.clickable(onClick = { personSelected(person) })
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
val personImageUrl = person.personImageUrl ?: ""
if (personImageUrl.isNotEmpty()) {
Image(
painter = rememberImagePainter(personImageUrl),
modifier = Modifier.size(60.dp), contentDescription = person.name
)
} else {
Spacer(modifier = Modifier.size(60.dp))
}
Spacer(modifier = Modifier.size(12.dp))
Column {
Text(text = person.name, style = TextStyle(fontSize = 20.sp))
Text(text = person.craft, style = TextStyle(color = Color.DarkGray, fontSize = 14.sp))
}
}
}

View file

@ -1,36 +0,0 @@
package com.surrus.peopleinspace.ui
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material.MaterialTheme
import androidx.compose.material.darkColors
import androidx.compose.material.lightColors
import androidx.compose.runtime.Composable
private val DarkColorPalette = darkColors(
primary = maroon200,
primaryVariant = maroon700,
secondary = teal200
)
private val LightColorPalette = lightColors(
primary = maroon500,
primaryVariant = maroon700,
secondary = teal200
)
@Composable
fun PeopleInSpaceTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable() () -> Unit
) {
val colors = if (darkTheme) {
DarkColorPalette
} else {
LightColorPalette
}
MaterialTheme(
colors = colors,
content = content
)
}

View file

@ -1,21 +0,0 @@
package com.surrus.peopleinspace.util
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.flowWithLifecycle
import kotlinx.coroutines.flow.Flow
@Composable
fun <T> Flow<T>.collectAsStateWithLifecycle(
owner: LifecycleOwner,
initial: T,
minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
): State<T> {
return remember(this, owner) {
flowWithLifecycle(owner.lifecycle, minActiveState)
}.collectAsState(initial)
}

View file

@ -1,34 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeWidth="1"
android:strokeColor="#00000000">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View file

@ -1,263 +0,0 @@
<vector android:height="50.6dp" android:viewportHeight="847"
android:viewportWidth="385" android:width="23dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#BF9586" android:pathData="M88.188,10.5c0,9.583 0,19.167 0,28.75c29.532,-2.404 59.044,-4.063 88.626,-5.874c21.633,-1.324 67.814,-12.872 82.985,9.115c10.227,14.821 9.438,27.326 10.052,45.106c0.222,6.446 1.379,10.841 4.587,16.528c3.953,7.008 3.604,14.493 3.875,22.438c0.521,15.255 1.72,30.486 2.303,45.75c0.208,5.47 -1.598,16.568 0.822,21.563c2.621,5.41 12.739,8.2 17.65,11.33c6.127,3.905 9.348,10.715 13.413,16.671c3.825,5.605 9.62,10.142 13.437,15.999c7.843,12.035 11.888,26.538 18.242,39.403c13.548,27.425 22.34,52.58 16.52,83.551c-3.112,16.566 -10.924,25.477 -25.137,34.796c-5.239,3.435 -11.405,5.852 -17,8.707c-11.009,5.618 -10.466,6.807 -13.011,19.07c-3.189,15.369 -3.989,29.261 -3.989,44.959c0,12.234 8.642,27.777 12.46,39.571c9.394,29.011 16.033,59.139 22.958,88.842c3.271,14.033 3.362,29.792 4.534,44.159c1.112,13.652 7.868,26.241 9.832,39.771c1.001,6.899 3.091,14.62 3.091,21.608c0,8.577 -4.215,14.454 -3.375,22.436c1.776,16.891 9.226,27.403 19.287,40.269c5.157,6.595 11.396,30.684 7.088,38.608c-4.814,8.856 -30.952,14.176 -39.981,14.711c-8.112,0.48 -33.541,5.416 -33.901,-9.797c-0.282,-11.894 1.398,-25.152 -3.806,-36.204c-3.239,-6.878 -7.119,-13.728 -9.812,-20.837c-0.796,-2.102 0.088,-5.656 -1.984,-6.881c-3.258,-1.925 -5.235,-3.384 -7.789,-6.138c-9.567,-10.316 -14.569,-22.602 -19.337,-35.637c-4.508,-12.326 -13.262,-20.086 -13.885,-33.96c-0.559,-12.43 -10.433,-27.2 -15.835,-38.135c-5.704,-11.543 -9.188,-23.077 -11.713,-35.598c-1.412,-7.006 -3.737,-36.459 -11.332,-37.025c-1.829,15.894 -9.9,28.164 -12.611,43.704c-2.472,14.173 -3.737,27.892 -8.165,41.726c-2.421,7.563 0.887,11.288 0.273,18.595c-0.583,6.955 -0.649,14.153 -1.935,21.021c-2.729,14.577 -2.847,34.049 -9.001,47.491c-2.575,5.626 -4.624,14.581 -9.435,18.583c-9.454,7.864 -10.506,7.491 -9.125,19.567c0.862,7.539 1.563,15.033 2.141,22.601c0.697,9.13 -3.316,12.024 -9.445,18.65c-4.756,5.142 -7.461,12.823 -10.742,18.965c-3.082,5.769 -9.683,8.14 -15.578,10.221c-15.008,5.297 -28.782,5.051 -43.969,1.501c-4.272,-0.998 -5.2,-13.176 -5.532,-16.374c-0.886,-8.555 3.324,-13.692 7.392,-20.966c6.636,-11.864 10.248,-26.148 10.784,-39.561c0.146,-3.629 3.242,-15.388 1.947,-17.978c-3.64,-7.282 -5.106,-14.659 -5.747,-22.873c-1.13,-14.474 3,-28.828 3,-43.437c0,-14.442 2.25,-28.59 2.25,-42.97c0,-7.57 1.08,-15.837 -0.792,-23.217c-1.78,-7.019 -3.084,-14.493 -3.084,-21.755c0,-7.729 -0.71,-15.801 0,-23.495c0.62,-6.719 3.626,-13.327 3.626,-20.093c0,-15.694 0.143,-31.326 0.795,-47.008c0.614,-14.763 -6.546,-27.976 -5.171,-43.399c-3.574,4.958 -11.04,11.074 -8.795,17.115c2.02,5.438 -0.339,14.252 -1.64,19.95c-1.521,6.66 -4.761,14.174 -3.457,20.984c1.647,8.603 3.966,17.055 3.767,25.823c-0.166,7.296 -7.125,12.766 -7.125,19.241c0,8.876 -1.774,15.393 -4.259,23.982c-3.871,13.38 -19.129,21.46 -31.678,13.653c-13.762,-8.562 -14.932,-27.48 -18.42,-41.704c-1.693,-6.903 -2.893,-12.319 -2.893,-19.389c0,-6.342 5.089,-13.196 1.981,-19.604c-6.162,-12.703 -4.996,-31.832 -4.763,-45.896c0.1,-6.003 3.176,-11.979 3.97,-18.035c0.935,-7.132 0.442,-14.378 1.375,-21.479c1.939,-14.755 5.937,-29.42 6.063,-44.394c0.128,-15.263 -2.336,-28.45 1.25,-43.688c3.438,-14.61 4.556,-29.692 7.874,-44.312c1.698,-7.481 4.188,-14.859 6.269,-22.245c1.666,-5.911 7.521,-12.521 7.144,-18.688c-1.003,-16.431 -1.937,-32.863 -2.843,-49.299c-0.348,-6.318 2.657,-12.763 4.863,-18.512c2.016,-5.254 7.648,-10.292 7.314,-15.942c-0.982,-16.627 -1.959,-33.251 -3.083,-49.869c-0.932,-13.789 -5.834,-29.325 -3.97,-42.996c1.909,-13.999 10.471,-30.212 22.963,-37.397c3.423,-1.969 5.491,-2.645 9.403,-3.317c4.609,-0.792 2.816,-4.521 2.816,-8.901c0,-6.844 1.874,-16.853 -3.626,-22.209l1.126,-4.75h5L88.188,10.5z"/>
<path android:fillColor="#FFFFFF"
android:pathData="M177.313,498.377c3.891,-8.237 18.606,-4.169 26.527,-5.556c10.382,-1.819 19.325,-4.771 27.657,-11.667c7.175,-5.938 -16.429,-47.24 -19.811,-54.777c6.543,3.951 9.538,8.634 12.523,15.771c2.493,5.961 5.974,20.196 12.352,22.729c0,-10.295 -4.481,-17.948 -8.625,-27.377c3.917,-0.467 7.863,3.155 11.246,2.227c5.679,-1.559 7.76,-3.639 12.191,-7.663c7.106,-6.454 13.804,-13.243 20.563,-20.063c0.441,1.995 1.883,3.798 2.5,5.75c3.133,-1.061 6.125,-3.39 6.125,-6.873c0,4.334 -1.616,9.537 2.75,11.873c1.851,-3.701 3.917,-7.244 5,-11.25c1.517,5.222 -4.055,10.545 0,14.377c5.224,-4.229 5.066,-8.646 6.125,-15c-0.317,3.897 -3.38,15.785 0.125,17.687c5.361,2.907 0.382,15.139 -0.615,19.367c-1.517,6.43 -0.135,14.62 -0.135,21.219c0,6.054 -7.09,6.888 -12.125,8.478c1.385,-0.958 6.356,-6.029 2.5,-7.25c-1.548,-0.49 -7.915,5.095 -9.759,6.148c-3.383,1.933 -14.245,13.37 -4.429,11.163c7.908,-1.778 15.495,-5.342 23.075,-8.179c3.038,-1.137 7.907,13.503 9.362,16.617c-3.059,-1.284 -6.43,-1.805 -9.375,0c4.008,3.644 12.242,7.717 14,12.5c-5.283,-3.123 -14.578,-8.825 -20.75,-8.377c4.056,6.576 10.599,11.533 16.625,16.627c-8.388,-4.934 -23.594,-19.377 -33.188,-19.377c-3.955,0 -2.864,5.656 0.299,6.483c3.513,0.918 5.207,1.913 8.206,3.951c6.023,4.095 12.039,8.201 18.081,12.27c6.495,4.373 11.003,8.115 16.267,13.912c3.728,4.106 4.69,13.689 6.354,19.058c4.104,13.237 6.519,27.179 9.203,40.81c2.797,14.204 3.536,28.693 4.874,43.103c1.258,13.558 5.737,26.809 8.273,40.227c1.354,7.158 -2.989,6.807 -9.259,10.271c-7.018,3.879 -13.942,8.076 -21.111,11.667c-5.182,2.596 -11.945,6.9 -17.648,7.836c-4.267,0.699 -8.534,1.399 -12.802,2.099c-3.108,0.51 -2.841,-2.927 -6.05,-4.185c5.146,-7.459 1.767,-10.418 -6.125,-8c4.462,-1.58 13.732,-2.528 14.5,-7.5c-4.609,0 -9.266,-0.434 -13.765,0.616c-6.292,1.468 -6.918,-1.119 -10.505,-6.446c-6.276,-9.323 -2.648,-20.737 -7.933,-30.986c-9.508,-18.438 -19.429,-37.207 -24.644,-57.264c-2.403,-9.243 -3.474,-18.876 -5.092,-28.293c-0.976,-5.677 -0.207,-10.939 -5.304,-13.852c-2.14,-1.223 -11.521,-1.083 -11.875,-1.685c-5.846,-9.91 -11.865,-19.611 -18.103,-29.28C180.422,514.295 173.869,508.319 177.313,498.377z"
android:strokeColor="#000000" android:strokeLineCap="round"
android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#FFFFFF"
android:pathData="M107.813,452.377c0.662,7.818 7.573,-0.051 8.726,-3.574c0.799,-2.442 1.288,-4.989 2.836,-7.053c2.458,-3.276 4.482,3.172 7.312,2.627c7.851,-1.511 -3.45,-7.495 -3.354,-9.566c0.284,-6.145 0.569,-12.289 0.854,-18.434c2.78,6.672 2.48,12.478 10.5,8.623c-3.242,4.682 -1.119,10.25 0.87,14.983c3.115,7.413 2.651,11.308 11.006,11.644c1.936,0.078 5.023,0.665 6.589,-0.56c1.792,-1.4 4.864,-5.16 7.196,-4.544c4.365,1.153 11.735,7.631 15.839,3.354c2,-2.084 3.791,-14.872 4.785,-17.856c0.68,-2.038 -8.49,-9.621 -10.066,-12.06c-1.281,-1.982 -5.233,-6.089 -5.469,-8.461c-0.143,-1.433 2.902,-5.493 3.313,-7.5c0.985,-4.813 0.791,-11.56 -2.813,-15.25c5.115,-0.759 7.511,5.146 11.219,8.021c1.634,1.267 4.62,0.256 6.583,0.077c2.212,-0.202 4.081,3.232 5.452,4.75c6.738,7.461 10.967,14.387 15.487,23.228c4.245,8.302 29.737,51.044 17.677,55.779c-10.206,4.008 -20.369,6.146 -31.418,6.895c-4.441,0.301 -11.047,-1.76 -13.882,2.82c-2.647,4.276 -4.859,8.164 -5.242,13.243c-0.809,10.721 8.768,21.343 13.965,30.005c1.933,3.221 2.885,8.118 5.118,10.839c2.93,3.568 11.071,9.678 8.541,14.593c-4.434,-5.911 -8.677,-11.821 -12.783,-17.962c-2.179,-3.259 -14.341,-20.88 -10.114,-7.29c2.198,7.066 9.06,14.296 13.09,20.451c3.501,5.346 11.229,10.92 9.308,16.928c-6.502,-9.036 -11.682,-18.474 -21,-24.627c3.358,12.636 14.217,20.889 17.375,33.5c-3.501,-4.587 -6.176,-8.608 -10.499,-12.5c-1.248,6.69 3.76,14.123 7.499,19.377c-1.96,-3.109 -4.616,-5.467 -8.125,-3.25c5.959,13.073 4.768,27.96 0.872,42.041c-1.857,6.712 -5.293,13.104 -6.872,19.832c-1.673,7.131 1.418,13.344 1.155,20.528c-0.247,6.77 -0.717,12.667 -2.028,19.276c-1.235,6.218 -1.24,21.544 -5.251,26.445c-3.897,4.762 -14.587,3.338 -20.116,4.233c-7.723,1.251 -14.852,-0.356 -21.822,-0.356c-7.143,0 -14.781,-1.926 -21.355,-4.609c-8.343,-3.406 -10.384,-2.273 -8.957,-12.268c0.641,-4.491 1.877,-4.549 0.583,-10.379c-0.837,-3.766 -0.972,-5.803 -0.521,-9.621c0.898,-7.59 0.721,-15.146 1.673,-22.76c3.626,-28.988 -3.875,-56.686 -2.924,-85.688c0.253,-7.728 3.439,-15.249 3.439,-22.974c0,-12.982 0,-25.965 0,-38.947c0,-6.902 1.742,-16.386 -1.205,-22.505c-1.918,-3.984 -1.741,-9.742 -2.387,-14.091c-0.631,-4.241 3.594,-7.54 4.092,-11.908C93.697,461.048 103.875,453.231 107.813,452.377z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#FFFFFF"
android:pathData="M132.813,333.126c-1.377,1.042 -3.415,1.537 -5,2.25c-0.477,-4.906 2.776,-7.961 3.874,-12.5c1.239,-5.12 0.422,-11.619 0.613,-16.866c0.174,-4.789 2.954,-26.351 -4.487,-26.26c0,6.742 -0.165,13.177 -0.876,19.876c-3.87,-14.32 -6.795,-35.584 -18,-46.25c-3.251,6.761 3.301,13.887 5.626,20c2.451,6.443 3.513,13.119 4.624,19.874c-1.381,-2.466 -3.023,-7.722 -5.25,-9.374c-3.273,-2.428 -5.79,-2.383 -3.766,2.44c3.456,8.235 7.302,16.302 9.642,24.934c-2.102,-2.208 -3.236,-1.818 -5.876,-2.25c-0.565,5.697 2.9,8.813 6.137,12.991c3.923,5.065 2.678,6.908 0.183,12.579c-4.49,10.2 -10.363,16.436 -12.793,27.579c-2.231,10.23 -7.96,19.427 -9.838,29.664c-2.069,11.28 -3.754,22.756 -6.446,33.963c-2.229,9.274 -6.165,18.1 -9.796,26.93c-2.084,5.068 -2.956,13.282 -8.696,13.544c-0.276,0.013 -12.788,-5.338 -13.25,-5.873c-5.348,-6.194 4.288,-25.591 -13.435,-18.857c-3.059,1.163 0.01,11.859 -2.505,12.31c-6.145,1.099 -12.29,2.198 -18.435,3.298c0,-5.784 0.638,-57.877 6.587,-57.877c9.175,0 18.76,0.362 27.495,3.414c4.817,1.684 9.912,4.216 14.918,5.213c3.249,0.647 8.238,-0.475 10.874,1.874c-3.423,-9.255 -21.051,-11.329 -29.124,-13.75c4.075,0 8.588,1.183 11.374,-2.5c-6.398,-3.075 -11.749,-2.5 -18.819,-2.5c-4.27,0 -14.445,2.682 -15.394,-0.554c-3.928,-13.395 -4.401,-27.219 -1.752,-40.956c5.139,-26.644 9.172,-52.179 17.139,-78.176c3.878,-12.655 14.55,-21.844 23.331,-31.039c4.184,-4.382 8.964,-7.804 13.773,-11.508c8.58,-6.609 9.221,-2.749 17.926,3.738c11.201,8.347 22.465,16.016 34.449,23.163c12.914,7.701 20.818,10.249 36.224,11.083c-12.943,2.275 -24.94,0.499 -33.912,11.121c-3.4,4.025 -1.588,11.855 -1.588,16.885c0,7.691 -0.261,15.433 0,23.12c0.208,6.112 0.973,12.208 1.254,18.319c0.435,9.464 3.937,12.673 9.786,20.248c1.064,1.378 3.858,0.235 4.433,1.983c1.005,3.058 1.729,6.416 3.207,9.282c3.239,6.282 4.753,12.585 7.571,19.041c-4.104,-2.364 -10.85,-11.873 -14.676,-11.873c-7.175,0 -12.04,4.398 -14.418,11.101c-1.937,5.458 -4.214,13.646 -2.613,19.308c1.266,4.478 10.759,14.278 5.457,16.092c-1.305,-4.268 -4.018,-7.016 -6.88,-10.279c-2.933,-3.344 0.063,-8.275 1.13,-12.348c-2.425,4.903 -4.489,10.206 -7.75,14.627c2.404,-14.487 4.463,-28.65 8.874,-42.627C135.533,365.693 148.464,330.283 132.813,333.126z"
android:strokeColor="#000000" android:strokeLineCap="round"
android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#FFFFFF"
android:pathData="M353.313,317.876c1.832,14.859 3.971,28.936 -0.25,43.5c-0.702,-2.944 -4.053,-7.742 -6.875,-4.5c-1.129,1.297 1.151,10.045 1.375,12c-2.174,-0.616 -4.152,0.03 -6.375,-1.126c-2.104,4.608 2.525,7.184 0,12.126c-0.68,-2.45 -2.335,-3.571 -3.625,-5.5c-4.204,5.386 -11.125,11.691 -17.5,14.373c-2.054,0.864 -4.669,-0.852 -6.398,1.311c-2.552,3.192 -1.175,6.221 -2.727,9.816c-6.025,-17.198 -6.989,4.204 -10.5,2.123c-3.62,-2.146 -4.851,-22.551 -5.579,-27.03c-1.564,-9.627 -7.46,-17.153 -12.957,-24.864c-2.509,-3.52 -5.083,-4.657 -2.065,-8.729c4.117,-5.557 -0.709,-8.31 -4.773,-12.375c11.021,-8.733 21.879,-16.624 36.75,-16.624C324.855,312.376 340.307,315.567 353.313,317.876z"
android:strokeColor="#000000" android:strokeLineCap="round"
android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#FFFFFF"
android:pathData="M191.688,268.626c0.824,-4.412 6.249,-3.299 9.875,-3.688c6.561,-0.703 8.555,-0.776 13.602,-4.946c6.745,-5.572 11.088,-13.126 15.472,-20.508c7.49,-12.61 23.45,-32.097 23.677,-46.984c11.12,4.083 21.685,7.713 32.022,13.537c9.611,5.414 13.216,11.935 20.7,19.775c7.4,7.753 13.906,16.94 18.408,26.654c2.926,6.315 -2.733,7.168 -8.166,10.033c-3.432,1.81 1.683,3.314 3.16,3.377c2.066,0.088 6.101,-3.146 8,-4.126c8.592,16.958 18.058,33.171 24.375,51.126c-9.041,-1.292 -18.043,-2.087 -27.125,-3c2.667,-0.542 5.333,-1.084 8,-1.626c-0.65,-5.397 -4.565,-3.559 -8.625,-3.124c3.603,-3.119 11.877,-5.827 8.125,-11.25c-11.156,5.758 -19.748,11.736 -32.002,13.98c-11.9,2.179 -20.854,9.082 -30.623,15.894c-1.335,-5.062 -4.372,-11.1 -1.899,-16.228c2.215,-4.592 9.656,-11.707 7.399,-16.146c-2.416,1.037 -4.528,2.822 -6,5c1.511,-6.764 11.443,-16.543 9.625,-22.5c-3.486,2.988 -6.497,5.697 -9.125,9.5c1.062,-6.274 5.02,-14.374 4.5,-20.5c-3.922,5.566 -10.846,13.542 -10.875,20.5c0,-5.667 0,-11.333 0,-17c-5.916,5.442 -4.375,10.362 -4.375,18.127c0,4.881 -5.006,9.649 -2.269,14.014c4.45,7.097 3.448,11.124 5.269,19.109c2.285,10.025 2.921,8.471 -4.625,16.624c0.096,-4.889 -0.421,-24.648 -3.931,-26.403c-6.937,-3.467 -6.933,-3.38 -7.93,-11.051c-0.732,-5.631 0.687,-9.145 -5.45,-9.651c-7.456,-0.615 -8.826,0.764 -11.097,-6.358c-1.392,-4.367 -2.84,-8.533 -6.843,-10.536C215.637,266.597 199.988,269.058 191.688,268.626z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#FFFFFF"
android:pathData="M183.438,49.5c28.987,10.7 50.989,28.876 56.738,60.335c3.149,17.238 6.86,34.485 7.762,52.041c0.603,11.734 4.246,34.477 -4.75,44c-0.281,-1.839 -1.717,-2.813 -2.25,-4.25c-7.757,8.229 -40.307,33.065 -24.875,44.624c1.245,-10.528 2.029,-12.242 9.759,-19.536c5.454,-5.147 10.141,-11.35 15.116,-16.964c-6.548,11.136 -13.096,22.271 -19.645,33.407c-2.135,3.631 -4.359,6.318 -7.203,9.496c-4.473,4.999 -5.262,3.827 -11.902,3.847c19.361,-22.924 -16.904,-7.617 -23.75,-14.124c23.412,-7.863 39.81,-17.38 54.407,-37.95c1.781,-2.51 9.767,-9.809 10.231,-12.291c1.107,-5.904 0.747,-9.426 0.603,-15.089c-0.319,-12.538 0.029,-24.979 -2.308,-37.308c-2.008,-10.596 -3.277,-23.825 -11.621,-31.613c-7.361,-6.871 -19.631,-11.975 -29.118,-16.362c-2.632,-1.217 -9.318,-2.141 -12.405,-2.576c-7.768,-1.095 -15.536,-2.189 -23.303,-3.285c-12.811,-1.806 -20.455,0.426 -31.61,6.223C145.881,71.442 160.3,57.479 183.438,49.5z"
android:strokeColor="#FFFFFF" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#FFFFFF"
android:pathData="M263.688,100.626c10.691,9.33 8.421,31.461 9.375,44.374c1.189,16.093 2.32,32.37 1.375,48.626c-6.082,-2.493 -19.98,-4.598 -20.262,-11.707c-0.351,-8.869 -0.683,-17.675 -1.499,-26.509c-1.669,-18.071 -3.048,-38.176 -8.989,-55.284C247.552,99.189 262.908,95.396 263.688,100.626z"
android:strokeColor="#000000" android:strokeLineCap="round"
android:strokeLineJoin="round" android:strokeWidth="2"/>
<path android:fillColor="#FFFFFF"
android:pathData="M258.188,57.5c6.447,5.976 4.33,20.602 4.649,28.304c0.484,11.693 -12.542,2.919 -19.649,8.822c-6.668,-16.568 -17.521,-28.595 -31.5,-39.876C227.354,53.681 243.08,52.465 258.188,57.5z"
android:strokeColor="#000000" android:strokeLineCap="round"
android:strokeLineJoin="round" android:strokeWidth="2"/>
<path android:fillColor="#FF000000"
android:pathData="M257.563,52c-7.697,-4.972 -21.434,-5.167 -30.287,-4.662c-7.394,0.421 -14.788,0.843 -22.182,1.265c-1.857,0.105 -1.174,1.945 -3.306,1.204c-3.408,-1.186 -6.816,-2.371 -10.226,-3.557c-5.022,-1.747 -9.637,-4 -14.956,-4c-3.829,0 -16.521,-2.151 -19.231,0.563c-7.443,7.456 -24.992,2.195 -31.812,11.937c-3.743,-6.065 -34.692,1.03 -42.014,1.695c-5.863,0.533 -9.539,2.182 -14.353,5.444c-2.847,1.928 -8.524,8.728 -9.759,11.987c2.96,-2.054 4.959,-5.319 7.5,-2.5c13.106,-12.117 32.913,-9.998 50,-9.126C106.819,74.13 97.212,84.725 92.369,99.497c-4.368,13.321 -8.306,26.859 -8.306,40.865c0,9.299 0,18.597 0,27.896c0,2.938 -15.077,2.649 -18.346,2.501c-7.472,-0.339 -6.03,-33.322 -6.03,-40.07c0,-29.073 -13.36,-56.9 13.241,-78.785c7.922,-6.518 29.192,-7.702 39.31,-8C127.46,43.453 142.655,42 157.871,42c17.499,0 35.412,-1.459 52.82,-3.215c10.194,-1.028 19.386,-0.614 29.639,0.095C250.125,39.558 252.475,44.219 257.563,52z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#13132D" android:pathData="M181.469,199.969c-3,-14 -19,-21 -24,-33c-8,-20 8,-40 14,-59c1.493,-5.226 -0.358,-10.452 -2.641,-15.678c-27.291,-1.158 -54.426,11.526 -67.488,37.658c-5.551,11.104 -0.515,34.702 0.201,46.712c0.804,13.501 9.675,23.861 19.556,32.79c11.491,10.383 20.909,20.316 35.873,24.058c7.745,1.936 14.34,2.294 20.688,1.397C179.28,223.531 184.563,212.349 181.469,199.969z"/>
<path android:fillColor="#363844" android:pathData="M236.417,154.423c-0.198,-12.696 0.148,-30.628 -10.512,-39.381c-12.791,-10.502 -24.972,-15.782 -40.842,-20.416c-5.345,-1.336 -10.793,-2.105 -16.236,-2.336c2.283,5.226 4.134,10.452 2.641,15.678c-6,19 -22,39 -14,59c5,12 21,19 24,33c3.095,12.38 -2.188,23.563 -3.813,34.937c7.68,-1.084 14.995,-4.011 23.531,-8.279c11.265,-5.632 23.966,-17.232 29.502,-28.776C237.611,183.419 236.664,170.254 236.417,154.423z"/>
<path android:fillColor="#FF000000"
android:pathData="M85.688,183.376c0.359,6.385 0.815,12.622 1.616,18.961c1.215,9.622 4.201,14.072 -3.203,20.291c-10.597,8.901 -19.846,19.709 -30.538,28.248c-1.719,-15.686 -5.664,-36.644 -0.699,-51.879c2.588,-7.943 5.519,-17.118 13.823,-20.871c2.048,-0.926 7.945,-1.376 10.375,-1.376C84.563,176.75 84.43,175.962 85.688,183.376z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#FFFFFF"
android:pathData="M255.063,336.5c-12.07,7.293 -20.179,20.825 -33.509,26.438c-9.586,4.036 -17.755,24.561 -17.616,33.811c-7.695,-11.629 -3.978,-14.751 -0.375,-27.373c2.067,-7.245 0.225,-7.114 7.995,-8.032c7.362,-0.87 7.504,-1.348 12.692,-5.156c6.993,-5.132 7.232,-16.739 6.968,-24.278c-0.153,-4.39 -1.995,-12.023 -0.093,-16.097c1.298,-2.78 9.348,-4.055 12.063,-6.813C244.295,323.301 243.141,328.274 255.063,336.5z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#FFFFFF"
android:pathData="M132.438,338.376c8.165,11.792 -3.332,31.704 -6.647,43.722c-4.535,16.438 -6.791,31.377 -8.853,48.401c0,-0.917 0,-1.833 0,-2.75c-5.878,2.924 -1.167,12.209 -5.435,15.963c-4.354,3.83 -10.554,3.804 -14.439,8.664c3.046,-3.877 12.355,-10.994 6.624,-15.5c-3.233,2.685 -6.736,5.103 -9.124,8.623c-0.542,-5.344 7.121,-12.93 10.048,-17.321c1.284,-1.926 4.568,-5.782 1.077,-7.052c-2.894,-1.052 -6.337,5.749 -8.125,8c-0.058,-4.182 12.552,-20.025 3.374,-17.127c2.589,-3.02 5.354,-5.36 8.063,-8.062c3.381,-3.373 -4.685,-3.078 -6.937,-0.562c1.932,-5.644 9.653,-8.011 11.624,-13.438c2.667,-7.343 -8.53,0.596 -10.5,2.438c2.005,-1.845 9.974,-6.61 10.75,-8.877c1.919,-5.603 -4.753,-4.012 -7.5,-1.123c-0.665,-6.499 8.108,-4.54 11.126,-9.127c5.165,-7.85 -3.121,-3.91 -6.876,-2.5c0.385,-3.74 19.824,-21.497 10.126,-20.499C123.098,347.333 130.067,336.511 132.438,338.376z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#FFFFFF"
android:pathData="M101.813,196.75c10.273,12.271 19.525,22.736 31.922,32.786c9.674,7.842 30.274,15.593 42.702,14.214c-7.783,0.819 -15.567,1.639 -23.35,2.458c-1.069,0.112 -1.095,7.995 -1.209,9.301c-0.166,1.898 4.905,2.894 6.309,3.117c12.667,2.01 29.866,-1.376 42.875,-1.376c-11.194,1.023 -22.192,2.31 -33.427,2.034c-5.077,-0.125 -9.909,0.582 -14.424,-1.769c-2.445,-1.273 -15.292,-6.039 -15.433,-8.66c-0.318,-5.904 1.103,-9.957 -4.026,-13.054c-5.556,-3.354 -10.113,-6.211 -14.767,-10.669C109.949,216.475 101.813,209.615 101.813,196.75z"
android:strokeColor="#FFFFFF" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#FF000000" android:pathData="M97.563,190.876c0,6.226 0,12.453 0,18.679c0,3.119 9.819,11.409 11.679,13.47c3.544,3.928 8.143,7.421 12.445,10.476c7.465,5.299 10.569,4.526 11.376,13.876c-10.746,-6.686 -19.382,-12.845 -28.601,-21.472c-3.767,-3.524 -6.993,-6.896 -10.255,-10.891c-2.863,-3.506 -2.014,-13.691 -2.523,-18.145c-2.441,-21.336 -0.747,-42.723 -0.747,-63.674c0,-23.747 10.692,-45.102 25.614,-62.82c15.217,-18.069 38.202,-21.749 60.512,-21.749c-14.345,4.632 -25.385,13.759 -35.847,24.221c-5.155,5.155 -8.588,14.156 -11.946,20.523c-2.347,4.448 -12.167,9.425 -16.225,13.381c-6.28,6.124 -10.519,11.005 -15.108,18.439c-4.029,6.525 -3.124,11.899 -3.124,19.621C94.813,160.476 95.864,175.327 97.563,190.876z"/>
<path android:fillColor="#A89187"
android:pathData="M85.688,247.376c-2.899,3.708 -5.82,-0.186 -9.124,-2.25C79.075,240.626 83.718,244.005 85.688,247.376z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#A89187"
android:pathData="M302.938,246.5c2.284,4.423 4.454,8.922 6.949,13.231c0.517,0.893 9.24,14.781 4.176,13.645c-2.995,-0.672 -15.817,-25.346 -15.25,-29.876C300.852,243.487 301.84,244.675 302.938,246.5z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#A89187"
android:pathData="M78.813,252.376c1.833,2.292 3.667,4.583 5.5,6.874c-0.708,-0.667 -1.417,-1.333 -2.126,-2c-4.151,3.751 -9.45,-4.85 -13.874,-6.874C70.348,245.576 76.249,249.985 78.813,252.376z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#FF000000"
android:pathData="M201.938,249.5c-2.667,0.292 -5.333,0.584 -8,0.876C196.566,249.066 199.438,248.129 201.938,249.5z"
android:strokeColor="#FF9100" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#FF000000"
android:pathData="M185.313,251.25c-2.837,-0.36 -5.645,-0.811 -8.5,-0.874c4.716,-0.166 9.405,-0.25 14.124,-0.25C189.066,250.5 187.196,250.945 185.313,251.25z"
android:strokeColor="#FF0000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#A89187"
android:pathData="M74.938,258.626c4.221,3.685 9.273,6.971 11.626,12.25c-9.548,-1.025 -15.959,-13.899 -26.626,-15C62.749,249.834 71.068,256.304 74.938,258.626z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#A89187"
android:pathData="M294.063,283.626c-1.86,4.494 -5.976,3.954 -3.433,-0.832c3.575,-6.73 5.245,-7.908 12.058,-11.418C301.957,276.394 297.258,279.967 294.063,283.626z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#FF000000"
android:pathData="M206.438,277.25c-6.504,5.153 1.116,12.832 6.875,13c11.788,0.344 5.397,-5.984 7.125,-13.874c7.675,9.903 3.375,36.119 3.375,48.148c0,11.538 -0.197,16.738 -5.379,26.899c-1.858,3.645 -7.438,1.953 -11.219,1.953c-3.793,0 -4.625,-3.119 -6.152,-6.376c3.936,-0.354 7.872,-0.708 11.808,-1.062c2.611,-0.234 3.362,-6.438 0.237,-6.438c-8.761,0 -17.635,-0.435 -26.383,0.018c-8.762,0.453 -17.528,0.85 -26.287,1.358c-2.444,0.142 -8.262,-0.793 -9.374,2.25c-1.53,4.187 3.642,5.674 6.374,4.374c-0.941,7.436 -6.627,2.822 -9.124,-1.624c-3.179,-5.662 -1.998,-14.208 -2.348,-20.46c-0.718,-12.808 -1.881,-25.226 -1.044,-38.017c0.295,-4.513 -0.415,-10.647 6.014,-11.055c5.992,-0.38 12.11,-1.197 18.114,-1.077c14.296,0.287 28.592,0.573 42.889,0.859C210.111,276.5 208.28,276.973 206.438,277.25z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#A89187"
android:pathData="M330.063,281.626c-7.801,2.065 -14.775,6.5 -22.125,8.624C306.098,282.555 328.329,270.151 330.063,281.626z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#A89187"
android:pathData="M187.063,287.75c0.456,9.423 1.028,18.781 1.826,28.181c0.325,3.843 -18.59,5.104 -20.452,3.319c-3.411,-3.269 -1.124,-23.851 -1.124,-29.52C167.313,283.207 182.992,285.344 187.063,287.75z"
android:strokeColor="#A89187" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#00FFFF" android:pathData="M183.938,313.25c-3.667,0.25 -7.333,0.5 -11,0.75c-0.324,-7.449 -0.861,-14.917 -0.874,-22.374c5.086,0.236 10.624,-1.876 10.926,3.801C183.306,301.368 183.621,307.31 183.938,313.25z"/>
<path android:fillColor="#13007C"
android:pathData="M240.438,295.75c-4.033,1.78 -9.745,4.139 -8.875,9.376c-0.438,-1.813 -3.396,-8.88 -0.977,-9.669C233.794,294.412 239.348,291.82 240.438,295.75z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#A89187"
android:pathData="M160.438,305.126c2.341,5.195 1.194,12.482 1.362,18.151c0.188,6.352 4.15,14.599 -5.195,14.599c-6.529,0 -6.112,-30.577 -1.167,-35.25C156.868,304.685 158.271,304.126 160.438,305.126z"
android:strokeColor="#A89187" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#A89187"
android:pathData="M100.938,314.626c4.542,4.25 9.084,8.5 13.626,12.75c-2.396,-2.428 -4.744,-0.617 -7.5,-2c-2.879,-1.445 -5.72,-5.646 -8.016,-7.911c-3.885,-3.834 -16.117,-7.539 -15.234,-13.339C90.469,304.522 95.922,310.814 100.938,314.626z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#FF000000"
android:pathData="M240.438,308.75c-0.458,0.375 -0.917,0.75 -1.375,1.126c-0.842,-1.17 -1.16,-1.971 -1.125,-3.376c1.125,0 2.25,0 3.375,0C241.021,307.25 240.729,308 240.438,308.75z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#6CFF00" android:pathData="M157.438,334.25c-5.526,-4.073 -2.617,-19.78 -2.75,-26.25C159.959,307.818 161.703,330.906 157.438,334.25z"/>
<path android:fillColor="#FF000000"
android:pathData="M250.063,322.376c-1.535,-2.114 -1.201,-4.872 -0.5,-7.5C249.899,317.364 250.067,319.87 250.063,322.376z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#A89187"
android:pathData="M313.188,333.126c0.198,1.239 0,2.615 0,3.874c-2.983,-2.688 -17.137,-11.68 -18,-15.25C293.536,314.919 312.031,331.788 313.188,333.126z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#A89187"
android:pathData="M64.438,330.126c-2.18,9.125 -2.329,27.813 -7.75,34.874c-3.957,-3.67 1.789,-21.867 2.607,-27.301c0.565,-3.756 0.688,-11.658 2.893,-14.573C67.064,316.678 64.911,327.942 64.438,330.126z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#A89187"
android:pathData="M189.813,323.75c0.578,5.492 2.899,8.989 -3.057,9.369c-1.688,0.108 -16.451,1.859 -16.735,0.706c-0.962,-3.899 -4.211,-12.347 2.96,-11.983C178.031,322.098 185.606,320.915 189.813,323.75z"
android:strokeColor="#A89187" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#00000000"
android:pathData="M173.438,328.75c3.06,-2.572 8.379,-1.214 12.5,-1.124C181.771,328.001 177.604,328.376 173.438,328.75z"
android:strokeColor="#FF0000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#A89187"
android:pathData="M103.688,337.25c3.458,5.849 -5.57,2.727 -5.25,-1.624C100.19,335.818 102.189,336.276 103.688,337.25z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#FFFF00" android:pathData="M259.563,348.376c7.59,5.087 16.046,14.193 19.531,22.644c1.269,3.077 9.177,34.404 0.094,29.356c-5.037,-2.8 0.027,-21.725 -4.438,-27.534c-7.332,-9.54 -14.344,-15.845 -23.813,-23.342C253.541,344.552 255.862,344.098 259.563,348.376z"/>
<path android:fillColor="#13007C"
android:pathData="M200.563,358.876c-2.454,8.548 -5.058,15.983 -8.875,24c-2.937,-9.811 -17.504,-10.767 -19.624,0c-4.822,-5.033 -5.566,-10.553 -8.565,-16.639c-5.107,-10.366 -2.742,-7.805 1.627,-17.424c1.242,-2.734 16.699,-1.563 20.495,-1.563C196.289,347.25 195.563,349.386 200.563,358.876z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#FF000000"
android:pathData="M277.563,336.5c-5.898,6.193 -4.518,6.5 0,13.75c3.227,5.179 7.723,10.085 9.848,15.829c4.428,11.968 8.423,23.756 6.652,36.547c-7.801,1.036 -5.098,-9.271 -5.234,-14.393c-0.167,-6.297 -4.068,-13.798 -5.918,-19.82c-3.323,-10.815 -13.645,-20.853 -22.348,-27.537c3.386,-2.913 6.771,-5.825 10.158,-8.738C273.422,329.814 275.447,334.287 277.563,336.5z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#FF000000"
android:pathData="M260.313,367.75c4.569,7.092 8.239,12.441 10.227,20.592c0.796,3.262 3.246,13.1 0.854,15.621c-10.066,10.612 -20.093,26.737 -35.456,26.787c-9.021,0.029 -11.832,-7.586 -17.508,-12.816c-2.075,-1.912 -5.421,0.273 -8.18,-1.934c-3.102,-2.48 -4.067,-4.908 -1.604,-8.466c3.421,-4.941 -2.849,-12.047 2.167,-18.784c1.154,-1.551 16.55,-11.327 5.75,-10.75c2.772,-7.785 13.679,-12.316 20,-16.873C247.326,353.368 250.879,359.859 260.313,367.75z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#FF000000"
android:pathData="M187.813,386c-1.686,2.064 -13.877,7.631 -11.274,0.979C178.616,381.671 185.527,377.577 187.813,386z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#A89187"
android:pathData="M335.563,384.376c-0.708,0.333 -1.417,0.667 -2.125,1c0.04,-2.644 -0.199,-2.056 1.625,-3.877C335.396,382.434 335.3,383.484 335.563,384.376z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#FF000000"
android:pathData="M153.563,386.75c7.177,4.215 10.385,10.581 8.25,18.627c-0.931,3.508 -4.848,4.997 -2.021,8.81c2.966,4 5.933,8 8.898,12c3.103,4.185 8.78,15.929 -0.504,16.813c-3.04,0.29 -6.964,-3.873 -10.74,-3.873c-3.669,0 -6.358,5.126 -9.76,5.25c-4.806,0.175 -11.27,-9.867 -7.175,-14.499c3.208,-3.629 14.637,-12.92 9.113,-18.439c-3.267,-3.264 -7.083,1.137 -9.688,-4.189c-1.47,-3.005 0.455,-10.817 0.599,-14.234C140.797,386.836 150.813,376.118 153.563,386.75z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#A89187"
android:pathData="M327.063,389.876c-1.093,-0.67 -3.414,0.649 -0.625,-2.25C326.646,388.376 326.854,389.126 327.063,389.876z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#FF000000"
android:pathData="M198.938,398.75c1.292,2.375 2.583,4.751 3.875,7.127c-4.046,-4.716 -6.986,-9.549 -10,-14.877C194.854,393.583 196.896,396.166 198.938,398.75z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#A89187"
android:pathData="M235.938,410.877c0.77,1.706 4.544,7.634 3.375,9.75c-2.836,5.135 -6.79,-7.477 -7.135,-8.308c-1.887,-4.545 -8.157,-17.23 -2.49,-19.943C232.261,398.487 234.114,404.493 235.938,410.877z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#A89187"
android:pathData="M208.063,444.877c6.279,10.938 2.696,27.903 -12.523,20.301C186.373,460.599 196.059,434.559 208.063,444.877z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#FFFFFF"
android:pathData="M205.813,452.127c-0.415,4.371 -5.919,13.81 -9.125,5.5C193.979,450.608 201.321,448.549 205.813,452.127z"
android:strokeColor="#FFFFFF" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#A89187"
android:pathData="M145.438,466.5c5.49,1.89 2.685,-2.333 5.626,-4.123c3.564,-2.169 5.859,-2.323 10.859,-2.684c8.859,-0.639 7.837,3.99 6.265,12.307c2.626,-4.195 12.711,-11 16.876,-7.75c4.48,3.495 -8.841,6.465 -10.476,10.39c-3.299,7.92 2.161,15.263 -10.365,14.085c-7.714,-0.726 -8.892,-9.377 -2.409,-12.848c-1.833,-1.626 -3.667,-3.251 -5.5,-4.877c-1.651,5.008 -2.901,15.814 -10.713,11.757c-7.489,-3.891 -15.028,-7.236 -22.794,-10.547c-4.938,-2.104 0.573,-11.096 3.194,-13.021C129.924,456.306 141.905,464.779 145.438,466.5z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#FFFFFF"
android:pathData="M66.688,479.5c5.025,3.038 7.616,4.401 5.464,10.858c-1.75,5.251 -2.041,10.639 -3.838,15.769c-7.489,-3.137 -13.58,-6.435 -21.598,-2.684c-7.499,3.507 -15.022,6.804 -22.652,9.307c-0.806,-5.367 -7.504,-38.609 -2.341,-39.877C37.626,468.968 51.769,474.47 66.688,479.5z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#A89187"
android:pathData="M225.813,493.127c7.112,5.575 -17.545,7.534 -19.528,7.798c-5.713,0.76 -22.345,4.553 -26.471,-1.111c-3.587,-4.925 17.508,-2.536 19.956,-2.817C206.316,496.244 220.97,491.704 225.813,493.127z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#A89187"
android:pathData="M196.438,506.377c-3.442,4.375 -12.568,5.338 -16.124,0.873C183.83,502.563 192.15,503.856 196.438,506.377z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#FF000000"
android:pathData="M65.188,512.5c5.698,5.89 10.519,29.75 3.876,35.627c-0.344,-3.045 0.308,-12.207 -1.626,-14.377c-2.802,-3.143 -3.66,0.448 -3.451,2.975c0.83,10.039 0.852,18.942 0.201,28.902c-0.611,9.343 -1.701,19.31 -8.895,26.025c-8.796,8.211 -16.757,-1.5 -21.457,-8.601c-10.636,-16.074 -11.455,-37.119 -8.901,-55.342C26.875,513.867 53.676,504.028 65.188,512.5z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#A89187"
android:pathData="M190.563,516.627c-1.925,0.501 -11.222,-1.199 -6.874,-3.612C187.177,511.078 192.1,511.25 190.563,516.627z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#FFFFFF"
android:pathData="M347.563,706.127c2.827,10.743 -24.355,21.701 -31.46,25.866c-5.481,3.215 -15.369,2.255 -21.613,2.919c-8.831,0.939 -10.492,-2.626 -16.427,-9.162c10.097,-7.609 24.412,-6.044 35.649,-11.23c7.77,-3.586 15.539,-7.172 23.309,-10.758C344.429,700.343 345.8,695.744 347.563,706.127z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#FFFFFF"
android:pathData="M107.063,728c2.812,0.779 5.708,1.041 8.5,0.25c0.254,3.753 23.388,8.174 25.75,5c4.42,5.243 8.703,0.566 13.624,0c1.673,-0.192 2.848,1.589 4.7,1.084c2.86,-0.781 5.351,-1.591 8.3,-1.584c-2.406,1.721 -2.468,2.997 -2.13,5.622c0.274,2.121 -3.735,3.727 -5.37,4.878c1.19,0.577 0.997,0.959 2.5,1.377c-5.047,5.384 -8.459,10.25 -15.844,10.25c-7.569,0 -15.8,-0.318 -23.156,-2.25c-6.461,-1.696 -14.617,-6.329 -19.652,-10.525c-3.844,-3.203 -10.049,-14.414 -7.722,-19.102C100.25,724.44 103.964,725.494 107.063,728z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#FF000000"
android:pathData="M344.813,729.127c1.183,14.487 6.975,23.606 16.148,34.457c7.267,8.595 9.12,21.742 10.137,31.998c1.016,10.245 -27.745,13.067 -34.418,14.313c-12.13,2.265 -26.692,8.847 -25.367,-10.768c2.989,5.224 6.408,-1.427 9.41,-4.219c3.716,-3.457 12.509,-4.174 17.283,-5.562c4.608,-1.34 11.135,-4.383 15.932,-4.597c3.506,-0.156 9.207,4.035 11.25,1.127c7.558,-10.758 -27.36,-3.568 -30.514,-2.739c-6.93,1.821 -20.75,6.063 -22.861,14.112c-3.152,-4.81 -1.522,-12.932 -1.599,-18.576c-0.074,-5.439 -4.552,-8.488 -0.026,-13.297c-2.284,-0.462 -3.976,-1.593 -6.375,-1.377c2.567,-1.798 5.897,-1.922 5.5,-6.123c-3.174,-0.105 -6.572,0.672 -8.875,-1.627c2.47,-2.102 8,-0.46 8,-4.562c0,-1.918 -8.538,-1.552 -10.5,-3.438c2.265,-0.387 18.64,-0.854 16.125,-6.123c-3.014,-6.312 -15.192,4.396 -18.875,-1.877c7.186,-0.666 18.631,0.42 25.187,-3.3C325.761,733.895 341.331,720.169 344.813,729.127z"
android:strokeColor="#000000" android:strokeLineCap="round"
android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#FF000000"
android:pathData="M160.438,737.127c0.726,-0.258 1.461,-1.174 3,-0.75C162.438,736.627 161.438,736.877 160.438,737.127z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#FF000000"
android:pathData="M123.313,761.25c7.675,0.866 15.349,1.731 23.024,2.598c3.334,0.376 4.321,14.507 5.1,18.402c3.142,15.709 -9.428,26.272 -16.621,39.235c-6.638,11.964 -21.567,15.892 -34.894,15.892c-7.624,0 -15.038,0.83 -18.359,-7.5c-2.55,-6.397 -0.152,-16.521 5,-21c-0.157,1.568 0.511,2.913 0.5,4.373c3.155,-5.297 7.952,-2.895 12.905,-3.705c7.833,-1.282 13.483,-3.006 20.438,1.344c2.335,1.46 2.588,-3.633 1.087,-4.952c-2.813,-2.473 -4.873,-3.85 -8.676,-3.751c-7.495,0.194 -14.904,0.518 -22.38,1.064c-0.392,-8.586 9.178,-18.335 9.626,-27.873c1.909,1.315 6.971,1.857 6.75,-2c-0.057,-0.988 -5.069,-4.182 -6.126,-5.25c0.747,-0.223 14.673,2.827 7.75,-3.25c4.56,1.721 10.91,4.372 14.126,-0.877c-5.984,-2.372 -21.022,-4.47 -19.626,-13.873C109.831,754.79 115.474,758.555 123.313,761.25z"
android:strokeColor="#000000" android:strokeLineCap="round"
android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#FFFFFF" android:pathData="M207.813,145.5c13.659,-2.167 10.148,-31.902 -0.939,-28.458C195.919,120.445 195.34,143.716 207.813,145.5z"/>
<path android:fillColor="#FA002A"
android:pathData="M153.813,404c4.07,-9.431 -1.446,-6.081 -4.75,-14.123c-3.146,3.039 -3.12,6.077 -3.406,10.125C145.302,405.048 149.279,403.527 153.813,404z"
android:strokeColor="#FA002A" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#FA7800" android:pathData="M153.563,423.877c6.251,0 18.406,4.306 15.124,13c-10.103,-4.718 -12.256,-5.389 -21.5,0.873C142.788,433.468 150.714,427.534 153.563,423.877z"/>
<path android:fillColor="#FFFFFF" android:pathData="M63.04,523.027c0,0 -0.218,-0.135 -0.626,-0.388c-0.396,-0.275 -1.006,-0.624 -1.75,-1.019c-0.752,-0.344 -1.784,-0.754 -2.639,-0.543c-0.399,0.146 -0.739,0.391 -1.023,0.866c-0.273,0.477 -0.493,1.068 -0.642,1.752c-0.314,1.352 -0.434,2.944 -0.481,4.633c-0.041,1.693 -0.014,3.501 0.044,5.378c0.08,1.876 0.176,3.823 0.321,5.804c0.172,1.987 0.347,4.007 0.522,6.025c0.087,1.074 0.061,2.041 0.097,3.063l0.032,1.509c-0.014,0.506 -0.028,1.01 -0.042,1.513c-0.039,1.004 -0.042,2.003 -0.11,2.982c-0.074,0.978 -0.146,1.943 -0.218,2.893c-0.176,1.896 -0.382,3.725 -0.617,5.45c-0.247,1.726 -0.473,3.356 -0.738,4.854c-0.484,3 -0.973,5.482 -1.297,7.224c-0.333,1.74 -0.523,2.733 -0.523,2.733c-0.004,0.02 -0.022,0.032 -0.042,0.028c-0.019,-0.003 -0.031,-0.021 -0.028,-0.04c0,0 0.158,-1 0.434,-2.749c0.266,-1.75 0.673,-4.247 1.058,-7.256c0.215,-1.501 0.387,-3.135 0.576,-4.86c0.178,-1.726 0.322,-3.551 0.435,-5.438c0.041,-0.943 0.081,-1.903 0.122,-2.875c0.035,-0.971 0.006,-1.949 0.012,-2.937c-0.002,-0.493 -0.004,-0.988 -0.007,-1.485l-0.081,-1.506c-0.067,-0.991 -0.081,-2.053 -0.192,-2.993c-0.528,-3.981 -1.245,-8.004 -1.585,-11.82c-0.182,-1.909 -0.274,-3.771 -0.216,-5.555c0.063,-1.784 0.246,-3.49 0.727,-5.071c0.215,-0.79 0.596,-1.547 1.082,-2.221c0.484,-0.689 1.327,-1.19 2.125,-1.292c0.403,-0.029 0.785,-0.035 1.111,0.069c0.167,0.039 0.334,0.077 0.486,0.128c0.141,0.066 0.279,0.132 0.415,0.196c0.559,0.23 0.947,0.575 1.334,0.838c0.715,0.61 1.203,1.115 1.501,1.489c0.308,0.369 0.473,0.565 0.473,0.565c0.013,0.016 0.011,0.038 -0.004,0.051C63.07,523.034 63.053,523.035 63.04,523.027z"/>
<path android:fillColor="#FFFFFF" android:pathData="M248.966,363.022c0,0 -0.143,0.494 -0.503,1.273c-0.359,0.782 -0.916,1.855 -1.723,3.055c-0.405,0.601 -0.889,1.218 -1.399,1.872c-0.509,0.653 -1.099,1.304 -1.738,1.949c-0.647,0.64 -1.287,1.32 -2.011,1.949c-0.716,0.635 -1.425,1.292 -2.122,1.958c-0.734,0.628 -1.469,1.256 -2.191,1.874c-0.702,0.64 -1.497,1.145 -2.209,1.697c-0.724,0.541 -1.42,1.064 -2.129,1.491c-0.695,0.445 -1.352,0.866 -1.958,1.255c-0.596,0.403 -1.192,0.692 -1.693,0.991c-0.508,0.288 -0.95,0.539 -1.314,0.745c-0.729,0.413 -1.145,0.648 -1.145,0.648c-0.017,0.01 -0.039,0.004 -0.049,-0.013c-0.009,-0.017 -0.002,-0.039 0.014,-0.048c0,0 0.4,-0.261 1.102,-0.718c0.351,-0.228 0.775,-0.504 1.264,-0.822c0.48,-0.328 1.055,-0.649 1.616,-1.096c0.572,-0.428 1.192,-0.892 1.848,-1.383c0.669,-0.472 1.319,-1.039 1.993,-1.623c0.662,-0.596 1.402,-1.144 2.047,-1.823c0.663,-0.655 1.337,-1.321 2.01,-1.987c0.629,-0.711 1.245,-1.432 1.835,-2.153c0.6,-0.711 1.109,-1.488 1.658,-2.183c1.099,-1.39 2.111,-2.705 3.1,-3.735c0.503,-0.507 0.945,-1.003 1.384,-1.398c0.422,-0.41 0.821,-0.743 1.152,-1.015c0.664,-0.544 1.106,-0.806 1.106,-0.806c0.019,-0.011 0.042,-0.005 0.053,0.014C248.968,363.001 248.969,363.013 248.966,363.022z"/>
<path android:fillColor="#00000000"
android:pathData="M337.563,739.377c3.495,12.533 8.209,19.954 16.625,29.873"
android:strokeColor="#FFFFFF" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#00000000"
android:pathData="M360.188,792.75c-5.732,-1.196 -9.619,0.341 -15.125,2"
android:strokeColor="#FFFFFF" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#00000000"
android:pathData="M137.438,771.5c-0.549,11.256 -4.222,15.88 -10.5,25.377"
android:strokeColor="#FFFFFF" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#00000000"
android:pathData="M113.688,816.877c-6.847,2.125 -12.803,2.576 -20,3"
android:strokeColor="#FFFFFF" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#FFFFFF" android:pathData="M250.813,403.876c0,0 -0.246,-0.283 -0.62,-0.8c-0.377,-0.515 -0.88,-1.265 -1.434,-2.183c-1.104,-1.838 -2.333,-4.381 -3.33,-7.011c-0.861,-2.711 -1.486,-5.505 -1.76,-7.644c-0.137,-1.07 -0.205,-1.97 -0.226,-2.606c-0.025,-0.635 -0.006,-1.006 -0.006,-1.006s0.232,0.3 0.578,0.838c0.35,0.537 0.813,1.313 1.313,2.254c0.999,1.884 2.071,4.453 2.907,7.105c0.993,2.631 1.755,5.351 2.143,7.459c0.193,1.054 0.313,1.949 0.37,2.584C250.81,403.502 250.813,403.876 250.813,403.876z"/>
<path android:fillColor="#FA002A" android:pathData="M275.813,259.75c2.225,-4.67 23.463,-28.569 21.125,-31C291.535,223.132 265.829,261.555 275.813,259.75z"/>
<path android:fillColor="#FA002A" android:pathData="M279.188,221.626c1.113,-2.504 7.331,-6.185 6.625,-1.376c-0.468,3.186 -6.914,7.801 -8.875,10.5c-3.701,5.094 -8.024,19.159 -14.125,20.5C260.807,241.297 272.867,228.612 279.188,221.626z"/>
<path android:fillColor="#FA002A" android:pathData="M271.688,218c5.358,-5.668 10.277,-1.495 3.375,3.626C270.036,225.356 266.084,221.832 271.688,218z"/>
<path android:fillColor="#FA002A" android:pathData="M262.563,221c0.733,-2.185 11.817,-15.949 13.875,-10.25C277.19,212.836 264.699,221.63 262.563,221z"/>
<path android:fillColor="#FA002A" android:pathData="M257.063,218.5c1.981,-3.302 7.674,-15.482 12.375,-10.75C265.533,210.375 260.92,220.092 257.063,218.5z"/>
<path android:fillColor="#1300C6" android:pathData="M255.938,220.5c5.788,4.081 13.771,2.381 13.25,9.626c-0.026,0.37 -8.12,15.954 -8.625,16.374c-3.659,3.046 -14.066,-2.89 -13.62,-7.8C247.461,232.996 250.542,223.237 255.938,220.5z"/>
<path android:fillColor="#FA002A" android:pathData="M289.938,224.626c0.333,5.91 -7.832,12.249 -11.391,16.565c-3.909,4.741 -5.481,12.765 -11.234,15.309C267.316,249.658 281.768,220.788 289.938,224.626z"/>
<path android:fillColor="#A89187"
android:pathData="M103.438,326.75c3.49,2.737 7.772,4.977 8.25,9.75c-6.138,-1.255 -25.76,-11.779 -24.25,-18.374C93.332,318.604 98.615,323.664 103.438,326.75z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#A89187"
android:pathData="M56.688,385.126c-2.159,1.787 -18.553,5.835 -19.124,2.25C36.463,380.464 55.263,383.531 56.688,385.126z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#A60053"
android:pathData="M78.813,394.25c-2.641,0.338 1.361,-0.095 2.25,0C80.313,394.25 79.563,394.25 78.813,394.25z"
android:strokeColor="#A60053" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#FFFF00" android:pathData="M53.563,463.25c5.965,4.16 13.117,6.081 18.874,10.5c-5.903,1.747 -17.396,-4.874 -23.671,-6.508c-9.498,-2.473 -17.147,-1.08 -26.453,-0.742c1.038,-5.998 14.309,-4.932 19.44,-4.529c12.097,0.948 3.525,-14.243 11.06,-13.971C53.063,453.083 53.313,458.167 53.563,463.25z"/>
<path android:fillColor="#A89187"
android:pathData="M232.938,503.877c-1.235,9.531 -4.108,19.102 -5.75,28.623c-0.8,4.638 -0.996,10.029 -2.5,14.5c-0.903,2.686 -2.414,7.929 -5.25,1.127c-2.6,-6.236 4.209,-23.522 5.433,-29.439C225.71,514.622 229.626,495.624 232.938,503.877z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#A89187"
android:pathData="M240.188,505.5c0.49,18.999 -8.947,34.421 -10.5,52c0,-9.837 1.354,-19.467 1.851,-29.383C231.729,524.29 234.769,501.56 240.188,505.5z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#A89187"
android:pathData="M133.938,509.127c5.188,1.984 12.112,3.215 16,7.25c-4.437,3.969 -12.418,-0.811 -17.147,-2.577c-6.249,-2.335 -13.143,-3.05 -19.729,-4.385c-6.679,-1.354 -8.324,-4.569 -0.247,-4.165C120.03,505.61 126.891,507.542 133.938,509.127z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#A89187"
android:pathData="M247.313,506.377c0.537,4.927 -3.634,26.399 -7.125,18.5C238.668,521.438 242.461,503.3 247.313,506.377z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#A89187"
android:pathData="M127.188,517.127c0.583,0.541 1.167,1.082 1.75,1.623c-5.068,1.583 -18.704,0.165 -21.624,-4.373C113.616,511.233 121.182,514.888 127.188,517.127z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#A89187"
android:pathData="M119.813,524.877c0.844,6.79 -9.675,3.093 -10,-2.25C113.197,523.043 117.344,522.659 119.813,524.877z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#A89187"
android:pathData="M271.688,572c0.773,8.227 1.547,16.453 2.32,24.68c0.567,6.04 1.542,1.898 3.805,5.447c1.54,2.416 5.701,3.513 2.75,6.873c-0.929,-1.015 -2.388,-1.538 -3.375,-2.5c-0.857,3.351 0.093,5.071 3.375,4.25c-0.667,0.459 -1.333,0.918 -2,1.377c2.004,2.838 7.75,5.175 7.75,8.437c0,2.019 -8.258,4.788 -0.991,7.484c6.181,2.293 2.54,8.87 -2.073,5.052c-3.792,-3.139 -8.391,-7.059 -9.295,-11.989c-1.661,-9.063 -12.095,-62.388 -5.016,-65.983C269.854,560.751 270.771,566.376 271.688,572z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#A89187"
android:pathData="M143.813,612.377c-4.079,5.317 -11.072,6.564 -15.924,10.877c-4.354,3.87 -10.099,12.56 -15.576,8.996c2.428,-6.34 8.99,-11.623 14.68,-15.012c5.541,-3.3 4.569,-11.064 9.32,-12.861c0.277,2.033 1.548,3.96 1.75,6c2.81,-2.87 2.101,-13.25 4.375,-13.25C145.29,597.127 143.813,609.424 143.813,612.377z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#A89187"
android:pathData="M172.313,655c-0.79,4.546 -32.291,7.431 -35.626,4.627c5.006,-5.458 5.655,-5.415 15.571,-6.095C156.253,653.259 170.864,649.868 172.313,655z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#A89187"
android:pathData="M321.563,661c-2.592,3.908 -5.184,7.815 -7.775,11.724c-1.862,2.807 3.057,4.037 2.15,6.026c-2.131,4.678 -18.698,5.782 -23.62,7.209c-4.669,1.353 -2.869,-4.493 -1.093,-6.432c2.537,-2.768 8.138,-2.279 11.449,-4.583c7.477,-5.201 12.119,-11.943 18.014,-18.817C319.493,658.676 321.263,658.806 321.563,661z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#A89187"
android:pathData="M155.438,665.25c-0.743,5.374 -13.75,7.711 -13.75,1.375c0,-6.806 12.909,-2.51 16.25,-1.375C157.104,665.25 156.271,665.25 155.438,665.25z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#A89187"
android:pathData="M309.313,690.127c-1.826,1.37 -12.029,4.19 -10.5,-0.25C299.974,686.507 308.207,687.872 309.313,690.127z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#FF000000"
android:pathData="M344.813,692c-11.668,5.957 -22.197,12.215 -34.367,17.402c-4.347,1.853 -34.706,13.177 -37.07,7.036c-1.405,-3.65 14.092,-3.136 16.281,-3.277c5.316,-0.343 14.506,-2.691 19.035,-5.502c11.026,-6.844 23.721,-13.549 35.496,-19.159C344.766,689.629 344.917,690.744 344.813,692z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#FF000000"
android:pathData="M102.563,714.5c17.159,-2.251 32.889,15.131 49.874,4.127c-0.667,0.75 -1.333,1.5 -2,2.25c5.008,0.152 14.498,-0.661 19.376,-3.127c-1.728,2.915 0.97,9.614 -1.861,10.357c-4.976,1.305 -9.879,2.143 -15.027,2.143c-4.826,0 -9.651,0 -14.477,0c-4.904,0 -8.795,-2 -13.506,-2c-6.384,0 -15.344,-4.258 -21.505,-7.373c-4.171,-2.109 -9.427,-1.75 -8.754,-7.307C95.183,709.453 100.844,712.602 102.563,714.5z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.126"/>
<path android:fillColor="#FF000000" android:pathData="M88.849,22.891C88.601,17.992 89.917,6.615 83,5.75c-0.421,-0.053 -0.896,0.28 -1.087,0.633c-1.324,2.432 -0.769,4.769 -0.324,7.343c0.67,3.875 0.502,7.938 0.817,11.854c0.632,7.834 2.416,15.559 2.938,23.42c0.226,3.407 5.161,3.444 5.313,0C91.046,40.131 89.292,31.678 88.849,22.891z"/>
<path android:fillColor="#CBFF00" android:pathData="M207.5,283a5.75,5.5 0,1 0,11.5 0a5.75,5.5 0,1 0,-11.5 0z"/>
</vector>

View file

@ -1,5 +0,0 @@
<vector android:height="64dp" android:viewportHeight="401.294"
android:viewportWidth="401.294" android:width="64dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M115.342,188.015c-7.025,0 -12.741,5.716 -12.741,12.741s5.716,12.741 12.741,12.741c7.026,0 12.742,-5.716 12.742,-12.741S122.368,188.015 115.342,188.015zM115.342,205.497c-2.614,0 -4.741,-2.127 -4.741,-4.741s2.127,-4.741 4.741,-4.741c2.615,0 4.742,2.127 4.742,4.741S117.957,205.497 115.342,205.497z"/>
<path android:fillColor="#FF000000" android:pathData="M397.294,164.141c2.209,0 4,-1.791 4,-4V89.215c0,-2.209 -1.791,-4 -4,-4h-33.219c-2.209,0 -4,1.791 -4,4v70.926c0,2.209 1.791,4 4,4h12.61v8.999h-36.205c-6.008,-4.585 -13.401,-7.094 -20.986,-7.094c-17.762,0 -32.434,13.455 -34.376,30.706h-3.421v-9.79c0,-2.209 -1.791,-4 -4,-4h-28.993l-6.173,-11.125c-0.705,-1.271 -2.044,-2.059 -3.498,-2.059h-39.513v-6.524h16.196c2.209,0 4,-1.791 4,-4V73.281c0,-2.209 -1.791,-4 -4,-4h-40.266c-2.209,0 -4,1.791 -4,4v85.972c0,2.209 1.791,4 4,4h16.069v6.524h-38.468c-2.209,0 -4,1.791 -4,4v22.974h-4.52c-1.97,-15.047 -14.865,-26.705 -30.44,-26.705c-10.313,0 -19.703,5.125 -25.326,13.331c-5.623,-8.206 -15.012,-13.331 -25.326,-13.331H44.653v-5.905h6.324c2.209,0 4,-1.791 4,-4V89.215c0,-2.209 -1.791,-4 -4,-4H17.758c-2.209,0 -4,1.791 -4,4v70.926c0,2.209 1.791,4 4,4h6.325v5.905H4c-2.209,0 -4,1.791 -4,4v53.419c0,2.209 1.791,4 4,4h20.083v5.905h-6.325c-2.209,0 -4,1.791 -4,4v70.927c0,2.209 1.791,4 4,4h33.219c2.209,0 4,-1.791 4,-4v-70.927c0,-2.209 -1.791,-4 -4,-4h-6.325v-5.905h18.789c10.313,0 19.703,-5.125 25.326,-13.331c5.624,8.205 15.013,13.331 25.326,13.331c15.578,0 28.476,-11.663 30.441,-26.714h4.518v22.765c0,2.209 1.791,4 4,4h38.468v6.524h-16.069c-2.209,0 -4,1.791 -4,4v85.972c0,2.209 1.791,4 4,4h40.266c2.209,0 4,-1.791 4,-4V242.04c0,-2.209 -1.791,-4 -4,-4h-16.196v-6.524h39.513c1.454,0 2.792,-0.789 3.498,-2.059l6.173,-11.125h28.993c2.209,0 4,-1.791 4,-4v-9.581h3.447c2.037,17.151 16.66,30.497 34.35,30.497c7.581,0 14.972,-2.507 20.978,-7.087h36.214v9.209h-12.61c-2.209,0 -4,1.791 -4,4v70.927c0,2.209 1.791,4 4,4h33.219c2.209,0 4,-1.791 4,-4v-70.927c0,-2.209 -1.791,-4 -4,-4h-12.609v-9.209h12.435c2.209,0 4,-1.791 4,-4V177.14c0,-2.209 -1.791,-4 -4,-4h-12.435v-8.999H397.294zM199.521,155.253v-13.221h12.196v13.221H199.521zM191.521,112.267h-12.069V98.502h12.069V112.267zM199.521,98.502h12.196v13.765h-12.196V98.502zM191.521,120.267v13.765h-12.069v-13.765H191.521zM199.521,120.267h12.196v13.765h-12.196V120.267zM211.717,90.502h-12.196V77.281h12.196V90.502zM191.521,77.281v13.221h-12.069V77.281H191.521zM179.451,155.253v-13.221h12.069v13.221H179.451zM24.689,223.466v-45.419h22.038v45.419H24.689zM54.727,178.046h6.641v45.419h-6.641V178.046zM46.977,128.678v9.956H21.758v-9.956H46.977zM21.758,120.678v-9.957h25.219v9.957H21.758zM46.977,93.215v9.507H21.758v-9.507H46.977zM21.758,146.634h25.219v9.507H21.758V146.634zM32.083,164.141h4.569v5.905h-4.569V164.141zM8,178.046h8.689v45.419H8V178.046zM21.758,272.834v-9.956h25.219v9.956H21.758zM46.977,280.834v9.956H21.758v-9.956H46.977zM21.758,308.298v-9.507h25.219v9.507H21.758zM46.977,254.878H21.758v-9.507h25.219V254.878zM36.652,237.371h-4.569v-5.905h4.569V237.371zM69.368,222.667v-43.822c6.952,1.876 12.703,6.983 15.31,13.918v15.987C82.071,215.684 76.32,220.791 69.368,222.667zM114.093,223.466c-9.664,0 -18.222,-6.091 -21.415,-15.184v-15.05c3.193,-9.094 11.751,-15.185 21.415,-15.185c12.522,0 22.709,10.188 22.709,22.71C136.802,213.278 126.615,223.466 114.093,223.466zM191.521,246.04v13.221h-12.069V246.04H191.521zM199.521,289.027h12.196v13.765h-12.196V289.027zM191.521,302.792h-12.069v-13.765h12.069V302.792zM199.521,281.027v-13.765h12.196v13.765H199.521zM191.521,281.027h-12.069v-13.765h12.069V281.027zM179.451,310.792h12.069v13.221h-12.069V310.792zM199.521,324.012v-13.221h12.196v13.221H199.521zM211.717,246.04v13.221h-12.196V246.04H211.717zM157.052,177.778h11.046v45.739h-11.046V177.778zM236.678,223.516h-60.58v-45.739h60.58l5.311,9.571v26.596L236.678,223.516zM273.696,210.332h-23.707v-19.371h23.707V210.332zM368.075,272.834v-9.956h25.219v9.956H368.075zM393.294,280.834v9.956h-25.219v-9.956H393.294zM368.075,308.298v-9.507h25.219v9.507H368.075zM393.294,254.878h-25.219v-9.507h25.219V254.878zM292.892,200.647c0,-8.817 4.315,-16.641 10.94,-21.484v42.968C297.207,217.288 292.892,209.463 292.892,200.647zM319.493,227.248c-2.663,0 -5.234,-0.398 -7.661,-1.129v-50.944c2.428,-0.731 4.998,-1.129 7.661,-1.129c6.217,0 12.267,2.193 17.036,6.175c0.248,0.208 0.521,0.368 0.803,0.506v39.849c-0.29,0.141 -0.57,0.304 -0.821,0.513C331.747,225.061 325.703,227.248 319.493,227.248zM393.119,220.161h-47.787V181.14h47.787V220.161zM393.294,128.678v9.956h-25.219v-9.956H393.294zM368.075,120.678v-9.957h25.219v9.957H368.075zM393.294,93.215v9.507h-25.219v-9.507H393.294zM368.075,146.634h25.219v9.507h-25.219V146.634z"/>
</vector>

View file

@ -1,170 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#008577"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View file

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="maroon_200">#FFb73d2a</color>
<color name="maroon_500">#FF800000</color>
<color name="maroon_700">#FF4f0000</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

View file

@ -1,5 +0,0 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
</resources>

View file

@ -1,3 +0,0 @@
<resources>
<string name="app_name">PeopleInSpace</string>
</resources>

View file

@ -1,3 +0,0 @@
<resources>
</resources>

View file

@ -1,23 +0,0 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.PeopleInSpace" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<item name="colorPrimary">@color/maroon_500</item>
<item name="colorPrimaryVariant">@color/maroon_700</item>
<item name="colorOnPrimary">@color/white</item>
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
</style>
<style name="Theme.PeopleInSpace.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="Theme.MyApplication.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="Theme.MyApplication.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
</resources>

View file

@ -1,6 +0,0 @@
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="80dp"
android:minHeight="80dp"
android:targetCellWidth="4"
android:targetCellHeight="2"
android:resizeMode="none" />

View file

@ -1,10 +0,0 @@
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="80dp"
android:minHeight="80dp"
android:targetCellWidth="2"
android:targetCellHeight="2"
android:minResizeWidth="40dp"
android:minResizeHeight="40dp"
android:maxResizeWidth="120dp"
android:maxResizeHeight="120dp"
android:resizeMode="horizontal|vertical" />

View file

@ -1,32 +0,0 @@
package com.surrus.peopleinspace
import android.content.Context
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import com.surrus.common.di.initKoin
import com.surrus.peopleinspace.di.appModule
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.koin.android.ext.koin.androidContext
import org.koin.test.check.checkModules
import org.koin.test.mock.MockProviderRule
import org.mockito.Mockito
import org.robolectric.RobolectricTestRunner
@RunWith(RobolectricTestRunner::class)
class TestKoinGraph {
private val context = getApplicationContext<Context>()
@get:Rule
val mockProvider = MockProviderRule.create { clazz ->
Mockito.mock(clazz.java)
}
@Test
fun `checking koin modules`() {
initKoin {
androidContext(context)
modules(appModule)
}.checkModules()
}
}

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

@ -1,45 +0,0 @@
buildscript {
val kotlinVersion: String by project
println(kotlinVersion)
repositories {
google()
mavenCentral()
gradlePluginPortal()
maven(uri("https://plugins.gradle.org/m2/")) // For kotlinter-gradle
}
dependencies {
// keeping this here to allow AS to automatically update
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}")
}
}
}
allprojects {
apply(plugin = "org.jmailen.kotlinter")
repositories {
google()
mavenCentral()
maven(url = "https://maven.pkg.jetbrains.space/kotlin/p/kotlin/kotlin-js-wrappers")
maven(url = "https://jitpack.io")
maven(url = "https://maven.pkg.jetbrains.space/public/p/kotlinx-coroutines/maven")
}
}
// On Apple Silicon we need Node.js 16.0.0
// https://youtrack.jetbrains.com/issue/KT-49109
rootProject.plugins.withType(org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin::class) {
rootProject.the(org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension::class).nodeVersion = "16.0.0"
}

1
buildSrc/.gitignore vendored
View file

@ -1 +0,0 @@
build

View file

@ -1,7 +0,0 @@
plugins {
`kotlin-dsl`
}
repositories {
mavenCentral()
}

View file

@ -1,164 +0,0 @@
object Versions {
const val androidMinSdk = 21
const val androidCompileSdk = 31
const val androidTargetSdk = androidCompileSdk
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 kmpNativeCoroutinesVersion = "0.11.1-new-mm"
const val compose = "1.1.0-rc01"
const val composeCompiler = "1.1.0-rc02"
const val wearCompose = "1.0.0-alpha13"
const val navCompose = "2.4.0-rc01"
const val accompanist = "0.22.0-rc"
const val composeDesktopWeb = "1.0.1"
const val junit = "4.12"
const val androidXTestJUnit = "1.1.3"
const val testCore = "1.3.0"
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"
const val material = "1.4.0"
const val activityCompose = "1.4.0-beta01"
const val lifecycleKtx = "2.4.0-rc01"
const val lifecycleRuntimeKtx = lifecycleKtx
const val lifecycleViewmodelKtx = lifecycleKtx
const val osmdroidAndroid = "6.1.10"
const val kotlinReact = "17.0.1-pre.146-kotlin-1.4.30"
const val kotlinReactDom = "17.0.1-pre.146-kotlin-1.4.30"
const val kotlinReactRouterDom = "5.1.2-pre.110-kotlin-1.4.0"
const val kotlinStyled = "5.2.1-pre.146-kotlin-1.4.30"
const val slf4j = "1.7.30"
const val logback = "1.2.3"
const val kermit = "1.0.0"
const val gradleVersionsPlugin = "0.39.0"
}
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}"
}
object Kotlinx {
const val serializationCore = "org.jetbrains.kotlinx:kotlinx-serialization-core:${Versions.kotlinxSerialization}"
const val coroutinesCore = "org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.kotlinCoroutines}"
const val coroutinesTest = "org.jetbrains.kotlinx:kotlinx-coroutines-test:${Versions.kotlinCoroutines}"
const val htmlJs = "org.jetbrains.kotlinx:kotlinx-html-js:${Versions.kotlinxHtmlJs}"
}
object Android {
const val material = "com.google.android.material:material:${Versions.material}"
const val osmdroidAndroid = "org.osmdroid:osmdroid-android:${Versions.osmdroidAndroid}"
}
object AndroidX {
const val lifecycleRuntimeKtx = "androidx.lifecycle:lifecycle-runtime-ktx:${Versions.lifecycleRuntimeKtx}"
const val lifecycleViewmodelKtx = "androidx.lifecycle:lifecycle-viewmodel-ktx:${Versions.lifecycleViewmodelKtx}"
const val activityCompose = "androidx.activity:activity-compose:${Versions.activityCompose}"
}
object Test {
const val junit = "junit:junit:${Versions.junit}"
const val androidXTestJUnit = "androidx.test.ext:junit:${Versions.androidXTestJUnit}"
const val mockito = "org.mockito:mockito-inline:${Versions.mockito}"
const val robolectric = "org.robolectric:robolectric:${Versions.robolectric}"
const val testCore = "androidx.test:core:${Versions.testCore}"
const val composeUiTest = "androidx.compose.ui:ui-test:${Versions.compose}"
const val composeUiTestJUnit = "androidx.compose.ui:ui-test-junit4:${Versions.compose}"
const val composeUiTestManifest = "androidx.compose.ui:ui-test-manifest:${Versions.compose}"
}
object Compose {
const val compiler = "androidx.compose.compiler:compiler:${Versions.composeCompiler}"
const val ui = "androidx.compose.ui:ui:${Versions.compose}"
const val uiGraphics = "androidx.compose.ui:ui-graphics:${Versions.compose}"
const val uiTooling = "androidx.compose.ui:ui-tooling:${Versions.compose}"
const val foundationLayout = "androidx.compose.foundation:foundation-layout:${Versions.compose}"
const val material = "androidx.compose.material:material:${Versions.compose}"
const val navigation = "androidx.navigation:navigation-compose:${Versions.navCompose}"
const val wearFoundation = "androidx.wear.compose:compose-foundation:${Versions.wearCompose}"
const val wearMaterial = "androidx.wear.compose:compose-material:${Versions.wearCompose}"
const val wearNavigation = "androidx.wear.compose:compose-navigation:${Versions.wearCompose}"
const val coilCompose = "io.coil-kt:coil-compose:1.3.1"
const val accompanistNavigationAnimation = "com.google.accompanist:accompanist-navigation-animation:${Versions.accompanist}"
}
object Koin {
const val core = "io.insert-koin:koin-core:${Versions.koin}"
const val test = "io.insert-koin:koin-test:${Versions.koin}"
const val testJUnit4 = "io.insert-koin:koin-test-junit4:${Versions.koin}"
const val android = "io.insert-koin:koin-android:${Versions.koin}"
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}"
const val routerDom = "org.jetbrains:kotlin-react-router-dom:${Versions.kotlinReactRouterDom}"
const val styled = "org.jetbrains:kotlin-styled:${Versions.kotlinStyled}"
}
object Ok {
const val okhttp = "com.squareup.okhttp3:okhttp:4.9.2"
const val loggingInterceptor = "com.squareup.okhttp3:logging-interceptor:4.9.2"
}
object Log {
const val slf4j = "org.slf4j:slf4j-simple:${Versions.slf4j}"
const val logback = "ch.qos.logback:logback-classic:${Versions.logback}"
const val kermit = "co.touchlab:kermit:${Versions.kermit}"
}
object Glance {
const val tiles = "androidx.glance:glance-wear-tiles:1.0.0-alpha02"
const val appwidget = "androidx.glance:glance-appwidget:1.0.0-alpha02"
}
}

3
common/.gitignore vendored
View file

@ -1,3 +0,0 @@
/build
*.iml

View file

@ -1,155 +0,0 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
plugins {
kotlin("multiplatform")
id("kotlinx-serialization")
id("com.android.library")
id("org.jetbrains.kotlin.native.cocoapods")
id("com.squareup.sqldelight")
id("com.rickclephas.kmp.nativecoroutines")
id("com.chromaticnoise.multiplatform-swiftpackage") version "2.0.3"
}
// CocoaPods requires the podspec to have a version.
version = "1.0"
android {
compileSdk = Versions.androidCompileSdk
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
minSdk = Versions.androidMinSdk
targetSdk = Versions.androidTargetSdk
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
}
kotlin {
val iosTarget: (String, KotlinNativeTarget.() -> Unit) -> KotlinNativeTarget = when {
System.getenv("SDK_NAME")?.startsWith("iphoneos") == true -> ::iosArm64
System.getenv("NATIVE_ARCH")?.startsWith("arm") == true -> ::iosSimulatorArm64 // available to KT 1.5.30
else -> ::iosX64
}
iosTarget("iOS") {}
val sdkName: String? = System.getenv("SDK_NAME")
val isWatchOSDevice = sdkName.orEmpty().startsWith("watchos")
if (isWatchOSDevice) {
watchosArm64("watch")
} else {
watchosX64("watch")
}
macosX64("macOS")
android()
jvm()
cocoapods {
// Configure fields required by CocoaPods.
summary = "PeopleInSpace"
homepage = "https://github.com/joreilly/PeopleInSpace"
}
js(IR) {
useCommonJs()
browser()
}
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.Koin) {
api(core)
api(test)
}
with(Deps.Log) {
api(kermit)
}
}
sourceSets["commonTest"].dependencies {
implementation(Deps.Koin.test)
implementation(Deps.Kotlinx.coroutinesTest)
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
}
sourceSets["androidMain"].dependencies {
implementation(Deps.Ktor.clientAndroid)
implementation(Deps.SqlDelight.androidDriver)
}
sourceSets["androidTest"].dependencies {
implementation(Deps.Test.junit)
}
sourceSets["jvmMain"].dependencies {
implementation(Deps.Ktor.clientJava)
implementation(Deps.SqlDelight.sqliteDriver)
implementation(Deps.Log.slf4j)
}
sourceSets["iOSMain"].dependencies {
implementation(Deps.Ktor.clientIos)
implementation(Deps.SqlDelight.nativeDriver)
}
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)
}
sourceSets["jsMain"].dependencies {
implementation(Deps.Ktor.clientJs)
}
}
}
tasks.withType<KotlinCompile> {
kotlinOptions {
jvmTarget = "1.8"
}
}
sqldelight {
database("PeopleInSpaceDatabase") {
packageName = "com.surrus.peopleinspace.db"
sourceFolders = listOf("sqldelight")
}
}
multiplatformSwiftPackage {
packageName("PeopleInSpace")
swiftToolsVersion("5.3")
targetPlatforms {
iOS { v("13") }
}
}

View file

@ -1,42 +0,0 @@
Pod::Spec.new do |spec|
spec.name = 'common'
spec.version = '1.0'
spec.homepage = 'https://github.com/joreilly/PeopleInSpace'
spec.source = { :git => "Not Published", :tag => "Cocoapods/#{spec.name}/#{spec.version}" }
spec.authors = ''
spec.license = ''
spec.summary = 'PeopleInSpace'
spec.vendored_frameworks = "build/cocoapods/framework/common.framework"
spec.libraries = "c++"
spec.module_name = "#{spec.name}_umbrella"
spec.pod_target_xcconfig = {
'KOTLIN_PROJECT_PATH' => ':common',
'PRODUCT_MODULE_NAME' => 'common',
}
spec.script_phases = [
{
:name => 'Build common',
:execution_position => :before_compile,
:shell_path => '/bin/sh',
:script => <<-SCRIPT
if [ "YES" = "$COCOAPODS_SKIP_KOTLIN_BUILD" ]; then
echo "Skipping Gradle build task invocation due to COCOAPODS_SKIP_KOTLIN_BUILD environment variable set to \"YES\""
exit 0
fi
set -ev
REPO_ROOT="$PODS_TARGET_SRCROOT"
"$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \
-Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \
-Pkotlin.native.cocoapods.archs="$ARCHS" \
-Pkotlin.native.cocoapods.configuration=$CONFIGURATION
SCRIPT
}
]
end

View file

@ -1,21 +0,0 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View file

@ -1,2 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.surrus.common" />

View file

@ -1,19 +0,0 @@
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 org.koin.dsl.module
actual fun platformModule() = module {
single {
val driver =
AndroidSqliteDriver(PeopleInSpaceDatabase.Schema, get(), "peopleinspace.db")
PeopleInSpaceDatabaseWrapper(PeopleInSpaceDatabase(driver))
}
single { Android.create() }
}

View file

@ -1,53 +0,0 @@
package com.surrus.common.di
import com.surrus.common.remote.PeopleInSpaceApi
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
fun initKoin(enableNetworkLogs: Boolean = false, appDeclaration: KoinAppDeclaration = {}) =
startKoin {
appDeclaration()
modules(commonModule(enableNetworkLogs = enableNetworkLogs), platformModule())
}
// called by iOS etc
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()) }
}
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
}
}
}

View file

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

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,5 +0,0 @@
package com.surrus.common.repository
import org.koin.core.module.Module
expect fun platformModule(): Module

View file

@ -1,91 +0,0 @@
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 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>
suspend fun fetchPeople(): List<Assignment>
suspend fun fetchAndStorePeople()
}
class PeopleInSpaceRepository : KoinComponent, PeopleInSpaceRepositoryInterface {
private val peopleInSpaceApi: PeopleInSpaceApi 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(): 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 fetchAndStorePeople() {
logger.d { "fetchAndStorePeople" }
try {
val result = peopleInSpaceApi.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" }
}
}
// 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,43 +0,0 @@
package com.surrus.peopleinspace
import com.surrus.common.di.PeopleInSpaceDatabaseWrapper
import com.surrus.common.di.commonModule
import com.surrus.common.repository.PeopleInSpaceRepositoryInterface
import com.surrus.common.repository.platformModule
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.koin.core.context.startKoin
import org.koin.dsl.module
import org.koin.test.KoinTest
import org.koin.test.inject
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertTrue
class PeopleInSpaceTest: KoinTest {
private val repo : PeopleInSpaceRepositoryInterface by inject()
@BeforeTest
fun setUp() {
Dispatchers.setMain(StandardTestDispatcher())
startKoin{
modules(
commonModule(true),
platformModule(),
module {
single { PeopleInSpaceDatabaseWrapper(null) }
}
)
}
}
@Test
fun testGetPeople() = runTest {
val result = repo.fetchPeople()
println(result)
assertTrue(result.isNotEmpty())
}
}

View file

@ -1,16 +0,0 @@
package com.surrus.common.repository
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() }
}

View file

@ -1,12 +0,0 @@
package com.surrus.common.repository
import com.surrus.common.di.PeopleInSpaceDatabaseWrapper
import io.ktor.client.engine.js.*
import org.koin.dsl.module
actual fun platformModule() = module {
single {
PeopleInSpaceDatabaseWrapper(null)
}
single { Js.create() }
}

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 +0,0 @@
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 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() }
}

View file

@ -1,15 +0,0 @@
package com.surrus.common.repository
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() }
}

View file

@ -1,15 +0,0 @@
package com.surrus.common.repository
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() }
}

View file

@ -1,2 +0,0 @@
/build
*.iml

View file

@ -1,29 +0,0 @@
import org.jetbrains.compose.compose
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm")
id("org.jetbrains.compose") version Versions.composeDesktopWeb
application
}
group = "me.joreilly"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
maven(url = "https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
dependencies {
implementation(compose.desktop.currentOs)
implementation(project(":common"))
}
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
}
application {
mainClass.set("MainKt")
}

View file

@ -1,176 +0,0 @@
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
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 kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.jetbrains.skia.Image.Companion.makeFromEncoded
import java.awt.image.BufferedImage
import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.net.HttpURLConnection
import java.net.URL
import javax.imageio.ImageIO
private val koin = initKoin(enableNetworkLogs = true).koin
fun main() = application {
val windowState = rememberWindowState()
var peopleState by remember { mutableStateOf(emptyList<Assignment>()) }
var selectedPerson by remember { mutableStateOf<Assignment?>(null) }
val peopleInSpaceApi = koin.get<PeopleInSpaceApi>()
LaunchedEffect(true) {
peopleState = peopleInSpaceApi.fetchPeople().people
selectedPerson = peopleState.first()
}
Window(
onCloseRequest = ::exitApplication,
state = windowState,
title = "People In Space"
) {
Row(Modifier.fillMaxSize()) {
Box(Modifier.width(250.dp).fillMaxHeight().background(color = Color.LightGray)) {
PersonList(peopleState, selectedPerson) {
selectedPerson = it
}
}
Spacer(modifier = Modifier.width(1.dp).fillMaxHeight())
Box(Modifier.fillMaxHeight()) {
selectedPerson?.let {
PersonDetailsView(it)
}
}
}
}
}
@Composable
fun PersonList(
people: List<Assignment>,
selectedPerson: Assignment?,
personSelected: (person: Assignment) -> Unit
) {
// workaround for compose desktop but if LazyColumn is empty
if (people.isNotEmpty()) {
LazyColumn {
items(people) { person ->
PersonView(person, selectedPerson, personSelected)
}
}
}
}
@Composable
fun PersonView(
person: Assignment,
selectedPerson: Assignment?,
personSelected: (person: Assignment) -> Unit
) {
Row(
modifier = Modifier.fillMaxWidth().clickable(onClick = { personSelected(person) })
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Column {
Text(
person.name,
style = if (person.name == selectedPerson?.name) MaterialTheme.typography.h6 else MaterialTheme.typography.body1
)
Text(text = person.craft, style = TextStyle(color = Color.DarkGray, fontSize = 14.sp))
}
}
}
@Composable
fun PersonDetailsView(person: Assignment) {
LazyColumn(
modifier = Modifier.padding(16.dp).fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
item(person) {
Text(person.name, style = MaterialTheme.typography.h4)
Spacer(modifier = Modifier.size(12.dp))
val imageUrl = person.personImageUrl
imageUrl?.let {
val imageAsset = fetchImage(it)
imageAsset?.let {
Image(
it,
contentDescription = "personName",
modifier = Modifier.size(240.dp)
)
}
}
Spacer(modifier = Modifier.size(24.dp))
val bio = person.personBio ?: ""
Text(bio, style = MaterialTheme.typography.body1)
}
}
}
@Composable
fun fetchImage(url: String): ImageBitmap? {
var image by remember(url) { mutableStateOf<ImageBitmap?>(null) }
LaunchedEffect(url) {
loadFullImage(url)?.let {
image = makeFromEncoded(toByteArray(it)).asImageBitmap()
}
}
return image
}
fun toByteArray(bitmap: BufferedImage): ByteArray {
val baos = ByteArrayOutputStream()
ImageIO.write(bitmap, "png", baos)
return baos.toByteArray()
}
suspend fun loadFullImage(source: String): BufferedImage? = withContext(Dispatchers.IO) {
runCatching {
val url = URL(source)
val connection: HttpURLConnection = url.openConnection() as HttpURLConnection
connection.connectTimeout = 5000
connection.connect()
val input: InputStream = connection.inputStream
val bitmap: BufferedImage? = ImageIO.read(input)
bitmap
}.getOrNull()
}

View file

@ -1,3 +0,0 @@
/build
*.iml

View file

@ -1,44 +0,0 @@
plugins {
kotlin("multiplatform")
id("org.jetbrains.compose") version Versions.composeDesktopWeb
}
version = "1.0"
repositories {
mavenCentral()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
kotlin {
js(IR) {
browser()
binaries.executable()
}
sourceSets {
val jsMain by getting {
dependencies {
implementation(compose.web.widgets)
implementation(compose.web.core)
implementation(compose.runtime)
implementation(project(":common"))
}
}
}
}
// workaround for https://youtrack.jetbrains.com/issue/KT-48273
afterEvaluate {
rootProject.extensions.configure<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension> {
versions.webpackDevServer.version = "4.0.0"
versions.webpackCli.version = "4.9.0"
}
}
compose.desktop {
application {
mainClass = ""
}
}

View file

@ -1,100 +0,0 @@
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.repository.PeopleInSpaceRepositoryInterface
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.flow.collect
import org.jetbrains.compose.common.foundation.layout.Column
import org.jetbrains.compose.web.css.*
import org.jetbrains.compose.web.dom.*
import org.jetbrains.compose.web.renderComposable
private val koin = initKoin(enableNetworkLogs = true).koin
@InternalCoroutinesApi
fun main() {
val repo = koin.get<PeopleInSpaceRepositoryInterface>()
renderComposable(rootElementId = "root") {
Style(TextStyles)
var people by remember { mutableStateOf(emptyList<Assignment>()) }
LaunchedEffect(true) {
people = repo.fetchPeople()
}
val issPosition by produceState(initialValue = IssPosition(0.0, 0.0), repo) {
repo.pollISSPosition().collect { value = it }
}
Div(attrs = { style { padding(16.px) } }) {
H1(attrs = { classes(TextStyles.titleText) }) {
Text("People In Space")
}
H2 {
Text("ISS Position: latitude = ${issPosition.latitude}, longitude = ${issPosition.longitude}")
}
people.forEach { person ->
Div(
attrs = {
style {
display(DisplayStyle.Flex)
alignItems(AlignItems.Center)
}
}
) {
val imageUrl = person.personImageUrl ?: ""
Img(
src = imageUrl,
attrs = {
style {
width(48.px)
property("padding-right", 16.px)
}
}
)
Span(attrs = { classes(TextStyles.personText) }) {
Text("${person.name} (${person.craft})")
}
}
}
}
}
}
object TextStyles : StyleSheet() {
val titleText by style {
color(rgb(23,24, 28))
fontSize(50.px)
property("font-size", 50.px)
property("letter-spacing", (-1.5).px)
property("font-weight", 900)
property("line-height", 58.px)
property(
"font-family",
"Gotham SSm A,Gotham SSm B,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Droid Sans,Helvetica Neue,Arial,sans-serif"
)
}
val personText by style {
color(rgb(23,24, 28))
fontSize(24.px)
property("font-size", 28.px)
property("letter-spacing", "normal")
property("font-weight", 300)
property("line-height", 40.px)
property(
"font-family",
"Gotham SSm A,Gotham SSm B,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Droid Sans,Helvetica Neue,Arial,sans-serif"
)
}
}

View file

@ -1,11 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>People In Space</title>
</head>
<body>
<div id="root"></div>
<script src="compose-web.js"></script>
</body>
</html>

View file

@ -1,30 +0,0 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# -XX:+UseParallelGC: https://developer.android.com/studio/releases/gradle-plugin#optimize-gc-jdk-11
org.gradle.jvmargs=-Xmx1536m -XX:+UseParallelGC
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=false
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
# XCode
xcodeproj=./ios/PeopleInSpaceSwiftUI
# Kotlin/Native clients can override this through updating common.podspec
kotlinVersion=1.6.10
kotlin.native.binary.memoryModel=experimental
kotlin.native.binary.freezing=disabled

Binary file not shown.

View file

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

185
gradlew vendored
View file

@ -1,185 +0,0 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

104
gradlew.bat vendored
View file

@ -1,104 +0,0 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

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()
}

Some files were not shown because too many files have changed in this diff Show more