diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..07940e1
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,229 @@
+# Created by https://www.gitignore.io/api/java,macos,android,androidstudio
+
+### Android ###
+# Built application files
+*.apk
+*.ap_
+
+# Allow launched apk
+!/zombietag.apk
+
+# Files for the ART/Dalvik VM
+*.dex
+
+# Java class files
+*.class
+
+# Generated files
+bin/
+gen/
+out/
+
+# Gradle files
+.gradle/
+build/
+
+# Local configuration file (sdk path, etc)
+local.properties
+
+# Proguard folder generated by Eclipse
+proguard/
+
+# Log Files
+*.log
+
+# Android Studio Navigation editor temp files
+.navigation/
+
+# Android Studio captures folder
+captures/
+
+# Intellij
+*.iml
+**/.idea/workspace.xml
+**/.idea/tasks.xml
+**/.idea/gradle.xml
+**/.idea/dictionaries
+**/.idea/libraries
+**/.idea/markdown-navigator/
+
+# External native build folder generated in Android Studio 2.2 and later
+.externalNativeBuild
+
+# Freeline
+freeline.py
+freeline/
+freeline_project_description.json
+
+### Android Patch ###
+gen-external-apklibs
+
+### AndroidStudio ###
+# Covers files to be ignored for android development using Android Studio.
+
+# Built application files
+
+# Files for the ART/Dalvik VM
+
+# Java class files
+
+# Generated files
+
+# Gradle files
+.gradle
+
+# Signing files
+.signing/
+
+# Local configuration file (sdk path, etc)
+
+# Proguard folder generated by Eclipse
+
+# Log Files
+
+# Android Studio
+/*/build/
+/*/local.properties
+/*/out
+/*/*/build
+/*/*/production
+*.ipr
+*~
+*.swp
+
+# Android Patch
+
+# External native build folder generated in Android Studio 2.2 and later
+
+# NDK
+obj/
+
+# IntelliJ IDEA
+*.iws
+/out/
+
+# User-specific configurations
+**/.idea/libraries/
+**/.idea/.name
+**/.idea/compiler.xml
+**/.idea/copyright/profiles_settings.xml
+**/.idea/encodings.xml
+**/.idea/misc.xml
+**/.idea/modules.xml
+**/.idea/scopes/scope_settings.xml
+**/.idea/vcs.xml
+**/.idea/jsLibraryMappings.xml
+**/.idea/datasources.xml
+**/.idea/dataSources.ids
+**/.idea/sqlDataSources.xml
+**/.idea/dynamic.xml
+**/.idea/uiDesigner.xml
+
+# OS-specific files
+.DS_Store
+.DS_Store?
+._*
+.Spotlight-V100
+.Trashes
+ehthumbs.db
+Thumbs.db
+
+# Legacy Eclipse project files
+.classpath
+.project
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.war
+*.ear
+
+# virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml)
+hs_err_pid*
+
+## Plugin-specific files:
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Mongo Explorer plugin
+.idea/mongoSettings.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+### Java ###
+# Compiled class file
+
+# Log file
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+
+# Package Files #
+*.jar
+*.zip
+*.tar.gz
+*.rar
+
+### AndroidStudio Patch ###
+
+!/gradle/wrapper/gradle-wrapper.jar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+
+### macOS ###
+*.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
+.TemporaryItems
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# IDE
+**/.vscode/
+**/.settings/
+
+# End of https://www.gitignore.io/api/java,macos,android,androidstudio
+**markdown-navigator.xml
+
+# google maps api key
+google_maps_api.xml
+google-services.json
+
+#amplify
+amplify/\#current-cloud-backend
+amplify/.config/local-*
+amplify/mock-data
+amplify/backend/amplify-meta.json
+amplify/backend/awscloudformation
+build/
+dist/
+node_modules/
+aws-exports.js
+awsconfiguration.json
\ No newline at end of file
diff --git a/.graphqlconfig.yml b/.graphqlconfig.yml
new file mode 100644
index 0000000..843227d
--- /dev/null
+++ b/.graphqlconfig.yml
@@ -0,0 +1,15 @@
+projects:
+ javatag:
+ schemaPath: app/src/main/graphql/schema.json
+ includes:
+ - app/src/main/graphql/**/*.graphql
+ excludes:
+ - ./amplify/**
+ extensions:
+ amplify:
+ codeGenTarget: ''
+ generatedFileName: ''
+ docsFilePath: app/src/main/graphql/com/amazonaws/amplify/generated/graphql
+extensions:
+ amplify:
+ version: 3
diff --git a/.idea/assetWizardSettings.xml b/.idea/assetWizardSettings.xml
new file mode 100644
index 0000000..4276ea6
--- /dev/null
+++ b/.idea/assetWizardSettings.xml
@@ -0,0 +1,149 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser
new file mode 100644
index 0000000..5030b8f
Binary files /dev/null and b/.idea/caches/build_file_checksums.ser differ
diff --git a/.idea/caches/gradle_models.ser b/.idea/caches/gradle_models.ser
new file mode 100644
index 0000000..fa0b55f
Binary files /dev/null and b/.idea/caches/gradle_models.ser differ
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..3279b6b
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ xmlns:android
+
+ ^$
+
+
+
+
+
+
+
+
+ xmlns:.*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:id
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:name
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ name
+
+ ^$
+
+
+
+
+
+
+
+
+ style
+
+ ^$
+
+
+
+
+
+
+
+
+ .*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+
+ http://schemas.android.com/apk/res/android
+
+
+ ANDROID_ATTRIBUTE_ORDER
+
+
+
+
+
+
+ .*
+
+ .*
+
+
+ BY_NAME
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 0000000..9b770a6
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 0a7fdb8..bfacb31 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,10 @@
# JavaTag: Code 401 Final Project
+## APK
+To download and play Zombie Tag click the zombie below
+
+[](https://github.com/JavaAwesome/JavaTag/blob/Dev/zombietag.apk)
+
## Team Members
* Ahren Swett
* James Dansie
@@ -25,6 +30,9 @@ No contribution guidelines at this point.
### User Stories
### Wireframes
+
+
+
### Project Scope
#### MVP
@@ -44,10 +52,56 @@ No contribution guidelines at this point.
### Daily Team Workflow
#### Day 1
+* Implemented the Google Maps object
+ * TAG possibility of Distance implemented
+ * TAG possibility of tapping tag button partially implemented
+* Figured out how to pass data to Google Maps to render pins
+* Setup DynamoDB
+ * Player model
+ * Session model
+* Created activities
+ * Map
+ * Main
+* Setup Cognito
+* Created recyclerview to pull current sessions
+* Created dummy team and session, loaded it to the database
+* Started logic to utilize tagging by pictures
+* Started logic to update which user is tagged
#### Day 2
+* Added permissions
+* Fixed recycler view :D
+* Creating a session and passing in the users lat/lon to create the boundary
+* Querying the database for sessions
+* Modified initializeMarkerAndPlayers to allow its self to override
+ * Sets the markers and colors based on players
+ * Now called from callback from querying the database
+* Created a query for a selected session
+ * Creates an instance based on that ID
+ * Creates the starting point based on the users lat/lon
+ * Converting players from database back into players
+ * Created overloaded constructor in Player to allow this
+* Completed logic to render players to map
+ * Still need to add refresh logic to the render
+ * Validated if a user is already in a session or not
+* Started fun logic when player is tagged to show some sort of notification
+ * Figured out Vibrator!
+* Allowed players to join existing sessions
#### Day 3
+* Camera logic is mostly config complete, need to save and pull from database
+ * Added a new permission for CAMERA
+* Figured out more logic of updating users in the database
+ * A users lat/lon will now update when moving
+* Created logic to prevent duplicate users each time we login/start app
+* Completed logic to render players on a map live!
+ * When a player joins a game, it creates their initial markers
+ * When a player moves, the markers moves
+ * When it marker collides with not it, not it becomes tagged
+ * When a player creates a session, it no longer crashes
+* Started styling the app!
+ * Figured out how to create a custom icon, theme, action bar, animations, and button design
+ * Started working on map pin icon
#### Day 4
@@ -112,4 +166,6 @@ Java 401 Instructional Team
* Jeff
* Ginger
+* Camera icon made by Pixel Buddha from Flaticon www.flaticon.com
+
## License
diff --git a/Software-Requirements.md b/Software-Requirements.md
new file mode 100644
index 0000000..28791fd
--- /dev/null
+++ b/Software-Requirements.md
@@ -0,0 +1,60 @@
+# Software Requirements
+
+## Vision
+To create an engaging, fun, and high-tech Android app that enables users to create custom games of Tag with their friends.
+
+## Scope (In/Out)
+### In
+Our app will provide the following functionality in the MVP:
+* Multiple users
+* Functionality to 'tag' other users
+* Notifications when users are tagged
+* A map view of the game players
+
+### Out
+Our app will not provide the following functionality in the MVP:
+* This app will never hold personal biometrics such as facial recognition on our server
+* This app will never devulge a user's location to members not in the current game group
+* This app will never sell a user's location to outside vendors
+
+## MVP
+Our MVP will include:
+* At least two total players (one other player)
+* Possible tag definitions, may change as we go:
+ * By distance
+ * By QR Code
+ * By taking a picture of them
+ * By facial recognition
+* Tag notifications
+
+## Stretch
+Our stretch goals include:
+* Showing multiple players on a map
+* Geo fence (bounds)
+* Control who can see the game by using authentication
+* Bread crumbs
+* Power ups
+* Themed games
+* Ability to have multiple games running
+* Ability to customize who is invited to a game
+* Customize notifications when tagged
+* Have a stats page that shows metrics including how far user has walked, how many times user has been tagged
+* Bonus items to find or purchase that give you extra powers like improved visibility, increased frequency of location updates, cloaking
+* Monetization
+
+## Functional Requirements
+Functional requirements of our MVP include:
+1. A user can view the location of all members in the current game group
+2. A user be notified when they have been tagged and when they have tagged someone else
+3. A user can log in and log out
+4. A user's map view will follow their movement during the game
+
+## Non-Functional Requirements
+Non-Functional requirements of our MVP include:
+1. A user’s password will be protected using Amplify Cognito
+2. The app will be tested using Espresso
+
+## Data Flow
+A user opens the app and signs in. The app pings their location at the point of sign in at a lower frequency than during active games. The app will display active games within a certain distance of the user's location as options to join an active game. The user will also have the option to create a new game instance for others nearby to join. Once the user is in an active game the app will ping their location at a defined frequency. The game starts with one person who is 'it'. Other users they tag also become 'it'. Those who are tagged mark themselves as tagged using the 'I got tagged' button. Games end when when all the users in the game instance have been tagged. The user can leave a game early by signing out of the app. Location pinging ceases when the game ends or the user signs out of the app.
+
+![Data Flow Sketch]()
diff --git a/USER_STORIES.md b/USER_STORIES.md
new file mode 100644
index 0000000..93ca6be
--- /dev/null
+++ b/USER_STORIES.md
@@ -0,0 +1,47 @@
+## Wants to Play With Friends
+As a user that likes to play games with their friends, I want an app to play a casual group game.
+
+### Feature Tasks:
+Create a game that a person can play with at least one other person
+
+### Acceptance Test
+Ensure that user can join a game with at least one other friend.
+
+## Exploration of a New Place
+As a user new to the area, I want a game that lets me explore my new stomping grounds so I can see new things.
+
+### Feature Task
+Create an Android app that includes a map of the user's area, so person can see / find new places.
+
+### Acceptance Tests
+* Ensure that person can see the map
+* Ensure the user can see their location on the map.
+
+## Exercise Goals
+As a user, I want an app that makes it fun to get some exercise, because I want to get in shape.
+
+### Feature Task
+* Create at least one feature that provides the user the opportunity to travel some distance by foot.
+* App has an easy to use, inviting interface.
+
+### Acceptance Test
+* Ensure user tests are positive
+* Ensure that player locations update accordingly while user(s) in motion.
+
+## Map Reading
+As a user that has little practice on reading a map, I want an app that pushes me a little to correlate where I am with where other objects or people are, so I can get better at my map reading skills.
+
+### Feature Task
+Create an app that has a clean and easy to use interface that lets users of all map-reading skill levels easily use the app.
+
+### Acceptance Test
+Ensure a user with zero map reading skills can figure out the app.
+
+## Login Ability
+As a user, I want to be able to have an account with the app, so I can have some game details saved.
+
+### Feature Task
+Create a login page where the user can create an account, login into their account, and have features available only after login.
+
+### Acceptance Test
+Username and hashed password is saved in the cloud.
diff --git a/amplify/.config/project-config.json b/amplify/.config/project-config.json
new file mode 100644
index 0000000..81ef3c9
--- /dev/null
+++ b/amplify/.config/project-config.json
@@ -0,0 +1,13 @@
+{
+ "projectName": "JavaTag",
+ "version": "2.0",
+ "frontend": "android",
+ "android": {
+ "config": {
+ "ResDir": "app/src/main/res"
+ }
+ },
+ "providers": [
+ "awscloudformation"
+ ]
+}
\ No newline at end of file
diff --git a/amplify/backend/api/javatag/parameters.json b/amplify/backend/api/javatag/parameters.json
new file mode 100644
index 0000000..ccee55d
--- /dev/null
+++ b/amplify/backend/api/javatag/parameters.json
@@ -0,0 +1,5 @@
+{
+ "AppSyncApiName": "javatag",
+ "DynamoDBBillingMode": "PAY_PER_REQUEST",
+ "DynamoDBEnableServerSideEncryption": "false"
+}
\ No newline at end of file
diff --git a/amplify/backend/api/javatag/schema.graphql b/amplify/backend/api/javatag/schema.graphql
new file mode 100644
index 0000000..dadb55d
--- /dev/null
+++ b/amplify/backend/api/javatag/schema.graphql
@@ -0,0 +1,18 @@
+type Session @model {
+ id: ID!
+ title: String!
+ players: [Player] @connection(name: "Players")
+ lat: Float!
+ lon: Float!
+ radius: Int!
+}
+
+type Player @model {
+ id: ID!
+ username: String!
+ session: Session @connection(name: "Players")
+ isIt: Boolean!
+ lat: Float!
+ lon: Float!
+ Photo: String
+}
diff --git a/amplify/backend/api/javatag/stacks/CustomResources.json b/amplify/backend/api/javatag/stacks/CustomResources.json
new file mode 100644
index 0000000..2d8cf0a
--- /dev/null
+++ b/amplify/backend/api/javatag/stacks/CustomResources.json
@@ -0,0 +1,61 @@
+{
+ "AWSTemplateFormatVersion": "2010-09-09",
+ "Description": "An auto-generated nested stack.",
+ "Metadata": {},
+ "Parameters": {
+ "AppSyncApiId": {
+ "Type": "String",
+ "Description": "The id of the AppSync API associated with this project."
+ },
+ "AppSyncApiName": {
+ "Type": "String",
+ "Description": "The name of the AppSync API",
+ "Default": "AppSyncSimpleTransform"
+ },
+ "env": {
+ "Type": "String",
+ "Description": "The environment name. e.g. Dev, Test, or Production",
+ "Default": "NONE"
+ },
+ "S3DeploymentBucket": {
+ "Type": "String",
+ "Description": "The S3 bucket containing all deployment assets for the project."
+ },
+ "S3DeploymentRootKey": {
+ "Type": "String",
+ "Description": "An S3 key relative to the S3DeploymentBucket that points to the root\nof the deployment directory."
+ }
+ },
+ "Resources": {
+ "EmptyResource": {
+ "Type": "Custom::EmptyResource",
+ "Condition": "AlwaysFalse"
+ }
+ },
+ "Conditions": {
+ "HasEnvironmentParameter": {
+ "Fn::Not": [
+ {
+ "Fn::Equals": [
+ {
+ "Ref": "env"
+ },
+ "NONE"
+ ]
+ }
+ ]
+ },
+ "AlwaysFalse": {
+ "Fn::Equals": [
+ "true",
+ "false"
+ ]
+ }
+ },
+ "Outputs": {
+ "EmptyOutput": {
+ "Description": "An empty output. You may delete this if you have at least one resource above.",
+ "Value": ""
+ }
+ }
+}
\ No newline at end of file
diff --git a/amplify/backend/api/javatag/transform.conf.json b/amplify/backend/api/javatag/transform.conf.json
new file mode 100644
index 0000000..c506ce7
--- /dev/null
+++ b/amplify/backend/api/javatag/transform.conf.json
@@ -0,0 +1,3 @@
+{
+ "Version": 4
+}
\ No newline at end of file
diff --git a/amplify/backend/auth/javatagcc3c0915/javatagcc3c0915-cloudformation-template.yml b/amplify/backend/auth/javatagcc3c0915/javatagcc3c0915-cloudformation-template.yml
new file mode 100644
index 0000000..ffaa6cb
--- /dev/null
+++ b/amplify/backend/auth/javatagcc3c0915/javatagcc3c0915-cloudformation-template.yml
@@ -0,0 +1,361 @@
+AWSTemplateFormatVersion: 2010-09-09
+
+Parameters:
+ env:
+ Type: String
+ authRoleArn:
+ Type: String
+ unauthRoleArn:
+ Type: String
+
+
+
+
+ identityPoolName:
+ Type: String
+
+ allowUnauthenticatedIdentities:
+ Type: String
+
+ resourceNameTruncated:
+ Type: String
+
+ userPoolName:
+ Type: String
+
+ autoVerifiedAttributes:
+ Type: CommaDelimitedList
+
+ mfaConfiguration:
+ Type: String
+
+ mfaTypes:
+ Type: CommaDelimitedList
+
+ smsAuthenticationMessage:
+ Type: String
+
+ smsVerificationMessage:
+ Type: String
+
+ emailVerificationSubject:
+ Type: String
+
+ emailVerificationMessage:
+ Type: String
+
+ defaultPasswordPolicy:
+ Type: String
+
+ passwordPolicyMinLength:
+ Type: Number
+
+ passwordPolicyCharacters:
+ Type: CommaDelimitedList
+
+ requiredAttributes:
+ Type: CommaDelimitedList
+
+ userpoolClientGenerateSecret:
+ Type: String
+
+ userpoolClientRefreshTokenValidity:
+ Type: Number
+
+ userpoolClientWriteAttributes:
+ Type: CommaDelimitedList
+
+ userpoolClientReadAttributes:
+ Type: CommaDelimitedList
+
+ userpoolClientLambdaRole:
+ Type: String
+
+ userpoolClientSetAttributes:
+ Type: String
+
+ resourceName:
+ Type: String
+
+ authSelections:
+ Type: String
+
+ useDefault:
+ Type: String
+
+ dependsOn:
+ Type: CommaDelimitedList
+
+Conditions:
+ ShouldNotCreateEnvResources: !Equals [ !Ref env, NONE ]
+
+Resources:
+
+
+ # BEGIN SNS ROLE RESOURCE
+ SNSRole:
+ # Created to allow the UserPool SMS Config to publish via the Simple Notification Service during MFA Process
+ Type: AWS::IAM::Role
+ Properties:
+ RoleName: !If [ShouldNotCreateEnvResources, 'javatacc3c0915_sns-role', !Join ['',['javatacc3c0915_sns-role', '-', !Ref env]]]
+ AssumeRolePolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ - Sid: ""
+ Effect: "Allow"
+ Principal:
+ Service: "cognito-idp.amazonaws.com"
+ Action:
+ - "sts:AssumeRole"
+ Condition:
+ StringEquals:
+ sts:ExternalId: javatacc3c0915_role_external_id
+ Policies:
+ -
+ PolicyName: javatacc3c0915-sns-policy
+ PolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ -
+ Effect: "Allow"
+ Action:
+ - "sns:Publish"
+ Resource: "*"
+ # BEGIN USER POOL RESOURCES
+ UserPool:
+ # Created upon user selection
+ # Depends on SNS Role for Arn if MFA is enabled
+ Type: AWS::Cognito::UserPool
+ UpdateReplacePolicy: Retain
+ Properties:
+ UserPoolName: !If [ShouldNotCreateEnvResources, !Ref userPoolName, !Join ['',[!Ref userPoolName, '-', !Ref env]]]
+
+ Schema:
+
+ -
+ Name: email
+ Required: true
+ Mutable: true
+
+
+
+
+ AutoVerifiedAttributes: !Ref autoVerifiedAttributes
+
+
+ EmailVerificationMessage: !Ref emailVerificationMessage
+ EmailVerificationSubject: !Ref emailVerificationSubject
+
+ Policies:
+ PasswordPolicy:
+ MinimumLength: !Ref passwordPolicyMinLength
+ RequireLowercase: false
+ RequireNumbers: false
+ RequireSymbols: false
+ RequireUppercase: false
+
+ MfaConfiguration: !Ref mfaConfiguration
+ SmsVerificationMessage: !Ref smsVerificationMessage
+ SmsConfiguration:
+ SnsCallerArn: !GetAtt SNSRole.Arn
+ ExternalId: javatacc3c0915_role_external_id
+
+
+ UserPoolClientWeb:
+ # Created provide application access to user pool
+ # Depends on UserPool for ID reference
+ Type: "AWS::Cognito::UserPoolClient"
+ Properties:
+ ClientName: javatacc3c0915_app_clientWeb
+
+ RefreshTokenValidity: !Ref userpoolClientRefreshTokenValidity
+ UserPoolId: !Ref UserPool
+ DependsOn: UserPool
+ UserPoolClient:
+ # Created provide application access to user pool
+ # Depends on UserPool for ID reference
+ Type: "AWS::Cognito::UserPoolClient"
+ Properties:
+ ClientName: javatacc3c0915_app_client
+
+ GenerateSecret: !Ref userpoolClientGenerateSecret
+ RefreshTokenValidity: !Ref userpoolClientRefreshTokenValidity
+ UserPoolId: !Ref UserPool
+ DependsOn: UserPool
+ # BEGIN USER POOL LAMBDA RESOURCES
+ UserPoolClientRole:
+ # Created to execute Lambda which gets userpool app client config values
+ Type: 'AWS::IAM::Role'
+ Properties:
+ RoleName: !If [ShouldNotCreateEnvResources, !Ref userpoolClientLambdaRole, !Join ['',[!Ref userpoolClientLambdaRole, '-', !Ref env]]]
+ AssumeRolePolicyDocument:
+ Version: '2012-10-17'
+ Statement:
+ - Effect: Allow
+ Principal:
+ Service:
+ - lambda.amazonaws.com
+ Action:
+ - 'sts:AssumeRole'
+ DependsOn: UserPoolClient
+ UserPoolClientLambda:
+ # Lambda which gets userpool app client config values
+ # Depends on UserPool for id
+ # Depends on UserPoolClientRole for role ARN
+ Type: 'AWS::Lambda::Function'
+ Properties:
+ Code:
+ ZipFile: !Join
+ - |+
+ - - 'const response = require(''cfn-response'');'
+ - 'const aws = require(''aws-sdk'');'
+ - 'const identity = new aws.CognitoIdentityServiceProvider();'
+ - 'exports.handler = (event, context, callback) => {'
+ - ' if (event.RequestType == ''Delete'') { '
+ - ' response.send(event, context, response.SUCCESS, {})'
+ - ' }'
+ - ' if (event.RequestType == ''Update'' || event.RequestType == ''Create'') {'
+ - ' const params = {'
+ - ' ClientId: event.ResourceProperties.clientId,'
+ - ' UserPoolId: event.ResourceProperties.userpoolId'
+ - ' };'
+ - ' identity.describeUserPoolClient(params).promise()'
+ - ' .then((res) => {'
+ - ' response.send(event, context, response.SUCCESS, {''appSecret'': res.UserPoolClient.ClientSecret});'
+ - ' })'
+ - ' .catch((err) => {'
+ - ' response.send(event, context, response.FAILED, {err});'
+ - ' });'
+ - ' }'
+ - '};'
+ Handler: index.handler
+ Runtime: nodejs8.10
+ Timeout: '300'
+ Role: !GetAtt
+ - UserPoolClientRole
+ - Arn
+ DependsOn: UserPoolClientRole
+ UserPoolClientLambdaPolicy:
+ # Sets userpool policy for the role that executes the Userpool Client Lambda
+ # Depends on UserPool for Arn
+ # Marked as depending on UserPoolClientRole for easier to understand CFN sequencing
+ Type: 'AWS::IAM::Policy'
+ Properties:
+ PolicyName: javatacc3c0915_userpoolclient_lambda_iam_policy
+ Roles:
+ - !If [ShouldNotCreateEnvResources, !Ref userpoolClientLambdaRole, !Join ['',[!Ref userpoolClientLambdaRole, '-', !Ref env]]]
+ PolicyDocument:
+ Version: '2012-10-17'
+ Statement:
+ - Effect: Allow
+ Action:
+ - 'cognito-idp:DescribeUserPoolClient'
+ Resource: !GetAtt UserPool.Arn
+ DependsOn: UserPoolClientLambda
+ UserPoolClientLogPolicy:
+ # Sets log policy for the role that executes the Userpool Client Lambda
+ # Depends on UserPool for Arn
+ # Marked as depending on UserPoolClientLambdaPolicy for easier to understand CFN sequencing
+ Type: 'AWS::IAM::Policy'
+ Properties:
+ PolicyName: javatacc3c0915_userpoolclient_lambda_log_policy
+ Roles:
+ - !If [ShouldNotCreateEnvResources, !Ref userpoolClientLambdaRole, !Join ['',[!Ref userpoolClientLambdaRole, '-', !Ref env]]]
+ PolicyDocument:
+ Version: 2012-10-17
+ Statement:
+ - Effect: Allow
+ Action:
+ - 'logs:CreateLogGroup'
+ - 'logs:CreateLogStream'
+ - 'logs:PutLogEvents'
+ Resource: !Sub
+ - arn:aws:logs:${region}:${account}:log-group:/aws/lambda/${lambda}:log-stream:*
+ - { region: !Ref "AWS::Region", account: !Ref "AWS::AccountId", lambda: !Ref UserPoolClientLambda}
+ DependsOn: UserPoolClientLambdaPolicy
+ UserPoolClientInputs:
+ # Values passed to Userpool client Lambda
+ # Depends on UserPool for Id
+ # Depends on UserPoolClient for Id
+ # Marked as depending on UserPoolClientLambdaPolicy for easier to understand CFN sequencing
+ Type: 'Custom::LambdaCallout'
+ Properties:
+ ServiceToken: !GetAtt UserPoolClientLambda.Arn
+ clientId: !Ref UserPoolClient
+ userpoolId: !Ref UserPool
+ DependsOn: UserPoolClientLogPolicy
+
+
+
+
+
+
+
+ # BEGIN IDENTITY POOL RESOURCES
+
+
+ IdentityPool:
+ # Always created
+ Type: AWS::Cognito::IdentityPool
+ Properties:
+ IdentityPoolName: !If [ShouldNotCreateEnvResources, 'javatagcc3c0915_identitypool_cc3c0915', !Join ['',['javatagcc3c0915_identitypool_cc3c0915', '__', !Ref env]]]
+
+ CognitoIdentityProviders:
+ - ClientId: !Ref UserPoolClient
+ ProviderName: !Sub
+ - cognito-idp.${region}.amazonaws.com/${client}
+ - { region: !Ref "AWS::Region", client: !Ref UserPool}
+ - ClientId: !Ref UserPoolClientWeb
+ ProviderName: !Sub
+ - cognito-idp.${region}.amazonaws.com/${client}
+ - { region: !Ref "AWS::Region", client: !Ref UserPool}
+
+ AllowUnauthenticatedIdentities: !Ref allowUnauthenticatedIdentities
+
+
+ DependsOn: UserPoolClientInputs
+
+
+ IdentityPoolRoleMap:
+ # Created to map Auth and Unauth roles to the identity pool
+ # Depends on Identity Pool for ID ref
+ Type: AWS::Cognito::IdentityPoolRoleAttachment
+ Properties:
+ IdentityPoolId: !Ref IdentityPool
+ Roles:
+ unauthenticated: !Ref unauthRoleArn
+ authenticated: !Ref authRoleArn
+ DependsOn: IdentityPool
+
+
+Outputs :
+
+ IdentityPoolId:
+ Value: !Ref 'IdentityPool'
+ Description: Id for the identity pool
+ IdentityPoolName:
+ Value: !GetAtt IdentityPool.Name
+
+
+
+
+ UserPoolId:
+ Value: !Ref 'UserPool'
+ Description: Id for the user pool
+ UserPoolName:
+ Value: !Ref userPoolName
+ AppClientIDWeb:
+ Value: !Ref 'UserPoolClientWeb'
+ Description: The user pool app client id for web
+ AppClientID:
+ Value: !Ref 'UserPoolClient'
+ Description: The user pool app client id
+ AppClientSecret:
+ Value: !GetAtt UserPoolClientInputs.appSecret
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/amplify/backend/auth/javatagcc3c0915/parameters.json b/amplify/backend/auth/javatagcc3c0915/parameters.json
new file mode 100644
index 0000000..d3c08e6
--- /dev/null
+++ b/amplify/backend/auth/javatagcc3c0915/parameters.json
@@ -0,0 +1,49 @@
+{
+ "identityPoolName": "javatagcc3c0915_identitypool_cc3c0915",
+ "allowUnauthenticatedIdentities": false,
+ "resourceNameTruncated": "javatacc3c0915",
+ "userPoolName": "javatagcc3c0915_userpool_cc3c0915",
+ "autoVerifiedAttributes": [
+ "email"
+ ],
+ "mfaConfiguration": "OFF",
+ "mfaTypes": [
+ "SMS Text Message"
+ ],
+ "smsAuthenticationMessage": "Your authentication code is {####}",
+ "smsVerificationMessage": "Your verification code is {####}",
+ "emailVerificationSubject": "Your verification code",
+ "emailVerificationMessage": "Your verification code is {####}",
+ "defaultPasswordPolicy": false,
+ "passwordPolicyMinLength": 8,
+ "passwordPolicyCharacters": [],
+ "requiredAttributes": [
+ "email"
+ ],
+ "userpoolClientGenerateSecret": true,
+ "userpoolClientRefreshTokenValidity": 30,
+ "userpoolClientWriteAttributes": [
+ "email"
+ ],
+ "userpoolClientReadAttributes": [
+ "email"
+ ],
+ "userpoolClientLambdaRole": "javatacc3c0915_userpoolclient_lambda_role",
+ "userpoolClientSetAttributes": false,
+ "resourceName": "javatagcc3c0915",
+ "authSelections": "identityPoolAndUserPool",
+ "authRoleArn": {
+ "Fn::GetAtt": [
+ "AuthRole",
+ "Arn"
+ ]
+ },
+ "unauthRoleArn": {
+ "Fn::GetAtt": [
+ "UnauthRole",
+ "Arn"
+ ]
+ },
+ "useDefault": "default",
+ "dependsOn": []
+}
\ No newline at end of file
diff --git a/amplify/backend/backend-config.json b/amplify/backend/backend-config.json
new file mode 100644
index 0000000..71b4e5c
--- /dev/null
+++ b/amplify/backend/backend-config.json
@@ -0,0 +1,33 @@
+{
+ "api": {
+ "javatag": {
+ "service": "AppSync",
+ "providerPlugin": "awscloudformation",
+ "output": {
+ "authConfig": {
+ "additionalAuthenticationProviders": [],
+ "defaultAuthentication": {
+ "authenticationType": "API_KEY",
+ "apiKeyConfig": {
+ "description": "tagkey",
+ "apiKeyExpirationDays": "365"
+ }
+ }
+ }
+ }
+ }
+ },
+ "auth": {
+ "javatagcc3c0915": {
+ "service": "Cognito",
+ "providerPlugin": "awscloudformation",
+ "dependsOn": []
+ }
+ },
+ "storage": {
+ "s390656fcb": {
+ "service": "S3",
+ "providerPlugin": "awscloudformation"
+ }
+ }
+}
\ No newline at end of file
diff --git a/amplify/backend/storage/s390656fcb/parameters.json b/amplify/backend/storage/s390656fcb/parameters.json
new file mode 100644
index 0000000..8ee812d
--- /dev/null
+++ b/amplify/backend/storage/s390656fcb/parameters.json
@@ -0,0 +1,35 @@
+{
+ "bucketName": "javatag091c7e33ab0441e4bdf34cbdf68d2bd1",
+ "authPolicyName": "s3_amplify_90656fcb",
+ "unauthPolicyName": "s3_amplify_90656fcb",
+ "authRoleName": {
+ "Ref": "AuthRoleName"
+ },
+ "unauthRoleName": {
+ "Ref": "UnauthRoleName"
+ },
+ "selectedGuestPermissions": [
+ "s3:GetObject",
+ "s3:ListBucket"
+ ],
+ "selectedAuthenticatedPermissions": [
+ "s3:PutObject",
+ "s3:GetObject",
+ "s3:ListBucket",
+ "s3:DeleteObject"
+ ],
+ "s3PermissionsAuthenticatedPublic": "s3:PutObject,s3:GetObject,s3:DeleteObject",
+ "s3PublicPolicy": "Public_policy_2475756c",
+ "s3PermissionsAuthenticatedUploads": "s3:PutObject",
+ "s3UploadsPolicy": "Uploads_policy_2475756c",
+ "s3PermissionsAuthenticatedProtected": "s3:PutObject,s3:GetObject,s3:DeleteObject",
+ "s3ProtectedPolicy": "Protected_policy_2475756c",
+ "s3PermissionsAuthenticatedPrivate": "s3:PutObject,s3:GetObject,s3:DeleteObject",
+ "s3PrivatePolicy": "Private_policy_2475756c",
+ "AuthenticatedAllowList": "ALLOW",
+ "s3ReadPolicy": "read_policy_2475756c",
+ "s3PermissionsGuestPublic": "DISALLOW",
+ "s3PermissionsGuestUploads": "DISALLOW",
+ "GuestAllowList": "DISALLOW",
+ "triggerFunction": "NONE"
+}
\ No newline at end of file
diff --git a/amplify/backend/storage/s390656fcb/s3-cloudformation-template.json b/amplify/backend/storage/s390656fcb/s3-cloudformation-template.json
new file mode 100644
index 0000000..73848eb
--- /dev/null
+++ b/amplify/backend/storage/s390656fcb/s3-cloudformation-template.json
@@ -0,0 +1,631 @@
+{
+ "AWSTemplateFormatVersion": "2010-09-09",
+ "Description": "S3 resource stack creation using Amplify CLI",
+ "Parameters": {
+ "bucketName": {
+ "Type": "String"
+ },
+ "authPolicyName": {
+ "Type": "String"
+ },
+ "unauthPolicyName": {
+ "Type": "String"
+ },
+ "authRoleName": {
+ "Type": "String"
+ },
+ "unauthRoleName": {
+ "Type": "String"
+ },
+ "s3PublicPolicy": {
+ "Type": "String"
+ },
+ "s3PrivatePolicy": {
+ "Type": "String"
+ },
+ "s3ProtectedPolicy": {
+ "Type": "String"
+ },
+ "s3UploadsPolicy": {
+ "Type": "String"
+ },
+ "s3ReadPolicy": {
+ "Type": "String"
+ },
+ "s3PermissionsAuthenticatedPublic": {
+ "Type": "String"
+ },
+ "s3PermissionsAuthenticatedProtected": {
+ "Type": "String"
+ },
+ "s3PermissionsAuthenticatedPrivate": {
+ "Type": "String"
+ },
+ "s3PermissionsAuthenticatedUploads": {
+ "Type": "String"
+ },
+ "s3PermissionsGuestPublic": {
+ "Type": "String",
+ "Default" : "DISALLOW"
+ },
+ "s3PermissionsGuestUploads": {
+ "Type": "String",
+ "Default" : "DISALLOW" },
+ "AuthenticatedAllowList": {
+ "Type": "String"
+ },
+ "GuestAllowList": {
+ "Type": "String",
+ "Default" : "DISALLOW"
+ },
+ "selectedGuestPermissions": {
+ "Type": "CommaDelimitedList"
+ },
+ "selectedAuthenticatedPermissions": {
+ "Type": "CommaDelimitedList"
+ },
+ "env": {
+ "Type": "String"
+ },
+ "triggerFunction": {
+ "Type": "String"
+ }
+
+
+ },
+ "Conditions": {
+ "ShouldNotCreateEnvResources": {
+ "Fn::Equals": [
+ {
+ "Ref": "env"
+ },
+ "NONE"
+ ]
+ },
+ "CreateAuthPublic": {
+ "Fn::Not" : [{
+ "Fn::Equals" : [
+ {"Ref" : "s3PermissionsAuthenticatedPublic"},
+ "DISALLOW"
+ ]
+ }]
+ },
+ "CreateAuthProtected": {
+ "Fn::Not" : [{
+ "Fn::Equals" : [
+ {"Ref" : "s3PermissionsAuthenticatedProtected"},
+ "DISALLOW"
+ ]
+ }]
+ },
+ "CreateAuthPrivate": {
+ "Fn::Not" : [{
+ "Fn::Equals" : [
+ {"Ref" : "s3PermissionsAuthenticatedPrivate"},
+ "DISALLOW"
+ ]
+ }]
+ },
+ "CreateAuthUploads": {
+ "Fn::Not" : [{
+ "Fn::Equals" : [
+ {"Ref" : "s3PermissionsAuthenticatedUploads"},
+ "DISALLOW"
+ ]
+ }]
+ },
+ "CreateGuestPublic": {
+ "Fn::Not" : [{
+ "Fn::Equals" : [
+ {"Ref" : "s3PermissionsGuestPublic"},
+ "DISALLOW"
+ ]
+ }]
+ },
+ "CreateGuestUploads": {
+ "Fn::Not" : [{
+ "Fn::Equals" : [
+ {"Ref" : "s3PermissionsGuestUploads"},
+ "DISALLOW"
+ ]
+ }]
+ },
+ "AuthReadAndList": {
+ "Fn::Not" : [{
+ "Fn::Equals" : [
+ {"Ref" : "AuthenticatedAllowList"},
+ "DISALLOW"
+ ]
+ }]
+ },
+ "GuestReadAndList": {
+ "Fn::Not" : [{
+ "Fn::Equals" : [
+ {"Ref" : "GuestAllowList"},
+ "DISALLOW"
+ ]
+ }]
+ }
+ },
+ "Resources": {
+ "S3Bucket": {
+ "Type": "AWS::S3::Bucket",
+
+ "DeletionPolicy" : "Retain",
+ "Properties": {
+ "BucketName": {
+ "Fn::If": [
+ "ShouldNotCreateEnvResources",
+ {
+ "Ref": "bucketName"
+ },
+ {
+ "Fn::Join": [
+ "",
+ [
+ {
+ "Ref": "bucketName"
+ },
+ "-",
+ {
+ "Ref": "env"
+ }
+ ]
+ ]
+ }
+ ]
+ },
+
+ "CorsConfiguration": {
+ "CorsRules": [
+ {
+ "AllowedHeaders": [
+ "*"
+ ],
+ "AllowedMethods": [
+ "GET",
+ "HEAD",
+ "PUT",
+ "POST",
+ "DELETE"
+ ],
+ "AllowedOrigins": [
+ "*"
+ ],
+ "ExposedHeaders": [
+ "x-amz-server-side-encryption",
+ "x-amz-request-id",
+ "x-amz-id-2",
+ "ETag"
+ ],
+ "Id": "S3CORSRuleId1",
+ "MaxAge": "3000"
+ }
+ ]
+ }
+ }
+ },
+
+ "S3AuthPublicPolicy": {
+ "DependsOn": [
+ "S3Bucket"
+ ],
+ "Condition": "CreateAuthPublic",
+ "Type": "AWS::IAM::Policy",
+ "Properties": {
+ "PolicyName": {
+ "Ref": "s3PublicPolicy"
+ },
+ "Roles": [
+ {
+ "Ref": "authRoleName"
+ }
+ ],
+ "PolicyDocument": {
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Action": {
+ "Fn::Split" : [ "," , {
+ "Ref": "s3PermissionsAuthenticatedPublic"
+ } ]
+ },
+ "Resource": [
+ {
+ "Fn::Join": [
+ "",
+ [
+ "arn:aws:s3:::",
+ {
+ "Ref": "S3Bucket"
+ },
+ "/public/*"
+ ]
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "S3AuthProtectedPolicy": {
+ "DependsOn": [
+ "S3Bucket"
+ ],
+ "Condition": "CreateAuthProtected",
+ "Type": "AWS::IAM::Policy",
+ "Properties": {
+ "PolicyName": {
+ "Ref": "s3ProtectedPolicy"
+ },
+ "Roles": [
+ {
+ "Ref": "authRoleName"
+ }
+ ],
+ "PolicyDocument": {
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Action": {
+ "Fn::Split" : [ "," , {
+ "Ref": "s3PermissionsAuthenticatedProtected"
+ } ]
+ },
+ "Resource": [
+ {
+ "Fn::Join": [
+ "",
+ [
+ "arn:aws:s3:::",
+ {
+ "Ref": "S3Bucket"
+ },
+ "/protected/${cognito-identity.amazonaws.com:sub}/*"
+ ]
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "S3AuthPrivatePolicy": {
+ "DependsOn": [
+ "S3Bucket"
+ ],
+ "Condition": "CreateAuthPrivate",
+ "Type": "AWS::IAM::Policy",
+ "Properties": {
+ "PolicyName": {
+ "Ref": "s3PrivatePolicy"
+ },
+ "Roles": [
+ {
+ "Ref": "authRoleName"
+ }
+ ],
+ "PolicyDocument": {
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Action": {
+ "Fn::Split" : [ "," , {
+ "Ref": "s3PermissionsAuthenticatedPrivate"
+ } ]
+ },
+ "Resource": [
+ {
+ "Fn::Join": [
+ "",
+ [
+ "arn:aws:s3:::",
+ {
+ "Ref": "S3Bucket"
+ },
+ "/private/${cognito-identity.amazonaws.com:sub}/*"
+ ]
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "S3AuthUploadPolicy": {
+ "DependsOn": [
+ "S3Bucket"
+ ],
+ "Condition": "CreateAuthUploads",
+ "Type": "AWS::IAM::Policy",
+ "Properties": {
+ "PolicyName": {
+ "Ref": "s3UploadsPolicy"
+ },
+ "Roles": [
+ {
+ "Ref": "authRoleName"
+ }
+ ],
+ "PolicyDocument": {
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Action": {
+ "Fn::Split" : [ "," , {
+ "Ref": "s3PermissionsAuthenticatedUploads"
+ } ]
+ },
+ "Resource": [
+ {
+ "Fn::Join": [
+ "",
+ [
+ "arn:aws:s3:::",
+ {
+ "Ref": "S3Bucket"
+ },
+ "/uploads/*"
+ ]
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "S3AuthReadPolicy": {
+ "DependsOn": [
+ "S3Bucket"
+ ],
+ "Condition": "AuthReadAndList",
+ "Type": "AWS::IAM::Policy",
+ "Properties": {
+ "PolicyName": {
+ "Ref": "s3ReadPolicy"
+ },
+ "Roles": [
+ {
+ "Ref": "authRoleName"
+ }
+ ],
+ "PolicyDocument": {
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Action": [
+ "s3:GetObject"
+ ],
+ "Resource": [
+ {
+ "Fn::Join": [
+ "",
+ [
+ "arn:aws:s3:::",
+ {
+ "Ref": "S3Bucket"
+ },
+ "/protected/*"
+ ]
+ ]
+ }
+ ]
+ },
+ {
+ "Effect": "Allow",
+ "Action": [
+ "s3:ListBucket"
+ ],
+ "Resource": [
+ {
+ "Fn::Join": [
+ "",
+ [
+ "arn:aws:s3:::",
+ {
+ "Ref": "S3Bucket"
+ }
+ ]
+ ]
+ }
+ ],
+ "Condition": {
+ "StringLike": {
+ "s3:prefix": [
+ "public/",
+ "public/*",
+ "protected/",
+ "protected/*",
+ "private/${cognito-identity.amazonaws.com:sub}/",
+ "private/${cognito-identity.amazonaws.com:sub}/*"
+ ]
+ }
+ }
+ }
+ ]
+ }
+ }
+ },
+ "S3GuestPublicPolicy": {
+ "DependsOn": [
+ "S3Bucket"
+ ],
+ "Condition": "CreateGuestPublic",
+ "Type": "AWS::IAM::Policy",
+ "Properties": {
+ "PolicyName": {
+ "Ref": "s3PublicPolicy"
+ },
+ "Roles": [
+ {
+ "Ref": "unauthRoleName"
+ }
+ ],
+ "PolicyDocument": {
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Action": {
+ "Fn::Split" : [ "," , {
+ "Ref": "s3PermissionsGuestPublic"
+ } ]
+ },
+ "Resource": [
+ {
+ "Fn::Join": [
+ "",
+ [
+ "arn:aws:s3:::",
+ {
+ "Ref": "S3Bucket"
+ },
+ "/public/*"
+ ]
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "S3GuestUploadPolicy": {
+ "DependsOn": [
+ "S3Bucket"
+ ],
+ "Condition": "CreateGuestUploads",
+ "Type": "AWS::IAM::Policy",
+ "Properties": {
+ "PolicyName": {
+ "Ref": "s3UploadsPolicy"
+ },
+ "Roles": [
+ {
+ "Ref": "unauthRoleName"
+ }
+ ],
+ "PolicyDocument": {
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Action": {
+ "Fn::Split" : [ "," , {
+ "Ref": "s3PermissionsGuestUploads"
+ } ]
+ },
+ "Resource": [
+ {
+ "Fn::Join": [
+ "",
+ [
+ "arn:aws:s3:::",
+ {
+ "Ref": "S3Bucket"
+ },
+ "/uploads/*"
+ ]
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "S3GuestReadPolicy": {
+ "DependsOn": [
+ "S3Bucket"
+ ],
+ "Condition": "GuestReadAndList",
+ "Type": "AWS::IAM::Policy",
+ "Properties": {
+ "PolicyName": {
+ "Ref": "s3ReadPolicy"
+ },
+ "Roles": [
+ {
+ "Ref": "unauthRoleName"
+ }
+ ],
+ "PolicyDocument": {
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Action": [
+ "s3:GetObject"
+ ],
+ "Resource": [
+ {
+ "Fn::Join": [
+ "",
+ [
+ "arn:aws:s3:::",
+ {
+ "Ref": "S3Bucket"
+ },
+ "/protected/*"
+ ]
+ ]
+ }
+ ]
+ },
+ {
+ "Effect": "Allow",
+ "Action": [
+ "s3:ListBucket"
+ ],
+ "Resource": [
+ {
+ "Fn::Join": [
+ "",
+ [
+ "arn:aws:s3:::",
+ {
+ "Ref": "S3Bucket"
+ }
+ ]
+ ]
+ }
+ ],
+ "Condition": {
+ "StringLike": {
+ "s3:prefix": [
+ "public/",
+ "public/*",
+ "protected/",
+ "protected/*"
+ ]
+ }
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "Outputs": {
+ "BucketName": {
+ "Value": {
+ "Ref": "S3Bucket"
+ },
+ "Description": "Bucket name for the S3 bucket"
+ },
+ "Region": {
+ "Value": {
+ "Ref": "AWS::Region"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/amplify/team-provider-info.json b/amplify/team-provider-info.json
new file mode 100644
index 0000000..8fdb336
--- /dev/null
+++ b/amplify/team-provider-info.json
@@ -0,0 +1,19 @@
+{
+ "local": {
+ "awscloudformation": {
+ "AuthRoleName": "amplify-javatag-local-112001-authRole",
+ "UnauthRoleArn": "arn:aws:iam::798671911408:role/amplify-javatag-local-112001-unauthRole",
+ "AuthRoleArn": "arn:aws:iam::798671911408:role/amplify-javatag-local-112001-authRole",
+ "Region": "us-west-2",
+ "DeploymentBucketName": "amplify-javatag-local-112001-deployment",
+ "UnauthRoleName": "amplify-javatag-local-112001-unauthRole",
+ "StackName": "amplify-javatag-local-112001",
+ "StackId": "arn:aws:cloudformation:us-west-2:798671911408:stack/amplify-javatag-local-112001/6ae1ca10-0a38-11ea-93e5-0a13a2454304"
+ },
+ "categories": {
+ "auth": {
+ "javatagcc3c0915": {}
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..3543521
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..dda8ef5
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,78 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 29
+ buildToolsVersion "29.0.2"
+ defaultConfig {
+ applicationId "com.javaawesome.tag"
+ minSdkVersion 26
+ targetSdkVersion 29
+ versionCode 1
+ versionName "1.0"
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ }
+}
+
+apply plugin: 'com.amazonaws.appsync'
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation 'androidx.appcompat:appcompat:1.1.0'
+ implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+ implementation 'androidx.legacy:legacy-support-v4:1.0.0'
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.1'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
+
+ //Google Services
+ implementation 'com.google.android.gms:play-services-maps:16.1.0'
+ implementation 'com.google.android.gms:play-services-location:15.0.1'
+
+ //Base SDK
+ implementation 'com.amazonaws:aws-android-sdk-core:2.15.+'
+ //AppSync SDK
+ implementation 'com.amazonaws:aws-android-sdk-appsync:2.8.+'
+ implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.0'
+ implementation 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1'
+
+ //For AWSMobileClient only:
+ implementation 'com.amazonaws:aws-android-sdk-mobile-client:2.15.+'
+
+ //For the drop-in UI also:
+ implementation 'com.amazonaws:aws-android-sdk-auth-userpools:2.15.+'
+ implementation 'com.amazonaws:aws-android-sdk-auth-ui:2.15.+'
+ implementation 'androidx.recyclerview:recyclerview:1.0.0'
+
+ //s3
+ implementation 'com.amazonaws:aws-android-sdk-s3:2.15.+'
+ implementation('com.amazonaws:aws-android-sdk-mobile-client:2.15.+@aar') { transitive = true }
+ implementation('com.amazonaws:aws-android-sdk-auth-userpools:2.15.+@aar') { transitive = true }
+
+
+ // CameraX core library
+ def camerax_version = "1.0.0-alpha05"
+ // CameraX view library
+ def camerax_view_version = "1.0.0-alpha03"
+ // CameraX extensions library
+ def camerax_ext_version = "1.0.0-alpha03"
+
+ implementation "androidx.camera:camera-core:$camerax_version"
+ // If you want to use Camera2 extensions
+ implementation "androidx.camera:camera-camera2:$camerax_version"
+ // If you to use the Camera View class
+ implementation "androidx.camera:camera-view:$camerax_view_version"
+ // If you to use Camera Extensions
+ implementation "androidx.camera:camera-extensions:$camerax_ext_version"
+ implementation 'com.google.android.material:material:1.0.0'
+
+}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..6e7ffa9
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# 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
diff --git a/app/src/androidTest/java/com/javaawesome/tag/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/javaawesome/tag/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..8e6941a
--- /dev/null
+++ b/app/src/androidTest/java/com/javaawesome/tag/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package com.javaawesome.tag;
+
+import android.content.Context;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ assertEquals("com.javaawesome.tag", appContext.getPackageName());
+ }
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..5460d57
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/graphql/com/amazonaws/amplify/generated/graphql/mutations.graphql b/app/src/main/graphql/com/amazonaws/amplify/generated/graphql/mutations.graphql
new file mode 100644
index 0000000..74936f1
--- /dev/null
+++ b/app/src/main/graphql/com/amazonaws/amplify/generated/graphql/mutations.graphql
@@ -0,0 +1,121 @@
+# this is an auto generated file. This will be overwritten
+mutation CreateSession($input: CreateSessionInput!) {
+ createSession(input: $input) {
+ id
+ title
+ players {
+ items {
+ id
+ username
+ isIt
+ lat
+ lon
+ Photo
+ }
+ nextToken
+ }
+ lat
+ lon
+ radius
+ }
+}
+mutation UpdateSession($input: UpdateSessionInput!) {
+ updateSession(input: $input) {
+ id
+ title
+ players {
+ items {
+ id
+ username
+ isIt
+ lat
+ lon
+ Photo
+ }
+ nextToken
+ }
+ lat
+ lon
+ radius
+ }
+}
+mutation DeleteSession($input: DeleteSessionInput!) {
+ deleteSession(input: $input) {
+ id
+ title
+ players {
+ items {
+ id
+ username
+ isIt
+ lat
+ lon
+ Photo
+ }
+ nextToken
+ }
+ lat
+ lon
+ radius
+ }
+}
+mutation CreatePlayer($input: CreatePlayerInput!) {
+ createPlayer(input: $input) {
+ id
+ username
+ session {
+ id
+ title
+ players {
+ nextToken
+ }
+ lat
+ lon
+ radius
+ }
+ isIt
+ lat
+ lon
+ Photo
+ }
+}
+mutation UpdatePlayer($input: UpdatePlayerInput!) {
+ updatePlayer(input: $input) {
+ id
+ username
+ session {
+ id
+ title
+ players {
+ nextToken
+ }
+ lat
+ lon
+ radius
+ }
+ isIt
+ lat
+ lon
+ Photo
+ }
+}
+mutation DeletePlayer($input: DeletePlayerInput!) {
+ deletePlayer(input: $input) {
+ id
+ username
+ session {
+ id
+ title
+ players {
+ nextToken
+ }
+ lat
+ lon
+ radius
+ }
+ isIt
+ lat
+ lon
+ Photo
+ }
+}
diff --git a/app/src/main/graphql/com/amazonaws/amplify/generated/graphql/queries.graphql b/app/src/main/graphql/com/amazonaws/amplify/generated/graphql/queries.graphql
new file mode 100644
index 0000000..ce402c9
--- /dev/null
+++ b/app/src/main/graphql/com/amazonaws/amplify/generated/graphql/queries.graphql
@@ -0,0 +1,84 @@
+# this is an auto generated file. This will be overwritten
+query GetSession($id: ID!) {
+ getSession(id: $id) {
+ id
+ title
+ players {
+ items {
+ id
+ username
+ isIt
+ lat
+ lon
+ Photo
+ }
+ nextToken
+ }
+ lat
+ lon
+ radius
+ }
+}
+query ListSessions(
+ $filter: ModelSessionFilterInput
+ $limit: Int
+ $nextToken: String
+) {
+ listSessions(filter: $filter, limit: $limit, nextToken: $nextToken) {
+ items {
+ id
+ title
+ players {
+ nextToken
+ }
+ lat
+ lon
+ radius
+ }
+ nextToken
+ }
+}
+query GetPlayer($id: ID!) {
+ getPlayer(id: $id) {
+ id
+ username
+ session {
+ id
+ title
+ players {
+ nextToken
+ }
+ lat
+ lon
+ radius
+ }
+ isIt
+ lat
+ lon
+ Photo
+ }
+}
+query ListPlayers(
+ $filter: ModelPlayerFilterInput
+ $limit: Int
+ $nextToken: String
+) {
+ listPlayers(filter: $filter, limit: $limit, nextToken: $nextToken) {
+ items {
+ id
+ username
+ session {
+ id
+ title
+ lat
+ lon
+ radius
+ }
+ isIt
+ lat
+ lon
+ Photo
+ }
+ nextToken
+ }
+}
diff --git a/app/src/main/graphql/com/amazonaws/amplify/generated/graphql/subscriptions.graphql b/app/src/main/graphql/com/amazonaws/amplify/generated/graphql/subscriptions.graphql
new file mode 100644
index 0000000..e1d6052
--- /dev/null
+++ b/app/src/main/graphql/com/amazonaws/amplify/generated/graphql/subscriptions.graphql
@@ -0,0 +1,121 @@
+# this is an auto generated file. This will be overwritten
+subscription OnCreateSession {
+ onCreateSession {
+ id
+ title
+ players {
+ items {
+ id
+ username
+ isIt
+ lat
+ lon
+ Photo
+ }
+ nextToken
+ }
+ lat
+ lon
+ radius
+ }
+}
+subscription OnUpdateSession {
+ onUpdateSession {
+ id
+ title
+ players {
+ items {
+ id
+ username
+ isIt
+ lat
+ lon
+ Photo
+ }
+ nextToken
+ }
+ lat
+ lon
+ radius
+ }
+}
+subscription OnDeleteSession {
+ onDeleteSession {
+ id
+ title
+ players {
+ items {
+ id
+ username
+ isIt
+ lat
+ lon
+ Photo
+ }
+ nextToken
+ }
+ lat
+ lon
+ radius
+ }
+}
+subscription OnCreatePlayer {
+ onCreatePlayer {
+ id
+ username
+ session {
+ id
+ title
+ players {
+ nextToken
+ }
+ lat
+ lon
+ radius
+ }
+ isIt
+ lat
+ lon
+ Photo
+ }
+}
+subscription OnUpdatePlayer {
+ onUpdatePlayer {
+ id
+ username
+ session {
+ id
+ title
+ players {
+ nextToken
+ }
+ lat
+ lon
+ radius
+ }
+ isIt
+ lat
+ lon
+ Photo
+ }
+}
+subscription OnDeletePlayer {
+ onDeletePlayer {
+ id
+ username
+ session {
+ id
+ title
+ players {
+ nextToken
+ }
+ lat
+ lon
+ radius
+ }
+ isIt
+ lat
+ lon
+ Photo
+ }
+}
diff --git a/app/src/main/graphql/schema.json b/app/src/main/graphql/schema.json
new file mode 100644
index 0000000..959af0f
--- /dev/null
+++ b/app/src/main/graphql/schema.json
@@ -0,0 +1,3018 @@
+{
+ "__schema": {
+ "queryType": {
+ "name": "Query"
+ },
+ "mutationType": {
+ "name": "Mutation"
+ },
+ "subscriptionType": {
+ "name": "Subscription"
+ },
+ "types": [
+ {
+ "kind": "OBJECT",
+ "name": "Query",
+ "description": null,
+ "fields": [
+ {
+ "name": "getSession",
+ "description": null,
+ "args": [
+ {
+ "name": "id",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "Session",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "listSessions",
+ "description": null,
+ "args": [
+ {
+ "name": "filter",
+ "description": null,
+ "type": {
+ "kind": "INPUT_OBJECT",
+ "name": "ModelSessionFilterInput",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "limit",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "nextToken",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "ModelSessionConnection",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "getPlayer",
+ "description": null,
+ "args": [
+ {
+ "name": "id",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "Player",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "listPlayers",
+ "description": null,
+ "args": [
+ {
+ "name": "filter",
+ "description": null,
+ "type": {
+ "kind": "INPUT_OBJECT",
+ "name": "ModelPlayerFilterInput",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "limit",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "nextToken",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "ModelPlayerConnection",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "SCALAR",
+ "name": "ID",
+ "description": "The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"4\"`) or integer (such as `4`) input value will be accepted as an ID.",
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "Session",
+ "description": null,
+ "fields": [
+ {
+ "name": "id",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "title",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "players",
+ "description": null,
+ "args": [
+ {
+ "name": "filter",
+ "description": null,
+ "type": {
+ "kind": "INPUT_OBJECT",
+ "name": "ModelPlayerFilterInput",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "sortDirection",
+ "description": null,
+ "type": {
+ "kind": "ENUM",
+ "name": "ModelSortDirection",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "limit",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "nextToken",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "ModelPlayerConnection",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "lat",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Float",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "lon",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Float",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "radius",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "SCALAR",
+ "name": "String",
+ "description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.",
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "INPUT_OBJECT",
+ "name": "ModelPlayerFilterInput",
+ "description": null,
+ "fields": null,
+ "inputFields": [
+ {
+ "name": "id",
+ "description": null,
+ "type": {
+ "kind": "INPUT_OBJECT",
+ "name": "ModelIDFilterInput",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "username",
+ "description": null,
+ "type": {
+ "kind": "INPUT_OBJECT",
+ "name": "ModelStringFilterInput",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "isIt",
+ "description": null,
+ "type": {
+ "kind": "INPUT_OBJECT",
+ "name": "ModelBooleanFilterInput",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "lat",
+ "description": null,
+ "type": {
+ "kind": "INPUT_OBJECT",
+ "name": "ModelFloatFilterInput",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "lon",
+ "description": null,
+ "type": {
+ "kind": "INPUT_OBJECT",
+ "name": "ModelFloatFilterInput",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "Photo",
+ "description": null,
+ "type": {
+ "kind": "INPUT_OBJECT",
+ "name": "ModelStringFilterInput",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "and",
+ "description": null,
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "INPUT_OBJECT",
+ "name": "ModelPlayerFilterInput",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "or",
+ "description": null,
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "INPUT_OBJECT",
+ "name": "ModelPlayerFilterInput",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "not",
+ "description": null,
+ "type": {
+ "kind": "INPUT_OBJECT",
+ "name": "ModelPlayerFilterInput",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "INPUT_OBJECT",
+ "name": "ModelIDFilterInput",
+ "description": null,
+ "fields": null,
+ "inputFields": [
+ {
+ "name": "ne",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "eq",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "le",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "lt",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "ge",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "gt",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "contains",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "notContains",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "between",
+ "description": null,
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "beginsWith",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "INPUT_OBJECT",
+ "name": "ModelStringFilterInput",
+ "description": null,
+ "fields": null,
+ "inputFields": [
+ {
+ "name": "ne",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "eq",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "le",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "lt",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "ge",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "gt",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "contains",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "notContains",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "between",
+ "description": null,
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "beginsWith",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "INPUT_OBJECT",
+ "name": "ModelBooleanFilterInput",
+ "description": null,
+ "fields": null,
+ "inputFields": [
+ {
+ "name": "ne",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "Boolean",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "eq",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "Boolean",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "SCALAR",
+ "name": "Boolean",
+ "description": "The `Boolean` scalar type represents `true` or `false`.",
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "INPUT_OBJECT",
+ "name": "ModelFloatFilterInput",
+ "description": null,
+ "fields": null,
+ "inputFields": [
+ {
+ "name": "ne",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "Float",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "eq",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "Float",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "le",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "Float",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "lt",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "Float",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "ge",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "Float",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "gt",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "Float",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "contains",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "Float",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "notContains",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "Float",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "between",
+ "description": null,
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Float",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ }
+ ],
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "SCALAR",
+ "name": "Float",
+ "description": "The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point). ",
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "ENUM",
+ "name": "ModelSortDirection",
+ "description": null,
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": [
+ {
+ "name": "ASC",
+ "description": null,
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "DESC",
+ "description": null,
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "possibleTypes": null
+ },
+ {
+ "kind": "SCALAR",
+ "name": "Int",
+ "description": "The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1. ",
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "ModelPlayerConnection",
+ "description": null,
+ "fields": [
+ {
+ "name": "items",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "Player",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "nextToken",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "Player",
+ "description": null,
+ "fields": [
+ {
+ "name": "id",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "username",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "session",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "OBJECT",
+ "name": "Session",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "isIt",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Boolean",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "lat",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Float",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "lon",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Float",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "Photo",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "INPUT_OBJECT",
+ "name": "ModelSessionFilterInput",
+ "description": null,
+ "fields": null,
+ "inputFields": [
+ {
+ "name": "id",
+ "description": null,
+ "type": {
+ "kind": "INPUT_OBJECT",
+ "name": "ModelIDFilterInput",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "title",
+ "description": null,
+ "type": {
+ "kind": "INPUT_OBJECT",
+ "name": "ModelStringFilterInput",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "lat",
+ "description": null,
+ "type": {
+ "kind": "INPUT_OBJECT",
+ "name": "ModelFloatFilterInput",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "lon",
+ "description": null,
+ "type": {
+ "kind": "INPUT_OBJECT",
+ "name": "ModelFloatFilterInput",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "radius",
+ "description": null,
+ "type": {
+ "kind": "INPUT_OBJECT",
+ "name": "ModelIntFilterInput",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "and",
+ "description": null,
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "INPUT_OBJECT",
+ "name": "ModelSessionFilterInput",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "or",
+ "description": null,
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "INPUT_OBJECT",
+ "name": "ModelSessionFilterInput",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "not",
+ "description": null,
+ "type": {
+ "kind": "INPUT_OBJECT",
+ "name": "ModelSessionFilterInput",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "INPUT_OBJECT",
+ "name": "ModelIntFilterInput",
+ "description": null,
+ "fields": null,
+ "inputFields": [
+ {
+ "name": "ne",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "eq",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "le",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "lt",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "ge",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "gt",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "contains",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "notContains",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "between",
+ "description": null,
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ }
+ ],
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "ModelSessionConnection",
+ "description": null,
+ "fields": [
+ {
+ "name": "items",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "Session",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "nextToken",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "Mutation",
+ "description": null,
+ "fields": [
+ {
+ "name": "createSession",
+ "description": null,
+ "args": [
+ {
+ "name": "input",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "INPUT_OBJECT",
+ "name": "CreateSessionInput",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "Session",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "updateSession",
+ "description": null,
+ "args": [
+ {
+ "name": "input",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "INPUT_OBJECT",
+ "name": "UpdateSessionInput",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "Session",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "deleteSession",
+ "description": null,
+ "args": [
+ {
+ "name": "input",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "INPUT_OBJECT",
+ "name": "DeleteSessionInput",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "Session",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "createPlayer",
+ "description": null,
+ "args": [
+ {
+ "name": "input",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "INPUT_OBJECT",
+ "name": "CreatePlayerInput",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "Player",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "updatePlayer",
+ "description": null,
+ "args": [
+ {
+ "name": "input",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "INPUT_OBJECT",
+ "name": "UpdatePlayerInput",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "Player",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "deletePlayer",
+ "description": null,
+ "args": [
+ {
+ "name": "input",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "INPUT_OBJECT",
+ "name": "DeletePlayerInput",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "Player",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "INPUT_OBJECT",
+ "name": "CreateSessionInput",
+ "description": null,
+ "fields": null,
+ "inputFields": [
+ {
+ "name": "id",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "title",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "lat",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Float",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "lon",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Float",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "radius",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ }
+ ],
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "INPUT_OBJECT",
+ "name": "UpdateSessionInput",
+ "description": null,
+ "fields": null,
+ "inputFields": [
+ {
+ "name": "id",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "title",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "lat",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "Float",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "lon",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "Float",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "radius",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "INPUT_OBJECT",
+ "name": "DeleteSessionInput",
+ "description": null,
+ "fields": null,
+ "inputFields": [
+ {
+ "name": "id",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "INPUT_OBJECT",
+ "name": "CreatePlayerInput",
+ "description": null,
+ "fields": null,
+ "inputFields": [
+ {
+ "name": "id",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "username",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "isIt",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Boolean",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "lat",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Float",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "lon",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Float",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "Photo",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "playerSessionId",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "INPUT_OBJECT",
+ "name": "UpdatePlayerInput",
+ "description": null,
+ "fields": null,
+ "inputFields": [
+ {
+ "name": "id",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "username",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "isIt",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "Boolean",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "lat",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "Float",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "lon",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "Float",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "Photo",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "playerSessionId",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "INPUT_OBJECT",
+ "name": "DeletePlayerInput",
+ "description": null,
+ "fields": null,
+ "inputFields": [
+ {
+ "name": "id",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "Subscription",
+ "description": null,
+ "fields": [
+ {
+ "name": "onCreateSession",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "OBJECT",
+ "name": "Session",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "onUpdateSession",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "OBJECT",
+ "name": "Session",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "onDeleteSession",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "OBJECT",
+ "name": "Session",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "onCreatePlayer",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "OBJECT",
+ "name": "Player",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "onUpdatePlayer",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "OBJECT",
+ "name": "Player",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "onDeletePlayer",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "OBJECT",
+ "name": "Player",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "__Schema",
+ "description": "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.",
+ "fields": [
+ {
+ "name": "types",
+ "description": "A list of all types supported by this server.",
+ "args": [],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "__Type",
+ "ofType": null
+ }
+ }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "queryType",
+ "description": "The type that query operations will be rooted at.",
+ "args": [],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "__Type",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "mutationType",
+ "description": "If this server supports mutation, the type that mutation operations will be rooted at.",
+ "args": [],
+ "type": {
+ "kind": "OBJECT",
+ "name": "__Type",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "subscriptionType",
+ "description": "If this server support subscription, the type that subscription operations will be rooted at.",
+ "args": [],
+ "type": {
+ "kind": "OBJECT",
+ "name": "__Type",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "directives",
+ "description": "A list of all directives supported by this server.",
+ "args": [],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "__Directive",
+ "ofType": null
+ }
+ }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "__Type",
+ "description": "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.",
+ "fields": [
+ {
+ "name": "kind",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "ENUM",
+ "name": "__TypeKind",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "name",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "description",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "fields",
+ "description": null,
+ "args": [
+ {
+ "name": "includeDeprecated",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "Boolean",
+ "ofType": null
+ },
+ "defaultValue": "false"
+ }
+ ],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "__Field",
+ "ofType": null
+ }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "interfaces",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "__Type",
+ "ofType": null
+ }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "possibleTypes",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "__Type",
+ "ofType": null
+ }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "enumValues",
+ "description": null,
+ "args": [
+ {
+ "name": "includeDeprecated",
+ "description": null,
+ "type": {
+ "kind": "SCALAR",
+ "name": "Boolean",
+ "ofType": null
+ },
+ "defaultValue": "false"
+ }
+ ],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "__EnumValue",
+ "ofType": null
+ }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "inputFields",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "__InputValue",
+ "ofType": null
+ }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "ofType",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "OBJECT",
+ "name": "__Type",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "ENUM",
+ "name": "__TypeKind",
+ "description": "An enum describing what kind of type a given `__Type` is.",
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": [
+ {
+ "name": "SCALAR",
+ "description": "Indicates this type is a scalar.",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "OBJECT",
+ "description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "INTERFACE",
+ "description": "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "UNION",
+ "description": "Indicates this type is a union. `possibleTypes` is a valid field.",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "ENUM",
+ "description": "Indicates this type is an enum. `enumValues` is a valid field.",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "INPUT_OBJECT",
+ "description": "Indicates this type is an input object. `inputFields` is a valid field.",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "LIST",
+ "description": "Indicates this type is a list. `ofType` is a valid field.",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "NON_NULL",
+ "description": "Indicates this type is a non-null. `ofType` is a valid field.",
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "__Field",
+ "description": "Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.",
+ "fields": [
+ {
+ "name": "name",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "description",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "args",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "__InputValue",
+ "ofType": null
+ }
+ }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "type",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "__Type",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "isDeprecated",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Boolean",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "deprecationReason",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "__InputValue",
+ "description": "Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.",
+ "fields": [
+ {
+ "name": "name",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "description",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "type",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "__Type",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "defaultValue",
+ "description": "A GraphQL-formatted string representing the default value for this input value.",
+ "args": [],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "__EnumValue",
+ "description": "One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.",
+ "fields": [
+ {
+ "name": "name",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "description",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "isDeprecated",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Boolean",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "deprecationReason",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "__Directive",
+ "description": "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.",
+ "fields": [
+ {
+ "name": "name",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "description",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "locations",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "ENUM",
+ "name": "__DirectiveLocation",
+ "ofType": null
+ }
+ }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "args",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "__InputValue",
+ "ofType": null
+ }
+ }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "onOperation",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Boolean",
+ "ofType": null
+ }
+ },
+ "isDeprecated": true,
+ "deprecationReason": "Use `locations`."
+ },
+ {
+ "name": "onFragment",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Boolean",
+ "ofType": null
+ }
+ },
+ "isDeprecated": true,
+ "deprecationReason": "Use `locations`."
+ },
+ {
+ "name": "onField",
+ "description": null,
+ "args": [],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Boolean",
+ "ofType": null
+ }
+ },
+ "isDeprecated": true,
+ "deprecationReason": "Use `locations`."
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "ENUM",
+ "name": "__DirectiveLocation",
+ "description": "A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.",
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": [
+ {
+ "name": "QUERY",
+ "description": "Location adjacent to a query operation.",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "MUTATION",
+ "description": "Location adjacent to a mutation operation.",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "SUBSCRIPTION",
+ "description": "Location adjacent to a subscription operation.",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "FIELD",
+ "description": "Location adjacent to a field.",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "FRAGMENT_DEFINITION",
+ "description": "Location adjacent to a fragment definition.",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "FRAGMENT_SPREAD",
+ "description": "Location adjacent to a fragment spread.",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "INLINE_FRAGMENT",
+ "description": "Location adjacent to an inline fragment.",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "SCHEMA",
+ "description": "Location adjacent to a schema definition.",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "SCALAR",
+ "description": "Location adjacent to a scalar definition.",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "OBJECT",
+ "description": "Location adjacent to an object type definition.",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "FIELD_DEFINITION",
+ "description": "Location adjacent to a field definition.",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "ARGUMENT_DEFINITION",
+ "description": "Location adjacent to an argument definition.",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "INTERFACE",
+ "description": "Location adjacent to an interface definition.",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "UNION",
+ "description": "Location adjacent to a union definition.",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "ENUM",
+ "description": "Location adjacent to an enum definition.",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "ENUM_VALUE",
+ "description": "Location adjacent to an enum value definition.",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "INPUT_OBJECT",
+ "description": "Location adjacent to an input object type definition.",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "INPUT_FIELD_DEFINITION",
+ "description": "Location adjacent to an input object field definition.",
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "possibleTypes": null
+ },
+ {
+ "kind": "SCALAR",
+ "name": "AWSDate",
+ "description": null,
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "SCALAR",
+ "name": "AWSTime",
+ "description": null,
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "SCALAR",
+ "name": "AWSDateTime",
+ "description": null,
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "SCALAR",
+ "name": "AWSTimestamp",
+ "description": null,
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "SCALAR",
+ "name": "AWSEmail",
+ "description": null,
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "SCALAR",
+ "name": "AWSJSON",
+ "description": null,
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "SCALAR",
+ "name": "AWSURL",
+ "description": null,
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "SCALAR",
+ "name": "AWSPhone",
+ "description": null,
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "SCALAR",
+ "name": "AWSIPAddress",
+ "description": null,
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "SCALAR",
+ "name": "BigInt",
+ "description": null,
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "SCALAR",
+ "name": "Double",
+ "description": null,
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ }
+ ],
+ "directives": [
+ {
+ "name": "aws_subscribe",
+ "description": null,
+ "locations": [
+ "FIELD_DEFINITION"
+ ],
+ "args": [
+ {
+ "name": "mutations",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ }
+ }
+ },
+ "defaultValue": null
+ }
+ ]
+ },
+ {
+ "name": "deprecated",
+ "description": null,
+ "locations": [
+ "INPUT_FIELD_DEFINITION",
+ "ENUM"
+ ],
+ "args": [
+ {
+ "name": "reason",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ }
+ ]
+ },
+ {
+ "name": "aws_auth",
+ "description": null,
+ "locations": [
+ "FIELD_DEFINITION"
+ ],
+ "args": [
+ {
+ "name": "cognito_groups",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ }
+ }
+ },
+ "defaultValue": null
+ }
+ ]
+ },
+ {
+ "name": "aws_api_key",
+ "description": null,
+ "locations": [
+ "FIELD_DEFINITION",
+ "OBJECT"
+ ],
+ "args": []
+ },
+ {
+ "name": "aws_iam",
+ "description": null,
+ "locations": [
+ "FIELD_DEFINITION",
+ "OBJECT"
+ ],
+ "args": []
+ },
+ {
+ "name": "aws_oidc",
+ "description": null,
+ "locations": [
+ "FIELD_DEFINITION",
+ "OBJECT"
+ ],
+ "args": []
+ },
+ {
+ "name": "aws_cognito_user_pools",
+ "description": null,
+ "locations": [
+ "FIELD_DEFINITION",
+ "OBJECT"
+ ],
+ "args": [
+ {
+ "name": "cognito_groups",
+ "description": null,
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ }
+ },
+ "defaultValue": null
+ }
+ ]
+ },
+ {
+ "name": "skip",
+ "description": "Directs the executor to skip this field or fragment when the `if` argument is true.",
+ "locations": [
+ "FIELD",
+ "FRAGMENT_SPREAD",
+ "INLINE_FRAGMENT"
+ ],
+ "args": [
+ {
+ "name": "if",
+ "description": "Skipped when true.",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Boolean",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ }
+ ]
+ },
+ {
+ "name": "include",
+ "description": "Directs the executor to include this field or fragment only when the `if` argument is true.",
+ "locations": [
+ "FIELD",
+ "FRAGMENT_SPREAD",
+ "INLINE_FRAGMENT"
+ ],
+ "args": [
+ {
+ "name": "if",
+ "description": "Included when true.",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Boolean",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ }
+ ]
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/ic_launcher-web.png b/app/src/main/ic_launcher-web.png
new file mode 100644
index 0000000..638cc00
Binary files /dev/null and b/app/src/main/ic_launcher-web.png differ
diff --git a/app/src/main/java/com/javaawesome/tag/MainActivity.java b/app/src/main/java/com/javaawesome/tag/MainActivity.java
new file mode 100644
index 0000000..5de57a3
--- /dev/null
+++ b/app/src/main/java/com/javaawesome/tag/MainActivity.java
@@ -0,0 +1,386 @@
+package com.javaawesome.tag;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.app.ActivityCompat;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.location.Location;
+import android.location.LocationManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.Toast;
+
+import com.amazonaws.amplify.generated.graphql.CreatePlayerMutation;
+import com.amazonaws.amplify.generated.graphql.CreateSessionMutation;
+import com.amazonaws.amplify.generated.graphql.ListPlayersQuery;
+import com.amazonaws.amplify.generated.graphql.ListSessionsQuery;
+import com.amazonaws.mobile.client.AWSMobileClient;
+import com.amazonaws.mobile.client.Callback;
+import com.amazonaws.mobile.client.SignInUIOptions;
+import com.amazonaws.mobile.client.UserStateDetails;
+import com.amazonaws.mobile.config.AWSConfiguration;
+import com.amazonaws.mobileconnectors.appsync.AWSAppSyncClient;
+import com.amazonaws.mobileconnectors.appsync.fetcher.AppSyncResponseFetchers;
+import com.amazonaws.mobileconnectors.s3.transferutility.TransferService;
+import com.apollographql.apollo.GraphQLCall;
+import com.apollographql.apollo.api.Response;
+import com.apollographql.apollo.exception.ApolloException;
+import com.google.android.gms.location.FusedLocationProviderClient;
+import com.google.android.gms.location.LocationServices;
+import com.google.android.gms.maps.model.LatLng;
+import com.google.android.gms.tasks.OnFailureListener;
+import com.google.android.gms.tasks.OnSuccessListener;
+import java.util.LinkedList;
+import java.util.List;
+import javax.annotation.Nonnull;
+import type.CreatePlayerInput;
+import type.CreateSessionInput;
+import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+
+public class MainActivity extends AppCompatActivity implements SessionAdapter.OnSessionInteractionListener {
+
+ private final String TAG = "javatag";
+ RecyclerView recyclerNearbySessions;
+ SessionAdapter sessionAdapter;
+ List sessions;
+ AWSAppSyncClient awsAppSyncClient;
+ FusedLocationProviderClient fusedLocationClient;
+ String sessionId;
+ String playerId;
+ LatLng currentUserLocation;
+ LocationManager locationManager;
+ AlertDialog alert;
+ private final int distanceForNearbySessions = 1000;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ getApplicationContext().startService(new Intent(getApplicationContext(), TransferService.class));
+
+ if (this.checkSelfPermission(ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED ||
+ this.checkSelfPermission(ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
+ ActivityCompat.requestPermissions(this, new String[]{ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION}, 10);
+ }
+
+ // initialize aws mobile client and check if you are logged in or not
+ AWSMobileClient.getInstance().initialize(getApplicationContext(), new Callback() {
+ @Override
+ public void onResult(UserStateDetails result) {
+ // if the user is signed out, show them the sign in page
+ if (result.getUserState().toString().equals("SIGNED_OUT")) {
+ signInUser();
+ }
+ }
+
+ @Override
+ public void onError(Exception e) {
+ Log.e(TAG, e.getMessage());
+ }
+ });
+
+ // connect to AWS
+ awsAppSyncClient = AWSAppSyncClient.builder()
+ .context(getApplicationContext())
+ .awsConfiguration(new AWSConfiguration(getApplicationContext()))
+ .build();
+
+ // initialize client for google location services
+ fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
+
+ sessions = new LinkedList<>();
+
+ // initialize recycler view to display nearby game sessions
+ // TODO: have recycler view filter sessions by distance to user
+ recyclerNearbySessions = findViewById(R.id.recycler_nearby_sessions);
+ recyclerNearbySessions.setLayoutManager(new LinearLayoutManager(this));
+ this.sessionAdapter = new SessionAdapter(this.sessions, this);
+ recyclerNearbySessions.setAdapter(this.sessionAdapter);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ Log.i(TAG, "onresume called");
+ if (checkGpsStatus()) {
+// getCurrentUserLocation();
+ checkIfPlayerAlreadyExistInDatabase();
+ } else {
+ buildAlertMessageNoGps();
+ }
+ }
+
+ // Create new game session and go to map page
+ public void goToMap(View view) {
+ // TODO: check if player already exist in the database
+ EditText sessionName = findViewById(R.id.editText_session_name);
+ Log.i(TAG, "goToMap: "+sessionName.getText());
+ if(sessionName.getText().length()>0) {
+ CreateSessionInput input = CreateSessionInput.builder()
+ .title(sessionName.getText().toString())
+ .lat(currentUserLocation.latitude)
+ .lon(currentUserLocation.longitude)
+ .radius(500)
+ .build();
+ CreateSessionMutation createSessionMutation = CreateSessionMutation.builder().input(input).build();
+ awsAppSyncClient.mutate(createSessionMutation).enqueue(new GraphQLCall.Callback() {
+ @Override
+ public void onResponse(@Nonnull Response response) {
+ sessionId = response.data().createSession().id();
+ Intent goToMapIntent = new Intent(MainActivity.this, MapsActivity.class);
+ goToMapIntent.putExtra("sessionId", sessionId);
+ goToMapIntent.putExtra("userID", playerId);
+ MainActivity.this.startActivity(goToMapIntent);
+ }
+
+ @Override
+ public void onFailure(@Nonnull ApolloException e) {
+ Log.e(TAG, "error in creating new game session" + e.getMessage());
+ }
+ });
+ }else{
+ Toast.makeText(getBaseContext(), "Please enter a session title.",Toast.LENGTH_LONG).show();
+ }
+ }
+
+ //////// TEST BUTTON /////
+ public void onTestyClick(View view) {
+ startActivity(new Intent(MainActivity.this, NotificationActivity.class));
+ }
+
+ ///////////// Turn on Camera ///////////////////
+ public void goToCameraClass(View view){
+ Intent goToCamera = new Intent(this, ShowMeYourFace.class);
+ this.startActivity(goToCamera);
+ }
+
+ /////////////
+
+ // Direct users to sign in page
+ private void signInUser() {
+ AWSMobileClient.getInstance().showSignIn(MainActivity.this,
+ // customize the built in sign in page
+ SignInUIOptions.builder().backgroundColor(16763080).build(),
+ new Callback() {
+ @Override
+ public void onResult(UserStateDetails result) {
+ Log.i(TAG, "successfully show signed in page");
+ }
+
+ @Override
+ public void onError(Exception e) {
+ Log.e(TAG, e.getMessage());
+ }
+ });
+ }
+
+ // sign out user and show them sign in page
+ public void signoutCurrentUser(View view) {
+ AWSMobileClient.getInstance().signOut();
+ signInUser();
+ }
+
+ // onclick method for button to join existing game sessions
+ @Override
+ public void joinExistingGameSession(ListSessionsQuery.Item session) {
+ Intent goToMapIntent = new Intent(this, MapsActivity.class);
+ goToMapIntent.putExtra("sessionId", session.id());
+ goToMapIntent.putExtra("userID", playerId);
+ this.startActivity(goToMapIntent);
+ }
+
+// @Override
+// public void addPlayerToChosenGame(final ListSessionsQuery.Item session) {
+//// Query
+// CreatePlayerInput playerInput = CreatePlayerInput.builder()
+// .playerSessionId(session.id())
+// .isIt(false)
+// .lat(currentUserLocation.latitude)
+// .lon(currentUserLocation.longitude)
+// .username(AWSMobileClient.getInstance().getUsername())
+// .build();
+// CreatePlayerMutation createPlayerMutation = CreatePlayerMutation.builder().input(playerInput).build();
+// awsAppSyncClient.mutate(createPlayerMutation).enqueue((new GraphQLCall.Callback() {
+// @Override
+// public void onResponse(@Nonnull Response response) {
+// String userID = response.data().createPlayer().id();
+// Log.i(TAG, "player mutation happened! ... inside of a session mutation");
+// Intent goToMapIntent = new Intent(MainActivity.this, MapsActivity.class);
+// goToMapIntent.putExtra("sessionId", session.id());
+// goToMapIntent.putExtra("userID", userID);
+// Log.i("veach", session.id() + "\n" +userID);
+// }
+// @Override
+// public void onFailure(@Nonnull ApolloException e) {
+// Log.i(TAG, "mutation of player failed, boohoo!");
+// }
+// }));
+// }
+
+ // get all sessions
+ private void queryAllSessions() {
+ Log.i(TAG, "query all sessions");
+ awsAppSyncClient.query(ListSessionsQuery.builder().build())
+ .responseFetcher(AppSyncResponseFetchers.NETWORK_ONLY)
+ .enqueue(getAllSessionsCallBack);
+ }
+
+ // Callback to update the list of sessions and recycler view that displays them
+ private GraphQLCall.Callback getAllSessionsCallBack = new GraphQLCall.Callback() {
+ @Override
+ public void onResponse(@Nonnull final Response response) {
+ Handler h = new Handler(Looper.getMainLooper()) {
+ @Override
+ public void handleMessage(Message inputMessage) {
+ sessions.clear();
+ List filteredSessions = filterSessionsBasedOnDistance(response.data().listSessions().items());
+ sessions.addAll(filteredSessions);
+ sessionAdapter.notifyDataSetChanged();
+ }
+ };
+ h.obtainMessage().sendToTarget();
+ }
+
+ @Override
+ public void onFailure(@Nonnull ApolloException e) {
+
+ }
+ };
+
+ // get current user location
+ private void getCurrentUserLocation() {
+ Log.i(TAG, "called getCurrentUserLocation");
+ fusedLocationClient.getLastLocation().addOnSuccessListener(this, new OnSuccessListener() {
+ @Override
+ public void onSuccess(final Location location) {
+ Log.i(TAG, "" + location);
+ if (location != null) {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ currentUserLocation = new LatLng(location.getLatitude(), location.getLongitude());
+ queryAllSessions();
+ Log.i(TAG, "playerId in getcurrentUserlocation " + playerId);
+ if (playerId == null) {
+ createPlayer();
+ }
+ }
+ }).run();
+ }
+ }
+ }).addOnFailureListener(this, new OnFailureListener() {
+ @Override
+ public void onFailure(@NonNull Exception e) {
+ Log.e(TAG, e.getMessage());
+ }
+ });
+ }
+
+ // TODO: Build onDestroy that deletes user data from DB
+
+
+ private void checkIfPlayerAlreadyExistInDatabase() {
+ awsAppSyncClient.query(ListPlayersQuery.builder().build())
+ .responseFetcher(AppSyncResponseFetchers.NETWORK_ONLY)
+ .enqueue(new GraphQLCall.Callback() {
+ @Override
+ public void onResponse(@Nonnull Response response) {
+ Log.i(TAG, "this is playerID " + playerId);
+ String playerName = AWSMobileClient.getInstance().getUsername();
+ List players = response.data().listPlayers().items();
+ for(ListPlayersQuery.Item player : players){
+ if(player.username().equals(playerName)){
+ Log.i(TAG, "Username match " + playerName + " " + player.id());
+ playerId = player.id();
+ getCurrentUserLocation();
+ return;
+ }
+ }
+ getCurrentUserLocation();
+ }
+
+ @Override
+ public void onFailure(@Nonnull ApolloException e) {
+ Log.e(TAG, "error in checking if a player already exists in database");
+ }
+ });
+ }
+
+ // Make a Player
+ private void createPlayer() {
+ CreatePlayerInput input = CreatePlayerInput.builder()
+ .lat(currentUserLocation.latitude)
+ .lon(currentUserLocation.longitude)
+ .username(AWSMobileClient.getInstance().getUsername())
+ .isIt(false)
+ .build();
+ CreatePlayerMutation createPlayerMutation = CreatePlayerMutation.builder().input(input).build();
+ awsAppSyncClient.mutate(createPlayerMutation).enqueue(new GraphQLCall.Callback() {
+ @Override
+ public void onResponse(@Nonnull Response response) {
+ Log.i(TAG, "created a player");
+ playerId = response.data().createPlayer().id();
+ }
+
+ @Override
+ public void onFailure(@Nonnull ApolloException e) {
+ Log.e(TAG, "error in creating new player");
+ }
+ });
+ }
+
+ // Checks if Gps Location is turned on or not on the user's phone
+ private boolean checkGpsStatus() {
+ locationManager = (LocationManager)getApplicationContext().getSystemService(Context.LOCATION_SERVICE);
+ return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
+ }
+
+ private void buildAlertMessageNoGps() {
+ final AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setMessage("Your GPS is disabled, do you want to enable it?")
+ .setCancelable(false)
+ .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
+ public void onClick(final DialogInterface dialog, final int id) {
+ startActivity(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS));
+ }
+ })
+ .setNegativeButton("No", new DialogInterface.OnClickListener() {
+ public void onClick(final DialogInterface dialog, final int id) {
+ dialog.cancel();
+ }
+ });
+ alert = builder.create();
+ alert.show();
+ }
+
+ // filter out list of sessions to a smaller list that consist of sessions nearby the player's location
+ private List filterSessionsBasedOnDistance(List allSessions) {
+ List filteredSessions = new LinkedList<>();
+ for (ListSessionsQuery.Item session : allSessions) {
+ double distanceBetweenSessionAndPlayer = Utility.distanceBetweenLatLongPoints(currentUserLocation.latitude,
+ currentUserLocation.longitude,
+ session.lat(),
+ session.lon());
+ if (distanceBetweenSessionAndPlayer < distanceForNearbySessions) {
+ filteredSessions.add(session);
+ }
+ }
+ return filteredSessions;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/javaawesome/tag/MapsActivity.java b/app/src/main/java/com/javaawesome/tag/MapsActivity.java
new file mode 100644
index 0000000..4ded9fe
--- /dev/null
+++ b/app/src/main/java/com/javaawesome/tag/MapsActivity.java
@@ -0,0 +1,640 @@
+package com.javaawesome.tag;
+
+import androidx.annotation.NonNull;
+import androidx.core.app.ActivityCompat;
+import androidx.fragment.app.FragmentActivity;
+
+import android.Manifest;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.location.Location;
+import android.nfc.Tag;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.amazonaws.amplify.generated.graphql.CreatePlayerMutation;
+import com.amazonaws.amplify.generated.graphql.GetPlayerQuery;
+import com.amazonaws.amplify.generated.graphql.GetSessionQuery;
+import com.amazonaws.amplify.generated.graphql.OnCreatePlayerSubscription;
+import com.amazonaws.amplify.generated.graphql.OnUpdatePlayerSubscription;
+import com.amazonaws.amplify.generated.graphql.UpdatePlayerMutation;
+import com.amazonaws.mobile.client.AWSMobileClient;
+import com.amazonaws.mobile.config.AWSConfiguration;
+import com.amazonaws.mobileconnectors.appsync.AWSAppSyncClient;
+import com.amazonaws.mobileconnectors.appsync.AppSyncSubscriptionCall;
+import com.amazonaws.mobileconnectors.appsync.fetcher.AppSyncResponseFetchers;
+import com.apollographql.apollo.GraphQLCall;
+import com.apollographql.apollo.api.Response;
+import com.apollographql.apollo.exception.ApolloException;
+import com.google.android.gms.location.FusedLocationProviderClient;
+import com.google.android.gms.location.LocationCallback;
+import com.google.android.gms.location.LocationRequest;
+import com.google.android.gms.location.LocationResult;
+import com.google.android.gms.location.LocationServices;
+import com.google.android.gms.maps.CameraUpdateFactory;
+import com.google.android.gms.maps.GoogleMap;
+import com.google.android.gms.maps.OnMapReadyCallback;
+import com.google.android.gms.maps.SupportMapFragment;
+import com.google.android.gms.maps.model.BitmapDescriptor;
+import com.google.android.gms.maps.model.BitmapDescriptorFactory;
+import com.google.android.gms.maps.model.Circle;
+import com.google.android.gms.maps.model.CircleOptions;
+import com.google.android.gms.maps.model.LatLng;
+import com.google.android.gms.maps.model.LatLngBounds;
+import com.google.android.gms.maps.model.Marker;
+import com.google.android.gms.maps.model.MarkerOptions;
+import com.google.android.gms.tasks.OnSuccessListener;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import javax.annotation.Nonnull;
+
+import type.CreatePlayerInput;
+import type.UpdatePlayerInput;
+
+public class MapsActivity extends FragmentActivity implements OnMapReadyCallback, GoogleMap.OnMyLocationButtonClickListener, GoogleMap.OnMyLocationClickListener {
+
+ private GoogleMap mMap;
+ AWSAppSyncClient awsAppSyncClient;
+ GetSessionQuery.GetSession currentSession;
+
+ LatLng startingPoint;
+ final static long REFRESHRATE = 3*1000;
+ final static int SUBJECT = 0;
+ Handler locationHandler;
+ LocationCallback mLocationCallback;
+ private FusedLocationProviderClient mFusedLocationClient;
+ final private int tagDistance = 50;
+ List itPlayers;
+ int itColor = Color.GREEN;
+ int notItColor = Color.BLUE;
+ BitmapDescriptor zombiepin;
+ BitmapDescriptor playerpin;
+ List players;
+ private final String TAG = "javatag";
+ String playerID;
+ Player player;
+ String sessionId;
+ private AppSyncSubscriptionCall subscriptionWatcher;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_maps);
+ // Obtain the SupportMapFragment and get notified when the map is ready to be used.
+ SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
+ .findFragmentById(R.id.map);
+ mapFragment.getMapAsync(this);
+
+ // initialize connection with google location services
+ mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
+
+ // establish connection to AWS
+ awsAppSyncClient = AWSAppSyncClient.builder()
+ .context(getApplicationContext())
+ .awsConfiguration(new AWSConfiguration(getApplicationContext()))
+ .build();
+
+ zombiepin = BitmapDescriptorFactory.fromResource(R.drawable.zombiepin);
+ playerpin = BitmapDescriptorFactory.fromResource(R.drawable.playerpin);
+
+ // getting extras
+ playerID = getIntent().getStringExtra("userID");
+ sessionId = getIntent().getStringExtra("sessionId");
+ Log.i(TAG, "Session ID for map is: " + sessionId + "the player Id is " + playerID);
+
+ queryForSelectedSession(sessionId);
+
+ // Pull user ID from MainActivity
+ // If player comes from the recyclerView it will come through as null so we will create a new player
+ // Else the player created the game and we will query the player object
+// if (playerID == null) {
+// createPlayer();
+// } else {
+// queryForPlayerObject();
+// }
+
+ //Stuff doesn't start running until the map is ready in onMapReady(Map stuff)
+ mLocationCallback = new LocationCallback() {
+ @Override
+ public void onLocationResult(LocationResult locationResult) {
+ Log.i(TAG, "Location call back results " + locationResult.toString());
+ if (locationResult == null) {
+ Log.i(TAG, "location result is null");
+ return;
+ }
+
+ sendUserLocationQuery(locationResult);
+ updateMarkerAndCircleForAllPlayers(players);
+
+ }
+ };
+ }
+
+ // ===== Send user info to DynamoDB ====
+ private void sendUserLocationQuery(LocationResult locationResult) {
+ Log.i(TAG, "player being sent " + (player == null ? "null" : player.toString()));
+ UpdatePlayerInput updatePlayerInput = UpdatePlayerInput.builder()
+ .id(playerID)
+ .playerSessionId(sessionId)
+ .lat(locationResult.getLastLocation().getLatitude())
+ .lon(locationResult.getLastLocation().getLongitude())
+ .isIt(player.getIt())
+ .build();
+
+ UpdatePlayerMutation updatePlayerMutation = UpdatePlayerMutation.builder()
+ .input(updatePlayerInput).build();
+
+ awsAppSyncClient.mutate(updatePlayerMutation)
+ .enqueue(new GraphQLCall.Callback() {
+ @Override
+ public void onResponse(@Nonnull Response response) {
+ Log.i(TAG, "update success");
+ }
+
+ @Override
+ public void onFailure(@Nonnull ApolloException e) {
+ Log.e(TAG, "update not successful");
+ }
+ });
+ }
+
+ // ===== Subscribe to Data real-time ======
+ // https://aws-amplify.github.io/docs/android/api
+
+ private void subscribe() {
+
+ OnUpdatePlayerSubscription subscription = OnUpdatePlayerSubscription.builder().build();
+ subscriptionWatcher = awsAppSyncClient.subscribe(subscription);
+ subscriptionWatcher.execute(subCallback);
+ }
+
+ private AppSyncSubscriptionCall.Callback subCallback = new AppSyncSubscriptionCall.Callback() {
+ @Override
+ public void onResponse(@Nonnull final Response response) {
+ Log.i(TAG, "************* !!!! *******" + response.data().toString());
+
+ Handler h = new Handler(Looper.getMainLooper()) {
+ @Override
+ public void handleMessage (Message inputMessage) {
+ // Iterate over the players on the map
+ //TODO: if session matches; then if player in players update location, else make a new player and add their marker.
+ OnUpdatePlayerSubscription.OnUpdatePlayer updatePlayer = response.data().onUpdatePlayer();
+
+ Log.i(TAG, "updated user is " + updatePlayer.toString() + " session id is " + sessionId);
+ //checking if the updated player is in this session
+ if(updatePlayer.session().id().equals(sessionId)){
+ boolean contains = false;
+
+ //checking if the updated player is in the current player list
+ for(Player player : players){
+
+ // if we have a match update players lat/long
+ if(updatePlayer.id().equals(player.getId())){
+ List bananasList = new LinkedList<>();
+ bananasList.add(new LatLng(response.data().onUpdatePlayer().lat(),
+ response.data().onUpdatePlayer().lon()));
+ player.setLocations(bananasList); // sets location for the player
+ player.setIt(updatePlayer.isIt());
+ contains = true;
+ }
+ }
+
+ //if the player is in the session, but not in the player list, then make a new player and add them to the players list and add a marker
+ if(contains == false){
+ Marker marker = mMap.addMarker(new MarkerOptions()
+ .position(player.getLastLocation())
+ .title(player.getUsername()));
+ Circle circle = mMap.addCircle(new CircleOptions()
+ .center(player.getLastLocation())
+ .radius(tagDistance)
+ .fillColor(Color.TRANSPARENT)
+ .strokeWidth(3));
+
+ marker.setIcon(playerpin);
+ circle.setStrokeColor(notItColor);
+
+ Player newPlayer = new Player();
+ newPlayer.setId(updatePlayer.id());
+ newPlayer.setIt(false);
+ newPlayer.setMarker(marker);
+ newPlayer.setCircle(circle);
+ newPlayer.setUsername(updatePlayer.username());
+ List potatoes = new LinkedList<>();
+ potatoes.add(new LatLng(updatePlayer.lat(), updatePlayer.lon()));
+ newPlayer.setLocations(potatoes);
+
+ //adding player to the list of players in the game
+ players.add(newPlayer);
+ }
+ }
+// for(Player player : players) {
+// if(response.data().onUpdatePlayer().id().equals(player.getId())) {
+// // if true (we have a match) update players lat/long
+// List bananasList = new LinkedList<>();
+// bananasList.add(new LatLng(response.data().onUpdatePlayer().lat(),
+// response.data().onUpdatePlayer().lon()));
+// player.setLocations(bananasList); // sets location for the player
+// //Might have been causing the starting point to move
+//// player.getCircle().setCenter(player.getLastLocation());
+//// player.getMarker().setPosition(player.getLastLocation());
+// }
+// }
+ }
+ };
+ h.obtainMessage().sendToTarget();
+ }
+
+ @Override
+ public void onFailure(@Nonnull ApolloException e) {
+ Log.e(TAG, e.toString());
+ }
+
+ @Override
+ public void onCompleted() {
+ Log.i(TAG, "Subscription completed ");
+ }
+ };
+
+ // =============
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ stopTrackingLocation();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ subscribe();
+// startLocationUpdates();
+ }
+
+ /**
+ * Manipulates the map once available.
+ * This callback is triggered when the map is ready to be used.
+ * This is where we can add markers or lines, add listeners or move the camera. In this case,
+ * we just add a marker near Sydney, Australia.
+ * If Google Play services is not installed on the device, the user will be prompted to install
+ * it inside the SupportMapFragment. This method will only be triggered once the user has
+ * installed Google Play services and returned to the app.
+ */
+
+
+ @Override
+ public void onMapReady(GoogleMap googleMap) {
+ Log.i(TAG, "map is ready");
+ mMap = googleMap;
+
+ mMap.setMyLocationEnabled(true);
+ mMap.setOnMyLocationButtonClickListener(this);
+ mMap.setOnMyLocationClickListener(this);
+
+ startLocationUpdates();
+ }
+
+ public void stopTrackingLocation() {
+ mFusedLocationClient.removeLocationUpdates(mLocationCallback);
+ }
+
+ // Starts pulling location updates from the DB. 3 second delay to let the aws callbacks load first.
+ private void startLocationUpdates() {
+ try {
+ TimeUnit.SECONDS.sleep(3);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ mFusedLocationClient.requestLocationUpdates(getLocationRequest(), mLocationCallback, Looper.getMainLooper());
+ mMap.setMyLocationEnabled(true);
+ }
+
+ @Override
+ public void onMyLocationClick(@NonNull Location location) {
+ Toast.makeText(this, "Current Location:\n" + location, Toast.LENGTH_SHORT).show();
+ }
+
+ @Override
+ public boolean onMyLocationButtonClick() {
+ Toast.makeText(this, "My Location button clicked", Toast.LENGTH_SHORT).show();
+ return false;
+ }
+
+ // Called in startLocationUpdates to pull location updates from the DB
+ private LocationRequest getLocationRequest() {
+ Log.i(TAG, "getting location request");
+ LocationRequest locationRequest = new LocationRequest();
+ locationRequest.setInterval(10000);
+ locationRequest.setFastestInterval(5000);
+ locationRequest.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
+ return locationRequest;
+ }
+
+ // Creates markers and circles for each player in the list for that session
+ private void initializeMarkersAndCirclesForPlayers(List players) {
+ Log.i(TAG, "made it to initialized markers");
+ for(Player player: players) {
+ Marker marker = mMap.addMarker(new MarkerOptions()
+ .position(player.getLastLocation())
+ .title(player.getUsername()));
+ Circle circle = mMap.addCircle(new CircleOptions()
+ .center(player.getLastLocation())
+ .radius(tagDistance)
+ .fillColor(Color.TRANSPARENT)
+ .strokeWidth(3));
+
+ // change color of marker depending on if player is it or not
+ if (player.isIt()) {
+ marker.setIcon(zombiepin);
+ circle.setStrokeColor(itColor);
+ } else {
+ marker.setIcon(playerpin);
+ circle.setStrokeColor(notItColor);
+ }
+
+ player.setCircle(circle);
+ player.setMarker(marker);
+
+ }
+ // Add a marker in center of game camera and move the camera
+ mMap.moveCamera(CameraUpdateFactory.zoomTo(16));
+ mMap.moveCamera(CameraUpdateFactory.newLatLng(startingPoint));
+ Circle gameBounds = mMap.addCircle(new CircleOptions()
+ .center(startingPoint)
+ .radius(currentSession.radius())
+ .strokeColor(Color.BLUE)
+ .fillColor(Color.TRANSPARENT)
+ .strokeWidth(5));
+ }
+
+
+ private void updateMarkerAndCircleForAllPlayers(List players) {
+ Log.i(TAG, "updating markers");
+ Log.i(TAG, "How many players? " + players.size());
+
+ if(itPlayers == null){
+ itPlayers = new LinkedList<>();
+ for(Player player : players) {
+ if (player.isIt()) {
+ itPlayers.add(player);
+ }
+ }
+
+ if (itPlayers.isEmpty()) {
+ itPlayers.add(players.get(0));
+ }
+ }
+
+ List playersJustGotTagged = new LinkedList<>();
+ for (Player player : players) {
+ player.getMarker().setPosition(player.getLastLocation());
+ player.getCircle().setCenter(player.getLastLocation());
+ if (checkForTag(player)) {
+ Log.i(TAG, "In the updateMarkerAndCircleForAllPlayers");
+ playersJustGotTagged.add(player);
+
+// player.getMarker().setIcon(zombiepin);
+// player.getCircle().setStrokeColor(itColor);
+
+// mMap.addCircle(player.getCircle());
+ }
+// else {
+// player.getMarker().setIcon(playerpin);
+// player.getCircle().setStrokeColor(notItColor);
+// }
+ }
+ //TODO add the player instance is it update, only updates the player list so far
+ for(Player player : players){
+ if(player.getIt()){
+ player.getMarker().setIcon(zombiepin);
+ player.getCircle().setStrokeColor(itColor);
+ } else {
+ player.getMarker().setIcon(playerpin);
+ player.getCircle().setStrokeColor(notItColor);
+ }
+ }
+
+ for(Player p : players){
+ if(p.getId().equals(playerID)){
+ Log.i(TAG, "Player Ids match");
+ player.setIt(p.getIt());
+ }
+ }
+
+ itPlayers.addAll(playersJustGotTagged);
+ }
+
+ // Equation is from https://stackoverflow.com/questions/639695/how-to-convert-latitude-or-longitude-to-meters
+ // convert to two location points to distance between them in meters
+ private double distanceBetweenLatLongPoints(double lat1, double long1, double lat2, double long2) {
+ // radius of the Earth in km
+ double R = 6378.137;
+ double dLat = (lat2 * Math.PI / 180) - (lat1 * Math.PI / 180);
+ double dLong = (long2 * Math.PI / 180) - (long1 * Math.PI / 180);
+ double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
+ Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
+ Math.sin(dLong / 2) * Math.sin(dLong / 2);
+ double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
+ double d = R * c;
+ return d * 1000;
+ }
+
+ // check if the player is tagged by the it player
+ // check if the distance between the it player and the other player is less than the specified tag distance
+ private boolean isTagged(Player player, Player itPlayer) {
+ double distanceBetweenPlayers = distanceBetweenLatLongPoints(itPlayer.getLastLocation().latitude,
+ itPlayer.getLastLocation().longitude,
+ player.getLastLocation().latitude,
+ player.getLastLocation().longitude);
+
+ Log.i(TAG, "distance between players is " + distanceBetweenPlayers + " meters");
+
+// if (distanceBetweenPlayers < tagDistance) {
+ if (distanceBetweenPlayers < tagDistance && itPlayer != player) {
+ Log.i(TAG, "*************player is set to true*************");
+ player.setIt(true);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private boolean checkForTag(Player player) {
+ Log.i(TAG, "Made it into checkForTag");
+ if (player.isIt()) {
+ return false;
+ }
+ for(Player itPlayer : itPlayers) {
+ Log.i(TAG, itPlayer.toString());
+ if (isTagged(player, itPlayer)) {
+// Toast.makeText(this, "" + player.getUsername() + " is now it!!!", Toast.LENGTH_SHORT);
+ //TODO: If future views added to app, may need to change "this"?
+
+ //TODO: this activity traps the user, so disabled for now.
+// startActivity(new Intent(MapsActivity.this, NotificationActivity.class));
+ return true;
+
+ }
+ }
+ return false;
+ }
+
+ // query for the session associated with the sessionId that was passed from MainActivity
+ private void queryForSelectedSession(String sessionId) {
+ GetSessionQuery getSessionQuery = GetSessionQuery.builder().id(sessionId).build();
+ awsAppSyncClient.query(getSessionQuery)
+ .responseFetcher(AppSyncResponseFetchers.NETWORK_ONLY)
+ .enqueue(getSessionCallBack);
+ }
+
+ // Make a Player
+ private void createPlayer() {
+ Log.i(TAG, "Making a player with " + sessionId + " " + startingPoint.toString());
+ CreatePlayerInput input = CreatePlayerInput.builder()
+ .playerSessionId(sessionId)
+ .lat(startingPoint.latitude)
+ .lon(startingPoint.longitude)
+ .username(AWSMobileClient.getInstance().getUsername())
+ .isIt(false)
+ .build();
+ CreatePlayerMutation createPlayerMutation = CreatePlayerMutation.builder().input(input).build();
+
+ awsAppSyncClient.mutate(createPlayerMutation).enqueue(new GraphQLCall.Callback() {
+ @Override
+ public void onResponse(@Nonnull Response response) {
+ Log.i(TAG, "made it to creating a new player");
+ playerID = response.data().createPlayer().id();
+ player = new Player();
+ player.setId(playerID);
+ player.setIt(false);
+ player.setUsername(AWSMobileClient.getInstance().getUsername());
+ List bananas = new LinkedList<>();
+ bananas.add(startingPoint);
+ player.setLocations(bananas);
+ }
+
+ @Override
+ public void onFailure(@Nonnull ApolloException e) {
+ Log.e(TAG, "couldn't make a new player");
+ }
+ });
+ }
+
+ // Query for Player
+ private void queryForPlayerObject() {
+ GetPlayerQuery query = GetPlayerQuery.builder().id(playerID).build();
+ awsAppSyncClient.query(query)
+ .responseFetcher(AppSyncResponseFetchers.NETWORK_ONLY)
+ .enqueue(new GraphQLCall.Callback() {
+ @Override
+ public void onResponse(@Nonnull Response response) {
+ Log.i(TAG, "made it to making a query for player object");
+ // make a player instance
+ player = new Player(response.data().getPlayer());
+
+ // Making stuff on the map for the player
+ Handler markerHandler = new Handler(Looper.getMainLooper()){
+ @Override
+ public void handleMessage (Message inputMessage) {
+ Marker marker = mMap.addMarker(new MarkerOptions()
+ .position(player.getLastLocation())
+ .title(player.getUsername()));
+ Circle circle = mMap.addCircle(new CircleOptions()
+ .center(player.getLastLocation())
+ .radius(tagDistance)
+ .fillColor(Color.TRANSPARENT)
+ .strokeWidth(3));
+ // change color of marker depending on if player is it or not
+ if (player.isIt()) {
+ marker.setIcon(zombiepin);
+ circle.setStrokeColor(itColor);
+ } else {
+ marker.setIcon(playerpin);
+ circle.setStrokeColor(notItColor);
+ }
+
+ if(players == null){
+ players = new LinkedList<>();
+ }
+
+ player.setCircle(circle);
+ player.setMarker(marker);
+ //adding player to the list of players in the game
+ players.add(player);
+ }
+ };
+ markerHandler.obtainMessage().sendToTarget();
+ }
+
+ @Override
+ public void onFailure(@Nonnull ApolloException e) {
+ Log.e(TAG, "failed to get query for player object");
+ }
+ });
+ }
+
+ // Callback to get current game session
+ private GraphQLCall.Callback getSessionCallBack = new GraphQLCall.Callback() {
+ @Override
+ public void onResponse(@Nonnull final Response response) {
+ currentSession = response.data().getSession();
+ Log.i(TAG, "Current session is "+ currentSession.toString());
+ startingPoint = new LatLng(currentSession.lat(), currentSession.lon());
+ Log.i(TAG, "Starting point is " + startingPoint);
+
+ //once the session ID and starting loc are in place, then make the first player.
+ if (playerID == null) {
+ createPlayer();
+ } else {
+ queryForPlayerObject();
+ }
+
+ Log.i(TAG, "Made it to the after the if/else within getSessionCallBack");
+ //converting from GetSessionItems to players
+ players = playerConverter(currentSession.players().items());
+
+ Handler h = new Handler(Looper.getMainLooper()){
+ @Override
+ public void handleMessage(Message inputMessage){
+ Log.i(TAG, "Made it to handleMessage");
+ //lat and long for the session
+
+ try {
+ TimeUnit.SECONDS.sleep(1);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ initializeMarkersAndCirclesForPlayers(players);
+
+ }
+ };
+ h.obtainMessage().sendToTarget();
+ }
+
+ @Override
+ public void onFailure(@Nonnull ApolloException e) {
+ Log.e(TAG, "error from getSessionQuery: " + e.getMessage());
+ }
+ };
+
+ private List playerConverter(List incomingList){
+ List outGoingList = new LinkedList<>();
+ for(GetSessionQuery.Item item : incomingList){
+ Player newPlayer = new Player(item);
+ outGoingList.add(newPlayer);
+ }
+ return outGoingList;
+ };
+
+ // TODO: Build onDestroy that deletes user data from DB
+
+}
diff --git a/app/src/main/java/com/javaawesome/tag/NotificationActivity.java b/app/src/main/java/com/javaawesome/tag/NotificationActivity.java
new file mode 100644
index 0000000..438fee2
--- /dev/null
+++ b/app/src/main/java/com/javaawesome/tag/NotificationActivity.java
@@ -0,0 +1,27 @@
+package com.javaawesome.tag;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Vibrator;
+import android.view.View;
+
+public class NotificationActivity extends AppCompatActivity {
+
+ Vibrator vibrator;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_notification);
+
+ // Variable for vibration notification
+ vibrator = (Vibrator)getSystemService(VIBRATOR_SERVICE);
+ vibrator.vibrate(1000);
+ }
+
+ public void acknowledgePlayerIsIt (View view) {
+ finish();
+ }
+}
diff --git a/app/src/main/java/com/javaawesome/tag/Player.java b/app/src/main/java/com/javaawesome/tag/Player.java
new file mode 100644
index 0000000..8e3b386
--- /dev/null
+++ b/app/src/main/java/com/javaawesome/tag/Player.java
@@ -0,0 +1,126 @@
+package com.javaawesome.tag;
+
+import com.amazonaws.amplify.generated.graphql.GetPlayerQuery;
+import com.amazonaws.amplify.generated.graphql.GetSessionQuery;
+import com.google.android.gms.maps.model.Circle;
+import com.google.android.gms.maps.model.LatLng;
+import com.google.android.gms.maps.model.Marker;
+
+import java.util.LinkedList;
+import java.util.List;
+
+public class Player {
+ private String id;
+ private String username;
+ private Session gameSession;
+ private boolean isIt;
+ private List locations;
+ private Marker marker;
+ private Circle circle;
+
+ public Player(String username) {
+ this.username = username;
+ this.gameSession = null;
+ this.isIt = false;
+ this.locations = new LinkedList<>();
+ }
+
+ public Player(GetSessionQuery.Item item){
+ this.id = item.id();
+ this.username = item.username();
+ this.gameSession = null;
+ this.isIt = item.isIt();
+ locations = new LinkedList<>();
+ locations.add(new LatLng(item.lat(), item.lon()));
+ }
+
+ public Player(GetPlayerQuery.GetPlayer query) {
+ this.id = query.id();
+ this.username = query.username();
+ this.gameSession = null;
+ this.isIt = query.isIt();
+ this.locations = new LinkedList<>();
+ locations.add(new LatLng(query.lat(), query.lon()));
+ }
+
+ public Player() {
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public Session getGameSession() {
+ return gameSession;
+ }
+
+ public boolean getIt(){return isIt;}
+
+ public boolean isIt() {
+ return isIt;
+ }
+
+ public List getLocations() {
+ return locations;
+ }
+
+ public LatLng getLastLocation() { return locations.get(locations.size() -1); }
+
+ public void setIt(boolean it) {
+ isIt = it;
+ }
+
+ public void addLocations(LatLng location) {
+ this.locations.add(location);
+ }
+
+ public Marker getMarker() {
+ return marker;
+ }
+
+ public void setMarker(Marker marker) {
+ this.marker = marker;
+ }
+
+ public Circle getCircle() {
+ return circle;
+ }
+
+ public void setCircle(Circle circle) {
+ this.circle = circle;
+ }
+
+ @Override
+ public String toString() {
+ return "Player{" +
+ "id='" + id + '\'' +
+ ", username='" + username + '\'' +
+ ", gameSession=" + gameSession +
+ ", isIt=" + isIt +
+ ", locations=" + locations +
+ ", marker=" + marker +
+ ", circle=" + circle +
+ '}';
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public void setGameSession(Session gameSession) {
+ this.gameSession = gameSession;
+ }
+
+ public void setLocations(List locations) {
+ this.locations = locations;
+ }
+}
+
diff --git a/app/src/main/java/com/javaawesome/tag/Session.java b/app/src/main/java/com/javaawesome/tag/Session.java
new file mode 100644
index 0000000..0966a26
--- /dev/null
+++ b/app/src/main/java/com/javaawesome/tag/Session.java
@@ -0,0 +1,54 @@
+package com.javaawesome.tag;
+
+import com.amazonaws.amplify.generated.graphql.ListSessionsQuery;
+import com.google.android.gms.maps.model.LatLng;
+
+import java.util.LinkedList;
+import java.util.List;
+
+public class Session {
+ private String id;
+ private String title;
+ private List players;
+ private LatLng location;
+ private int radius;
+
+ public Session(String title, LatLng location, int radius) {
+ this.title = title;
+ this.players = new LinkedList<>();
+ this.location = location;
+ this.radius = radius;
+ }
+
+ public Session(ListSessionsQuery.Item item) {
+ this.id = item.id();
+ this.title = item.title();
+ this.players = new LinkedList<>();
+ }
+
+ public void addPlayer(Player player) {
+ this.players.add(player);
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public List getPlayers() {
+ return players;
+ }
+
+ public int getTotalPlayers() { return players.size(); }
+
+ public LatLng getLocation() {
+ return location;
+ }
+
+ public int getRadius() {
+ return radius;
+ }
+}
diff --git a/app/src/main/java/com/javaawesome/tag/SessionAdapter.java b/app/src/main/java/com/javaawesome/tag/SessionAdapter.java
new file mode 100644
index 0000000..7a93f5d
--- /dev/null
+++ b/app/src/main/java/com/javaawesome/tag/SessionAdapter.java
@@ -0,0 +1,84 @@
+package com.javaawesome.tag;
+
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.amazonaws.amplify.generated.graphql.ListSessionsQuery;
+
+import java.util.List;
+
+public class SessionAdapter extends RecyclerView.Adapter {
+
+ public OnSessionInteractionListener listener;
+ List sessions;
+
+ public SessionAdapter(List sessions, OnSessionInteractionListener listener) {
+ this.sessions = sessions;
+ this.listener = listener;
+ }
+
+ @NonNull
+ @Override
+ public SessionAdapter.SessionViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, int viewType) {
+ final View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.recyler_view_sessions, parent, false);
+ final SessionViewHolder holder = new SessionViewHolder(v);
+ v.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ new AlertDialog.Builder(parent.getContext())
+ .setTitle("Join game?")
+ .setMessage("Would you like to join this game?")
+ .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+// listener.addPlayerToChosenGame(holder.session);
+ listener.joinExistingGameSession(holder.session);
+ //TODO: Save the users ID to the database based on the session that they clicked
+ }
+ })
+ .setNegativeButton("No", null).show();
+ }
+ });
+ return holder;
+ };
+
+ public static class SessionViewHolder extends RecyclerView.ViewHolder {
+
+ ListSessionsQuery.Item session;
+ TextView sessionTitle;
+// TextView numberOfPlayers;
+
+ public SessionViewHolder(@NonNull View itemView) {
+ super(itemView);
+ this.sessionTitle = itemView.findViewById(R.id.session_title);
+// this.numberOfPlayers = itemView.findViewById(R.id.session_total_players);
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull SessionAdapter.SessionViewHolder holder, int position) {
+ ListSessionsQuery.Item sessionAtPosition = this.sessions.get(position);
+ holder.session = sessionAtPosition;
+ holder.sessionTitle.setText(sessionAtPosition.title());
+// holder.numberOfPlayers.setText("Population: " + sessionAtPosition.players());
+ }
+
+ @Override
+ public int getItemCount() {
+ return sessions.size();
+ }
+
+ public static interface OnSessionInteractionListener {
+ public void joinExistingGameSession(ListSessionsQuery.Item session);
+// public void addPlayerToChosenGame(ListSessionsQuery.Item session);
+ }
+}
diff --git a/app/src/main/java/com/javaawesome/tag/ShowMeYourFace.java b/app/src/main/java/com/javaawesome/tag/ShowMeYourFace.java
new file mode 100644
index 0000000..b7198ed
--- /dev/null
+++ b/app/src/main/java/com/javaawesome/tag/ShowMeYourFace.java
@@ -0,0 +1,285 @@
+package com.javaawesome.tag;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.camera.core.CameraX;
+import androidx.camera.core.ImageCapture;
+import androidx.camera.core.ImageCaptureConfig;
+import androidx.camera.core.ImageProxy;
+import androidx.camera.core.Preview;
+import androidx.camera.core.PreviewConfig;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.Environment;
+import android.Manifest;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.util.Log;
+import android.util.Size;
+import android.view.TextureView;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.Toast;
+
+import com.amazonaws.mobile.client.AWSMobileClient;
+import com.amazonaws.mobile.config.AWSConfiguration;
+import com.amazonaws.mobileconnectors.appsync.AWSAppSyncClient;
+import com.amazonaws.mobileconnectors.s3.transferutility.TransferListener;
+import com.amazonaws.mobileconnectors.s3.transferutility.TransferObserver;
+import com.amazonaws.mobileconnectors.s3.transferutility.TransferState;
+import com.amazonaws.mobileconnectors.s3.transferutility.TransferUtility;
+import com.amazonaws.services.s3.AmazonS3Client;
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+
+import java.io.File;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+
+public class ShowMeYourFace extends AppCompatActivity {
+ private static final String TAG = "ahren:javatag";
+ private static boolean upload = false;
+ private ImageCapture imageCapture;
+ final CameraX.LensFacing camera = CameraX.LensFacing.FRONT;
+ String profPicPath = null;
+ String s3path = null;
+ AWSAppSyncClient mAWSAppSyncClient;
+ File profilePic = null;
+
+ public static boolean isUpload() {
+ return upload;
+ }
+
+ public static void setUpload(boolean upload) {
+ ShowMeYourFace.upload = upload;
+ }
+
+ public void goToPicturePreview(String profilePicPath){
+ Intent goToPicturePreview = new Intent(this, picturePreview.class);
+ this.startActivity(goToPicturePreview.putExtra("picpath",profilePicPath));
+ }
+
+ protected void onResume() {
+ super.onResume();
+ if(upload && profPicPath != null && profilePic != null){
+ uploadDataToS3(s3path, profilePic);
+ finish();
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_show_me_your_face);
+
+ mAWSAppSyncClient = AWSAppSyncClient.builder()
+ .context(getApplicationContext())
+ .awsConfiguration(new AWSConfiguration(getApplicationContext()))
+ .build();
+
+//************* Check If app has camera permissions ************************
+ Log.i(TAG, "onCreate: Hello World");
+ if (ContextCompat.checkSelfPermission(this,
+ Manifest.permission.CAMERA)
+ != PackageManager.PERMISSION_GRANTED){
+ if(ActivityCompat.shouldShowRequestPermissionRationale(this,
+ Manifest.permission.CAMERA)) {
+ Log.i(TAG, "onCreate: permission not granted");
+ // Show an explanation to the user *asynchronously* -- don't block
+ // this thread waiting for the user's response! After the user
+ // sees the explanation, try again to request the permission.
+ } else {
+ // No explanation needed; request the permission
+ ActivityCompat.requestPermissions(this,
+ new String[]{Manifest.permission.CAMERA},
+ 1);
+ }
+ }else {
+
+//************************************ Setup Buttons **********************************************
+ FloatingActionButton picSnap = findViewById(R.id.picSnap);
+// FloatingActionButton switchCamera = findViewById(R.id.fab_switch_camera);
+// FloatingActionButton fab_flash = findViewById(R.id.fab_flash);
+
+// ********************************** Setup Camera **********************************************
+ bindCamera();
+
+//*************************************** Shutter Button Action ****************************************
+
+ picSnap.setOnClickListener(event -> {
+ s3path = AWSMobileClient.getInstance().getUsername()+ "profilePic.png";
+ profilePic = new File(Environment.getExternalStorageDirectory() + "/" + s3path);
+//
+// New Thread
+ Executor executor = Executors.newSingleThreadExecutor();
+
+ imageCapture.takePicture(profilePic, executor, new ImageCapture.OnImageSavedListener() {
+ @Override
+ public void onError(
+ @NonNull ImageCapture.ImageCaptureError imageCaptureError, @NonNull String message, Throwable cause) {
+// TODO: insert your code here.
+ }
+
+ @Override
+ public void onImageSaved(@NonNull File file) {
+ profPicPath = file.getAbsolutePath();
+ Log.v(TAG, "onImageSaved: Saved");
+ String msg = "file saved at " + file.getAbsolutePath();
+ Log.i(TAG, "onImageSaved: "+msg);
+ goToPicturePreview(file.getAbsolutePath());
+
+ }
+ });
+
+ });
+
+
+// @Override
+// public void onClick(View view){
+// File file = new File(Environment.getExternalStorageDirectory() + "/" + System.currentTimeMillis() + ".png");
+// imageCapture.takePicture(file, new ImageCapture.OnImageSavedListener(){
+// @Override
+// public void onImageSaved(@NonNull File file) {
+// String msg = "Pic captured at " + file.getAbsolutePath();
+// Toast.makeText(getBaseContext(), msg,Toast.LENGTH_LONG).show();
+// }
+//
+// @Override
+// public void onError(@NonNull ImageCapture.ImageCaptureError imageCaptureError, @NonNull String message, @Nullable Throwable cause) {
+// }
+// });
+// }
+// ImageCaptureConfig imageCaptureConfig = new ImageCaptureConfig.Builder().setCaptureMode(ImageCapture.CaptureMode.MIN_LATENCY)
+// .setTargetRotation(getWindowManager().getDefaultDisplay().getRotation()).build();
+// final ImageCapture imgCap = new ImageCapture(imageCaptureConfig);
+
+
+
+
+
+
+
+
+//***************************** Turn Off / On Flash***********************************************
+// Adapted from Kotlin code at https://gabrieltanner.org/blog/android-camerax
+// fab_flash.setOnClickListener(new View.OnClickListener() {
+// @Override
+// public void onClick(View view) {
+// FlashMode flashMode = imageCapture.getFlashMode();
+// if (flashMode == FlashMode.ON) {
+// imageCapture.setFlashMode(FlashMode.OFF);
+// } else {
+// imageCapture.setFlashMode(FlashMode.ON);
+// }
+// }
+// });
+
+// ******************* Changes the lens direction if the button is clicked ****************************
+
+// switchCamera.setOnClickListener(new View.OnClickListener() {
+// @Override
+// public void onClick(View view) {
+// if (CameraX.LensFacing.FRONT == camera) {
+// camera = CameraX.LensFacing.BACK;
+// } else {
+// camera[0] = CameraX.LensFacing.FRONT;
+// }
+// bindCamera();
+// }
+// });
+ }
+ }
+
+
+// ******************************* Method that sets up camera and preview settings ***************************************
+ private void bindCamera(){
+ CameraX.unbindAll();
+
+ final TextureView textureView = findViewById(R.id.view_finder);
+ Size screen = new Size(textureView.getWidth(), textureView.getHeight()); //size of the screen
+
+
+
+ PreviewConfig config = new PreviewConfig.Builder()
+ .setLensFacing(camera)
+ .setTargetResolution(screen)
+ .build();
+ Preview preview = new Preview(config);
+
+// Set the display view for the camera preview
+ preview.setOnPreviewOutputUpdateListener(new Preview.OnPreviewOutputUpdateListener() {
+ @Override
+ public void onUpdated(@NonNull Preview.PreviewOutput previewOutput) {
+ // Your code here. For example, use
+ textureView.setSurfaceTexture(previewOutput.getSurfaceTexture());
+ }
+
+ });
+
+ ImageCaptureConfig config2 =
+ new ImageCaptureConfig.Builder()
+ .setTargetRotation(getWindowManager().getDefaultDisplay().getRotation())
+ .setLensFacing(camera)
+ .build();
+
+ imageCapture = new ImageCapture(config2);
+
+// Causes camera u=instance to only exist on this activity is started and destroyed on start and finish
+ CameraX.bindToLifecycle(this, imageCapture, preview);
+ }
+
+//************************************ Upload to S3 **********************************************
+ protected void uploadDataToS3( String picName, File profilePic){
+ TransferUtility transferUtility =
+ TransferUtility.builder()
+ .context(getApplicationContext())
+ .awsConfiguration(AWSMobileClient.getInstance().getConfiguration())
+ .s3Client(new AmazonS3Client(AWSMobileClient.getInstance()))
+ .build();
+ final TransferObserver uploadObserver =
+ transferUtility.upload("public/" + picName , profilePic);
+
+ // Attach a listener to the observer to get state update and progress notifications
+ uploadObserver.setTransferListener(new TransferListener() {
+
+ @Override
+ public void onStateChanged(int id, TransferState state) {
+ if (TransferState.COMPLETED == state) {
+ upload=false;
+ Log.i(TAG, "onStateChanged: Uploaded Profile Pic");
+ Toast.makeText(getBaseContext(), "Picture Save Complete",Toast.LENGTH_LONG).show();
+ String bucketPath = uploadObserver.getBucket() +"/" +uploadObserver.getKey();
+ Log.i(TAG, "onStateChanged: " + bucketPath + "*************************************************************************");
+ }
+ }
+ @Override
+ public void onProgressChanged(int id, long bytesCurrent, long bytesTotal) {
+ float percentDonef = ((float) bytesCurrent / (float) bytesTotal) * 100;
+ int percentDone = (int) percentDonef;
+ Log.d(TAG, "ID:" + id + " bytesCurrent: " + bytesCurrent
+ + " bytesTotal: " + bytesTotal + " " + percentDone + "%");
+ }
+
+ @Override
+ public void onError(int id, Exception ex) {
+ // Handle errors
+ }
+ });
+ }
+
+}
+//// **************** Checks to see if flash is present on the current camera and *********************
+// try {
+// CameraInfo cameraInfo = CameraX.getCameraInfo(camera);
+// LiveData isFlashAvailable = cameraInfo.isFlashAvailable();
+// fab_flash.setVisibility(isFlashAvailable.getValue() ? View.VISIBLE : View.INVISIBLE);
+// } catch (CameraInfoUnavailableException e) {
+// Log.w(TAG, "Cannot get flash available information", e);
+// fab_flash.setVisibility(View.VISIBLE);
+// }
diff --git a/app/src/main/java/com/javaawesome/tag/UserProfile.java b/app/src/main/java/com/javaawesome/tag/UserProfile.java
new file mode 100644
index 0000000..d94b3f1
--- /dev/null
+++ b/app/src/main/java/com/javaawesome/tag/UserProfile.java
@@ -0,0 +1,14 @@
+package com.javaawesome.tag;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import android.os.Bundle;
+
+public class UserProfile extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_user_profile);
+ }
+}
diff --git a/app/src/main/java/com/javaawesome/tag/Utility.java b/app/src/main/java/com/javaawesome/tag/Utility.java
new file mode 100644
index 0000000..248be99
--- /dev/null
+++ b/app/src/main/java/com/javaawesome/tag/Utility.java
@@ -0,0 +1,19 @@
+package com.javaawesome.tag;
+
+public class Utility {
+
+ // Equation is from https://stackoverflow.com/questions/639695/how-to-convert-latitude-or-longitude-to-meters
+ // convert to two location points to distance between them in meters
+ protected static double distanceBetweenLatLongPoints(double lat1, double long1, double lat2, double long2) {
+ // radius of the Earth in km
+ double R = 6378.137;
+ double dLat = (lat2 * Math.PI / 180) - (lat1 * Math.PI / 180);
+ double dLong = (long2 * Math.PI / 180) - (long1 * Math.PI / 180);
+ double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
+ Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
+ Math.sin(dLong / 2) * Math.sin(dLong / 2);
+ double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
+ double d = R * c;
+ return d * 1000;
+ }
+}
diff --git a/app/src/main/java/com/javaawesome/tag/picturePreview.java b/app/src/main/java/com/javaawesome/tag/picturePreview.java
new file mode 100644
index 0000000..4d293ec
--- /dev/null
+++ b/app/src/main/java/com/javaawesome/tag/picturePreview.java
@@ -0,0 +1,50 @@
+package com.javaawesome.tag;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import android.annotation.SuppressLint;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.util.Log;
+import android.view.View;
+import android.widget.ImageView;
+
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+
+
+public class picturePreview extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_picture_preview);
+ String TAG = "ahren:picturePreview";
+ ImageView profilePicPreview;
+ Log.i(TAG, "onCreate: hello");
+//TODO look at passing in the file and uploading here rather than
+ ImageView pic = findViewById(R.id.profilePicPreview);
+ String picPath = getIntent().getStringExtra("picpath");
+ pic.setImageURI(Uri.parse(picPath));
+ FloatingActionButton accept = findViewById(R.id.accept);
+ FloatingActionButton deny = findViewById(R.id.deny);
+
+ accept.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ ShowMeYourFace.setUpload(true);
+ finish();
+ }
+ });
+
+ deny.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ finish();
+ }
+ });
+
+ }
+}
diff --git a/app/src/main/java/com/javaawesome/tag/recycler_view_sessions.java b/app/src/main/java/com/javaawesome/tag/recycler_view_sessions.java
new file mode 100644
index 0000000..7525998
--- /dev/null
+++ b/app/src/main/java/com/javaawesome/tag/recycler_view_sessions.java
@@ -0,0 +1,110 @@
+package com.javaawesome.tag;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+
+import androidx.fragment.app.Fragment;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+
+/**
+ * A simple {@link Fragment} subclass.
+ * Activities that contain this fragment must implement the
+ * {@link recycler_view_sessions.OnFragmentInteractionListener} interface
+ * to handle interaction events.
+ * Use the {@link recycler_view_sessions#newInstance} factory method to
+ * create an instance of this fragment.
+ */
+public class recycler_view_sessions extends Fragment {
+ // TODO: Rename parameter arguments, choose names that match
+ // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
+ private static final String ARG_PARAM1 = "param1";
+ private static final String ARG_PARAM2 = "param2";
+
+ // TODO: Rename and change types of parameters
+ private String mParam1;
+ private String mParam2;
+
+ private OnFragmentInteractionListener mListener;
+
+ public recycler_view_sessions() {
+ // Required empty public constructor
+ }
+
+ /**
+ * Use this factory method to create a new instance of
+ * this fragment using the provided parameters.
+ *
+ * @param param1 Parameter 1.
+ * @param param2 Parameter 2.
+ * @return A new instance of fragment recycler_view_sessions.
+ */
+ // TODO: Rename and change types and number of parameters
+ public static recycler_view_sessions newInstance(String param1, String param2) {
+ recycler_view_sessions fragment = new recycler_view_sessions();
+ Bundle args = new Bundle();
+ args.putString(ARG_PARAM1, param1);
+ args.putString(ARG_PARAM2, param2);
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (getArguments() != null) {
+ mParam1 = getArguments().getString(ARG_PARAM1);
+ mParam2 = getArguments().getString(ARG_PARAM2);
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ // Inflate the layout for this fragment
+ return inflater.inflate(R.layout.fragment_recycler_view_sessions, container, false);
+ }
+
+ // TODO: Rename method, update argument and hook method into UI event
+ public void onButtonPressed(Uri uri) {
+ if (mListener != null) {
+ mListener.onFragmentInteraction(uri);
+ }
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ if (context instanceof OnFragmentInteractionListener) {
+ mListener = (OnFragmentInteractionListener) context;
+ } else {
+ throw new RuntimeException(context.toString()
+ + " must implement OnFragmentInteractionListener");
+ }
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ mListener = null;
+ }
+
+ /**
+ * This interface must be implemented by activities that contain this
+ * fragment to allow an interaction in this fragment to be communicated
+ * to the activity and potentially other fragments contained in that
+ * activity.
+ *
+ * See the Android Training lesson Communicating with Other Fragments for more information.
+ */
+ public interface OnFragmentInteractionListener {
+ // TODO: Update argument type and name
+ void onFragmentInteraction(Uri uri);
+ }
+}
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..fde21ee
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..fa4801d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/photo_camera.png b/app/src/main/res/drawable/photo_camera.png
new file mode 100644
index 0000000..f4bfe9f
Binary files /dev/null and b/app/src/main/res/drawable/photo_camera.png differ
diff --git a/app/src/main/res/drawable/playerpin.png b/app/src/main/res/drawable/playerpin.png
new file mode 100644
index 0000000..84ca952
Binary files /dev/null and b/app/src/main/res/drawable/playerpin.png differ
diff --git a/app/src/main/res/drawable/zombieicon.jpg b/app/src/main/res/drawable/zombieicon.jpg
new file mode 100644
index 0000000..0b7b66e
Binary files /dev/null and b/app/src/main/res/drawable/zombieicon.jpg differ
diff --git a/app/src/main/res/drawable/zombiepin.png b/app/src/main/res/drawable/zombiepin.png
new file mode 100644
index 0000000..500c984
Binary files /dev/null and b/app/src/main/res/drawable/zombiepin.png differ
diff --git a/app/src/main/res/font/infected.ttf b/app/src/main/res/font/infected.ttf
new file mode 100644
index 0000000..881f0b6
Binary files /dev/null and b/app/src/main/res/font/infected.ttf differ
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..73f7e61
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_maps.xml b/app/src/main/res/layout/activity_maps.xml
new file mode 100644
index 0000000..73f24d1
--- /dev/null
+++ b/app/src/main/res/layout/activity_maps.xml
@@ -0,0 +1,9 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_notification.xml b/app/src/main/res/layout/activity_notification.xml
new file mode 100644
index 0000000..f0846d2
--- /dev/null
+++ b/app/src/main/res/layout/activity_notification.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_picture_preview.xml b/app/src/main/res/layout/activity_picture_preview.xml
new file mode 100644
index 0000000..d99cfe5
--- /dev/null
+++ b/app/src/main/res/layout/activity_picture_preview.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_show_me_your_face.xml b/app/src/main/res/layout/activity_show_me_your_face.xml
new file mode 100644
index 0000000..d3d9a5c
--- /dev/null
+++ b/app/src/main/res/layout/activity_show_me_your_face.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_user_profile.xml b/app/src/main/res/layout/activity_user_profile.xml
new file mode 100644
index 0000000..7f83062
--- /dev/null
+++ b/app/src/main/res/layout/activity_user_profile.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_recycler_view_sessions.xml b/app/src/main/res/layout/fragment_recycler_view_sessions.xml
new file mode 100644
index 0000000..15a5fde
--- /dev/null
+++ b/app/src/main/res/layout/fragment_recycler_view_sessions.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/recyler_view_sessions.xml b/app/src/main/res/layout/recyler_view_sessions.xml
new file mode 100644
index 0000000..2f77e02
--- /dev/null
+++ b/app/src/main/res/layout/recyler_view_sessions.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..67820c5
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..67820c5
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..8cb7e61
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..09fe5f2
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..b8eab76
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..59e6cae
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..9f3f9ef
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..8267247
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..08d0bd9
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..57c029d
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..6633cf8
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..17a309d
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..8edca5d
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..909eae6
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..01c014b
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..ae1749f
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..c63e8c4
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..acc12f8
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,12 @@
+
+
+ #81cba8 //Green medium
+ #005d48 //Green dark
+ #c2dfa7 //Green main skin tone
+ #70af92 //Green medium dark
+ #f484b4
+ #df4897
+ #000000
+ #000000
+ #c2dfa7
+
\ No newline at end of file
diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml
new file mode 100644
index 0000000..cfa9be0
--- /dev/null
+++ b/app/src/main/res/values/ic_launcher_background.xml
@@ -0,0 +1,4 @@
+
+
+ #FFFFFF
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..747877a
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,14 @@
+
+ Zombie Tag
+ Map
+ Name Your Game
+ Sign Out
+ Start New Game
+ I\'ve been infected!
+ Session Title
+ Session Total Players
+ Welcome to Zombie Tag!\nHow long can you survive?\nStart or join a game below.
+ Test your survival skills with a current game listed below
+ Population:
+
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..ec4c360
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/test/java/com/javaawesome/tag/ExampleUnitTest.java b/app/src/test/java/com/javaawesome/tag/ExampleUnitTest.java
new file mode 100644
index 0000000..d469ec0
--- /dev/null
+++ b/app/src/test/java/com/javaawesome/tag/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package com.javaawesome.tag;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/assets/Page_1.png b/assets/Page_1.png
new file mode 100644
index 0000000..23f9b6b
Binary files /dev/null and b/assets/Page_1.png differ
diff --git a/assets/Page_2.png b/assets/Page_2.png
new file mode 100644
index 0000000..9c00576
Binary files /dev/null and b/assets/Page_2.png differ
diff --git a/assets/Page_3.png b/assets/Page_3.png
new file mode 100644
index 0000000..73f215b
Binary files /dev/null and b/assets/Page_3.png differ
diff --git a/assets/javatag.pdf b/assets/javatag.pdf
new file mode 100644
index 0000000..05e48d4
Binary files /dev/null and b/assets/javatag.pdf differ
diff --git a/assets/javatag_page1.jpg b/assets/javatag_page1.jpg
new file mode 100644
index 0000000..8f7b307
Binary files /dev/null and b/assets/javatag_page1.jpg differ
diff --git a/assets/javatag_page2.jpg b/assets/javatag_page2.jpg
new file mode 100644
index 0000000..ed4dc5a
Binary files /dev/null and b/assets/javatag_page2.jpg differ
diff --git a/assets/javatag_page3.jpg b/assets/javatag_page3.jpg
new file mode 100644
index 0000000..29be87d
Binary files /dev/null and b/assets/javatag_page3.jpg differ
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..95e78e3
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,27 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ google()
+ jcenter()
+
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.5.2'
+ classpath 'com.amazonaws:aws-android-sdk-appsync-gradle-plugin:2.9.+'
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..74fbe26
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,25 @@
+# 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.
+org.gradle.jvmargs=-Xmx1536m
+
+# 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=true
+
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..f6b961f
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..c37e72f
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Nov 18 10:34:46 PST 2019
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..cccdd3d
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## 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=""
+
+# 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, switch paths to Windows format before running java
+if $cygwin ; 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=$((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"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..e95643d
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@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 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=
+
+@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
diff --git a/output.json b/output.json
new file mode 100644
index 0000000..92a0784
--- /dev/null
+++ b/output.json
@@ -0,0 +1 @@
+[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":1,"versionName":"1.0","enabled":true,"outputFile":"app-debug.apk","fullName":"debug","baseName":"debug"},"path":"app-debug.apk","properties":{}}]
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..605971f
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,2 @@
+include ':app'
+rootProject.name='Tag'
diff --git a/zombietag.apk b/zombietag.apk
new file mode 100644
index 0000000..eb74420
Binary files /dev/null and b/zombietag.apk differ