├── .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 |
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 |
11 |
12 |
13 | ## Features
14 | ### Facebook and Google log in
15 | App uses Facebook and Google auth providers in combination with Firebase Auth
16 |
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 |
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 |
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 |
31 |
32 | ### Ranking
33 | Ranking of users with their points
34 |
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 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
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 |
--------------------------------------------------------------------------------