├── .firebaserc ├── .gitignore ├── .metadata ├── README.md ├── android.iml ├── android ├── .gitignore ├── app │ ├── build.gradle │ ├── google-services.json │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── mszalek │ │ │ └── capchallenge │ │ │ └── MainActivity.java │ │ └── res │ │ ├── drawable │ │ └── launch_background.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ └── values │ │ ├── strings.xml │ │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle ├── cap_challenge.iml ├── cap_challenge_android.iml ├── firebase.json ├── fonts └── PermanentMarker-Regular.ttf ├── functions ├── package-lock.json ├── package.json ├── src │ └── index.ts ├── tsconfig.json └── tslint.json ├── images ├── bottle_empty.png ├── bottle_filled.png ├── bubbles.jpg ├── bubbles_long.jpg ├── cap.png ├── cap_reverse.png ├── challenge_bottle.png ├── coca_cola │ ├── bottle_big.png │ ├── bottle_small.png │ └── can.png ├── coca_cola_baner.jpg ├── coca_family.png ├── coke_light.png ├── coke_zero.png ├── fanta │ ├── bottle_big.png │ ├── bottle_small.png │ └── can.png ├── fanta_baner.jpg ├── qr_code.png ├── sign-in-facebook.png ├── sign-in-google.png ├── sprite │ ├── bottle_big.png │ ├── bottle_medium.png │ ├── bottle_small.png │ └── can.png ├── sprite_baner.jpeg ├── ticket-vertical.jpg └── ticket.jpg ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ └── contents.xcworkspacedata └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-App-1024x1024@1x.png │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ └── Icon-App-83.5x83.5@2x.png │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── LaunchImage.png │ │ ├── LaunchImage@2x.png │ │ ├── LaunchImage@3x.png │ │ └── README.md │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h ├── lib ├── generated │ └── i18n.dart ├── logic │ ├── actions.dart │ ├── app_state.dart │ ├── auth_service.dart │ ├── http_service.dart │ ├── middleware.dart │ └── reducer.dart ├── main.dart ├── models │ ├── add_code_result.dart │ ├── bottle.dart │ ├── challenge.dart │ └── user.dart └── widgets │ ├── challenges │ ├── challenge_card.dart │ ├── challenge_common_views.dart │ ├── challenge_details_page.dart │ └── challenges_page.dart │ ├── code_cap.dart │ ├── collection │ ├── collection_grid_item.dart │ ├── collection_page.dart │ └── ticket_page.dart │ ├── daily_challenge │ └── timer_page.dart │ ├── login │ └── login_page.dart │ ├── main_scaffold.dart │ ├── points_indicator.dart │ ├── profile_dialog.dart │ └── ranking │ └── ranking_page.dart ├── pubspec.lock ├── pubspec.yaml ├── res └── values │ ├── strings_en.arb │ └── strings_pl.arb ├── screenshots ├── addBottle.gif ├── challenges.gif ├── login.gif ├── ranking.gif └── shake.gif └── test └── widget_test.dart /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "cap-challenge" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .atom/ 3 | .idea 4 | .vscode/ 5 | .packages 6 | .pub/ 7 | build/ 8 | ios/.generated/ 9 | packages 10 | .flutter-plugins 11 | /functions/node_modules/ 12 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 3ea4d06340a97a1e9d7cae97567c64e0569dcaa2 8 | channel: beta 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cap Challenge 2 | Get it on Google Play 3 | 4 | A concept of loyality application for Coca-Cola clients written in Flutter. Main idea of app is to encourage clients of Coca-Cola to buy more products. 5 | 6 | This project was made for one of my courses at University. I know UX is not very intuitive but it's the best I could do in that time frame. 7 | 8 | ## Screenshots 9 | 10 | Login Bubbles Ranking 11 | AddBottle Challenges 12 | 13 | ## Features 14 | ### Facebook and Google log in 15 | App uses Facebook and Google auth providers in combination with Firebase Auth 16 | Login 17 | 18 | ### Bubbles challenge 19 | *Note: I had no idea how to explain it in UI* 20 | The concept is that there is global pool of bubbles. Users can shake their phones to add bubbles. If user adds last (10th) bubble, he or she earns extra points. There is 5 second interval between new shakes available. 21 | Bubbles 22 | 23 | ### Adding caps/bottles 24 | User can type in code from under the bottle to earn that bottle into collection and to gain some points. As for now, you can use one code multiple times. Some of available codes: 25 | `C111111111`, `C2cccccccc`, `C3qwqwqwqw`, `L4qaqaqaqa`, `L2mmmmmmmm`, `Z1zzzzzzzz`, `Z2aaddaadd`, `Z333333333`, `Z4tutututu`, `C4oooooooo`, `S2ssssssss`, `S3uuuuuuuu`, `S4hhhhhhhh`, `S5kkkkkkkk`, `F3fjfjfjfj`, `F5colacola`. 26 | AddBottle 27 | 28 | ### Challenges 29 | If user has collected right amount of bottles, he or she can use them to complete challenge to gain extra points and a ticket for free Cola can. 30 | Challenges 31 | 32 | ### Ranking 33 | Ranking of users with their points 34 | Ranking 35 | 36 | -------------------------------------------------------------------------------- /android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.class 3 | .gradle 4 | /local.properties 5 | /.idea/workspace.xml 6 | /.idea/libraries 7 | .DS_Store 8 | /build 9 | /captures 10 | GeneratedPluginRegistrant.java 11 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | apply plugin: 'com.android.application' 15 | apply plugin: 'com.google.gms.google-services' 16 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 17 | 18 | def keystorePropertiesFile = rootProject.file("key.properties") 19 | def keystoreProperties = new Properties() 20 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 21 | 22 | android { 23 | compileSdkVersion 27 24 | 25 | lintOptions { 26 | disable 'InvalidPackage' 27 | } 28 | 29 | defaultConfig { 30 | applicationId "com.mszalek.capchallenge" 31 | minSdkVersion 16 32 | targetSdkVersion 27 33 | versionCode 8 34 | versionName "1.2" 35 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 36 | } 37 | 38 | signingConfigs { 39 | release { 40 | keyAlias keystoreProperties['keyAlias'] 41 | keyPassword keystoreProperties['keyPassword'] 42 | storeFile file(keystoreProperties['storeFile']) 43 | storePassword keystoreProperties['storePassword'] 44 | } 45 | } 46 | buildTypes { 47 | release { 48 | signingConfig signingConfigs.release 49 | } 50 | } 51 | } 52 | 53 | flutter { 54 | source '../..' 55 | } 56 | 57 | dependencies { 58 | testImplementation 'junit:junit:4.12' 59 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 60 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' 61 | } 62 | -------------------------------------------------------------------------------- /android/app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "506626629602", 4 | "firebase_url": "https://cap-challenge.firebaseio.com", 5 | "project_id": "cap-challenge", 6 | "storage_bucket": "cap-challenge.appspot.com" 7 | }, 8 | "client": [ 9 | { 10 | "client_info": { 11 | "mobilesdk_app_id": "1:506626629602:android:c123024910d55d86", 12 | "android_client_info": { 13 | "package_name": "com.mszalek.capchallenge" 14 | } 15 | }, 16 | "oauth_client": [ 17 | { 18 | "client_id": "506626629602-fl10c89l4qr8b7dldct4e9f0vj70u4eh.apps.googleusercontent.com", 19 | "client_type": 1, 20 | "android_info": { 21 | "package_name": "com.mszalek.capchallenge", 22 | "certificate_hash": "4cfcaae693f041e62c51224b17ff5920ca45db7c" 23 | } 24 | }, 25 | { 26 | "client_id": "506626629602-n892s5iidd9ertp0lhjtm77ogkuf3rvs.apps.googleusercontent.com", 27 | "client_type": 1, 28 | "android_info": { 29 | "package_name": "com.mszalek.capchallenge", 30 | "certificate_hash": "7305c2681fd5185da3dff42cd346b1290238e177" 31 | } 32 | }, 33 | { 34 | "client_id": "506626629602-930q9u6bt8h4gnc29uvlfs83itpdqlg8.apps.googleusercontent.com", 35 | "client_type": 1, 36 | "android_info": { 37 | "package_name": "com.mszalek.capchallenge", 38 | "certificate_hash": "a64ae41edc2af37d0400491cffdfc08e96ca7acb" 39 | } 40 | }, 41 | { 42 | "client_id": "506626629602-5e105hgn58e00oth18f1hhdh4vlej87k.apps.googleusercontent.com", 43 | "client_type": 1, 44 | "android_info": { 45 | "package_name": "com.mszalek.capchallenge", 46 | "certificate_hash": "78cd2db691f417e2b4a13f9918971eb12f757527" 47 | } 48 | }, 49 | { 50 | "client_id": "506626629602-fbosetmu8blk8v8pnkqeqm4m0u0g4pge.apps.googleusercontent.com", 51 | "client_type": 1, 52 | "android_info": { 53 | "package_name": "com.mszalek.capchallenge", 54 | "certificate_hash": "edf7cf3a1df53849acc766d2809eb03c60928e5b" 55 | } 56 | }, 57 | { 58 | "client_id": "506626629602-t53t06721gbgjh0di6qnoruvlshfsilr.apps.googleusercontent.com", 59 | "client_type": 3 60 | } 61 | ], 62 | "api_key": [ 63 | { 64 | "current_key": "AIzaSyA62F66el7iid5o5WK6jn7VylmSr_RyKOo" 65 | } 66 | ], 67 | "services": { 68 | "analytics_service": { 69 | "status": 1 70 | }, 71 | "appinvite_service": { 72 | "status": 2, 73 | "other_platform_oauth_client": [ 74 | { 75 | "client_id": "506626629602-t53t06721gbgjh0di6qnoruvlshfsilr.apps.googleusercontent.com", 76 | "client_type": 3 77 | }, 78 | { 79 | "client_id": "506626629602-m9smnjb9201eu8l598bbs368jo7n5c82.apps.googleusercontent.com", 80 | "client_type": 2, 81 | "ios_info": { 82 | "bundle_id": "com.mszalek.capChallenge" 83 | } 84 | } 85 | ] 86 | }, 87 | "ads_service": { 88 | "status": 2 89 | } 90 | } 91 | } 92 | ], 93 | "configuration_version": "1" 94 | } -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 15 | 19 | 26 | 30 | 33 | 34 | 35 | 36 | 37 | 38 | 40 | 41 | 45 | 46 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/mszalek/capchallenge/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.mszalek.capchallenge; 2 | 3 | import android.os.Bundle; 4 | import io.flutter.app.FlutterActivity; 5 | import io.flutter.plugins.GeneratedPluginRegistrant; 6 | 7 | public class MainActivity extends FlutterActivity { 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | GeneratedPluginRegistrant.registerWith(this); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Cap Challenge. 4 | 227649727794199 5 | fb227649727794199 6 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.0.1' 9 | classpath 'com.google.gms:google-services:3.1.0' 10 | } 11 | } 12 | 13 | allprojects { 14 | repositories { 15 | google() 16 | jcenter() 17 | } 18 | } 19 | 20 | rootProject.buildDir = '../build' 21 | subprojects { 22 | project.buildDir = "${rootProject.buildDir}/${project.name}" 23 | } 24 | subprojects { 25 | project.evaluationDependsOn(':app') 26 | } 27 | 28 | task clean(type: Delete) { 29 | delete rootProject.buildDir 30 | } 31 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip 7 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /cap_challenge.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /cap_challenge_android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "functions": { 3 | "predeploy": [ 4 | "npm --prefix \"$RESOURCE_DIR\" run lint", 5 | "npm --prefix \"$RESOURCE_DIR\" run build" 6 | ] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /fonts/PermanentMarker-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/fonts/PermanentMarker-Regular.ttf -------------------------------------------------------------------------------- /functions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "functions", 3 | "scripts": { 4 | "lint": "tslint --project tsconfig.json", 5 | "build": "tsc", 6 | "serve": "npm run build && firebase serve --only functions", 7 | "shell": "npm run build && firebase experimental:functions:shell", 8 | "start": "npm run shell", 9 | "deploy": "firebase deploy --only functions", 10 | "logs": "firebase functions:log" 11 | }, 12 | "main": "lib/index.js", 13 | "dependencies": { 14 | "firebase-admin": "~5.10.0", 15 | "firebase-functions": "^0.9.0" 16 | }, 17 | "devDependencies": { 18 | "tslint": "^5.8.0", 19 | "typescript": "^2.5.3" 20 | }, 21 | "private": true 22 | } 23 | -------------------------------------------------------------------------------- /functions/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as functions from 'firebase-functions'; 2 | import * as admin from "firebase-admin"; 3 | 4 | admin.initializeApp(functions.config().firebase); 5 | 6 | const types = { 7 | 'c': 'COCA_COLA', 8 | 's': 'SPRITE', 9 | 'f': 'FANTA', 10 | 'z': 'ZERO', 11 | 'l': 'LIGHT', 12 | }; 13 | 14 | const sizes = { 15 | '1': '_250', 16 | '2': '_300', 17 | '3': '_500', 18 | '4': '_1L', 19 | '5': '_2L' 20 | }; 21 | 22 | const pointSheet = { 23 | '1': 100, 24 | '2': 150, 25 | '3': 200, 26 | '4': 300, 27 | '5': 500, 28 | }; 29 | 30 | export const OnCreateFunction = functions.auth.user().onCreate((event) => { 31 | return admin.database().ref('users/' + event.data.uid).set({ 32 | username: event.data.displayName, 33 | email: event.data.email, 34 | points: 0, 35 | currentChallenges: { 36 | challengeId1: false, 37 | challengeId2: false, 38 | challengeId3: false, 39 | } 40 | }); 41 | }); 42 | 43 | export const AddCapCode = functions.https.onRequest((request, response) => { 44 | const uid = request.get("uid"); 45 | 46 | if (request.method === 'POST') { 47 | const typeChar = request.get("code").charAt(0).toLowerCase(); 48 | const sizeChar = request.get("code").charAt(1).toLowerCase(); 49 | 50 | const type = types[typeChar]; 51 | const size = sizes[sizeChar]; 52 | const name = type + size; 53 | 54 | if (type == null || size == null) { 55 | response.status(400).send("Invalid code"); 56 | return null; 57 | } 58 | 59 | const path = 'users/' + uid;//+ '/bottles/' + type + size; 60 | return Promise.all([ 61 | admin.database().ref(path + '/bottles/' + name).once('value', (snapshot) => { 62 | let quantity = 1; 63 | if (snapshot.exists()) { 64 | quantity += snapshot.val(); 65 | } 66 | return admin.database().ref(path + '/bottles/' + name) 67 | .set(quantity) 68 | .then(value => response.status(200).send(), 69 | reason => response.status(500).send()); 70 | }), 71 | admin.database().ref(path + '/points').once('value', (snapshot) => { 72 | let points = pointSheet[sizeChar]; 73 | if (snapshot.exists()) { 74 | points += snapshot.val(); 75 | } 76 | return admin.database().ref(path + '/points') 77 | .set(points) 78 | .then(value => response.status(200).send(), 79 | reason => response.status(500).send()); 80 | }), 81 | ]).then(value => response.status(200).send(), 82 | reason => response.status(500).send()); 83 | } else { 84 | response.status(404).send(); 85 | return null; 86 | } 87 | 88 | }); -------------------------------------------------------------------------------- /functions/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "es6" 5 | ], 6 | "module": "commonjs", 7 | "noImplicitReturns": true, 8 | "outDir": "lib", 9 | "sourceMap": true, 10 | "target": "es6" 11 | }, 12 | "compileOnSave": true, 13 | "include": [ 14 | "src" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /functions/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | // -- Strict errors -- 4 | // These lint rules are likely always a good idea. 5 | 6 | // Force function overloads to be declared together. This ensures readers understand APIs. 7 | "adjacent-overload-signatures": true, 8 | // Do not allow the subtle/obscure comma operator. 9 | "ban-comma-operator": true, 10 | // Do not allow internal modules or namespaces . These are deprecated in favor of ES6 modules. 11 | "no-namespace": true, 12 | // Do not allow parameters to be reassigned. To avoid bugs, developers should instead assign new values to new vars. 13 | "no-parameter-reassignment": true, 14 | // Force the use of ES6-style imports instead of /// imports. 15 | "no-reference": true, 16 | // Do not allow type assertions that do nothing. This is a big warning that the developer may not understand the 17 | // code currently being edited (they may be incorrectly handling a different type case that does not exist). 18 | "no-unnecessary-type-assertion": true, 19 | // Disallow nonsensical label usage. 20 | "label-position": true, 21 | // Disallows the (often typo) syntax if (var1 = var2). Replace with if (var2) { var1 = var2 }. 22 | "no-conditional-assignment": true, 23 | // Disallows constructors for primitive types (e.g. new Number('123'), though Number('123') is still allowed). 24 | "no-construct": true, 25 | // Do not allow super() to be called twice in a constructor. 26 | "no-duplicate-super": true, 27 | // Do not allow the same case to appear more than once in a switch block. 28 | "no-duplicate-switch-case": true, 29 | // Do not allow a variable to be declared more than once in the same block. Consider function parameters in this 30 | // rule. 31 | "no-duplicate-variable": [ 32 | true, 33 | "check-parameters" 34 | ], 35 | // Disallows a variable definition in an inner scope from shadowing a variable in an outer scope. Developers should 36 | // instead use a separate variable name. 37 | "no-shadowed-variable": true, 38 | // Empty blocks are almost never needed. Allow the one general exception: empty catch blocks. 39 | "no-empty": [ 40 | true, 41 | "allow-empty-catch" 42 | ], 43 | // Functions must either be handled directly (e.g. with a catch() handler) or returned to another function. 44 | // This is a major source of errors in Cloud Functions and the team strongly recommends leaving this rule on. 45 | "no-floating-promises": true, 46 | // Do not allow any imports for modules that are not in package.json. These will almost certainly fail when 47 | // deployed. 48 | "no-implicit-dependencies": true, 49 | // The 'this' keyword can only be used inside of classes. 50 | "no-invalid-this": true, 51 | // Do not allow strings to be thrown because they will not include stack traces. Throw Errors instead. 52 | "no-string-throw": true, 53 | // Disallow control flow statements, such as return, continue, break, and throw in finally blocks. 54 | "no-unsafe-finally": true, 55 | // Do not allow variables to be used before they are declared. 56 | "no-use-before-declare": true, 57 | // Expressions must always return a value. Avoids common errors like const myValue = functionReturningVoid(); 58 | "no-void-expression": [ 59 | true, 60 | "ignore-arrow-function-shorthand" 61 | ], 62 | // Disallow duplicate imports in the same file. 63 | "no-duplicate-imports": true, 64 | // -- Strong Warnings -- 65 | // These rules should almost never be needed, but may be included due to legacy code. 66 | // They are left as a warning to avoid frustration with blocked deploys when the developer 67 | // understand the warning and wants to deploy anyway. 68 | 69 | // Warn when an empty interface is defined. These are generally not useful. 70 | "no-empty-interface": { 71 | "severity": "warning" 72 | }, 73 | // Warn when an import will have side effects. 74 | "no-import-side-effect": { 75 | "severity": "warning" 76 | }, 77 | // Warn when variables are defined with var. Var has subtle meaning that can lead to bugs. Strongly prefer const for 78 | // most values and let for values that will change. 79 | "no-var-keyword": { 80 | "severity": "warning" 81 | }, 82 | // Prefer === and !== over == and !=. The latter operators support overloads that are often accidental. 83 | "triple-equals": { 84 | "severity": "warning" 85 | }, 86 | // Warn when using deprecated APIs. 87 | "deprecation": { 88 | "severity": "warning" 89 | }, 90 | // -- Light Warnigns -- 91 | // These rules are intended to help developers use better style. Simpler code has fewer bugs. These would be "info" 92 | // if TSLint supported such a level. 93 | 94 | // prefer for( ... of ... ) to an index loop when the index is only used to fetch an object from an array. 95 | // (Even better: check out utils like .map if transforming an array!) 96 | "prefer-for-of": { 97 | "severity": "warning" 98 | }, 99 | // Warns if function overloads could be unified into a single function with optional or rest parameters. 100 | "unified-signatures": { 101 | "severity": "warning" 102 | }, 103 | // Warns if code has an import or variable that is unused. 104 | "no-unused-variable": { 105 | "severity": "warning" 106 | }, 107 | // Prefer const for values that will not change. This better documents code. 108 | "prefer-const": { 109 | "severity": "warning" 110 | }, 111 | // Multi-line object liiterals and function calls should have a trailing comma. This helps avoid merge conflicts. 112 | "trailing-comma": { 113 | "severity": "warning" 114 | } 115 | }, 116 | "defaultSeverity": "error" 117 | } 118 | -------------------------------------------------------------------------------- /images/bottle_empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/images/bottle_empty.png -------------------------------------------------------------------------------- /images/bottle_filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/images/bottle_filled.png -------------------------------------------------------------------------------- /images/bubbles.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/images/bubbles.jpg -------------------------------------------------------------------------------- /images/bubbles_long.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/images/bubbles_long.jpg -------------------------------------------------------------------------------- /images/cap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/images/cap.png -------------------------------------------------------------------------------- /images/cap_reverse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/images/cap_reverse.png -------------------------------------------------------------------------------- /images/challenge_bottle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/images/challenge_bottle.png -------------------------------------------------------------------------------- /images/coca_cola/bottle_big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/images/coca_cola/bottle_big.png -------------------------------------------------------------------------------- /images/coca_cola/bottle_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/images/coca_cola/bottle_small.png -------------------------------------------------------------------------------- /images/coca_cola/can.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/images/coca_cola/can.png -------------------------------------------------------------------------------- /images/coca_cola_baner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/images/coca_cola_baner.jpg -------------------------------------------------------------------------------- /images/coca_family.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/images/coca_family.png -------------------------------------------------------------------------------- /images/coke_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/images/coke_light.png -------------------------------------------------------------------------------- /images/coke_zero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/images/coke_zero.png -------------------------------------------------------------------------------- /images/fanta/bottle_big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/images/fanta/bottle_big.png -------------------------------------------------------------------------------- /images/fanta/bottle_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/images/fanta/bottle_small.png -------------------------------------------------------------------------------- /images/fanta/can.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/images/fanta/can.png -------------------------------------------------------------------------------- /images/fanta_baner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/images/fanta_baner.jpg -------------------------------------------------------------------------------- /images/qr_code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/images/qr_code.png -------------------------------------------------------------------------------- /images/sign-in-facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/images/sign-in-facebook.png -------------------------------------------------------------------------------- /images/sign-in-google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/images/sign-in-google.png -------------------------------------------------------------------------------- /images/sprite/bottle_big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/images/sprite/bottle_big.png -------------------------------------------------------------------------------- /images/sprite/bottle_medium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/images/sprite/bottle_medium.png -------------------------------------------------------------------------------- /images/sprite/bottle_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/images/sprite/bottle_small.png -------------------------------------------------------------------------------- /images/sprite/can.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/images/sprite/can.png -------------------------------------------------------------------------------- /images/sprite_baner.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/images/sprite_baner.jpeg -------------------------------------------------------------------------------- /images/ticket-vertical.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/images/ticket-vertical.jpg -------------------------------------------------------------------------------- /images/ticket.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/images/ticket.jpg -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | *.pbxuser 16 | *.mode1v3 17 | *.mode2v3 18 | *.perspectivev3 19 | 20 | !default.pbxuser 21 | !default.mode1v3 22 | !default.mode2v3 23 | !default.perspectivev3 24 | 25 | xcuserdata 26 | 27 | *.moved-aside 28 | 29 | *.pyc 30 | *sync/ 31 | Icon? 32 | .tags* 33 | 34 | /Flutter/app.flx 35 | /Flutter/app.zip 36 | /Flutter/flutter_assets/ 37 | /Flutter/App.framework 38 | /Flutter/Flutter.framework 39 | /Flutter/Generated.xcconfig 40 | /ServiceDefinitions.json 41 | 42 | Pods/ 43 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | UIRequiredDeviceCapabilities 24 | 25 | arm64 26 | 27 | MinimumOSVersion 28 | 8.0 29 | 30 | 31 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */ = {isa = PBXBuildFile; fileRef = 2D5378251FAA1A9400D5DBA9 /* flutter_assets */; }; 12 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 13 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 14 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 15 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 16 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 17 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 18 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; 19 | 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB31CF90195004384FC /* Generated.xcconfig */; }; 20 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 21 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 22 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 23 | /* End PBXBuildFile section */ 24 | 25 | /* Begin PBXCopyFilesBuildPhase section */ 26 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 27 | isa = PBXCopyFilesBuildPhase; 28 | buildActionMask = 2147483647; 29 | dstPath = ""; 30 | dstSubfolderSpec = 10; 31 | files = ( 32 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, 33 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, 34 | ); 35 | name = "Embed Frameworks"; 36 | runOnlyForDeploymentPostprocessing = 0; 37 | }; 38 | /* End PBXCopyFilesBuildPhase section */ 39 | 40 | /* Begin PBXFileReference section */ 41 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 42 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 43 | 2D5378251FAA1A9400D5DBA9 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = Flutter/flutter_assets; sourceTree = SOURCE_ROOT; }; 44 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 45 | 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 46 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 47 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 48 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 49 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 50 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 51 | 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; 52 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 53 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 54 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 55 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 56 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 57 | /* End PBXFileReference section */ 58 | 59 | /* Begin PBXFrameworksBuildPhase section */ 60 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 61 | isa = PBXFrameworksBuildPhase; 62 | buildActionMask = 2147483647; 63 | files = ( 64 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 65 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 66 | ); 67 | runOnlyForDeploymentPostprocessing = 0; 68 | }; 69 | /* End PBXFrameworksBuildPhase section */ 70 | 71 | /* Begin PBXGroup section */ 72 | 9740EEB11CF90186004384FC /* Flutter */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | 2D5378251FAA1A9400D5DBA9 /* flutter_assets */, 76 | 3B80C3931E831B6300D905FE /* App.framework */, 77 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 78 | 9740EEBA1CF902C7004384FC /* Flutter.framework */, 79 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 80 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 81 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 82 | ); 83 | name = Flutter; 84 | sourceTree = ""; 85 | }; 86 | 97C146E51CF9000F007C117D = { 87 | isa = PBXGroup; 88 | children = ( 89 | 9740EEB11CF90186004384FC /* Flutter */, 90 | 97C146F01CF9000F007C117D /* Runner */, 91 | 97C146EF1CF9000F007C117D /* Products */, 92 | ); 93 | sourceTree = ""; 94 | }; 95 | 97C146EF1CF9000F007C117D /* Products */ = { 96 | isa = PBXGroup; 97 | children = ( 98 | 97C146EE1CF9000F007C117D /* Runner.app */, 99 | ); 100 | name = Products; 101 | sourceTree = ""; 102 | }; 103 | 97C146F01CF9000F007C117D /* Runner */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 107 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 108 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 109 | 97C147021CF9000F007C117D /* Info.plist */, 110 | 97C146F11CF9000F007C117D /* Supporting Files */, 111 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 112 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 113 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 114 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 115 | ); 116 | path = Runner; 117 | sourceTree = ""; 118 | }; 119 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | ); 123 | name = "Supporting Files"; 124 | sourceTree = ""; 125 | }; 126 | /* End PBXGroup section */ 127 | 128 | /* Begin PBXNativeTarget section */ 129 | 97C146ED1CF9000F007C117D /* Runner */ = { 130 | isa = PBXNativeTarget; 131 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 132 | buildPhases = ( 133 | 9740EEB61CF901F6004384FC /* Run Script */, 134 | 97C146EA1CF9000F007C117D /* Sources */, 135 | 97C146EB1CF9000F007C117D /* Frameworks */, 136 | 97C146EC1CF9000F007C117D /* Resources */, 137 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 138 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 139 | ); 140 | buildRules = ( 141 | ); 142 | dependencies = ( 143 | ); 144 | name = Runner; 145 | productName = Runner; 146 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 147 | productType = "com.apple.product-type.application"; 148 | }; 149 | /* End PBXNativeTarget section */ 150 | 151 | /* Begin PBXProject section */ 152 | 97C146E61CF9000F007C117D /* Project object */ = { 153 | isa = PBXProject; 154 | attributes = { 155 | LastUpgradeCheck = 0910; 156 | ORGANIZATIONNAME = "The Chromium Authors"; 157 | TargetAttributes = { 158 | 97C146ED1CF9000F007C117D = { 159 | CreatedOnToolsVersion = 7.3.1; 160 | LastSwiftMigration = 0910; 161 | }; 162 | }; 163 | }; 164 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 165 | compatibilityVersion = "Xcode 3.2"; 166 | developmentRegion = English; 167 | hasScannedForEncodings = 0; 168 | knownRegions = ( 169 | en, 170 | Base, 171 | ); 172 | mainGroup = 97C146E51CF9000F007C117D; 173 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 174 | projectDirPath = ""; 175 | projectRoot = ""; 176 | targets = ( 177 | 97C146ED1CF9000F007C117D /* Runner */, 178 | ); 179 | }; 180 | /* End PBXProject section */ 181 | 182 | /* Begin PBXResourcesBuildPhase section */ 183 | 97C146EC1CF9000F007C117D /* Resources */ = { 184 | isa = PBXResourcesBuildPhase; 185 | buildActionMask = 2147483647; 186 | files = ( 187 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 188 | 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */, 189 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 190 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, 191 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 192 | 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */, 193 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 194 | ); 195 | runOnlyForDeploymentPostprocessing = 0; 196 | }; 197 | /* End PBXResourcesBuildPhase section */ 198 | 199 | /* Begin PBXShellScriptBuildPhase section */ 200 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 201 | isa = PBXShellScriptBuildPhase; 202 | buildActionMask = 2147483647; 203 | files = ( 204 | ); 205 | inputPaths = ( 206 | ); 207 | name = "Thin Binary"; 208 | outputPaths = ( 209 | ); 210 | runOnlyForDeploymentPostprocessing = 0; 211 | shellPath = /bin/sh; 212 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; 213 | }; 214 | 9740EEB61CF901F6004384FC /* Run Script */ = { 215 | isa = PBXShellScriptBuildPhase; 216 | buildActionMask = 2147483647; 217 | files = ( 218 | ); 219 | inputPaths = ( 220 | ); 221 | name = "Run Script"; 222 | outputPaths = ( 223 | ); 224 | runOnlyForDeploymentPostprocessing = 0; 225 | shellPath = /bin/sh; 226 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 227 | }; 228 | /* End PBXShellScriptBuildPhase section */ 229 | 230 | /* Begin PBXSourcesBuildPhase section */ 231 | 97C146EA1CF9000F007C117D /* Sources */ = { 232 | isa = PBXSourcesBuildPhase; 233 | buildActionMask = 2147483647; 234 | files = ( 235 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 236 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 237 | ); 238 | runOnlyForDeploymentPostprocessing = 0; 239 | }; 240 | /* End PBXSourcesBuildPhase section */ 241 | 242 | /* Begin PBXVariantGroup section */ 243 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 244 | isa = PBXVariantGroup; 245 | children = ( 246 | 97C146FB1CF9000F007C117D /* Base */, 247 | ); 248 | name = Main.storyboard; 249 | sourceTree = ""; 250 | }; 251 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 252 | isa = PBXVariantGroup; 253 | children = ( 254 | 97C147001CF9000F007C117D /* Base */, 255 | ); 256 | name = LaunchScreen.storyboard; 257 | sourceTree = ""; 258 | }; 259 | /* End PBXVariantGroup section */ 260 | 261 | /* Begin XCBuildConfiguration section */ 262 | 97C147031CF9000F007C117D /* Debug */ = { 263 | isa = XCBuildConfiguration; 264 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 265 | buildSettings = { 266 | ALWAYS_SEARCH_USER_PATHS = NO; 267 | CLANG_ANALYZER_NONNULL = YES; 268 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 269 | CLANG_CXX_LIBRARY = "libc++"; 270 | CLANG_ENABLE_MODULES = YES; 271 | CLANG_ENABLE_OBJC_ARC = YES; 272 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 273 | CLANG_WARN_BOOL_CONVERSION = YES; 274 | CLANG_WARN_COMMA = YES; 275 | CLANG_WARN_CONSTANT_CONVERSION = YES; 276 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 277 | CLANG_WARN_EMPTY_BODY = YES; 278 | CLANG_WARN_ENUM_CONVERSION = YES; 279 | CLANG_WARN_INFINITE_RECURSION = YES; 280 | CLANG_WARN_INT_CONVERSION = YES; 281 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 282 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 283 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 284 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 285 | CLANG_WARN_STRICT_PROTOTYPES = YES; 286 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 287 | CLANG_WARN_UNREACHABLE_CODE = YES; 288 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 289 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 290 | COPY_PHASE_STRIP = NO; 291 | DEBUG_INFORMATION_FORMAT = dwarf; 292 | ENABLE_STRICT_OBJC_MSGSEND = YES; 293 | ENABLE_TESTABILITY = YES; 294 | GCC_C_LANGUAGE_STANDARD = gnu99; 295 | GCC_DYNAMIC_NO_PIC = NO; 296 | GCC_NO_COMMON_BLOCKS = YES; 297 | GCC_OPTIMIZATION_LEVEL = 0; 298 | GCC_PREPROCESSOR_DEFINITIONS = ( 299 | "DEBUG=1", 300 | "$(inherited)", 301 | ); 302 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 303 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 304 | GCC_WARN_UNDECLARED_SELECTOR = YES; 305 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 306 | GCC_WARN_UNUSED_FUNCTION = YES; 307 | GCC_WARN_UNUSED_VARIABLE = YES; 308 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 309 | MTL_ENABLE_DEBUG_INFO = YES; 310 | ONLY_ACTIVE_ARCH = YES; 311 | SDKROOT = iphoneos; 312 | TARGETED_DEVICE_FAMILY = "1,2"; 313 | }; 314 | name = Debug; 315 | }; 316 | 97C147041CF9000F007C117D /* Release */ = { 317 | isa = XCBuildConfiguration; 318 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 319 | buildSettings = { 320 | ALWAYS_SEARCH_USER_PATHS = NO; 321 | CLANG_ANALYZER_NONNULL = YES; 322 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 323 | CLANG_CXX_LIBRARY = "libc++"; 324 | CLANG_ENABLE_MODULES = YES; 325 | CLANG_ENABLE_OBJC_ARC = YES; 326 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 327 | CLANG_WARN_BOOL_CONVERSION = YES; 328 | CLANG_WARN_COMMA = YES; 329 | CLANG_WARN_CONSTANT_CONVERSION = YES; 330 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 331 | CLANG_WARN_EMPTY_BODY = YES; 332 | CLANG_WARN_ENUM_CONVERSION = YES; 333 | CLANG_WARN_INFINITE_RECURSION = YES; 334 | CLANG_WARN_INT_CONVERSION = YES; 335 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 336 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 337 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 338 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 339 | CLANG_WARN_STRICT_PROTOTYPES = YES; 340 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 341 | CLANG_WARN_UNREACHABLE_CODE = YES; 342 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 343 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 344 | COPY_PHASE_STRIP = NO; 345 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 346 | ENABLE_NS_ASSERTIONS = NO; 347 | ENABLE_STRICT_OBJC_MSGSEND = YES; 348 | GCC_C_LANGUAGE_STANDARD = gnu99; 349 | GCC_NO_COMMON_BLOCKS = YES; 350 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 351 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 352 | GCC_WARN_UNDECLARED_SELECTOR = YES; 353 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 354 | GCC_WARN_UNUSED_FUNCTION = YES; 355 | GCC_WARN_UNUSED_VARIABLE = YES; 356 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 357 | MTL_ENABLE_DEBUG_INFO = NO; 358 | SDKROOT = iphoneos; 359 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 360 | TARGETED_DEVICE_FAMILY = "1,2"; 361 | VALIDATE_PRODUCT = YES; 362 | }; 363 | name = Release; 364 | }; 365 | 97C147061CF9000F007C117D /* Debug */ = { 366 | isa = XCBuildConfiguration; 367 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 368 | buildSettings = { 369 | ARCHS = arm64; 370 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 371 | CLANG_ENABLE_MODULES = YES; 372 | ENABLE_BITCODE = NO; 373 | FRAMEWORK_SEARCH_PATHS = ( 374 | "$(inherited)", 375 | "$(PROJECT_DIR)/Flutter", 376 | ); 377 | INFOPLIST_FILE = Runner/Info.plist; 378 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 379 | LIBRARY_SEARCH_PATHS = ( 380 | "$(inherited)", 381 | "$(PROJECT_DIR)/Flutter", 382 | ); 383 | PRODUCT_BUNDLE_IDENTIFIER = com.mszalek.capChallenge; 384 | PRODUCT_NAME = "$(TARGET_NAME)"; 385 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 386 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 387 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 388 | SWIFT_VERSION = 4.0; 389 | }; 390 | name = Debug; 391 | }; 392 | 97C147071CF9000F007C117D /* Release */ = { 393 | isa = XCBuildConfiguration; 394 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 395 | buildSettings = { 396 | ARCHS = arm64; 397 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 398 | CLANG_ENABLE_MODULES = YES; 399 | ENABLE_BITCODE = NO; 400 | FRAMEWORK_SEARCH_PATHS = ( 401 | "$(inherited)", 402 | "$(PROJECT_DIR)/Flutter", 403 | ); 404 | INFOPLIST_FILE = Runner/Info.plist; 405 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 406 | LIBRARY_SEARCH_PATHS = ( 407 | "$(inherited)", 408 | "$(PROJECT_DIR)/Flutter", 409 | ); 410 | PRODUCT_BUNDLE_IDENTIFIER = com.mszalek.capChallenge; 411 | PRODUCT_NAME = "$(TARGET_NAME)"; 412 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 413 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 414 | SWIFT_VERSION = 4.0; 415 | }; 416 | name = Release; 417 | }; 418 | /* End XCBuildConfiguration section */ 419 | 420 | /* Begin XCConfigurationList section */ 421 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 422 | isa = XCConfigurationList; 423 | buildConfigurations = ( 424 | 97C147031CF9000F007C117D /* Debug */, 425 | 97C147041CF9000F007C117D /* Release */, 426 | ); 427 | defaultConfigurationIsVisible = 0; 428 | defaultConfigurationName = Release; 429 | }; 430 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 431 | isa = XCConfigurationList; 432 | buildConfigurations = ( 433 | 97C147061CF9000F007C117D /* Debug */, 434 | 97C147071CF9000F007C117D /* Release */, 435 | ); 436 | defaultConfigurationIsVisible = 0; 437 | defaultConfigurationName = Release; 438 | }; 439 | /* End XCConfigurationList section */ 440 | }; 441 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 442 | } 443 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "size": "20x20", 5 | "idiom": "iphone", 6 | "filename": "Icon-App-20x20@2x.png", 7 | "scale": "2x" 8 | }, 9 | { 10 | "size": "20x20", 11 | "idiom": "iphone", 12 | "filename": "Icon-App-20x20@3x.png", 13 | "scale": "3x" 14 | }, 15 | { 16 | "size": "29x29", 17 | "idiom": "iphone", 18 | "filename": "Icon-App-29x29@1x.png", 19 | "scale": "1x" 20 | }, 21 | { 22 | "size": "29x29", 23 | "idiom": "iphone", 24 | "filename": "Icon-App-29x29@2x.png", 25 | "scale": "2x" 26 | }, 27 | { 28 | "size": "29x29", 29 | "idiom": "iphone", 30 | "filename": "Icon-App-29x29@3x.png", 31 | "scale": "3x" 32 | }, 33 | { 34 | "size": "40x40", 35 | "idiom": "iphone", 36 | "filename": "Icon-App-40x40@2x.png", 37 | "scale": "2x" 38 | }, 39 | { 40 | "size": "40x40", 41 | "idiom": "iphone", 42 | "filename": "Icon-App-40x40@3x.png", 43 | "scale": "3x" 44 | }, 45 | { 46 | "size": "60x60", 47 | "idiom": "iphone", 48 | "filename": "Icon-App-60x60@2x.png", 49 | "scale": "2x" 50 | }, 51 | { 52 | "size": "60x60", 53 | "idiom": "iphone", 54 | "filename": "Icon-App-60x60@3x.png", 55 | "scale": "3x" 56 | }, 57 | { 58 | "size": "20x20", 59 | "idiom": "ipad", 60 | "filename": "Icon-App-20x20@1x.png", 61 | "scale": "1x" 62 | }, 63 | { 64 | "size": "20x20", 65 | "idiom": "ipad", 66 | "filename": "Icon-App-20x20@2x.png", 67 | "scale": "2x" 68 | }, 69 | { 70 | "size": "29x29", 71 | "idiom": "ipad", 72 | "filename": "Icon-App-29x29@1x.png", 73 | "scale": "1x" 74 | }, 75 | { 76 | "size": "29x29", 77 | "idiom": "ipad", 78 | "filename": "Icon-App-29x29@2x.png", 79 | "scale": "2x" 80 | }, 81 | { 82 | "size": "40x40", 83 | "idiom": "ipad", 84 | "filename": "Icon-App-40x40@1x.png", 85 | "scale": "1x" 86 | }, 87 | { 88 | "size": "40x40", 89 | "idiom": "ipad", 90 | "filename": "Icon-App-40x40@2x.png", 91 | "scale": "2x" 92 | }, 93 | { 94 | "size": "76x76", 95 | "idiom": "ipad", 96 | "filename": "Icon-App-76x76@1x.png", 97 | "scale": "1x" 98 | }, 99 | { 100 | "size": "76x76", 101 | "idiom": "ipad", 102 | "filename": "Icon-App-76x76@2x.png", 103 | "scale": "2x" 104 | }, 105 | { 106 | "size": "83.5x83.5", 107 | "idiom": "ipad", 108 | "filename": "Icon-App-83.5x83.5@2x.png", 109 | "scale": "2x" 110 | }, 111 | { 112 | "size": "1024x1024", 113 | "idiom": "ios-marketing", 114 | "filename": "Icon-App-1024x1024@1x.png", 115 | "scale": "1x" 116 | } 117 | ], 118 | "info": { 119 | "version": 1, 120 | "author": "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "idiom": "universal", 5 | "filename": "LaunchImage.png", 6 | "scale": "1x" 7 | }, 8 | { 9 | "idiom": "universal", 10 | "filename": "LaunchImage@2x.png", 11 | "scale": "2x" 12 | }, 13 | { 14 | "idiom": "universal", 15 | "filename": "LaunchImage@3x.png", 16 | "scale": "3x" 17 | } 18 | ], 19 | "info": { 20 | "version": 1, 21 | "author": "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | cap_challenge 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | arm64 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | UIViewControllerBasedStatusBarAppearance 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /lib/generated/i18n.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'dart:async'; 3 | 4 | import 'package:flutter/foundation.dart'; 5 | import 'package:flutter/material.dart'; 6 | // ignore_for_file: non_constant_identifier_names 7 | // ignore_for_file: camel_case_types 8 | // ignore_for_file: prefer_single_quotes 9 | 10 | //This file is automatically generated. DO NOT EDIT, all your changes would be lost. 11 | 12 | class S implements WidgetsLocalizations { 13 | const S(); 14 | 15 | static const GeneratedLocalizationsDelegate delegate = 16 | const GeneratedLocalizationsDelegate(); 17 | 18 | static S of(BuildContext context) => 19 | Localizations.of(context, WidgetsLocalizations); 20 | 21 | @override 22 | TextDirection get textDirection => TextDirection.ltr; 23 | 24 | String get addCodeTooltip => "Add code from under the cap"; 25 | 26 | String get addFirstChallenge => 27 | "Complete you first challenge,\nto earn a ticket for free Coca-Cola can!"; 28 | String get addFirstCode => "Press \"+\" button,\nto add code from the cap!"; 29 | String get bottle1l => "bottle 1 l"; 30 | String get bottle250ml => "bottle 250 ml"; 31 | String get bottle2l => "bottle 2 l"; 32 | String get bottle500ml => "bottle 500 ml"; 33 | String get bubbleWinMessage => "Congratulations!\nYou've earned 100 pts!"; 34 | String get can330 => "can 330 ml"; 35 | String get caps => "Caps"; 36 | String get challenges => "Challenges"; 37 | String get close => "Close"; 38 | String get codeAddedMsg => "Bottle added to your collection"; 39 | String get codeUnderCap => "Code under the cap"; 40 | String get collection => "Collection"; 41 | String get completed => "Completed!"; 42 | String get dailyChallenge => "Bubbles"; 43 | String get invalidCode => "Invalid code"; 44 | 45 | String get invalidCodeMsg => 46 | "Check if you typed code from under the cap correctly."; 47 | String get loginWithFacebook => "Log in with Facebook"; 48 | String get loginWithGoogle => "Log in with Google"; 49 | String get logout => "Logout"; 50 | String get pts => "pts"; 51 | String get ranking => "Ranking"; 52 | String get requiredProducts => "Required products"; 53 | String get reward => "Reward:"; 54 | String get sendCode => "Send code"; 55 | String get shakeForBubble => "Shake your phone to add a bubble!"; 56 | String get showMore => "Show more"; 57 | 58 | String get showTicketMsg => 59 | "Show this ticket to salesman\nin order to get a Coca-Cola can!"; 60 | String get ticketPlaceholder => "What are we looking here? :)"; 61 | String get tickets => "Tickets"; 62 | String get unknown => "Unknown"; 63 | String get userProfile => "User profile"; 64 | String get yourTicket => "Your ticket"; 65 | 66 | String bubblesExplanation(String counter, String maxCounter) => 67 | "Add last bubble to earn extra 100 points!\nBottle contains $counter/$maxCounter bubbles!"; 68 | String missingBottleMsg(String bottles) => "You only need $bottles more!"; 69 | String nextBubbleIn(String time) => "Next bubble available in $time s!"; 70 | 71 | String placeInRanking(String rankingPlace) => 72 | "Place in ranking: $rankingPlace"; 73 | String totalPoints(String points) => "Total points: $points"; 74 | String totalTickets(String tickets) => "Total tickets: $tickets"; 75 | } 76 | 77 | class en extends S { 78 | const en(); 79 | } 80 | 81 | class pl extends S { 82 | const pl(); 83 | 84 | @override 85 | TextDirection get textDirection => TextDirection.ltr; 86 | 87 | @override 88 | String get yourTicket => "Twój bilet"; 89 | @override 90 | String get codeUnderCap => "Kod spod nakrętki"; 91 | @override 92 | String get tickets => "Bilety"; 93 | @override 94 | String get loginWithFacebook => "Zaloguj się z Facebook"; 95 | @override 96 | String get challenges => "Wyzwania"; 97 | @override 98 | String get sendCode => "Wyślij kod"; 99 | @override 100 | String get loginWithGoogle => "Zaloguj się z Google"; 101 | @override 102 | String get userProfile => "Profil użytkownika"; 103 | @override 104 | String get bottle2l => "butelka 2 l"; 105 | @override 106 | String get caps => "Kapsle"; 107 | @override 108 | String get addFirstChallenge => 109 | "Wykonaj swoje pierwsze wyzwanie,\naby dostać bilet na darmową Colę!"; 110 | @override 111 | String get unknown => "Nieznane"; 112 | @override 113 | String get addCodeTooltip => "Dodaj kod spod nakrętki"; 114 | @override 115 | String get showTicketMsg => 116 | "Pokaż ten bilet sprzedawcy\nw celu odbioru puszki Coca-Cola!"; 117 | @override 118 | String get logout => "Wyloguj"; 119 | @override 120 | String get shakeForBubble => "Potrząśnij telefonem by dodać bąbelek!"; 121 | @override 122 | String get ticketPlaceholder => "A czego tutaj szukamy? :)"; 123 | @override 124 | String get close => "Zamknij"; 125 | @override 126 | String get requiredProducts => "Wymagane produkty"; 127 | @override 128 | String get reward => "Nagroda:"; 129 | @override 130 | String get codeAddedMsg => "Butelka została dodana do Twojej kolekcji"; 131 | @override 132 | String get bottle250ml => "butelka 250 ml"; 133 | 134 | @override 135 | String get dailyChallenge => "Wyzwanie dnia"; 136 | @override 137 | String get collection => "Kolekcja"; 138 | @override 139 | String get completed => "Wykonano!"; 140 | @override 141 | String get invalidCode => "Nieprawidłowy kod"; 142 | @override 143 | String get pts => "pkt"; 144 | @override 145 | String get bottle1l => "butelka 1 l"; 146 | @override 147 | String get invalidCodeMsg => 148 | "Sprawdź czy poprawnie wpisałeś kod spod nakrętki."; 149 | 150 | @override 151 | String get addFirstCode => 152 | "Naciśnij przycisk \"+\",\naby dodać kod spod nakrętki!"; 153 | @override 154 | String get bubbleWinMessage => "Gratulacje!\nOtrzymałeś 100 pkt!"; 155 | @override 156 | String get showMore => "Pokaż więcej"; 157 | @override 158 | String get can330 => "puszka 330 ml"; 159 | 160 | @override 161 | String get ranking => "Ranking"; 162 | @override 163 | String bubblesExplanation(String counter, String maxCounter) => 164 | "Dodaj ostatni bąbelek by otrzymać dodatkowe 100 punktów!\nButelka zawiera $counter/$maxCounter bąbelków!"; 165 | @override 166 | String placeInRanking(String rankingPlace) => 167 | "Miejsce w rankingu: $rankingPlace"; 168 | @override 169 | String missingBottleMsg(String bottles) => "Brakuje Ci tylko $bottles!"; 170 | @override 171 | String totalPoints(String points) => "Liczba punktów: $points"; 172 | @override 173 | String totalTickets(String tickets) => "Liczba biletów: $tickets"; 174 | @override 175 | String nextBubbleIn(String time) => "Następny bąbelek dostępny za $time s!"; 176 | } 177 | 178 | 179 | class GeneratedLocalizationsDelegate 180 | extends LocalizationsDelegate { 181 | const GeneratedLocalizationsDelegate(); 182 | 183 | List get supportedLocales { 184 | return const [ 185 | 186 | const Locale("en", ""), 187 | const Locale("pl", ""), 188 | 189 | ]; 190 | } 191 | 192 | LocaleResolutionCallback resolution({Locale fallback}) { 193 | return (Locale locale, Iterable supported) { 194 | final Locale languageLocale = new Locale(locale.languageCode, ""); 195 | if (supported.contains(locale)) 196 | return locale; 197 | else if (supported.contains(languageLocale)) 198 | return languageLocale; 199 | else { 200 | final Locale fallbackLocale = fallback ?? supported.first; 201 | return fallbackLocale; 202 | } 203 | }; 204 | } 205 | 206 | @override 207 | Future load(Locale locale) { 208 | final String lang = getLang(locale); 209 | switch (lang) { 210 | case "en": 211 | return new SynchronousFuture(const en()); 212 | case "pl": 213 | return new SynchronousFuture(const pl()); 214 | 215 | default: 216 | return new SynchronousFuture(const S()); 217 | } 218 | } 219 | 220 | @override 221 | bool isSupported(Locale locale) => supportedLocales.contains(locale); 222 | 223 | @override 224 | bool shouldReload(GeneratedLocalizationsDelegate old) => false; 225 | } 226 | 227 | String getLang(Locale l) => l.countryCode != null && l.countryCode.isEmpty 228 | ? l.languageCode 229 | : l.toString(); 230 | -------------------------------------------------------------------------------- /lib/logic/actions.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:cap_challenge/models/bottle.dart'; 4 | import 'package:cap_challenge/models/challenge.dart'; 5 | import 'package:cap_challenge/models/user.dart'; 6 | import 'package:firebase_auth/firebase_auth.dart'; 7 | 8 | class InitAction {} 9 | 10 | class AddCodeAction { 11 | final String code; 12 | final Completer completer; 13 | 14 | AddCodeAction(this.code, this.completer); 15 | } 16 | 17 | class CompleteChallengeAction { 18 | final Challenge challenge; 19 | 20 | CompleteChallengeAction(this.challenge); 21 | } 22 | 23 | class LoginWithGoogleAction {} 24 | 25 | class UserProvidedAction { 26 | final FirebaseUser user; 27 | 28 | UserProvidedAction(this.user); 29 | } 30 | 31 | class LogoutAction {} 32 | 33 | class CounterUpdatedAction { 34 | final int counter; 35 | 36 | CounterUpdatedAction(this.counter); 37 | } 38 | 39 | class PointsUpdatedAction { 40 | final int points; 41 | 42 | PointsUpdatedAction(this.points); 43 | } 44 | 45 | class ChallengesUpdatedAction { 46 | final List challenges; 47 | 48 | ChallengesUpdatedAction(this.challenges); 49 | } 50 | 51 | class ChallengeAddedAction { 52 | final Challenge challenge; 53 | 54 | ChallengeAddedAction(this.challenge); 55 | } 56 | 57 | class ChallengeUpdatedAction { 58 | final String key; 59 | final bool isCompleted; 60 | 61 | ChallengeUpdatedAction(this.key, this.isCompleted); 62 | } 63 | 64 | class CapsChangedAction { 65 | final Map collection; 66 | 67 | CapsChangedAction(this.collection); 68 | } 69 | 70 | class TicketsUpdatedAction { 71 | final int tickets; 72 | 73 | TicketsUpdatedAction(this.tickets); 74 | } 75 | 76 | class RankingUpdatedAction { 77 | final List ranking; 78 | 79 | RankingUpdatedAction(this.ranking); 80 | } 81 | -------------------------------------------------------------------------------- /lib/logic/app_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:cap_challenge/models/bottle.dart'; 2 | import 'package:cap_challenge/models/challenge.dart'; 3 | import 'package:cap_challenge/models/user.dart'; 4 | import 'package:firebase_auth/firebase_auth.dart'; 5 | 6 | class AppState { 7 | final FirebaseUser user; 8 | final int points; 9 | final int tickets; 10 | final int counter; 11 | final Map collection; 12 | final List challenges; 13 | final List usersRanking; 14 | 15 | AppState( 16 | {this.user, 17 | this.points, 18 | this.tickets, 19 | this.counter, 20 | this.collection, 21 | this.challenges, 22 | this.usersRanking}); 23 | 24 | AppState copyWith( 25 | {FirebaseUser user, 26 | int points, 27 | int tickets, 28 | int counter, 29 | Map collection, 30 | List challenges, 31 | List usersRanking}) { 32 | return new AppState( 33 | user: user ?? this.user, 34 | points: points ?? this.points, 35 | tickets: tickets ?? this.tickets, 36 | counter: counter ?? this.counter, 37 | collection: collection ?? this.collection, 38 | challenges: challenges ?? this.challenges, 39 | usersRanking: usersRanking ?? this.usersRanking); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/logic/auth_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:firebase_auth/firebase_auth.dart'; 4 | import 'package:flutter_facebook_login/flutter_facebook_login.dart'; 5 | import 'package:google_sign_in/google_sign_in.dart'; 6 | 7 | class AuthService { 8 | static AuthService instance = new AuthService._(); 9 | 10 | AuthService._(); 11 | 12 | final FirebaseAuth _firebaseAuth = FirebaseAuth.instance; 13 | final GoogleSignIn _googleSignIn = new GoogleSignIn(); 14 | final FacebookLogin _facebookLogin = new FacebookLogin(); 15 | 16 | FirebaseUser currentUser; 17 | 18 | Future loadCurrentUser() async { 19 | currentUser = await _firebaseAuth.currentUser(); 20 | return currentUser; 21 | } 22 | 23 | Future loginWithGoogle() async { 24 | GoogleSignInAccount googleUser = _googleSignIn.currentUser; 25 | if (googleUser == null) { 26 | googleUser = await _googleSignIn.signInSilently(); 27 | } 28 | if (googleUser == null) { 29 | googleUser = await _googleSignIn.signIn(); 30 | } 31 | if (await loadCurrentUser() == null) { 32 | GoogleSignInAuthentication credentials = await googleUser.authentication; 33 | await _firebaseAuth.signInWithGoogle( 34 | idToken: credentials.idToken, 35 | accessToken: credentials.accessToken, 36 | ); 37 | await _firebaseAuth.updateProfile(new UserUpdateInfo() 38 | ..photoUrl = googleUser.photoUrl 39 | ..displayName = googleUser.displayName); 40 | } 41 | return await loadCurrentUser(); 42 | } 43 | 44 | Future logInWithFacebook() async { 45 | final FacebookLoginResult result = 46 | await _facebookLogin.logInWithReadPermissions(['email']); 47 | 48 | switch (result.status) { 49 | case FacebookLoginStatus.loggedIn: 50 | final FacebookAccessToken accessToken = result.accessToken; 51 | await _firebaseAuth.signInWithFacebook(accessToken: accessToken.token); 52 | return loadCurrentUser(); 53 | case FacebookLoginStatus.cancelledByUser: 54 | print('Login cancelled by the user.'); 55 | return null; 56 | case FacebookLoginStatus.error: 57 | print('Something went wrong with the login process.\n' 58 | 'Here\'s the error Facebook gave us: ${result.errorMessage}'); 59 | return null; 60 | default: 61 | return null; 62 | } 63 | } 64 | 65 | Future logout() async { 66 | await Future.wait([ 67 | _googleSignIn.signOut(), 68 | _facebookLogin.logOut(), 69 | _firebaseAuth.signOut(), 70 | ]); 71 | new Future.delayed( 72 | const Duration(milliseconds: 200), () => loadCurrentUser()); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lib/logic/http_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | 4 | import 'package:cap_challenge/logic/auth_service.dart'; 5 | import 'package:http/http.dart' as http; 6 | 7 | const String FIREBASE_URL = 'us-central1-cap-challenge.cloudfunctions.net'; 8 | 9 | Future sendBottleCode(String code) async { 10 | String uid = AuthService.instance.currentUser.uid; 11 | http.Response response = await http.post( 12 | new Uri.https(FIREBASE_URL, "AddCapCode"), 13 | headers: { 14 | "uid": uid, 15 | "code": code, 16 | "Accept": "application/json", 17 | "Content-Type": "application/json" 18 | }, 19 | body: json.encode({}), 20 | ); 21 | print(response.statusCode); 22 | print(response.body); 23 | return response; 24 | } 25 | -------------------------------------------------------------------------------- /lib/logic/middleware.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:cap_challenge/logic/actions.dart'; 4 | import 'package:cap_challenge/logic/app_state.dart'; 5 | import 'package:cap_challenge/models/bottle.dart'; 6 | import 'package:cap_challenge/models/challenge.dart'; 7 | import 'package:cap_challenge/models/user.dart'; 8 | import 'package:firebase_auth/firebase_auth.dart'; 9 | import 'package:firebase_database/firebase_database.dart'; 10 | import 'package:redux/redux.dart'; 11 | 12 | final FirebaseDatabase database = FirebaseDatabase.instance; 13 | final FirebaseAuth auth = FirebaseAuth.instance; 14 | 15 | middleware(Store store, action, NextDispatcher next) { 16 | print("middleware sees ${action.runtimeType}"); 17 | if (action is InitAction) { 18 | _handleInitAction(store); 19 | } else if (action is UserProvidedAction) { 20 | _handleUserProvidedAction(store, action.user); 21 | } else if (action is CompleteChallengeAction) { 22 | _completeChallenge(action.challenge, store); 23 | } 24 | next(action); 25 | } 26 | 27 | _handleInitAction(Store store) async { 28 | FirebaseUser user = await auth.currentUser(); 29 | if (user != null) { 30 | store.dispatch(new UserProvidedAction(user)); 31 | } 32 | database.reference().child("counter").onValue.listen((Event ev) { 33 | int counter = ev.snapshot.value % 10; 34 | store.dispatch(new CounterUpdatedAction(counter)); 35 | }); 36 | database.reference().child('users') 37 | ..onValue.listen((Event event) { 38 | List ranking = []; 39 | (event.snapshot.value as Map).forEach((key, val) { 40 | User user = User.fromMap(val); 41 | ranking.add(user); 42 | }); 43 | ranking.sort((u1, u2) => u2.points.compareTo(u1.points)); 44 | store.dispatch(new RankingUpdatedAction(ranking)); 45 | }); 46 | } 47 | 48 | StreamSubscription bottlesSubscription; 49 | StreamSubscription addChallengeSubscription; 50 | StreamSubscription changeChallengeSubscription; 51 | StreamSubscription pointsSubscription; 52 | StreamSubscription ticketsSubscription; 53 | 54 | _handleUserProvidedAction(Store store, FirebaseUser user) async { 55 | await Future.wait([ 56 | bottlesSubscription?.cancel() ?? new Future(() {}), 57 | addChallengeSubscription?.cancel() ?? new Future(() {}), 58 | changeChallengeSubscription?.cancel() ?? new Future(() {}), 59 | pointsSubscription?.cancel() ?? new Future(() {}), 60 | ticketsSubscription?.cancel() ?? new Future(() {}), 61 | ]); 62 | 63 | if (user == null) { 64 | return; 65 | } 66 | 67 | DatabaseReference userRef = 68 | FirebaseDatabase.instance.reference().child('users').child(user.uid); 69 | bottlesSubscription = userRef 70 | .child('bottles') 71 | .onValue 72 | .listen((ev) => _onBottlesValue(ev, store)); 73 | addChallengeSubscription = userRef 74 | .child('currentChallenges') 75 | .onChildAdded 76 | .listen((ev) => _onChallengeAdded(ev, store)); 77 | changeChallengeSubscription = userRef 78 | .child('currentChallenges') 79 | .onChildChanged 80 | .listen((ev) => store.dispatch( 81 | new ChallengeUpdatedAction(ev.snapshot.key, ev.snapshot.value))); 82 | pointsSubscription = userRef.child('points').onValue.listen((Event ev) { 83 | store.dispatch(new PointsUpdatedAction(ev.snapshot.value)); 84 | }); 85 | ticketsSubscription = userRef.child('tickets').onValue.listen((Event ev) { 86 | store.dispatch(new TicketsUpdatedAction(ev.snapshot.value ?? 0)); 87 | }); 88 | } 89 | 90 | void _onChallengeAdded(Event event, Store store) async { 91 | DataSnapshot dataSnapshot = await FirebaseDatabase.instance 92 | .reference() 93 | .child('challenges/${event.snapshot.key}') 94 | .once(); 95 | Challenge challenge = 96 | new Challenge.fromMap(dataSnapshot.value, dataSnapshot.key); 97 | challenge.isCompleted = event.snapshot.value; 98 | store.dispatch(new ChallengeAddedAction(challenge)); 99 | } 100 | 101 | void _onBottlesValue(event, Store store) { 102 | Map bottleCollection = {}; 103 | Map bottles = event.snapshot.value; 104 | bottles?.forEach((key, val) { 105 | if (val != 0) { 106 | Bottle bottle = new Bottle(key); 107 | bottleCollection[bottle] = val; 108 | } 109 | }); 110 | store.dispatch(new CapsChangedAction(bottleCollection)); 111 | } 112 | 113 | _completeChallenge(Challenge challenge, Store store) async { 114 | FirebaseUser user = await auth.currentUser(); 115 | DatabaseReference userRef = 116 | FirebaseDatabase.instance.reference().child('users').child(user.uid); 117 | 118 | Map collection = store.state.collection; 119 | 120 | challenge.requirements.forEach((bottle, quantity) { 121 | collection[bottle] -= quantity; 122 | }); 123 | 124 | userRef.child('bottles').set(collection 125 | .map((bottle, quantity) => new MapEntry(bottle.dbKey, quantity))); 126 | 127 | userRef.child('tickets').set(store.state.tickets + 1); 128 | 129 | userRef.child('currentChallenges/${challenge.key}').set(true); 130 | 131 | userRef.child('points').set(store.state.points + challenge.reward); 132 | } 133 | -------------------------------------------------------------------------------- /lib/logic/reducer.dart: -------------------------------------------------------------------------------- 1 | import 'package:cap_challenge/logic/app_state.dart'; 2 | import 'package:cap_challenge/models/challenge.dart'; 3 | 4 | import 'actions.dart'; 5 | 6 | AppState reduce(AppState state, action) { 7 | if (action is UserProvidedAction) { 8 | AppState newState = state.copyWith(user: action.user); 9 | if (action.user == null) { 10 | newState = newState.copyWith( 11 | tickets: 0, collection: {}, points: 0, challenges: [],); 12 | } 13 | return newState; 14 | } else if (action is PointsUpdatedAction) { 15 | return state.copyWith(points: action.points); 16 | } else if (action is TicketsUpdatedAction) { 17 | return state.copyWith(tickets: action.tickets); 18 | } else if (action is CounterUpdatedAction) { 19 | return state.copyWith(counter: action.counter); 20 | } else if (action is RankingUpdatedAction) { 21 | return state.copyWith(usersRanking: action.ranking); 22 | } else if (action is ChallengesUpdatedAction) { 23 | return state.copyWith(challenges: action.challenges); 24 | } else if (action is ChallengeAddedAction) { 25 | List challenges = new List.from(state.challenges); 26 | challenges.removeWhere((challenge) => 27 | challenge.key == action.challenge.key); 28 | challenges.add(action.challenge); 29 | return state.copyWith(challenges: challenges); 30 | } else if (action is ChallengeUpdatedAction) { 31 | List challenges = new List.from(state.challenges); 32 | challenges 33 | .singleWhere((challenge) => challenge.key == action.key) 34 | .isCompleted = action.isCompleted; 35 | return state.copyWith(challenges: challenges); 36 | } else if (action is CapsChangedAction) { 37 | return state.copyWith(collection: action.collection); 38 | } 39 | return state; 40 | } 41 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:cap_challenge/generated/i18n.dart'; 2 | import 'package:cap_challenge/logic/actions.dart'; 3 | import 'package:cap_challenge/logic/app_state.dart'; 4 | import 'package:cap_challenge/logic/auth_service.dart'; 5 | import 'package:cap_challenge/logic/middleware.dart'; 6 | import 'package:cap_challenge/logic/reducer.dart'; 7 | import 'package:cap_challenge/widgets/login/login_page.dart'; 8 | import 'package:cap_challenge/widgets/main_scaffold.dart'; 9 | import 'package:firebase_auth/firebase_auth.dart'; 10 | import 'package:flutter/material.dart'; 11 | import 'package:flutter_localizations/flutter_localizations.dart'; 12 | import 'package:flutter_redux/flutter_redux.dart'; 13 | import 'package:redux/redux.dart'; 14 | 15 | void main() async { 16 | Widget _defaultHome = new LoginPage(); 17 | 18 | FirebaseUser currentUser = await AuthService.instance.loadCurrentUser(); 19 | if (currentUser != null) { 20 | _defaultHome = new MainScaffold(); 21 | } 22 | 23 | runApp(new MyApp(_defaultHome)); 24 | } 25 | 26 | class MyApp extends StatelessWidget { 27 | final Store store = new Store(reduce, 28 | initialState: new AppState( 29 | points: 0, 30 | tickets: 0, 31 | counter: 0, 32 | collection: {}, 33 | challenges: [], 34 | usersRanking: []), 35 | middleware: [middleware].toList()); 36 | final Widget defaultHome; 37 | 38 | MyApp(this.defaultHome); 39 | 40 | @override 41 | Widget build(BuildContext context) { 42 | store.dispatch(new InitAction()); 43 | return new StoreProvider( 44 | store: store, 45 | child: new MaterialApp( 46 | theme: new ThemeData( 47 | primarySwatch: Colors.red, 48 | ), 49 | supportedLocales: [ 50 | const Locale('en', ''), 51 | const Locale('pl', ''), 52 | ], 53 | localizationsDelegates: [ 54 | new GeneratedLocalizationsDelegate(), 55 | GlobalMaterialLocalizations.delegate, 56 | GlobalWidgetsLocalizations.delegate, 57 | ], 58 | debugShowCheckedModeBanner: false, 59 | home: defaultHome, 60 | routes: { 61 | "login": (context) => new LoginPage(), 62 | "main": (context) => new MainScaffold(), 63 | }, 64 | ), 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/models/add_code_result.dart: -------------------------------------------------------------------------------- 1 | import 'package:meta/meta.dart'; 2 | 3 | class ScannedQRCodeResult { 4 | final String qrCode; 5 | 6 | ScannedQRCodeResult(this.qrCode); 7 | } 8 | 9 | class AddedCodeResult { 10 | final bool isOk; 11 | 12 | AddedCodeResult({@required this.isOk}); 13 | } -------------------------------------------------------------------------------- /lib/models/bottle.dart: -------------------------------------------------------------------------------- 1 | import 'package:cap_challenge/generated/i18n.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:quiver/core.dart'; 4 | 5 | class Bottle { 6 | final BottleName bottleName; 7 | final Capacity capacity; 8 | final int points; 9 | final String imagePath; 10 | 11 | const Bottle._internal(this.bottleName, this.capacity, this.points, 12 | this.imagePath); 13 | 14 | factory Bottle(String key) { 15 | switch (key) { 16 | case 'COCA_COLA_250': 17 | return COCA_COLA_250; 18 | case 'COCA_COLA_300': 19 | return COCA_COLA_300; 20 | case 'COCA_COLA_500': 21 | return COCA_COLA_500; 22 | case 'COCA_COLA_1L': 23 | return COCA_COLA_1L; 24 | case 'COCA_COLA_2L': 25 | return COCA_COLA_2L; 26 | case 'SPRITE_250': 27 | return SPRITE_250; 28 | case 'SPRITE_300': 29 | return SPRITE_300; 30 | case 'SPRITE_500': 31 | return SPRITE_500; 32 | case 'SPRITE_1L': 33 | return SPRITE_1L; 34 | case 'SPRITE_2L': 35 | return SPRITE_2L; 36 | case 'ZERO_250': 37 | return ZERO_250; 38 | case 'ZERO_300': 39 | return ZERO_300; 40 | case 'ZERO_500': 41 | return ZERO_500; 42 | case 'ZERO_1L': 43 | return ZERO_1L; 44 | case 'ZERO_2L': 45 | return ZERO_2L; 46 | case 'LIGHT_250': 47 | return LIGHT_250; 48 | case 'LIGHT_300': 49 | return LIGHT_300; 50 | case 'LIGHT_500': 51 | return LIGHT_500; 52 | case 'LIGHT_1L': 53 | return LIGHT_1L; 54 | case 'LIGHT_2L': 55 | return LIGHT_2L; 56 | case 'FANTA_250': 57 | return FANTA_250; 58 | case 'FANTA_300': 59 | return FANTA_300; 60 | case 'FANTA_500': 61 | return FANTA_500; 62 | case 'FANTA_1L': 63 | return FANTA_1L; 64 | case 'FANTA_2L': 65 | return FANTA_2L; 66 | default: 67 | return null; 68 | } 69 | } 70 | 71 | String get dbKey => types[bottleName] + sizes[capacity]; 72 | 73 | static const COCA_COLA_300 = const Bottle._internal( 74 | BottleName.COCA_COLA, Capacity.CAN_300, 50, "images/coca_cola_baner.jpg"); 75 | static const COCA_COLA_250 = const Bottle._internal(BottleName.COCA_COLA, 76 | Capacity.PLASTIC_250, 100, "images/coca_cola_baner.jpg"); 77 | static const COCA_COLA_500 = const Bottle._internal(BottleName.COCA_COLA, 78 | Capacity.PLASTIC_500, 200, "images/coca_cola_baner.jpg"); 79 | static const COCA_COLA_1L = const Bottle._internal(BottleName.COCA_COLA, 80 | Capacity.PLASTIC_1L, 450, "images/coca_cola_baner.jpg"); 81 | static const COCA_COLA_2L = const Bottle._internal(BottleName.COCA_COLA, 82 | Capacity.PLASTIC_2L, 1000, "images/coca_cola_baner.jpg"); 83 | static const SPRITE_300 = const Bottle._internal( 84 | BottleName.SPRITE, Capacity.CAN_300, 50, "images/sprite_baner.jpeg"); 85 | static const SPRITE_250 = const Bottle._internal( 86 | BottleName.SPRITE, Capacity.PLASTIC_250, 100, "images/sprite_baner.jpeg"); 87 | static const SPRITE_500 = const Bottle._internal( 88 | BottleName.SPRITE, Capacity.PLASTIC_500, 200, "images/sprite_baner.jpeg"); 89 | static const SPRITE_1L = const Bottle._internal( 90 | BottleName.SPRITE, Capacity.PLASTIC_1L, 450, "images/sprite_baner.jpeg"); 91 | static const SPRITE_2L = const Bottle._internal( 92 | BottleName.SPRITE, Capacity.PLASTIC_2L, 1000, "images/sprite_baner.jpeg"); 93 | static const FANTA_300 = const Bottle._internal( 94 | BottleName.FANTA, Capacity.CAN_300, 50, "images/fanta_baner.jpg"); 95 | static const FANTA_250 = const Bottle._internal( 96 | BottleName.FANTA, Capacity.PLASTIC_250, 100, "images/fanta_baner.jpg"); 97 | static const FANTA_500 = const Bottle._internal( 98 | BottleName.FANTA, Capacity.PLASTIC_500, 200, "images/fanta_baner.jpg"); 99 | static const FANTA_1L = const Bottle._internal( 100 | BottleName.FANTA, Capacity.PLASTIC_1L, 450, "images/fanta_baner.jpg"); 101 | static const FANTA_2L = const Bottle._internal( 102 | BottleName.FANTA, Capacity.PLASTIC_2L, 1000, "images/fanta_baner.jpg"); 103 | static const ZERO_300 = const Bottle._internal( 104 | BottleName.COCA_COLA_ZERO, Capacity.CAN_300, 50, "images/coke_zero.png"); 105 | static const ZERO_250 = const Bottle._internal(BottleName.COCA_COLA_ZERO, 106 | Capacity.PLASTIC_250, 100, "images/coke_zero.png"); 107 | static const ZERO_500 = const Bottle._internal(BottleName.COCA_COLA_ZERO, 108 | Capacity.PLASTIC_500, 200, "images/coke_zero.png"); 109 | static const ZERO_1L = const Bottle._internal(BottleName.COCA_COLA_ZERO, 110 | Capacity.PLASTIC_1L, 450, "images/coke_zero.png"); 111 | static const ZERO_2L = const Bottle._internal(BottleName.COCA_COLA_ZERO, 112 | Capacity.PLASTIC_2L, 1000, "images/coke_zero.png"); 113 | static const LIGHT_300 = const Bottle._internal(BottleName.COCA_COLA_LIGHT, 114 | Capacity.CAN_300, 50, "images/coke_light.png"); 115 | static const LIGHT_250 = const Bottle._internal(BottleName.COCA_COLA_LIGHT, 116 | Capacity.PLASTIC_250, 100, "images/coke_light.png"); 117 | static const LIGHT_500 = const Bottle._internal(BottleName.COCA_COLA_LIGHT, 118 | Capacity.PLASTIC_500, 200, "images/coke_light.png"); 119 | static const LIGHT_1L = const Bottle._internal(BottleName.COCA_COLA_LIGHT, 120 | Capacity.PLASTIC_1L, 450, "images/coke_light.png"); 121 | static const LIGHT_2L = const Bottle._internal(BottleName.COCA_COLA_LIGHT, 122 | Capacity.PLASTIC_2L, 1000, "images/coke_light.png"); 123 | 124 | @override 125 | bool operator ==(other) { 126 | return other != null && 127 | other is Bottle && 128 | other.bottleName == this.bottleName && 129 | other.capacity == this.capacity; 130 | } 131 | 132 | @override 133 | int get hashCode { 134 | return hash2(bottleName, capacity); 135 | } 136 | 137 | @override 138 | String toString() { 139 | return bottleNameToString(bottleName) + 140 | ' - ' + '$capacity'; 141 | // bottleCapacityToLongString(capacity); 142 | } 143 | } 144 | 145 | const Map types = { 146 | BottleName.COCA_COLA: 'COCA_COLA', 147 | BottleName.SPRITE: 'SPRITE', 148 | BottleName.FANTA: 'FANTA', 149 | BottleName.COCA_COLA_ZERO: 'ZERO', 150 | BottleName.COCA_COLA_LIGHT: 'LIGHT', 151 | }; 152 | 153 | const Map sizes = { 154 | Capacity.PLASTIC_250: '_250', 155 | Capacity.CAN_300: '_300', 156 | Capacity.PLASTIC_500: '_500', 157 | Capacity.PLASTIC_1L: '_1L', 158 | Capacity.PLASTIC_2L: '_2L' 159 | }; 160 | 161 | enum BottleName { 162 | COCA_COLA, 163 | SPRITE, 164 | FANTA, 165 | COCA_COLA_ZERO, 166 | COCA_COLA_LIGHT, 167 | } 168 | 169 | enum Capacity { 170 | CAN_300, 171 | PLASTIC_250, 172 | PLASTIC_500, 173 | PLASTIC_1L, 174 | PLASTIC_2L, 175 | } 176 | 177 | String bottleNameToString(BottleName name) { 178 | switch (name) { 179 | case BottleName.COCA_COLA: 180 | return "Coca-Cola"; 181 | case BottleName.SPRITE: 182 | return "Sprite"; 183 | case BottleName.FANTA: 184 | return "Fanta"; 185 | case BottleName.COCA_COLA_ZERO: 186 | return "Coca-Cola Zero"; 187 | case BottleName.COCA_COLA_LIGHT: 188 | return "Coca-Cola Light"; 189 | default: 190 | return "Nieznane"; 191 | } 192 | } 193 | 194 | String bottleCapacityToShortString(Capacity capacity) { 195 | switch (capacity) { 196 | case Capacity.CAN_300: 197 | return "330ml"; 198 | case Capacity.PLASTIC_1L: 199 | return "1L"; 200 | case Capacity.PLASTIC_2L: 201 | return "2L"; 202 | case Capacity.PLASTIC_250: 203 | return "250ml"; 204 | case Capacity.PLASTIC_500: 205 | return "0,5L"; 206 | default: 207 | return "Nieznane"; 208 | } 209 | } 210 | 211 | String bottleCapacityToLongString(Capacity capacity, BuildContext context) { 212 | switch (capacity) { 213 | case Capacity.CAN_300: 214 | return S 215 | .of(context) 216 | .can330; 217 | case Capacity.PLASTIC_1L: 218 | return S 219 | .of(context) 220 | .bottle1l; 221 | case Capacity.PLASTIC_2L: 222 | return S 223 | .of(context) 224 | .bottle2l; 225 | case Capacity.PLASTIC_250: 226 | return S 227 | .of(context) 228 | .bottle250ml; 229 | case Capacity.PLASTIC_500: 230 | return S 231 | .of(context) 232 | .bottle500ml; 233 | default: 234 | return S 235 | .of(context) 236 | .unknown; 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /lib/models/challenge.dart: -------------------------------------------------------------------------------- 1 | import 'package:cap_challenge/models/bottle.dart'; 2 | 3 | class Challenge { 4 | final String key; 5 | final Map requirements; 6 | final int reward; 7 | final String photoUrl; 8 | final String name; 9 | final Difficulty difficulty; 10 | bool isCompleted = false; 11 | 12 | Challenge({this.key, 13 | this.requirements, 14 | this.reward, 15 | this.photoUrl, 16 | this.name, 17 | this.difficulty}); 18 | 19 | Challenge.fromMap(Map map, String key) 20 | : key = key, 21 | reward = map['reward'], 22 | photoUrl = map['photoUrl'], 23 | name = map['name'], 24 | difficulty = Difficulty.EASY, 25 | requirements = (map['requirements'] as Map) 26 | .map((name, val) => 27 | new MapEntry(new Bottle(name), val)); 28 | 29 | int get totalBottles => requirements.values.reduce((i1, i2) => i1 + i2); 30 | 31 | List get bottleNames => 32 | requirements.keys.map((bottle) => bottle.bottleName).toSet().toList(); 33 | } 34 | 35 | enum Difficulty { 36 | EASY, 37 | MEDIUM, 38 | HARD, 39 | } 40 | -------------------------------------------------------------------------------- /lib/models/user.dart: -------------------------------------------------------------------------------- 1 | class User { 2 | final String name; 3 | final String email; 4 | final int points; 5 | 6 | User.fromMap(Map map) 7 | : name = map['username'], 8 | points = map['points'], 9 | email = map['email']; 10 | } 11 | -------------------------------------------------------------------------------- /lib/widgets/challenges/challenge_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:cap_challenge/generated/i18n.dart'; 2 | import 'package:cap_challenge/models/bottle.dart'; 3 | import 'package:cap_challenge/models/challenge.dart'; 4 | import 'package:cap_challenge/widgets/challenges/challenge_common_views.dart'; 5 | import 'package:cap_challenge/widgets/challenges/challenge_details_page.dart'; 6 | import 'package:flutter/material.dart'; 7 | 8 | class ChallengeCard extends StatelessWidget { 9 | final Challenge challenge; 10 | final Map bottleCollection; 11 | final Function(Challenge) completeChallenge; 12 | 13 | ChallengeCard(this.challenge, this.bottleCollection, this.completeChallenge); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return new Padding( 18 | padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 8.0), 19 | child: new Card( 20 | shape: new RoundedRectangleBorder( 21 | borderRadius: new BorderRadius.circular(12.0), 22 | ), 23 | elevation: 8.0, 24 | child: new Column( 25 | crossAxisAlignment: CrossAxisAlignment.stretch, 26 | children: [ 27 | _buildImageStack(context), 28 | _buildUnderImage(context), 29 | ], 30 | ), 31 | ), 32 | ); 33 | } 34 | 35 | Widget _buildUnderImage(BuildContext context) { 36 | return new Padding( 37 | padding: const EdgeInsets.all(8.0), 38 | child: new Column( 39 | crossAxisAlignment: CrossAxisAlignment.start, 40 | children: [ 41 | new Row( 42 | crossAxisAlignment: CrossAxisAlignment.start, 43 | children: [ 44 | _buildNeededBottles(context), 45 | new Expanded(child: new Container()), 46 | getRewardView(challenge), 47 | ], 48 | ), 49 | 50 | _buildBottomButtons(context), 51 | ], 52 | ), 53 | ); 54 | } 55 | 56 | Widget _buildNeededBottles(BuildContext context) { 57 | Text header = new Text( 58 | S 59 | .of(context) 60 | .requiredProducts, 61 | style: new TextStyle(fontWeight: FontWeight.bold), 62 | ); 63 | 64 | List names = challenge.bottleNames 65 | .take(3) 66 | .map((name) => new Text(bottleNameToString(name))) 67 | .toList(); 68 | 69 | return new Padding( 70 | padding: const EdgeInsets.all(8.0), 71 | child: new Stack( 72 | children: [ 73 | new Column( 74 | crossAxisAlignment: CrossAxisAlignment.start, 75 | children: [header]..addAll(names), 76 | ), 77 | new Positioned( 78 | bottom: 0.0, 79 | left: 0.0, 80 | right: 0.0, 81 | child: new Container( 82 | height: 30.0, 83 | decoration: new BoxDecoration( 84 | gradient: new LinearGradient( 85 | begin: Alignment.bottomCenter, 86 | end: Alignment.topCenter, 87 | colors: [ 88 | Colors.white, 89 | const Color(0x00FFFFFF), 90 | ], 91 | ), 92 | ), 93 | ), 94 | ), 95 | ], 96 | ), 97 | ); 98 | } 99 | 100 | Widget _buildImageStack(BuildContext context) { 101 | List stackChildren = [ 102 | new Hero( 103 | tag: "challenge_image_${challenge.name}", 104 | child: new Image.network( 105 | challenge.photoUrl, 106 | height: 180.0, 107 | fit: BoxFit.cover, 108 | ), 109 | ), 110 | new Positioned( 111 | bottom: 0.0, 112 | left: 0.0, 113 | right: 0.0, 114 | child: _buildShade(context, challenge), 115 | ), 116 | new Positioned( 117 | left: 0.0, 118 | right: 0.0, 119 | bottom: 0.0, 120 | child: _buildTitle(challenge, context), 121 | ), 122 | ]; 123 | 124 | if (challenge.isCompleted) { 125 | stackChildren.addAll([ 126 | new Positioned.fill( 127 | child: new Container( 128 | decoration: new BoxDecoration(color: Colors.grey.withAlpha(220)), 129 | ), 130 | ), 131 | Positioned.fill( 132 | child: Column( 133 | mainAxisSize: MainAxisSize.max, 134 | mainAxisAlignment: MainAxisAlignment.center, 135 | children: [ 136 | new Text( 137 | S 138 | .of(context) 139 | .completed, 140 | style: new TextStyle(fontSize: 28.0, color: Colors.white), 141 | ), 142 | new Padding( 143 | padding: const EdgeInsets.all(8.0), 144 | child: new Icon( 145 | Icons.check, 146 | color: Colors.white, 147 | size: 40.0, 148 | ), 149 | ) 150 | ], 151 | ), 152 | ) 153 | ]); 154 | } 155 | 156 | return new GestureDetector( 157 | onTap: () => _goToDetails(context, challenge), 158 | child: new Stack( 159 | fit: StackFit.passthrough, 160 | children: stackChildren, 161 | ), 162 | ); 163 | } 164 | 165 | Widget _buildShade(BuildContext context, Challenge challenge) { 166 | return new Column( 167 | crossAxisAlignment: CrossAxisAlignment.stretch, 168 | children: [ 169 | new Container( 170 | height: 80.0, 171 | decoration: new BoxDecoration( 172 | gradient: new LinearGradient( 173 | begin: Alignment.bottomCenter, 174 | end: Alignment.topCenter, 175 | colors: [ 176 | const Color(0x77FF0000), 177 | const Color(0x00FFFFFF), 178 | ], 179 | ), 180 | ), 181 | ), 182 | ], 183 | ); 184 | } 185 | 186 | Widget _buildTitle(Challenge challenge, BuildContext context) { 187 | return new Column( 188 | crossAxisAlignment: CrossAxisAlignment.center, 189 | children: [ 190 | new Padding( 191 | padding: const EdgeInsets.all(8.0), 192 | child: new Text( 193 | challenge.name, 194 | style: Theme 195 | .of(context) 196 | .textTheme 197 | .display1 198 | .copyWith(color: Colors.white), 199 | ), 200 | ), 201 | ], 202 | ); 203 | } 204 | 205 | Widget _buildBottomButtons(BuildContext context) { 206 | return new Row( 207 | mainAxisAlignment: MainAxisAlignment.end, 208 | children: [ 209 | new FlatButton( 210 | textColor: Colors.red, 211 | onPressed: () => _goToDetails(context, challenge), 212 | child: new Text(S 213 | .of(context) 214 | .showMore 215 | .toUpperCase()), 216 | ), 217 | ], 218 | ); 219 | } 220 | 221 | _goToDetails(BuildContext context, Challenge challenge) { 222 | Navigator 223 | .of(context) 224 | .push(new MaterialPageRoute( 225 | builder: (context) => 226 | new ChallengeDetailsPage(challenge, bottleCollection))) 227 | .then((challengeCompleted) { 228 | if (challengeCompleted ?? false) { 229 | this.completeChallenge(challenge); 230 | } 231 | }); 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /lib/widgets/challenges/challenge_common_views.dart: -------------------------------------------------------------------------------- 1 | import 'package:cap_challenge/models/challenge.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | Widget getRewardView(Challenge challenge) { 5 | return new Row( 6 | children: [ 7 | new Text( 8 | challenge.reward.toString(), 9 | style: new TextStyle( 10 | color: Colors.yellow[600], 11 | fontWeight: FontWeight.bold, 12 | fontSize: 24.0), 13 | ), 14 | new Icon( 15 | Icons.monetization_on, 16 | color: Colors.yellow[600], 17 | size: 36.0, 18 | ), 19 | ], 20 | ); 21 | } -------------------------------------------------------------------------------- /lib/widgets/challenges/challenge_details_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math' as math; 2 | 3 | import 'package:cap_challenge/generated/i18n.dart'; 4 | import 'package:cap_challenge/models/bottle.dart'; 5 | import 'package:cap_challenge/models/challenge.dart'; 6 | import 'package:cap_challenge/widgets/challenges/challenge_common_views.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:sliver_fab/sliver_fab.dart'; 9 | 10 | class ChallengeDetailsPage extends StatefulWidget { 11 | final Challenge challenge; 12 | final Map bottleCollection; 13 | 14 | ChallengeDetailsPage(this.challenge, this.bottleCollection); 15 | 16 | @override 17 | ChallengeDetailsPageState createState() { 18 | return new ChallengeDetailsPageState(); 19 | } 20 | } 21 | 22 | class ChallengeDetailsPageState extends State { 23 | final double _rowHeight = 32.0; 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return new Scaffold( 28 | body: new SliverFab( 29 | floatingActionButton: 30 | _buildFab(widget.bottleCollection, widget.challenge), 31 | slivers: [ 32 | _buildSliverAppBar(), 33 | new SliverList( 34 | delegate: new SliverChildListDelegate( 35 | [ 36 | _buildCompletedLabel(widget.challenge.isCompleted), 37 | _buildMissingBottlesLabel( 38 | widget.bottleCollection, widget.challenge), 39 | //_buildDifficultyRow(), 40 | _buildPriceRow(), 41 | ]..addAll(_buildRequirementsRows(widget.challenge)), 42 | ), 43 | ), 44 | ], 45 | ), 46 | ); 47 | } 48 | 49 | Widget _buildFab(Map collection, Challenge challenge) { 50 | if (_canChallengeBeCompleted(collection, challenge) && 51 | !challenge.isCompleted) { 52 | return new Builder( 53 | builder: (context) => new FloatingActionButton( 54 | onPressed: () { 55 | Navigator.of(context).pop(true); 56 | }, 57 | child: new Icon(Icons.check), 58 | ), 59 | ); 60 | } else { 61 | return new Container(); 62 | } 63 | } 64 | 65 | Padding _buildPriceRow() { 66 | return new Padding( 67 | padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), 68 | child: new Row( 69 | children: [ 70 | new Padding( 71 | padding: const EdgeInsets.only(right: 8.0), 72 | child: new Text(S 73 | .of(context) 74 | .reward), 75 | ), 76 | getRewardView(widget.challenge), 77 | ], 78 | ), 79 | ); 80 | } 81 | 82 | List _buildRequirementsRows(Challenge challenge) { 83 | return widget.challenge.requirements.keys.map((bottle) { 84 | return _buildRequirementRow( 85 | context, 86 | bottle, 87 | widget.challenge.requirements[bottle], 88 | widget.bottleCollection[bottle] ?? 0, 89 | ); 90 | }).toList(); 91 | } 92 | 93 | Widget _buildMissingBottlesLabel( 94 | Map collection, Challenge challenge) { 95 | if (_canChallengeBeCompleted(collection, challenge)) { 96 | return new Container(); 97 | } else { 98 | return new Padding( 99 | padding: new EdgeInsets.all(16.0), 100 | child: new Text( 101 | S.of(context).missingBottleMsg( 102 | _getMissingBottles(collection, challenge).toString()), 103 | textAlign: TextAlign.center, 104 | ), 105 | ); 106 | } 107 | } 108 | 109 | bool _canChallengeBeCompleted( 110 | Map collection, Challenge challenge) { 111 | return challenge.isCompleted || _getMissingBottles(collection, challenge) == 0; 112 | } 113 | 114 | int _getMissingBottles(Map collection, Challenge challenge) { 115 | int missing = 0; 116 | challenge.requirements.forEach((bottle, required) { 117 | int diff = required - (collection[bottle] ?? 0); 118 | if (diff > 0) { 119 | missing += diff; 120 | } 121 | }); 122 | return missing; 123 | } 124 | 125 | Widget _buildRequirementRow( 126 | BuildContext context, Bottle bottle, int required, int current) { 127 | return new ListTile( 128 | title: new Text(bottleNameToString(bottle.bottleName)), 129 | subtitle: new Text(bottleCapacityToLongString(bottle.capacity, context)), 130 | trailing: _buildProgressIndicator(required, current), 131 | ); 132 | } 133 | 134 | Widget _buildProgressIndicator(int required, int current) { 135 | if (widget.challenge.isCompleted) { 136 | current = required; 137 | } 138 | int viewCurrent = math.min(current, required); 139 | int missing = required - viewCurrent; 140 | List filledBottles = new Iterable.generate( 141 | viewCurrent, 142 | (i) => new Padding( 143 | padding: const EdgeInsets.symmetric(horizontal: 4.0), 144 | child: new Image.asset( 145 | "images/bottle_filled.png", 146 | height: _rowHeight, 147 | color: widget.challenge.isCompleted ? Colors.grey : Colors.black, 148 | ), 149 | ), 150 | ).toList(); 151 | List emptyBottles = new Iterable.generate( 152 | missing, 153 | (i) => new Padding( 154 | padding: const EdgeInsets.symmetric(horizontal: 4.0), 155 | child: new Image.asset( 156 | "images/bottle_empty.png", 157 | height: _rowHeight, 158 | ), 159 | ), 160 | ).toList(); 161 | return new Row( 162 | mainAxisSize: MainAxisSize.min, 163 | children: filledBottles..addAll(emptyBottles), 164 | ); 165 | } 166 | 167 | SliverAppBar _buildSliverAppBar() { 168 | return new SliverAppBar( 169 | expandedHeight: 256.0, 170 | pinned: true, 171 | flexibleSpace: new FlexibleSpaceBar( 172 | title: new Text(widget.challenge.name), 173 | background: new Container( 174 | color: Colors.white, 175 | child: new Hero( 176 | tag: "challenge_image_${widget.challenge.name}", 177 | child: new Stack( 178 | fit: StackFit.expand, 179 | children: [ 180 | new Image.network( 181 | widget.challenge.photoUrl, 182 | height: 256.0, 183 | fit: BoxFit.cover, 184 | ), 185 | const DecoratedBox( 186 | decoration: const BoxDecoration( 187 | gradient: const LinearGradient( 188 | begin: const Alignment(0.0, -1.0), 189 | end: const Alignment(0.0, -0.4), 190 | colors: const [ 191 | const Color(0x60FF0000), 192 | const Color(0x00FF0000) 193 | ], 194 | ), 195 | ), 196 | ), 197 | const DecoratedBox( 198 | decoration: const BoxDecoration( 199 | gradient: const LinearGradient( 200 | begin: const Alignment(0.0, 1.0), 201 | end: const Alignment(0.0, 0.4), 202 | colors: const [ 203 | const Color(0x60FF0000), 204 | const Color(0x00FF0000) 205 | ], 206 | ), 207 | ), 208 | ), 209 | ], 210 | ), 211 | ), 212 | ), 213 | ), 214 | ); 215 | } 216 | 217 | Widget _buildCompletedLabel(bool isCompleted) { 218 | if (isCompleted) { 219 | return new Padding( 220 | padding: const EdgeInsets.all(8.0), 221 | child: new Row( 222 | children: [ 223 | new Icon(Icons.check, color: Colors.green), 224 | new Padding( 225 | padding: const EdgeInsets.symmetric(horizontal: 8.0), 226 | child: new Text( 227 | "Wyzwanie wykonane!", 228 | style: new TextStyle(color: Colors.green, fontSize: 24.0), 229 | ), 230 | ), 231 | new Icon(Icons.check, color: Colors.green), 232 | ], 233 | mainAxisAlignment: MainAxisAlignment.center, 234 | ), 235 | ); 236 | } else { 237 | return new Container(); 238 | } 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /lib/widgets/challenges/challenges_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:cap_challenge/logic/actions.dart'; 2 | import 'package:cap_challenge/logic/app_state.dart'; 3 | import 'package:cap_challenge/models/bottle.dart'; 4 | import 'package:cap_challenge/models/challenge.dart'; 5 | import 'package:cap_challenge/widgets/challenges/challenge_card.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter_redux/flutter_redux.dart'; 8 | 9 | class ChallengesPage extends StatelessWidget { 10 | ChallengesPage(); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return new StoreConnector( 15 | converter: (store) { 16 | return new _ViewModel( 17 | bottleCollection: store.state.collection, 18 | challenges: store.state.challenges, 19 | completeChallenge: (challenge) => 20 | store.dispatch(new CompleteChallengeAction(challenge)), 21 | ); 22 | }, 23 | builder: (BuildContext context, _ViewModel vm) { 24 | return new ListView( 25 | children: vm.challenges 26 | .map((c) => 27 | new ChallengeCard(c, vm.bottleCollection, (challenge) { 28 | Scaffold.of(context).showSnackBar(new SnackBar( 29 | content: new Text("Wykonano zadanie"), 30 | )); 31 | vm.completeChallenge(challenge); 32 | })) 33 | .toList(), 34 | ); 35 | }, 36 | ); 37 | } 38 | } 39 | 40 | class _ViewModel { 41 | final Map bottleCollection; 42 | final List challenges; 43 | final Function(Challenge) completeChallenge; 44 | 45 | _ViewModel( 46 | {@required this.bottleCollection, @required this.challenges, @required this.completeChallenge}); 47 | 48 | 49 | } -------------------------------------------------------------------------------- /lib/widgets/code_cap.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:math' as math; 3 | 4 | import 'package:cap_challenge/generated/i18n.dart'; 5 | import 'package:cap_challenge/logic/http_service.dart' show sendBottleCode; 6 | import 'package:cap_challenge/models/add_code_result.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:http/http.dart'; 9 | 10 | class CodeCap extends StatefulWidget { 11 | @override 12 | State createState() { 13 | return new CodeCapState(); 14 | } 15 | } 16 | 17 | class CodeCapState extends State with TickerProviderStateMixin { 18 | TextEditingController _textController; 19 | AnimationController _animationController; 20 | Animation _animation; 21 | bool _shouldShowSubmitButton = false; 22 | 23 | @override 24 | void initState() { 25 | super.initState(); 26 | _animationController = new AnimationController( 27 | duration: const Duration(seconds: 1), vsync: this); 28 | _animation = new Tween(begin: 0.0, end: 2 * math.pi) 29 | .animate(_animationController); 30 | _animationController.addStatusListener((status) { 31 | if (status == AnimationStatus.completed) { 32 | _animationController.reset(); 33 | _animationController.forward(); 34 | } 35 | }); 36 | _textController = new TextEditingController(); 37 | } 38 | 39 | @override 40 | void dispose() { 41 | _textController.dispose(); 42 | _animationController.dispose(); 43 | super.dispose(); 44 | } 45 | 46 | @override 47 | Widget build(BuildContext context) { 48 | return new GestureDetector( 49 | onTap: () => Navigator.of(context).pop(), 50 | child: new Material( 51 | color: Colors.transparent, 52 | child: new Center( 53 | child: _buildCap(), 54 | ), 55 | ), 56 | ); 57 | } 58 | 59 | Hero _buildCap() { 60 | return new Hero( 61 | tag: "fab-cap", 62 | child: new Container( 63 | height: 300.0, 64 | width: 300.0, 65 | child: new Stack( 66 | children: [ 67 | _buildCapImage(), 68 | _buildInput(), 69 | _buildSubmitButton(), 70 | ], 71 | ), 72 | ), 73 | ); 74 | } 75 | 76 | Widget _buildCapImage() { 77 | return new AnimatedBuilder( 78 | animation: _animation, 79 | builder: (context, child) => 80 | new Transform( 81 | alignment: Alignment.center, 82 | transform: new Matrix4.identity() 83 | ..rotateZ(_animation.value), 84 | child: new Image.asset( 85 | "images/cap.png", 86 | fit: BoxFit.fill, 87 | ), 88 | ), 89 | ); 90 | } 91 | 92 | Widget _buildSubmitButton() { 93 | if (!_shouldShowSubmitButton) { 94 | return new Container(); 95 | } 96 | return new Align( 97 | alignment: Alignment.topCenter, 98 | child: new Padding( 99 | padding: const EdgeInsets.only(top: 72.0), 100 | child: new RaisedButton( 101 | shape: new RoundedRectangleBorder( 102 | borderRadius: new BorderRadius.circular(24.0), 103 | ), 104 | onPressed: () => 105 | _sendBottleCode(_textController.text) 106 | .then(_handleSubmitResponse) 107 | .catchError((err) { 108 | print(err); 109 | _animationController.stop(); 110 | _showBadCodeDialog(); 111 | }), 112 | color: Colors.red, 113 | child: new Text( 114 | S 115 | .of(context) 116 | .sendCode 117 | .toUpperCase(), 118 | style: new TextStyle(color: Colors.white), 119 | ), 120 | ), 121 | ), 122 | ); 123 | } 124 | 125 | Future _sendBottleCode(String code) async { 126 | FocusScope.of(context).requestFocus(new FocusNode()); 127 | _animationController.forward(); 128 | Response response = await sendBottleCode(code); 129 | _animationController.stop(); 130 | return response; 131 | } 132 | 133 | _handleSubmitResponse(Response response) { 134 | if (response.statusCode == 200) { 135 | Navigator.of(context).pop(new AddedCodeResult(isOk: true)); 136 | } else { 137 | _showBadCodeDialog(); 138 | } 139 | } 140 | 141 | void _showBadCodeDialog() { 142 | showDialog( 143 | context: context, 144 | builder: (context) { 145 | return new AlertDialog( 146 | title: new Text(S 147 | .of(context) 148 | .invalidCode), 149 | content: 150 | new Text(S 151 | .of(context) 152 | .invalidCodeMsg), 153 | actions: [ 154 | new FlatButton( 155 | onPressed: () => Navigator.of(context).pop(), 156 | child: new Text(S 157 | .of(context) 158 | .close), 159 | ) 160 | ], 161 | ); 162 | }, 163 | ); 164 | } 165 | 166 | Center _buildInput() { 167 | return new Center( 168 | child: new Padding( 169 | padding: const EdgeInsets.symmetric(horizontal: 68.0), 170 | child: new TextField( 171 | autocorrect: false, 172 | maxLength: 10, 173 | decoration: new InputDecoration(labelText: S 174 | .of(context) 175 | .codeUnderCap), 176 | controller: _textController, 177 | onChanged: (text) => 178 | setState(() => _shouldShowSubmitButton = text.length == 10), 179 | ), 180 | ), 181 | ); 182 | } 183 | } 184 | 185 | class HeroDialogRoute extends PageRoute { 186 | HeroDialogRoute({this.builder}) : super(); 187 | 188 | final WidgetBuilder builder; 189 | 190 | @override 191 | bool get opaque => false; 192 | 193 | @override 194 | bool get barrierDismissible => true; 195 | 196 | @override 197 | Duration get transitionDuration => const Duration(milliseconds: 300); 198 | 199 | @override 200 | bool get maintainState => true; 201 | 202 | @override 203 | Color get barrierColor => Colors.black54; 204 | 205 | @override 206 | Widget buildTransitions(BuildContext context, Animation animation, 207 | Animation secondaryAnimation, Widget child) { 208 | return new FadeTransition( 209 | opacity: new CurvedAnimation(parent: animation, curve: Curves.easeOut), 210 | child: child); 211 | } 212 | 213 | @override 214 | Widget buildPage(BuildContext context, Animation animation, 215 | Animation secondaryAnimation) { 216 | return builder(context); 217 | } 218 | 219 | @override 220 | String get barrierLabel => null; 221 | } 222 | -------------------------------------------------------------------------------- /lib/widgets/collection/collection_grid_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:cap_challenge/models/bottle.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class CollectionGridItem extends StatelessWidget { 5 | final Bottle bottle; 6 | final int quantity; 7 | 8 | CollectionGridItem(this.bottle, this.quantity); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return new Stack( 13 | children: [ 14 | new Padding( 15 | padding: const EdgeInsets.all(2.0), 16 | child: new Card( 17 | shape: RoundedRectangleBorder( 18 | borderRadius: BorderRadius.all(Radius.circular(8.0)), 19 | ), 20 | child: new Stack( 21 | alignment: Alignment.center, 22 | children: [ 23 | new Image.asset( 24 | bottle.imagePath, 25 | fit: BoxFit.cover, 26 | ), 27 | new Positioned( 28 | bottom: 2.0, 29 | left: 2.0, 30 | child: new Text( 31 | bottleCapacityToShortString(bottle.capacity), 32 | style: new TextStyle( 33 | color: Colors.white, fontWeight: FontWeight.bold), 34 | ), 35 | ), 36 | ], 37 | ), 38 | ), 39 | ), 40 | new Positioned( 41 | top: 0.0, 42 | right: 0.0, 43 | child: new CircleAvatar( 44 | backgroundColor: Colors.grey, 45 | radius: 12.0, 46 | child: new Text( 47 | "$quantity", 48 | style: new TextStyle(fontSize: 16.0, color: Colors.white), 49 | ), 50 | ), 51 | ), 52 | ], 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/widgets/collection/collection_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:cap_challenge/generated/i18n.dart'; 2 | import 'package:cap_challenge/logic/app_state.dart'; 3 | import 'package:cap_challenge/models/bottle.dart'; 4 | import 'package:cap_challenge/widgets/collection/collection_grid_item.dart'; 5 | import 'package:cap_challenge/widgets/collection/ticket_page.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter_redux/flutter_redux.dart'; 8 | 9 | class CollectionPage extends StatefulWidget { 10 | 11 | 12 | CollectionPage(); 13 | 14 | @override 15 | State createState() { 16 | return new CollectionPageState(); 17 | } 18 | } 19 | 20 | class CollectionPageState extends State 21 | with SingleTickerProviderStateMixin { 22 | TabController _tabController; 23 | 24 | @override 25 | void initState() { 26 | super.initState(); 27 | _tabController = new TabController(length: 2, vsync: this); 28 | } 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | return new StoreConnector( 33 | converter: (store) { 34 | return new _ViewModel(bottleCollection: store.state.collection, 35 | numberOfTickets: store.state.tickets); 36 | }, 37 | builder: (BuildContext context, _ViewModel vm) { 38 | return new Column( 39 | children: [ 40 | new TabBar( 41 | tabs: [ 42 | new Tab( 43 | child: new Text( 44 | S 45 | .of(context) 46 | .caps, 47 | style: new TextStyle(color: Colors.red), 48 | ), 49 | ), 50 | new Tab( 51 | child: new Text( 52 | S 53 | .of(context) 54 | .tickets, 55 | style: new TextStyle(color: Colors.red), 56 | ), 57 | ), 58 | ], 59 | controller: _tabController, 60 | ), 61 | new Expanded( 62 | child: new TabBarView( 63 | children: [ 64 | _buildCapsView(vm), 65 | _buildTicketsView(vm), 66 | ], 67 | controller: _tabController, 68 | ), 69 | ), 70 | ], 71 | ); 72 | }, 73 | ); 74 | } 75 | 76 | Widget _buildCapsView(_ViewModel vm) { 77 | if (vm.bottleCollection.values 78 | .where((quantity) => quantity > 0) 79 | .isEmpty) { 80 | return new Center( 81 | child: new Text( 82 | S 83 | .of(context) 84 | .addFirstCode, 85 | textAlign: TextAlign.center, 86 | ), 87 | ); 88 | } 89 | return new GridView.count( 90 | padding: new EdgeInsets.all(8.0), 91 | crossAxisCount: 3, 92 | children: vm.bottleCollection.keys.map((bottle) { 93 | return new CollectionGridItem(bottle, vm.bottleCollection[bottle]); 94 | }).toList(), 95 | ); 96 | } 97 | 98 | Widget _buildTicketsView(_ViewModel vm) { 99 | if (vm.numberOfTickets == 0) { 100 | return new Center( 101 | child: new Text( 102 | S 103 | .of(context) 104 | .addFirstChallenge, 105 | textAlign: TextAlign.center, 106 | ), 107 | ); 108 | } 109 | return new ListView.builder( 110 | itemBuilder: (context, index) { 111 | return new Padding( 112 | padding: const EdgeInsets.all(8.0), 113 | child: new GestureDetector( 114 | onTap: () => 115 | Navigator.of(context).push( 116 | new MaterialPageRoute( 117 | builder: (context) => new TicketPage())), 118 | child: new Image.asset( 119 | "images/ticket.jpg", 120 | fit: BoxFit.fitWidth, 121 | ), 122 | ), 123 | ); 124 | }, 125 | itemCount: vm.numberOfTickets, 126 | ); 127 | } 128 | } 129 | 130 | class _ViewModel { 131 | final Map bottleCollection; 132 | final int numberOfTickets; 133 | 134 | _ViewModel({@required this.bottleCollection, @required this.numberOfTickets}); 135 | } 136 | -------------------------------------------------------------------------------- /lib/widgets/collection/ticket_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:cap_challenge/generated/i18n.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:qr_flutter/qr_flutter.dart'; 4 | 5 | class TicketPage extends StatelessWidget { 6 | @override 7 | Widget build(BuildContext context) { 8 | return new Scaffold( 9 | body: new Stack( 10 | children: [ 11 | new Image.asset( 12 | "images/ticket-vertical.jpg", 13 | height: double.infinity, 14 | width: double.infinity, 15 | fit: BoxFit.cover, 16 | ), 17 | new Center( 18 | child: new Column( 19 | mainAxisAlignment: MainAxisAlignment.center, 20 | children: [ 21 | new QrImage( 22 | data: S 23 | .of(context) 24 | .ticketPlaceholder, 25 | size: 220.0, 26 | backgroundColor: Colors.white.withAlpha(200), 27 | ), 28 | ], 29 | ), 30 | ), 31 | new Positioned.fill( 32 | bottom: null, 33 | child: new AppBar( 34 | backgroundColor: Colors.red.withAlpha(50), 35 | elevation: 8.0, 36 | title: new Text(S 37 | .of(context) 38 | .yourTicket), 39 | ), 40 | ), 41 | new Positioned.fill( 42 | top: null, 43 | child: new Container( 44 | color: Colors.white.withAlpha(200), 45 | child: new Padding( 46 | padding: const EdgeInsets.all(8.0), 47 | child: new Text( 48 | S 49 | .of(context) 50 | .showTicketMsg, 51 | textAlign: TextAlign.center, 52 | ), 53 | )), 54 | ) 55 | ], 56 | fit: StackFit.passthrough, 57 | ), 58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/widgets/daily_challenge/timer_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:math' as math; 3 | 4 | import 'package:cap_challenge/generated/i18n.dart'; 5 | import 'package:cap_challenge/logic/app_state.dart'; 6 | import 'package:firebase_auth/firebase_auth.dart'; 7 | import 'package:firebase_database/firebase_database.dart'; 8 | import 'package:flutter/animation.dart'; 9 | import 'package:flutter/material.dart'; 10 | import 'package:flutter_redux/flutter_redux.dart'; 11 | import 'package:sensors/sensors.dart'; 12 | 13 | class TimerPage extends StatefulWidget { 14 | @override 15 | TimerPageState createState() { 16 | return new TimerPageState(); 17 | } 18 | } 19 | 20 | class TimerPageState extends State with TickerProviderStateMixin { 21 | AnimationController _animationController; 22 | AnimationController _capAnimationController; 23 | Animation _bubblesFlowAnimation; 24 | double baseHeight = 800.0; 25 | 26 | Timer timer; 27 | StreamSubscription subscription; 28 | 29 | //shaker: 30 | DateTime lastUpdate = new DateTime.now(); 31 | DateTime lastShake = new DateTime.now().subtract(Duration(seconds: 3)); 32 | final double shakeThreshold = 800.0; 33 | double x, y, z; 34 | int maxCounter = 10; 35 | 36 | double fillPercentage(int counter) => math.max(0.07, counter / maxCounter); 37 | 38 | @override 39 | void initState() { 40 | super.initState(); 41 | _animationController = new AnimationController( 42 | duration: new Duration(seconds: 15), 43 | vsync: this, 44 | ); 45 | _bubblesFlowAnimation = new Tween( 46 | begin: -baseHeight * 2, 47 | end: 0.0, 48 | ).animate(new CurvedAnimation( 49 | parent: _animationController, 50 | curve: Curves.linear, 51 | )); 52 | _bubblesFlowAnimation.addStatusListener((status) { 53 | if (status == AnimationStatus.completed) { 54 | _animationController.reset(); 55 | } else if (status == AnimationStatus.dismissed) { 56 | _animationController.forward(); 57 | } 58 | }); 59 | _animationController.forward(); 60 | 61 | subscription = accelerometerEvents.listen(_onAccelerometerEvent); 62 | 63 | timer = new Timer.periodic( 64 | Duration(seconds: 1), 65 | (Timer t) => setState(() {}), 66 | ); 67 | 68 | _capAnimationController = new AnimationController( 69 | vsync: this, 70 | duration: new Duration(milliseconds: 1000), 71 | ); 72 | } 73 | 74 | @override 75 | Widget build(BuildContext context) { 76 | return new StoreConnector( 77 | converter: (store) { 78 | return new _ViewModel( 79 | counter: store.state.counter, 80 | ); 81 | }, 82 | builder: (BuildContext context, _ViewModel vm) { 83 | return new GestureDetector( 84 | onTap: () => _capAnimationController.reset(), 85 | child: new Stack( 86 | overflow: Overflow.visible, 87 | fit: StackFit.expand, 88 | alignment: Alignment.bottomCenter, 89 | children: _buildStackChildren(vm), 90 | ), 91 | ); 92 | }, 93 | ); 94 | } 95 | 96 | List _buildStackChildren(_ViewModel vm) { 97 | List children = []; 98 | if (MediaQuery 99 | .of(context) 100 | .orientation == Orientation.portrait) { 101 | children.addAll([ 102 | _buildBottleFilling(), 103 | _buildProgressIndicatorContainer(vm.counter), 104 | _buildBottleOutline(), 105 | ]); 106 | } else { 107 | children.add(new Center( 108 | child: new Text( 109 | "${vm.counter * 100 ~/ maxCounter}%", 110 | style: Theme 111 | .of(context) 112 | .textTheme 113 | .display4, 114 | ), 115 | )); 116 | } 117 | children.addAll([ 118 | _buildTopCaption(), 119 | _buildBottomCaption(vm.counter), 120 | _buildAnimatedCap(), 121 | ]); 122 | return children; 123 | } 124 | 125 | @override 126 | void dispose() { 127 | _animationController.dispose(); 128 | timer.cancel(); 129 | subscription.cancel(); 130 | super.dispose(); 131 | } 132 | 133 | _addPoints(int points) async { 134 | FirebaseUser user = await FirebaseAuth.instance.currentUser(); 135 | FirebaseDatabase.instance 136 | .reference() 137 | .child("users/${user.uid}/points") 138 | .runTransaction((MutableData data) async { 139 | int pts = (data.value ?? 0) + points; 140 | return data..value = pts; 141 | }); 142 | _capAnimationController.forward(); 143 | } 144 | 145 | _increaseCounter() { 146 | FirebaseDatabase.instance 147 | .reference() 148 | .child('counter') 149 | .runTransaction((MutableData data) async { 150 | int counter = (data.value ?? 0) + 1; 151 | if (counter % maxCounter == 0) { 152 | _addPoints(100); 153 | } 154 | return data..value = counter; 155 | }); 156 | } 157 | 158 | _onAccelerometerEvent(AccelerometerEvent event) { 159 | DateTime current = new DateTime.now(); 160 | int diff = current 161 | .difference(lastUpdate) 162 | .inMilliseconds; 163 | if (diff > 100) { 164 | lastUpdate = current; 165 | 166 | double speed = ((x ?? event.x) + 167 | (y ?? event.y) + 168 | (z ?? event.z) - 169 | event.x - 170 | event.y - 171 | event.z) 172 | .abs() / 173 | diff * 174 | 10000; 175 | 176 | if (speed > shakeThreshold) { 177 | _onShakeEvent(speed); 178 | } 179 | setState(() { 180 | x = event.x; 181 | y = event.y; 182 | z = event.z; 183 | }); 184 | } 185 | } 186 | 187 | _onShakeEvent(double speed) { 188 | if (_canShake()) { 189 | _increaseCounter(); 190 | lastShake = new DateTime.now(); 191 | } 192 | } 193 | 194 | Positioned _buildBottomCaption(int counter) { 195 | return new Positioned( 196 | left: 0.0, 197 | right: 0.0, 198 | bottom: 8.0, 199 | child: new Text( 200 | S.of(context).bubblesExplanation( 201 | counter.toString(), maxCounter.toString()), 202 | textAlign: TextAlign.center, 203 | style: Theme.of(context).textTheme.caption, 204 | ), 205 | ); 206 | } 207 | 208 | Transform _buildProgressIndicatorContainer(int counter) { 209 | return new Transform( 210 | transform: new Matrix4.identity()..scale(0.75), 211 | alignment: Alignment.center, 212 | child: new Container( 213 | transform: new Matrix4.identity() 214 | ..scale(1.0, 1 - fillPercentage(counter)), 215 | color: const Color(0xFFFAFAFA), 216 | ), 217 | ); 218 | } 219 | 220 | AnimatedBuilder _buildBottleFilling() { 221 | return new AnimatedBuilder( 222 | animation: _bubblesFlowAnimation, 223 | builder: (context, child) { 224 | return new Positioned( 225 | bottom: _bubblesFlowAnimation.value, 226 | child: new Container( 227 | width: 130.0, 228 | height: 3 * baseHeight, 229 | child: new Image.asset( 230 | "images/bubbles_long.jpg", 231 | fit: BoxFit.cover, 232 | ), 233 | ), 234 | ); 235 | }, 236 | ); 237 | } 238 | 239 | Image _buildBottleOutline() { 240 | return new Image.asset( 241 | "images/challenge_bottle.png", 242 | fit: BoxFit.fill, 243 | ); 244 | } 245 | 246 | Positioned _buildTopCaption() { 247 | String text = _canShake() 248 | ? S 249 | .of(context) 250 | .shakeForBubble 251 | : S.of(context).nextBubbleIn((5 - _shakeDiff()).toString()); 252 | return new Positioned( 253 | child: new Text(text), 254 | top: 18.0, 255 | ); 256 | } 257 | 258 | int _shakeDiff() { 259 | return new DateTime.now() 260 | .difference(lastShake) 261 | .inSeconds; 262 | } 263 | 264 | bool _canShake() { 265 | return _shakeDiff() >= 5; 266 | } 267 | 268 | Widget _buildAnimatedCap() { 269 | return new Align( 270 | alignment: Alignment.topCenter, 271 | child: new AnimatedBuilder( 272 | animation: _capAnimationController, 273 | builder: (context, child) { 274 | double angleX = math.pi * 6 * _capAnimationController.value; 275 | bool showFront = (angleX / math.pi) % 2 < 1; 276 | return new Padding( 277 | padding: const EdgeInsets.only(top: 80.0), 278 | child: new Transform( 279 | transform: new Matrix4.identity() 280 | ..rotateX(angleX), 281 | alignment: Alignment.center, 282 | child: new Container( 283 | child: new Stack( 284 | alignment: Alignment.center, 285 | children: [ 286 | new Image.asset( 287 | showFront ? "images/cap.png" : "images/cap_reverse.png", 288 | fit: BoxFit.fill, 289 | ), 290 | _capAnimationController.isCompleted 291 | ? new Text( 292 | S 293 | .of(context) 294 | .bubbleWinMessage, 295 | textAlign: TextAlign.center, 296 | ) 297 | : new Container(), 298 | ], 299 | ), 300 | height: 260.0 * _capAnimationController.value, 301 | width: 260.0 * _capAnimationController.value, 302 | decoration: new ShapeDecoration( 303 | color: Colors.red, 304 | shape: new CircleBorder(), 305 | ), 306 | ), 307 | ), 308 | ); 309 | }, 310 | ), 311 | ); 312 | } 313 | } 314 | 315 | class _ViewModel { 316 | final int counter; 317 | 318 | _ViewModel({this.counter}); 319 | } 320 | -------------------------------------------------------------------------------- /lib/widgets/login/login_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:cap_challenge/generated/i18n.dart'; 2 | import 'package:cap_challenge/logic/actions.dart'; 3 | import 'package:cap_challenge/logic/app_state.dart'; 4 | import 'package:cap_challenge/logic/auth_service.dart'; 5 | import 'package:firebase_auth/firebase_auth.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter_redux/flutter_redux.dart'; 8 | import 'package:meta/meta.dart'; 9 | 10 | class LoginPage extends StatelessWidget { 11 | @override 12 | Widget build(BuildContext context) { 13 | return new StoreConnector( 14 | converter: (store) { 15 | return new _ViewModel( 16 | onUserLoggedIn: (user) => 17 | store.dispatch(new UserProvidedAction(user)) 18 | ); 19 | }, 20 | builder: (BuildContext context, _ViewModel vm) { 21 | return new Stack( 22 | children: [ 23 | new Container( 24 | decoration: new BoxDecoration( 25 | gradient: new RadialGradient(colors: [ 26 | Colors.red, 27 | Colors.orange, 28 | ], radius: 1.0), 29 | ), 30 | ), 31 | new Align( 32 | alignment: Alignment.bottomCenter, 33 | child: new Image.asset( 34 | "images/coca_family.png", 35 | fit: BoxFit.fill, 36 | )), 37 | buildShade(context), 38 | new Center( 39 | child: new Column( 40 | children: [ 41 | new Padding( 42 | padding: 43 | const EdgeInsets.symmetric(vertical: 72.0, horizontal: 8.0), 44 | child: new Text( 45 | "Cap Challenge", 46 | style: Theme 47 | .of(context) 48 | .textTheme 49 | .display2 50 | .copyWith( 51 | color: Colors.white, 52 | fontFamily: "Marker", 53 | decorationColor: Colors.black, 54 | decorationStyle: TextDecorationStyle.dotted, 55 | ), 56 | ), 57 | ), 58 | new OAuthLoginButton( 59 | onPressed: () => _loginWithGoogle(context, vm), 60 | text: S 61 | .of(context) 62 | .loginWithGoogle, 63 | assetName: "images/sign-in-google.png", 64 | backgroundColor: Colors.red, 65 | ), 66 | new Padding( 67 | padding: const EdgeInsets.only(top: 16.0), 68 | child: new OAuthLoginButton( 69 | onPressed: () => _loginWithFacebook(context, vm), 70 | text: S 71 | .of(context) 72 | .loginWithFacebook, 73 | assetName: "images/sign-in-facebook.png", 74 | backgroundColor: Colors.blue[700], 75 | ), 76 | ), 77 | ], 78 | ), 79 | ) 80 | ], 81 | ); 82 | }, 83 | ); 84 | } 85 | 86 | Widget buildShade(BuildContext context) { 87 | if (MediaQuery.of(context).orientation == Orientation.portrait) { 88 | return new Container(); 89 | } else { 90 | return new Positioned.fill( 91 | child: new DecoratedBox( 92 | decoration: new BoxDecoration( 93 | gradient: new LinearGradient( 94 | colors: [Colors.red.withAlpha(120), Colors.transparent], 95 | begin: Alignment.topCenter, 96 | end: Alignment.bottomCenter, 97 | ), 98 | ), 99 | ), 100 | ); 101 | } 102 | } 103 | 104 | _loginWithFacebook(BuildContext context, _ViewModel vm) async { 105 | FirebaseUser user = await AuthService.instance.logInWithFacebook(); 106 | if (user != null) { 107 | vm.onUserLoggedIn(user); 108 | Navigator.of(context).pushReplacementNamed("main"); 109 | } 110 | } 111 | 112 | _loginWithGoogle(BuildContext context, _ViewModel vm) async { 113 | FirebaseUser user = await AuthService.instance.loginWithGoogle(); 114 | if (user != null) { 115 | vm.onUserLoggedIn(user); 116 | Navigator.of(context).pushReplacementNamed("main"); 117 | } 118 | } 119 | } 120 | 121 | class _ViewModel { 122 | final Function(FirebaseUser) onUserLoggedIn; 123 | 124 | _ViewModel({@required this.onUserLoggedIn}); 125 | } 126 | 127 | class OAuthLoginButton extends StatelessWidget { 128 | final Function() onPressed; 129 | final String text; 130 | final String assetName; 131 | final Color backgroundColor; 132 | 133 | OAuthLoginButton({@required this.onPressed, 134 | @required this.text, 135 | @required this.assetName, 136 | @required this.backgroundColor}); 137 | 138 | @override 139 | Widget build(BuildContext context) { 140 | return new Container( 141 | width: 240.0, 142 | child: new RaisedButton( 143 | color: backgroundColor, 144 | onPressed: onPressed, 145 | padding: new EdgeInsets.only(right: 8.0), 146 | child: new Row( 147 | children: [ 148 | new Image.asset( 149 | assetName, 150 | height: 36.0, 151 | ), 152 | new Expanded( 153 | child: new Padding( 154 | padding: const EdgeInsets.only(left: 8.0), 155 | child: new Text( 156 | text, 157 | style: Theme 158 | .of(context) 159 | .textTheme 160 | .button 161 | .copyWith(color: Colors.white), 162 | ), 163 | )), 164 | ], 165 | ), 166 | ), 167 | ); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /lib/widgets/main_scaffold.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:cap_challenge/generated/i18n.dart'; 4 | import 'package:cap_challenge/logic/app_state.dart'; 5 | import 'package:cap_challenge/logic/auth_service.dart'; 6 | import 'package:cap_challenge/models/add_code_result.dart'; 7 | import 'package:cap_challenge/widgets/challenges/challenges_page.dart'; 8 | import 'package:cap_challenge/widgets/code_cap.dart'; 9 | import 'package:cap_challenge/widgets/collection/collection_page.dart'; 10 | import 'package:cap_challenge/widgets/daily_challenge/timer_page.dart'; 11 | import 'package:cap_challenge/widgets/points_indicator.dart'; 12 | import 'package:cap_challenge/widgets/profile_dialog.dart'; 13 | import 'package:cap_challenge/widgets/ranking/ranking_page.dart'; 14 | import 'package:firebase_database/firebase_database.dart'; 15 | import 'package:flutter/material.dart'; 16 | import 'package:flutter_redux/flutter_redux.dart'; 17 | 18 | class MainScaffold extends StatefulWidget { 19 | @override 20 | State createState() { 21 | return new MainScaffoldState(); 22 | } 23 | } 24 | 25 | class MainScaffoldState extends State 26 | with SingleTickerProviderStateMixin { 27 | DatabaseReference userRef = FirebaseDatabase.instance 28 | .reference() 29 | .child('users') 30 | .child(AuthService.instance.currentUser.uid); 31 | bool _showFab = true; 32 | bool _isCapOpened = false; 33 | int _page = 0; 34 | 35 | Widget _buildBody() { 36 | switch (_page) { 37 | case 0: 38 | return new TimerPage(); 39 | case 1: 40 | return new CollectionPage(); 41 | case 2: 42 | return new ChallengesPage(); 43 | case 3: 44 | return new RankingPage(); 45 | default: 46 | return new TimerPage(); 47 | } 48 | } 49 | 50 | Widget _buildFab(BuildContext context) { 51 | switch (_page) { 52 | case 1: 53 | return _createHeroFab(context); 54 | default: 55 | return new Container(); 56 | } 57 | } 58 | 59 | @override 60 | Widget build(BuildContext context) { 61 | return new StoreConnector( 62 | converter: (store) { 63 | return new _ViewModel(points: store.state.points); 64 | }, 65 | builder: (BuildContext context, _ViewModel vm) { 66 | return new Scaffold( 67 | appBar: new AppBar( 68 | elevation: 0.0, 69 | title: new Text("Cap challenge"), 70 | actions: [ 71 | new Center( 72 | child: new PointsIndicator( 73 | actualPoints: vm.points, 74 | ), 75 | ), 76 | new GestureDetector( 77 | child: new Padding( 78 | padding: const EdgeInsets.all(6.0), 79 | child: new CircleAvatar( 80 | radius: 22.0, 81 | backgroundImage: new NetworkImage( 82 | AuthService.instance.currentUser.photoUrl), 83 | ), 84 | ), 85 | onTap: () { 86 | showDialog( 87 | context: context, 88 | builder: (context) => new ProfileDialog(), 89 | ); 90 | }, 91 | ), 92 | ], 93 | ), 94 | body: new Material( 95 | elevation: 0.0, 96 | shape: RoundedRectangleBorder( 97 | borderRadius: 98 | new BorderRadius.vertical(top: Radius.circular(24.0)), 99 | ), 100 | child: _buildBody(), 101 | ), 102 | backgroundColor: Colors.red, 103 | bottomNavigationBar: new BottomNavigationBar( 104 | items: [ 105 | new BottomNavigationBarItem( 106 | backgroundColor: Colors.red, 107 | icon: new Icon(Icons.whatshot), 108 | title: new Text(S 109 | .of(context) 110 | .dailyChallenge), 111 | ), 112 | new BottomNavigationBarItem( 113 | backgroundColor: Colors.red, 114 | icon: new Icon(Icons.view_module), 115 | title: new Text(S 116 | .of(context) 117 | .collection), 118 | ), 119 | new BottomNavigationBarItem( 120 | backgroundColor: Colors.red, 121 | icon: new Icon(Icons.star), 122 | title: new Text(S 123 | .of(context) 124 | .challenges), 125 | ), 126 | new BottomNavigationBarItem( 127 | backgroundColor: Colors.red, 128 | icon: new Icon(Icons.swap_vert), 129 | title: new Text(S 130 | .of(context) 131 | .ranking), 132 | ), 133 | ], 134 | type: BottomNavigationBarType.fixed, 135 | onTap: _navigationTapped, 136 | currentIndex: _page, 137 | ), 138 | floatingActionButton: new Builder( 139 | builder: (BuildContext context) => _buildFab(context), 140 | ), 141 | ); 142 | }, 143 | ); 144 | } 145 | 146 | void _navigationTapped(int page) { 147 | setState(() => this._page = page); 148 | } 149 | 150 | Widget _createHeroFab(BuildContext context) { 151 | return new Hero( 152 | tag: "fab-cap", 153 | child: _showFab ? _createActualFab(context) : new Container(), 154 | ); 155 | } 156 | 157 | Widget _createActualFab(BuildContext context) { 158 | return new FloatingActionButton( 159 | child: new Icon(Icons.add), 160 | tooltip: S 161 | .of(context) 162 | .addCodeTooltip, 163 | onPressed: () { 164 | _pushCapView(context).then((dynamic result) { 165 | _onCapClosed(); 166 | if (result is ScannedQRCodeResult) { 167 | _handleQrCode(result, context); 168 | } else if (result is AddedCodeResult) { 169 | if (result.isOk) { 170 | Scaffold.of(context).showSnackBar( 171 | new SnackBar(content: new Text(S 172 | .of(context) 173 | .codeAddedMsg))); 174 | } 175 | } 176 | }); 177 | _onCapOpened(); 178 | }, 179 | ); 180 | } 181 | 182 | Future _pushCapView(BuildContext context) { 183 | return Navigator.of(context).push( 184 | new HeroDialogRoute( 185 | builder: (BuildContext context) { 186 | return new Center( 187 | child: new CodeCap(), 188 | ); 189 | }, 190 | ), 191 | ); 192 | } 193 | 194 | void _handleQrCode(ScannedQRCodeResult result, BuildContext context) { 195 | Scaffold.of(context).showSnackBar(new SnackBar( 196 | content: new Text(result.qrCode), 197 | )); 198 | } 199 | 200 | _onCapClosed() { 201 | setState(() => _isCapOpened = false); 202 | new Future.delayed( 203 | const Duration(milliseconds: 5), 204 | () => setState(() => _showFab = true), 205 | ); 206 | } 207 | 208 | _onCapOpened() { 209 | setState(() => _isCapOpened = true); 210 | new Future.delayed( 211 | const Duration(milliseconds: 300), 212 | () => setState( 213 | () { 214 | if (_isCapOpened) { 215 | _showFab = false; 216 | } 217 | }, 218 | ), 219 | ); 220 | } 221 | } 222 | 223 | class _ViewModel { 224 | final int points; 225 | 226 | _ViewModel({@required this.points}); 227 | } 228 | -------------------------------------------------------------------------------- /lib/widgets/points_indicator.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:cap_challenge/generated/i18n.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | class PointsIndicator extends StatefulWidget { 7 | final int actualPoints; 8 | 9 | const PointsIndicator({Key key, this.actualPoints}) : super(key: key); 10 | 11 | @override 12 | _PoinstIndicatorState createState() => _PoinstIndicatorState(); 13 | } 14 | 15 | class _PoinstIndicatorState extends State { 16 | int displayPoints; 17 | Timer timer; 18 | 19 | @override 20 | void initState() { 21 | super.initState(); 22 | displayPoints = widget.actualPoints; 23 | int oneTickMillis = 20; 24 | timer = 25 | new Timer.periodic(new Duration(milliseconds: oneTickMillis), (timer) { 26 | int diff = widget.actualPoints - displayPoints; 27 | if (diff > 0) { 28 | setState(() { 29 | if (diff > 100) { 30 | displayPoints += (0.1 * diff).round(); 31 | } else if (diff > 20) { 32 | displayPoints += 3; 33 | } else { 34 | displayPoints += 1; 35 | } 36 | }); 37 | } else { 38 | setState(() { 39 | displayPoints = widget.actualPoints; 40 | }); 41 | } 42 | }); 43 | } 44 | 45 | @override 46 | void dispose() { 47 | timer.cancel(); 48 | super.dispose(); 49 | } 50 | 51 | @override 52 | Widget build(BuildContext context) { 53 | return Row( 54 | children: [ 55 | new Text( 56 | "$displayPoints", 57 | style: new TextStyle(fontSize: 22.0, fontWeight: FontWeight.w500), 58 | ), 59 | new Text(S 60 | .of(context) 61 | .pts), 62 | ], 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lib/widgets/profile_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:cap_challenge/generated/i18n.dart'; 2 | import 'package:cap_challenge/logic/actions.dart'; 3 | import 'package:cap_challenge/logic/app_state.dart'; 4 | import 'package:cap_challenge/logic/auth_service.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter_redux/flutter_redux.dart'; 7 | class ProfileDialog extends StatelessWidget { 8 | 9 | 10 | const ProfileDialog({Key key}) 11 | : super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return new StoreConnector( 16 | converter: (store) { 17 | return new _ViewModel( 18 | points: store.state.points, 19 | tickets: store.state.tickets, 20 | rankingPlace: store.state.usersRanking.indexWhere((user) => 21 | user.email == 22 | AuthService.instance.currentUser.email) + 23 | 1, 24 | onLogout: () => store.dispatch(new UserProvidedAction(null)), 25 | ); 26 | }, 27 | builder: (BuildContext context, _ViewModel vm) { 28 | return new AlertDialog( 29 | title: new Center(child: new Text(S 30 | .of(context) 31 | .userProfile)), 32 | content: new SingleChildScrollView( 33 | child: new Column( 34 | mainAxisSize: MainAxisSize.min, 35 | children: [ 36 | new CircleAvatar( 37 | radius: 36.0, 38 | backgroundImage: 39 | new NetworkImage(AuthService.instance.currentUser.photoUrl), 40 | ), 41 | new Padding( 42 | padding: const EdgeInsets.all(8.0), 43 | child: new Text( 44 | AuthService.instance.currentUser.displayName, 45 | style: new TextStyle(fontSize: 22.0), 46 | ), 47 | ), 48 | new Text( 49 | AuthService.instance.currentUser.email, 50 | style: new TextStyle(fontSize: 14.0), 51 | ), 52 | new Divider(), 53 | new Text(S.of(context).totalPoints(vm.points.toString())), 54 | new Padding( 55 | padding: const EdgeInsets.all(8.0), 56 | child: new Text( 57 | S.of(context).totalTickets(vm.tickets.toString())), 58 | ), 59 | new Text( 60 | S.of(context).placeInRanking(vm.rankingPlace.toString())), 61 | ], 62 | ), 63 | ), 64 | actions: [ 65 | new FlatButton( 66 | onPressed: () { 67 | AuthService.instance.logout().then((_) { 68 | Navigator.of(context).pop(); 69 | Navigator.of(context).pushReplacementNamed("login"); 70 | vm.onLogout(); 71 | }); 72 | }, 73 | child: new Text(S 74 | .of(context) 75 | .logout 76 | .toUpperCase()), 77 | textColor: Colors.red, 78 | ), 79 | new FlatButton( 80 | onPressed: () => Navigator.of(context).pop(), 81 | child: new Text(S 82 | .of(context) 83 | .close 84 | .toUpperCase()), 85 | textColor: Colors.red, 86 | ) 87 | ], 88 | ); 89 | }, 90 | ); 91 | } 92 | } 93 | 94 | class _ViewModel { 95 | final int points; 96 | final int tickets; 97 | final int rankingPlace; 98 | final Function onLogout; 99 | 100 | _ViewModel({@required this.points, @required this.tickets, @required this.rankingPlace, @required this.onLogout}); 101 | } 102 | -------------------------------------------------------------------------------- /lib/widgets/ranking/ranking_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:cap_challenge/logic/app_state.dart'; 2 | import 'package:cap_challenge/models/user.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_redux/flutter_redux.dart'; 5 | 6 | class RankingPage extends StatelessWidget { 7 | const RankingPage({Key key}) : super(key: key); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return new StoreConnector( 12 | converter: (store) { 13 | return new _ViewModel( 14 | ranking: store.state.usersRanking, 15 | ); 16 | }, 17 | builder: (BuildContext context, _ViewModel vm) { 18 | return new ListView.builder( 19 | padding: new EdgeInsets.only(top: 8.0), 20 | itemCount: vm.ranking.length, 21 | itemBuilder: (context, index) { 22 | User user = vm.ranking[index]; 23 | Color color = getColor(index); 24 | FontWeight fontWeight = getFontWeight(index); 25 | double size = getFontSize(index); 26 | 27 | TextStyle style = new TextStyle( 28 | color: color, 29 | fontWeight: fontWeight, 30 | fontSize: size, 31 | ); 32 | 33 | return new ListTile( 34 | leading: Padding( 35 | padding: const EdgeInsets.only(left: 8.0), 36 | child: new Text( 37 | "${index + 1}", 38 | style: style, 39 | ), 40 | ), 41 | title: new Text( 42 | user.name, 43 | style: style, 44 | ), 45 | trailing: new Text( 46 | "${user.points}p", 47 | style: style, 48 | ), 49 | ); 50 | }, 51 | ); 52 | }, 53 | ); 54 | } 55 | 56 | Color getColor(int index) { 57 | switch (index) { 58 | case 0: 59 | return Colors.yellow[800]; 60 | case 1: 61 | return Colors.grey[500]; 62 | case 2: 63 | return Colors.brown; 64 | default: 65 | return Colors.black; 66 | } 67 | } 68 | 69 | double getFontSize(int index) { 70 | switch (index) { 71 | case 0: 72 | return 22.0; 73 | case 1: 74 | return 20.0; 75 | case 2: 76 | return 18.0; 77 | default: 78 | return 16.0; 79 | } 80 | } 81 | 82 | FontWeight getFontWeight(int index) { 83 | switch (index) { 84 | case 0: 85 | return FontWeight.w700; 86 | case 1: 87 | return FontWeight.w600; 88 | case 2: 89 | return FontWeight.w500; 90 | default: 91 | return FontWeight.normal; 92 | } 93 | } 94 | } 95 | 96 | class _ViewModel { 97 | final List ranking; 98 | 99 | _ViewModel({this.ranking}); 100 | } 101 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://www.dartlang.org/tools/pub/glossary#lockfile 3 | packages: 4 | analyzer: 5 | dependency: transitive 6 | description: 7 | name: analyzer 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "0.31.2-alpha.2" 11 | args: 12 | dependency: transitive 13 | description: 14 | name: args 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "1.4.3" 18 | async: 19 | dependency: transitive 20 | description: 21 | name: async 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.0.7" 25 | boolean_selector: 26 | dependency: transitive 27 | description: 28 | name: boolean_selector 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.0.3" 32 | charcode: 33 | dependency: transitive 34 | description: 35 | name: charcode 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.1" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.14.6" 46 | convert: 47 | dependency: transitive 48 | description: 49 | name: convert 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "2.0.1" 53 | crypto: 54 | dependency: transitive 55 | description: 56 | name: crypto 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "2.0.3" 60 | csslib: 61 | dependency: transitive 62 | description: 63 | name: csslib 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "0.14.4" 67 | cupertino_icons: 68 | dependency: "direct main" 69 | description: 70 | name: cupertino_icons 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "0.1.2" 74 | firebase_auth: 75 | dependency: "direct main" 76 | description: 77 | name: firebase_auth 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "0.5.10" 81 | firebase_core: 82 | dependency: transitive 83 | description: 84 | name: firebase_core 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "0.2.3" 88 | firebase_database: 89 | dependency: "direct main" 90 | description: 91 | name: firebase_database 92 | url: "https://pub.dartlang.org" 93 | source: hosted 94 | version: "1.0.0" 95 | flutter: 96 | dependency: "direct main" 97 | description: flutter 98 | source: sdk 99 | version: "0.0.0" 100 | flutter_facebook_login: 101 | dependency: "direct main" 102 | description: 103 | name: flutter_facebook_login 104 | url: "https://pub.dartlang.org" 105 | source: hosted 106 | version: "1.1.1" 107 | flutter_localizations: 108 | dependency: "direct main" 109 | description: flutter 110 | source: sdk 111 | version: "0.0.0" 112 | flutter_redux: 113 | dependency: "direct main" 114 | description: 115 | name: flutter_redux 116 | url: "https://pub.dartlang.org" 117 | source: hosted 118 | version: "0.5.2" 119 | flutter_test: 120 | dependency: "direct dev" 121 | description: flutter 122 | source: sdk 123 | version: "0.0.0" 124 | front_end: 125 | dependency: transitive 126 | description: 127 | name: front_end 128 | url: "https://pub.dartlang.org" 129 | source: hosted 130 | version: "0.1.0-alpha.12" 131 | glob: 132 | dependency: transitive 133 | description: 134 | name: glob 135 | url: "https://pub.dartlang.org" 136 | source: hosted 137 | version: "1.1.5" 138 | google_sign_in: 139 | dependency: "direct main" 140 | description: 141 | name: google_sign_in 142 | url: "https://pub.dartlang.org" 143 | source: hosted 144 | version: "3.0.3+1" 145 | html: 146 | dependency: transitive 147 | description: 148 | name: html 149 | url: "https://pub.dartlang.org" 150 | source: hosted 151 | version: "0.13.3" 152 | http: 153 | dependency: transitive 154 | description: 155 | name: http 156 | url: "https://pub.dartlang.org" 157 | source: hosted 158 | version: "0.11.3+16" 159 | http_multi_server: 160 | dependency: transitive 161 | description: 162 | name: http_multi_server 163 | url: "https://pub.dartlang.org" 164 | source: hosted 165 | version: "2.0.4" 166 | http_parser: 167 | dependency: transitive 168 | description: 169 | name: http_parser 170 | url: "https://pub.dartlang.org" 171 | source: hosted 172 | version: "3.1.2" 173 | intl: 174 | dependency: transitive 175 | description: 176 | name: intl 177 | url: "https://pub.dartlang.org" 178 | source: hosted 179 | version: "0.15.6" 180 | io: 181 | dependency: transitive 182 | description: 183 | name: io 184 | url: "https://pub.dartlang.org" 185 | source: hosted 186 | version: "0.3.2+1" 187 | js: 188 | dependency: transitive 189 | description: 190 | name: js 191 | url: "https://pub.dartlang.org" 192 | source: hosted 193 | version: "0.6.1" 194 | kernel: 195 | dependency: transitive 196 | description: 197 | name: kernel 198 | url: "https://pub.dartlang.org" 199 | source: hosted 200 | version: "0.3.0-alpha.12" 201 | logging: 202 | dependency: transitive 203 | description: 204 | name: logging 205 | url: "https://pub.dartlang.org" 206 | source: hosted 207 | version: "0.11.3+1" 208 | matcher: 209 | dependency: transitive 210 | description: 211 | name: matcher 212 | url: "https://pub.dartlang.org" 213 | source: hosted 214 | version: "0.12.2+1" 215 | meta: 216 | dependency: transitive 217 | description: 218 | name: meta 219 | url: "https://pub.dartlang.org" 220 | source: hosted 221 | version: "1.1.5" 222 | mime: 223 | dependency: transitive 224 | description: 225 | name: mime 226 | url: "https://pub.dartlang.org" 227 | source: hosted 228 | version: "0.9.6" 229 | multi_server_socket: 230 | dependency: transitive 231 | description: 232 | name: multi_server_socket 233 | url: "https://pub.dartlang.org" 234 | source: hosted 235 | version: "1.0.1" 236 | node_preamble: 237 | dependency: transitive 238 | description: 239 | name: node_preamble 240 | url: "https://pub.dartlang.org" 241 | source: hosted 242 | version: "1.4.1" 243 | package_config: 244 | dependency: transitive 245 | description: 246 | name: package_config 247 | url: "https://pub.dartlang.org" 248 | source: hosted 249 | version: "1.0.3" 250 | package_resolver: 251 | dependency: transitive 252 | description: 253 | name: package_resolver 254 | url: "https://pub.dartlang.org" 255 | source: hosted 256 | version: "1.0.2" 257 | path: 258 | dependency: transitive 259 | description: 260 | name: path 261 | url: "https://pub.dartlang.org" 262 | source: hosted 263 | version: "1.5.1" 264 | plugin: 265 | dependency: transitive 266 | description: 267 | name: plugin 268 | url: "https://pub.dartlang.org" 269 | source: hosted 270 | version: "0.2.0+2" 271 | pool: 272 | dependency: transitive 273 | description: 274 | name: pool 275 | url: "https://pub.dartlang.org" 276 | source: hosted 277 | version: "1.3.4" 278 | pub_semver: 279 | dependency: transitive 280 | description: 281 | name: pub_semver 282 | url: "https://pub.dartlang.org" 283 | source: hosted 284 | version: "1.4.1" 285 | qr: 286 | dependency: transitive 287 | description: 288 | name: qr 289 | url: "https://pub.dartlang.org" 290 | source: hosted 291 | version: "1.0.0" 292 | qr_flutter: 293 | dependency: "direct main" 294 | description: 295 | name: qr_flutter 296 | url: "https://pub.dartlang.org" 297 | source: hosted 298 | version: "1.1.1" 299 | quiver: 300 | dependency: transitive 301 | description: 302 | name: quiver 303 | url: "https://pub.dartlang.org" 304 | source: hosted 305 | version: "0.29.0+1" 306 | redux: 307 | dependency: "direct main" 308 | description: 309 | name: redux 310 | url: "https://pub.dartlang.org" 311 | source: hosted 312 | version: "3.0.0" 313 | sensors: 314 | dependency: "direct main" 315 | description: 316 | name: sensors 317 | url: "https://pub.dartlang.org" 318 | source: hosted 319 | version: "0.3.2" 320 | shelf: 321 | dependency: transitive 322 | description: 323 | name: shelf 324 | url: "https://pub.dartlang.org" 325 | source: hosted 326 | version: "0.7.3" 327 | shelf_packages_handler: 328 | dependency: transitive 329 | description: 330 | name: shelf_packages_handler 331 | url: "https://pub.dartlang.org" 332 | source: hosted 333 | version: "1.0.3" 334 | shelf_static: 335 | dependency: transitive 336 | description: 337 | name: shelf_static 338 | url: "https://pub.dartlang.org" 339 | source: hosted 340 | version: "0.2.7" 341 | shelf_web_socket: 342 | dependency: transitive 343 | description: 344 | name: shelf_web_socket 345 | url: "https://pub.dartlang.org" 346 | source: hosted 347 | version: "0.2.2" 348 | sky_engine: 349 | dependency: transitive 350 | description: flutter 351 | source: sdk 352 | version: "0.0.99" 353 | sliver_fab: 354 | dependency: "direct main" 355 | description: 356 | name: sliver_fab 357 | url: "https://pub.dartlang.org" 358 | source: hosted 359 | version: "0.1.0" 360 | source_map_stack_trace: 361 | dependency: transitive 362 | description: 363 | name: source_map_stack_trace 364 | url: "https://pub.dartlang.org" 365 | source: hosted 366 | version: "1.1.4" 367 | source_maps: 368 | dependency: transitive 369 | description: 370 | name: source_maps 371 | url: "https://pub.dartlang.org" 372 | source: hosted 373 | version: "0.10.5" 374 | source_span: 375 | dependency: transitive 376 | description: 377 | name: source_span 378 | url: "https://pub.dartlang.org" 379 | source: hosted 380 | version: "1.4.0" 381 | stack_trace: 382 | dependency: transitive 383 | description: 384 | name: stack_trace 385 | url: "https://pub.dartlang.org" 386 | source: hosted 387 | version: "1.9.2" 388 | stream_channel: 389 | dependency: transitive 390 | description: 391 | name: stream_channel 392 | url: "https://pub.dartlang.org" 393 | source: hosted 394 | version: "1.6.6" 395 | string_scanner: 396 | dependency: transitive 397 | description: 398 | name: string_scanner 399 | url: "https://pub.dartlang.org" 400 | source: hosted 401 | version: "1.0.2" 402 | term_glyph: 403 | dependency: transitive 404 | description: 405 | name: term_glyph 406 | url: "https://pub.dartlang.org" 407 | source: hosted 408 | version: "1.0.0" 409 | test: 410 | dependency: transitive 411 | description: 412 | name: test 413 | url: "https://pub.dartlang.org" 414 | source: hosted 415 | version: "0.12.37" 416 | typed_data: 417 | dependency: transitive 418 | description: 419 | name: typed_data 420 | url: "https://pub.dartlang.org" 421 | source: hosted 422 | version: "1.1.5" 423 | utf: 424 | dependency: transitive 425 | description: 426 | name: utf 427 | url: "https://pub.dartlang.org" 428 | source: hosted 429 | version: "0.9.0+4" 430 | vector_math: 431 | dependency: transitive 432 | description: 433 | name: vector_math 434 | url: "https://pub.dartlang.org" 435 | source: hosted 436 | version: "2.0.6" 437 | watcher: 438 | dependency: transitive 439 | description: 440 | name: watcher 441 | url: "https://pub.dartlang.org" 442 | source: hosted 443 | version: "0.9.7+7" 444 | web_socket_channel: 445 | dependency: transitive 446 | description: 447 | name: web_socket_channel 448 | url: "https://pub.dartlang.org" 449 | source: hosted 450 | version: "1.0.7" 451 | yaml: 452 | dependency: transitive 453 | description: 454 | name: yaml 455 | url: "https://pub.dartlang.org" 456 | source: hosted 457 | version: "2.1.13" 458 | sdks: 459 | dart: ">=2.0.0-dev.52.0 <=2.0.0-dev.58.0.flutter-f981f09760" 460 | flutter: ">=0.1.4 <2.0.0" 461 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: cap_challenge 2 | description: Cap Challenge - school project. 3 | 4 | dependencies: 5 | flutter: 6 | sdk: flutter 7 | google_sign_in: "^3.0.3" 8 | firebase_auth: "^0.5.10" 9 | firebase_database: "^1.0.0" 10 | cupertino_icons: ^0.1.0 11 | flutter_facebook_login: "^1.1.1" 12 | sliver_fab: "^0.1.0" 13 | sensors: "^0.3.2" 14 | qr_flutter: "^1.1.1" 15 | redux: "^3.0.0" 16 | flutter_redux: "^0.5.0" 17 | flutter_localizations: 18 | sdk: flutter 19 | 20 | dev_dependencies: 21 | flutter_test: 22 | sdk: flutter 23 | 24 | 25 | # For information on the generic Dart part of this file, see the 26 | # following page: https://www.dartlang.org/tools/pub/pubspec 27 | 28 | # The following section is specific to Flutter. 29 | flutter: 30 | 31 | # The following line ensures that the Material Icons font is 32 | # included with your application, so that you can use the icons in 33 | # the material Icons class. 34 | uses-material-design: true 35 | 36 | # To add assets to your application, add an assets section, like this: 37 | # assets: 38 | # - images/a_dot_burr.jpeg 39 | # - images/a_dot_ham.jpeg 40 | assets: 41 | - images/cap.png 42 | - images/qr_code.png 43 | - images/sign-in-facebook.png 44 | - images/sign-in-google.png 45 | - images/coca_family.png 46 | - images/bottle_empty.png 47 | - images/bottle_filled.png 48 | - images/coca_cola/can.png 49 | - images/coca_cola/bottle_small.png 50 | - images/coca_cola/bottle_big.png 51 | - images/fanta/can.png 52 | - images/fanta/bottle_small.png 53 | - images/fanta/bottle_big.png 54 | - images/sprite/can.png 55 | - images/sprite/bottle_small.png 56 | - images/sprite/bottle_medium.png 57 | - images/sprite/bottle_big.png 58 | - images/coca_cola_baner.jpg 59 | - images/coke_light.png 60 | - images/coke_zero.png 61 | - images/fanta_baner.jpg 62 | - images/sprite_baner.jpeg 63 | - images/bubbles.jpg 64 | - images/bubbles_long.jpg 65 | - images/challenge_bottle.png 66 | - images/ticket.jpg 67 | - images/ticket-vertical.jpg 68 | - images/cap_reverse.png 69 | 70 | # An image asset can refer to one or more resolution-specific "variants", see 71 | # https://flutter.io/assets-and-images/#resolution-aware. 72 | 73 | # For details regarding adding assets from package dependencies, see 74 | # https://flutter.io/assets-and-images/#from-packages 75 | 76 | # To add custom fonts to your application, add a fonts section here, 77 | # in this "flutter" section. Each entry in this list should have a 78 | # "family" key with the font family name, and a "fonts" key with a 79 | # list giving the asset and other descriptors for the font. For 80 | # example: 81 | fonts: 82 | - family: Marker 83 | fonts: 84 | - asset: fonts/PermanentMarker-Regular.ttf 85 | # fonts: 86 | # - family: Schyler 87 | # fonts: 88 | # - asset: fonts/Schyler-Regular.ttf 89 | # - asset: fonts/Schyler-Italic.ttf 90 | # style: italic 91 | # - family: Trajan Pro 92 | # fonts: 93 | # - asset: fonts/TrajanPro.ttf 94 | # - asset: fonts/TrajanPro_Bold.ttf 95 | # weight: 700 96 | # 97 | # For details regarding fonts from package dependencies, 98 | # see https://flutter.io/custom-fonts/#from-packages 99 | -------------------------------------------------------------------------------- /res/values/strings_en.arb: -------------------------------------------------------------------------------- 1 | { 2 | "dailyChallenge": "Bubbles", 3 | "collection": "Collection", 4 | "challenges": "Challenges", 5 | "ranking": "Ranking", 6 | "sendCode": "Send code", 7 | "invalidCode": "Invalid code", 8 | "invalidCodeMsg": "Check if you typed code from under the cap correctly.", 9 | "close": "Close", 10 | "codeUnderCap": "Code under the cap", 11 | "addCodeTooltip": "Add code from under the cap", 12 | "codeAddedMsg": "Bottle added to your collection", 13 | "requiredProducts": "Required products", 14 | "completed": "Completed!", 15 | "showMore": "Show more", 16 | "caps": "Caps", 17 | "tickets": "Tickets", 18 | "addFirstCode": "Press \"+\" button,\nto add code from the cap!", 19 | "addFirstChallenge": "Complete you first challenge,\nto earn a ticket for free Coca-Cola can!", 20 | "ticketPlaceholder": "What are we looking here? :)", 21 | "yourTicket": "Your ticket", 22 | "showTicketMsg": "Show this ticket to salesman\nin order to get a Coca-Cola can!", 23 | "bubblesExplanation": "Add last bubble to earn extra 100 points!\nBottle contains $counter/$maxCounter bubbles!", 24 | "shakeForBubble": "Shake your phone to add a bubble!", 25 | "nextBubbleIn": "Next bubble available in $time s!", 26 | "bubbleWinMessage": "Congratulations!\nYou've earned 100 pts!", 27 | "loginWithGoogle": "Log in with Google", 28 | "loginWithFacebook": "Log in with Facebook", 29 | "userProfile": "User profile", 30 | "totalPoints": "Total points: $points", 31 | "totalTickets": "Total tickets: $tickets", 32 | "placeInRanking": "Place in ranking: $rankingPlace", 33 | "logout": "Logout", 34 | "pts": "pts", 35 | "missingBottleMsg": "You only need $bottles more!", 36 | "reward": "Reward:", 37 | "can330": "can 330 ml", 38 | "bottle1l": "bottle 1 l", 39 | "bottle2l": "bottle 2 l", 40 | "bottle250ml": "bottle 250 ml", 41 | "bottle500ml": "bottle 500 ml", 42 | "unknown": "Unknown" 43 | } -------------------------------------------------------------------------------- /res/values/strings_pl.arb: -------------------------------------------------------------------------------- 1 | { 2 | "dailyChallenge": "Wyzwanie dnia", 3 | "collection": "Kolekcja", 4 | "challenges": "Wyzwania", 5 | "ranking": "Ranking", 6 | "sendCode": "Wyślij kod", 7 | "invalidCode": "Nieprawidłowy kod", 8 | "invalidCodeMsg": "Sprawdź czy poprawnie wpisałeś kod spod nakrętki.", 9 | "close": "Zamknij", 10 | "codeUnderCap": "Kod spod nakrętki", 11 | "addCodeTooltip": "Dodaj kod spod nakrętki", 12 | "codeAddedMsg": "Butelka została dodana do Twojej kolekcji", 13 | "requiredProducts": "Wymagane produkty", 14 | "completed": "Wykonano!", 15 | "showMore": "Pokaż więcej", 16 | "caps": "Kapsle", 17 | "tickets": "Bilety", 18 | "addFirstCode": "Naciśnij przycisk \"+\",\naby dodać kod spod nakrętki!", 19 | "addFirstChallenge": "Wykonaj swoje pierwsze wyzwanie,\naby dostać bilet na darmową Colę!", 20 | "ticketPlaceholder": "A czego tutaj szukamy? :)", 21 | "yourTicket": "Twój bilet", 22 | "showTicketMsg": "Pokaż ten bilet sprzedawcy\nw celu odbioru puszki Coca-Cola!", 23 | "bubblesExplanation": "Dodaj ostatni bąbelek by otrzymać dodatkowe 100 punktów!\nButelka zawiera $counter/$maxCounter bąbelków!", 24 | "shakeForBubble": "Potrząśnij telefonem by dodać bąbelek!", 25 | "nextBubbleIn": "Następny bąbelek dostępny za $time s!", 26 | "bubbleWinMessage": "Gratulacje!\nOtrzymałeś 100 pkt!", 27 | "loginWithGoogle": "Zaloguj się z Google", 28 | "loginWithFacebook": "Zaloguj się z Facebook", 29 | "userProfile": "Profil użytkownika", 30 | "totalPoints": "Liczba punktów: $points", 31 | "totalTickets": "Liczba biletów: $tickets", 32 | "placeInRanking": "Miejsce w rankingu: $rankingPlace", 33 | "logout": "Wyloguj", 34 | "pts": "pkt", 35 | "missingBottleMsg": "Brakuje Ci tylko $bottles!", 36 | "reward": "Nagroda:", 37 | "can330": "puszka 330 ml", 38 | "bottle1l": "butelka 1 l", 39 | "bottle2l": "butelka 2 l", 40 | "bottle250ml": "butelka 250 ml", 41 | "unknown": "Nieznane" 42 | } -------------------------------------------------------------------------------- /screenshots/addBottle.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/screenshots/addBottle.gif -------------------------------------------------------------------------------- /screenshots/challenges.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/screenshots/challenges.gif -------------------------------------------------------------------------------- /screenshots/login.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/screenshots/login.gif -------------------------------------------------------------------------------- /screenshots/ranking.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/screenshots/ranking.gif -------------------------------------------------------------------------------- /screenshots/shake.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinusX/cap_challenge/9a53b5271345a4fad8c7a19963abd9acd0bdb27d/screenshots/shake.gif -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // To perform an interaction with a widget in your test, use the WidgetTester utility that Flutter 3 | // provides. For example, you can send tap and scroll gestures. You can also use WidgetTester to 4 | // find child widgets in the widget tree, read text, and verify that the values of widget properties 5 | // are correct. 6 | 7 | import 'package:cap_challenge/main.dart'; 8 | import 'package:cap_challenge/widgets/main_scaffold.dart'; 9 | import 'package:flutter/material.dart'; 10 | import 'package:flutter_test/flutter_test.dart'; 11 | 12 | void main() { 13 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 14 | // Build our app and trigger a frame. 15 | await tester.pumpWidget(new MyApp(new MainScaffold())); 16 | 17 | // Verify that our counter starts at 0. 18 | expect(find.text('0'), findsOneWidget); 19 | expect(find.text('1'), findsNothing); 20 | 21 | // Tap the '+' icon and trigger a frame. 22 | await tester.tap(find.byIcon(Icons.add)); 23 | await tester.pump(); 24 | 25 | // Verify that our counter has incremented. 26 | expect(find.text('0'), findsNothing); 27 | expect(find.text('1'), findsOneWidget); 28 | }); 29 | } 30 | --------------------------------------------------------------------------------