├── .analysis_options ├── .gitignore ├── .travis.yml ├── Flutter ├── AppFrameworkInfo.plist ├── Debug.xcconfig └── Release.xcconfig ├── README.md ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ ├── com │ │ │ └── flitter │ │ │ │ └── MainActivity.java │ │ └── io │ │ │ └── flutter │ │ │ └── plugins │ │ │ └── GeneratedPluginRegistrant.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 ├── assets ├── html │ └── success.html └── images │ └── banner.jpg ├── flitter.iml ├── generate_main_from_env.dart ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ └── contents.xcworkspacedata └── Runner │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── 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 │ ├── GeneratedPluginRegistrant.h │ ├── GeneratedPluginRegistrant.m │ ├── Info.plist │ └── main.m ├── lib ├── app.dart ├── intl │ └── messages_all.dart ├── redux │ ├── actions.dart │ ├── flitter_app_reducer.dart │ ├── flitter_app_state.dart │ └── store.dart ├── services │ ├── flitter_auth.dart │ ├── flitter_config.dart │ ├── flitter_request.dart │ └── flutter_gitter_auth.dart └── widgets │ ├── common │ ├── chat_room.dart │ ├── drawer.dart │ ├── list_room.dart │ ├── search.dart │ └── utils.dart │ └── routes │ ├── group.dart │ ├── home.dart │ ├── login.dart │ ├── people.dart │ ├── room.dart │ └── settings.dart ├── pubspec.yaml ├── screenshots ├── flutter_01.png ├── flutter_02.png ├── flutter_03.png ├── flutter_04.png ├── flutter_05.png ├── flutter_06.png └── flutter_07.png └── test ├── utils.dart └── widgets ├── drawer_test.dart ├── group_test.dart ├── home_test.dart ├── people_test.dart └── splash_test.dart /.analysis_options: -------------------------------------------------------------------------------- 1 | analyzer: 2 | strong-mode: true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .atom/ 3 | .idea 4 | .packages 5 | .pub/ 6 | build/ 7 | ios/.generated/ 8 | packages 9 | pubspec.lock 10 | .flutter-plugins 11 | .dart_tool 12 | config.yaml 13 | lib/main.dart -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | matrix: 2 | # This causes the build to complete immediately upon first failure or once 3 | # required jobs are green. 4 | fast_finish: true 5 | 6 | # Building APK/IPA takes a long time; do not wait for them to finish. 7 | allow_failures: 8 | - env: JOB=APK 9 | - env: JOB=IPA 10 | 11 | include: 12 | # Runs unit tests without emulators. 13 | - env: JOB=PR 14 | os: linux 15 | language: generic 16 | sudo: false 17 | addons: 18 | apt: 19 | # Flutter depends on /usr/lib/x86_64-linux-gnu/libstdc++.so.6 version GLIBCXX_3.4.18 20 | sources: 21 | - ubuntu-toolchain-r-test # if we don't specify this, the libstdc++6 we get is the wrong version 22 | packages: 23 | - libstdc++6 24 | - fonts-droid 25 | before_script: 26 | - git clone https://github.com/flutter/flutter.git -b beta --depth 1 27 | script: 28 | - ./flutter/bin/flutter test 29 | 30 | # Builds an APK. 31 | - env: JOB=APK 32 | os: linux 33 | language: android 34 | licenses: 35 | - 'android-sdk-preview-license-.+' 36 | - 'android-sdk-license-.+' 37 | - 'google-gdk-license-.+' 38 | android: 39 | components: 40 | - tools 41 | - platform-tools 42 | - build-tools-27.0.3 43 | - android-27 44 | - sys-img-armeabi-v7a-google_apis-25 45 | - extra-android-m2repository 46 | - extra-google-m2repository 47 | - extra-google-android-support 48 | jdk: oraclejdk8 49 | sudo: false 50 | addons: 51 | apt: 52 | # Flutter depends on /usr/lib/x86_64-linux-gnu/libstdc++.so.6 version GLIBCXX_3.4.18 53 | sources: 54 | - ubuntu-toolchain-r-test # if we don't specify this, the libstdc++6 we get is the wrong version 55 | packages: 56 | - libstdc++6 57 | - fonts-droid 58 | before_script: 59 | - wget http://services.gradle.org/distributions/gradle-3.5-bin.zip 60 | - unzip -qq gradle-3.5-bin.zip 61 | - export GRADLE_HOME=$PWD/gradle-3.5 62 | - export PATH=$GRADLE_HOME/bin:$PATH 63 | - git clone https://github.com/flutter/flutter.git -b beta --depth 1 64 | script: 65 | - ./flutter/bin/internal/update_dart_sdk.sh 66 | - ./flutter/bin/cache/dart-sdk/bin/dart generate_main_from_env.dart 67 | - ./flutter/bin/flutter -v build apk 68 | 69 | # Builds an IPA. 70 | - env: JOB=IPA 71 | os: osx 72 | language: generic 73 | osx_image: xcode8.3 74 | before_script: 75 | - pip install six 76 | - brew update 77 | - brew install --HEAD libimobiledevice 78 | - brew install ideviceinstaller 79 | - brew install ios-deploy 80 | - git clone https://github.com/flutter/flutter.git -b beta --depth 1 81 | script: 82 | - ./flutter/bin/internal/update_dart_sdk.sh 83 | - ./flutter/bin/cache/dart-sdk/bin/dart generate_main_from_env.dart 84 | - ./flutter/bin/flutter -v build ios --no-codesign 85 | 86 | cache: 87 | directories: 88 | - $HOME/.pub-cache -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flitter 2 | 3 | Gitter Client for Mobile made with Flutter 4 | 5 | [![Build Status](https://travis-ci.org/dart-flitter/flitter.svg?branch=master)](https://travis-ci.org/dart-flitter/flitter) 6 | 7 |
8 | 11 | 14 | 17 | 18 |
9 | 10 | 12 | 13 | 15 | 16 |
19 |
20 |
21 | 24 | 27 | 30 | 31 |
22 | 23 | 25 | 26 | 28 | 29 |
32 |
33 | 34 | ## Getting Started 35 | 36 | For help getting started with Flutter, view our online 37 | [documentation](http://flutter.io/). 38 | 39 | 40 | ## Configuration 41 | 42 | Create a `main.dart` inside `lib` folder with the following content. 43 | 44 | ```dart 45 | import 'package:flitter/app.dart' as flitter; 46 | import 'package:flitter/services/flitter_config.dart'; 47 | 48 | main() { 49 | Config.init(gitter: const GitterConfig( 50 | appId: "", 51 | appSecret: "", 52 | redirectionUrl: "")); 53 | 54 | flitter.run(); 55 | } 56 | ``` 57 | 58 | As `GITTER_REDIRECTION_URL` value use "http://localhost:8080". 59 | 60 | [More Infos](https://developer.gitter.im/docs/welcome) 61 | -------------------------------------------------------------------------------- /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 from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 16 | 17 | android { 18 | compileSdkVersion 27 19 | 20 | lintOptions { 21 | disable 'InvalidPackage' 22 | } 23 | 24 | defaultConfig { 25 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 26 | applicationId "com.flitter" 27 | minSdkVersion 16 28 | targetSdkVersion 27 29 | versionCode 1 30 | versionName "1.0" 31 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 32 | } 33 | 34 | buildTypes { 35 | release { 36 | // TODO: Add your own signing config for the release build. 37 | // Signing with the debug keys for now, so `flutter run --release` works. 38 | signingConfig signingConfigs.debug 39 | } 40 | } 41 | } 42 | 43 | flutter { 44 | source '../..' 45 | } 46 | 47 | dependencies { 48 | testImplementation 'junit:junit:4.12' 49 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 50 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' 51 | } 52 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 17 | 22 | 26 | 33 | 37 | 40 | 41 | 42 | 43 | 44 | 45 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/flitter/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.flitter; 2 | 3 | import android.os.Bundle; 4 | 5 | import io.flutter.app.FlutterActivity; 6 | import io.flutter.plugins.GeneratedPluginRegistrant; 7 | 8 | public class MainActivity extends FlutterActivity { 9 | @Override 10 | protected void onCreate(Bundle savedInstanceState) { 11 | super.onCreate(savedInstanceState); 12 | GeneratedPluginRegistrant.registerWith(this); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java: -------------------------------------------------------------------------------- 1 | package io.flutter.plugins; 2 | 3 | import io.flutter.plugin.common.PluginRegistry; 4 | import com.flutter_webview_plugin.FlutterWebviewPlugin; 5 | import io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin; 6 | import io.flutter.plugins.urllauncher.UrlLauncherPlugin; 7 | 8 | /** 9 | * Generated file. Do not edit. 10 | */ 11 | public final class GeneratedPluginRegistrant { 12 | public static void registerWith(PluginRegistry registry) { 13 | if (alreadyRegisteredWith(registry)) { 14 | return; 15 | } 16 | FlutterWebviewPlugin.registerWith(registry.registrarFor("com.flutter_webview_plugin.FlutterWebviewPlugin")); 17 | SharedPreferencesPlugin.registerWith(registry.registrarFor("io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin")); 18 | UrlLauncherPlugin.registerWith(registry.registrarFor("io.flutter.plugins.urllauncher.UrlLauncherPlugin")); 19 | } 20 | 21 | private static boolean alreadyRegisteredWith(PluginRegistry registry) { 22 | final String key = GeneratedPluginRegistrant.class.getCanonicalName(); 23 | if (registry.hasPlugin(key)) { 24 | return true; 25 | } 26 | registry.registrarFor(key); 27 | return false; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /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/dart-flitter/flitter/369e337768566f3d0b2c947fac23bf7900b24cf5/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-flitter/flitter/369e337768566f3d0b2c947fac23bf7900b24cf5/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-flitter/flitter/369e337768566f3d0b2c947fac23bf7900b24cf5/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-flitter/flitter/369e337768566f3d0b2c947fac23bf7900b24cf5/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-flitter/flitter/369e337768566f3d0b2c947fac23bf7900b24cf5/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /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 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | } 17 | } 18 | 19 | rootProject.buildDir = '../build' 20 | subprojects { 21 | project.buildDir = "${rootProject.buildDir}/${project.name}" 22 | } 23 | subprojects { 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-flitter/flitter/369e337768566f3d0b2c947fac23bf7900b24cf5/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 | -------------------------------------------------------------------------------- /assets/html/success.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Success 6 | 7 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /assets/images/banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-flitter/flitter/369e337768566f3d0b2c947fac23bf7900b24cf5/assets/images/banner.jpg -------------------------------------------------------------------------------- /flitter.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /generate_main_from_env.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | main() { 4 | File file = new File("lib/main.dart"); 5 | if (!file.existsSync()) { 6 | file.createSync(recursive: true); 7 | file.writeAsStringSync( 8 | _generate(Platform.environment["GITTER_APP_ID"], 9 | Platform.environment["GITTER_APP_SECRET"])); 10 | } 11 | } 12 | 13 | 14 | String _generate(String appId, String appSecret) => 15 | ''' 16 | import 'package:flitter/app.dart' as flitter; 17 | import 'package:flitter/services/flitter_config.dart'; 18 | 19 | main() { 20 | Config.init( 21 | gitter: const GitterConfig( 22 | appId: "$appId", 23 | appSecret: "$appSecret", 24 | redirectionUrl: "http://localhost:8080/")); 25 | 26 | flitter.run(); 27 | } 28 | 29 | '''; -------------------------------------------------------------------------------- /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 "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | if ENV['FLUTTER_FRAMEWORK_DIR'] == nil 5 | abort('Please set FLUTTER_FRAMEWORK_DIR to the directory containing Flutter.framework') 6 | end 7 | 8 | target 'Runner' do 9 | use_frameworks! 10 | 11 | # Pods for Runner 12 | 13 | # Flutter Pods 14 | pod 'Flutter', :path => ENV['FLUTTER_FRAMEWORK_DIR'] 15 | 16 | if File.exists? '../.flutter-plugins' 17 | flutter_root = File.expand_path('..') 18 | File.foreach('../.flutter-plugins') { |line| 19 | plugin = line.split(pattern='=') 20 | if plugin.length == 2 21 | name = plugin[0].strip() 22 | path = plugin[1].strip() 23 | resolved_path = File.expand_path("#{path}/ios", flutter_root) 24 | pod name, :path => resolved_path 25 | else 26 | puts "Invalid plugin specification: #{line}" 27 | end 28 | } 29 | end 30 | end 31 | 32 | post_install do |installer| 33 | installer.pods_project.targets.each do |target| 34 | target.build_configurations.each do |config| 35 | config.build_settings['ENABLE_BITCODE'] = 'NO' 36 | end 37 | end 38 | end -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - flutter_webview_plugin (0.0.1): 4 | - Flutter 5 | - shared_preferences (0.0.1): 6 | - Flutter 7 | - url_launcher (0.0.1): 8 | - Flutter 9 | 10 | DEPENDENCIES: 11 | - Flutter (from `/Users/izambasiron/Workspace/flutter/bin/cache/artifacts/engine/ios`) 12 | - flutter_webview_plugin (from `/Users/izambasiron/.pub-cache/hosted/pub.dartlang.org/flutter_webview_plugin-0.1.2/ios`) 13 | - shared_preferences (from `/Users/izambasiron/.pub-cache/hosted/pub.dartlang.org/shared_preferences-0.3.3/ios`) 14 | - url_launcher (from `/Users/izambasiron/.pub-cache/hosted/pub.dartlang.org/url_launcher-2.0.2/ios`) 15 | 16 | EXTERNAL SOURCES: 17 | Flutter: 18 | :path: /Users/izambasiron/Workspace/flutter/bin/cache/artifacts/engine/ios 19 | flutter_webview_plugin: 20 | :path: /Users/izambasiron/.pub-cache/hosted/pub.dartlang.org/flutter_webview_plugin-0.1.2/ios 21 | shared_preferences: 22 | :path: /Users/izambasiron/.pub-cache/hosted/pub.dartlang.org/shared_preferences-0.3.3/ios 23 | url_launcher: 24 | :path: /Users/izambasiron/.pub-cache/hosted/pub.dartlang.org/url_launcher-2.0.2/ios 25 | 26 | SPEC CHECKSUMS: 27 | Flutter: 9d0fac939486c9aba2809b7982dfdbb47a7b0296 28 | flutter_webview_plugin: 116575b48572029304775b768e9f15ebfc316274 29 | shared_preferences: 5a1d487c427ee18fcd3ea1f2a131569481834b53 30 | url_launcher: 92b89c1029a0373879933c21642958c874539095 31 | 32 | PODFILE CHECKSUM: da0a90142b97ff5764bbe022af2a7676460e1454 33 | 34 | COCOAPODS: 1.4.0 35 | -------------------------------------------------------------------------------- /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 | 3826EC1C73D033A8D054855E /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F69DC8EB3C3C4F8DEB111E68 /* Pods_Runner.framework */; }; 13 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 14 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 15 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 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 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 21 | 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 22 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 23 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 24 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 25 | /* End PBXBuildFile section */ 26 | 27 | /* Begin PBXCopyFilesBuildPhase section */ 28 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 29 | isa = PBXCopyFilesBuildPhase; 30 | buildActionMask = 2147483647; 31 | dstPath = ""; 32 | dstSubfolderSpec = 10; 33 | files = ( 34 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, 35 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, 36 | ); 37 | name = "Embed Frameworks"; 38 | runOnlyForDeploymentPostprocessing = 0; 39 | }; 40 | /* End PBXCopyFilesBuildPhase section */ 41 | 42 | /* Begin PBXFileReference section */ 43 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 44 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 45 | 2D5378251FAA1A9400D5DBA9 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = Flutter/flutter_assets; sourceTree = SOURCE_ROOT; }; 46 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 47 | 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 48 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 49 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 50 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 51 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 52 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 53 | 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; 54 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 55 | 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 56 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 57 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 58 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 59 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 60 | F69DC8EB3C3C4F8DEB111E68 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 61 | /* End PBXFileReference section */ 62 | 63 | /* Begin PBXFrameworksBuildPhase section */ 64 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 65 | isa = PBXFrameworksBuildPhase; 66 | buildActionMask = 2147483647; 67 | files = ( 68 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 69 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 70 | 3826EC1C73D033A8D054855E /* Pods_Runner.framework in Frameworks */, 71 | ); 72 | runOnlyForDeploymentPostprocessing = 0; 73 | }; 74 | /* End PBXFrameworksBuildPhase section */ 75 | 76 | /* Begin PBXGroup section */ 77 | 5D36FEDFF9D30CC5AE9379AF /* Frameworks */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | F69DC8EB3C3C4F8DEB111E68 /* Pods_Runner.framework */, 81 | ); 82 | name = Frameworks; 83 | sourceTree = ""; 84 | }; 85 | 9740EEB11CF90186004384FC /* Flutter */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | 2D5378251FAA1A9400D5DBA9 /* flutter_assets */, 89 | 3B80C3931E831B6300D905FE /* App.framework */, 90 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 91 | 9740EEBA1CF902C7004384FC /* Flutter.framework */, 92 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 93 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 94 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 95 | ); 96 | name = Flutter; 97 | sourceTree = ""; 98 | }; 99 | 97C146E51CF9000F007C117D = { 100 | isa = PBXGroup; 101 | children = ( 102 | 9740EEB11CF90186004384FC /* Flutter */, 103 | 97C146F01CF9000F007C117D /* Runner */, 104 | 97C146EF1CF9000F007C117D /* Products */, 105 | CECB9306FD4080E41371CE13 /* Pods */, 106 | 5D36FEDFF9D30CC5AE9379AF /* Frameworks */, 107 | ); 108 | sourceTree = ""; 109 | }; 110 | 97C146EF1CF9000F007C117D /* Products */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | 97C146EE1CF9000F007C117D /* Runner.app */, 114 | ); 115 | name = Products; 116 | sourceTree = ""; 117 | }; 118 | 97C146F01CF9000F007C117D /* Runner */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, 122 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 123 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 124 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 125 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 126 | 97C147021CF9000F007C117D /* Info.plist */, 127 | 97C146F11CF9000F007C117D /* Supporting Files */, 128 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 129 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 130 | ); 131 | path = Runner; 132 | sourceTree = ""; 133 | }; 134 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | 97C146F21CF9000F007C117D /* main.m */, 138 | ); 139 | name = "Supporting Files"; 140 | sourceTree = ""; 141 | }; 142 | CECB9306FD4080E41371CE13 /* Pods */ = { 143 | isa = PBXGroup; 144 | children = ( 145 | ); 146 | name = Pods; 147 | sourceTree = ""; 148 | }; 149 | /* End PBXGroup section */ 150 | 151 | /* Begin PBXNativeTarget section */ 152 | 97C146ED1CF9000F007C117D /* Runner */ = { 153 | isa = PBXNativeTarget; 154 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 155 | buildPhases = ( 156 | A0274BCFFF2EFA15188D1EEB /* [CP] Check Pods Manifest.lock */, 157 | 9740EEB61CF901F6004384FC /* Run Script */, 158 | 97C146EA1CF9000F007C117D /* Sources */, 159 | 97C146EB1CF9000F007C117D /* Frameworks */, 160 | 97C146EC1CF9000F007C117D /* Resources */, 161 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 162 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 163 | B6BB1EDD7B45F95EFCB73BA8 /* [CP] Embed Pods Frameworks */, 164 | D65495B808B50135AED36BDB /* [CP] Copy Pods Resources */, 165 | ); 166 | buildRules = ( 167 | ); 168 | dependencies = ( 169 | ); 170 | name = Runner; 171 | productName = Runner; 172 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 173 | productType = "com.apple.product-type.application"; 174 | }; 175 | /* End PBXNativeTarget section */ 176 | 177 | /* Begin PBXProject section */ 178 | 97C146E61CF9000F007C117D /* Project object */ = { 179 | isa = PBXProject; 180 | attributes = { 181 | LastUpgradeCheck = 0910; 182 | ORGANIZATIONNAME = "The Chromium Authors"; 183 | TargetAttributes = { 184 | 97C146ED1CF9000F007C117D = { 185 | CreatedOnToolsVersion = 7.3.1; 186 | }; 187 | }; 188 | }; 189 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 190 | compatibilityVersion = "Xcode 3.2"; 191 | developmentRegion = English; 192 | hasScannedForEncodings = 0; 193 | knownRegions = ( 194 | en, 195 | Base, 196 | ); 197 | mainGroup = 97C146E51CF9000F007C117D; 198 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 199 | projectDirPath = ""; 200 | projectRoot = ""; 201 | targets = ( 202 | 97C146ED1CF9000F007C117D /* Runner */, 203 | ); 204 | }; 205 | /* End PBXProject section */ 206 | 207 | /* Begin PBXResourcesBuildPhase section */ 208 | 97C146EC1CF9000F007C117D /* Resources */ = { 209 | isa = PBXResourcesBuildPhase; 210 | buildActionMask = 2147483647; 211 | files = ( 212 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 213 | 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */, 214 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 215 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, 216 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 217 | 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */, 218 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 219 | ); 220 | runOnlyForDeploymentPostprocessing = 0; 221 | }; 222 | /* End PBXResourcesBuildPhase section */ 223 | 224 | /* Begin PBXShellScriptBuildPhase section */ 225 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 226 | isa = PBXShellScriptBuildPhase; 227 | buildActionMask = 2147483647; 228 | files = ( 229 | ); 230 | inputPaths = ( 231 | ); 232 | name = "Thin Binary"; 233 | outputPaths = ( 234 | ); 235 | runOnlyForDeploymentPostprocessing = 0; 236 | shellPath = /bin/sh; 237 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; 238 | }; 239 | 9740EEB61CF901F6004384FC /* Run Script */ = { 240 | isa = PBXShellScriptBuildPhase; 241 | buildActionMask = 2147483647; 242 | files = ( 243 | ); 244 | inputPaths = ( 245 | ); 246 | name = "Run Script"; 247 | outputPaths = ( 248 | ); 249 | runOnlyForDeploymentPostprocessing = 0; 250 | shellPath = /bin/sh; 251 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 252 | }; 253 | A0274BCFFF2EFA15188D1EEB /* [CP] Check Pods Manifest.lock */ = { 254 | isa = PBXShellScriptBuildPhase; 255 | buildActionMask = 2147483647; 256 | files = ( 257 | ); 258 | inputPaths = ( 259 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 260 | "${PODS_ROOT}/Manifest.lock", 261 | ); 262 | name = "[CP] Check Pods Manifest.lock"; 263 | outputPaths = ( 264 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 265 | ); 266 | runOnlyForDeploymentPostprocessing = 0; 267 | shellPath = /bin/sh; 268 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 269 | showEnvVarsInLog = 0; 270 | }; 271 | B6BB1EDD7B45F95EFCB73BA8 /* [CP] Embed Pods Frameworks */ = { 272 | isa = PBXShellScriptBuildPhase; 273 | buildActionMask = 2147483647; 274 | files = ( 275 | ); 276 | inputPaths = ( 277 | "${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", 278 | "${PODS_ROOT}/../../../flutter/bin/cache/artifacts/engine/ios/Flutter.framework", 279 | "${BUILT_PRODUCTS_DIR}/flutter_webview_plugin/flutter_webview_plugin.framework", 280 | "${BUILT_PRODUCTS_DIR}/shared_preferences/shared_preferences.framework", 281 | "${BUILT_PRODUCTS_DIR}/url_launcher/url_launcher.framework", 282 | ); 283 | name = "[CP] Embed Pods Frameworks"; 284 | outputPaths = ( 285 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", 286 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_webview_plugin.framework", 287 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences.framework", 288 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher.framework", 289 | ); 290 | runOnlyForDeploymentPostprocessing = 0; 291 | shellPath = /bin/sh; 292 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 293 | showEnvVarsInLog = 0; 294 | }; 295 | D65495B808B50135AED36BDB /* [CP] Copy Pods Resources */ = { 296 | isa = PBXShellScriptBuildPhase; 297 | buildActionMask = 2147483647; 298 | files = ( 299 | ); 300 | inputPaths = ( 301 | ); 302 | name = "[CP] Copy Pods Resources"; 303 | outputPaths = ( 304 | ); 305 | runOnlyForDeploymentPostprocessing = 0; 306 | shellPath = /bin/sh; 307 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; 308 | showEnvVarsInLog = 0; 309 | }; 310 | /* End PBXShellScriptBuildPhase section */ 311 | 312 | /* Begin PBXSourcesBuildPhase section */ 313 | 97C146EA1CF9000F007C117D /* Sources */ = { 314 | isa = PBXSourcesBuildPhase; 315 | buildActionMask = 2147483647; 316 | files = ( 317 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 318 | 97C146F31CF9000F007C117D /* main.m in Sources */, 319 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 320 | ); 321 | runOnlyForDeploymentPostprocessing = 0; 322 | }; 323 | /* End PBXSourcesBuildPhase section */ 324 | 325 | /* Begin PBXVariantGroup section */ 326 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 327 | isa = PBXVariantGroup; 328 | children = ( 329 | 97C146FB1CF9000F007C117D /* Base */, 330 | ); 331 | name = Main.storyboard; 332 | sourceTree = ""; 333 | }; 334 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 335 | isa = PBXVariantGroup; 336 | children = ( 337 | 97C147001CF9000F007C117D /* Base */, 338 | ); 339 | name = LaunchScreen.storyboard; 340 | sourceTree = ""; 341 | }; 342 | /* End PBXVariantGroup section */ 343 | 344 | /* Begin XCBuildConfiguration section */ 345 | 97C147031CF9000F007C117D /* Debug */ = { 346 | isa = XCBuildConfiguration; 347 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 348 | buildSettings = { 349 | ALWAYS_SEARCH_USER_PATHS = NO; 350 | CLANG_ANALYZER_NONNULL = YES; 351 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 352 | CLANG_CXX_LIBRARY = "libc++"; 353 | CLANG_ENABLE_MODULES = YES; 354 | CLANG_ENABLE_OBJC_ARC = YES; 355 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 356 | CLANG_WARN_BOOL_CONVERSION = YES; 357 | CLANG_WARN_COMMA = YES; 358 | CLANG_WARN_CONSTANT_CONVERSION = YES; 359 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 360 | CLANG_WARN_EMPTY_BODY = YES; 361 | CLANG_WARN_ENUM_CONVERSION = YES; 362 | CLANG_WARN_INFINITE_RECURSION = YES; 363 | CLANG_WARN_INT_CONVERSION = YES; 364 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 365 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 366 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 367 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 368 | CLANG_WARN_STRICT_PROTOTYPES = YES; 369 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 370 | CLANG_WARN_UNREACHABLE_CODE = YES; 371 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 372 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 373 | COPY_PHASE_STRIP = NO; 374 | DEBUG_INFORMATION_FORMAT = dwarf; 375 | ENABLE_STRICT_OBJC_MSGSEND = YES; 376 | ENABLE_TESTABILITY = YES; 377 | GCC_C_LANGUAGE_STANDARD = gnu99; 378 | GCC_DYNAMIC_NO_PIC = NO; 379 | GCC_NO_COMMON_BLOCKS = YES; 380 | GCC_OPTIMIZATION_LEVEL = 0; 381 | GCC_PREPROCESSOR_DEFINITIONS = ( 382 | "DEBUG=1", 383 | "$(inherited)", 384 | ); 385 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 386 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 387 | GCC_WARN_UNDECLARED_SELECTOR = YES; 388 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 389 | GCC_WARN_UNUSED_FUNCTION = YES; 390 | GCC_WARN_UNUSED_VARIABLE = YES; 391 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 392 | MTL_ENABLE_DEBUG_INFO = YES; 393 | ONLY_ACTIVE_ARCH = YES; 394 | SDKROOT = iphoneos; 395 | TARGETED_DEVICE_FAMILY = "1,2"; 396 | }; 397 | name = Debug; 398 | }; 399 | 97C147041CF9000F007C117D /* Release */ = { 400 | isa = XCBuildConfiguration; 401 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 402 | buildSettings = { 403 | ALWAYS_SEARCH_USER_PATHS = NO; 404 | CLANG_ANALYZER_NONNULL = YES; 405 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 406 | CLANG_CXX_LIBRARY = "libc++"; 407 | CLANG_ENABLE_MODULES = YES; 408 | CLANG_ENABLE_OBJC_ARC = YES; 409 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 410 | CLANG_WARN_BOOL_CONVERSION = YES; 411 | CLANG_WARN_COMMA = YES; 412 | CLANG_WARN_CONSTANT_CONVERSION = YES; 413 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 414 | CLANG_WARN_EMPTY_BODY = YES; 415 | CLANG_WARN_ENUM_CONVERSION = YES; 416 | CLANG_WARN_INFINITE_RECURSION = YES; 417 | CLANG_WARN_INT_CONVERSION = YES; 418 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 419 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 420 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 421 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 422 | CLANG_WARN_STRICT_PROTOTYPES = YES; 423 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 424 | CLANG_WARN_UNREACHABLE_CODE = YES; 425 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 426 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 427 | COPY_PHASE_STRIP = NO; 428 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 429 | ENABLE_NS_ASSERTIONS = NO; 430 | ENABLE_STRICT_OBJC_MSGSEND = YES; 431 | GCC_C_LANGUAGE_STANDARD = gnu99; 432 | GCC_NO_COMMON_BLOCKS = YES; 433 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 434 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 435 | GCC_WARN_UNDECLARED_SELECTOR = YES; 436 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 437 | GCC_WARN_UNUSED_FUNCTION = YES; 438 | GCC_WARN_UNUSED_VARIABLE = YES; 439 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 440 | MTL_ENABLE_DEBUG_INFO = NO; 441 | SDKROOT = iphoneos; 442 | TARGETED_DEVICE_FAMILY = "1,2"; 443 | VALIDATE_PRODUCT = YES; 444 | }; 445 | name = Release; 446 | }; 447 | 97C147061CF9000F007C117D /* Debug */ = { 448 | isa = XCBuildConfiguration; 449 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 450 | buildSettings = { 451 | ARCHS = arm64; 452 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 453 | ENABLE_BITCODE = NO; 454 | FRAMEWORK_SEARCH_PATHS = ( 455 | "$(inherited)", 456 | "$(PROJECT_DIR)/Flutter", 457 | ); 458 | INFOPLIST_FILE = Runner/Info.plist; 459 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 460 | LIBRARY_SEARCH_PATHS = ( 461 | "$(inherited)", 462 | "$(PROJECT_DIR)/Flutter", 463 | ); 464 | PRODUCT_BUNDLE_IDENTIFIER = com.flitter; 465 | PRODUCT_NAME = "$(TARGET_NAME)"; 466 | }; 467 | name = Debug; 468 | }; 469 | 97C147071CF9000F007C117D /* Release */ = { 470 | isa = XCBuildConfiguration; 471 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 472 | buildSettings = { 473 | ARCHS = arm64; 474 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 475 | ENABLE_BITCODE = NO; 476 | FRAMEWORK_SEARCH_PATHS = ( 477 | "$(inherited)", 478 | "$(PROJECT_DIR)/Flutter", 479 | ); 480 | INFOPLIST_FILE = Runner/Info.plist; 481 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 482 | LIBRARY_SEARCH_PATHS = ( 483 | "$(inherited)", 484 | "$(PROJECT_DIR)/Flutter", 485 | ); 486 | PRODUCT_BUNDLE_IDENTIFIER = com.flitter; 487 | PRODUCT_NAME = "$(TARGET_NAME)"; 488 | }; 489 | name = Release; 490 | }; 491 | /* End XCBuildConfiguration section */ 492 | 493 | /* Begin XCConfigurationList section */ 494 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 495 | isa = XCConfigurationList; 496 | buildConfigurations = ( 497 | 97C147031CF9000F007C117D /* Debug */, 498 | 97C147041CF9000F007C117D /* Release */, 499 | ); 500 | defaultConfigurationIsVisible = 0; 501 | defaultConfigurationName = Release; 502 | }; 503 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 504 | isa = XCConfigurationList; 505 | buildConfigurations = ( 506 | 97C147061CF9000F007C117D /* Debug */, 507 | 97C147071CF9000F007C117D /* Release */, 508 | ); 509 | defaultConfigurationIsVisible = 0; 510 | defaultConfigurationName = Release; 511 | }; 512 | /* End XCConfigurationList section */ 513 | }; 514 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 515 | } 516 | -------------------------------------------------------------------------------- /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 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 7 | [GeneratedPluginRegistrant registerWithRegistry:self]; 8 | // Override point for customization after application launch. 9 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 10 | } 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /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/dart-flitter/flitter/369e337768566f3d0b2c947fac23bf7900b24cf5/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/dart-flitter/flitter/369e337768566f3d0b2c947fac23bf7900b24cf5/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/dart-flitter/flitter/369e337768566f3d0b2c947fac23bf7900b24cf5/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/dart-flitter/flitter/369e337768566f3d0b2c947fac23bf7900b24cf5/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/dart-flitter/flitter/369e337768566f3d0b2c947fac23bf7900b24cf5/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/dart-flitter/flitter/369e337768566f3d0b2c947fac23bf7900b24cf5/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/dart-flitter/flitter/369e337768566f3d0b2c947fac23bf7900b24cf5/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/dart-flitter/flitter/369e337768566f3d0b2c947fac23bf7900b24cf5/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/dart-flitter/flitter/369e337768566f3d0b2c947fac23bf7900b24cf5/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/dart-flitter/flitter/369e337768566f3d0b2c947fac23bf7900b24cf5/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/dart-flitter/flitter/369e337768566f3d0b2c947fac23bf7900b24cf5/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/dart-flitter/flitter/369e337768566f3d0b2c947fac23bf7900b24cf5/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/dart-flitter/flitter/369e337768566f3d0b2c947fac23bf7900b24cf5/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/dart-flitter/flitter/369e337768566f3d0b2c947fac23bf7900b24cf5/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/dart-flitter/flitter/369e337768566f3d0b2c947fac23bf7900b24cf5/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/dart-flitter/flitter/369e337768566f3d0b2c947fac23bf7900b24cf5/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-flitter/flitter/369e337768566f3d0b2c947fac23bf7900b24cf5/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-flitter/flitter/369e337768566f3d0b2c947fac23bf7900b24cf5/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/GeneratedPluginRegistrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | #ifndef GeneratedPluginRegistrant_h 6 | #define GeneratedPluginRegistrant_h 7 | 8 | #import 9 | 10 | @interface GeneratedPluginRegistrant : NSObject 11 | + (void)registerWithRegistry:(NSObject*)registry; 12 | @end 13 | 14 | #endif /* GeneratedPluginRegistrant_h */ 15 | -------------------------------------------------------------------------------- /ios/Runner/GeneratedPluginRegistrant.m: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | #import "GeneratedPluginRegistrant.h" 6 | #import 7 | #import 8 | #import 9 | 10 | @implementation GeneratedPluginRegistrant 11 | 12 | + (void)registerWithRegistry:(NSObject*)registry { 13 | [FlutterWebviewPlugin registerWithRegistrar:[registry registrarForPlugin:@"FlutterWebviewPlugin"]]; 14 | [FLTSharedPreferencesPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTSharedPreferencesPlugin"]]; 15 | [FLTUrlLauncherPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTUrlLauncherPlugin"]]; 16 | } 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /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 | flitter 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 | NSAppTransportSecurity 49 | 50 | NSAllowsArbitraryLoads 51 | 52 | NSExceptionDomains 53 | 54 | localhost 55 | 56 | NSExceptionAllowsInsecureHTTPLoads 57 | 58 | NSIncludesSubdomains 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char * argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/app.dart: -------------------------------------------------------------------------------- 1 | library flitter.app; 2 | 3 | import 'dart:async'; 4 | import 'package:flitter/redux/actions.dart'; 5 | import 'package:flitter/redux/flitter_app_state.dart'; 6 | import 'package:flitter/redux/store.dart'; 7 | import 'package:flitter/services/flitter_auth.dart'; 8 | import 'package:flitter/services/flitter_request.dart'; 9 | import 'package:flitter/widgets/routes/group.dart'; 10 | import 'package:flitter/widgets/routes/home.dart'; 11 | import 'package:flitter/widgets/routes/login.dart'; 12 | import 'package:flitter/widgets/routes/people.dart'; 13 | import 'package:flitter/widgets/routes/settings.dart'; 14 | import 'package:flutter/material.dart'; 15 | import 'package:shared_preferences/shared_preferences.dart'; 16 | 17 | const appName = "Flitter"; 18 | 19 | class Splash extends StatelessWidget { 20 | @override 21 | Widget build(BuildContext context) { 22 | return new MaterialApp( 23 | home: new Scaffold( 24 | body: new Column(children: [ 25 | new Center( 26 | child: new FlutterLogo( 27 | colors: themeStore?.state?.theme?.accentColor ?? Colors.pink, 28 | size: 80.0)), 29 | new Center( 30 | child: new Text(appName, style: new TextStyle(fontSize: 32.0))), 31 | new Center( 32 | child: 33 | new Text("for Gitter", style: new TextStyle(fontSize: 16.0))) 34 | ], mainAxisAlignment: MainAxisAlignment.center)), 35 | theme: themeStore?.state?.theme); 36 | } 37 | } 38 | 39 | class LoadingView extends StatelessWidget { 40 | @override 41 | Widget build(BuildContext context) => 42 | new Center(child: new CircularProgressIndicator()); 43 | } 44 | 45 | class App extends StatefulWidget { 46 | @override 47 | _AppState createState() => new _AppState(); 48 | } 49 | 50 | class _AppState extends State { 51 | var _subscription; 52 | var _themeSubscription; 53 | 54 | _AppState() { 55 | _subscription = gitterStore.onChange.listen((_) async { 56 | setState(() {}); 57 | }); 58 | _themeSubscription = themeStore.onChange.listen((_) { 59 | setState(() {}); 60 | }); 61 | } 62 | 63 | @override 64 | void dispose() { 65 | super.dispose(); 66 | _subscription.cancel(); 67 | _themeSubscription.cancel(); 68 | } 69 | 70 | @override 71 | Widget build(BuildContext context) { 72 | if (gitterApi == null || gitterToken == null) { 73 | return new LoginView(); 74 | } 75 | 76 | return new MaterialApp( 77 | theme: themeStore.state.theme, 78 | title: appName, 79 | routes: { 80 | HomeView.path: (BuildContext context) => new HomeView(), 81 | PeopleView.path: (BuildContext context) => new PeopleView(), 82 | GroupView.path: (BuildContext context) => new GroupView(), 83 | SettingsView.path: (BuildContext context) => new SettingsView() 84 | }); 85 | } 86 | } 87 | 88 | Future run() async { 89 | runApp(new Splash()); 90 | 91 | await _init(); 92 | 93 | runApp(new App()); 94 | } 95 | 96 | Future _init() async { 97 | gitterStore = new GitterStore(); 98 | themeStore = new ThemeStore(); 99 | final token = await FlitterAuth.getToken(); 100 | if (token != null) { 101 | await initStores(token); 102 | } 103 | 104 | SharedPreferences prefs = await SharedPreferences.getInstance(); 105 | 106 | bool bright = prefs.getBool(ThemeState.kBrightnessKey); 107 | int primary = prefs.getInt(ThemeState.kPrimaryColorKey); 108 | int accent = prefs.getInt(ThemeState.kAccentColorKey); 109 | 110 | themeStore.dispatch(new ChangeThemeAction( 111 | brightness: bright == true ? Brightness.dark : Brightness.light, 112 | primaryColor: 113 | primary != null && primary >= 0 && primary > Colors.primaries.length 114 | ? Colors.primaries[primary] 115 | : null, 116 | accentColor: 117 | accent != null && accent >= 0 && accent > Colors.accents.length 118 | ? Colors.accents[accent] 119 | : null)); 120 | } 121 | -------------------------------------------------------------------------------- /lib/intl/messages_all.dart: -------------------------------------------------------------------------------- 1 | import 'package:intl/intl.dart'; 2 | 3 | String allConversations() => 4 | Intl.message("All Conversations", name: "allConversations", args: []); 5 | 6 | String people() => Intl.message("People", name: "people", args: []); 7 | 8 | String typeChatMessage() => Intl.message("Touch here to type a chat message.", 9 | name: "typeChatMessage", args: []); 10 | 11 | String communities() => 12 | Intl.message("Communities", name: "communities", args: []); 13 | 14 | String logout() => Intl.message("Logout", name: "logout", args: []); 15 | 16 | String theme() => Intl.message("Theme", name: "theme", args: []); 17 | 18 | String settings() => Intl.message("Settings", name: "settings", args: []); 19 | 20 | String darkMode() => Intl.message("Dark mode", name: "darkMode", args: []); 21 | 22 | String primaryColor() => 23 | Intl.message("Primary Color", name: "primaryColor", args: []); 24 | 25 | String accentColor() => 26 | Intl.message("Accent Color", name: "accentColor", args: []); 27 | -------------------------------------------------------------------------------- /lib/redux/actions.dart: -------------------------------------------------------------------------------- 1 | library flitter.redux.actions; 2 | 3 | import 'package:gitter/gitter.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:gitter/src/faye.dart'; 6 | 7 | abstract class FlitterAction { 8 | FlitterAction(); 9 | 10 | String toString() => '$runtimeType'; 11 | } 12 | 13 | class AuthGitterAction extends FlitterAction { 14 | final GitterToken token; 15 | final GitterFayeSubscriber subscriber; 16 | 17 | AuthGitterAction(this.token, this.subscriber); 18 | } 19 | 20 | class FetchRoomsAction extends FlitterAction { 21 | final Iterable rooms; 22 | 23 | FetchRoomsAction(this.rooms); 24 | } 25 | 26 | class FetchGroupsAction extends FlitterAction { 27 | final Iterable groups; 28 | 29 | FetchGroupsAction(this.groups); 30 | } 31 | 32 | class LogoutAction extends FlitterAction { 33 | LogoutAction(); 34 | } 35 | 36 | class FetchUser extends FlitterAction { 37 | final User user; 38 | 39 | FetchUser(this.user); 40 | } 41 | 42 | class SelectRoomAction extends FlitterAction { 43 | final Room room; 44 | 45 | SelectRoomAction(this.room); 46 | } 47 | 48 | class FetchMessagesForCurrentRoomAction extends FlitterAction { 49 | final Iterable messages; 50 | 51 | FetchMessagesForCurrentRoomAction(this.messages); 52 | } 53 | 54 | class OnMessagesForCurrentRoom extends FlitterAction { 55 | final Iterable messages; 56 | 57 | OnMessagesForCurrentRoom(this.messages); 58 | } 59 | 60 | class OnMessageForCurrentRoom extends FlitterAction { 61 | final Message message; 62 | 63 | OnMessageForCurrentRoom(this.message); 64 | } 65 | 66 | class JoinRoomAction extends FlitterAction { 67 | final Room room; 68 | 69 | JoinRoomAction(this.room); 70 | } 71 | 72 | class LeaveRoomAction extends FlitterAction { 73 | final Room room; 74 | 75 | LeaveRoomAction(this.room); 76 | } 77 | 78 | class OnSendMessage extends FlitterAction { 79 | final Message message; 80 | 81 | OnSendMessage(this.message); 82 | } 83 | 84 | class FetchRoomsOfGroup extends FlitterAction { 85 | final Iterable rooms; 86 | 87 | FetchRoomsOfGroup(this.rooms); 88 | } 89 | 90 | class SelectGroupAction extends FlitterAction { 91 | final Group group; 92 | 93 | SelectGroupAction(this.group); 94 | } 95 | 96 | class ShowSearchBarAction extends FlitterAction { 97 | ShowSearchBarAction(); 98 | } 99 | 100 | class StartSearchAction extends FlitterAction { 101 | StartSearchAction(); 102 | } 103 | 104 | class EndSearchAction extends FlitterAction { 105 | EndSearchAction(); 106 | } 107 | 108 | class FetchSearchAction extends FlitterAction { 109 | final Iterable result; 110 | 111 | FetchSearchAction(this.result); 112 | } 113 | 114 | class ChangeThemeAction extends FlitterAction { 115 | final Brightness brightness; 116 | final MaterialColor primaryColor; 117 | final MaterialAccentColor accentColor; 118 | 119 | ChangeThemeAction({this.brightness, this.primaryColor, this.accentColor}); 120 | } 121 | 122 | class UnreadMessagesForRoom extends FlitterAction { 123 | final String roomId; 124 | final num addMessage; 125 | final num removeMessage; 126 | 127 | UnreadMessagesForRoom( 128 | {this.roomId, this.addMessage: 0, this.removeMessage: 0}); 129 | } 130 | -------------------------------------------------------------------------------- /lib/redux/flitter_app_reducer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flitter/redux/actions.dart'; 2 | import 'package:flitter/redux/flitter_app_state.dart'; 3 | import 'package:flitter/redux/store.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:gitter/gitter.dart'; 6 | import 'package:redux/redux.dart' as redux; 7 | 8 | T orElseNull() => null; 9 | 10 | class ThemeReducer extends redux.ReducerClass { 11 | final _mapper = const {ChangeThemeAction: _changeThemeAction}; 12 | 13 | @override 14 | ThemeState call(ThemeState state, action) { 15 | Function reducer = _mapper[action.runtimeType]; 16 | return reducer != null ? reducer(state, action) : state; 17 | } 18 | } 19 | 20 | ThemeState _changeThemeAction(ThemeState state, ChangeThemeAction action) { 21 | return state.apply( 22 | primaryColor: action.primaryColor, 23 | brightness: action.brightness, 24 | accentColor: action.accentColor); 25 | } 26 | 27 | class FlitterLoggingMiddleware 28 | implements redux.MiddlewareClass { 29 | const FlitterLoggingMiddleware(); 30 | 31 | call(redux.Store store, action, next) { 32 | debugPrint('${new DateTime.now()}: $action'); 33 | next(action); 34 | } 35 | } 36 | 37 | class GitterLoggingMiddleware 38 | implements redux.MiddlewareClass { 39 | const GitterLoggingMiddleware(); 40 | 41 | call(redux.Store store, action, next) { 42 | debugPrint('${new DateTime.now()}: $action'); 43 | next(action); 44 | } 45 | } 46 | 47 | class FlitterAppReducer extends redux.ReducerClass { 48 | final _mapper = const { 49 | FetchRoomsAction: _fetchRooms, 50 | FetchGroupsAction: _fetchGroups, 51 | FetchUser: _fetchUser, 52 | SelectRoomAction: _selectRoom, 53 | OnMessagesForCurrentRoom: _onMessages, 54 | OnSendMessage: _onSendMessage, 55 | FetchMessagesForCurrentRoomAction: _fetchMessages, 56 | JoinRoomAction: _joinRoom, 57 | LeaveRoomAction: _leaveRoom, 58 | SelectGroupAction: _selectGroup, 59 | FetchRoomsOfGroup: _fetchRoomsOfGroup, 60 | ShowSearchBarAction: _showSearchBar, 61 | StartSearchAction: _startSearch, 62 | EndSearchAction: _endSearch, 63 | FetchSearchAction: _fetchSearch, 64 | OnMessageForCurrentRoom: _onMessageForCurrentRoom, 65 | UnreadMessagesForRoom: _unreadMessageForRoom 66 | }; 67 | 68 | @override 69 | FlitterAppState call(FlitterAppState state, action) { 70 | Function reducer = _mapper[action.runtimeType]; 71 | return reducer != null ? reducer(state, action) : state; 72 | } 73 | } 74 | 75 | FlitterAppState _unreadMessageForRoom( 76 | FlitterAppState state, UnreadMessagesForRoom action) { 77 | if (action.roomId != null) { 78 | Room room = state.rooms.firstWhere((Room room) => room.id == action.roomId, 79 | orElse: orElseNull); 80 | room.unreadItems += action.addMessage; 81 | room.unreadItems -= action.removeMessage; 82 | 83 | List rooms = 84 | state.rooms.where((Room room) => room.id != action.roomId).toList(); 85 | rooms.add(room); 86 | 87 | return state.apply(rooms: _sortRooms(rooms)); 88 | } 89 | return state; 90 | } 91 | 92 | FlitterAppState _showSearchBar( 93 | FlitterAppState state, ShowSearchBarAction action) { 94 | return state.apply(search: state.search.apply(searching: true, result: [])); 95 | } 96 | 97 | FlitterAppState _startSearch(FlitterAppState state, StartSearchAction action) { 98 | return state.apply( 99 | search: 100 | state.search.apply(searching: true, requesting: true, result: [])); 101 | } 102 | 103 | FlitterAppState _fetchSearch(FlitterAppState state, FetchSearchAction action) { 104 | return state.apply( 105 | search: state.search 106 | .apply(searching: true, requesting: false, result: action.result)); 107 | } 108 | 109 | FlitterAppState _endSearch(FlitterAppState state, EndSearchAction action) { 110 | return state.apply( 111 | search: 112 | state.search.apply(searching: false, requesting: false, result: [])); 113 | } 114 | 115 | List _sortRooms(List rooms) { 116 | final unreadRooms = rooms.where((Room r) => r.unreadItems > 0).toList(); 117 | unreadRooms.sort((Room a, Room b) { 118 | return b.unreadItems - a.unreadItems; 119 | }); 120 | 121 | final readedRooms = rooms.where((Room r) => r.unreadItems == 0).toList(); 122 | readedRooms.sort((Room a, Room b) { 123 | if (a.lastAccessTime != null && b.lastAccessTime != null) { 124 | DateTime lA = parseLastAccessTime(a.lastAccessTime); 125 | DateTime lB = parseLastAccessTime(b.lastAccessTime); 126 | return lB.millisecondsSinceEpoch - lA.millisecondsSinceEpoch; 127 | } 128 | return 0; 129 | }); 130 | 131 | final _rooms = []; 132 | _rooms.addAll(unreadRooms); 133 | _rooms.addAll(readedRooms); 134 | 135 | return _rooms; 136 | } 137 | 138 | FlitterAppState _fetchRooms(FlitterAppState state, FetchRoomsAction action) { 139 | return state.apply(rooms: _sortRooms(action.rooms)); 140 | } 141 | 142 | FlitterAppState _fetchGroups(FlitterAppState state, FetchGroupsAction action) { 143 | return state.apply(groups: action.groups); 144 | } 145 | 146 | FlitterAppState _fetchUser(FlitterAppState state, FetchUser action) { 147 | return state.apply(user: action.user); 148 | } 149 | 150 | FlitterAppState _selectRoom(FlitterAppState state, SelectRoomAction action) { 151 | CurrentRoomState current = 152 | new CurrentRoomState(room: action.room, messages: null); 153 | return state.apply(selectedRoom: current); 154 | } 155 | 156 | FlitterAppState _fetchMessages( 157 | FlitterAppState state, FetchMessagesForCurrentRoomAction action) { 158 | final currentRoom = state.selectedRoom?.apply(messages: action.messages); 159 | return state.apply(selectedRoom: currentRoom); 160 | } 161 | 162 | FlitterAppState _onMessages( 163 | FlitterAppState state, OnMessagesForCurrentRoom action) { 164 | final messages = new List.from(action.messages); 165 | final messagesRooms = 166 | new List.from(state.selectedRoom.messages ?? []); 167 | messages.addAll(messagesRooms ?? []); 168 | final currentRoom = state.selectedRoom?.apply(messages: messages); 169 | return state.apply(selectedRoom: currentRoom); 170 | } 171 | 172 | FlitterAppState _joinRoom(FlitterAppState state, JoinRoomAction action) { 173 | final rooms = new List.from(state.rooms); 174 | rooms.add(action.room); 175 | return state.apply(rooms: rooms); 176 | } 177 | 178 | FlitterAppState _leaveRoom(FlitterAppState state, LeaveRoomAction action) { 179 | final rooms = new List.from(state.rooms); 180 | rooms.removeWhere((Room room) => room.id == action.room.id); 181 | return state.apply(rooms: rooms); 182 | } 183 | 184 | FlitterAppState _onSendMessage(FlitterAppState state, OnSendMessage action) { 185 | Iterable messages = _addOrUpdateMessage(state, action.message); 186 | CurrentRoomState currentRoom = state.selectedRoom?.apply(messages: messages); 187 | return state.apply(selectedRoom: currentRoom); 188 | } 189 | 190 | FlitterAppState _selectGroup(FlitterAppState state, SelectGroupAction action) { 191 | CurrentGroupState current = new CurrentGroupState(group: action.group); 192 | return state.apply(selectedGroup: current); 193 | } 194 | 195 | FlitterAppState _fetchRoomsOfGroup( 196 | FlitterAppState state, FetchRoomsOfGroup action) { 197 | CurrentGroupState current = new CurrentGroupState( 198 | group: state.selectedGroup.group, rooms: action.rooms); 199 | return state.apply(selectedGroup: current); 200 | } 201 | 202 | FlitterAppState _onMessageForCurrentRoom( 203 | FlitterAppState state, OnMessageForCurrentRoom action) { 204 | Iterable messages = _addOrUpdateMessage(state, action.message); 205 | 206 | final currentRoom = state.selectedRoom?.apply(messages: messages); 207 | return state.apply(selectedRoom: currentRoom); 208 | } 209 | 210 | Iterable _addOrUpdateMessage(FlitterAppState state, Message message) { 211 | List messages = new List.from(state.selectedRoom.messages ?? []); 212 | 213 | final exist = 214 | messages.firstWhere((msg) => msg.id == message.id, orElse: orElseNull); 215 | 216 | if (exist != null) { 217 | final idx = messages.indexOf(exist); 218 | messages[idx] = message; 219 | } else { 220 | messages.add(message); 221 | } 222 | return messages; 223 | } 224 | 225 | class GitterReducer extends redux.ReducerClass { 226 | final _mapper = const { 227 | AuthGitterAction: _initGitter, 228 | LogoutAction: _logout 229 | }; 230 | 231 | @override 232 | GitterState call(GitterState state, action) { 233 | Function reducer = _mapper[action.runtimeType]; 234 | return reducer != null ? reducer(state, action) : state; 235 | } 236 | } 237 | 238 | GitterState _initGitter(GitterState state, AuthGitterAction action) { 239 | GitterApi api; 240 | if (action.token != null) { 241 | api = new GitterApi(action.token); 242 | } 243 | return state.apply( 244 | api: api, token: action.token, subscriber: action.subscriber); 245 | } 246 | 247 | GitterState _logout(GitterState state, LogoutAction action) { 248 | state.subscriber?.close(); 249 | flitterStore = null; 250 | return new GitterState.initial(); 251 | } 252 | -------------------------------------------------------------------------------- /lib/redux/flitter_app_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:gitter/gitter.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:gitter/src/faye.dart'; 4 | 5 | class ThemeState { 6 | static final kBrightnessKey = "BrightnessKey"; 7 | static final kPrimaryColorKey = "PrimaryColorKey"; 8 | static final kAccentColorKey = "kAccentColorKey"; 9 | 10 | final ThemeData _theme; 11 | final Brightness brightness; 12 | final MaterialColor primaryColor; 13 | final MaterialAccentColor accentColor; 14 | 15 | ThemeData get theme => _theme; 16 | 17 | ThemeState({this.brightness, this.primaryColor, this.accentColor}) 18 | : _theme = new ThemeData( 19 | brightness: brightness, 20 | primarySwatch: primaryColor, 21 | accentColor: accentColor); 22 | 23 | factory ThemeState.initial() => new ThemeState( 24 | brightness: Brightness.light, 25 | primaryColor: Colors.indigo, 26 | accentColor: Colors.pinkAccent); 27 | 28 | ThemeState apply( 29 | {Brightness brightness, 30 | MaterialColor primaryColor, 31 | MaterialAccentColor accentColor}) { 32 | return new ThemeState( 33 | brightness: brightness ?? this.brightness, 34 | primaryColor: primaryColor ?? this.primaryColor, 35 | accentColor: accentColor ?? this.accentColor); 36 | } 37 | } 38 | 39 | class SearchState { 40 | final Iterable result; 41 | final bool requesting; 42 | final bool searching; 43 | 44 | SearchState({this.result, this.requesting, this.searching}); 45 | 46 | SearchState.initial() 47 | : result = [], 48 | requesting = false, 49 | searching = false; 50 | 51 | SearchState apply({Iterable result, bool requesting, bool searching}) { 52 | return new SearchState( 53 | result: result ?? this.result, 54 | requesting: requesting ?? this.requesting, 55 | searching: searching ?? this.searching); 56 | } 57 | } 58 | 59 | class CurrentRoomState { 60 | final Room room; 61 | final Iterable messages; 62 | 63 | CurrentRoomState({this.room, this.messages}); 64 | 65 | CurrentRoomState apply({Room room, Iterable messages}) { 66 | return new CurrentRoomState( 67 | room: room ?? this.room, messages: messages ?? this.messages); 68 | } 69 | } 70 | 71 | class CurrentGroupState { 72 | final Group group; 73 | final Iterable rooms; 74 | 75 | CurrentGroupState({this.group, this.rooms}); 76 | 77 | CurrentGroupState apply({Group group, Iterable rooms}) { 78 | return new CurrentGroupState( 79 | group: group ?? this.group, rooms: rooms ?? this.rooms); 80 | } 81 | } 82 | 83 | class FlitterAppState { 84 | final Iterable rooms; 85 | final Iterable groups; 86 | final User user; 87 | final CurrentRoomState selectedRoom; 88 | final CurrentGroupState selectedGroup; 89 | final SearchState search; 90 | 91 | FlitterAppState( 92 | {this.rooms, 93 | this.groups, 94 | this.user, 95 | this.search, 96 | this.selectedRoom, 97 | this.selectedGroup}); 98 | 99 | FlitterAppState.initial() 100 | : rooms = null, 101 | groups = null, 102 | user = null, 103 | selectedRoom = null, 104 | search = new SearchState.initial(), 105 | selectedGroup = null; 106 | 107 | FlitterAppState apply( 108 | {Iterable rooms, 109 | Iterable groups, 110 | User user, 111 | bool init, 112 | CurrentRoomState selectedRoom, 113 | SearchState search, 114 | CurrentGroupState selectedGroup, 115 | GitterApi api, 116 | GitterToken token}) { 117 | return new FlitterAppState( 118 | rooms: rooms ?? this.rooms, 119 | groups: groups ?? this.groups, 120 | user: user ?? this.user, 121 | selectedRoom: selectedRoom ?? this.selectedRoom, 122 | search: search ?? this.search, 123 | selectedGroup: selectedGroup ?? this.selectedGroup); 124 | } 125 | } 126 | 127 | class GitterState { 128 | final GitterApi api; 129 | final GitterToken token; 130 | final GitterFayeSubscriber subscriber; 131 | 132 | GitterState({this.api, this.token, this.subscriber}); 133 | 134 | GitterState apply( 135 | {GitterApi api, GitterToken token, GitterFayeSubscriber subscriber}) { 136 | return new GitterState( 137 | api: api ?? this.api, 138 | token: token ?? this.token, 139 | subscriber: subscriber ?? this.subscriber); 140 | } 141 | 142 | GitterState.initial() 143 | : api = null, 144 | token = null, 145 | subscriber = null; 146 | } 147 | -------------------------------------------------------------------------------- /lib/redux/store.dart: -------------------------------------------------------------------------------- 1 | library flitter.redux.store; 2 | 3 | import 'package:flitter/redux/flitter_app_reducer.dart'; 4 | import 'package:flitter/redux/flitter_app_state.dart'; 5 | import 'package:gitter/gitter.dart'; 6 | import 'package:gitter/src/faye.dart'; 7 | import 'package:redux/redux.dart' as redux; 8 | 9 | class FlitterStore extends redux.Store { 10 | FlitterStore( 11 | {FlitterAppState initialState, 12 | redux.Reducer reducer, 13 | List middlewares: const [ 14 | const FlitterLoggingMiddleware() 15 | ]}) 16 | : super(reducer ?? new FlitterAppReducer(), 17 | initialState: initialState ?? new FlitterAppState.initial(), 18 | middleware: middlewares); 19 | } 20 | 21 | class ThemeStore extends redux.Store { 22 | ThemeStore( 23 | {ThemeState initialState, 24 | redux.Reducer reducer}) 25 | : super(reducer ?? new ThemeReducer(), 26 | initialState: initialState ?? new ThemeState.initial()); 27 | } 28 | 29 | class GitterStore extends redux.Store { 30 | GitterStore( 31 | {GitterState initialState, 32 | redux.Reducer reducer, 33 | List middlewares: const [ 34 | const GitterLoggingMiddleware() 35 | ]}) 36 | : super(reducer ?? new GitterReducer(), 37 | initialState: initialState ?? new GitterState.initial(), 38 | middleware: middlewares); 39 | } 40 | 41 | FlitterStore flitterStore; 42 | GitterStore gitterStore; 43 | ThemeStore themeStore; 44 | 45 | GitterApi get gitterApi => gitterStore.state.api; 46 | GitterToken get gitterToken => gitterStore.state.token; 47 | GitterFayeSubscriber get gitterSubscriber => gitterStore.state.subscriber; 48 | -------------------------------------------------------------------------------- /lib/services/flitter_auth.dart: -------------------------------------------------------------------------------- 1 | library flitter.auth; 2 | 3 | import 'dart:async'; 4 | import 'dart:convert'; 5 | 6 | import 'package:flitter/redux/store.dart'; 7 | import 'package:flitter/services/flitter_config.dart'; 8 | import 'package:gitter/gitter.dart'; 9 | import 'package:flitter/services/flutter_gitter_auth.dart'; 10 | import 'package:flitter/redux/actions.dart'; 11 | import 'package:gitter/src/oauth/oauth.dart'; 12 | import 'package:shared_preferences/shared_preferences.dart'; 13 | 14 | class FlitterAuth { 15 | static const _tokenKey = "gitter_token"; 16 | 17 | static Future getToken() async { 18 | final prefs = await SharedPreferences.getInstance(); 19 | final tokenJson = prefs.getString(_tokenKey); 20 | if (tokenJson == null) { 21 | return null; 22 | } 23 | return new GitterToken.fromJson(JSON.decode(tokenJson)); 24 | } 25 | 26 | static Future saveToken(GitterToken token) async { 27 | final prefs = await SharedPreferences.getInstance(); 28 | prefs.setString( 29 | _tokenKey, token == null ? null : JSON.encode(token?.toMap())); 30 | return prefs.commit(); 31 | } 32 | 33 | static Future auth() async { 34 | final cfg = Config.getInstance(); 35 | final GitterOAuth gitterOAuth = new FlutterGitterOAuth(new AppInformations( 36 | cfg.gitter.appId, 37 | cfg.gitter.appSecret, 38 | cfg.gitter.redirectionUrl, 39 | )); 40 | GitterToken token = await gitterOAuth.signIn(); 41 | await saveToken(token); 42 | return token; 43 | } 44 | 45 | static Future logout() async { 46 | saveToken(null); 47 | gitterStore.dispatch(new LogoutAction()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/services/flitter_config.dart: -------------------------------------------------------------------------------- 1 | import 'package:meta/meta.dart'; 2 | 3 | class Config { 4 | final GitterConfig gitter; 5 | 6 | static Config _instance; 7 | 8 | Config._({this.gitter}); 9 | 10 | static init({@required GitterConfig gitter}) => 11 | _instance ??= new Config._(gitter: gitter); 12 | 13 | static Config getInstance() => _instance; 14 | } 15 | 16 | class GitterConfig { 17 | final String appId; 18 | final String appSecret; 19 | final String redirectionUrl; 20 | 21 | const GitterConfig( 22 | {@required this.appId, 23 | @required this.appSecret, 24 | @required this.redirectionUrl}); 25 | } 26 | -------------------------------------------------------------------------------- /lib/services/flitter_request.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:flitter/redux/actions.dart'; 3 | import 'package:flitter/redux/store.dart'; 4 | import 'package:gitter/gitter.dart'; 5 | import 'package:gitter/src/faye.dart'; 6 | import 'package:gitter/src/models/faye_message.dart'; 7 | 8 | Future> fetchRooms() async { 9 | final rooms = await gitterApi.user.me.rooms(); 10 | flitterStore.dispatch(new FetchRoomsAction(rooms)); 11 | subscribeToUnreadMessages(rooms); 12 | return rooms; 13 | } 14 | 15 | Future fetchUser() async { 16 | final user = await gitterApi.user.me.get(); 17 | flitterStore.dispatch(new FetchUser(user)); 18 | return user; 19 | } 20 | 21 | Future> fetchGroups() async { 22 | final groups = await gitterApi.group.get(); 23 | flitterStore.dispatch(new FetchGroupsAction(groups)); 24 | return groups; 25 | } 26 | 27 | Future> fetchRoomsOfGroup() async { 28 | final groupId = flitterStore.state.selectedGroup.group.id; 29 | final rooms = await gitterApi.group.suggestedRoomsOf(groupId); 30 | flitterStore.dispatch(new FetchRoomsOfGroup(rooms)); 31 | subscribeToUnreadMessages(rooms); 32 | return rooms; 33 | } 34 | 35 | Future> fetchMessagesOfRoom( 36 | String roomId, String beforeId) async { 37 | final messages = 38 | await gitterApi.room.messagesFromRoomId(roomId, beforeId: beforeId); 39 | flitterStore.dispatch(new OnMessagesForCurrentRoom(messages)); 40 | return messages; 41 | } 42 | 43 | Future leaveRoom(Room room) async { 44 | final success = 45 | await gitterApi.room.removeUserFrom(room.id, flitterStore.state.user.id); 46 | if (success == true) { 47 | flitterStore.dispatch(new LeaveRoomAction(room)); 48 | } 49 | return success; 50 | } 51 | 52 | Future joinRoom(Room room) async { 53 | final joinedRoom = 54 | await gitterApi.user.userJoinRoom(flitterStore.state.user.id, room.id); 55 | flitterStore.dispatch(new JoinRoomAction(room)); 56 | return joinedRoom; 57 | } 58 | 59 | Future sendMessage(String value, Room room) async { 60 | final message = await gitterApi.room.sendMessageToRoomId(room.id, value); 61 | flitterStore.dispatch(new OnSendMessage(message)); 62 | return message; 63 | } 64 | 65 | Future search(String query) async { 66 | flitterStore.dispatch(new StartSearchAction()); 67 | final result = []; 68 | result.addAll(await gitterApi.user.search(query, limit: 5)); 69 | result.addAll(await gitterApi.room.search(query, limit: 10)); 70 | flitterStore.dispatch(new FetchSearchAction(result)); 71 | return result; 72 | } 73 | 74 | initStores(GitterToken token) async { 75 | flitterStore = new FlitterStore(); 76 | gitterStore 77 | .dispatch(new AuthGitterAction(token, await initWebSocket(token.access))); 78 | fetchRooms(); 79 | fetchGroups(); 80 | } 81 | 82 | List _mapperUnreads; 83 | 84 | Future initWebSocket(String token) async { 85 | gitterSubscriber?.close(); 86 | _mapperUnreads = []; 87 | GitterFayeSubscriber subscriber = new GitterFayeSubscriber(token); 88 | await subscriber.connect(); 89 | flitterStore.dispatch(new FetchUser(subscriber.user)); 90 | return subscriber; 91 | } 92 | 93 | subscribeToUnreadMessages(List rooms) { 94 | final newRooms = 95 | rooms.map((r) => r.id).where((r) => !_mapperUnreads.contains(r)).toList(); 96 | _mapperUnreads.addAll(newRooms); 97 | for (String roomId in newRooms) { 98 | gitterSubscriber.subscribeToUserRoomUnreadItems( 99 | roomId, gitterSubscriber.user.id, (List messages) { 100 | for (GitterFayeMessage msg in messages) { 101 | if (msg.data != null && 102 | msg.data["notification"] == GitterFayeNotifications.unreadItems) { 103 | flitterStore.dispatch(new UnreadMessagesForRoom( 104 | roomId: roomId, addMessage: msg.data["items"]["chat"].length)); 105 | } else if (msg.data != null && 106 | msg.data["notification"] == 107 | GitterFayeNotifications.unreadItemsRemoved) { 108 | flitterStore.dispatch(new UnreadMessagesForRoom( 109 | roomId: roomId, removeMessage: msg.data["items"]["chat"].length)); 110 | } 111 | } 112 | }); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /lib/services/flutter_gitter_auth.dart: -------------------------------------------------------------------------------- 1 | library gitter.flutter.auth; 2 | 3 | import 'dart:async'; 4 | import 'dart:io'; 5 | 6 | import 'package:flutter/services.dart'; 7 | import 'package:flutter_webview_plugin/flutter_webview_plugin.dart'; 8 | import 'package:gitter/gitter.dart'; 9 | import 'package:gitter/src/oauth/oauth.dart'; 10 | 11 | Future getContent() async { 12 | final content = await rootBundle.loadString("assets/html/success.html"); 13 | if (content.isNotEmpty) { 14 | return content; 15 | } 16 | return """ 17 | 18 | 19 |

You can now close this page !

20 | 21 | 22 | """; 23 | } 24 | 25 | class FlutterGitterOAuth extends GitterOAuth { 26 | final StreamController _onCode = new StreamController(); 27 | 28 | final FlutterWebviewPlugin flutterWebviewPlugin = new FlutterWebviewPlugin(); 29 | 30 | var _isOpen = false; 31 | var _server; 32 | var _onCodeStream; 33 | 34 | Stream get onCode => 35 | _onCodeStream ??= _onCode.stream.asBroadcastStream(); 36 | 37 | FlutterGitterOAuth(AppInformations appInformations, {bool force: false}) 38 | : super(appInformations, force: force); 39 | 40 | @override 41 | Future requestCode() async { 42 | if (shouldRequestCode() && !_isOpen) { 43 | // close any open browser (happen on hot reload) 44 | // FIXME: await flutterWebviewPlugin.close(); 45 | _isOpen = true; 46 | 47 | // init server 48 | _server = await _createServer(); 49 | _listenCode(_server); 50 | 51 | // construct url 52 | final String urlParams = constructUrlParams(); 53 | 54 | // catch onDestroy event of WebView 55 | flutterWebviewPlugin.onDestroy.first.then((_) { 56 | _close(); 57 | }); 58 | 59 | // launch url inside webview 60 | flutterWebviewPlugin.launch("${codeInformations.url}?$urlParams", 61 | clearCookies: true); 62 | 63 | code = await onCode.first; 64 | _close(); 65 | } 66 | return code; 67 | } 68 | 69 | void _close([_]) { 70 | if (_isOpen) { 71 | // close server 72 | _server.close(force: true); 73 | 74 | flutterWebviewPlugin.close(); 75 | } 76 | _isOpen = false; 77 | } 78 | 79 | Future _createServer() async { 80 | final server = await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 8080, 81 | shared: true); 82 | return server; 83 | } 84 | 85 | _listenCode(HttpServer server) { 86 | server.listen((HttpRequest request) async { 87 | final uri = request.uri; 88 | request.response 89 | ..statusCode = 200 90 | ..headers.set("Content-Type", ContentType.HTML.mimeType) 91 | ..write(await getContent()); 92 | 93 | final code = uri.queryParameters["code"]; 94 | final error = uri.queryParameters["error"]; 95 | await request.response.close(); 96 | if (code != null && error == null) { 97 | _onCode.add(code); 98 | } else if (error != null) { 99 | _onCode.add(null); 100 | _onCode.addError(error); 101 | } 102 | }); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /lib/widgets/common/chat_room.dart: -------------------------------------------------------------------------------- 1 | library flitter.common.chat_room_widget; 2 | 3 | import 'dart:async'; 4 | import 'package:flitter/redux/store.dart'; 5 | import 'package:flitter/services/flitter_request.dart'; 6 | import 'package:gitter/gitter.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter/services.dart'; 9 | import 'package:meta/meta.dart'; 10 | import 'package:flitter/intl/messages_all.dart' as intl; 11 | import 'package:intl/intl.dart'; 12 | import 'package:flutter_markdown/flutter_markdown.dart'; 13 | import 'package:url_launcher/url_launcher.dart' as url_launcher; 14 | 15 | final _dateFormat = new DateFormat.MMMd()..add_Hm(); 16 | 17 | class ChatRoom extends StatefulWidget { 18 | final Iterable messages; 19 | final Room room; 20 | final _onNeedData; 21 | 22 | @override 23 | _ChatRoomWidgetState createState() => new _ChatRoomWidgetState(); 24 | 25 | ChatRoom({@required this.messages, @required this.room}) 26 | : _onNeedData = new StreamController(); 27 | 28 | Stream get onNeedDataStream => onNeedDataController.stream; 29 | 30 | StreamController get onNeedDataController => _onNeedData; 31 | } 32 | 33 | class _ChatRoomWidgetState extends State { 34 | bool get _userHasJoined => 35 | flitterStore.state.rooms.any((Room r) => r.id == widget.room.id); 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | var children = [ 40 | new Flexible( 41 | child: new ListView.builder( 42 | reverse: true, 43 | itemCount: widget.messages.length, 44 | itemBuilder: _buildListItem, 45 | )), 46 | ]; 47 | 48 | if (_userHasJoined) { 49 | children.addAll([ 50 | new Divider(height: 1.0), 51 | new Container( 52 | decoration: new BoxDecoration(color: Theme.of(context).cardColor), 53 | child: _buildChatInput()) 54 | ]); 55 | } 56 | 57 | return new Column(children: children); 58 | } 59 | 60 | Widget _buildChatInput() => new ChatInput( 61 | onSubmit: (String value) async { 62 | sendMessage(value, widget.room); 63 | }, 64 | ); 65 | 66 | _shouldMergeMessages(Message message, int index) => 67 | index != widget.messages.length - 1 && 68 | widget.messages.elementAt(index + 1).fromUser.id == message.fromUser.id && 69 | message.sent 70 | .difference(widget.messages.elementAt(index + 1).sent) 71 | .inMinutes <= 72 | 10; 73 | 74 | _buildListItem(BuildContext context, int index) { 75 | final message = widget.messages.elementAt(index); 76 | 77 | if (widget.messages.length >= 50 && index == widget.messages.length - 5) { 78 | widget.onNeedDataController.add(null); 79 | } 80 | 81 | if (_shouldMergeMessages(message, index)) { 82 | return new ChatMessage( 83 | withDivider: false, 84 | withAvatar: false, 85 | withTitle: false, 86 | message: message, 87 | atBottom: index == 0); 88 | } 89 | 90 | return new ChatMessage(message: message, atBottom: index == 0); 91 | } 92 | } 93 | 94 | class ChatInput extends StatefulWidget { 95 | final ValueChanged onSubmit; 96 | 97 | ChatInput({@required this.onSubmit}); 98 | 99 | @override 100 | _ChatInputState createState() => new _ChatInputState(); 101 | } 102 | 103 | class _ChatInputState extends State { 104 | final _textController = new TextEditingController(); 105 | 106 | @override 107 | Widget build(BuildContext context) { 108 | return new Container( 109 | padding: new EdgeInsets.all(8.0), 110 | decoration: new BoxDecoration(color: Theme.of(context).cardColor), 111 | child: new IconTheme( 112 | data: new IconThemeData(color: Theme.of(context).accentColor), 113 | child: new Container( 114 | margin: const EdgeInsets.symmetric(horizontal: 8.0), 115 | child: new Row(children: [ 116 | new Flexible( 117 | child: new TextField( 118 | onSubmitted: (_) => _handleSubmitted(), 119 | controller: _textController, 120 | decoration: new InputDecoration.collapsed( 121 | hintText: intl.typeChatMessage()), 122 | maxLines: 3, 123 | ), 124 | ), 125 | new Container( 126 | margin: new EdgeInsets.symmetric(horizontal: 4.0), 127 | child: new IconButton( 128 | icon: new Icon(Icons.send), 129 | onPressed: _handleSubmitted)), 130 | ])))); 131 | } 132 | 133 | _handleSubmitted() { 134 | String value = _textController.text; 135 | _textController.clear(); 136 | if (value.isNotEmpty) { 137 | widget.onSubmit(value); 138 | } 139 | } 140 | } 141 | 142 | class ChatMessage extends StatelessWidget { 143 | final Message message; 144 | final bool withDivider; 145 | final bool withAvatar; 146 | final bool withTitle; 147 | final bool atBottom; 148 | 149 | ChatMessage( 150 | {@required this.message, 151 | this.withDivider: true, 152 | this.withAvatar: true, 153 | this.withTitle: true, 154 | this.atBottom: false}); 155 | 156 | @override 157 | Widget build(BuildContext context) { 158 | final row = []; 159 | 160 | if (withAvatar) { 161 | row.add(new ChatMessageAvatar( 162 | avatar: new NetworkImage(message.fromUser.avatarUrlSmall))); 163 | } else { 164 | row.add(new Container(width: 54.0)); 165 | } 166 | 167 | row.add(new Expanded( 168 | child: new ChatMessageContent(message: message, withTitle: withTitle))); 169 | 170 | final column = []; 171 | 172 | if (withDivider) { 173 | column.add(new Divider(color: Colors.grey[200])); 174 | } 175 | 176 | column.add(new Padding( 177 | child: new Row( 178 | children: row, crossAxisAlignment: CrossAxisAlignment.start), 179 | padding: new EdgeInsets.only( 180 | bottom: withTitle || atBottom ? 8.0 : 0.0, right: 12.0))); 181 | 182 | return new Column(children: column); 183 | } 184 | } 185 | 186 | class ChatMessageAvatar extends StatelessWidget { 187 | final ImageProvider avatar; 188 | 189 | ChatMessageAvatar({@required this.avatar}); 190 | 191 | @override 192 | Widget build(BuildContext context) { 193 | return new Column(children: [ 194 | new Container( 195 | margin: new EdgeInsets.only(left: 12.0, right: 12.0, top: 12.0), 196 | width: 30.0, 197 | child: new CircleAvatar( 198 | backgroundImage: avatar, backgroundColor: Colors.grey[200]), 199 | ) 200 | ], crossAxisAlignment: CrossAxisAlignment.start); 201 | } 202 | } 203 | 204 | class ChatMessageContent extends StatelessWidget { 205 | final Message message; 206 | final bool withTitle; 207 | 208 | ChatMessageContent({@required this.message, this.withTitle: true}); 209 | 210 | TextStyle _titleTextStyle() { 211 | return new TextStyle(color: Colors.grey); 212 | } 213 | 214 | @override 215 | Widget build(BuildContext context) { 216 | final column = []; 217 | 218 | if (message.fromUser.displayName != null) { 219 | column.add(new AnimatedDefaultTextStyle( 220 | style: _titleTextStyle(), 221 | duration: kThemeChangeDuration, 222 | child: new Container( 223 | padding: new EdgeInsets.only(bottom: 6.0), 224 | child: withTitle 225 | ? new Row(children: [ 226 | new Expanded( 227 | child: new Text(message.fromUser.displayName, 228 | softWrap: true)), 229 | new Text(_dateFormat.format(message.sent)) 230 | ]) 231 | : null))); 232 | } 233 | 234 | column.add(new MarkdownBody( 235 | data: message.text, 236 | //fixme does not seem to work 237 | onTapLink: (String url) async { 238 | bool can = await url_launcher.canLaunch(url); 239 | if (can) { 240 | url_launcher.launch(url); 241 | } 242 | })); 243 | 244 | return new Column( 245 | mainAxisSize: MainAxisSize.min, 246 | crossAxisAlignment: CrossAxisAlignment.start, 247 | children: column); 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /lib/widgets/common/drawer.dart: -------------------------------------------------------------------------------- 1 | library flitter.common.drawer; 2 | 3 | import 'package:flitter/redux/actions.dart'; 4 | import 'package:flitter/redux/store.dart'; 5 | import 'package:flitter/services/flitter_auth.dart'; 6 | import 'package:flitter/widgets/routes/settings.dart'; 7 | import 'package:flutter/src/rendering/sliver.dart'; 8 | import 'package:flutter/src/rendering/sliver_grid.dart'; 9 | import 'package:gitter/gitter.dart'; 10 | import 'package:flitter/widgets/routes/group.dart'; 11 | import 'package:flutter/material.dart'; 12 | import 'package:meta/meta.dart'; 13 | import 'package:flutter/painting.dart'; 14 | import 'package:flitter/intl/messages_all.dart' as intl; 15 | 16 | class FlitterDrawer extends StatefulWidget { 17 | final VoidCallback onTapAllConversation; 18 | final VoidCallback onTapPeoples; 19 | final VoidCallback onTapSettings; 20 | 21 | FlitterDrawer( 22 | {@required this.onTapAllConversation, 23 | @required this.onTapPeoples, 24 | @required this.onTapSettings}); 25 | 26 | @override 27 | _FlitterDrawerState createState() => new _FlitterDrawerState(); 28 | } 29 | 30 | class _FlitterDrawerState extends State { 31 | var _subscription; 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | if (flitterStore.state.user != null) { 36 | return new Drawer( 37 | child: new FlitterDrawerContent( 38 | onTapAllConversation: widget.onTapAllConversation, 39 | onTapPeoples: widget.onTapPeoples, 40 | onTapSettings: widget.onTapSettings)); 41 | } 42 | return new Center(child: new CircularProgressIndicator()); 43 | } 44 | 45 | //////// 46 | 47 | @override 48 | void initState() { 49 | super.initState(); 50 | _subscription = flitterStore.onChange.listen((_) { 51 | setState(() {}); 52 | }); 53 | } 54 | 55 | @override 56 | void dispose() { 57 | super.dispose(); 58 | _subscription.cancel(); 59 | } 60 | } 61 | 62 | class FlitterDrawerContent extends StatelessWidget { 63 | final VoidCallback onTapAllConversation; 64 | final VoidCallback onTapPeoples; 65 | final VoidCallback onTapSettings; 66 | 67 | FlitterDrawerContent( 68 | {@required this.onTapAllConversation, 69 | @required this.onTapPeoples, 70 | @required this.onTapSettings}); 71 | 72 | @override 73 | Widget build(BuildContext context) { 74 | final child = [ 75 | new FlitterDrawerHeader(), 76 | new ListTile( 77 | leading: new Icon(Icons.home), 78 | title: new Text(intl.allConversations()), 79 | onTap: onTapAllConversation), 80 | new ListTile( 81 | leading: new Icon(Icons.person), 82 | title: new Text(intl.people()), 83 | onTap: onTapPeoples), 84 | new ListTile( 85 | leading: new Icon(Icons.settings), 86 | title: new Text(intl.settings()), 87 | onTap: onTapSettings) 88 | ]; 89 | child.addAll(_drawerCommunities(context)); 90 | child.addAll(_drawerFooter(context)); 91 | return new ListView(children: child); 92 | } 93 | 94 | Iterable _drawerCommunities(BuildContext context) { 95 | final communities = [ 96 | new Divider(), 97 | new ListTile(title: new Text(intl.communities()), dense: true) 98 | ]; 99 | 100 | communities.addAll(_buildCommunities(context)); 101 | return communities; 102 | } 103 | 104 | Iterable _drawerFooter(BuildContext context) => 105 | [new Divider(), new FlitterDrawerFooter()]; 106 | 107 | Iterable _buildCommunities(BuildContext context) { 108 | if (flitterStore.state.groups == null) { 109 | return []; 110 | } 111 | return flitterStore.state.groups.map((group) { 112 | return new FlitterDrawerCommunityTile(group: group); 113 | }).toList(); 114 | } 115 | } 116 | 117 | class FlitterDrawerHeader extends StatelessWidget { 118 | @override 119 | Widget build(BuildContext context) { 120 | return new UserAccountsDrawerHeader( 121 | accountName: new Text(flitterStore.state.user.username), 122 | accountEmail: new Text(flitterStore.state.user.displayName), 123 | currentAccountPicture: new CircleAvatar( 124 | backgroundImage: 125 | new NetworkImage(flitterStore.state.user.avatarUrlMedium)), 126 | decoration: new BoxDecoration( 127 | image: new DecorationImage( 128 | image: new AssetImage('assets/images/banner.jpg'), 129 | fit: BoxFit.cover))); 130 | } 131 | } 132 | 133 | class FlitterDrawerFooter extends StatelessWidget { 134 | @override 135 | Widget build(BuildContext context) { 136 | return new ListTile( 137 | leading: new Icon(Icons.exit_to_app), 138 | title: new Text(intl.logout()), 139 | onTap: () { 140 | Navigator.of(context).pop(); 141 | FlitterAuth.logout(); 142 | }); 143 | } 144 | } 145 | 146 | class FlitterDrawerCommunityTile extends StatelessWidget { 147 | final Group group; 148 | 149 | FlitterDrawerCommunityTile({@required this.group}); 150 | 151 | @override 152 | Widget build(BuildContext context) { 153 | return new ListTile( 154 | dense: false, 155 | title: new Text(group.name), 156 | leading: new CircleAvatar( 157 | backgroundImage: new NetworkImage(group.avatarUrl), 158 | backgroundColor: Theme.of(context).canvasColor), 159 | trailing: null, 160 | //TODO: unread inside roomsOf(group) 161 | onTap: () { 162 | flitterStore.dispatch(new SelectGroupAction(group)); 163 | GroupView.go(context, group); 164 | }); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /lib/widgets/common/list_room.dart: -------------------------------------------------------------------------------- 1 | library flitter.common.list_room; 2 | 3 | import 'package:flitter/widgets/common/utils.dart'; 4 | import 'package:meta/meta.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flitter/redux/store.dart'; 7 | import 'package:gitter/gitter.dart'; 8 | import 'package:flitter/widgets/routes/room.dart'; 9 | import 'package:flitter/redux/actions.dart'; 10 | 11 | class ListRoom extends StatelessWidget { 12 | final GlobalKey _refreshIndicatorKey = 13 | new GlobalKey(); 14 | 15 | final Iterable rooms; 16 | final RefreshCallback onRefresh; 17 | 18 | ListRoom({@required this.rooms, @required this.onRefresh}); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return new RefreshIndicator( 23 | child: new ListView.builder( 24 | physics: const AlwaysScrollableScrollPhysics(), 25 | key: _refreshIndicatorKey, 26 | itemCount: rooms.length, 27 | itemBuilder: _buildListTile, 28 | ), 29 | onRefresh: onRefresh); 30 | } 31 | 32 | Widget _buildListTile(BuildContext context, int index) { 33 | return new RoomTile(room: rooms.elementAt(index)); 34 | } 35 | } 36 | 37 | class RoomTile extends StatelessWidget { 38 | final Room room; 39 | 40 | RoomTile({@required this.room}); 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | return new ListTile( 45 | dense: false, 46 | title: new Text(room.name), 47 | leading: new CircleAvatar( 48 | backgroundImage: 49 | room.avatarUrl != null ? new NetworkImage(room.avatarUrl) : null, 50 | backgroundColor: Theme.of(context).canvasColor), 51 | trailing: room?.unreadItems != null && room.unreadItems > 0 52 | ? new MessageCount(room.unreadItems) 53 | : null, 54 | onTap: () { 55 | flitterStore.dispatch(new SelectRoomAction(room)); 56 | materialNavigateTo(context, new RoomView(), path: RoomView.path); 57 | }, 58 | ); 59 | } 60 | } 61 | 62 | class UserTile extends StatelessWidget { 63 | final User user; 64 | 65 | UserTile({@required this.user}); 66 | 67 | @override 68 | Widget build(BuildContext context) { 69 | return new ListTile( 70 | dense: false, 71 | title: new Text(user.username), 72 | leading: new CircleAvatar( 73 | backgroundImage: new NetworkImage(user.avatarUrlSmall), 74 | backgroundColor: Theme.of(context).canvasColor), 75 | onTap: () async { 76 | final Room room = await gitterApi.room.roomFromUri(user.url); 77 | flitterStore.dispatch(new SelectRoomAction(room)); 78 | materialNavigateTo(context, new RoomView(), path: RoomView.path); 79 | }, 80 | ); 81 | } 82 | } 83 | 84 | class MessageCount extends StatelessWidget { 85 | final int count; 86 | 87 | MessageCount(this.count); 88 | 89 | @override 90 | Widget build(BuildContext context) { 91 | return new Row(children: [ 92 | new Container( 93 | decoration: new BoxDecoration( 94 | color: Theme.of(context).accentColor, 95 | borderRadius: new BorderRadius.circular(16.0)), 96 | padding: 97 | new EdgeInsets.only(left: 6.0, right: 6.0, top: 4.0, bottom: 4.0), 98 | child: new DefaultTextStyle( 99 | style: new TextStyle(fontSize: 10.0), child: new Text("$count"))) 100 | ]); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /lib/widgets/common/search.dart: -------------------------------------------------------------------------------- 1 | library flitter.common.search; 2 | 3 | import 'package:flitter/widgets/common/list_room.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:gitter/gitter.dart'; 6 | import 'package:meta/meta.dart'; 7 | import 'package:flitter/redux/actions.dart'; 8 | import 'package:flitter/redux/store.dart'; 9 | import 'package:flitter/services/flitter_request.dart'; 10 | 11 | class SearchBar extends StatelessWidget { 12 | final VoidCallback onSearchEnd; 13 | final ValueChanged onChange; 14 | final TextEditingController controller; 15 | final String hintText; 16 | 17 | SearchBar({this.onSearchEnd, this.controller, this.hintText, this.onChange}); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return buildSearchBar(context, hintText, 22 | onSearchEnd: onSearchEnd, onChange: onChange); 23 | } 24 | 25 | //todo: remove this when Scaffold will be less restrictive for appBar 26 | static Widget buildSearchBar(BuildContext context, String hintText, 27 | {TextEditingController controller, 28 | ValueChanged onChange, 29 | VoidCallback onSearchEnd}) => 30 | new AppBar( 31 | leading: new IconButton( 32 | icon: const Icon(Icons.arrow_back), 33 | color: Theme.of(context).accentColor, 34 | onPressed: onSearchEnd), 35 | title: new TextField( 36 | controller: controller, 37 | onChanged: onChange, 38 | autofocus: true, 39 | decoration: new InputDecoration( 40 | hintText: hintText, 41 | ), 42 | ), 43 | backgroundColor: Theme.of(context).canvasColor, 44 | ); 45 | } 46 | 47 | class ListSearchResult extends StatelessWidget { 48 | final Iterable results; 49 | 50 | ListSearchResult(this.results); 51 | 52 | @override 53 | Widget build(BuildContext context) => new ListView.builder( 54 | physics: const AlwaysScrollableScrollPhysics(), 55 | itemCount: results.length, 56 | itemBuilder: _buildListTile, 57 | ); 58 | 59 | _buildListTile(BuildContext context, int index) => 60 | new SearchResultTile(result: results.elementAt(index)); 61 | } 62 | 63 | class SearchResultTile extends StatelessWidget { 64 | final result; 65 | 66 | SearchResultTile({@required this.result}); 67 | 68 | @override 69 | Widget build(BuildContext context) { 70 | if (result is Room) { 71 | return new RoomTile(room: result); 72 | } else if (result is User) { 73 | return new UserTile(user: result); 74 | } 75 | return new ListTile(); 76 | } 77 | } 78 | 79 | class ScaffoldWithSearchbar extends StatefulWidget { 80 | final Widget body; 81 | final String title; 82 | final Widget drawer; 83 | 84 | ScaffoldWithSearchbar( 85 | {@required this.body, @required this.title, this.drawer}); 86 | 87 | @override 88 | _ScaffoldWithSearchbarState createState() => 89 | new _ScaffoldWithSearchbarState(); 90 | } 91 | 92 | class _ScaffoldWithSearchbarState extends State { 93 | @override 94 | Widget build(BuildContext context) { 95 | var body; 96 | if (flitterStore.state.search.searching == true) { 97 | if (flitterStore.state.search.requesting == true) { 98 | body = new Container(child: new LinearProgressIndicator()); 99 | } else { 100 | body = _buildSearchResult(); 101 | } 102 | } else { 103 | body = widget.body; 104 | } 105 | 106 | return new Scaffold( 107 | appBar: flitterStore.state.search.searching == true 108 | ? _buildSearchBar() 109 | : _buildAppBar(), 110 | drawer: widget.drawer, 111 | body: body); 112 | } 113 | 114 | void _handleSearchBegin() { 115 | ModalRoute.of(context).addLocalHistoryEntry(new LocalHistoryEntry( 116 | onRemove: () { 117 | flitterStore.dispatch(new EndSearchAction()); 118 | }, 119 | )); 120 | flitterStore.dispatch(new ShowSearchBarAction()); 121 | } 122 | 123 | void _handleSearchEnd() { 124 | Navigator.pop(context); 125 | } 126 | 127 | _handleSearchChange(String query) async { 128 | if (query.length > 3) { 129 | search(query); 130 | } 131 | } 132 | 133 | _buildSearchResult() => 134 | new ListSearchResult(flitterStore.state.search.result); 135 | 136 | Widget _buildAppBar() => new AppBar(title: new Text(widget.title), actions: [ 137 | new IconButton( 138 | icon: new Icon(Icons.search), onPressed: _handleSearchBegin) 139 | ]); 140 | 141 | Widget _buildSearchBar() { 142 | return SearchBar.buildSearchBar(context, 'Search', //todo: intl 143 | onSearchEnd: _handleSearchEnd, 144 | onChange: _handleSearchChange); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /lib/widgets/common/utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | void navigateTo(BuildContext context, Widget widget, 4 | {String path: '', bool replace: false}) { 5 | final builder = new PageRouteBuilder( 6 | settings: path.isNotEmpty ? new RouteSettings(name: path) : null, 7 | pageBuilder: (_, __, ___) { 8 | return widget; 9 | }, 10 | ); 11 | if (replace) { 12 | Navigator.of(context).pushReplacement(builder); 13 | } else { 14 | Navigator.of(context).push(builder); 15 | } 16 | } 17 | 18 | void materialNavigateTo(BuildContext context, Widget widget, 19 | {String path: '', bool replace: false}) { 20 | final route = new MaterialPageRoute( 21 | settings: path.isNotEmpty ? new RouteSettings(name: path) : null, 22 | builder: (BuildContext context) => widget, 23 | ); 24 | if (replace) { 25 | Navigator.pushReplacement(context, route); 26 | } else { 27 | Navigator.push(context, route); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/widgets/routes/group.dart: -------------------------------------------------------------------------------- 1 | library flitter.routes.group_room; 2 | 3 | import 'package:flitter/widgets/common/drawer.dart'; 4 | import 'package:flitter/widgets/common/list_room.dart'; 5 | import 'package:flitter/widgets/common/utils.dart'; 6 | import 'package:flitter/widgets/routes/settings.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:flitter/redux/flitter_app_state.dart'; 9 | import 'package:flitter/redux/store.dart'; 10 | import 'package:flitter/services/flitter_request.dart'; 11 | import 'package:gitter/gitter.dart'; 12 | import 'package:flitter/widgets/routes/home.dart'; 13 | import 'package:flitter/widgets/routes/people.dart'; 14 | import 'package:flitter/app.dart'; 15 | 16 | class GroupView extends StatefulWidget { 17 | static const path = "/group"; 18 | 19 | static go(BuildContext context, Group group, {bool replace: true}) { 20 | fetchRoomsOfGroup(); 21 | materialNavigateTo(context, new GroupView(), 22 | path: GroupView.path, replace: replace); 23 | } 24 | 25 | @override 26 | _GroupRoomViewState createState() => new _GroupRoomViewState(); 27 | } 28 | 29 | class _GroupRoomViewState extends State { 30 | var _subscription; 31 | 32 | CurrentGroupState get groupState => flitterStore.state.selectedGroup; 33 | 34 | @override 35 | void initState() { 36 | super.initState(); 37 | _subscription = flitterStore.onChange.listen((_) { 38 | setState(() {}); 39 | }); 40 | } 41 | 42 | @override 43 | void dispose() { 44 | super.dispose(); 45 | _subscription.cancel(); 46 | } 47 | 48 | @override 49 | Widget build(BuildContext context) { 50 | var body; 51 | 52 | if (groupState?.rooms != null) { 53 | body = new ListView( 54 | children: groupState.rooms 55 | .map((room) => new RoomTile(room: room)) 56 | .toList()); 57 | } else { 58 | body = new LoadingView(); 59 | } 60 | 61 | return new Scaffold( 62 | appBar: new AppBar(title: new Text(groupState?.group?.name ?? "")), 63 | body: body, 64 | drawer: new FlitterDrawer(onTapAllConversation: () { 65 | HomeView.go(context); 66 | }, onTapPeoples: () { 67 | PeopleView.go(context); 68 | }, onTapSettings: () { 69 | SettingsView.go(context); 70 | }), 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/widgets/routes/home.dart: -------------------------------------------------------------------------------- 1 | library flitter.routes.home; 2 | 3 | import 'dart:async'; 4 | 5 | import 'package:flitter/redux/store.dart'; 6 | import 'package:flitter/services/flitter_request.dart'; 7 | import 'package:flitter/widgets/common/drawer.dart'; 8 | import 'package:flitter/widgets/common/list_room.dart'; 9 | import 'package:flitter/widgets/common/search.dart'; 10 | import 'package:flitter/widgets/common/utils.dart'; 11 | import 'package:flitter/widgets/routes/people.dart'; 12 | import 'package:flitter/widgets/routes/settings.dart'; 13 | import 'package:flutter/material.dart'; 14 | import 'package:flitter/app.dart'; 15 | import 'package:flitter/intl/messages_all.dart' as intl; 16 | 17 | class HomeView extends StatefulWidget { 18 | static final String path = "/"; 19 | final RefreshCallback onRefresh; 20 | 21 | static void go(BuildContext context, {bool replace: true}) { 22 | fetchRooms(); 23 | materialNavigateTo(context, new HomeView(), 24 | path: HomeView.path, replace: replace); 25 | } 26 | 27 | HomeView({this.onRefresh}); 28 | 29 | @override 30 | _HomeViewState createState() => new _HomeViewState(); 31 | } 32 | 33 | class _HomeViewState extends State { 34 | StreamSubscription _subscription; 35 | 36 | @override 37 | void initState() { 38 | super.initState(); 39 | _subscription = flitterStore.onChange.listen((_) { 40 | setState(() {}); 41 | }); 42 | } 43 | 44 | @override 45 | void dispose() { 46 | super.dispose(); 47 | _subscription.cancel(); 48 | } 49 | 50 | @override 51 | Widget build(BuildContext context) { 52 | var body; 53 | 54 | final drawer = new FlitterDrawer(onTapAllConversation: () { 55 | Navigator.pop(context); 56 | }, onTapPeoples: () { 57 | PeopleView.go(context); 58 | }, onTapSettings: () { 59 | SettingsView.go(context); 60 | }); 61 | 62 | if (flitterStore.state.rooms != null) { 63 | body = new ListRoom( 64 | rooms: flitterStore.state.rooms, 65 | onRefresh: () { 66 | if (widget.onRefresh != null) { 67 | return widget.onRefresh(); 68 | } 69 | return fetchRooms(); 70 | }); 71 | } else { 72 | body = new LoadingView(); 73 | } 74 | 75 | return new ScaffoldWithSearchbar( 76 | body: body, title: intl.allConversations(), drawer: drawer); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/widgets/routes/login.dart: -------------------------------------------------------------------------------- 1 | library flitter.routes.login; 2 | 3 | import 'package:flitter/redux/store.dart'; 4 | import 'package:flitter/services/flitter_auth.dart'; 5 | import 'package:flitter/services/flitter_request.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter/services.dart'; 8 | import 'package:flutter_test/flutter_test.dart'; 9 | import 'package:gitter/gitter.dart'; 10 | 11 | typedef void OnLoginCallback(GitterToken token); 12 | 13 | class LoginView extends StatefulWidget { 14 | @override 15 | _LoginViewState createState() => new _LoginViewState(); 16 | } 17 | 18 | class _LoginViewState extends State { 19 | var _subscription; 20 | 21 | // FIXME: final FlutterWebviewPlugin flutterWebviewPlugin = new FlutterWebviewPlugin(); 22 | 23 | @override 24 | void initState() { 25 | super.initState(); 26 | _subscription = gitterStore.onChange.listen((_) { 27 | setState(() {}); 28 | }); 29 | } 30 | 31 | @override 32 | void dispose() { 33 | super.dispose(); 34 | _subscription.cancel(); 35 | } 36 | 37 | Future _auth() async { 38 | GitterToken token = await FlitterAuth.auth(); 39 | initStores(token); 40 | } 41 | 42 | Future _catchOnBackPressed() async { 43 | // catch onBackPressed for Android 44 | // FIXME: await flutterWebviewPlugin.onBackPressed.first; 45 | return SystemNavigator.pop(); 46 | } 47 | 48 | @override 49 | Widget build(BuildContext context) { 50 | if (gitterToken == null) { 51 | _auth(); 52 | 53 | // FIXME: _catchOnBackPressed(); 54 | } 55 | return new MaterialApp( 56 | home: new Scaffold( 57 | body: new Center(child: new CircularProgressIndicator())), 58 | theme: themeStore.state.theme); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/widgets/routes/people.dart: -------------------------------------------------------------------------------- 1 | library flitter.routes.people; 2 | 3 | import 'package:flitter/widgets/common/list_room.dart'; 4 | import 'package:flitter/widgets/common/search.dart'; 5 | import 'package:flitter/widgets/routes/settings.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flitter/redux/store.dart'; 8 | import 'package:flitter/services/flitter_request.dart'; 9 | import 'package:gitter/gitter.dart'; 10 | import 'package:flitter/widgets/common/drawer.dart'; 11 | import 'package:flitter/widgets/common/utils.dart'; 12 | import 'package:flitter/widgets/routes/home.dart'; 13 | import 'package:flitter/intl/messages_all.dart' as intl; 14 | import 'package:flitter/app.dart'; 15 | 16 | class PeopleView extends StatefulWidget { 17 | static const String path = "/people"; 18 | final RefreshCallback onRefresh; 19 | 20 | static void go(BuildContext context, {bool replace: true}) { 21 | fetchRooms(); 22 | materialNavigateTo(context, new PeopleView(), 23 | path: PeopleView.path, replace: replace); 24 | } 25 | 26 | PeopleView({this.onRefresh}); 27 | 28 | @override 29 | _PeopleViewState createState() => new _PeopleViewState(); 30 | } 31 | 32 | class _PeopleViewState extends State { 33 | var _subscription; 34 | 35 | @override 36 | void initState() { 37 | super.initState(); 38 | _subscription = flitterStore.onChange.listen((_) { 39 | setState(() {}); 40 | }); 41 | } 42 | 43 | @override 44 | void dispose() { 45 | super.dispose(); 46 | _subscription.cancel(); 47 | } 48 | 49 | @override 50 | Widget build(BuildContext context) { 51 | var body; 52 | 53 | final drawer = new FlitterDrawer(onTapAllConversation: () { 54 | HomeView.go(context); 55 | }, onTapPeoples: () { 56 | Navigator.pop(context); 57 | }, onTapSettings: () { 58 | SettingsView.go(context); 59 | }); 60 | 61 | if (flitterStore.state.rooms != null) { 62 | body = new ListRoom( 63 | rooms: flitterStore.state.rooms 64 | .where((Room room) => room.oneToOne) 65 | .toList(), 66 | onRefresh: () { 67 | if (widget.onRefresh != null) { 68 | return widget.onRefresh(); 69 | } 70 | return fetchRooms(); 71 | }); 72 | } else { 73 | body = new LoadingView(); 74 | } 75 | 76 | return new ScaffoldWithSearchbar( 77 | body: body, title: intl.people(), drawer: drawer); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lib/widgets/routes/room.dart: -------------------------------------------------------------------------------- 1 | library flitter.routes.room; 2 | 3 | import 'dart:async'; 4 | import 'package:flitter/redux/actions.dart'; 5 | import 'package:flitter/redux/store.dart'; 6 | import 'package:flitter/services/flitter_request.dart'; 7 | import 'package:gitter/gitter.dart'; 8 | import 'package:flitter/widgets/common/chat_room.dart'; 9 | import 'package:flutter/material.dart'; 10 | import 'package:flitter/app.dart'; 11 | import 'package:gitter/src/models/faye_message.dart'; 12 | 13 | enum RoomMenuAction { leave } 14 | 15 | class RoomView extends StatefulWidget { 16 | static const path = "/room"; 17 | 18 | RoomView(); 19 | 20 | @override 21 | _RoomViewState createState() => new _RoomViewState(); 22 | } 23 | 24 | class _RoomViewState extends State { 25 | Iterable get messages => flitterStore.state.selectedRoom.messages; 26 | 27 | Room get room => flitterStore.state.selectedRoom.room; 28 | 29 | var _subscription; 30 | 31 | @override 32 | void initState() { 33 | super.initState(); 34 | _subscription = flitterStore.onChange.listen((_) { 35 | setState(() {}); 36 | }); 37 | 38 | _fetchMessages(); 39 | 40 | gitterSubscriber.subscribeToChatMessages(room.id, _onMessageHandler); 41 | } 42 | 43 | _onMessageHandler(List msgs) { 44 | for (GitterFayeMessage msg in msgs) { 45 | if (msg.data != null && msg.data["operation"] == "create") { 46 | flitterStore.dispatch(new OnMessageForCurrentRoom( 47 | new Message.fromJson(msg.data["model"]))); 48 | } 49 | } 50 | } 51 | 52 | @override 53 | void dispose() { 54 | super.dispose(); 55 | _subscription.cancel(); 56 | gitterSubscriber.unsubscribeToChatMessages(room.id); 57 | } 58 | 59 | @override 60 | Widget build(BuildContext context) { 61 | var body; 62 | 63 | if (messages != null) { 64 | final ChatRoom chatRoom = 65 | new ChatRoom(messages: messages.toList().reversed, room: room); 66 | chatRoom.onNeedDataStream.listen((_) => _fetchMessages()); 67 | body = chatRoom; 68 | } else { 69 | body = new LoadingView(); 70 | } 71 | 72 | return new Scaffold( 73 | appBar: new AppBar(title: new Text(room.name), actions: [_buildMenu()]), 74 | body: body, 75 | floatingActionButton: 76 | _userHasJoined || messages == null ? null : _joinRoomButton()); 77 | } 78 | 79 | _fetchMessages() { 80 | String id = messages?.isNotEmpty == true ? messages.first.id : null; 81 | fetchMessagesOfRoom(room.id, id); 82 | } 83 | 84 | Widget _buildMenu() => new PopupMenuButton( 85 | itemBuilder: (BuildContext context) => >[ 86 | new PopupMenuItem( 87 | value: RoomMenuAction.leave, 88 | child: new Text('Leave room')) //todo: intl 89 | ], 90 | onSelected: (RoomMenuAction action) { 91 | switch (action) { 92 | case RoomMenuAction.leave: 93 | _onLeaveRoom(); 94 | break; 95 | } 96 | }); 97 | 98 | _onLeaveRoom() async { 99 | bool success = await leaveRoom(room); 100 | if (success == true) { 101 | Navigator.of(context).pop(); 102 | } else { 103 | // Todo: show error 104 | } 105 | } 106 | 107 | Widget _joinRoomButton() { 108 | return new FloatingActionButton( 109 | child: new Icon(Icons.message), onPressed: _onTapJoinRoom); 110 | } 111 | 112 | void _onTapJoinRoom() { 113 | joinRoom(room); 114 | } 115 | 116 | bool get _userHasJoined => 117 | flitterStore.state.rooms.any((Room r) => r.id == room.id); 118 | } 119 | -------------------------------------------------------------------------------- /lib/widgets/routes/settings.dart: -------------------------------------------------------------------------------- 1 | library flitter.routes.settings; 2 | 3 | import 'package:flutter_color_picker/flutter_color_picker.dart'; 4 | import 'package:flitter/redux/actions.dart'; 5 | import 'package:flitter/redux/flitter_app_state.dart'; 6 | import 'package:flitter/widgets/routes/people.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:flitter/redux/store.dart'; 9 | import 'package:flitter/services/flitter_request.dart'; 10 | import 'package:flitter/widgets/common/drawer.dart'; 11 | import 'package:flitter/widgets/common/utils.dart'; 12 | import 'package:flitter/widgets/routes/home.dart'; 13 | import 'package:flitter/intl/messages_all.dart' as intl; 14 | import 'package:shared_preferences/shared_preferences.dart'; 15 | 16 | class SettingsView extends StatefulWidget { 17 | static const String path = "/settings"; 18 | 19 | static void go(BuildContext context, {bool replace: true}) { 20 | fetchRooms(); 21 | materialNavigateTo(context, new SettingsView(), 22 | path: SettingsView.path, replace: replace); 23 | } 24 | 25 | @override 26 | _SettingsViewState createState() => new _SettingsViewState(); 27 | } 28 | 29 | class _SettingsViewState extends State { 30 | var _themeSubscription; 31 | 32 | @override 33 | void initState() { 34 | super.initState(); 35 | _themeSubscription = themeStore.onChange.listen((_) { 36 | setState(() {}); 37 | }); 38 | } 39 | 40 | @override 41 | void dispose() { 42 | super.dispose(); 43 | _themeSubscription.cancel(); 44 | } 45 | 46 | @override 47 | Widget build(BuildContext context) { 48 | final drawer = new FlitterDrawer(onTapAllConversation: () { 49 | HomeView.go(context); 50 | }, onTapPeoples: () { 51 | PeopleView.go(context); 52 | }, onTapSettings: () { 53 | Navigator.pop(context); 54 | }); 55 | 56 | return new Scaffold( 57 | body: new ListView(children: [ 58 | _buildDarkModeSwitch(), 59 | _buildPrimaryColor(), 60 | _buildAccentColor(), 61 | new Divider() 62 | ]), 63 | appBar: new AppBar(title: new Text(intl.settings())), 64 | drawer: drawer); 65 | } 66 | 67 | Widget _buildDarkModeSwitch() { 68 | final brightness = themeStore.state.theme.brightness != Brightness.light; 69 | 70 | return new ListTile( 71 | title: new Row(children: [ 72 | new Expanded(child: new Text(intl.darkMode())), 73 | new Switch( 74 | value: brightness, 75 | onChanged: (bool value) async { 76 | if (value != brightness) { 77 | themeStore.dispatch(new ChangeThemeAction( 78 | brightness: 79 | value == true ? Brightness.dark : Brightness.light)); 80 | persistBrightness(value); 81 | } 82 | }) 83 | ])); 84 | } 85 | 86 | Widget _buildPrimaryColor() { 87 | return _buildColorTile(intl.primaryColor(), themeStore.state.primaryColor, 88 | () async { 89 | Color color = await showDialog( 90 | context: context, 91 | child: new PrimaryColorPickerDialog( 92 | selected: themeStore.state.primaryColor)); 93 | if (color != null) { 94 | themeStore.dispatch(new ChangeThemeAction(primaryColor: color)); 95 | persistAccentColor(color); 96 | } 97 | }); 98 | } 99 | 100 | Widget _buildAccentColor() { 101 | return _buildColorTile(intl.accentColor(), themeStore.state.accentColor, 102 | () async { 103 | Color color = await showDialog( 104 | context: context, 105 | child: new AccentColorPickerDialog( 106 | selected: themeStore.state.accentColor)); 107 | if (color != null) { 108 | themeStore.dispatch(new ChangeThemeAction(accentColor: color)); 109 | persistAccentColor(color); 110 | } 111 | }); 112 | } 113 | 114 | persistBrightness(bool value) async { 115 | SharedPreferences prefs = await SharedPreferences.getInstance(); 116 | prefs.setBool(ThemeState.kBrightnessKey, value); 117 | await prefs.commit(); 118 | } 119 | 120 | persistPrimaryColor(ColorSwatch color) async { 121 | SharedPreferences prefs = await SharedPreferences.getInstance(); 122 | prefs.setInt(ThemeState.kPrimaryColorKey, Colors.primaries.indexOf(color)); 123 | await prefs.commit(); 124 | } 125 | 126 | persistAccentColor(ColorSwatch color) async { 127 | SharedPreferences prefs = await SharedPreferences.getInstance(); 128 | prefs.setInt(ThemeState.kAccentColorKey, Colors.accents.indexOf(color)); 129 | await prefs.commit(); 130 | } 131 | 132 | Widget _buildColorTile(String text, ColorSwatch color, VoidCallback onTap) { 133 | return new ListTile( 134 | title: new Row(children: [ 135 | new Expanded(child: new Text(text)), 136 | new Padding( 137 | padding: new EdgeInsets.only(right: 14.0), 138 | child: new ColorTile(color: color, size: 40.0, rounded: true)), 139 | ]), 140 | onTap: onTap); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flitter 2 | description: A new flutter project. 3 | version: 0.0.1 4 | 5 | dependencies: 6 | flutter: 7 | sdk: flutter 8 | intl: ^0.14.0 9 | http: ^0.11.3+13 10 | flutter_webview_plugin: ">=0.0.5 <=0.1.2" 11 | redux: ^2.1.1 12 | shared_preferences: ">=0.2.0 <1.0.0" 13 | url_launcher: ">=0.4.0 <=2.0.2" 14 | gitter: ^0.1.0 15 | flutter_markdown: "^0.0.9" 16 | flutter_color_picker: "^0.0.1" 17 | 18 | dev_dependencies: 19 | flutter_test: 20 | sdk: flutter 21 | 22 | flutter: 23 | uses-material-design: true 24 | assets: 25 | - assets/html/success.html 26 | - assets/images/banner.jpg 27 | -------------------------------------------------------------------------------- /screenshots/flutter_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-flitter/flitter/369e337768566f3d0b2c947fac23bf7900b24cf5/screenshots/flutter_01.png -------------------------------------------------------------------------------- /screenshots/flutter_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-flitter/flitter/369e337768566f3d0b2c947fac23bf7900b24cf5/screenshots/flutter_02.png -------------------------------------------------------------------------------- /screenshots/flutter_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-flitter/flitter/369e337768566f3d0b2c947fac23bf7900b24cf5/screenshots/flutter_03.png -------------------------------------------------------------------------------- /screenshots/flutter_04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-flitter/flitter/369e337768566f3d0b2c947fac23bf7900b24cf5/screenshots/flutter_04.png -------------------------------------------------------------------------------- /screenshots/flutter_05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-flitter/flitter/369e337768566f3d0b2c947fac23bf7900b24cf5/screenshots/flutter_05.png -------------------------------------------------------------------------------- /screenshots/flutter_06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-flitter/flitter/369e337768566f3d0b2c947fac23bf7900b24cf5/screenshots/flutter_06.png -------------------------------------------------------------------------------- /screenshots/flutter_07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-flitter/flitter/369e337768566f3d0b2c947fac23bf7900b24cf5/screenshots/flutter_07.png -------------------------------------------------------------------------------- /test/utils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flitter/redux/actions.dart'; 4 | import 'package:flitter/redux/flitter_app_state.dart'; 5 | import 'package:flitter/redux/store.dart'; 6 | import 'package:gitter/gitter.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:gitter/src/faye.dart'; 9 | import 'package:http/http.dart' as http; 10 | import 'package:http/testing.dart' as http; 11 | 12 | class MockableApp extends StatelessWidget { 13 | final Widget drawer; 14 | final Widget body; 15 | final AppBar appBar; 16 | final Widget scaffold; 17 | 18 | MockableApp({this.drawer, this.body, this.appBar, this.scaffold}); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | if (scaffold != null) { 23 | return new MaterialApp(home: scaffold); 24 | } 25 | return new MaterialApp( 26 | home: new Scaffold(drawer: drawer, body: body, appBar: appBar)); 27 | } 28 | } 29 | 30 | initStores() { 31 | final token = new GitterToken() 32 | ..access = "xxx" 33 | ..type = "xxx"; 34 | final api = new GitterApi(token); 35 | gitterStore = new GitterStore( 36 | initialState: new GitterState( 37 | api: api, 38 | token: token, 39 | subscriber: new GitterFayeSubscriber(token.access))); 40 | 41 | flitterStore = new FlitterStore( 42 | initialState: new FlitterAppState(search: new SearchState.initial()), 43 | middlewares: const []); 44 | } 45 | 46 | fetchUser() { 47 | final user = new User.fromJson({ 48 | "id": "53307734c3599d1de448e192", 49 | "username": "malditogeek", 50 | "displayName": "Mauro Pompilio", 51 | "url": "/malditogeek", 52 | "avatarUrlSmall": "https://avatars.githubusercontent.com/u/14751?", 53 | "avatarUrlMedium": "https://avatars.githubusercontent.com/u/14751?" 54 | }); 55 | flitterStore.dispatch(new FetchUser(user)); 56 | } 57 | 58 | final groups = [ 59 | new Group.fromJson({ 60 | "id": "57542c12c43b8c601976fa66", 61 | "name": "gitterHQ", 62 | "uri": "gitterHQ", 63 | "backedBy": {"type": "GH_ORG", "linkPath": "gitterHQ"}, 64 | "avatarUrl": 65 | "http://gitter.im/api/private/avatars/group/i/577ef7e4e897e2a459b1b881" 66 | }), 67 | new Group.fromJson({ 68 | "id": "577faf61a7d5727908337209", 69 | "name": "i-love-cats", 70 | "uri": "i-love-cats", 71 | "backedBy": {"type": null}, 72 | "avatarUrl": 73 | "http://gitter.im/api/private/avatars/group/i/577faf61a7d5727908337209" 74 | }) 75 | ]; 76 | 77 | Iterable fetchCommunities() { 78 | flitterStore.dispatch(new FetchGroupsAction(groups)); 79 | return groups; 80 | } 81 | 82 | final rooms = [ 83 | new Room.fromJson({ 84 | "id": "53307860c3599d1de448e19d", 85 | "name": "Andrew Newdigate", 86 | "topic": "", 87 | "oneToOne": true, 88 | "user": { 89 | "id": "53307831c3599d1de448e19a", 90 | "username": "suprememoocow", 91 | "displayName": "Andrew Newdigate", 92 | "url": "/suprememoocow", 93 | "avatarUrlSmall": "https://avatars.githubusercontent.com/u/594566?", 94 | "avatarUrlMedium": "https://avatars.githubusercontent.com/u/594566?" 95 | }, 96 | "unreadItems": 0, 97 | "mentions": 0, 98 | "lurk": false, 99 | "url": "/suprememoocow", 100 | "githubType": "ONETOONE" 101 | }), 102 | new Room.fromJson({ 103 | "id": "5330777dc3599d1de448e194", 104 | "name": "gitterHQ", 105 | "topic": "Gitter", 106 | "uri": "gitterHQ", 107 | "oneToOne": false, 108 | "userCount": 2, 109 | "unreadItems": 0, 110 | "mentions": 0, 111 | "lastAccessTime": "2014-03-24T18:22:28.105Z", 112 | "lurk": false, 113 | "url": "/gitterHQ", 114 | "githubType": "ORG", 115 | "v": 1 116 | }), 117 | new Room.fromJson({ 118 | "id": "5330780dc3599d1de448e198", 119 | "name": "gitterHQ/devops", 120 | "topic": "", 121 | "uri": "gitterHQ/devops", 122 | "oneToOne": false, 123 | "userCount": 2, 124 | "unreadItems": 0, 125 | "mentions": 0, 126 | "lastAccessTime": "2014-03-24T18:23:10.512Z", 127 | "lurk": false, 128 | "url": "/gitterHQ/devops", 129 | "githubType": "ORG_CHANNEL", 130 | "security": "INHERITED", 131 | "v": 1 132 | }), 133 | new Room.fromJson({ 134 | "id": "53307793c3599d1de448e196", 135 | "name": "malditogeek/vmux", 136 | "topic": "VMUX - Plugin-free video calls in your browser using WebRTC", 137 | "uri": "malditogeek/vmux", 138 | "oneToOne": false, 139 | "userCount": 2, 140 | "unreadItems": 0, 141 | "mentions": 0, 142 | "lastAccessTime": "2014-03-24T18:21:08.448Z", 143 | "favourite": 1, 144 | "lurk": false, 145 | "url": "/malditogeek/vmux", 146 | "githubType": "REPO", 147 | "tags": ["javascript", "nodejs"], 148 | "v": 1 149 | }) 150 | ]; 151 | 152 | Iterable fetchRooms() { 153 | flitterStore.dispatch(new FetchRoomsAction(rooms)); 154 | return rooms; 155 | } 156 | 157 | Iterable fetchRoomsOfGroup() { 158 | flitterStore.dispatch(new SelectGroupAction(groups.first)); 159 | flitterStore.dispatch(new FetchRoomsOfGroup(rooms)); 160 | return rooms; 161 | } 162 | 163 | // Returns a mock HTTP client that responds with an image to all requests. 164 | // See: https://github.com/flutter/flutter/issues/13433 165 | ValueGetter createMockImageHttpClient = () { 166 | return new http.MockClient((http.BaseRequest request) { 167 | return new Future.value( 168 | new http.Response.bytes(_transparentImage, 200, request: request)); 169 | }); 170 | }; 171 | 172 | const List _transparentImage = const [ 173 | 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 174 | 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06, 175 | 0x00, 0x00, 0x00, 0x1F, 0x15, 0xC4, 0x89, 0x00, 0x00, 0x00, 0x0A, 0x49, 0x44, 176 | 0x41, 0x54, 0x78, 0x9C, 0x63, 0x00, 0x01, 0x00, 0x00, 0x05, 0x00, 0x01, 0x0D, 177 | 0x0A, 0x2D, 0xB4, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 178 | ]; -------------------------------------------------------------------------------- /test/widgets/drawer_test.dart: -------------------------------------------------------------------------------- 1 | import '../utils.dart'; 2 | import 'package:flitter/widgets/common/drawer.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_test/flutter_test.dart'; 5 | import 'package:flitter/redux/store.dart'; 6 | import 'package:flitter/intl/messages_all.dart' as intl; 7 | import 'package:flutter/services.dart' show createHttpClient; 8 | 9 | main() { 10 | group("$FlitterDrawer Widget", () { 11 | setUpAll(() { 12 | initStores(); 13 | createHttpClient = createMockImageHttpClient; 14 | }); 15 | 16 | testWidgets("No user", (WidgetTester tester) async { 17 | await tester.pumpWidget(new MockableApp( 18 | drawer: new FlitterDrawer( 19 | onTapAllConversation: () {}, 20 | onTapPeoples: () {}, 21 | onTapSettings: () {}))); 22 | 23 | // open drawer 24 | final ScaffoldState state = tester.firstState(find.byType(Scaffold)); 25 | state.openDrawer(); 26 | 27 | await tester.pump(); 28 | 29 | // drawer with a Loader 30 | final progressFinder = find.byType(CircularProgressIndicator); 31 | expect(progressFinder, findsOneWidget); 32 | }); 33 | 34 | testWidgets("fetch user", (WidgetTester tester) async { 35 | await tester.pumpWidget(new MockableApp( 36 | drawer: new FlitterDrawer( 37 | onTapAllConversation: () {}, 38 | onTapPeoples: () {}, 39 | onTapSettings: () {}))); 40 | 41 | // open drawer 42 | final ScaffoldState state = tester.firstState(find.byType(Scaffold)); 43 | state.openDrawer(); 44 | 45 | fetchUser(); 46 | 47 | await tester.pump(); 48 | 49 | // drawer with user 50 | final drawerFinder = find.byType(FlitterDrawerContent); 51 | expect(drawerFinder, findsOneWidget); 52 | }); 53 | 54 | testWidgets("Header", (WidgetTester tester) async { 55 | await tester.pumpWidget(new MockableApp( 56 | drawer: new FlitterDrawer( 57 | onTapAllConversation: () {}, 58 | onTapPeoples: () {}, 59 | onTapSettings: () {}))); 60 | 61 | // open drawer 62 | final ScaffoldState state = tester.firstState(find.byType(Scaffold)); 63 | state.openDrawer(); 64 | 65 | fetchUser(); 66 | 67 | await tester.pump(); 68 | 69 | // drawer with header 70 | final userAccountHeaderFinder = find.byType(UserAccountsDrawerHeader); 71 | expect(userAccountHeaderFinder, findsOneWidget); 72 | final userAccountHeader = tester.firstWidget(userAccountHeaderFinder) 73 | as UserAccountsDrawerHeader; 74 | expect((userAccountHeader.accountName as Text).data, 75 | equals(flitterStore.state.user.username)); 76 | expect((userAccountHeader.accountEmail as Text).data, 77 | equals(flitterStore.state.user.displayName)); 78 | }); 79 | 80 | testWidgets("Footer", (WidgetTester tester) async { 81 | await tester.pumpWidget(new MockableApp( 82 | drawer: new FlitterDrawer( 83 | onTapAllConversation: () {}, 84 | onTapPeoples: () {}, 85 | onTapSettings: () {}))); 86 | 87 | // open drawer 88 | final ScaffoldState state = tester.firstState(find.byType(Scaffold)); 89 | state.openDrawer(); 90 | 91 | fetchUser(); 92 | 93 | await tester.pump(); 94 | 95 | // drawer with footer 96 | final drawerFooterFinder = find.byType(FlitterDrawerFooter); 97 | expect(drawerFooterFinder, findsOneWidget); 98 | 99 | final logoutButtonFinder = find.descendant( 100 | of: drawerFooterFinder, matching: find.byType(ListTile)); 101 | expect(logoutButtonFinder, findsOneWidget); 102 | 103 | final logoutButton = tester.firstWidget(logoutButtonFinder) as ListTile; 104 | expect((logoutButton.leading as Icon).icon, equals(Icons.exit_to_app)); 105 | expect((logoutButton.title as Text).data, equals(intl.logout())); 106 | expect(logoutButton.onTap, isNotNull); 107 | }); 108 | 109 | testWidgets("Communities", (WidgetTester tester) async { 110 | await tester.pumpWidget(new MockableApp( 111 | drawer: new FlitterDrawer( 112 | onTapAllConversation: () {}, 113 | onTapPeoples: () {}, 114 | onTapSettings: () {}))); 115 | 116 | // open drawer 117 | final ScaffoldState state = tester.firstState(find.byType(Scaffold)); 118 | state.openDrawer(); 119 | 120 | fetchUser(); 121 | 122 | await tester.pump(); 123 | 124 | final drawerCommunitiesFinder = find.byType(FlitterDrawerCommunityTile); 125 | 126 | // drawer without Communities 127 | expect(drawerCommunitiesFinder, findsNothing); 128 | 129 | fetchCommunities(); 130 | 131 | await tester.pump(); 132 | 133 | // drawer with Communities footer 134 | expect(drawerCommunitiesFinder, findsNWidgets(2)); 135 | 136 | final tiles = tester.widgetList(drawerCommunitiesFinder) 137 | as Iterable; 138 | for (var tile in tiles) { 139 | expect(flitterStore.state.groups, contains(tile.group)); 140 | } 141 | }); 142 | 143 | testWidgets("Tap logout", (WidgetTester tester) async { 144 | await tester.pumpWidget(new MockableApp( 145 | drawer: new FlitterDrawer( 146 | onTapAllConversation: () {}, 147 | onTapPeoples: () {}, 148 | onTapSettings: () {}))); 149 | 150 | // open drawer 151 | final ScaffoldState state = tester.firstState(find.byType(Scaffold)); 152 | state.openDrawer(); 153 | 154 | fetchUser(); 155 | 156 | await tester.pump(); 157 | 158 | // drawer with footer 159 | final drawerFooterFinder = find.byType(FlitterDrawerFooter); 160 | final logoutButtonFinder = find.descendant( 161 | of: drawerFooterFinder, matching: find.byType(ListTile)); 162 | 163 | expect(logoutButtonFinder, findsOneWidget); 164 | 165 | //await tester.tap(logoutButtonFinder); 166 | final logoutButton = tester.firstWidget(logoutButtonFinder) as ListTile; 167 | logoutButton.onTap(); 168 | 169 | await tester.pump(); 170 | }); 171 | }); 172 | } 173 | -------------------------------------------------------------------------------- /test/widgets/group_test.dart: -------------------------------------------------------------------------------- 1 | import '../utils.dart'; 2 | import 'package:flitter/app.dart'; 3 | import 'package:flitter/widgets/common/list_room.dart'; 4 | import 'package:flitter/widgets/routes/group.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter_test/flutter_test.dart'; 7 | 8 | main() { 9 | group("$GroupView Widget", () { 10 | setUpAll(initStores); 11 | 12 | testWidgets("No Rooms", (WidgetTester tester) async { 13 | await tester.pumpWidget(new MockableApp(scaffold: new GroupView())); 14 | 15 | final scaff = find.byType(Scaffold); 16 | 17 | expect(scaff, findsOneWidget); 18 | expect(find.descendant(of: scaff, matching: find.byType(LoadingView)), 19 | findsOneWidget); 20 | }); 21 | 22 | testWidgets("Fetching Rooms", (WidgetTester tester) async { 23 | await tester.pumpWidget(new MockableApp(scaffold: new GroupView())); 24 | 25 | final scaff = find.byType(Scaffold); 26 | 27 | fetchRoomsOfGroup(); 28 | 29 | expect(find.descendant(of: scaff, matching: find.byType(LoadingView)), 30 | findsOneWidget); 31 | 32 | await tester.pump(); 33 | 34 | expect(find.descendant(of: scaff, matching: find.byType(LoadingView)), 35 | findsNothing); 36 | expect(find.descendant(of: scaff, matching: find.byType(ListView)), 37 | findsOneWidget); 38 | expect(find.descendant(of: scaff, matching: find.byType(RoomTile)), 39 | findsNWidgets(4)); 40 | }); 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /test/widgets/home_test.dart: -------------------------------------------------------------------------------- 1 | import '../utils.dart'; 2 | import 'package:flitter/app.dart'; 3 | import 'package:flitter/redux/store.dart'; 4 | import 'package:flitter/widgets/common/list_room.dart'; 5 | import 'package:flitter/widgets/common/search.dart'; 6 | import 'package:flitter/widgets/routes/home.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_test/flutter_test.dart'; 9 | 10 | main() { 11 | group("$HomeView Widget", () { 12 | setUpAll(initStores); 13 | 14 | testWidgets("No Rooms", (WidgetTester tester) async { 15 | await tester.pumpWidget(new MockableApp(scaffold: new HomeView())); 16 | 17 | final scaff = find.byType(ScaffoldWithSearchbar); 18 | 19 | expect(scaff, findsOneWidget); 20 | expect(find.descendant(of: scaff, matching: find.byType(LoadingView)), 21 | findsOneWidget); 22 | }); 23 | 24 | testWidgets("Fetching Rooms", (WidgetTester tester) async { 25 | await tester.pumpWidget(new MockableApp(scaffold: new HomeView())); 26 | 27 | final scaff = find.byType(ScaffoldWithSearchbar); 28 | 29 | fetchRooms(); 30 | 31 | expect(find.descendant(of: scaff, matching: find.byType(LoadingView)), 32 | findsOneWidget); 33 | 34 | await tester.pump(); 35 | 36 | expect(find.descendant(of: scaff, matching: find.byType(LoadingView)), 37 | findsNothing); 38 | expect(find.descendant(of: scaff, matching: find.byType(ListRoom)), 39 | findsOneWidget); 40 | expect(find.descendant(of: scaff, matching: find.byType(RoomTile)), 41 | findsNWidgets(4)); 42 | }); 43 | 44 | testWidgets("Pull to Refresh", (WidgetTester tester) async { 45 | flitterStore = 46 | new FlitterStore(initialState: flitterStore.state.apply(rooms: [])); 47 | 48 | await tester.pumpWidget( 49 | new MockableApp(scaffold: new HomeView(onRefresh: () async { 50 | fetchRooms(); 51 | }))); 52 | 53 | final scaff = find.byType(ScaffoldWithSearchbar); 54 | expect(find.descendant(of: scaff, matching: find.byType(LoadingView)), 55 | findsNothing); 56 | expect(find.descendant(of: scaff, matching: find.byType(ListRoom)), 57 | findsOneWidget); 58 | expect(find.descendant(of: scaff, matching: find.byType(RoomTile)), 59 | findsNothing); 60 | 61 | await tester.flingFrom( 62 | const Offset(50.0, 300.0), const Offset(0.0, 300.0), 1000.0); 63 | await tester.pump(); 64 | await tester.pump(const Duration(seconds: 1)); 65 | await tester.pump(const Duration(seconds: 1)); 66 | await tester.pump(const Duration(seconds: 1)); 67 | 68 | expect(find.byType(RoomTile), findsNWidgets(4)); 69 | }); 70 | }); 71 | } 72 | -------------------------------------------------------------------------------- /test/widgets/people_test.dart: -------------------------------------------------------------------------------- 1 | import '../utils.dart'; 2 | import 'package:flitter/app.dart'; 3 | import 'package:flitter/redux/store.dart'; 4 | import 'package:flitter/widgets/common/list_room.dart'; 5 | import 'package:flitter/widgets/common/search.dart'; 6 | import 'package:flitter/widgets/routes/people.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_test/flutter_test.dart'; 9 | 10 | main() { 11 | group("$PeopleView Widget", () { 12 | setUpAll(initStores); 13 | 14 | testWidgets("No Rooms", (WidgetTester tester) async { 15 | await tester.pumpWidget(new MockableApp(scaffold: new PeopleView())); 16 | 17 | final scaff = find.byType(ScaffoldWithSearchbar); 18 | 19 | expect(scaff, findsOneWidget); 20 | expect(find.descendant(of: scaff, matching: find.byType(LoadingView)), 21 | findsOneWidget); 22 | }); 23 | 24 | testWidgets("Fetching Rooms", (WidgetTester tester) async { 25 | await tester.pumpWidget(new MockableApp(scaffold: new PeopleView())); 26 | 27 | final scaff = find.byType(ScaffoldWithSearchbar); 28 | 29 | fetchRooms(); 30 | 31 | expect(find.descendant(of: scaff, matching: find.byType(LoadingView)), 32 | findsOneWidget); 33 | 34 | await tester.pump(); 35 | 36 | expect(find.descendant(of: scaff, matching: find.byType(LoadingView)), 37 | findsNothing); 38 | expect(find.descendant(of: scaff, matching: find.byType(ListRoom)), 39 | findsOneWidget); 40 | expect(find.descendant(of: scaff, matching: find.byType(RoomTile)), 41 | findsNWidgets(1)); 42 | }); 43 | 44 | testWidgets("Pull to Refresh", (WidgetTester tester) async { 45 | flitterStore = 46 | new FlitterStore(initialState: flitterStore.state.apply(rooms: [])); 47 | 48 | await tester.pumpWidget( 49 | new MockableApp(scaffold: new PeopleView(onRefresh: () async { 50 | fetchRooms(); 51 | }))); 52 | 53 | final scaff = find.byType(ScaffoldWithSearchbar); 54 | expect(find.descendant(of: scaff, matching: find.byType(LoadingView)), 55 | findsNothing); 56 | expect(find.descendant(of: scaff, matching: find.byType(ListRoom)), 57 | findsOneWidget); 58 | expect(find.descendant(of: scaff, matching: find.byType(RoomTile)), 59 | findsNothing); 60 | 61 | await tester.flingFrom( 62 | const Offset(50.0, 300.0), const Offset(0.0, 300.0), 1000.0); 63 | await tester.pump(); 64 | await tester.pump(const Duration(seconds: 1)); 65 | await tester.pump(const Duration(seconds: 1)); 66 | await tester.pump(const Duration(seconds: 1)); 67 | 68 | expect(find.byType(RoomTile), findsNWidgets(1)); 69 | }); 70 | }); 71 | } 72 | -------------------------------------------------------------------------------- /test/widgets/splash_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flitter/app.dart'; 2 | import 'package:flitter/redux/flitter_app_state.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_test/flutter_test.dart'; 5 | import 'package:flitter/redux/store.dart'; 6 | 7 | main() { 8 | group("$Splash Widget", () { 9 | testWidgets("Inside Scaffold", (WidgetTester tester) async { 10 | await tester.pumpWidget(new Splash()); 11 | 12 | // MaterialApp exist 13 | final appFinder = find.byType(MaterialApp); 14 | expect(appFinder, findsOneWidget); 15 | 16 | // Scaffold exist 17 | final scaffoldFinder = 18 | find.descendant(of: appFinder, matching: find.byType(Scaffold)); 19 | expect(scaffoldFinder, findsOneWidget); 20 | 21 | // Text is correct 22 | final titleFinder = 23 | find.descendant(of: scaffoldFinder, matching: find.byType(Text)); 24 | expect(titleFinder, findsNWidgets(2)); 25 | final title = tester.firstWidget(titleFinder) as Text; 26 | expect(title.data, equals(appName)); 27 | }); 28 | 29 | testWidgets("Default Color", (WidgetTester tester) async { 30 | await tester.pumpWidget(new Splash()); 31 | 32 | final logoFinder = find.byType(FlutterLogo); 33 | expect(logoFinder, findsOneWidget); 34 | final logo = tester.firstWidget(logoFinder) as FlutterLogo; 35 | expect(logo.colors, equals(Colors.pink)); 36 | }); 37 | }); 38 | } 39 | --------------------------------------------------------------------------------