diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..1015322 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,33 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..bbcbbe7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.gitignore b/.gitignore index ac4a906..511b40a 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,7 @@ **/android/gradlew **/android/gradlew.bat **/android/local.properties +**/android/key.properties **/android/**/GeneratedPluginRegistrant.java # iOS/XCode related diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..90474e8 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at thamaraiselvam@live.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c6c4b44 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Thamaraiselvam + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 16558cf..a833c18 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,23 @@ # Numbers - Flutter Game -A new Flutter project. +Numbers is a simple game built in Flutter Framework and is purely based on numbers to improve problem solving skills. -## Getting Started +Get it on Google Play -This project is a starting point for a Flutter application. +### Screenshots -A few resources to get you started if this is your first Flutter project: +
+ + + + + + +
-- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) -For help getting started with Flutter, view our -[online documentation](https://flutter.dev/docs), which offers tutorials, -samples, guidance on mobile development, and a full API reference. +### How to play + +Just match the target shown by adding one or more number blocks. See Demo Here + +Privacy Policy diff --git a/android/app/build.gradle b/android/app/build.gradle index ec84bef..3296ff6 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -24,6 +24,12 @@ if (flutterVersionName == null) { apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" +def keystoreProperties = new Properties() +def keystorePropertiesFile = rootProject.file('key.properties') +if (keystorePropertiesFile.exists()) { + keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) +} + android { compileSdkVersion 28 @@ -33,21 +39,31 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.numbers" + applicationId "com.thamaraiselvam.numbers" minSdkVersion 16 targetSdkVersion 28 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + multiDexEnabled true } - buildTypes { - release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig signingConfigs.debug - } - } + signingConfigs { + release { + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + storeFile file(keystoreProperties['storeFile']) + storePassword keystoreProperties['storePassword'] + } + } + buildTypes { + release { + signingConfig signingConfigs.release + minifyEnabled true + useProguard true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } } flutter { @@ -56,6 +72,11 @@ flutter { dependencies { testImplementation 'junit:junit:4.12' - androidTestImplementation 'com.android.support.test:runner:1.0.2' - androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' + androidTestImplementation 'com.android.support.test:runner:1.1.1' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.1.1' + implementation 'com.google.firebase:firebase-core:17.0.1' + implementation 'com.android.support:multidex:1.0.3' } + +apply plugin: 'com.google.gms.google-services' +apply plugin: 'io.fabric' diff --git a/android/app/google-services.json b/android/app/google-services.json new file mode 100644 index 0000000..31f443a --- /dev/null +++ b/android/app/google-services.json @@ -0,0 +1,40 @@ +{ + "project_info": { + "project_number": "949039664482", + "firebase_url": "https://numbers-game-793c3.firebaseio.com", + "project_id": "numbers-game-793c3", + "storage_bucket": "numbers-game-793c3.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:949039664482:android:805680a6bc372b6a", + "android_client_info": { + "package_name": "com.thamaraiselvam.numbers" + } + }, + "oauth_client": [ + { + "client_id": "949039664482-2fvmt0aa8ngpmg70gtr9efuv2rm01se7.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyBqA77WYFEVn_Ty7fER_21cHFjZAcHZQn8" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "949039664482-2fvmt0aa8ngpmg70gtr9efuv2rm01se7.apps.googleusercontent.com", + "client_type": 3 + } + ] + } + } + } + ], + "configuration_version": "1" + } diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 0000000..239ec33 --- /dev/null +++ b/android/app/proguard-rules.pro @@ -0,0 +1,7 @@ +## Flutter wrapper +-keep class io.flutter.app.** { *; } +-keep class io.flutter.plugin.** { *; } +-keep class io.flutter.util.** { *; } +-keep class io.flutter.view.** { *; } +-keep class io.flutter.** { *; } +-keep class io.flutter.plugins.** { *; } diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml index 99304d4..e5e564c 100644 --- a/android/app/src/debug/AndroidManifest.xml +++ b/android/app/src/debug/AndroidManifest.xml @@ -1,5 +1,5 @@ + package="com.thamaraiselvam.numbers"> diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 0ec1c08..ae81bfe 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,7 @@ + package="com.thamaraiselvam.numbers"> + + diff --git a/android/build.gradle b/android/build.gradle index bb8a303..6431158 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,11 +1,16 @@ buildscript { repositories { google() + maven { + url 'https://maven.fabric.io/public' + } jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.2.1' + classpath 'com.android.tools.build:gradle:3.3.0' + classpath 'com.google.gms:google-services:4.3.0' + classpath 'io.fabric.tools:gradle:1.+' } } @@ -13,6 +18,7 @@ allprojects { repositories { google() jcenter() + maven { url 'https://maven.fabric.io/public' } } } diff --git a/android/gradle.properties b/android/gradle.properties index 2bd6f4f..94adc3a 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,2 +1,3 @@ org.gradle.jvmargs=-Xmx1536M - +android.useAndroidX=true +android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 2819f02..9b6616c 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip diff --git a/assets/images/gvector.png b/assets/images/gvector.png new file mode 100644 index 0000000..92889d8 Binary files /dev/null and b/assets/images/gvector.png differ diff --git a/ios/Flutter/flutter_export_environment.sh b/ios/Flutter/flutter_export_environment.sh new file mode 100644 index 0000000..72835d8 --- /dev/null +++ b/ios/Flutter/flutter_export_environment.sh @@ -0,0 +1,10 @@ +#!/bin/sh +# This is a generated file; do not edit or check into version control. +export "FLUTTER_ROOT=C:\Users\samar\flutter" +export "FLUTTER_APPLICATION_PATH=C:\Flutter Projects\HacktoberFest 2019\Numbers-Flutter-Game" +export "FLUTTER_TARGET=lib\main.dart" +export "FLUTTER_BUILD_DIR=build" +export "SYMROOT=${SOURCE_ROOT}/../build\ios" +export "FLUTTER_FRAMEWORK_DIR=C:\Users\samar\flutter\bin\cache\artifacts\engine\ios" +export "FLUTTER_BUILD_NAME=1.0.0" +export "FLUTTER_BUILD_NUMBER=1" diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 734635c..d80597b 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,20 +1,94 @@ PODS: + - Firebase/Core (6.4.0): + - Firebase/CoreOnly + - FirebaseAnalytics (= 6.0.3) + - Firebase/CoreOnly (6.4.0): + - FirebaseCore (= 6.0.4) + - firebase_core (0.0.1): + - Firebase/Core + - Flutter + - FirebaseAnalytics (6.0.3): + - FirebaseCore (~> 6.0) + - FirebaseInstanceID (~> 4.2) + - GoogleAppMeasurement (= 6.0.3) + - GoogleUtilities/AppDelegateSwizzler (~> 6.0) + - GoogleUtilities/MethodSwizzler (~> 6.0) + - GoogleUtilities/Network (~> 6.0) + - "GoogleUtilities/NSData+zlib (~> 6.0)" + - nanopb (~> 0.3) + - FirebaseCore (6.0.4): + - GoogleUtilities/Environment (~> 6.0) + - GoogleUtilities/Logger (~> 6.0) + - FirebaseInstanceID (4.2.1): + - FirebaseCore (~> 6.0) + - GoogleUtilities/Environment (~> 6.0) + - GoogleUtilities/UserDefaults (~> 6.0) - Flutter (1.0.0) + - GoogleAppMeasurement (6.0.3): + - GoogleUtilities/AppDelegateSwizzler (~> 6.0) + - GoogleUtilities/MethodSwizzler (~> 6.0) + - GoogleUtilities/Network (~> 6.0) + - "GoogleUtilities/NSData+zlib (~> 6.0)" + - nanopb (~> 0.3) + - GoogleUtilities/AppDelegateSwizzler (6.2.3): + - GoogleUtilities/Environment + - GoogleUtilities/Logger + - GoogleUtilities/Network + - GoogleUtilities/Environment (6.2.3) + - GoogleUtilities/Logger (6.2.3): + - GoogleUtilities/Environment + - GoogleUtilities/MethodSwizzler (6.2.3): + - GoogleUtilities/Logger + - GoogleUtilities/Network (6.2.3): + - GoogleUtilities/Logger + - "GoogleUtilities/NSData+zlib" + - GoogleUtilities/Reachability + - "GoogleUtilities/NSData+zlib (6.2.3)" + - GoogleUtilities/Reachability (6.2.3): + - GoogleUtilities/Logger + - GoogleUtilities/UserDefaults (6.2.3): + - GoogleUtilities/Logger + - nanopb (0.3.901): + - nanopb/decode (= 0.3.901) + - nanopb/encode (= 0.3.901) + - nanopb/decode (0.3.901) + - nanopb/encode (0.3.901) - shared_preferences (0.0.1): - Flutter DEPENDENCIES: + - firebase_core (from `.symlinks/plugins/firebase_core/ios`) - Flutter (from `.symlinks/flutter/ios`) - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) +SPEC REPOS: + https://github.com/cocoapods/specs.git: + - Firebase + - FirebaseAnalytics + - FirebaseCore + - FirebaseInstanceID + - GoogleAppMeasurement + - GoogleUtilities + - nanopb + EXTERNAL SOURCES: + firebase_core: + :path: ".symlinks/plugins/firebase_core/ios" Flutter: :path: ".symlinks/flutter/ios" shared_preferences: :path: ".symlinks/plugins/shared_preferences/ios" SPEC CHECKSUMS: + Firebase: 9445469655a98e9010737f4bc4720e5bda465873 + firebase_core: b3b02d0b5e9d01aab5e50ba7cbcf84c73cb6883c + FirebaseAnalytics: 69fe25866d2ec87c6f02d72c3c9a82bce9c065a0 + FirebaseCore: 7ea99e200f0024262650be56623fbe6a7bef1707 + FirebaseInstanceID: 9782fcfb89e4d7da76cc28eac4b8543dc7be4a4b Flutter: 58dd7d1b27887414a370fcccb9e645c08ffd7a6a + GoogleAppMeasurement: 1e45fa601e6280c180880d57010390f6679cd971 + GoogleUtilities: d2b0e277a95962e09bb27f5cd42f5f0b6a506c7d + nanopb: 2901f78ea1b7b4015c860c2fdd1ea2fee1a18d48 shared_preferences: 1feebfa37bb57264736e16865e7ffae7fc99b523 PODFILE CHECKSUM: 7fb83752f59ead6285236625b82473f90b1cb932 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index f3b7b3e..dcac373 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -390,7 +390,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.numbers; + PRODUCT_BUNDLE_IDENTIFIER = com.thamaraiselvam.numbers; PRODUCT_NAME = "$(TARGET_NAME)"; VERSIONING_SYSTEM = "apple-generic"; }; @@ -519,7 +519,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.numbers; + PRODUCT_BUNDLE_IDENTIFIER = com.thamaraiselvam.numbers; PRODUCT_NAME = "$(TARGET_NAME)"; VERSIONING_SYSTEM = "apple-generic"; }; @@ -542,7 +542,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.numbers; + PRODUCT_BUNDLE_IDENTIFIER = com.thamaraiselvam.numbers; PRODUCT_NAME = "$(TARGET_NAME)"; VERSIONING_SYSTEM = "apple-generic"; }; diff --git a/lib/component/LeaderboardCard.dart b/lib/component/LeaderboardCard.dart new file mode 100644 index 0000000..cbdaa86 --- /dev/null +++ b/lib/component/LeaderboardCard.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:numbers/service/LeaderboardService.dart'; +import 'package:numbers/utils/constants.dart'; +import 'package:numbers/widgets/dashedLine.dart'; +import 'package:numbers/widgets/leadershipBoardTable.dart'; + +class LeaderboardCard extends StatefulWidget { + _LeaderboardCardState createState() => _LeaderboardCardState(); +} + +class _LeaderboardCardState extends State { + String title= 'Leaderboard'; + List scores = []; + + @override + void initState() { + super.initState(); + _getRecentScore(); + } + + void _getRecentScore() { + LeaderboardService().getData().then((score) { + setState(() { + this.scores = score; + }); + }); + } + + @override + Widget build(BuildContext context) { + return Container( + color: secondaryBGColor, + padding: const EdgeInsets.only(top: 5, left: 10, right: 10, bottom: 5), + child: Column( + children: [ + Card( + color: Colors.white, + child: Column( + children: [ + ListTile( + leading: Icon( + Icons.show_chart, + color: primaryColor, + ), + title: Text(title, + style: TextStyle( + color: primaryColor, fontWeight: FontWeight.bold)), + ), + dashedLineBreak(Colors.grey), + (this.scores.length == 0) + ? Padding( + padding: const EdgeInsets.only(top: 20), + child: Text('No data found, Play some games'), + ) + : leaderboardTable(this.scores), + SizedBox( + height: 15, + ) + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/component/ScoreCard.dart b/lib/component/RecentScoreCard.dart similarity index 81% rename from lib/component/ScoreCard.dart rename to lib/component/RecentScoreCard.dart index 2b91a83..ae86238 100644 --- a/lib/component/ScoreCard.dart +++ b/lib/component/RecentScoreCard.dart @@ -2,19 +2,18 @@ import 'package:flutter/material.dart'; import 'package:numbers/store/RecentScoreStore.dart'; import 'package:numbers/utils/constants.dart'; import 'package:numbers/widgets/dashedLine.dart'; -import 'package:numbers/widgets/recentScore.dart'; - -class ScoreBoard extends StatefulWidget { - final String title; - ScoreBoard(this.title); +import 'package:numbers/widgets/recentScoreTable.dart'; +class RecentScoreBoard extends StatefulWidget { @override - _ScoreBoardState createState() => _ScoreBoardState(); + _RecentScoreBoardState createState() => _RecentScoreBoardState(); } -class _ScoreBoardState extends State { +class _RecentScoreBoardState extends State { List recentScore = []; Map scoreCardMeta = {}; + String title = 'Recent Scores'; + @override void initState() { super.initState(); @@ -45,7 +44,7 @@ class _ScoreBoardState extends State { Icons.show_chart, color: primaryColor, ), - title: Text(widget.title, + title: Text(title, style: TextStyle( color: primaryColor, fontWeight: FontWeight.bold)), ), @@ -55,7 +54,7 @@ class _ScoreBoardState extends State { padding: const EdgeInsets.only(top: 20), child: Text('No data found, Play some games'), ) - : buildTableRow(this.recentScore), + : recentScoreTable(this.recentScore), SizedBox( height: 15, ) diff --git a/lib/main.dart b/lib/main.dart index f41155f..ed1752e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,25 +1,56 @@ +import 'dart:async'; +import 'package:firebase_analytics/observer.dart'; +import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:numbers/screens/GameScreen/GameScreen.dart'; import 'package:numbers/screens/TurorialScreen.dart'; import 'package:numbers/screens/LoadingScreen.dart'; -import 'package:flutter/material.dart'; import 'package:numbers/screens/SplashScreen.dart'; import 'package:numbers/screens/HomeScreen/HomeScreen.dart'; +import 'package:firebase_analytics/firebase_analytics.dart'; +import 'package:flutter_crashlytics/flutter_crashlytics.dart'; +import 'package:numbers/utils/Config.dart'; Future main() async { + FirebaseAnalytics analytics = FirebaseAnalytics(); + + FlutterError.onError = (FlutterErrorDetails details) { + if (isInDebugMode) { + // In development mode simply print to console. + FlutterError.dumpErrorToConsole(details); + } else { + // In production mode report to the application zone to report to + // Crashlytics. + Zone.current.handleUncaughtError(details.exception, details.stack); + } + }; + + await FlutterCrashlytics().initialize(); + SystemChrome.setPreferredOrientations([ - DeviceOrientation.portraitUp, - DeviceOrientation.portraitDown, - ]); - runApp( - new MaterialApp( + DeviceOrientation.portraitUp, + DeviceOrientation.portraitDown, + ]); + + runZoned>(() async { + runApp(new MaterialApp( + debugShowCheckedModeBanner: false, initialRoute: '/', - routes: { + routes: { '/': (BuildContext context) => SplashScreen(), '/home': (BuildContext context) => HomeScreen(), '/loading': (BuildContext context) => LoadingScreen(), '/game': (BuildContext context) => GameScreen(), '/tutorial': (BuildContext context) => TurorialScreen(), }, + navigatorObservers: [ + FirebaseAnalyticsObserver(analytics: analytics), + ], )); + }, onError: (error, stackTrace) async { + // Whenever an error occurs, call the `reportCrash` function. This will send + // Dart errors to our dev console or Crashlytics depending on the environment. + await FlutterCrashlytics() + .reportCrash(error, stackTrace, forceCrash: false); + }); } diff --git a/lib/models/userModel/user_model.dart b/lib/models/userModel/user_model.dart new file mode 100644 index 0000000..b3db6f0 --- /dev/null +++ b/lib/models/userModel/user_model.dart @@ -0,0 +1,33 @@ +class User { + User._internal(); + + static final User _singleton = User._internal(); + + factory User() => _singleton; + String name; + String email; + String phone; + String uid; + toJson() { + return { + "name": this.name, + "phone": this.phone, + "email": this.email, + "userID": this.uid, + }; + } + + fromJson(Map json) { + this.email = json["email"]; + this.name = json["name"]; + this.phone = json["phone"]; + this.uid = json["userID"]; + } + + setDetails(String email, String name, String phone, String uid) { + this.email = email; + this.phone = phone; + this.name = name; + this.uid = uid; + } +} diff --git a/lib/screens/GameScreen/GameScreen.dart b/lib/screens/GameScreen/GameScreen.dart index bfb0d2f..5d3d051 100644 --- a/lib/screens/GameScreen/GameScreen.dart +++ b/lib/screens/GameScreen/GameScreen.dart @@ -1,5 +1,6 @@ import 'dart:async'; -import 'package:numbers/store/BestScore.dart'; +import 'package:numbers/service/LeaderboardService.dart'; +import 'package:numbers/store/BestScoreStore.dart'; import 'package:numbers/store/RecentScoreStore.dart'; import 'package:numbers/schema/BlockSchema.dart'; import 'package:numbers/screens/GameScreen/summaryModel.dart'; @@ -97,6 +98,7 @@ class _GameScreenState extends State { void updateScores() { RecentScoreStore().updateRecentScore(gameHistory['score']); BestScoreStore().updateScore(gameHistory['score']); + LeaderboardService().setData(gameHistory); } void fillBlocksData() { diff --git a/lib/screens/HomeScreen/HomeScreen.dart b/lib/screens/HomeScreen/HomeScreen.dart index 488320a..aa77856 100644 --- a/lib/screens/HomeScreen/HomeScreen.dart +++ b/lib/screens/HomeScreen/HomeScreen.dart @@ -1,6 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:numbers/component/ScoreCard.dart'; -import 'package:numbers/store/BestScore.dart'; +import 'package:numbers/component/LeaderboardCard.dart'; +import 'package:numbers/component/RecentScoreCard.dart'; +import 'package:numbers/store/BestScoreStore.dart'; +import 'package:numbers/store/SettingsStore.dart'; +import 'package:numbers/utils/Common.dart'; import 'package:numbers/utils/constants.dart'; import 'package:numbers/widgets/dashedLine.dart'; @@ -14,7 +17,6 @@ class HomeScreen extends StatefulWidget { } class _HomeScreenState extends State { - int bestScore = 0; @override @@ -63,18 +65,15 @@ class _HomeScreenState extends State { ], ), ), - // SizedBox( - // height: 10, - // ), - ScoreBoard('Recent Scores'), + LeaderboardCard(), + RecentScoreBoard(), ], ), ), ), floatingActionButton: FloatingActionButton( onPressed: () { - Navigator.of(context).pushNamedAndRemoveUntil( - '/loading', (Route route) => false); + this.floatingBtnAction(); }, child: Icon(Icons.play_arrow), backgroundColor: primaryColor, @@ -83,4 +82,67 @@ class _HomeScreenState extends State { ), ); } + + void floatingBtnAction() async { + String name = await SettingsStore().getKey('name'); + + if (name != null) { + return this.navigateToGame(); + } + + _asyncNameDialog(context); + } + + Future _asyncNameDialog(BuildContext context) async { + String name = ''; + return showDialog( + context: context, + barrierDismissible: + false, // dialog is dismissible with a tap on the barrier + builder: (BuildContext context) { + return AlertDialog( + title: Text('Enter your name'), + content: new Row( + children: [ + new Expanded( + child: new TextField( + autofocus: true, + decoration: new InputDecoration( + labelText: 'Name', hintText: 'eg. Thamaraiselvam'), + onChanged: (value) { + name = value; + }, + )) + ], + ), + actions: [ + FlatButton( + child: Text('Randamize'), + onPressed: () { + String name = Common.getRandomName(); + this.saveName(name); + this.navigateToGame(); + }, + ), + FlatButton( + child: Text('Save'), + onPressed: () { + this.saveName(name); + this.navigateToGame(); + }, + ), + ], + ); + }, + ); + } + + void saveName(String name){ + SettingsStore().setKey('name', name); + } + + void navigateToGame() { + Navigator.of(context) + .pushNamedAndRemoveUntil('/loading', (Route route) => false); + } } diff --git a/lib/screens/HomeScreen/tutorialBtn.widget.dart b/lib/screens/HomeScreen/tutorialBtn.widget.dart index 5abeb55..2aa4d86 100644 --- a/lib/screens/HomeScreen/tutorialBtn.widget.dart +++ b/lib/screens/HomeScreen/tutorialBtn.widget.dart @@ -1,4 +1,3 @@ - import 'package:flutter/material.dart'; import 'package:numbers/utils/constants.dart'; @@ -12,8 +11,7 @@ class TutorialWidget extends StatelessWidget { return Container( padding: EdgeInsets.all(10), child: RaisedButton( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20)), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), color: primaryColor, onPressed: () { Navigator.of(context).pushNamed('/tutorial'); diff --git a/lib/screens/login_screen.dart b/lib/screens/login_screen.dart new file mode 100644 index 0000000..9c03212 --- /dev/null +++ b/lib/screens/login_screen.dart @@ -0,0 +1,90 @@ +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/material.dart'; +import 'package:numbers/models/userModel/user_model.dart'; +import 'package:numbers/service/googleSignIn.dart'; + +Size size = Size(0, 0); +TextEditingController phone = new TextEditingController(); + +class LoginPage extends StatefulWidget { + @override + _LoginPageState createState() => _LoginPageState(); +} + +class _LoginPageState extends State { + final TextEditingController _email = new TextEditingController(); + final TextEditingController _password = new TextEditingController(); + bool check = true; + @override + Widget build(BuildContext context) { + size = MediaQuery.of(context).size; + + return Scaffold( + backgroundColor: Colors.white, + body: Center( + child: ListView( + shrinkWrap: true, + padding: EdgeInsets.only(left: 24.0, right: 24.0), + children: [ + SizedBox( + height: 28, + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: google(), + ) + ], + ), + ), + ); + } + + Widget google() { + MediaQueryData queryData = MediaQuery.of(context); + return RaisedButton( + onPressed: () async { + final user = await signInWithGoogle(); + User().fromJson({ + "name": user.displayName, + "phone": '12345', + "email": user.email, + "userID": user.uid, + }); + Navigator.of(context) + .pushNamedAndRemoveUntil('/game', (Route route) => false); + }, + child: Container( + alignment: Alignment.center, + height: queryData.size.height / 14, + width: queryData.size.width / 1.52, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.only(right: 15.0), + ), + Padding( + padding: const EdgeInsets.only(left: 8.0), + child: Image.asset( + "images/gvector.png", + height: queryData.size.height / 26, + ), + ), + Padding( + padding: const EdgeInsets.only(right: 27.0), + ), + Text( + 'Sign in with Google', + textAlign: TextAlign.center, + style: TextStyle(fontFamily: "Product Sans"), + ), + ], + ), + ), + animationDuration: Duration(seconds: 3), + highlightElevation: 20.0, + color: Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30.0)), + ); + } +} diff --git a/lib/service/googleSignIn.dart b/lib/service/googleSignIn.dart new file mode 100644 index 0000000..9a896ea --- /dev/null +++ b/lib/service/googleSignIn.dart @@ -0,0 +1,27 @@ +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:google_sign_in/google_sign_in.dart'; + +final GoogleSignIn googleSignIn = GoogleSignIn(); + +Future signInWithGoogle() async { + final GoogleSignInAccount googleSignInAccount = await googleSignIn.signIn(); + final GoogleSignInAuthentication googleSignInAuthentication = + await googleSignInAccount.authentication; + + final AuthCredential credential = GoogleAuthProvider.getCredential( + accessToken: googleSignInAuthentication.accessToken, + idToken: googleSignInAuthentication.idToken, + ); + + final AuthResult authResult = + await FirebaseAuth.instance.signInWithCredential(credential); + final FirebaseUser user = authResult.user; + assert(!user.isAnonymous); + assert(await user.getIdToken() != null); + + final FirebaseUser currentUser = await FirebaseAuth.instance.currentUser(); + assert(user.uid == currentUser.uid); + + print("User Sign In"); + return user; +} diff --git a/lib/service/leaderboardService.dart b/lib/service/leaderboardService.dart new file mode 100644 index 0000000..f7381c1 --- /dev/null +++ b/lib/service/leaderboardService.dart @@ -0,0 +1,56 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:numbers/store/SettingsStore.dart'; +import 'package:numbers/utils/Common.dart'; + +class LeaderboardService { + final String collection = 'leaderboard'; + final String orderByKey = 'score'; + + setData(Map gameState) async { + Map newData = await this._formatSetData(gameState); + Firestore.instance.collection(this.collection).document().setData(newData); + } + + _formatSetData(Map gameState) async { + String name = await SettingsStore().getKey('name'); + + if (name == null) { + name = Common.getRandomName(); + } + + return { + 'name': name, + 'time': DateTime.now().millisecondsSinceEpoch.toString(), + 'total': gameState['total'], + 'success': gameState['success'], + 'fail': gameState['fail'], + 'score': gameState['score'], + 'selectedBlocks': gameState['selectedBlocks'], + }; + } + + getData({int limit = 6}) async { + try { + var resultData = await Firestore.instance + .collection(this.collection) + .orderBy(this.orderByKey, descending: true) + .limit(limit) + .getDocuments(); + + return this._formatGetData(resultData); + } catch (error) { + print(error); + } + } + + _formatGetData(resultData) { + List formattedData = []; + + int counter = 0; + for (var document in resultData.documents) { + document.data['rank'] = ++counter; + formattedData.add(document.data); + } + return formattedData; + } +} diff --git a/lib/store/BestScore.dart b/lib/store/BestScoreStore.dart similarity index 100% rename from lib/store/BestScore.dart rename to lib/store/BestScoreStore.dart diff --git a/lib/store/SettingsStore.dart b/lib/store/SettingsStore.dart new file mode 100644 index 0000000..a390b87 --- /dev/null +++ b/lib/store/SettingsStore.dart @@ -0,0 +1,22 @@ +import 'package:shared_preferences/shared_preferences.dart'; + +class SettingsStore { + + SharedPreferences prefs; + + init() async { + if (this.prefs == null) { + this.prefs = await SharedPreferences.getInstance(); + } + } + + setKey(String key, String value) async { + await this.init(); + return this.prefs.setString(key, value); + } + + Future getKey(String key) async { + await this.init(); + return this.prefs.getString(key); + } +} diff --git a/lib/utils/Config.dart b/lib/utils/Config.dart index e473681..d4776cb 100644 --- a/lib/utils/Config.dart +++ b/lib/utils/Config.dart @@ -1,3 +1,4 @@ const int gameDuration = 60; const Map costs = {'success': 1000, 'fail': -500, 'block': 100}; const int maxKeepGameHistoryCount = 6; +const bool isInDebugMode = false; diff --git a/lib/utils/common.dart b/lib/utils/common.dart index 16e0708..254be60 100644 --- a/lib/utils/common.dart +++ b/lib/utils/common.dart @@ -1,4 +1,5 @@ import 'dart:math'; +import 'package:faker/faker.dart'; import 'package:flutter/material.dart'; class Common { @@ -57,4 +58,8 @@ class Common { return combinations; } + + static String getRandomName(){ + return Faker().person.name(); + } } diff --git a/lib/widgets/leadershipBoardTable.dart b/lib/widgets/leadershipBoardTable.dart new file mode 100644 index 0000000..5ea9a2b --- /dev/null +++ b/lib/widgets/leadershipBoardTable.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; + +DataTable leaderboardTable(List listOfColumns) { + return DataTable( + columns: [ + DataColumn(label: Text('Rank')), + DataColumn(label: Text('Name')), + DataColumn(label: Text('Score')), + ], + rows: listOfColumns + .map( + ((element) => DataRow( + cells: [ + DataCell(Text(element['rank'].toString())), + DataCell(Text(element['name'].toString())), + DataCell(Text(element['score'].toString())), + ], + )), + ) + .toList(), + ); +} diff --git a/lib/widgets/recentScore.dart b/lib/widgets/recentScoreTable.dart similarity index 92% rename from lib/widgets/recentScore.dart rename to lib/widgets/recentScoreTable.dart index 4439f7f..c6c8fb5 100644 --- a/lib/widgets/recentScore.dart +++ b/lib/widgets/recentScoreTable.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:timeago/timeago.dart' as timeago; -DataTable buildTableRow(List listOfColumns) { +DataTable recentScoreTable(List listOfColumns) { return DataTable( columns: [ DataColumn(label: Text('Score')), diff --git a/privacy_policy.md b/privacy_policy.md new file mode 100644 index 0000000..0f95d30 --- /dev/null +++ b/privacy_policy.md @@ -0,0 +1,62 @@ +## Privacy Policy + +built the Numbers app as an Open Source app. This SERVICE is provided by at no cost and is intended for use as is. + +This page is used to inform visitors regarding my policies with the collection, use, and disclosure of Personal Information if anyone decided to use my Service. + +If you choose to use my Service, then you agree to the collection and use of information in relation to this policy. The Personal Information that I collect is used for providing and improving the Service. I will not use or share your information with anyone except as described in this Privacy Policy. + +The terms used in this Privacy Policy have the same meanings as in our Terms and Conditions, which is accessible at Numbers unless otherwise defined in this Privacy Policy. + +**Information Collection and Use** + +For a better experience, while using our Service, I may require you to provide us with certain personally identifiable information. The information that I request will be retained on your device and is not collected by me in any way. + +The app does use third party services that may collect information used to identify you. + +Link to privacy policy of third party service providers used by the app + +* [Google Play Services](https://www.google.com/policies/privacy/) + +**Log Data** + +I want to inform you that whenever you use my Service, in a case of an error in the app I collect data and information (through third party products) on your phone called Log Data. This Log Data may include information such as your device Internet Protocol (“IP”) address, device name, operating system version, the configuration of the app when utilizing my Service, the time and date of your use of the Service, and other statistics. + +**Cookies** + +Cookies are files with a small amount of data that are commonly used as anonymous unique identifiers. These are sent to your browser from the websites that you visit and are stored on your device's internal memory. + +This Service does not use these “cookies” explicitly. However, the app may use third party code and libraries that use “cookies” to collect information and improve their services. You have the option to either accept or refuse these cookies and know when a cookie is being sent to your device. If you choose to refuse our cookies, you may not be able to use some portions of this Service. + +**Service Providers** + +I may employ third-party companies and individuals due to the following reasons: + +* To facilitate our Service; +* To provide the Service on our behalf; +* To perform Service-related services; or +* To assist us in analyzing how our Service is used. + +I want to inform users of this Service that these third parties have access to your Personal Information. The reason is to perform the tasks assigned to them on our behalf. However, they are obligated not to disclose or use the information for any other purpose. + +**Security** + +I value your trust in providing us your Personal Information, thus we are striving to use commercially acceptable means of protecting it. But remember that no method of transmission over the internet, or method of electronic storage is 100% secure and reliable, and I cannot guarantee its absolute security. + +**Links to Other Sites** + +This Service may contain links to other sites. If you click on a third-party link, you will be directed to that site. Note that these external sites are not operated by me. Therefore, I strongly advise you to review the Privacy Policy of these websites. I have no control over and assume no responsibility for the content, privacy policies, or practices of any third-party sites or services. + +**Children’s Privacy** + +These Services do not address anyone. I do not knowingly collect personally identifiable information from children. In the case I discover that a child has provided me with personal information, I immediately delete this from our servers. If you are a parent or guardian and you are aware that your child has provided us with personal information, please contact me so that I will be able to do necessary actions. + +**Changes to This Privacy Policy** + +I may update our Privacy Policy from time to time. Thus, you are advised to review this page periodically for any changes. I will notify you of any changes by posting the new Privacy Policy on this page. These changes are effective immediately after they are posted on this page. + +**Contact Us** + +If you have any questions or suggestions about my Privacy Policy, do not hesitate to contact me at thamaraiselvam@live.com. + +This privacy policy page was created at [privacypolicytemplate.net](https://privacypolicytemplate.net) and modified/generated by [App Privacy Policy Generator](https://app-privacy-policy-generator.firebaseapp.com/) diff --git a/pubspec.lock b/pubspec.lock index fdb6a09..07fde14 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -21,14 +21,14 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.2.0" + version: "2.3.0" boolean_selector: dependency: transitive description: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "1.0.5" charcode: dependency: transitive description: @@ -36,6 +36,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.2" + cloud_firestore: + dependency: "direct main" + description: + name: cloud_firestore + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.9" collection: dependency: transitive description: @@ -64,13 +71,48 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.1.2" + faker: + dependency: "direct main" + description: + name: faker + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + firebase_analytics: + dependency: "direct main" + description: + name: firebase_analytics + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0+1" + firebase_auth: + dependency: "direct main" + description: + name: firebase_auth + url: "https://pub.dartlang.org" + source: hosted + version: "0.14.0+5" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.0+8" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" + flutter_crashlytics: + dependency: "direct main" + description: + name: flutter_crashlytics + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" flutter_launcher_icons: - dependency: "direct dev" + dependency: "direct main" description: name: flutter_launcher_icons url: "https://pub.dartlang.org" @@ -81,6 +123,13 @@ packages: description: flutter source: sdk version: "0.0.0" + google_sign_in: + dependency: "direct main" + description: + name: google_sign_in + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.7" image: dependency: transitive description: @@ -101,21 +150,21 @@ packages: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.1.6" + version: "1.1.7" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.6.2" + version: "1.6.4" pedantic: dependency: transitive description: name: pedantic url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0+1" petitparser: dependency: transitive description: @@ -129,7 +178,7 @@ packages: name: quiver url: "https://pub.dartlang.org" source: hosted - version: "2.0.3" + version: "2.0.5" shared_preferences: dependency: "direct main" description: @@ -169,7 +218,7 @@ packages: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "1.0.5" term_glyph: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 2952ed7..45a4498 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,18 +19,24 @@ environment: dependencies: flutter: sdk: flutter + firebase_auth: ^0.14.0+5 + google_sign_in: ^4.0.7 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^0.1.2 shared_preferences: ^0.5.3+4 timeago: ^2.0.18 - + firebase_core: ^0.4.0+8 + firebase_analytics: ^1.0.2 + flutter_crashlytics: ^1.0.0 + flutter_launcher_icons: "^0.7.2" + cloud_firestore: ^0.12.9 + faker: ^1.1.1 dev_dependencies: flutter_test: sdk: flutter - flutter_launcher_icons: "^0.7.2" flutter_icons: android: "launcher_icon" @@ -44,7 +50,7 @@ flutter_icons: # The following section is specific to Flutter. flutter: assets: - - assets/images/ + - assets/images/gvector.png # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in