├── .gitignore
├── .metadata
├── LICENSE
├── README.md
├── android
    ├── .project
    ├── .settings
    │   └── org.eclipse.buildship.core.prefs
    ├── app
    │   ├── .classpath
    │   ├── .project
    │   ├── .settings
    │   │   └── org.eclipse.buildship.core.prefs
    │   ├── build.gradle
    │   └── src
    │   │   └── main
    │   │       ├── AndroidManifest.xml
    │   │       ├── java
    │   │           └── com
    │   │           │   └── example
    │   │           │       └── briefing
    │   │           │           └── MainActivity.java
    │   │       └── res
    │   │           ├── drawable
    │   │               └── launch_background.xml
    │   │           ├── mipmap-hdpi
    │   │               └── ic_launcher.png
    │   │           ├── mipmap-mdpi
    │   │               └── ic_launcher.png
    │   │           ├── mipmap-xhdpi
    │   │               └── ic_launcher.png
    │   │           ├── mipmap-xxhdpi
    │   │               └── ic_launcher.png
    │   │           ├── mipmap-xxxhdpi
    │   │               └── ic_launcher.png
    │   │           └── values
    │   │               └── styles.xml
    ├── build.gradle
    ├── gradle.properties
    ├── gradle
    │   └── wrapper
    │   │   └── gradle-wrapper.properties
    └── settings.gradle
├── assets
    ├── fonts
    │   ├── Crimson_Text
    │   │   ├── CrimsonText-Bold.ttf
    │   │   ├── CrimsonText-BoldItalic.ttf
    │   │   ├── CrimsonText-Italic.ttf
    │   │   ├── CrimsonText-Regular.ttf
    │   │   ├── CrimsonText-SemiBold.ttf
    │   │   ├── CrimsonText-SemiBoldItalic.ttf
    │   │   └── OFL.txt
    │   └── Libre_Franklin
    │   │   ├── LibreFranklin-Bold.ttf
    │   │   └── LibreFranklin-Regular.ttf
    └── images
    │   └── no_internet.png
├── ios
    ├── Flutter
    │   ├── AppFrameworkInfo.plist
    │   ├── Debug.xcconfig
    │   └── Release.xcconfig
    ├── Runner.xcodeproj
    │   ├── project.pbxproj
    │   ├── project.xcworkspace
    │   │   └── contents.xcworkspacedata
    │   └── xcshareddata
    │   │   └── xcschemes
    │   │       └── Runner.xcscheme
    ├── Runner.xcworkspace
    │   └── contents.xcworkspacedata
    └── Runner
    │   ├── AppDelegate.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
    │   ├── Info.plist
    │   └── main.m
├── lib
    ├── bloc
    │   ├── bloc_article.dart
    │   ├── bloc_bookmark_article.dart
    │   └── bloc_provider.dart
    ├── bookmarked_article_list.dart
    ├── briefing_card.dart
    ├── briefing_sliver_list.dart
    ├── main.dart
    ├── model
    │   └── article.dart
    ├── repository
    │   └── repository.dart
    ├── service
    │   ├── api_service.dart
    │   └── database.dart
    ├── theme
    │   ├── colors.dart
    │   └── theme.dart
    ├── util
    │   └── rate_limiter.dart
    └── widget
    │   ├── app_menu.dart
    │   ├── article_bottom_section.dart
    │   ├── article_menu.dart
    │   ├── article_thumbnail.dart
    │   ├── article_title_section.dart
    │   └── main_sliverappbar.dart
├── pubspec.yaml
├── screenshots
    ├── ui_bottomsheet.jpg
    ├── ui_list.jpg
    └── ui_main_list.jpg
└── test
    └── widget_test.dart
/.gitignore:
--------------------------------------------------------------------------------
 1 | # Miscellaneous
 2 | *.class
 3 | *.lock
 4 | *.log
 5 | *.pyc
 6 | *.swp
 7 | .DS_Store
 8 | .atom/
 9 | .buildlog/
10 | .history
11 | .svn/
12 | .gitignore
13 | 
14 | # IntelliJ related
15 | *.iml
16 | *.ipr
17 | *.iws
18 | .idea/
19 | 
20 | # Visual Studio Code related
21 | .vscode/
22 | 
23 | # Flutter/Dart/Pub related
24 | **/doc/api/
25 | .dart_tool/
26 | .flutter-plugins
27 | .packages
28 | .pub-cache/
29 | .pub/
30 | build/
31 | .pubspec.lock
32 | 
33 | # Android related
34 | **/android/**/gradle-wrapper.jar
35 | **/android/.gradle
36 | **/android/captures/
37 | **/android/gradlew
38 | **/android/gradlew.bat
39 | **/android/local.properties
40 | **/android/**/GeneratedPluginRegistrant.java
41 | 
42 | # iOS/XCode related
43 | **/ios/**/*.mode1v3
44 | **/ios/**/*.mode2v3
45 | **/ios/**/*.moved-aside
46 | **/ios/**/*.pbxuser
47 | **/ios/**/*.perspectivev3
48 | **/ios/**/*sync/
49 | **/ios/**/.sconsign.dblite
50 | **/ios/**/.tags*
51 | **/ios/**/.vagrant/
52 | **/ios/**/DerivedData/
53 | **/ios/**/Icon?
54 | **/ios/**/Pods/
55 | **/ios/**/.symlinks/
56 | **/ios/**/profile
57 | **/ios/**/xcuserdata
58 | **/ios/.generated/
59 | **/ios/Flutter/App.framework
60 | **/ios/Flutter/Flutter.framework
61 | **/ios/Flutter/Generated.xcconfig
62 | **/ios/Flutter/app.flx
63 | **/ios/Flutter/app.zip
64 | **/ios/Flutter/flutter_assets/
65 | **/ios/ServiceDefinitions.json
66 | **/ios/Runner/GeneratedPluginRegistrant.*
67 | 
68 | # Exceptions to above rules.
69 | !**/ios/**/default.mode1v3
70 | !**/ios/**/default.mode2v3
71 | !**/ios/**/default.pbxuser
72 | !**/ios/**/default.perspectivev3
73 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
74 | 
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
 1 | # This file tracks properties of this Flutter project.
 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
 3 | #
 4 | # This file should be version controlled and should not be manually edited.
 5 | 
 6 | version:
 7 |   revision: 5391447fae6209bb21a89e6a5a6583cac1af9b4b
 8 |   channel: stable
 9 | 
10 | project_type: app
11 | 
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
 1 | MIT License
 2 | 
 3 | Copyright (c) 2019 Kakiang Kyanguinbo
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining a copy
 6 | of this software and associated documentation files (the "Software"), to deal
 7 | in the Software without restriction, including without limitation the rights
 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 | 
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 | 
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 | 
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
 1 | # Briefing
 2 | 
 3 | [](https://opensource.org/licenses/MIT)
 4 | 
 5 | An attempt to clone part of Google News app design using Flutter
 6 | 
 7 | > Hard-coded data
 8 | 
 9 | ## Screenshots
10 | 
11 |     
12 |       
13 |      
14 |       
15 |      
16 |       
17 |     
18 | 
 
19 | 
--------------------------------------------------------------------------------
/android/.project:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 	android
 4 | 	Project android created by Buildship.
 5 | 	
 6 | 	
 7 | 	
 8 | 		
 9 | 			org.eclipse.buildship.core.gradleprojectbuilder
10 | 			
11 | 			
12 | 		
13 | 	
14 | 	
15 | 		org.eclipse.buildship.core.gradleprojectnature
16 | 	
17 | 
18 | 
--------------------------------------------------------------------------------
/android/.settings/org.eclipse.buildship.core.prefs:
--------------------------------------------------------------------------------
1 | connection.project.dir=
2 | eclipse.preferences.version=1
3 | 
--------------------------------------------------------------------------------
/android/app/.classpath:
--------------------------------------------------------------------------------
1 | 
2 | 
3 | 	
4 | 	
5 | 	
6 | 
7 | 
--------------------------------------------------------------------------------
/android/app/.project:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 	app
 4 | 	Project app created by Buildship.
 5 | 	
 6 | 	
 7 | 	
 8 | 		
 9 | 			org.eclipse.jdt.core.javabuilder
10 | 			
11 | 			
12 | 		
13 | 		
14 | 			org.eclipse.buildship.core.gradleprojectbuilder
15 | 			
16 | 			
17 | 		
18 | 	
19 | 	
20 | 		org.eclipse.jdt.core.javanature
21 | 		org.eclipse.buildship.core.gradleprojectnature
22 | 	
23 | 
24 | 
--------------------------------------------------------------------------------
/android/app/.settings/org.eclipse.buildship.core.prefs:
--------------------------------------------------------------------------------
1 | connection.project.dir=..
2 | eclipse.preferences.version=1
3 | 
--------------------------------------------------------------------------------
/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 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
15 | if (flutterVersionCode == null) {
16 |     flutterVersionCode = '1'
17 | }
18 | 
19 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
20 | if (flutterVersionName == null) {
21 |     flutterVersionName = '1.0'
22 | }
23 | 
24 | apply plugin: 'com.android.application'
25 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
26 | 
27 | android {
28 |     compileSdkVersion 28
29 | 
30 |     lintOptions {
31 |         disable 'InvalidPackage'
32 |     }
33 | 
34 |     defaultConfig {
35 |         // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
36 |         applicationId "com.kakiang.briefing"
37 |         minSdkVersion 16
38 |         targetSdkVersion 28
39 |         versionCode flutterVersionCode.toInteger()
40 |         versionName flutterVersionName
41 |         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
42 |     }
43 | 
44 |     buildTypes {
45 |         release {
46 |             // TODO: Add your own signing config for the release build.
47 |             // Signing with the debug keys for now, so `flutter run --release` works.
48 |             signingConfig signingConfigs.debug
49 |         }
50 |     }
51 | }
52 | 
53 | flutter {
54 |     source '../..'
55 | }
56 | 
57 | dependencies {
58 |     testImplementation 'junit:junit:4.12'
59 |     androidTestImplementation 'com.android.support.test:runner:1.0.2'
60 |     androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
61 | }
62 | 
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
 1 | 
 3 | 
 4 |     
 8 |     
 9 | 
10 |     
15 |     
19 |         
26 |             
30 |             
33 |             
34 |                 
35 |                 
36 |             
37 |         
38 |     
39 | 
40 | 
--------------------------------------------------------------------------------
/android/app/src/main/java/com/example/briefing/MainActivity.java:
--------------------------------------------------------------------------------
 1 | package com.example.briefing;
 2 | 
 3 | import android.os.Bundle;
 4 | import io.flutter.app.FlutterActivity;
 5 | import io.flutter.plugins.GeneratedPluginRegistrant;
 6 | 
 7 | public class MainActivity extends FlutterActivity {
 8 |   @Override
 9 |   protected void onCreate(Bundle savedInstanceState) {
10 |     super.onCreate(savedInstanceState);
11 |     GeneratedPluginRegistrant.registerWith(this);
12 |   }
13 | }
14 | 
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 4 |      
 5 | 
 6 |     
 7 |     
12 | 
13 | 
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kakiang/briefing/bbab6e8743c4a749a873fce4b9c46b2b4415a62a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kakiang/briefing/bbab6e8743c4a749a873fce4b9c46b2b4415a62a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kakiang/briefing/bbab6e8743c4a749a873fce4b9c46b2b4415a62a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kakiang/briefing/bbab6e8743c4a749a873fce4b9c46b2b4415a62a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kakiang/briefing/bbab6e8743c4a749a873fce4b9c46b2b4415a62a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/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.2.1'
 9 |         // classpath 'com.android.tools.build:gradle:2.2.0'
10 |     }
11 | }
12 | 
13 | allprojects {
14 |     repositories {
15 |         google()
16 |         jcenter()
17 |     }
18 | }
19 | 
20 | rootProject.buildDir = '../build'
21 | subprojects {
22 |     project.buildDir = "${rootProject.buildDir}/${project.name}"
23 | }
24 | subprojects {
25 |     project.evaluationDependsOn(':app')
26 | }
27 | 
28 | task clean(type: Delete) {
29 |     delete rootProject.buildDir
30 | }
31 | 
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | 
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.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.9-all.zip
7 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
8 | 
--------------------------------------------------------------------------------
/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/fonts/Crimson_Text/CrimsonText-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kakiang/briefing/bbab6e8743c4a749a873fce4b9c46b2b4415a62a/assets/fonts/Crimson_Text/CrimsonText-Bold.ttf
--------------------------------------------------------------------------------
/assets/fonts/Crimson_Text/CrimsonText-BoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kakiang/briefing/bbab6e8743c4a749a873fce4b9c46b2b4415a62a/assets/fonts/Crimson_Text/CrimsonText-BoldItalic.ttf
--------------------------------------------------------------------------------
/assets/fonts/Crimson_Text/CrimsonText-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kakiang/briefing/bbab6e8743c4a749a873fce4b9c46b2b4415a62a/assets/fonts/Crimson_Text/CrimsonText-Italic.ttf
--------------------------------------------------------------------------------
/assets/fonts/Crimson_Text/CrimsonText-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kakiang/briefing/bbab6e8743c4a749a873fce4b9c46b2b4415a62a/assets/fonts/Crimson_Text/CrimsonText-Regular.ttf
--------------------------------------------------------------------------------
/assets/fonts/Crimson_Text/CrimsonText-SemiBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kakiang/briefing/bbab6e8743c4a749a873fce4b9c46b2b4415a62a/assets/fonts/Crimson_Text/CrimsonText-SemiBold.ttf
--------------------------------------------------------------------------------
/assets/fonts/Crimson_Text/CrimsonText-SemiBoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kakiang/briefing/bbab6e8743c4a749a873fce4b9c46b2b4415a62a/assets/fonts/Crimson_Text/CrimsonText-SemiBoldItalic.ttf
--------------------------------------------------------------------------------
/assets/fonts/Crimson_Text/OFL.txt:
--------------------------------------------------------------------------------
 1 | Copyright (c) 2010, Sebastian Kosch (sebastian@aldusleaf.org),
 2 | with Reserved Font Name "Crimson" and "Crimson Text".
 3 | 
 4 | This Font Software is licensed under the SIL Open Font License, Version 1.1.
 5 | This license is copied below, and is also available with a FAQ at:
 6 | http://scripts.sil.org/OFL
 7 | 
 8 | 
 9 | -----------------------------------------------------------
10 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
11 | -----------------------------------------------------------
12 | 
13 | PREAMBLE
14 | The goals of the Open Font License (OFL) are to stimulate worldwide
15 | development of collaborative font projects, to support the font creation
16 | efforts of academic and linguistic communities, and to provide a free and
17 | open framework in which fonts may be shared and improved in partnership
18 | with others.
19 | 
20 | The OFL allows the licensed fonts to be used, studied, modified and
21 | redistributed freely as long as they are not sold by themselves. The
22 | fonts, including any derivative works, can be bundled, embedded, 
23 | redistributed and/or sold with any software provided that any reserved
24 | names are not used by derivative works. The fonts and derivatives,
25 | however, cannot be released under any other type of license. The
26 | requirement for fonts to remain under this license does not apply
27 | to any document created using the fonts or their derivatives.
28 | 
29 | DEFINITIONS
30 | "Font Software" refers to the set of files released by the Copyright
31 | Holder(s) under this license and clearly marked as such. This may
32 | include source files, build scripts and documentation.
33 | 
34 | "Reserved Font Name" refers to any names specified as such after the
35 | copyright statement(s).
36 | 
37 | "Original Version" refers to the collection of Font Software components as
38 | distributed by the Copyright Holder(s).
39 | 
40 | "Modified Version" refers to any derivative made by adding to, deleting,
41 | or substituting -- in part or in whole -- any of the components of the
42 | Original Version, by changing formats or by porting the Font Software to a
43 | new environment.
44 | 
45 | "Author" refers to any designer, engineer, programmer, technical
46 | writer or other person who contributed to the Font Software.
47 | 
48 | PERMISSION & CONDITIONS
49 | Permission is hereby granted, free of charge, to any person obtaining
50 | a copy of the Font Software, to use, study, copy, merge, embed, modify,
51 | redistribute, and sell modified and unmodified copies of the Font
52 | Software, subject to the following conditions:
53 | 
54 | 1) Neither the Font Software nor any of its individual components,
55 | in Original or Modified Versions, may be sold by itself.
56 | 
57 | 2) Original or Modified Versions of the Font Software may be bundled,
58 | redistributed and/or sold with any software, provided that each copy
59 | contains the above copyright notice and this license. These can be
60 | included either as stand-alone text files, human-readable headers or
61 | in the appropriate machine-readable metadata fields within text or
62 | binary files as long as those fields can be easily viewed by the user.
63 | 
64 | 3) No Modified Version of the Font Software may use the Reserved Font
65 | Name(s) unless explicit written permission is granted by the corresponding
66 | Copyright Holder. This restriction only applies to the primary font name as
67 | presented to the users.
68 | 
69 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
70 | Software shall not be used to promote, endorse or advertise any
71 | Modified Version, except to acknowledge the contribution(s) of the
72 | Copyright Holder(s) and the Author(s) or with their explicit written
73 | permission.
74 | 
75 | 5) The Font Software, modified or unmodified, in part or in whole,
76 | must be distributed entirely under this license, and must not be
77 | distributed under any other license. The requirement for fonts to
78 | remain under this license does not apply to any document created
79 | using the Font Software.
80 | 
81 | TERMINATION
82 | This license becomes null and void if any of the above conditions are
83 | not met.
84 | 
85 | DISCLAIMER
86 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
87 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
88 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
89 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
90 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
91 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
92 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
93 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
94 | OTHER DEALINGS IN THE FONT SOFTWARE.
95 | 
--------------------------------------------------------------------------------
/assets/fonts/Libre_Franklin/LibreFranklin-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kakiang/briefing/bbab6e8743c4a749a873fce4b9c46b2b4415a62a/assets/fonts/Libre_Franklin/LibreFranklin-Bold.ttf
--------------------------------------------------------------------------------
/assets/fonts/Libre_Franklin/LibreFranklin-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kakiang/briefing/bbab6e8743c4a749a873fce4b9c46b2b4415a62a/assets/fonts/Libre_Franklin/LibreFranklin-Regular.ttf
--------------------------------------------------------------------------------
/assets/images/no_internet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kakiang/briefing/bbab6e8743c4a749a873fce4b9c46b2b4415a62a/assets/images/no_internet.png
--------------------------------------------------------------------------------
/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 |   MinimumOSVersion
24 |   8.0
25 | 
26 | 
27 | 
--------------------------------------------------------------------------------
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 | 
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 | 
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
  1 | // !$*UTF8*$!
  2 | {
  3 | 	archiveVersion = 1;
  4 | 	classes = {
  5 | 	};
  6 | 	objectVersion = 46;
  7 | 	objects = {
  8 | 
  9 | /* Begin PBXBuildFile section */
 10 | 		1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
 11 | 		2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */ = {isa = PBXBuildFile; fileRef = 2D5378251FAA1A9400D5DBA9 /* flutter_assets */; };
 12 | 		3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
 13 | 		3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
 14 | 		3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
 15 | 		9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
 16 | 		9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
 17 | 		9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; };
 18 | 		978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
 19 | 		97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
 20 | 		97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
 21 | 		97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
 22 | 		97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
 23 | /* End PBXBuildFile section */
 24 | 
 25 | /* Begin PBXCopyFilesBuildPhase section */
 26 | 		9705A1C41CF9048500538489 /* Embed Frameworks */ = {
 27 | 			isa = PBXCopyFilesBuildPhase;
 28 | 			buildActionMask = 2147483647;
 29 | 			dstPath = "";
 30 | 			dstSubfolderSpec = 10;
 31 | 			files = (
 32 | 				3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
 33 | 				9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
 34 | 			);
 35 | 			name = "Embed Frameworks";
 36 | 			runOnlyForDeploymentPostprocessing = 0;
 37 | 		};
 38 | /* End PBXCopyFilesBuildPhase section */
 39 | 
 40 | /* Begin PBXFileReference section */
 41 | 		1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
 42 | 		1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
 43 | 		2D5378251FAA1A9400D5DBA9 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = Flutter/flutter_assets; sourceTree = SOURCE_ROOT; };
 44 | 		3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
 45 | 		3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; };
 46 | 		7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
 47 | 		7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
 48 | 		7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
 49 | 		9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
 50 | 		9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
 51 | 		9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; };
 52 | 		97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
 53 | 		97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
 54 | 		97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
 55 | 		97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
 56 | 		97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
 57 | 		97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
 58 | /* End PBXFileReference section */
 59 | 
 60 | /* Begin PBXFrameworksBuildPhase section */
 61 | 		97C146EB1CF9000F007C117D /* Frameworks */ = {
 62 | 			isa = PBXFrameworksBuildPhase;
 63 | 			buildActionMask = 2147483647;
 64 | 			files = (
 65 | 				9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
 66 | 				3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
 67 | 			);
 68 | 			runOnlyForDeploymentPostprocessing = 0;
 69 | 		};
 70 | /* End PBXFrameworksBuildPhase section */
 71 | 
 72 | /* Begin PBXGroup section */
 73 | 		9740EEB11CF90186004384FC /* Flutter */ = {
 74 | 			isa = PBXGroup;
 75 | 			children = (
 76 | 				2D5378251FAA1A9400D5DBA9 /* flutter_assets */,
 77 | 				3B80C3931E831B6300D905FE /* App.framework */,
 78 | 				3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
 79 | 				9740EEBA1CF902C7004384FC /* Flutter.framework */,
 80 | 				9740EEB21CF90195004384FC /* Debug.xcconfig */,
 81 | 				7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
 82 | 				9740EEB31CF90195004384FC /* Generated.xcconfig */,
 83 | 			);
 84 | 			name = Flutter;
 85 | 			sourceTree = "";
 86 | 		};
 87 | 		97C146E51CF9000F007C117D = {
 88 | 			isa = PBXGroup;
 89 | 			children = (
 90 | 				9740EEB11CF90186004384FC /* Flutter */,
 91 | 				97C146F01CF9000F007C117D /* Runner */,
 92 | 				97C146EF1CF9000F007C117D /* Products */,
 93 | 				CF3B75C9A7D2FA2A4C99F110 /* Frameworks */,
 94 | 			);
 95 | 			sourceTree = "";
 96 | 		};
 97 | 		97C146EF1CF9000F007C117D /* Products */ = {
 98 | 			isa = PBXGroup;
 99 | 			children = (
100 | 				97C146EE1CF9000F007C117D /* Runner.app */,
101 | 			);
102 | 			name = Products;
103 | 			sourceTree = "";
104 | 		};
105 | 		97C146F01CF9000F007C117D /* Runner */ = {
106 | 			isa = PBXGroup;
107 | 			children = (
108 | 				7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */,
109 | 				7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */,
110 | 				97C146FA1CF9000F007C117D /* Main.storyboard */,
111 | 				97C146FD1CF9000F007C117D /* Assets.xcassets */,
112 | 				97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
113 | 				97C147021CF9000F007C117D /* Info.plist */,
114 | 				97C146F11CF9000F007C117D /* Supporting Files */,
115 | 				1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
116 | 				1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
117 | 			);
118 | 			path = Runner;
119 | 			sourceTree = "";
120 | 		};
121 | 		97C146F11CF9000F007C117D /* Supporting Files */ = {
122 | 			isa = PBXGroup;
123 | 			children = (
124 | 				97C146F21CF9000F007C117D /* main.m */,
125 | 			);
126 | 			name = "Supporting Files";
127 | 			sourceTree = "";
128 | 		};
129 | /* End PBXGroup section */
130 | 
131 | /* Begin PBXNativeTarget section */
132 | 		97C146ED1CF9000F007C117D /* Runner */ = {
133 | 			isa = PBXNativeTarget;
134 | 			buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
135 | 			buildPhases = (
136 | 				9740EEB61CF901F6004384FC /* Run Script */,
137 | 				97C146EA1CF9000F007C117D /* Sources */,
138 | 				97C146EB1CF9000F007C117D /* Frameworks */,
139 | 				97C146EC1CF9000F007C117D /* Resources */,
140 | 				9705A1C41CF9048500538489 /* Embed Frameworks */,
141 | 				3B06AD1E1E4923F5004D2608 /* Thin Binary */,
142 | 			);
143 | 			buildRules = (
144 | 			);
145 | 			dependencies = (
146 | 			);
147 | 			name = Runner;
148 | 			productName = Runner;
149 | 			productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
150 | 			productType = "com.apple.product-type.application";
151 | 		};
152 | /* End PBXNativeTarget section */
153 | 
154 | /* Begin PBXProject section */
155 | 		97C146E61CF9000F007C117D /* Project object */ = {
156 | 			isa = PBXProject;
157 | 			attributes = {
158 | 				LastUpgradeCheck = 0910;
159 | 				ORGANIZATIONNAME = "The Chromium Authors";
160 | 				TargetAttributes = {
161 | 					97C146ED1CF9000F007C117D = {
162 | 						CreatedOnToolsVersion = 7.3.1;
163 | 					};
164 | 				};
165 | 			};
166 | 			buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
167 | 			compatibilityVersion = "Xcode 3.2";
168 | 			developmentRegion = English;
169 | 			hasScannedForEncodings = 0;
170 | 			knownRegions = (
171 | 				en,
172 | 				Base,
173 | 			);
174 | 			mainGroup = 97C146E51CF9000F007C117D;
175 | 			productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
176 | 			projectDirPath = "";
177 | 			projectRoot = "";
178 | 			targets = (
179 | 				97C146ED1CF9000F007C117D /* Runner */,
180 | 			);
181 | 		};
182 | /* End PBXProject section */
183 | 
184 | /* Begin PBXResourcesBuildPhase section */
185 | 		97C146EC1CF9000F007C117D /* Resources */ = {
186 | 			isa = PBXResourcesBuildPhase;
187 | 			buildActionMask = 2147483647;
188 | 			files = (
189 | 				97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
190 | 				3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
191 | 				9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */,
192 | 				97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
193 | 				2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */,
194 | 				97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
195 | 			);
196 | 			runOnlyForDeploymentPostprocessing = 0;
197 | 		};
198 | /* End PBXResourcesBuildPhase section */
199 | 
200 | /* Begin PBXShellScriptBuildPhase section */
201 | 		3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
202 | 			isa = PBXShellScriptBuildPhase;
203 | 			buildActionMask = 2147483647;
204 | 			files = (
205 | 			);
206 | 			inputPaths = (
207 | 			);
208 | 			name = "Thin Binary";
209 | 			outputPaths = (
210 | 			);
211 | 			runOnlyForDeploymentPostprocessing = 0;
212 | 			shellPath = /bin/sh;
213 | 			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
214 | 		};
215 | 		9740EEB61CF901F6004384FC /* Run Script */ = {
216 | 			isa = PBXShellScriptBuildPhase;
217 | 			buildActionMask = 2147483647;
218 | 			files = (
219 | 			);
220 | 			inputPaths = (
221 | 			);
222 | 			name = "Run Script";
223 | 			outputPaths = (
224 | 			);
225 | 			runOnlyForDeploymentPostprocessing = 0;
226 | 			shellPath = /bin/sh;
227 | 			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
228 | 		};
229 | /* End PBXShellScriptBuildPhase section */
230 | 
231 | /* Begin PBXSourcesBuildPhase section */
232 | 		97C146EA1CF9000F007C117D /* Sources */ = {
233 | 			isa = PBXSourcesBuildPhase;
234 | 			buildActionMask = 2147483647;
235 | 			files = (
236 | 				978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */,
237 | 				97C146F31CF9000F007C117D /* main.m in Sources */,
238 | 				1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
239 | 			);
240 | 			runOnlyForDeploymentPostprocessing = 0;
241 | 		};
242 | /* End PBXSourcesBuildPhase section */
243 | 
244 | /* Begin PBXVariantGroup section */
245 | 		97C146FA1CF9000F007C117D /* Main.storyboard */ = {
246 | 			isa = PBXVariantGroup;
247 | 			children = (
248 | 				97C146FB1CF9000F007C117D /* Base */,
249 | 			);
250 | 			name = Main.storyboard;
251 | 			sourceTree = "";
252 | 		};
253 | 		97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
254 | 			isa = PBXVariantGroup;
255 | 			children = (
256 | 				97C147001CF9000F007C117D /* Base */,
257 | 			);
258 | 			name = LaunchScreen.storyboard;
259 | 			sourceTree = "";
260 | 		};
261 | /* End PBXVariantGroup section */
262 | 
263 | /* Begin XCBuildConfiguration section */
264 | 		249021D3217E4FDB00AE95B9 /* Profile */ = {
265 | 			isa = XCBuildConfiguration;
266 | 			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
267 | 			buildSettings = {
268 | 				ALWAYS_SEARCH_USER_PATHS = NO;
269 | 				CLANG_ANALYZER_NONNULL = YES;
270 | 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
271 | 				CLANG_CXX_LIBRARY = "libc++";
272 | 				CLANG_ENABLE_MODULES = YES;
273 | 				CLANG_ENABLE_OBJC_ARC = YES;
274 | 				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
275 | 				CLANG_WARN_BOOL_CONVERSION = YES;
276 | 				CLANG_WARN_COMMA = YES;
277 | 				CLANG_WARN_CONSTANT_CONVERSION = YES;
278 | 				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
279 | 				CLANG_WARN_EMPTY_BODY = YES;
280 | 				CLANG_WARN_ENUM_CONVERSION = YES;
281 | 				CLANG_WARN_INFINITE_RECURSION = YES;
282 | 				CLANG_WARN_INT_CONVERSION = YES;
283 | 				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
284 | 				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
285 | 				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
286 | 				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
287 | 				CLANG_WARN_STRICT_PROTOTYPES = YES;
288 | 				CLANG_WARN_SUSPICIOUS_MOVE = YES;
289 | 				CLANG_WARN_UNREACHABLE_CODE = YES;
290 | 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
291 | 				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
292 | 				COPY_PHASE_STRIP = NO;
293 | 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
294 | 				ENABLE_NS_ASSERTIONS = NO;
295 | 				ENABLE_STRICT_OBJC_MSGSEND = YES;
296 | 				GCC_C_LANGUAGE_STANDARD = gnu99;
297 | 				GCC_NO_COMMON_BLOCKS = YES;
298 | 				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
299 | 				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
300 | 				GCC_WARN_UNDECLARED_SELECTOR = YES;
301 | 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
302 | 				GCC_WARN_UNUSED_FUNCTION = YES;
303 | 				GCC_WARN_UNUSED_VARIABLE = YES;
304 | 				IPHONEOS_DEPLOYMENT_TARGET = 8.0;
305 | 				MTL_ENABLE_DEBUG_INFO = NO;
306 | 				SDKROOT = iphoneos;
307 | 				TARGETED_DEVICE_FAMILY = "1,2";
308 | 				VALIDATE_PRODUCT = YES;
309 | 			};
310 | 			name = Profile;
311 | 		};
312 | 		249021D4217E4FDB00AE95B9 /* Profile */ = {
313 | 			isa = XCBuildConfiguration;
314 | 			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
315 | 			buildSettings = {
316 | 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
317 | 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
318 | 				DEVELOPMENT_TEAM = S8QB4VV633;
319 | 				ENABLE_BITCODE = NO;
320 | 				FRAMEWORK_SEARCH_PATHS = (
321 | 					"$(inherited)",
322 | 					"$(PROJECT_DIR)/Flutter",
323 | 				);
324 | 				INFOPLIST_FILE = Runner/Info.plist;
325 | 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
326 | 				LIBRARY_SEARCH_PATHS = (
327 | 					"$(inherited)",
328 | 					"$(PROJECT_DIR)/Flutter",
329 | 				);
330 | 				PRODUCT_BUNDLE_IDENTIFIER = com.example.briefing;
331 | 				PRODUCT_NAME = "$(TARGET_NAME)";
332 | 				VERSIONING_SYSTEM = "apple-generic";
333 | 			};
334 | 			name = Profile;
335 | 		};
336 | 		97C147031CF9000F007C117D /* Debug */ = {
337 | 			isa = XCBuildConfiguration;
338 | 			baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
339 | 			buildSettings = {
340 | 				ALWAYS_SEARCH_USER_PATHS = NO;
341 | 				CLANG_ANALYZER_NONNULL = YES;
342 | 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
343 | 				CLANG_CXX_LIBRARY = "libc++";
344 | 				CLANG_ENABLE_MODULES = YES;
345 | 				CLANG_ENABLE_OBJC_ARC = YES;
346 | 				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
347 | 				CLANG_WARN_BOOL_CONVERSION = YES;
348 | 				CLANG_WARN_COMMA = YES;
349 | 				CLANG_WARN_CONSTANT_CONVERSION = YES;
350 | 				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
351 | 				CLANG_WARN_EMPTY_BODY = YES;
352 | 				CLANG_WARN_ENUM_CONVERSION = YES;
353 | 				CLANG_WARN_INFINITE_RECURSION = YES;
354 | 				CLANG_WARN_INT_CONVERSION = YES;
355 | 				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
356 | 				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
357 | 				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
358 | 				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
359 | 				CLANG_WARN_STRICT_PROTOTYPES = YES;
360 | 				CLANG_WARN_SUSPICIOUS_MOVE = YES;
361 | 				CLANG_WARN_UNREACHABLE_CODE = YES;
362 | 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
363 | 				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
364 | 				COPY_PHASE_STRIP = NO;
365 | 				DEBUG_INFORMATION_FORMAT = dwarf;
366 | 				ENABLE_STRICT_OBJC_MSGSEND = YES;
367 | 				ENABLE_TESTABILITY = YES;
368 | 				GCC_C_LANGUAGE_STANDARD = gnu99;
369 | 				GCC_DYNAMIC_NO_PIC = NO;
370 | 				GCC_NO_COMMON_BLOCKS = YES;
371 | 				GCC_OPTIMIZATION_LEVEL = 0;
372 | 				GCC_PREPROCESSOR_DEFINITIONS = (
373 | 					"DEBUG=1",
374 | 					"$(inherited)",
375 | 				);
376 | 				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
377 | 				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
378 | 				GCC_WARN_UNDECLARED_SELECTOR = YES;
379 | 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
380 | 				GCC_WARN_UNUSED_FUNCTION = YES;
381 | 				GCC_WARN_UNUSED_VARIABLE = YES;
382 | 				IPHONEOS_DEPLOYMENT_TARGET = 8.0;
383 | 				MTL_ENABLE_DEBUG_INFO = YES;
384 | 				ONLY_ACTIVE_ARCH = YES;
385 | 				SDKROOT = iphoneos;
386 | 				TARGETED_DEVICE_FAMILY = "1,2";
387 | 			};
388 | 			name = Debug;
389 | 		};
390 | 		97C147041CF9000F007C117D /* Release */ = {
391 | 			isa = XCBuildConfiguration;
392 | 			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
393 | 			buildSettings = {
394 | 				ALWAYS_SEARCH_USER_PATHS = NO;
395 | 				CLANG_ANALYZER_NONNULL = YES;
396 | 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
397 | 				CLANG_CXX_LIBRARY = "libc++";
398 | 				CLANG_ENABLE_MODULES = YES;
399 | 				CLANG_ENABLE_OBJC_ARC = YES;
400 | 				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
401 | 				CLANG_WARN_BOOL_CONVERSION = YES;
402 | 				CLANG_WARN_COMMA = YES;
403 | 				CLANG_WARN_CONSTANT_CONVERSION = YES;
404 | 				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
405 | 				CLANG_WARN_EMPTY_BODY = YES;
406 | 				CLANG_WARN_ENUM_CONVERSION = YES;
407 | 				CLANG_WARN_INFINITE_RECURSION = YES;
408 | 				CLANG_WARN_INT_CONVERSION = YES;
409 | 				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
410 | 				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
411 | 				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
412 | 				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
413 | 				CLANG_WARN_STRICT_PROTOTYPES = YES;
414 | 				CLANG_WARN_SUSPICIOUS_MOVE = YES;
415 | 				CLANG_WARN_UNREACHABLE_CODE = YES;
416 | 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
417 | 				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
418 | 				COPY_PHASE_STRIP = NO;
419 | 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
420 | 				ENABLE_NS_ASSERTIONS = NO;
421 | 				ENABLE_STRICT_OBJC_MSGSEND = YES;
422 | 				GCC_C_LANGUAGE_STANDARD = gnu99;
423 | 				GCC_NO_COMMON_BLOCKS = YES;
424 | 				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
425 | 				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
426 | 				GCC_WARN_UNDECLARED_SELECTOR = YES;
427 | 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
428 | 				GCC_WARN_UNUSED_FUNCTION = YES;
429 | 				GCC_WARN_UNUSED_VARIABLE = YES;
430 | 				IPHONEOS_DEPLOYMENT_TARGET = 8.0;
431 | 				MTL_ENABLE_DEBUG_INFO = NO;
432 | 				SDKROOT = iphoneos;
433 | 				TARGETED_DEVICE_FAMILY = "1,2";
434 | 				VALIDATE_PRODUCT = YES;
435 | 			};
436 | 			name = Release;
437 | 		};
438 | 		97C147061CF9000F007C117D /* Debug */ = {
439 | 			isa = XCBuildConfiguration;
440 | 			baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
441 | 			buildSettings = {
442 | 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
443 | 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
444 | 				ENABLE_BITCODE = NO;
445 | 				FRAMEWORK_SEARCH_PATHS = (
446 | 					"$(inherited)",
447 | 					"$(PROJECT_DIR)/Flutter",
448 | 				);
449 | 				INFOPLIST_FILE = Runner/Info.plist;
450 | 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
451 | 				LIBRARY_SEARCH_PATHS = (
452 | 					"$(inherited)",
453 | 					"$(PROJECT_DIR)/Flutter",
454 | 				);
455 | 				PRODUCT_BUNDLE_IDENTIFIER = com.example.briefing;
456 | 				PRODUCT_NAME = "$(TARGET_NAME)";
457 | 				VERSIONING_SYSTEM = "apple-generic";
458 | 			};
459 | 			name = Debug;
460 | 		};
461 | 		97C147071CF9000F007C117D /* Release */ = {
462 | 			isa = XCBuildConfiguration;
463 | 			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
464 | 			buildSettings = {
465 | 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
466 | 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
467 | 				ENABLE_BITCODE = NO;
468 | 				FRAMEWORK_SEARCH_PATHS = (
469 | 					"$(inherited)",
470 | 					"$(PROJECT_DIR)/Flutter",
471 | 				);
472 | 				INFOPLIST_FILE = Runner/Info.plist;
473 | 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
474 | 				LIBRARY_SEARCH_PATHS = (
475 | 					"$(inherited)",
476 | 					"$(PROJECT_DIR)/Flutter",
477 | 				);
478 | 				PRODUCT_BUNDLE_IDENTIFIER = com.example.briefing;
479 | 				PRODUCT_NAME = "$(TARGET_NAME)";
480 | 				VERSIONING_SYSTEM = "apple-generic";
481 | 			};
482 | 			name = Release;
483 | 		};
484 | /* End XCBuildConfiguration section */
485 | 
486 | /* Begin XCConfigurationList section */
487 | 		97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
488 | 			isa = XCConfigurationList;
489 | 			buildConfigurations = (
490 | 				97C147031CF9000F007C117D /* Debug */,
491 | 				97C147041CF9000F007C117D /* Release */,
492 | 				249021D3217E4FDB00AE95B9 /* Profile */,
493 | 			);
494 | 			defaultConfigurationIsVisible = 0;
495 | 			defaultConfigurationName = Release;
496 | 		};
497 | 		97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
498 | 			isa = XCConfigurationList;
499 | 			buildConfigurations = (
500 | 				97C147061CF9000F007C117D /* Debug */,
501 | 				97C147071CF9000F007C117D /* Release */,
502 | 				249021D4217E4FDB00AE95B9 /* Profile */,
503 | 			);
504 | 			defaultConfigurationIsVisible = 0;
505 | 			defaultConfigurationName = Release;
506 | 		};
507 | /* End XCConfigurationList section */
508 | 	};
509 | 	rootObject = 97C146E61CF9000F007C117D /* Project object */;
510 | }
511 | 
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 | 
2 | 
4 |    
6 |    
7 | 
8 | 
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 5 |    
 8 |       
 9 |          
15 |             
21 |             
22 |          
23 |       
24 |    
25 |    
31 |       
32 |       
33 |       
34 |          
40 |          
41 |       
42 |       
43 |       
44 |    
45 |    
56 |       
58 |          
64 |          
65 |       
66 |       
67 |       
68 |    
69 |    
75 |       
77 |          
83 |          
84 |       
85 |    
86 |    
88 |    
89 |    
92 |    
93 | 
94 | 
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 | 
2 | 
4 |    
6 |    
7 | 
8 | 
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.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
 7 |     didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
 8 |   [GeneratedPluginRegistrant registerWithRegistry:self];
 9 |   // Override point for customization after application launch.
10 |   return [super application:application didFinishLaunchingWithOptions:launchOptions];
11 | }
12 | 
13 | @end
14 | 
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
  1 | {
  2 |   "images" : [
  3 |     {
  4 |       "size" : "20x20",
  5 |       "idiom" : "iphone",
  6 |       "filename" : "Icon-App-20x20@2x.png",
  7 |       "scale" : "2x"
  8 |     },
  9 |     {
 10 |       "size" : "20x20",
 11 |       "idiom" : "iphone",
 12 |       "filename" : "Icon-App-20x20@3x.png",
 13 |       "scale" : "3x"
 14 |     },
 15 |     {
 16 |       "size" : "29x29",
 17 |       "idiom" : "iphone",
 18 |       "filename" : "Icon-App-29x29@1x.png",
 19 |       "scale" : "1x"
 20 |     },
 21 |     {
 22 |       "size" : "29x29",
 23 |       "idiom" : "iphone",
 24 |       "filename" : "Icon-App-29x29@2x.png",
 25 |       "scale" : "2x"
 26 |     },
 27 |     {
 28 |       "size" : "29x29",
 29 |       "idiom" : "iphone",
 30 |       "filename" : "Icon-App-29x29@3x.png",
 31 |       "scale" : "3x"
 32 |     },
 33 |     {
 34 |       "size" : "40x40",
 35 |       "idiom" : "iphone",
 36 |       "filename" : "Icon-App-40x40@2x.png",
 37 |       "scale" : "2x"
 38 |     },
 39 |     {
 40 |       "size" : "40x40",
 41 |       "idiom" : "iphone",
 42 |       "filename" : "Icon-App-40x40@3x.png",
 43 |       "scale" : "3x"
 44 |     },
 45 |     {
 46 |       "size" : "60x60",
 47 |       "idiom" : "iphone",
 48 |       "filename" : "Icon-App-60x60@2x.png",
 49 |       "scale" : "2x"
 50 |     },
 51 |     {
 52 |       "size" : "60x60",
 53 |       "idiom" : "iphone",
 54 |       "filename" : "Icon-App-60x60@3x.png",
 55 |       "scale" : "3x"
 56 |     },
 57 |     {
 58 |       "size" : "20x20",
 59 |       "idiom" : "ipad",
 60 |       "filename" : "Icon-App-20x20@1x.png",
 61 |       "scale" : "1x"
 62 |     },
 63 |     {
 64 |       "size" : "20x20",
 65 |       "idiom" : "ipad",
 66 |       "filename" : "Icon-App-20x20@2x.png",
 67 |       "scale" : "2x"
 68 |     },
 69 |     {
 70 |       "size" : "29x29",
 71 |       "idiom" : "ipad",
 72 |       "filename" : "Icon-App-29x29@1x.png",
 73 |       "scale" : "1x"
 74 |     },
 75 |     {
 76 |       "size" : "29x29",
 77 |       "idiom" : "ipad",
 78 |       "filename" : "Icon-App-29x29@2x.png",
 79 |       "scale" : "2x"
 80 |     },
 81 |     {
 82 |       "size" : "40x40",
 83 |       "idiom" : "ipad",
 84 |       "filename" : "Icon-App-40x40@1x.png",
 85 |       "scale" : "1x"
 86 |     },
 87 |     {
 88 |       "size" : "40x40",
 89 |       "idiom" : "ipad",
 90 |       "filename" : "Icon-App-40x40@2x.png",
 91 |       "scale" : "2x"
 92 |     },
 93 |     {
 94 |       "size" : "76x76",
 95 |       "idiom" : "ipad",
 96 |       "filename" : "Icon-App-76x76@1x.png",
 97 |       "scale" : "1x"
 98 |     },
 99 |     {
100 |       "size" : "76x76",
101 |       "idiom" : "ipad",
102 |       "filename" : "Icon-App-76x76@2x.png",
103 |       "scale" : "2x"
104 |     },
105 |     {
106 |       "size" : "83.5x83.5",
107 |       "idiom" : "ipad",
108 |       "filename" : "Icon-App-83.5x83.5@2x.png",
109 |       "scale" : "2x"
110 |     },
111 |     {
112 |       "size" : "1024x1024",
113 |       "idiom" : "ios-marketing",
114 |       "filename" : "Icon-App-1024x1024@1x.png",
115 |       "scale" : "1x"
116 |     }
117 |   ],
118 |   "info" : {
119 |     "version" : 1,
120 |     "author" : "xcode"
121 |   }
122 | }
123 | 
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kakiang/briefing/bbab6e8743c4a749a873fce4b9c46b2b4415a62a/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/kakiang/briefing/bbab6e8743c4a749a873fce4b9c46b2b4415a62a/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/kakiang/briefing/bbab6e8743c4a749a873fce4b9c46b2b4415a62a/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/kakiang/briefing/bbab6e8743c4a749a873fce4b9c46b2b4415a62a/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/kakiang/briefing/bbab6e8743c4a749a873fce4b9c46b2b4415a62a/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/kakiang/briefing/bbab6e8743c4a749a873fce4b9c46b2b4415a62a/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/kakiang/briefing/bbab6e8743c4a749a873fce4b9c46b2b4415a62a/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/kakiang/briefing/bbab6e8743c4a749a873fce4b9c46b2b4415a62a/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/kakiang/briefing/bbab6e8743c4a749a873fce4b9c46b2b4415a62a/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/kakiang/briefing/bbab6e8743c4a749a873fce4b9c46b2b4415a62a/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/kakiang/briefing/bbab6e8743c4a749a873fce4b9c46b2b4415a62a/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/kakiang/briefing/bbab6e8743c4a749a873fce4b9c46b2b4415a62a/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/kakiang/briefing/bbab6e8743c4a749a873fce4b9c46b2b4415a62a/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/kakiang/briefing/bbab6e8743c4a749a873fce4b9c46b2b4415a62a/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/kakiang/briefing/bbab6e8743c4a749a873fce4b9c46b2b4415a62a/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/kakiang/briefing/bbab6e8743c4a749a873fce4b9c46b2b4415a62a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kakiang/briefing/bbab6e8743c4a749a873fce4b9c46b2b4415a62a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kakiang/briefing/bbab6e8743c4a749a873fce4b9c46b2b4415a62a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 | 
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 | 
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |     
 4 |         
 5 |         
 6 |     
 7 |     
 8 |         
 9 |         
10 |             
11 |                 
12 |                     
13 |                         
14 |                         
15 |                     
16 |                     
17 |                         
18 |                         
19 |                             
20 |                             
21 |                         
22 |                         
23 |                         
24 |                             
25 |                             
26 |                         
27 |                     
28 |                 
29 |                 
30 |             
31 |             
32 |         
33 |     
34 |     
35 |         
36 |     
37 | 
38 | 
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |     
 4 |         
 5 |         
 6 |     
 7 |     
 8 |         
 9 |         
10 |             
11 |                 
12 |                     
13 |                         
14 |                         
15 |                     
16 |                     
17 |                         
18 |                         
19 |                         
20 |                     
21 |                 
22 |                 
23 |             
24 |         
25 |     
26 | 
27 | 
--------------------------------------------------------------------------------
/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 4 | 
 5 | 	CFBundleDevelopmentRegion
 6 | 	en
 7 | 	CFBundleExecutable
 8 | 	$(EXECUTABLE_NAME)
 9 | 	CFBundleIdentifier
10 | 	$(PRODUCT_BUNDLE_IDENTIFIER)
11 | 	CFBundleInfoDictionaryVersion
12 | 	6.0
13 | 	CFBundleName
14 | 	briefing
15 | 	CFBundlePackageType
16 | 	APPL
17 | 	CFBundleShortVersionString
18 | 	$(FLUTTER_BUILD_NAME)
19 | 	CFBundleSignature
20 | 	????
21 | 	CFBundleVersion
22 | 	$(FLUTTER_BUILD_NUMBER)
23 | 	LSRequiresIPhoneOS
24 | 	
25 | 	UILaunchStoryboardName
26 | 	LaunchScreen
27 | 	UIMainStoryboardFile
28 | 	Main
29 | 	UISupportedInterfaceOrientations
30 | 	
31 | 		UIInterfaceOrientationPortrait
32 | 		UIInterfaceOrientationLandscapeLeft
33 | 		UIInterfaceOrientationLandscapeRight
34 | 	
35 | 	UISupportedInterfaceOrientations~ipad
36 | 	
37 | 		UIInterfaceOrientationPortrait
38 | 		UIInterfaceOrientationPortraitUpsideDown
39 | 		UIInterfaceOrientationLandscapeLeft
40 | 		UIInterfaceOrientationLandscapeRight
41 | 	
42 | 	UIViewControllerBasedStatusBarAppearance
43 | 	
44 | 
45 | 
46 | 
--------------------------------------------------------------------------------
/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/bloc/bloc_article.dart:
--------------------------------------------------------------------------------
  1 | import 'dart:async';
  2 | 
  3 | import 'package:briefing/bloc/bloc_provider.dart';
  4 | import 'package:briefing/model/article.dart';
  5 | import 'package:briefing/repository/repository.dart';
  6 | import 'package:briefing/util/rate_limiter.dart';
  7 | import 'package:rxdart/rxdart.dart';
  8 | 
  9 | class ArticleListBloc extends BlocBase {
 10 |   RateLimiter rateLimit = getRateLimiter;
 11 |   final menuSubject = BehaviorSubject.seeded(Menu.local);
 12 |   final _articleListSubject = BehaviorSubject>();
 13 |   final _articleCategorySubject = BehaviorSubject.seeded('All');
 14 |   List _articleList = [];
 15 | 
 16 |   Stream> get articleListObservable => _articleListSubject.stream;
 17 | 
 18 |   Stream get categoryObservable => _articleCategorySubject.stream;
 19 | 
 20 |   Sink get categorySink => _articleCategorySubject.sink;
 21 | 
 22 |   ArticleListBloc() {
 23 |     _articleListSubject.add(_articleList);
 24 | 
 25 |     categoryListener();
 26 |   }
 27 | 
 28 |   categoryListener() {
 29 |     categoryObservable.listen((category) async {
 30 |       print('categoryObservable.listen($category)');
 31 |       await _fetchDataAndPushToStream(category: categories[category]);
 32 |     });
 33 |   }
 34 | 
 35 |   refresh() async {
 36 |     print(':::refresh:::');
 37 |     var key = await categoryObservable.last;
 38 |     await rateLimit.reset(key);
 39 |     await _fetchDataAndPushToStream();
 40 |   }
 41 | 
 42 |   Future _fetchDataAndPushToStream({category}) async {
 43 |     await _loadFromDatabase(category: category);
 44 |     if (_articleList.isEmpty || await shouldFetch(category)) {
 45 |       await _fetchFromNetwork(category: category);
 46 |     }
 47 |   }
 48 | 
 49 |   sendToStream(List articles) {
 50 |     _articleList.clear();
 51 |     _articleList.addAll(articles);
 52 |     _articleListSubject.sink.add(_articleList);
 53 |   }
 54 | 
 55 |   @override
 56 |   dispose() {
 57 |     _articleListSubject.close();
 58 |     _articleCategorySubject.close();
 59 |     menuSubject.close();
 60 |   }
 61 | 
 62 |   Future _loadFromDatabase({String category}) async {
 63 |     try {
 64 |       var localData = await RepositoryArticle.getAllArticleByCategory(category);
 65 | 
 66 |       if (localData.isNotEmpty) {
 67 |         sendToStream(localData);
 68 |       } else {
 69 |         sendErrorMessage('No $category articles');
 70 |       }
 71 |     } catch (e) {
 72 |       print('=== _loadFromDb $e');
 73 |     }
 74 |   }
 75 | 
 76 |   Future _fetchFromNetwork({country = 'us', category}) async {
 77 |     var articles =
 78 |         await RepositoryArticle.getArticleListFromNetwork(country, category);
 79 |     if (articles.isNotEmpty) {
 80 |       try {
 81 |         await RepositoryArticle.insertArticleList(articles, category: category);
 82 |       } catch (error) {
 83 |         print('DB Error ${error.toString()}');
 84 |       }
 85 |       await _loadFromDatabase(category: category);
 86 |     }
 87 |   }
 88 | 
 89 |   Future shouldFetch(key) async {
 90 |     return await rateLimit.shouldFetch(key);
 91 |   }
 92 | 
 93 |   void sendErrorMessage([String message = "Can't connect to the internet!"]) {
 94 |     print('sendErrorMessage');
 95 |     _articleListSubject.addError(BriefingError(message));
 96 |     _articleList.clear();
 97 |   }
 98 | }
 99 | 
100 | class BriefingError extends Error {
101 |   final String message;
102 | 
103 |   BriefingError(this.message);
104 | 
105 |   @override
106 |   String toString() {
107 |     return '$message';
108 |   }
109 | }
110 | 
--------------------------------------------------------------------------------
/lib/bloc/bloc_bookmark_article.dart:
--------------------------------------------------------------------------------
 1 | import 'dart:async';
 2 | 
 3 | import 'package:briefing/bloc/bloc_provider.dart';
 4 | import 'package:briefing/model/article.dart';
 5 | import 'package:briefing/repository/repository.dart';
 6 | import 'package:rxdart/rxdart.dart';
 7 | 
 8 | class BookmarkArticleListBloc extends BlocBase {
 9 |   final _articleListSubject = BehaviorSubject>();
10 |   List _articleList = [];
11 | 
12 |   Stream> get articleListObservable => _articleListSubject.stream;
13 | 
14 |   BookmarkArticleListBloc() {
15 |     _articleListSubject.add(_articleList);
16 | 
17 |     _initList();
18 |   }
19 | 
20 |   _initList() async {
21 |     print('menu == Menu.favorites');
22 |     await _loadBookmarkedArticlesFromDatabase();
23 |   }
24 | 
25 |   sendToStream(List articles) {
26 |     _articleList.clear();
27 |     _articleList.addAll(articles);
28 |     _articleListSubject.sink.add(_articleList);
29 |   }
30 | 
31 |   @override
32 |   dispose() {
33 |     _articleListSubject.close();
34 |   }
35 | 
36 |   Future _loadBookmarkedArticlesFromDatabase() async {
37 |     try {
38 |       var localData = await RepositoryArticle.getBookmarkedArticles();
39 |       if (localData.isNotEmpty) {
40 |         sendToStream(localData);
41 |       } else {
42 |         sendErrorMessage('No bookmarked articles');
43 |       }
44 |     } catch (e) {
45 |       print(e);
46 |     }
47 |   }
48 | 
49 |   void sendErrorMessage([String message = "Can't connect to the internet!"]) {
50 |     print('sendErrorMessage');
51 |     _articleListSubject.addError(BriefingError(message));
52 |     _articleList.clear();
53 |   }
54 | }
55 | 
56 | class BriefingError extends Error {
57 |   final String message;
58 | 
59 |   BriefingError(this.message);
60 | 
61 |   @override
62 |   String toString() {
63 |     return '$message';
64 |   }
65 | }
66 | 
--------------------------------------------------------------------------------
/lib/bloc/bloc_provider.dart:
--------------------------------------------------------------------------------
 1 | import 'package:flutter/material.dart';
 2 | 
 3 | //to enforce the implementation of the dispose() method inside the bloc
 4 | abstract class BlocBase {
 5 |   void dispose();
 6 | }
 7 | 
 8 | class BlocProvider extends StatefulWidget {
 9 |   final Widget child;
10 |   final T bloc;
11 | 
12 |   const BlocProvider({Key key, @required this.bloc, @required this.child})
13 |       : super(key: key);
14 | 
15 |   @override
16 |   _BlocProviderState createState() => _BlocProviderState();
17 | 
18 |   static T of(BuildContext context) {
19 |     final type = _typeOf<_BlocProviderInherited>();
20 |     _BlocProviderInherited provider =
21 |         context.ancestorInheritedElementForWidgetOfExactType(type)?.widget;
22 |     return provider?.bloc;
23 |   }
24 | }
25 | 
26 | class _BlocProviderState extends State> {
27 |   @override
28 |   void dispose() {
29 |     widget.bloc.dispose();
30 |     super.dispose();
31 |   }
32 | 
33 |   @override
34 |   Widget build(BuildContext context) {
35 |     return _BlocProviderInherited(
36 |       bloc: widget.bloc,
37 |       child: widget.child,
38 |     );
39 |   }
40 | }
41 | 
42 | class _BlocProviderInherited extends InheritedWidget {
43 |   final T bloc;
44 | 
45 |   _BlocProviderInherited({Key key, @required Widget child, @required this.bloc})
46 |       : super(key: key, child: child);
47 | 
48 |   @override
49 |   bool updateShouldNotify(_BlocProviderInherited oldWidget) {
50 |     return oldWidget.bloc != bloc;
51 |   }
52 | }
53 | 
54 | Type _typeOf() => T;
55 | 
--------------------------------------------------------------------------------
/lib/bookmarked_article_list.dart:
--------------------------------------------------------------------------------
  1 | import 'dart:async';
  2 | 
  3 | import 'package:briefing/bloc/bloc_article.dart';
  4 | import 'package:briefing/bloc/bloc_bookmark_article.dart';
  5 | import 'package:briefing/briefing_card.dart';
  6 | import 'package:briefing/model/article.dart';
  7 | import 'package:flutter/material.dart';
  8 | 
  9 | class BookmarkArticleList extends StatefulWidget {
 10 |   const BookmarkArticleList({Key key}) : super(key: key);
 11 | 
 12 |   @override
 13 |   _BookmarkArticleListState createState() => _BookmarkArticleListState();
 14 | }
 15 | 
 16 | class _BookmarkArticleListState extends State {
 17 |   final GlobalKey _refreshIndicatorKey =
 18 |       new GlobalKey();
 19 |   BookmarkArticleListBloc _bloc;
 20 | 
 21 |   @override
 22 |   void initState() {
 23 |     super.initState();
 24 |     _bloc = BookmarkArticleListBloc();
 25 |     WidgetsBinding.instance
 26 |         .addPostFrameCallback((_) => _refreshIndicatorKey.currentState.show());
 27 |   }
 28 | 
 29 |   Future _onRefresh() async {
 30 |     await Future.delayed(Duration(seconds: 1));
 31 |   }
 32 | 
 33 |   @override
 34 |   void dispose() {
 35 |     _bloc.dispose();
 36 |     super.dispose();
 37 |   }
 38 | 
 39 |   @override
 40 |   Widget build(BuildContext context) {
 41 |     return SliverList(
 42 |       delegate: SliverChildListDelegate([
 43 |         StreamBuilder>(
 44 |             stream: _bloc.articleListObservable,
 45 |             initialData: List(),
 46 |             builder: (context, snapshot) {
 47 |               return RefreshIndicator(
 48 |                 key: _refreshIndicatorKey,
 49 |                 displacement: 5.0,
 50 |                 backgroundColor: Colors.white,
 51 |                 onRefresh: _onRefresh,
 52 |                 child: snapshot.hasData && snapshot.data.length > 0
 53 |                     ? ListView.separated(
 54 |                         padding: EdgeInsets.symmetric(
 55 |                             horizontal: 12.0, vertical: 18.0),
 56 |                         physics: ScrollPhysics(),
 57 |                         shrinkWrap: true,
 58 |                         itemCount: snapshot.data.length,
 59 |                         separatorBuilder: (BuildContext context, int index) {
 60 |                           return Divider();
 61 |                         },
 62 |                         itemBuilder: (BuildContext context, int index) {
 63 |                           return BriefingCard(
 64 |                               article: snapshot.data.elementAt(index));
 65 |                         })
 66 |                     : snapshot.hasError
 67 |                         ? Center(
 68 |                             child: ErrorWidget(message: ['${snapshot.error}']))
 69 |                         : Center(
 70 |                             child: Container(
 71 |                                 margin: EdgeInsets.all(16.0),
 72 |                                 width: 30,
 73 |                                 height: 30,
 74 |                                 child: CircularProgressIndicator()),
 75 |                           ),
 76 |               );
 77 |             }),
 78 |       ]),
 79 |     );
 80 |   }
 81 | }
 82 | 
 83 | class ErrorWidget extends StatelessWidget {
 84 |   final List message;
 85 | 
 86 |   const ErrorWidget({Key key, this.message}) : super(key: key);
 87 | 
 88 |   @override
 89 |   Widget build(BuildContext context) {
 90 |     return Container(
 91 |       margin: EdgeInsets.only(top: 92.0),
 92 |       child: Column(
 93 |         mainAxisAlignment: MainAxisAlignment.center,
 94 |         children: [
 95 |           Icon(Icons.cloud_off, size: 55.0),
 96 |           Text('Woops...',
 97 |               style: Theme.of(context).textTheme.subhead,
 98 |               textAlign: TextAlign.center),
 99 |           Text(message.join('\n'),
100 |               textAlign: TextAlign.center,
101 |               style: Theme.of(context)
102 |                   .textTheme
103 |                   .subhead
104 |                   .copyWith(fontWeight: FontWeight.w600)),
105 |         ],
106 |       ),
107 |     );
108 |   }
109 | }
110 | 
111 | class LoadingWidget extends StatelessWidget {
112 |   final Stream _isLoading;
113 | 
114 |   const LoadingWidget(this._isLoading);
115 | 
116 |   @override
117 |   Widget build(BuildContext context) {
118 |     return StreamBuilder(
119 |       stream: _isLoading,
120 |       initialData: true,
121 |       builder: (context, snapshot) {
122 |         debugPrint("_bloc.isLoading: ${snapshot.data}");
123 |         return snapshot.data
124 |             ? Center(
125 |                 child: Container(
126 |                   margin: EdgeInsets.only(top: 92.0),
127 |                   width: 30,
128 |                   height: 30,
129 |                   child: CircularProgressIndicator(
130 |                     backgroundColor: Colors.white,
131 |                     valueColor: AlwaysStoppedAnimation(Colors.blue),
132 |                   ),
133 |                 ),
134 |               )
135 |             : Container();
136 |       },
137 |     );
138 |   }
139 | }
140 | 
--------------------------------------------------------------------------------
/lib/briefing_card.dart:
--------------------------------------------------------------------------------
 1 | import 'package:briefing/model/article.dart';
 2 | import 'package:flutter/material.dart';
 3 | import 'package:briefing/widget/article_bottom_section.dart';
 4 | import 'package:briefing/widget/article_title_section.dart';
 5 | import 'package:flutter_custom_tabs/flutter_custom_tabs.dart';
 6 | 
 7 | class BriefingCard extends StatefulWidget {
 8 |   final Article article;
 9 | 
10 |   const BriefingCard({Key key, this.article}) : super(key: key);
11 | 
12 |   @override
13 |   BriefingCardState createState() {
14 |     return BriefingCardState();
15 |   }
16 | }
17 | 
18 | class BriefingCardState extends State {
19 |   @override
20 |   Widget build(BuildContext context) {
21 |     return Container(
22 |       margin: EdgeInsets.only(bottom: 4.0),
23 |       padding: EdgeInsets.fromLTRB(4.0, 0.0, 4.0, 4.0),
24 |       child: Column(
25 |         children: [
26 |           InkWell(
27 |             borderRadius: BorderRadius.circular(20.0),
28 |             child: ArticleTitleSection(article: widget.article),
29 |             onTap: () {
30 |               _launchURL(context, widget.article.url);
31 |             },
32 |           ),
33 |           ArticleBottomSection(article: widget.article),
34 |         ],
35 |       ),
36 |     );
37 |   }
38 | 
39 |   void _launchURL(BuildContext context, String link) async {
40 |     try {
41 |       await launch(
42 |         link,
43 |         option: new CustomTabsOption(
44 |           toolbarColor: Theme.of(context).primaryColor,
45 |           enableDefaultShare: true,
46 |           enableUrlBarHiding: true,
47 |           showPageTitle: true,
48 |           animation: CustomTabsAnimation.slideIn(),
49 |           extraCustomTabs: [
50 |             // ref. https://play.google.com/store/apps/details?id=org.mozilla.firefox
51 |             'org.mozilla.firefox',
52 |           ],
53 |         ),
54 |       );
55 |     } catch (e) {
56 |       // An exception is thrown if browser app is not installed on Android device.
57 |       debugPrint(e.toString());
58 |     }
59 |   }
60 | }
61 | 
--------------------------------------------------------------------------------
/lib/briefing_sliver_list.dart:
--------------------------------------------------------------------------------
  1 | import 'dart:async';
  2 | 
  3 | import 'package:briefing/bloc/bloc_article.dart';
  4 | import 'package:briefing/briefing_card.dart';
  5 | import 'package:briefing/model/article.dart';
  6 | import 'package:flutter/material.dart';
  7 | 
  8 | class BriefingSliverList extends StatefulWidget {
  9 |   final Menu menu;
 10 | 
 11 |   const BriefingSliverList({Key key, this.menu}) : super(key: key);
 12 | 
 13 |   @override
 14 |   _BriefingSliverListState createState() => _BriefingSliverListState();
 15 | }
 16 | 
 17 | class _BriefingSliverListState extends State {
 18 |   final GlobalKey _refreshIndicatorKey =
 19 |       new GlobalKey();
 20 |   ArticleListBloc _bloc;
 21 | 
 22 |   @override
 23 |   void initState() {
 24 |     super.initState();
 25 |     _bloc = ArticleListBloc();
 26 |     WidgetsBinding.instance
 27 |         .addPostFrameCallback((_) => _refreshIndicatorKey.currentState.show());
 28 |   }
 29 | 
 30 |   Future _onRefresh() async {
 31 | //    await _bloc.refresh()
 32 |     await Future.delayed(Duration(seconds: 3));
 33 |   }
 34 | 
 35 |   @override
 36 |   void dispose() {
 37 |     _bloc.dispose();
 38 |     super.dispose();
 39 |   }
 40 | 
 41 |   @override
 42 |   Widget build(BuildContext context) {
 43 |     _bloc.menuSubject.sink.add(widget.menu);
 44 |     if (widget.menu != null && widget.menu == Menu.local) {
 45 |       _bloc.categorySink.add('local');
 46 |     } else {
 47 |       _bloc.categorySink.add('All');
 48 |     }
 49 | 
 50 |     return SliverList(
 51 |       delegate: SliverChildListDelegate([
 52 |         if (widget.menu == Menu.headlines)
 53 |           StreamBuilder(
 54 |               stream: _bloc.categoryObservable,
 55 |               builder: (context, snapshot) {
 56 |                 return Card(
 57 |                   margin: EdgeInsets.zero,
 58 |                   shape: RoundedRectangleBorder(
 59 |                       borderRadius: BorderRadius.circular(0.0)),
 60 |                   elevation: 1.0,
 61 |                   child: Container(
 62 |                     margin: EdgeInsets.symmetric(vertical: 12.0),
 63 |                     height: 30.0,
 64 |                     width: MediaQuery.of(context).size.width,
 65 |                     child: ListView(
 66 |                       physics: ScrollPhysics(),
 67 |                       shrinkWrap: true,
 68 |                       scrollDirection: Axis.horizontal,
 69 |                       children: categories.keys
 70 |                           .where((category) => category != 'local')
 71 |                           .map(
 72 |                             (category) => Padding(
 73 |                               padding:
 74 |                                   const EdgeInsets.symmetric(horizontal: 4.0),
 75 |                               child: ChoiceChip(
 76 |                                   selectedColor: Theme.of(context).accentColor,
 77 |                                   label: Text(category),
 78 |                                   selected: snapshot.data == category,
 79 |                                   onSelected: (val) {
 80 |                                     _refreshIndicatorKey.currentState.show();
 81 |                                     _bloc.categorySink.add(category);
 82 |                                   }),
 83 |                             ),
 84 |                           )
 85 |                           .toList(),
 86 |                     ),
 87 |                   ),
 88 |                 );
 89 |               }),
 90 |         StreamBuilder>(
 91 |             stream: _bloc.articleListObservable,
 92 |             initialData: List(),
 93 |             builder: (context, snapshot) {
 94 |               debugPrint("!!!snapshot state: ${snapshot.connectionState}!!!");
 95 |               return RefreshIndicator(
 96 |                 key: _refreshIndicatorKey,
 97 |                 displacement: 5.0,
 98 |                 backgroundColor: Colors.white,
 99 |                 onRefresh: _onRefresh,
100 |                 child: snapshot.hasData && snapshot.data.length > 0
101 |                     ? ListView.separated(
102 |                         padding: EdgeInsets.symmetric(
103 |                             horizontal: 12.0, vertical: 18.0),
104 |                         physics: ScrollPhysics(),
105 |                         shrinkWrap: true,
106 |                         itemCount: snapshot.data.length,
107 |                         separatorBuilder: (BuildContext context, int index) {
108 |                           return Divider();
109 |                         },
110 |                         itemBuilder: (BuildContext context, int index) {
111 |                           return BriefingCard(
112 |                               article: snapshot.data.elementAt(index));
113 |                         },
114 |                       )
115 |                     : snapshot.hasError
116 |                         ? Center(
117 |                             child: GestureDetector(
118 |                               onTap: _onRefresh,
119 |                               child:
120 |                                   ErrorWidget(message: ['${snapshot.error}']),
121 |                             ),
122 |                           )
123 |                         : Center(
124 |                             child: Container(
125 |                               margin: EdgeInsets.all(16.0),
126 |                               width: 30,
127 |                               height: 30,
128 |                               child: CircularProgressIndicator(),
129 |                             ),
130 |                           ),
131 |               );
132 |             }),
133 |       ]),
134 |     );
135 |   }
136 | }
137 | 
138 | class ErrorWidget extends StatelessWidget {
139 |   final List message;
140 | 
141 |   const ErrorWidget({Key key, this.message}) : super(key: key);
142 | 
143 |   @override
144 |   Widget build(BuildContext context) {
145 |     return Container(
146 |       margin: EdgeInsets.only(top: 92.0),
147 |       child: Column(
148 |         mainAxisAlignment: MainAxisAlignment.center,
149 |         children: [
150 |           Icon(Icons.cloud_off, size: 55.0),
151 |           Text('Woops...',
152 |               style: Theme.of(context).textTheme.subhead,
153 |               textAlign: TextAlign.center),
154 |           Text(
155 |             message.join('\n'),
156 |             textAlign: TextAlign.center,
157 |             style: Theme.of(context)
158 |                 .textTheme
159 |                 .subhead
160 |                 .copyWith(fontWeight: FontWeight.w600),
161 |           ),
162 |         ],
163 |       ),
164 |     );
165 |   }
166 | }
167 | 
168 | class LoadingWidget extends StatelessWidget {
169 |   final Stream _isLoading;
170 | 
171 |   const LoadingWidget(this._isLoading);
172 | 
173 |   @override
174 |   Widget build(BuildContext context) {
175 |     return StreamBuilder(
176 |       stream: _isLoading,
177 |       initialData: true,
178 |       builder: (context, snapshot) {
179 |         debugPrint("_bloc.isLoading: ${snapshot.data}");
180 |         return snapshot.data
181 |             ? Center(
182 |                 child: Container(
183 |                   margin: EdgeInsets.only(top: 92.0),
184 |                   width: 30,
185 |                   height: 30,
186 |                   child: CircularProgressIndicator(
187 |                     backgroundColor: Colors.white,
188 |                     valueColor: AlwaysStoppedAnimation(Colors.blue),
189 |                   ),
190 |                 ),
191 |               )
192 |             : Container();
193 |       },
194 |     );
195 |   }
196 | }
197 | 
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
  1 | import 'package:briefing/bookmarked_article_list.dart';
  2 | import 'package:briefing/briefing_sliver_list.dart';
  3 | import 'package:briefing/model/article.dart';
  4 | import 'package:briefing/theme/theme.dart';
  5 | import 'package:briefing/widget/main_sliverappbar.dart';
  6 | import 'package:flutter/material.dart';
  7 | import 'package:flutter/services.dart';
  8 | 
  9 | void main() {
 10 |   runApp(MyApp());
 11 | }
 12 | 
 13 | class MyApp extends StatelessWidget {
 14 |   @override
 15 |   Widget build(BuildContext context) {
 16 |     return MaterialApp(
 17 |       debugShowCheckedModeBanner: false,
 18 |       title: 'Briefing',
 19 |       theme: buildAppTheme(),
 20 |       home: MyHomePage(title: 'Briefing'),
 21 |     );
 22 |   }
 23 | }
 24 | 
 25 | class MyHomePage extends StatefulWidget {
 26 |   MyHomePage({Key key, this.title}) : super(key: key);
 27 |   final String title;
 28 | 
 29 |   @override
 30 |   _MyHomePageState createState() => _MyHomePageState();
 31 | }
 32 | 
 33 | class _MyHomePageState extends State {
 34 |   int _selectedIndex = 0;
 35 |   final menus = [Menu.local, Menu.headlines, Menu.favorites, Menu.agencies];
 36 | 
 37 |   @override
 38 |   void initState() {
 39 |     super.initState();
 40 |   }
 41 | 
 42 |   @override
 43 |   void dispose() {
 44 |     super.dispose();
 45 |   }
 46 | 
 47 |   final GlobalKey _scaffoldKey = GlobalKey();
 48 | 
 49 |   @override
 50 |   Widget build(BuildContext context) {
 51 |     Widget getScreen() {
 52 |       if (menus[_selectedIndex] == Menu.favorites) {
 53 |         return BookmarkArticleList();
 54 |       }
 55 |       if (menus[_selectedIndex] == Menu.local ||
 56 |           menus[_selectedIndex] == Menu.headlines) {
 57 |         return BriefingSliverList(menu: menus[_selectedIndex]);
 58 |       }
 59 |       return SliverList(
 60 |           delegate: SliverChildListDelegate([
 61 |         Padding(
 62 |           padding: const EdgeInsets.symmetric(vertical: 64.0),
 63 |           child: Center(
 64 |             child: Text('Agencies(sources) comming soon...',
 65 |                 style: TextStyle(fontSize: 22)),
 66 |           ),
 67 |         )
 68 |       ]));
 69 |     }
 70 | 
 71 |     return AnnotatedRegion(
 72 |       value: SystemUiOverlayStyle(
 73 |         statusBarIconBrightness: Brightness.dark,
 74 |         statusBarBrightness: Brightness.light,
 75 |         statusBarColor: Theme.of(context).primaryColor,
 76 |         systemNavigationBarColor: Colors.white,
 77 |         systemNavigationBarIconBrightness: Brightness.dark,
 78 |       ),
 79 |       child: SafeArea(
 80 |         child: Scaffold(
 81 |           key: _scaffoldKey,
 82 |           body: CustomScrollView(
 83 |             slivers: [
 84 |               MainSliverAppBar(title: 'Briefing'),
 85 |               getScreen(),
 86 |             ],
 87 |           ),
 88 |           bottomNavigationBar: BottomAppBar(
 89 |             color: Theme.of(context).primaryColor,
 90 |             child: Container(
 91 |               decoration: BoxDecoration(boxShadow: [
 92 |                 BoxShadow(
 93 |                     color: Colors.cyan[100],
 94 |                     offset: Offset(-2.0, 2.0),
 95 |                     blurRadius: 2.0,
 96 |                     spreadRadius: 2.0)
 97 |               ]),
 98 |               height: 72.0,
 99 |               child: BottomNavigationBar(
100 |                 selectedItemColor: Theme.of(context).accentColor,
101 |                 currentIndex: _selectedIndex,
102 |                 onTap: (val) => _onItemTapped(val),
103 |                 type: BottomNavigationBarType.fixed,
104 |                 backgroundColor: Theme.of(context).primaryColor,
105 |                 selectedFontSize: 18.0,
106 |                 unselectedFontSize: 17.0,
107 |                 items: [
108 |                   BottomNavigationBarItem(
109 |                       icon: Icon(Icons.local_library), title: Text('Local')),
110 |                   BottomNavigationBarItem(
111 |                       icon: Icon(Icons.language), title: Text('Headlines')),
112 |                   BottomNavigationBarItem(
113 |                       icon: Icon(Icons.bookmark_border),
114 |                       title: Text('Favorites')),
115 |                   BottomNavigationBarItem(
116 |                       icon: Icon(Icons.filter_none), title: Text('Agencies'))
117 |                 ],
118 |                 elevation: 5.0,
119 |               ),
120 |             ),
121 |           ),
122 |         ),
123 |       ),
124 |     );
125 |   }
126 | 
127 |   void _onItemTapped(int index) {
128 |     setState(() {
129 |       _selectedIndex = index;
130 |     });
131 | //    Navigator.pop(context);
132 |   }
133 | }
134 | 
--------------------------------------------------------------------------------
/lib/model/article.dart:
--------------------------------------------------------------------------------
  1 | import 'package:intl/intl.dart';
  2 | 
  3 | class Article {
  4 |   num id;
  5 |   final String title;
  6 |   final String description;
  7 |   final String url;
  8 |   final String publishedAt;
  9 |   final String author;
 10 |   final String source;
 11 |   final String content;
 12 |   final String imageUrl;
 13 |   final String category;
 14 |   bool bookmarked = false;
 15 | 
 16 |   Article(
 17 |       {this.id,
 18 |       this.title,
 19 |       this.description,
 20 |       this.url,
 21 |       this.publishedAt,
 22 |       this.author,
 23 |       this.source,
 24 |       this.content,
 25 |       this.imageUrl,
 26 |       this.category,
 27 |       this.bookmarked});
 28 | 
 29 |   factory Article.fromMap(Map data, {network = false}) {
 30 |     return Article(
 31 |       id: data['id'],
 32 |       title: data['title'],
 33 |       description: data['description'],
 34 |       url: data['url'],
 35 |       publishedAt: data['publishedAt'],
 36 |       author: data['author'],
 37 |       source: network ? data['source']['name'] : data['source'],
 38 |       content: data['content'],
 39 |       imageUrl: data['urlToImage'],
 40 |       category: data['category'] ?? '',
 41 |       bookmarked: (data['bookmarked'] ?? false) == 1,
 42 |     );
 43 |   }
 44 | 
 45 |   Map toMap({category}) {
 46 |     return {
 47 |       'title': title,
 48 |       'description': description,
 49 |       'url': url,
 50 |       'publishedAt': publishedAt,
 51 |       'author': author,
 52 |       'source': source,
 53 |       'content': content,
 54 |       'urlToImage': imageUrl,
 55 |       'category': category ?? this.category,
 56 |       'bookmarked': bookmarked,
 57 |     };
 58 |   }
 59 | 
 60 |   String get timeAgo {
 61 |     var formatter = DateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
 62 | //    var formatter = DateFormat("EEE, d MMM yyyy HH:mm:ss zzz");
 63 | 
 64 |     DateTime parsedDate;
 65 | 
 66 |     try {
 67 |       parsedDate = formatter.parse(publishedAt);
 68 |     } catch (error) {
 69 |       try {
 70 |         parsedDate =
 71 |             DateFormat("EEE, d MMM yyyy HH:mm:ss zzz").parse(publishedAt);
 72 |       } catch (error) {
 73 |         print('${error.toString()}');
 74 |       }
 75 |     }
 76 |     if (parsedDate != null) {
 77 |       Duration duration = DateTime.now().difference(parsedDate);
 78 | 
 79 |       if (duration.inDays > 7 || duration.isNegative) {
 80 |         return DateFormat.MMMMd().format(parsedDate);
 81 |       } else if (duration.inDays >= 1 && duration.inDays <= 7) {
 82 |         return duration.inDays == 1
 83 |             ? "1 day ago"
 84 |             : "${duration.inDays} days ago";
 85 |       } else if (duration.inHours >= 1) {
 86 |         return duration.inHours == 1
 87 |             ? "1 hour ago"
 88 |             : "${duration.inHours} hours ago";
 89 |       } else {
 90 |         return duration.inMinutes == 1
 91 |             ? "1 minute ago"
 92 |             : "${duration.inMinutes} minutes ago";
 93 |       }
 94 |     } else {
 95 |       return publishedAt;
 96 |     }
 97 |   }
 98 | 
 99 |   bool isNew() {
100 |     var formatter = DateFormat("EEE, d MMM yyyy HH:mm:ss zzz");
101 | 
102 |     DateTime parsedDate = formatter.parse(publishedAt);
103 |     Duration duration = DateTime.now().difference(parsedDate);
104 |     if (duration.inHours < 24) {
105 |       return true;
106 |     }
107 |     return false;
108 |   }
109 | 
110 |   bool get isValid => title != null && title.length > 3 && url != null;
111 | }
112 | 
113 | const categories = {
114 |   'All': '',
115 |   'Technology': 'technology',
116 |   'Business': 'business',
117 |   'Entertainment': 'entertainment',
118 |   'Health': 'health',
119 |   'Science': 'science',
120 |   'Sports': 'sports',
121 |   'General': 'general',
122 |   'local': 'local'
123 | };
124 | 
125 | enum Menu { local, headlines, favorites, agencies }
126 | 
--------------------------------------------------------------------------------
/lib/repository/repository.dart:
--------------------------------------------------------------------------------
 1 | import 'package:briefing/model/article.dart';
 2 | import 'package:briefing/service/api_service.dart';
 3 | import 'package:briefing/service/database.dart';
 4 | 
 5 | class RepositoryCommon {
 6 |   static close() {
 7 |     DatabaseService.db.close();
 8 |   }
 9 | 
10 |   static Future getValue(String id) async {
11 |     return await DatabaseService.db.getValue(id);
12 |   }
13 | 
14 |   static Future insertMetadata(String id) async {
15 |     return await DatabaseService.db.insertMetadata(id);
16 |   }
17 | 
18 |   static Future deleteMetadata(String id) async {
19 |     return await DatabaseService.db.deleteMetadata(id);
20 |   }
21 | }
22 | 
23 | class RepositoryArticle {
24 |   //DatabaseService
25 |   static Future insertArticle(Article article) async {
26 |     return await DatabaseService.db.insertArticle(article);
27 |   }
28 | 
29 |   static Future insertArticleList(List articles,
30 |       {category}) async {
31 |     return await DatabaseService.db
32 |         .insertArticleList(articles, category: category);
33 |   }
34 | 
35 |   static Future> getBookmarkedArticles() async {
36 |     return await DatabaseService.db.getBookmarkedArticles();
37 |   }
38 | 
39 |   static Future> _getArticleFromDatabase() async {
40 |     return await DatabaseService.db.getAllArticle();
41 |   }
42 | 
43 |   static Future> getAllArticleByCategory(String category) async {
44 |     if (category != null && category.isNotEmpty) {
45 |       return await DatabaseService.db.getAllArticleByCategory(category);
46 |     }
47 |     return await _getArticleFromDatabase();
48 |   }
49 | 
50 |   //ApiService
51 |   static Future> getArticleListFromNetwork(
52 |       String country, String category) async {
53 |     if (category != null && category == 'local') {
54 |       return getLocalNewsFromNetwork();
55 |     }
56 |     return ApiService.getArticlesFromNetwork(country, category);
57 |   }
58 | 
59 |   static Future> getLocalNewsFromNetwork() async {
60 |     return ApiService.getLocalNewsFromNetwork();
61 |   }
62 | }
63 | 
--------------------------------------------------------------------------------
/lib/service/api_service.dart:
--------------------------------------------------------------------------------
  1 | import 'dart:convert';
  2 | 
  3 | import 'package:flutter/foundation.dart';
  4 | import 'package:http/http.dart' as http;
  5 | import 'package:xml/xml.dart' as xml;
  6 | import 'package:briefing/model/article.dart';
  7 | import 'package:xml/xml.dart';
  8 | 
  9 | const base_url = 'https://newsapi.org/v2';
 10 | const api_key = '11cd66d3a6994c108e7fb7d92cee5e12';
 11 | const local_news_url = 'https://news.google.com/rss';
 12 | 
 13 | String getUrl(String country, String category) {
 14 |   var url = '$base_url/top-headlines?page=1';
 15 |   if (country != null && country.isNotEmpty) {
 16 |     url += '&country=$country';
 17 |   }
 18 |   if (category != null && category.isNotEmpty) {
 19 |     url += '&category=$category';
 20 |   }
 21 |   return url += '&apiKey=$api_key';
 22 | }
 23 | 
 24 | class ApiService {
 25 |   static Future> getArticlesFromNetwork(country, category) async {
 26 |     var articles = [];
 27 |     try {
 28 |       final response = await http.get(getUrl(country, category));
 29 |       if (response.statusCode == 200) {
 30 |         articles = await compute(parseArticles, response.body);
 31 |       }
 32 |     } catch (e) {
 33 |       print('=== API::getArticlesFromNetwork Error ${e.toString()}');
 34 |     }
 35 |     return articles;
 36 |   }
 37 | 
 38 |   static Future> getLocalNewsFromNetwork() async {
 39 |     var articles = [];
 40 |     try {
 41 |       final response = await http.get(local_news_url);
 42 |       if (response.statusCode == 200) {
 43 |         articles = await compute(parseArticlesXml, response.body);
 44 |       }
 45 |     } catch (error) {
 46 |       print('=== API::LocalNewsFromNetwork::Error ${error.toString()}');
 47 |     }
 48 |     return articles;
 49 |   }
 50 | }
 51 | 
 52 | List parseArticles(String responseBody) {
 53 |   var articles = [];
 54 |   final parsed = json.decode(responseBody);
 55 |   if (parsed['totalResults'] > 0) {
 56 |     articles = List.from(parsed['articles']
 57 |         .map((article) => Article.fromMap(article, network: true)));
 58 |   }
 59 |   return articles;
 60 | }
 61 | 
 62 | List parseArticlesXml(String responseBody) {
 63 |   var document = xml.parse(responseBody);
 64 | 
 65 |   var channelElement = document.findAllElements("channel")?.first;
 66 |   var source = findElementOrNull(channelElement, 'title')?.text;
 67 | 
 68 |   return channelElement.findAllElements('item').map((element) {
 69 |     var title = findElementOrNull(element, 'title')?.text;
 70 |     var description = findElementOrNull(element, "description")?.text;
 71 |     var source2 = element.findElements("source").first.getAttribute('url');
 72 |     var link = findElementOrNull(element, "link")?.text;
 73 | //    var category =
 74 | //        element.findElements("category").first.getAttribute('domain');
 75 |     var pubDate = findElementOrNull(element, "pubDate")?.text;
 76 |     var author = findElementOrNull(element, "author")?.text;
 77 |     var image =
 78 |         findElementOrNull(element, "enclosure")?.getAttribute("url") ?? null;
 79 | 
 80 |     return Article(
 81 |         title: title,
 82 |         category: 'local',
 83 |         author: author,
 84 |         content: description,
 85 |         imageUrl: image,
 86 |         publishedAt: pubDate,
 87 |         url: link,
 88 |         source: source2?.replaceAll('https://www.', '') ?? '',
 89 |         description: description);
 90 |   }).toList();
 91 | }
 92 | 
 93 | XmlElement findElementOrNull(XmlElement element, String name) {
 94 |   try {
 95 |     return element.findAllElements(name).first;
 96 |   } on StateError {
 97 |     return null;
 98 |   }
 99 | }
100 | 
--------------------------------------------------------------------------------
/lib/service/database.dart:
--------------------------------------------------------------------------------
  1 | import 'dart:async';
  2 | import 'dart:io';
  3 | 
  4 | import 'package:briefing/model/article.dart';
  5 | import 'package:flutter/foundation.dart';
  6 | import 'package:path/path.dart';
  7 | import 'package:path_provider/path_provider.dart';
  8 | import 'package:sqflite/sqflite.dart';
  9 | 
 10 | class DatabaseService {
 11 |   DatabaseService._();
 12 | 
 13 |   static final DatabaseService db = DatabaseService._();
 14 | 
 15 |   Database _database;
 16 | 
 17 |   init() async {
 18 |     await database;
 19 |   }
 20 | 
 21 |   Future get database async {
 22 |     if (_database != null) return _database;
 23 |     _database = await initDB();
 24 |     return _database;
 25 |   }
 26 | 
 27 |   Future initDB() async {
 28 |     print('DBProvider.initDB() start');
 29 |     Directory documentsDirectory = await getApplicationDocumentsDirectory();
 30 |     String path = join(documentsDirectory.path, "briefing.db");
 31 |     var db = await openDatabase(path, version: 1, onCreate: populateDb);
 32 | //    print('DBProvider.initDB() end');
 33 |     return db;
 34 |   }
 35 | 
 36 |   populateDb(Database db, int version) async {
 37 |     await Future(() async {
 38 | //      print('  DBProvider.await populateDb start');
 39 | 
 40 |       var articleTable = "CREATE TABLE articles"
 41 |           "(id INTEGER PRIMARY KEY autoincrement, "
 42 |           "title TEXT,"
 43 |           "description TEXT,"
 44 |           "url TEXT,"
 45 |           "publishedAt TEXT,"
 46 |           "author TEXT,"
 47 |           "source TEXT,"
 48 |           "content TEXT,"
 49 |           "urlToImage TEXT,"
 50 |           "category TEXT,"
 51 |           "bookmarked BIT,"
 52 |           "UNIQUE(url));";
 53 | 
 54 |       var metadata = "CREATE TABLE metadata"
 55 |           "(id TEXT PRIMARY KEY,"
 56 |           "value INTEGER);";
 57 | 
 58 |       await db.transaction((txn) async {
 59 |         await txn.execute('$articleTable');
 60 |         await txn.execute('$metadata');
 61 | 
 62 | //        print('DBProvider.await populateDb end (txn)');
 63 |       });
 64 |     });
 65 |   }
 66 | 
 67 |   Future insertArticle(Article article) async {
 68 |     final db = await database;
 69 |     var res;
 70 |     res = await db.insert('articles', article.toMap(),
 71 |         conflictAlgorithm: ConflictAlgorithm.replace);
 72 | 
 73 |     return res;
 74 |   }
 75 | 
 76 |   Future> insertArticleList(List articles,
 77 |       {category}) async {
 78 |     final db = await database;
 79 |     var res;
 80 | 
 81 |     await db.transaction((txn) async {
 82 |       Batch batch = txn.batch();
 83 |       articles.forEach((article) async {
 84 |         batch.insert(
 85 |           'articles',
 86 |           article.toMap(category: category),
 87 |           conflictAlgorithm: ConflictAlgorithm.replace,
 88 |         );
 89 |       });
 90 |       res = await batch.commit(continueOnError: true);
 91 |     });
 92 |     print('article $res inserted');
 93 |     return res;
 94 |   }
 95 | 
 96 |   Future> getAllArticle() async {
 97 |     final db = await database;
 98 |     List