├── .gitignore
├── .metadata
├── LICENSE
├── README.md
├── analysis_options.yaml
├── android
├── app
│ ├── build.gradle
│ └── src
│ │ ├── debug
│ │ └── AndroidManifest.xml
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── lcc
│ │ │ │ └── notebulk
│ │ │ │ └── MainActivity.java
│ │ └── res
│ │ │ ├── drawable-hdpi
│ │ │ ├── ic_launcher_background.png
│ │ │ └── ic_launcher_foreground.png
│ │ │ ├── drawable-mdpi
│ │ │ ├── ic_launcher_background.png
│ │ │ └── ic_launcher_foreground.png
│ │ │ ├── drawable-xhdpi
│ │ │ ├── ic_launcher_background.png
│ │ │ └── ic_launcher_foreground.png
│ │ │ ├── drawable-xxhdpi
│ │ │ ├── ic_launcher_background.png
│ │ │ └── ic_launcher_foreground.png
│ │ │ ├── drawable-xxxhdpi
│ │ │ ├── ic_launcher_background.png
│ │ │ └── ic_launcher_foreground.png
│ │ │ ├── drawable
│ │ │ └── launch_background.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ ├── ic_launcher.xml
│ │ │ └── launcher_icon.xml
│ │ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── launcher_icon.png
│ │ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── launcher_icon.png
│ │ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── launcher_icon.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── launcher_icon.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── launcher_icon.png
│ │ │ └── values
│ │ │ ├── colors.xml
│ │ │ └── styles.xml
│ │ └── profile
│ │ └── AndroidManifest.xml
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
└── settings.gradle
├── assets
├── bg-adaptive.png
├── icon-adaptive.png
├── icon-android.png
└── screenshots
│ ├── actions.png
│ ├── actions_2.png
│ ├── actions_3.png
│ ├── actions_4.png
│ ├── archive.png
│ ├── dark_mode.png
│ ├── notes.png
│ ├── reminders.png
│ └── search.png
├── fonts
├── AppIcons.ttf
├── OFL.txt
├── Palanquin-Bold.ttf
├── Palanquin-Light.ttf
├── Palanquin-Medium.ttf
├── Palanquin-Regular.ttf
├── Palanquin-Thin.ttf
├── PalanquinDark-Bold.ttf
└── licenses.txt
├── 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
├── ecs
│ ├── components.dart
│ ├── ecs.dart
│ ├── matchers.dart
│ ├── systems.dart
│ └── util.dart
├── features
│ ├── features.dart
│ ├── noteFormFeature.dart
│ └── reminderFormFeature.dart
├── icons.dart
├── main.dart
├── mainApp.dart
├── pages
│ ├── archivePage.dart
│ ├── noteListPage.dart
│ ├── pages.dart
│ ├── reminderListPage.dart
│ ├── searchPage.dart
│ ├── settingsPage.dart
│ └── splashScreenPage.dart
├── theme.dart
├── util.dart
└── widgets
│ ├── cards.dart
│ └── util.dart
├── linux
├── Makefile
└── notebulk.cc
├── pubspec.lock
├── pubspec.yaml
└── test
└── entitas_basic_test.dart
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # The .vscode folder contains launch configuration and tasks you configure in
19 | # VS Code which you may wish to be included in version control, so this line
20 | # is commented out by default.
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 |
32 | # Android related
33 | **/android/**/gradle-wrapper.jar
34 | **/android/.gradle
35 | **/android/captures/
36 | **/android/gradlew
37 | **/android/gradlew.bat
38 | **/android/local.properties
39 | **/android/**/GeneratedPluginRegistrant.java
40 |
41 | # iOS/XCode related
42 | **/ios/**/*.mode1v3
43 | **/ios/**/*.mode2v3
44 | **/ios/**/*.moved-aside
45 | **/ios/**/*.pbxuser
46 | **/ios/**/*.perspectivev3
47 | **/ios/**/*sync/
48 | **/ios/**/.sconsign.dblite
49 | **/ios/**/.tags*
50 | **/ios/**/.vagrant/
51 | **/ios/**/DerivedData/
52 | **/ios/**/Icon?
53 | **/ios/**/Pods/
54 | **/ios/**/.symlinks/
55 | **/ios/**/profile
56 | **/ios/**/xcuserdata
57 | **/ios/.generated/
58 | **/ios/Flutter/App.framework
59 | **/ios/Flutter/Flutter.framework
60 | **/ios/Flutter/Generated.xcconfig
61 | **/ios/Flutter/app.flx
62 | **/ios/Flutter/app.zip
63 | **/ios/Flutter/flutter_assets/
64 | **/ios/ServiceDefinitions.json
65 | **/ios/Runner/GeneratedPluginRegistrant.*
66 |
67 | # Exceptions to above rules.
68 | !**/ios/**/default.mode1v3
69 | !**/ios/**/default.mode2v3
70 | !**/ios/**/default.pbxuser
71 | !**/ios/**/default.perspectivev3
72 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
73 |
--------------------------------------------------------------------------------
/.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: 919dcf53f368c0aa5c559728d9d89d241617913c
8 | channel: master
9 |
10 | project_type: app
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 lucasccustodio
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 | # Notebulk
2 |
3 | 
4 |
5 | Open source note taking application made with Flutter using Entitas framework for state management and Hive as database. Features: Context-aware status bar that can apply actions in batch to a group (hence the name ;)), database backup/restoration, search system with support for multiple terms (tags), reminders as Smart list that knows when one is late and custom theme with support for Dark mode.
6 |
7 | # Screenshots
8 |
9 | 
10 |
11 | 
12 |
13 | 
14 |
15 | 
16 |
17 | 
18 |
19 | 
20 |
21 | 
22 |
23 | 
24 |
25 | 
26 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2017 The Fuchsia Authors. All rights reserved.
2 | # Use of this source code is governed by a BSD-style license that can be
3 | # found in the LICENSE file.
4 |
5 | # Root analysis options shared among all Dart code in the respository. Based
6 | # on the Fuchsia standard analysis options, with some changes.
7 | linter:
8 | # Full list available at http://dart-lang.github.io/linter/lints/options/options.html.
9 | rules:
10 | - always_declare_return_types
11 | - always_put_control_body_on_new_line
12 | - always_put_required_named_parameters_first
13 | - always_require_non_null_named_parameters
14 | - annotate_overrides
15 | - avoid_as
16 | - avoid_bool_literals_in_conditional_expressions
17 | - avoid_catches_without_on_clauses
18 | - avoid_catching_errors
19 | - avoid_classes_with_only_static_members
20 | - avoid_empty_else
21 | # Not compatible with VS Code yet.
22 | # - avoid_field_initializers_in_const_classes
23 | - avoid_function_literals_in_foreach_calls
24 | - avoid_init_to_null
25 | - avoid_null_checks_in_equality_operators
26 | - avoid_positional_boolean_parameters
27 | - avoid_private_typedef_functions
28 | # TODO: Change relative imports for package imports
29 | # - avoid_relative_lib_imports
30 | # This puts an unnecessary burden on API clients.
31 | # - avoid_renaming_method_parameters
32 | - avoid_return_types_on_setters
33 | - avoid_returning_null
34 | - avoid_returning_this
35 | - avoid_single_cascade_in_expression_statements
36 | - avoid_setters_without_getters
37 | - avoid_slow_async_io
38 | - avoid_types_as_parameter_names
39 | - avoid_types_on_closure_parameters
40 | - avoid_unused_constructor_parameters
41 | - await_only_futures
42 | - camel_case_types
43 | - cancel_subscriptions
44 | - cascade_invocations
45 | - close_sinks
46 | - comment_references
47 | - constant_identifier_names
48 | - control_flow_in_finally
49 | - directives_ordering
50 | - empty_catches
51 | - empty_constructor_bodies
52 | - empty_statements
53 | - hash_and_equals
54 | - implementation_imports
55 | - invariant_booleans
56 | - iterable_contains_unrelated_type
57 | - join_return_with_assignment
58 | - library_names
59 | - library_prefixes
60 | - list_remove_unrelated_type
61 | - literal_only_boolean_expressions
62 | - no_adjacent_strings_in_list
63 | - no_duplicate_case_values
64 | - non_constant_identifier_names
65 | - omit_local_variable_types
66 | - one_member_abstracts
67 | - only_throw_errors
68 | - overridden_fields
69 | - package_api_docs
70 | - package_names
71 | - package_prefixed_library_names
72 | - parameter_assignments
73 | - prefer_adjacent_string_concatenation
74 | - prefer_asserts_in_initializer_lists
75 | - prefer_collection_literals
76 | - prefer_conditional_assignment
77 | # Disabled until bug is fixed
78 | # https://github.com/dart-lang/linter/issues/995
79 | # - prefer_const_constructors
80 | - prefer_const_constructors_in_immutables
81 | - prefer_const_declarations
82 | - prefer_const_literals_to_create_immutables
83 | - prefer_constructors_over_static_methods
84 | - prefer_contains
85 | - prefer_equal_for_default_values
86 | # Add this when 'short' is better defined.
87 | # - prefer_expression_function_bodies
88 | - prefer_final_fields
89 | - prefer_final_locals
90 | # Seems to have false positive with await for.
91 | # - prefer_foreach
92 | - prefer_function_declarations_over_variables
93 | - prefer_generic_function_type_aliases
94 | - prefer_initializing_formals
95 | - prefer_interpolation_to_compose_strings
96 | - prefer_is_empty
97 | - prefer_is_not_empty
98 | - prefer_iterable_whereType
99 | - prefer_single_quotes
100 | - prefer_typing_uninitialized_variables
101 | #- public_member_api_docs
102 | - recursive_getters
103 | - slash_for_doc_comments
104 | - sort_constructors_first
105 | - sort_unnamed_constructors_first
106 | - test_types_in_equals
107 | - throw_in_finally
108 | - type_annotate_public_apis
109 | - type_init_formals
110 | - unawaited_futures
111 | - unnecessary_brace_in_string_interps
112 | - unnecessary_getters_setters
113 | - unnecessary_lambdas
114 | - unnecessary_null_aware_assignments
115 | - unnecessary_null_in_if_null_operators
116 | - unnecessary_overrides
117 | - unnecessary_parenthesis
118 | - unnecessary_statements
119 | - unnecessary_this
120 | - unrelated_type_equality_checks
121 | - use_rethrow_when_possible
122 | - use_setters_to_change_properties
123 | - use_string_buffers
124 | - use_to_and_as_if_applicable
125 | - valid_regexps
126 | # Not compatible with VS Code yet.
127 | # - void_checks
128 |
--------------------------------------------------------------------------------
/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 | def keystoreProperties = new Properties()
28 | def keystorePropertiesFile = rootProject.file('keystore.properties')
29 | if (keystorePropertiesFile.exists()) {
30 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
31 | }
32 |
33 | android {
34 | compileSdkVersion 29
35 |
36 | lintOptions {
37 | disable 'InvalidPackage'
38 | }
39 |
40 | defaultConfig {
41 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
42 | applicationId "com.lcc.notebulk"
43 | minSdkVersion 21
44 | targetSdkVersion 29
45 | versionCode flutterVersionCode.toInteger()
46 | versionName flutterVersionName
47 | //testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
48 | }
49 |
50 | signingConfigs {
51 | release {
52 | if (keystorePropertiesFile.exists()) {
53 | keyAlias keystoreProperties['keyAlias']
54 | keyPassword keystoreProperties['keyPassword']
55 | storeFile file(keystoreProperties['storeFile'])
56 | storePassword keystoreProperties['storePassword']
57 | }
58 | }
59 | }
60 |
61 | buildTypes {
62 | release {
63 | if (keystorePropertiesFile.exists()) {
64 | signingConfig signingConfigs.release
65 | minifyEnabled true
66 | useProguard true
67 |
68 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
69 | } else {
70 | signingConfig signingConfigs.debug
71 | }
72 | }
73 | }
74 | }
75 |
76 | flutter {
77 | source '../..'
78 | }
79 |
80 | dependencies {
81 | //testImplementation 'junit:junit:4.12'
82 | //androidTestImplementation 'androidx.test:runner:1.1.1'
83 | //androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
84 | }
85 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
9 |
10 |
11 |
14 |
15 |
19 |
26 |
30 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/android/app/src/main/java/com/lcc/notebulk/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.lcc.notebulk;
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-hdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/android/app/src/main/res/drawable-hdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-mdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/android/app/src/main/res/drawable-mdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/android/app/src/main/res/drawable-xhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/android/app/src/main/res/drawable-xxhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/android/app/src/main/res/mipmap-hdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/android/app/src/main/res/mipmap-mdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFC83660
4 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | google()
4 | jcenter()
5 | }
6 |
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:3.5.0'
9 | }
10 | }
11 |
12 | allprojects {
13 | repositories {
14 | google()
15 | jcenter()
16 | }
17 | }
18 |
19 | rootProject.buildDir = '../build'
20 | subprojects {
21 | project.buildDir = "${rootProject.buildDir}/${project.name}"
22 | }
23 | subprojects {
24 | project.evaluationDependsOn(':app')
25 | }
26 |
27 | task clean(type: Delete) {
28 | delete rootProject.buildDir
29 | }
30 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | org.gradle.daemon=false
3 |
4 | android.useAndroidX=true
5 | android.enableJetifier=true
6 | android.enableR8=true
7 |
--------------------------------------------------------------------------------
/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-5.4.1-all.zip
7 |
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/bg-adaptive.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/assets/bg-adaptive.png
--------------------------------------------------------------------------------
/assets/icon-adaptive.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/assets/icon-adaptive.png
--------------------------------------------------------------------------------
/assets/icon-android.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/assets/icon-android.png
--------------------------------------------------------------------------------
/assets/screenshots/actions.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/assets/screenshots/actions.png
--------------------------------------------------------------------------------
/assets/screenshots/actions_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/assets/screenshots/actions_2.png
--------------------------------------------------------------------------------
/assets/screenshots/actions_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/assets/screenshots/actions_3.png
--------------------------------------------------------------------------------
/assets/screenshots/actions_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/assets/screenshots/actions_4.png
--------------------------------------------------------------------------------
/assets/screenshots/archive.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/assets/screenshots/archive.png
--------------------------------------------------------------------------------
/assets/screenshots/dark_mode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/assets/screenshots/dark_mode.png
--------------------------------------------------------------------------------
/assets/screenshots/notes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/assets/screenshots/notes.png
--------------------------------------------------------------------------------
/assets/screenshots/reminders.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/assets/screenshots/reminders.png
--------------------------------------------------------------------------------
/assets/screenshots/search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/assets/screenshots/search.png
--------------------------------------------------------------------------------
/fonts/AppIcons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/fonts/AppIcons.ttf
--------------------------------------------------------------------------------
/fonts/OFL.txt:
--------------------------------------------------------------------------------
1 | Copyright 2014 Pria Ravichandran (pria.ravichandran@gmail.com)
2 |
3 | This Font Software is licensed under the SIL Open Font License, Version 1.1.
4 | This license is copied below, and is also available with a FAQ at:
5 | http://scripts.sil.org/OFL
6 |
7 |
8 | -----------------------------------------------------------
9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10 | -----------------------------------------------------------
11 |
12 | PREAMBLE
13 | The goals of the Open Font License (OFL) are to stimulate worldwide
14 | development of collaborative font projects, to support the font creation
15 | efforts of academic and linguistic communities, and to provide a free and
16 | open framework in which fonts may be shared and improved in partnership
17 | with others.
18 |
19 | The OFL allows the licensed fonts to be used, studied, modified and
20 | redistributed freely as long as they are not sold by themselves. The
21 | fonts, including any derivative works, can be bundled, embedded,
22 | redistributed and/or sold with any software provided that any reserved
23 | names are not used by derivative works. The fonts and derivatives,
24 | however, cannot be released under any other type of license. The
25 | requirement for fonts to remain under this license does not apply
26 | to any document created using the fonts or their derivatives.
27 |
28 | DEFINITIONS
29 | "Font Software" refers to the set of files released by the Copyright
30 | Holder(s) under this license and clearly marked as such. This may
31 | include source files, build scripts and documentation.
32 |
33 | "Reserved Font Name" refers to any names specified as such after the
34 | copyright statement(s).
35 |
36 | "Original Version" refers to the collection of Font Software components as
37 | distributed by the Copyright Holder(s).
38 |
39 | "Modified Version" refers to any derivative made by adding to, deleting,
40 | or substituting -- in part or in whole -- any of the components of the
41 | Original Version, by changing formats or by porting the Font Software to a
42 | new environment.
43 |
44 | "Author" refers to any designer, engineer, programmer, technical
45 | writer or other person who contributed to the Font Software.
46 |
47 | PERMISSION & CONDITIONS
48 | Permission is hereby granted, free of charge, to any person obtaining
49 | a copy of the Font Software, to use, study, copy, merge, embed, modify,
50 | redistribute, and sell modified and unmodified copies of the Font
51 | Software, subject to the following conditions:
52 |
53 | 1) Neither the Font Software nor any of its individual components,
54 | in Original or Modified Versions, may be sold by itself.
55 |
56 | 2) Original or Modified Versions of the Font Software may be bundled,
57 | redistributed and/or sold with any software, provided that each copy
58 | contains the above copyright notice and this license. These can be
59 | included either as stand-alone text files, human-readable headers or
60 | in the appropriate machine-readable metadata fields within text or
61 | binary files as long as those fields can be easily viewed by the user.
62 |
63 | 3) No Modified Version of the Font Software may use the Reserved Font
64 | Name(s) unless explicit written permission is granted by the corresponding
65 | Copyright Holder. This restriction only applies to the primary font name as
66 | presented to the users.
67 |
68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69 | Software shall not be used to promote, endorse or advertise any
70 | Modified Version, except to acknowledge the contribution(s) of the
71 | Copyright Holder(s) and the Author(s) or with their explicit written
72 | permission.
73 |
74 | 5) The Font Software, modified or unmodified, in part or in whole,
75 | must be distributed entirely under this license, and must not be
76 | distributed under any other license. The requirement for fonts to
77 | remain under this license does not apply to any document created
78 | using the Font Software.
79 |
80 | TERMINATION
81 | This license becomes null and void if any of the above conditions are
82 | not met.
83 |
84 | DISCLAIMER
85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93 | OTHER DEALINGS IN THE FONT SOFTWARE.
94 |
--------------------------------------------------------------------------------
/fonts/Palanquin-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/fonts/Palanquin-Bold.ttf
--------------------------------------------------------------------------------
/fonts/Palanquin-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/fonts/Palanquin-Light.ttf
--------------------------------------------------------------------------------
/fonts/Palanquin-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/fonts/Palanquin-Medium.ttf
--------------------------------------------------------------------------------
/fonts/Palanquin-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/fonts/Palanquin-Regular.ttf
--------------------------------------------------------------------------------
/fonts/Palanquin-Thin.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/fonts/Palanquin-Thin.ttf
--------------------------------------------------------------------------------
/fonts/PalanquinDark-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/fonts/PalanquinDark-Bold.ttf
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
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.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 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
54 |
56 |
62 |
63 |
64 |
65 |
66 |
67 |
73 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/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/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/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/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/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/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/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/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/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/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/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/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/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/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/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/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/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/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/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/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/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/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/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/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/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/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/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/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/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/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/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/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasccustodio/notebulk/761f3bd54f46fcf28be4b58767726e1ef32df007/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 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | notebulk
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/ecs/components.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:entitas_ff/entitas_ff.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:notebulk/theme.dart';
6 |
7 | /*
8 | Naming conventions for unique components
9 |
10 | * = Ends with
11 |
12 | *Tag -> Identifier for unique components that represent some aspect of the app structure and helps with grouping relevant information.
13 | *Event -> Indentifier for event-like components, their modification triggers a system function and they rarely contain any data in them.
14 | */
15 |
16 | // Note/reminder/tag components
17 |
18 | // Text data present on notes and reminders
19 | class Contents extends Component {
20 | final String value;
21 |
22 | Contents(this.value);
23 | }
24 |
25 | // Whether a note is currently archived
26 | class Archived extends Component {}
27 |
28 | // Holds information for the database use
29 | class DatabaseKey extends Component {
30 | final int value;
31 |
32 | DatabaseKey(this.value);
33 | }
34 |
35 | // Note's Todo list
36 | class Todo extends Component {
37 | final List value;
38 |
39 | Todo({this.value = const []});
40 | }
41 |
42 | // Todo list item data
43 | class ListItem {
44 | bool isChecked;
45 |
46 | String label;
47 | ListItem(this.label, {this.isChecked = false});
48 | ListItem.fromJson(Map map)
49 | : label = map['label'] ?? '',
50 | isChecked = map['isChecked'] ?? false;
51 |
52 | @override
53 | int get hashCode => label.hashCode ^ isChecked.hashCode;
54 |
55 | @override
56 | bool operator ==(dynamic other) =>
57 | other is ListItem && other.hashCode == hashCode;
58 |
59 | Map toJson() => {'label': label, 'isChecked': isChecked};
60 |
61 | @override
62 | String toString() => toJson().toString();
63 | }
64 |
65 | // Note's image
66 | class Picture extends Component {
67 | final File value;
68 |
69 | Picture(String path) : value = File(path);
70 | }
71 |
72 | // Reminder's priority
73 | class Priority extends Component {
74 | final ReminderPriority value;
75 |
76 | Priority(this.value);
77 | }
78 |
79 | enum ReminderPriority { none, low, medium, high, maximum }
80 |
81 | // Tag's data
82 | class TagData extends Component {
83 | final String value;
84 |
85 | TagData(this.value);
86 | }
87 |
88 | // Note's tag list
89 | class Tags extends Component {
90 | final List value;
91 |
92 | Tags(this.value);
93 | }
94 |
95 | // Note and reminder creation/edit date
96 | class Timestamp extends Component {
97 | final DateTime value;
98 |
99 | Timestamp(String _timestamp) : value = DateTime.parse(_timestamp);
100 | }
101 |
102 | // Multipurpose components
103 |
104 | // Used by forms to enable the save button
105 | class Changed extends Component {}
106 |
107 | // Allows operations on multiple notes and reminders simultaneously
108 | class Selected extends Component {}
109 |
110 | // Marks a reminder as completed and toggles visiblity of status bar
111 | class Toggle extends Component {}
112 |
113 | // Tags for unique entities
114 | class AppSettingsTag extends UniqueComponent {}
115 |
116 | class StatusBarTag extends UniqueComponent {}
117 |
118 | class FeatureEntityTag extends UniqueComponent {}
119 |
120 | class SearchBarTag extends UniqueComponent {}
121 |
122 | class MainTickTag extends UniqueComponent {}
123 |
124 | // System components
125 |
126 | // Currently applied theme
127 | class AppTheme extends Component {
128 | final BaseTheme value;
129 |
130 | AppTheme(this.value);
131 | }
132 |
133 | // Whether the user has given permission for the database to load/persist the app's data
134 | class StoragePermission extends UniqueComponent {
135 | StoragePermission();
136 | }
137 |
138 | // Used to track how much time has passed since the user stopped typing in the search bar
139 | class Tick extends Component {
140 | final int value;
141 |
142 | Tick(this.value);
143 | }
144 |
145 | // Makes the statusbar dismissible only be user interaction
146 | class WaitForUser extends Component {}
147 |
148 | // Marks a note or reminder to be persisted
149 | class PersistMe extends Component {
150 | final int key;
151 |
152 | PersistMe([this.key]);
153 | }
154 |
155 | // Current visible page on main screen
156 | class PageIndex extends UniqueComponent {
157 | final int value;
158 |
159 | final int oldValue;
160 | PageIndex(this.value, {this.oldValue});
161 | }
162 |
163 | // Latest search string input by the user
164 | class SearchTerm extends Component {
165 | final String value;
166 |
167 | SearchTerm(this.value);
168 | }
169 |
170 | // Marks a note as being result of a search
171 | class SearchResult extends Component {}
172 |
173 | // Events
174 |
175 | // Archive notes
176 | class ArchiveNotesEvent extends UniqueComponent {}
177 |
178 | // Locale changed so updated localization
179 | class ChangeLocaleEvent extends UniqueComponent {
180 | final String localeCode;
181 |
182 | ChangeLocaleEvent(this.localeCode);
183 | }
184 |
185 | // Complete reminders
186 | class CompleteRemindersEvent extends UniqueComponent {}
187 |
188 | // Delete notes or reminders
189 | class DiscardSelectedEvent extends UniqueComponent {}
190 |
191 | // Export backup data to SD card
192 | class ExportBackupEvent extends UniqueComponent {}
193 |
194 | // Restore a previous backup
195 | class ImportBackupEvent extends UniqueComponent {}
196 |
197 | // Load user settings
198 | class LoadUserSettingsEvent extends UniqueComponent {}
199 |
200 | // Navigate somewhere
201 | class NavigationEvent extends UniqueComponent {
202 | final String routeName;
203 |
204 | final NavigationOps routeOp;
205 |
206 | NavigationEvent(
207 | {@required this.routeName, this.routeOp = NavigationOps.push});
208 |
209 | NavigationEvent.pop()
210 | : routeName = '',
211 | routeOp = NavigationOps.pop;
212 |
213 | NavigationEvent.push(this.routeName) : routeOp = NavigationOps.push;
214 |
215 | NavigationEvent.replace(this.routeName) : routeOp = NavigationOps.replace;
216 | NavigationEvent.showDialog(this.routeName)
217 | : routeOp = NavigationOps.showDialog;
218 | }
219 |
220 | enum NavigationOps { push, pop, replace, showDialog }
221 |
222 | // Perform a search
223 | class PerformSearchEvent extends UniqueComponent {}
224 |
225 | // Persist current user settings
226 | class PersistUserSettingsEvent extends UniqueComponent {}
227 |
228 | // Refresh notes and reminders
229 | class RefreshDatabaseEvent extends UniqueComponent {}
230 |
231 | // Restore notes from archive
232 | class RestoreNotesEvent extends UniqueComponent {}
233 |
234 | // Set up the database
235 | class SetupDatabaseEvent extends UniqueComponent {}
236 |
--------------------------------------------------------------------------------
/lib/ecs/ecs.dart:
--------------------------------------------------------------------------------
1 | export 'package:entitas_ff/entitas_ff.dart';
2 | export 'components.dart';
3 | export 'matchers.dart';
4 | export 'systems.dart';
5 | export 'util.dart';
--------------------------------------------------------------------------------
/lib/ecs/matchers.dart:
--------------------------------------------------------------------------------
1 | import 'package:entitas_ff/entitas_ff.dart';
2 | import 'package:notebulk/ecs/components.dart';
3 | import 'package:notebulk/util.dart';
4 |
5 | /*
6 | Since a Entity is a mere container for components there must be a way to classify and group them, that's what a matcher is for.
7 | */
8 |
9 | mixin Matchers {
10 | static EntityMatcher note = EntityMatcher(
11 | all: [Timestamp, Contents, DatabaseKey],
12 | none: [Archived, Priority],
13 | maybe: [Tags, Todo, Selected]);
14 |
15 | static EntityMatcher archived = EntityMatcher(
16 | none: [Priority],
17 | all: [Contents, Timestamp, DatabaseKey, Archived],
18 | maybe: [Selected, Tags, Todo]);
19 |
20 | static EntityMatcher reminder = EntityMatcher(
21 | all: [Timestamp, Contents, Priority, DatabaseKey], maybe: [Selected]);
22 |
23 | static EntityMatcher tag = EntityMatcher(all: [TagData], maybe: [Toggle]);
24 |
25 | static EntityMatcher searchResult = note.extend(all: [SearchResult]);
26 |
27 | static EntityMatcher settings =
28 | EntityMatcher(all: [AppTheme, Localization], maybe: [Toggle]);
29 | }
30 |
--------------------------------------------------------------------------------
/lib/ecs/util.dart:
--------------------------------------------------------------------------------
1 | import 'package:entitas_ff/entitas_ff.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
4 | import 'package:notebulk/ecs/components.dart';
5 |
6 | void toggleSelected(Entity target) {
7 | if (target.hasT())
8 | target.remove();
9 | else
10 | target.set(Selected());
11 | }
12 |
13 | /*
14 | Experimental untested stuff
15 |
16 | enum ListItemAnimation {
17 | fadeIn,
18 | fadeOut,
19 | enterLeft,
20 | enterRight,
21 | expand,
22 | shrink
23 | }
24 |
25 | class GroupObservingListBuilder extends StatefulWidget {
26 | const GroupObservingListBuilder(
27 | {@required this.matcher,
28 | @required this.itemBuilder,
29 | this.curve = Curves.bounceIn,
30 | this.reversed = true,
31 | this.duration = const Duration(milliseconds: 300),
32 | this.onAdded,
33 | this.onUpdated,
34 | Key key})
35 | : super(key: key);
36 |
37 | final Curve curve;
38 | final EntityMatcher matcher;
39 | final bool reversed;
40 | final EntityWidgetBuilder itemBuilder;
41 | final ListItemAnimation onAdded;
42 | final ListItemAnimation onUpdated;
43 | final Duration duration;
44 |
45 | @override
46 | _GroupObservingListBuilderState createState() =>
47 | _GroupObservingListBuilderState();
48 | }
49 |
50 | class _GroupObservingListBuilderState extends State
51 | implements GroupObserver {
52 | EntityGroup _group;
53 | int _added, _updated;
54 |
55 | @override
56 | void didChangeDependencies() {
57 | super.didChangeDependencies();
58 | _group?.removeObserver(this);
59 | final entityManager = EntityManagerProvider.of(context).entityManager;
60 | assert(entityManager != null);
61 | _group = entityManager.groupMatching(widget.matcher);
62 | _group?.addObserver(this);
63 | }
64 |
65 | @override
66 | void dispose() {
67 | _group?.removeObserver(this);
68 | super.dispose();
69 | }
70 |
71 | @override
72 | Widget build(BuildContext context) {
73 | final groupLength = _group?.entities?.length ?? 0;
74 |
75 | return ListView.builder(
76 | itemCount: groupLength,
77 | itemBuilder: (context, index) => AnimatableEntityObservingWidget(
78 | key: ValueKey('ListItem${_group.entities[index].creationIndex}'),
79 | provider: (_) => _group.entities[index],
80 | startAnimating: true,
81 | duration: widget.duration,
82 | curve: widget.curve,
83 | tweens: {'_default': Tween(begin: 0.0, end: 1.0)},
84 | onAnimationEnd: (end) {
85 | if (!end) {
86 | _added = -1;
87 | _updated = -1;
88 | _update();
89 | }
90 | },
91 | builder: (itemEntity, anim, context) {
92 | final delta = anim['_default'].value;
93 |
94 | if (index == _added) {
95 | return _AnimatedListItem(
96 | builder: widget.itemBuilder,
97 | delta: delta,
98 | animation: widget.onAdded,
99 | entity: itemEntity,
100 | );
101 | } else if (index == _updated) {
102 | return _AnimatedListItem(
103 | builder: widget.itemBuilder,
104 | delta: delta,
105 | animation: widget.onUpdated,
106 | entity: itemEntity,
107 | );
108 | } else
109 | return widget.itemBuilder(itemEntity, context);
110 | },
111 | ),
112 | reverse: widget.reversed,
113 | shrinkWrap: true,
114 | );
115 | }
116 |
117 | @override
118 | void added(EntityGroup group, ObservableEntity entity) {
119 | _added = _group.entities.indexOf(entity);
120 | _update();
121 | }
122 |
123 | @override
124 | void removed(EntityGroup group, ObservableEntity entity) {}
125 |
126 | @override
127 | void updated(EntityGroup group, ObservableEntity entity) {
128 | _updated = _group.entities.indexOf(entity);
129 | _update();
130 | }
131 |
132 | void _update() {
133 | if (mounted) {
134 | setState(() {});
135 | }
136 | }
137 | }
138 |
139 | class GroupObservingGridBuilder extends StatefulWidget {
140 | const GroupObservingGridBuilder(
141 | {@required this.matcher,
142 | @required this.itemBuilder,
143 | @required this.delegate,
144 | this.reversed = true,
145 | this.duration = const Duration(milliseconds: 300),
146 | this.onAdded,
147 | this.onUpdated,
148 | Key key})
149 | : super(key: key);
150 |
151 | final EntityMatcher matcher;
152 | final bool reversed;
153 | final SliverGridDelegate delegate;
154 | final EntityWidgetBuilder itemBuilder;
155 | final ListItemAnimation onAdded;
156 | final ListItemAnimation onUpdated;
157 | final Duration duration;
158 |
159 | @override
160 | _GroupObservingGridBuilderState createState() =>
161 | _GroupObservingGridBuilderState();
162 | }
163 |
164 | class _GroupObservingGridBuilderState extends State
165 | implements GroupObserver {
166 | EntityGroup _group;
167 | int _added, _updated;
168 |
169 | @override
170 | void didChangeDependencies() {
171 | super.didChangeDependencies();
172 | _group?.removeObserver(this);
173 | final entityManager = EntityManagerProvider.of(context).entityManager;
174 | assert(entityManager != null);
175 | _group = entityManager.groupMatching(widget.matcher);
176 | _group?.addObserver(this);
177 | }
178 |
179 | @override
180 | void dispose() {
181 | _group?.removeObserver(this);
182 | super.dispose();
183 | }
184 |
185 | @override
186 | Widget build(BuildContext context) {
187 | final groupLength = _group?.entities?.length ?? 0;
188 |
189 | return StaggeredGridView.countBuilder(
190 | itemCount: groupLength,
191 | shrinkWrap: true,
192 | physics: NeverScrollableScrollPhysics(),
193 | padding: EdgeInsets.symmetric(horizontal: 8, vertical: 8),
194 | crossAxisCount: 2,
195 | crossAxisSpacing: 4,
196 | mainAxisSpacing: 4,
197 | staggeredTileBuilder: (_) => StaggeredTile.fit(1),
198 | itemBuilder: (context, index) => AnimatableEntityObservingWidget(
199 | key: Key('GridItem${_group.entities[index].creationIndex}'),
200 | provider: (_) => _group.entities[index],
201 | startAnimating: true,
202 | duration: widget.duration,
203 | tweens: {'_default': Tween(begin: 0.0, end: 1.0)},
204 | onAnimationEnd: (end) {
205 | if (!end) {
206 | _updated = -1;
207 | _added = -1;
208 | }
209 | },
210 | builder: (itemEntity, anim, context) {
211 | final delta = anim['_default'].value;
212 |
213 | if (index == _added) {
214 | return _AnimatedListItem(
215 | builder: widget.itemBuilder,
216 | delta: delta,
217 | animation: widget.onAdded,
218 | entity: itemEntity,
219 | );
220 | } else if (index == _updated) {
221 | return _AnimatedListItem(
222 | builder: widget.itemBuilder,
223 | delta: delta,
224 | animation: widget.onUpdated,
225 | entity: itemEntity,
226 | );
227 | } else
228 | return widget.itemBuilder(itemEntity, context);
229 | },
230 | ),
231 | );
232 | }
233 |
234 | @override
235 | void added(EntityGroup group, ObservableEntity entity) {
236 | _added = group.entities.indexOf(entity);
237 | _update();
238 | }
239 |
240 | @override
241 | void removed(EntityGroup group, ObservableEntity entity) {
242 | _update();
243 | }
244 |
245 | @override
246 | void updated(EntityGroup group, ObservableEntity entity) {
247 | _updated = group.entities.indexOf(entity);
248 | _update();
249 | }
250 |
251 | void _update() {
252 | if (mounted) {
253 | setState(() {});
254 | }
255 | }
256 | }
257 |
258 | class _AnimatedListItem extends StatelessWidget {
259 | const _AnimatedListItem({
260 | @required this.entity,
261 | @required this.animation,
262 | @required this.builder,
263 | @required this.delta,
264 | Key key,
265 | }) : super(key: key);
266 |
267 | final EntityWidgetBuilder builder;
268 | final ListItemAnimation animation;
269 | final double delta;
270 | final Entity entity;
271 |
272 | @override
273 | Widget build(BuildContext context) {
274 | var itemWidget = builder(entity, context);
275 |
276 | switch (animation) {
277 | case ListItemAnimation.fadeIn:
278 | itemWidget = Opacity(
279 | opacity: delta,
280 | child: itemWidget,
281 | );
282 | break;
283 | case ListItemAnimation.fadeOut:
284 | itemWidget = Opacity(
285 | opacity: 1.0 - delta,
286 | child: itemWidget,
287 | );
288 | break;
289 | case ListItemAnimation.expand:
290 | itemWidget = Align(
291 | alignment: Alignment.centerLeft,
292 | child: itemWidget,
293 | widthFactor: delta,
294 | heightFactor: delta,
295 | );
296 | break;
297 | case ListItemAnimation.shrink:
298 | itemWidget = ClipRect(
299 | child: Align(
300 | child: itemWidget,
301 | widthFactor: 1.0 - delta,
302 | heightFactor: 1.0 - delta,
303 | ),
304 | );
305 | break;
306 | case ListItemAnimation.enterLeft:
307 | itemWidget = FractionalTranslation(
308 | child: itemWidget,
309 | translation: Offset(-1.0 + delta, 0),
310 | transformHitTests: true,
311 | );
312 | break;
313 | case ListItemAnimation.enterRight:
314 | itemWidget = FractionalTranslation(
315 | child: itemWidget,
316 | translation: Offset(1.0 + delta, 0),
317 | transformHitTests: true,
318 | );
319 | break;
320 | default:
321 | }
322 |
323 | return itemWidget;
324 | }
325 | }
326 | */
327 |
--------------------------------------------------------------------------------
/lib/features/features.dart:
--------------------------------------------------------------------------------
1 | /*
2 | Features
3 |
4 | Features are a variant of EntityManagerProvider that create and manage their own EntityManager instance.
5 | They allow the app to run logic isolated from the main execution context which includes having control over what systems will be run or entities copied from the main EntityManager with the lifecycle callback onCreate, and any changes made can be later transmitted, if needed, to the main EntityManager using the lifecycle callback onDestroy.
6 |
7 | For example, when creating or editing a note there's no need to have any system being active on the background, so a isolated context is ideal not only for performance but predictability.
8 | */
9 |
10 | export 'noteFormFeature.dart';
11 | export 'reminderFormFeature.dart';
12 |
--------------------------------------------------------------------------------
/lib/features/reminderFormFeature.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:notebulk/ecs/ecs.dart';
3 | import 'package:notebulk/theme.dart';
4 | import 'package:notebulk/util.dart';
5 | import 'package:tinycolor/tinycolor.dart';
6 | import 'package:notebulk/widgets/util.dart';
7 |
8 | // Reminder form, will get intially populated if the reminder already exists or start blank if creating a new one
9 | class ReminderFormFeature extends StatefulWidget {
10 | const ReminderFormFeature({Key key, this.title}) : super(key: key);
11 |
12 | final String title;
13 |
14 | @override
15 | _ReminderFormFeatureState createState() => _ReminderFormFeatureState();
16 | }
17 |
18 | class _ReminderFormFeatureState extends State {
19 | GlobalKey key = GlobalKey();
20 | FocusNode contentsNode;
21 |
22 | @override
23 | void initState() {
24 | super.initState();
25 | contentsNode = FocusNode();
26 | }
27 |
28 | @override
29 | Widget build(BuildContext context) {
30 | final entityManager = EntityManagerProvider.of(context).entityManager;
31 | final localization =
32 | entityManager.getUniqueEntity().get();
33 |
34 | final appTheme =
35 | entityManager.getUniqueEntity().get().value;
36 |
37 | void closeFeature() {
38 | Navigator.of(context).pop(true);
39 | }
40 |
41 | void closeDialog() {
42 | Navigator.of(context).pop(false);
43 | }
44 |
45 | return WillPopScope(
46 | onWillPop: () async {
47 | if (!entityManager
48 | .getUniqueEntity()
49 | .hasT()) {
50 | return true;
51 | } else {
52 | return await showDialog(
53 | context: context,
54 | builder: (context) => ShouldLeavePromptDialog(
55 | message: localization.promptLeaveUnsaved,
56 | yesLabel: localization.yes,
57 | noLabel: localization.no,
58 | appTheme: appTheme,
59 | onNo: closeDialog,
60 | onYes: () {
61 | // Exit without saving
62 | entityManager
63 | .getUniqueEntity()
64 | .remove();
65 | closeFeature();
66 | },
67 | ));
68 | }
69 | },
70 | child: Scaffold(
71 | resizeToAvoidBottomInset: true,
72 | backgroundColor: appTheme.appBarColor,
73 | body: Form(
74 | key: key,
75 | child: SafeArea(
76 | top: false,
77 | bottom: true,
78 | maintainBottomViewPadding: false,
79 | child: Theme(
80 | data: ThemeData(accentColor: appTheme.primaryColor),
81 | child: CustomScrollView(
82 | slivers: [
83 | SliverAppBar(
84 | floating: true,
85 | pinned: true,
86 | leading: IconButton(
87 | icon: Icon(
88 | AppIcons.left,
89 | color: appTheme.baseStyle.color,
90 | ),
91 | onPressed: () {
92 | Navigator.of(context).pop();
93 | },
94 | ),
95 | backgroundColor: appTheme.appBarColor,
96 | actions: [
97 | EntityObservingWidget(
98 | provider: (em) =>
99 | em.getUniqueEntity(),
100 | builder: (noteEntity, context) => FlatButton(
101 | child: Text(localization.saveChangesFeatureLabel,
102 | style: appTheme.appTitleTextStyle.copyWith(
103 | fontSize: 24,
104 | color: noteEntity.hasT()
105 | ? appTheme.primaryButtonColor
106 | : BaseTheme.lightGrey)),
107 | onPressed: noteEntity.hasT()
108 | ? () {
109 | if (key.currentState.validate())
110 | key.currentState.save();
111 | else {
112 | contentsNode
113 | ..unfocus()
114 | ..requestFocus();
115 | return;
116 | }
117 |
118 | entityManager
119 | .getUniqueEntity()
120 | .set(PersistMe());
121 |
122 | closeFeature();
123 | }
124 | : null,
125 | ),
126 | ),
127 | ],
128 | title: EntityObservingWidget(
129 | provider: (em) => em.getUniqueEntity(),
130 | builder: (noteEntity, context) => Text(
131 | "${widget.title}${noteEntity.hasT() ? '*' : ''}",
132 | style: appTheme.appTitleTextStyle
133 | .copyWith(fontFamily: 'Palanquin'),
134 | ),
135 | ),
136 | ),
137 | SliverPadding(
138 | padding: const EdgeInsets.all(16),
139 | sliver: SliverToBoxAdapter(
140 | child: EntityObservingWidget(
141 | provider: (em) =>
142 | em.getUniqueEntity(),
143 | builder: (noteEntity, context) =>
144 | buildFormBody(noteEntity, localization, appTheme),
145 | ),
146 | )),
147 | ],
148 | ),
149 | ),
150 | ),
151 | ),
152 | ),
153 | );
154 | }
155 |
156 | Widget buildFormBody(
157 | Entity noteEntity, Localization localization, BaseTheme appTheme) {
158 | final priority = noteEntity.get()?.value;
159 | final cardColor = priority != null
160 | ? appTheme.reminderPriorityColors[priority.index]
161 | : appTheme.appBarColor;
162 | final style = appTheme.formLabelStyle;
163 |
164 | return Padding(
165 | padding: const EdgeInsets.all(16),
166 | child: Column(
167 | mainAxisAlignment: MainAxisAlignment.start,
168 | crossAxisAlignment: CrossAxisAlignment.start,
169 | children: [
170 | buildTimestamp(noteEntity, localization, appTheme),
171 | Padding(
172 | padding: const EdgeInsets.only(top: 16),
173 | child: Text(
174 | localization.featurePriorityLabel,
175 | style: style,
176 | ),
177 | ),
178 | buildColorPicker(noteEntity, appTheme),
179 | Padding(
180 | padding: const EdgeInsets.only(top: 16.0),
181 | child: Text(
182 | localization.featureContentsLabel,
183 | style: style,
184 | ),
185 | ),
186 | buildContentsField(noteEntity, localization, appTheme),
187 | ],
188 | ),
189 | );
190 | }
191 |
192 | Widget buildColorPicker(Entity noteEntity, BaseTheme appTheme) {
193 | final reminderPriority = noteEntity.get()?.value;
194 | final size = MediaQuery.of(context).size.width * 0.15;
195 |
196 | return Row(
197 | crossAxisAlignment: CrossAxisAlignment.center,
198 | mainAxisAlignment: MainAxisAlignment.spaceEvenly,
199 | mainAxisSize: MainAxisSize.max,
200 | children: [
201 | for (final priority in ReminderPriority.values)
202 | InkWell(
203 | onTap: () {
204 | noteEntity..set(Priority(priority))..set(Changed());
205 | },
206 | child: Container(
207 | decoration: BoxDecoration(
208 | color: appTheme.reminderPriorityColors[priority.index],
209 | border: priority == reminderPriority
210 | ? Border.all(color: appTheme.baseStyle.color, width: 2)
211 | : null,
212 | ),
213 | width: size,
214 | height: size,
215 | ),
216 | )
217 | ],
218 | );
219 | }
220 |
221 | Widget buildTimestamp(
222 | Entity noteEntity, Localization localization, BaseTheme appTheme) {
223 | final timestamp = noteEntity.get()?.value ?? DateTime.now();
224 | final priority = noteEntity.get()?.value;
225 | final cardColor = priority != null
226 | ? appTheme.reminderPriorityColors[priority.index]
227 | : appTheme.appBarColor;
228 | final textColor =
229 | (TinyColor(cardColor).isDark() ? Colors.white : Colors.black)
230 | .withOpacity(0.7);
231 |
232 | return InkWell(
233 | onTap: () => showCalendarDialog(noteEntity, appTheme),
234 | child: Text(
235 | formatTimestamp(timestamp, localization),
236 | style: appTheme.titleTextStyle,
237 | ),
238 | );
239 | }
240 |
241 | void showCalendarDialog(Entity noteEntity, BaseTheme appTheme) async {
242 | final today = DateTime.now();
243 | final date = await showDatePicker(
244 | context: context,
245 | builder: (context, calendar) => Theme(
246 | data: ThemeData(
247 | brightness: appTheme.brightness,
248 | primaryColor: appTheme.primaryColor,
249 | backgroundColor: appTheme.primaryColor,
250 | fontFamily: 'Palanquin',
251 | dialogTheme: DialogTheme(
252 | titleTextStyle: appTheme.titleTextStyle,
253 | contentTextStyle: appTheme.subtitleTextStyle,
254 | ),
255 | accentColor: appTheme.accentColor),
256 | child: calendar,
257 | ),
258 | initialDate: noteEntity.get()?.value ?? today,
259 | firstDate: DateTime.utc(today.year),
260 | lastDate: DateTime.utc(today.year + 100));
261 |
262 | if (date != null) {
263 | noteEntity..set(Timestamp(date.toIso8601String()))..set(Changed());
264 | }
265 | }
266 |
267 | Widget buildContentsField(
268 | Entity noteEntity, Localization localization, BaseTheme appTheme) {
269 | final priority = noteEntity.get()?.value?.index ?? 0;
270 | final cardColor = appTheme.reminderPriorityColors[priority];
271 | final fillColor = cardColor;
272 |
273 | return Theme(
274 | data: ThemeData(
275 | fontFamily: 'Palanquin'), // Fixes desktop not showing error text
276 | child: TextFormField(
277 | focusNode: contentsNode,
278 | autofocus: true,
279 | initialValue: noteEntity.get()?.value ?? '',
280 | textInputAction: TextInputAction.newline,
281 | keyboardType: TextInputType.text,
282 | maxLines: null,
283 | minLines: 5,
284 | decoration: InputDecoration(
285 | filled: true,
286 | fillColor: fillColor,
287 | contentPadding: const EdgeInsets.all(12),
288 | border: OutlineInputBorder(
289 | borderSide: BorderSide.none,
290 | borderRadius: BorderRadius.circular(6),
291 | gapPadding: 0),
292 | hintText: localization.featureReminderHint,
293 | ),
294 | onSaved: (contents) {
295 | // Form validated so save modifications
296 | noteEntity.set(Contents(contents));
297 | },
298 | onChanged: (_) {
299 | // Inform that the note contents changed and enable save button
300 | noteEntity.set(Changed());
301 | },
302 | validator: (contents) =>
303 | contents.isEmpty ? localization.featureContentsError : null,
304 | style: appTheme.biggerBodyTextStyle.copyWith(
305 | color: TinyColor(cardColor).isDark() ? Colors.white : Colors.black),
306 | textAlign: TextAlign.left,
307 | ),
308 | );
309 | }
310 | }
311 |
--------------------------------------------------------------------------------
/lib/icons.dart:
--------------------------------------------------------------------------------
1 | /// Flutter icons AppIcons
2 | /// Copyright (C) 2019 by original authors @ fluttericon.com, fontello.com
3 | /// This font was generated by FlutterIcon.com, which is derived from Fontello.
4 | ///
5 | /// To use this font, place it in your fonts/ directory and include the
6 | /// following in your pubspec.yaml
7 | ///
8 | /// flutter:
9 | /// fonts:
10 | /// - family: AppIcons
11 | /// fonts:
12 | /// - asset: fonts/AppIcons.ttf
13 | ///
14 | ///
15 | /// * MFG Labs, Copyright (C) 2012 by Daniel Bruce
16 | /// Author: MFG Labs
17 | /// License: SIL (http://scripts.sil.org/OFL)
18 | /// Homepage: http://www.mfglabs.com/
19 | ///
20 | import 'package:flutter/widgets.dart';
21 |
22 | class AppIcons {
23 | AppIcons._();
24 |
25 | static const _kFontFam = 'AppIcons';
26 |
27 | static const IconData star = const IconData(0xe800, fontFamily: _kFontFam);
28 | static const IconData starEmpty =
29 | const IconData(0xe801, fontFamily: _kFontFam);
30 | static const IconData ok = const IconData(0xe802, fontFamily: _kFontFam);
31 | static const IconData cancel = const IconData(0xe803, fontFamily: _kFontFam);
32 | static const IconData plus = const IconData(0xe804, fontFamily: _kFontFam);
33 | static const IconData pencil = const IconData(0xe805, fontFamily: _kFontFam);
34 | static const IconData left = const IconData(0xe806, fontFamily: _kFontFam);
35 | static const IconData right = const IconData(0xe807, fontFamily: _kFontFam);
36 | static const IconData menu = const IconData(0xf008, fontFamily: _kFontFam);
37 | static const IconData grid = const IconData(0xf00a, fontFamily: _kFontFam);
38 | static const IconData gridEmpty =
39 | const IconData(0xf00b, fontFamily: _kFontFam);
40 | static const IconData download =
41 | const IconData(0xf02e, fontFamily: _kFontFam);
42 | static const IconData upload = const IconData(0xf02f, fontFamily: _kFontFam);
43 | static const IconData eye = const IconData(0xf082, fontFamily: _kFontFam);
44 | static const IconData trash = const IconData(0xf083, fontFamily: _kFontFam);
45 | static const IconData calendar =
46 | const IconData(0xf4c5, fontFamily: _kFontFam);
47 | static const IconData search = const IconData(0xf50d, fontFamily: _kFontFam);
48 | static const IconData lock = const IconData(0xf512, fontFamily: _kFontFam);
49 | static const IconData lockOpen =
50 | const IconData(0xf513, fontFamily: _kFontFam);
51 | }
52 |
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:entitas_ff/entitas_ff.dart';
4 | import 'package:flutter/foundation.dart';
5 | import 'package:flutter/material.dart';
6 | import 'package:flutter/rendering.dart';
7 | import 'package:flutter_localizations/flutter_localizations.dart';
8 | import 'package:notebulk/mainApp.dart';
9 | import 'package:notebulk/theme.dart';
10 | import 'package:notebulk/util.dart';
11 | import 'package:notebulk/widgets/util.dart';
12 |
13 | import 'ecs/ecs.dart';
14 | import 'features/features.dart';
15 | import 'pages/pages.dart';
16 |
17 | void main() async {
18 | // Necessary to debug on Linux/Fucshia
19 | if (Platform.isLinux || Platform.isFuchsia)
20 | debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia;
21 |
22 | // Main EntityManager instance
23 | final mainEntityManager = EntityManager();
24 |
25 | // Set up the app initial configuration
26 | mainEntityManager.setUnique(AppSettingsTag())
27 | ..set(Localization.en())
28 | ..set(AppTheme(BlankTheme()));
29 | mainEntityManager
30 | ..setUnique(StatusBarTag())
31 | ..setUnique(PageIndex(0, oldValue: 0))
32 | ..setUnique(SearchBarTag());
33 |
34 | if (Platform.isFuchsia || Platform.isLinux || Platform.isWindows)
35 | mainEntityManager.setUnique(
36 | StoragePermission()); // No need for external storage read/write permission
37 |
38 | // The app's Navigator instance
39 | final navigatorKey = GlobalKey();
40 |
41 | // Provides the EntityManager instance and hosts the RootSystem
42 | runApp(EntityManagerProvider(
43 | entityManager: mainEntityManager,
44 | child: EntityObservingWidget(
45 | provider: (em) => em.getUniqueEntity(),
46 | builder: (settings, context) {
47 | final appTheme = settings.get().value;
48 |
49 | return GradientBackground(
50 | appTheme: appTheme,
51 | child: MaterialApp(
52 | debugShowCheckedModeBanner: false,
53 | debugShowMaterialGrid: false,
54 | // Needed to have navigation events
55 | // TODO: Make RootSystem a NavigatorObserver and implement navigation on Entitas side instead
56 | navigatorKey: navigatorKey,
57 | title: 'Notebulk',
58 | initialRoute: Routes.splashScreen,
59 | supportedLocales: const [Locale('en', 'US'), Locale('pt', 'BR')],
60 | localeResolutionCallback: (locale, _) {
61 | if (locale != null) {
62 | mainEntityManager
63 | .setUnique(ChangeLocaleEvent(locale.languageCode));
64 | }
65 | return locale;
66 | },
67 | localizationsDelegates: [
68 | GlobalMaterialLocalizations.delegate,
69 | GlobalWidgetsLocalizations.delegate,
70 | ],
71 | // Handle navigation here
72 | // TODO: Consider changing to Fluro, Voyager or alike
73 | onGenerateRoute: (settings) {
74 | Widget pageWidget;
75 | final localization = mainEntityManager
76 | .getUniqueEntity()
77 | .get();
78 | final appTheme = mainEntityManager
79 | .getUniqueEntity()
80 | .get();
81 |
82 | // See features.dart for an explaination on Features
83 |
84 | switch (settings.name) {
85 | case Routes.splashScreen:
86 | pageWidget = SplashScreenPage();
87 | break;
88 | //
89 | case Routes.mainScreen:
90 | pageWidget = MainApp(
91 | entityManager: mainEntityManager,
92 | );
93 | break;
94 | case Routes.createNote:
95 | pageWidget = EntityManagerProvider.feature(
96 | child: NoteFormFeature(
97 | title: localization.createNoteFeatureTitle,
98 | ),
99 | system: FeatureSystem(
100 | rootEntityManager: mainEntityManager,
101 | onCreate: (em, root) {
102 | // Will hold the note data temporarily
103 | em.setUnique(FeatureEntityTag());
104 | // Copy the settings to match locale and theming
105 | em.setUnique(AppSettingsTag())
106 | ..set(localization)
107 | ..set(appTheme);
108 | },
109 | onDestroy: (em, root) {
110 | final note = em.getUniqueEntity();
111 |
112 | // User didn't exit without saving, so persist note
113 | if (note.hasT())
114 | root.createEntity()
115 | ..set(note.get())
116 | ..set(note.get())
117 | ..set(note.get())
118 | ..set(note.get())
119 | ..set(PersistMe());
120 | },
121 | ),
122 | );
123 | break;
124 | case Routes.editNote:
125 | pageWidget = EntityManagerProvider.feature(
126 | child: NoteFormFeature(
127 | title: localization.editNoteFeatureTitle,
128 | ),
129 | system: FeatureSystem(
130 | rootEntityManager: mainEntityManager,
131 | onCreate: (em, root) {
132 | final editNote =
133 | root.getUniqueEntity();
134 |
135 | // Copy theme and locale
136 | em.setUnique(AppSettingsTag())
137 | ..set(localization)
138 | ..set(appTheme);
139 |
140 | // Populate the temporary note entity with current data
141 | em.setUnique(FeatureEntityTag())
142 | ..set(editNote.get())
143 | ..set(editNote.get())
144 | ..set(editNote.get())
145 | ..set(editNote.get())
146 | ..set(editNote.get());
147 |
148 | // Failsafe to avoid conflicts
149 | root.removeUnique();
150 | },
151 | onDestroy: (em, root) {
152 | final note = em.getUniqueEntity();
153 |
154 | // User didn't exit without saving, so persist changes
155 | if (note.hasT())
156 | root.createEntity()
157 | ..set(note.get())
158 | ..set(note.get())
159 | ..set(note.get())
160 | ..set(note.get())
161 | ..set(PersistMe(note.get().value));
162 | },
163 | ),
164 | );
165 | break;
166 | case Routes.createReminder:
167 | pageWidget = EntityManagerProvider.feature(
168 | system: FeatureSystem(
169 | rootEntityManager: mainEntityManager,
170 | onCreate: (em, root) {
171 | // Temporary reminder with placeholder values
172 | em.setUnique(FeatureEntityTag())
173 | ..set(Priority(ReminderPriority.low))
174 | ..set(Timestamp(DateTime.now().toIso8601String()));
175 | // Copy to match theming and locale
176 | em.setUnique(AppSettingsTag())
177 | ..set(localization)
178 | ..set(appTheme);
179 | },
180 | onDestroy: (em, root) {
181 | final reminder =
182 | em.getUniqueEntity();
183 |
184 | // User didn't exit without saving, so persist reminder
185 | if (reminder.hasT())
186 | root.createEntity()
187 | ..set(reminder.get())
188 | ..set(reminder.get())
189 | ..set(reminder.get())
190 | ..set(PersistMe());
191 | }),
192 | child: ReminderFormFeature(
193 | title: localization.createEventFeatureTitle,
194 | ),
195 | );
196 | break;
197 | case Routes.editReminder:
198 | pageWidget = EntityManagerProvider.feature(
199 | child: ReminderFormFeature(
200 | title: localization.editEventFeatureTitle,
201 | ),
202 | system: FeatureSystem(
203 | rootEntityManager: mainEntityManager,
204 | onCreate: (em, root) {
205 | final editReminder =
206 | root.getUniqueEntity();
207 |
208 | em.setUnique(AppSettingsTag())
209 | ..set(localization)
210 | ..set(appTheme);
211 |
212 | // Populate with current data
213 | em.setUnique(FeatureEntityTag())
214 | ..set(editReminder.get())
215 | ..set(editReminder.get())
216 | ..set(editReminder.get())
217 | ..set(editReminder.get());
218 |
219 | // Failsafe to avoid conflicts
220 | root.removeUnique();
221 | },
222 | onDestroy: (em, root) {
223 | final reminder = em.getUniqueEntity();
224 |
225 | // User didn't exit without saving, so persist changes
226 | if (reminder.hasT())
227 | root.createEntity()
228 | ..set(reminder.get())
229 | ..set(reminder.get())
230 | ..set(reminder.get())
231 | ..set(PersistMe(reminder.get().value));
232 | },
233 | ),
234 | );
235 | break;
236 | default:
237 | pageWidget = Container();
238 | }
239 | return MaterialPageRoute(
240 | builder: (_) => pageWidget, fullscreenDialog: true);
241 | },
242 | // Basic theming
243 | theme: ThemeData(
244 | brightness: appTheme.brightness,
245 | canvasColor: Colors.transparent,
246 | splashColor: appTheme.accentColor,
247 | scaffoldBackgroundColor: Colors.transparent,
248 | textSelectionHandleColor: appTheme.primaryButtonColor,
249 | textSelectionColor: appTheme.primaryButtonColor,
250 | fontFamily: 'Palanquin',
251 | typography: Typography(
252 | englishLike: Typography.englishLike2018,
253 | dense: Typography.dense2018,
254 | tall: Typography.tall2018,
255 | )),
256 | ),
257 | );
258 | },
259 | ),
260 | // Manages all the app systems lifecycle and execution
261 | system: RootSystem(entityManager: mainEntityManager, systems: [
262 | DatabaseSystem(),
263 | UserSettingsSystem(),
264 | TickSystem(),
265 | BackupSystem(),
266 | PersistanceSystem(),
267 | DiscardSystem(),
268 | ReminderOperationsSystem(),
269 | NoteOperationsSystem(),
270 | NavigationSystem(navigatorKey),
271 | StatusBarSystem(),
272 | InBetweenNavigationSystem(),
273 | SearchSystem()
274 | ]),
275 | ));
276 | }
277 |
--------------------------------------------------------------------------------
/lib/mainApp.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:entitas_ff/entitas_ff.dart';
4 | import 'package:flutter/gestures.dart';
5 | import 'package:flutter/material.dart' hide TabBar;
6 | import 'package:notebulk/ecs/components.dart';
7 | import 'package:notebulk/pages/archivePage.dart';
8 | import 'package:notebulk/pages/noteListPage.dart';
9 | import 'package:notebulk/pages/reminderListPage.dart';
10 | import 'package:notebulk/pages/searchPage.dart';
11 | import 'package:notebulk/pages/settingsPage.dart';
12 | import 'package:notebulk/theme.dart';
13 | import 'package:notebulk/util.dart';
14 | import 'package:notebulk/widgets/util.dart';
15 |
16 | class MainApp extends StatefulWidget {
17 | const MainApp({Key key, this.entityManager}) : super(key: key);
18 |
19 | final EntityManager entityManager;
20 |
21 | @override
22 | _MainAppState createState() => _MainAppState();
23 | }
24 |
25 | class _MainAppState extends State {
26 | final Curve curve = Curves.easeIn;
27 |
28 | final Duration duration = const Duration(milliseconds: 200);
29 | PageController pageController;
30 | GlobalKey scaffoldKey;
31 |
32 | @override
33 | void didChangeDependencies() {
34 | super.didChangeDependencies();
35 | pageController = PageController(
36 | keepPage: true,
37 | initialPage: widget.entityManager.getUnique().value);
38 | }
39 |
40 | @override
41 | void initState() {
42 | super.initState();
43 | scaffoldKey = GlobalKey();
44 | }
45 |
46 | @override
47 | Widget build(BuildContext context) {
48 | final localization = widget.entityManager
49 | .getUniqueEntity()
50 | .get();
51 | final appTheme = widget.entityManager
52 | .getUniqueEntity()
53 | .get()
54 | .value;
55 |
56 | return WillPopScope(
57 | onWillPop: () async {
58 | return showDialog(
59 | context: context,
60 | builder: (context) => ShouldLeavePromptDialog(
61 | appTheme: appTheme,
62 | noLabel: localization.no,
63 | yesLabel: localization.yes,
64 | message: localization.promptLeaveApp,
65 | onYes: () {
66 | Navigator.of(context).pop(true);
67 | },
68 | onNo: () {
69 | Navigator.of(context).pop(false);
70 | }));
71 | },
72 | child: EntityObservingWidget(
73 | provider: (em) => em.getUniqueEntity(),
74 | blacklist: const [Localization],
75 | builder: (_, __) {
76 | final appTheme = widget.entityManager
77 | .getUniqueEntity()
78 | .get()
79 | .value;
80 |
81 | final isLandspace =
82 | MediaQuery.of(context).orientation == Orientation.landscape &&
83 | Platform.isAndroid;
84 |
85 | return GestureDetector(
86 | behavior: HitTestBehavior.translucent,
87 | onTap: () {
88 | final selected =
89 | widget.entityManager.group(any: [Selected]).entities;
90 | for (final e in selected) e.remove();
91 | },
92 | child: Scaffold(
93 | key: scaffoldKey,
94 | resizeToAvoidBottomInset: false,
95 | appBar: isLandspace
96 | ? null
97 | : AppBar(
98 | elevation: 4,
99 | brightness: appTheme.brightness,
100 | backgroundColor: appTheme.appBarColor,
101 | leading: Platform.isAndroid || Platform.isIOS
102 | ? IconButton(
103 | icon: Icon(
104 | AppIcons.menu,
105 | color: appTheme.tertiaryButtonColor,
106 | ),
107 | onPressed: () {
108 | scaffoldKey.currentState.openDrawer();
109 | },
110 | )
111 | : SizedBox(),
112 | automaticallyImplyLeading: false,
113 | bottom: PreferredSize(
114 | child: buildTabBar(appTheme, localization),
115 | preferredSize: Size(MediaQuery.of(context).size.width,
116 | kTextTabBarHeight),
117 | )),
118 | drawer: !isLandspace && (Platform.isAndroid || Platform.isIOS)
119 | ? buildDrawer(appTheme)
120 | : null,
121 | drawerDragStartBehavior: DragStartBehavior.down,
122 | drawerScrimColor: Colors.black54,
123 | body: buildScaffoldBody(appTheme)),
124 | );
125 | },
126 | ),
127 | );
128 | }
129 |
130 | Drawer buildDrawer(BaseTheme appTheme) {
131 | final localization = widget.entityManager
132 | .getUniqueEntity()
133 | .get();
134 |
135 | return Drawer(
136 | key: ValueKey('NotebulkDrawer'),
137 | child: Container(
138 | decoration: BoxDecoration(gradient: appTheme.backgroundGradient),
139 | child: ListView(
140 | shrinkWrap: true,
141 | children: [
142 | Container(
143 | height: MediaQuery.of(context).size.height * 0.25,
144 | width: MediaQuery.of(context).size.width,
145 | alignment: Alignment.center,
146 | child: Text(
147 | localization.settingsPageTitle,
148 | style: appTheme.appTitleTextStyle,
149 | ),
150 | ),
151 | SettingsPage(
152 | entityManager: widget.entityManager,
153 | ),
154 | /* Divider(),
155 | ListTile(
156 | title: Text(
157 | 'TAGS',
158 | style: appTheme.titleTextStyle,
159 | ),
160 | ),
161 | FlatButton.icon(
162 | materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
163 | icon: Icon(
164 | AppIcons.plus,
165 | color: appTheme.primaryButtonColor,
166 | ),
167 | label: Text(
168 | 'Criar tag',
169 | style: appTheme.actionableLabelStyle
170 | .copyWith(color: appTheme.primaryButtonColor),
171 | ),
172 | onPressed: () {
173 | widget.entityManager
174 | .setUnique(NavigationEvent.push(Routes.manageTags));
175 | },
176 | ),
177 | GroupObservingWidget(
178 | matcher: Matchers.tag,
179 | builder: (group, context) {
180 | final tags = group.entities.toList()
181 | ..sort((s1, s2) {
182 | final tag1 = s1.get().value;
183 | final tag2 = s1.get().value;
184 |
185 | return tag2.compareTo(tag1);
186 | });
187 |
188 | return ListView.builder(
189 | shrinkWrap: true,
190 | physics: NeverScrollableScrollPhysics(),
191 | itemCount: tags.length,
192 | itemBuilder: (context, index) => ListTile(
193 | title: Row(
194 | mainAxisSize: MainAxisSize.max,
195 | crossAxisAlignment: CrossAxisAlignment.start,
196 | children: [
197 | Icon(
198 | Icons.label,
199 | color: appTheme.subtitleTextStyle.color,
200 | ),
201 | SizedBox(
202 | width: 8,
203 | ),
204 | Text(
205 | tags[index].get().value,
206 | style: appTheme.subtitleTextStyle,
207 | ),
208 | ],
209 | ),
210 | ),
211 | );
212 | },
213 | ) */
214 | ],
215 | ),
216 | ),
217 | );
218 | }
219 |
220 | Widget buildScaffoldBody(BaseTheme appTheme) {
221 | final localization = widget.entityManager
222 | .getUniqueEntity()
223 | .get();
224 |
225 | return Stack(
226 | children: [
227 | SafeArea(
228 | maintainBottomViewPadding: false,
229 | bottom: true,
230 | child: Theme(
231 | data: ThemeData(accentColor: appTheme.primaryColor),
232 | child: Platform.isAndroid || Platform.isIOS
233 | ? PageView(
234 | dragStartBehavior: DragStartBehavior.down,
235 | controller: pageController,
236 | onPageChanged: (page) => widget.entityManager.setUnique(
237 | PageIndex(page,
238 | oldValue: widget.entityManager
239 | .getUnique()
240 | .value)),
241 | children: [
242 | NoteListPage(
243 | entityManager: widget.entityManager,
244 | key: ValueKey('NotesPage'),
245 | ),
246 | ReminderListPage(
247 | entityManager: widget.entityManager,
248 | key: ValueKey('EventsPage'),
249 | ),
250 | SearchPage(
251 | entityManager: widget.entityManager,
252 | key: ValueKey('SearchPage'),
253 | ),
254 | ArchivePage(
255 | entityManager: widget.entityManager,
256 | key: ValueKey('ArchivePage'),
257 | )
258 | ],
259 | )
260 | : EntityObservingWidget(
261 | provider: (em) => em.getUniqueEntity(),
262 | builder: (e, __) => [
263 | NoteListPage(
264 | entityManager: widget.entityManager,
265 | key: ValueKey('NotesPage'),
266 | ),
267 | ReminderListPage(
268 | entityManager: widget.entityManager,
269 | key: ValueKey('EventsPage'),
270 | ),
271 | SearchPage(
272 | entityManager: widget.entityManager,
273 | key: ValueKey('SearchPage'),
274 | ),
275 | ArchivePage(
276 | entityManager: widget.entityManager,
277 | key: ValueKey('ArchivePage'),
278 | ),
279 | SettingsPage(
280 | entityManager: widget.entityManager,
281 | key: ValueKey('SettingsPage'),
282 | )
283 | ][e.get().value],
284 | ),
285 | ),
286 | ),
287 | Positioned(
288 | bottom: 0,
289 | child: StatusBar(
290 | key: ValueKey('StatusBar'),
291 | actions: (index) => [
292 | if (index == 0)
293 | FlatButton(
294 | child: Text(
295 | localization.archiveActionLabel,
296 | style: appTheme.actionableLabelStyle,
297 | ),
298 | onPressed: () {
299 | widget.entityManager.setUnique(ArchiveNotesEvent());
300 | },
301 | )
302 | else if (index == 1 &&
303 | !widget.entityManager
304 | .group(any: [Selected])
305 | .entities
306 | .any((e) => e.hasT()))
307 | FlatButton(
308 | child: Text(localization.completeActionLabel),
309 | onPressed: () {
310 | widget.entityManager.setUnique(CompleteRemindersEvent());
311 | },
312 | )
313 | else if (index == 3)
314 | FlatButton(
315 | child: Text(localization.restoreActionLabel),
316 | onPressed: () {
317 | widget.entityManager.setUnique(RestoreNotesEvent());
318 | }),
319 | if (index != 3)
320 | FlatButton(
321 | child: Text(
322 | localization.deleteActionLabel,
323 | style: appTheme.actionableLabelStyle,
324 | ),
325 | onPressed: () {
326 | widget.entityManager.setUnique(DiscardSelectedEvent());
327 | },
328 | )
329 | ],
330 | ),
331 | )
332 | ],
333 | );
334 | }
335 |
336 | Widget buildTabBar(BaseTheme appTheme, Localization localization) {
337 | return AnimatableEntityObservingWidget(
338 | duration: duration,
339 | curve: curve,
340 | provider: (em) => em.getUniqueEntity(),
341 | startAnimating: false,
342 | tweens: {
343 | 'iconScale': Tween(begin: 0.0, end: 1.0),
344 | 'iconColor': ColorTween(
345 | begin: appTheme.otherTabItemColor,
346 | end: appTheme.selectedTabItemColor)
347 | },
348 | //animateUpdated: (_, __) => EntityAnimation.forward,
349 | builder: (pageEntity, animations, __) {
350 | final pageIndex = pageEntity.get().value;
351 |
352 | return TabBar(
353 | index: pageIndex,
354 | scaleIcon: animations['iconScale'],
355 | colorIcon: animations['iconColor'],
356 | appTheme: appTheme,
357 | prevIndex: pageEntity.get().oldValue,
358 | onTap: (index) async {
359 | if (index != pageIndex) {
360 | if (Platform.isAndroid || Platform.isIOS)
361 | await pageController.animateToPage(index,
362 | duration: duration, curve: curve);
363 |
364 | widget.entityManager
365 | .getUniqueEntity()
366 | .set(PageIndex(index, oldValue: pageIndex));
367 | }
368 | },
369 | items: [
370 | for (final label in localization.pageLabels) TabItem(label: label),
371 | if (Platform.isWindows || Platform.isLinux || Platform.isFuchsia)
372 | TabItem(label: localization.settingsPageTitle)
373 | ],
374 | );
375 | },
376 | );
377 | }
378 | }
379 |
--------------------------------------------------------------------------------
/lib/pages/archivePage.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:notebulk/ecs/ecs.dart';
3 | import 'package:notebulk/util.dart';
4 | import 'package:notebulk/widgets/cards.dart';
5 | import 'package:notebulk/widgets/util.dart';
6 |
7 | class ArchivePage extends StatelessWidget {
8 | const ArchivePage({Key key, this.entityManager}) : super(key: key);
9 |
10 | final EntityManager entityManager;
11 |
12 | @override
13 | Widget build(BuildContext context) {
14 | final localization =
15 | entityManager.getUniqueEntity().get();
16 | final appTheme =
17 | entityManager.getUniqueEntity().get().value;
18 |
19 | return GroupObservingWidget(
20 | matcher: Matchers.archived,
21 | builder: (group, context) {
22 | final notesList = group.entities;
23 | final notesByDate = >{};
24 |
25 | for (final note in notesList) {
26 | final date = note.get().value;
27 | final simplifiedDate = DateTime.utc(date.year, date.month);
28 | if (notesByDate[simplifiedDate] == null) {
29 | notesByDate[simplifiedDate] = [];
30 | }
31 | notesByDate[simplifiedDate].add(note);
32 | }
33 |
34 | if (notesByDate.isEmpty) {
35 | notesByDate[DateTime.now()] = [];
36 | }
37 |
38 | return Stack(
39 | children: [
40 | if (notesList.isEmpty)
41 | Center(
42 | child: RichText(
43 | textWidthBasis: TextWidthBasis.longestLine,
44 | text: TextSpan(
45 | text: '${localization.emptyArchiveHintTitle}\n',
46 | style: appTheme.titleTextStyle,
47 | children: [
48 | TextSpan(
49 | text: localization.emptyArchiveHintSubtitle,
50 | style: appTheme.subtitleTextStyle)
51 | ]),
52 | ),
53 | )
54 | else
55 | ListView(
56 | primary: true,
57 | physics: BouncingScrollPhysics(),
58 | key: PageStorageKey('archivedScroll'),
59 | children: [
60 | for (final noteGroup in notesByDate.entries) ...[
61 | if (noteGroup.value.isNotEmpty)
62 | CheckboxListTile(
63 | dense: true,
64 | activeColor: appTheme.primaryButtonColor,
65 | checkColor: appTheme.buttonIconColor,
66 | controlAffinity: ListTileControlAffinity.leading,
67 | title: Text(
68 | formatTimestamp(noteGroup.key, localization,
69 | includeDay: false, includeWeekDay: false),
70 | style: appTheme.titleTextStyle,
71 | ),
72 | value: noteGroup.value
73 | .where((e) => e.hasT())
74 | .length ==
75 | noteGroup.value.length,
76 | onChanged: (value) {
77 | if (value) {
78 | for (final note in noteGroup.value)
79 | note.set(Selected());
80 | } else {
81 | for (final note in noteGroup.value)
82 | note.remove();
83 | }
84 | },
85 | ),
86 | buildNotesGridView(
87 | noteGroup.value,
88 | buildNoteCard,
89 | ),
90 | ]
91 | ],
92 | ),
93 | ],
94 | );
95 | });
96 | }
97 |
98 | Widget buildNoteCard(Entity note) {
99 | return InkWell(
100 | onLongPress: () => toggleSelected(note),
101 | onTap: () {
102 | // Handle only selection since archived notes can't be edited
103 | if (entityManager.getUniqueEntity().hasT())
104 | toggleSelected(note);
105 | },
106 | child: NoteCardWidget(
107 | noteEntity: note,
108 | ),
109 | );
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/lib/pages/noteListPage.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:kt_dart/kt.dart';
3 | import 'package:notebulk/ecs/ecs.dart';
4 | import 'package:notebulk/theme.dart';
5 | import 'package:notebulk/util.dart';
6 | import 'package:notebulk/widgets/cards.dart';
7 | import 'package:notebulk/widgets/util.dart';
8 |
9 | class NoteListPage extends StatelessWidget {
10 | const NoteListPage({Key key, this.entityManager}) : super(key: key);
11 |
12 | final EntityManager entityManager;
13 |
14 | @override
15 | Widget build(BuildContext context) {
16 | final localization =
17 | entityManager.getUniqueEntity().get();
18 | final appTheme =
19 | entityManager.getUniqueEntity().get().value;
20 |
21 | return GroupObservingWidget(
22 | matcher: Matchers.note,
23 | builder: (group, context) {
24 | // Sort notes by creation/edited date
25 | final notesList = KtList.from(group.entities)
26 | .sortedByDescending((e) => e.get().value)
27 | .asList();
28 | // Group by date, only month and year
29 | final notesByDate = >{};
30 |
31 | for (final note in notesList) {
32 | final date = note.get().value;
33 | final simplifiedDate = DateTime.utc(date.year, date.month);
34 | if (notesByDate[simplifiedDate] == null) {
35 | notesByDate[simplifiedDate] = [];
36 | }
37 | notesByDate[simplifiedDate].add(note);
38 | }
39 |
40 | return Stack(
41 | children: [
42 | if (notesList.isEmpty)
43 | buildEmptyPage(localization, appTheme) // Render empty state
44 | else
45 | ListView(
46 | primary: true,
47 | shrinkWrap: false,
48 | physics: BouncingScrollPhysics(),
49 | children: [
50 | if (notesList.isNotEmpty)
51 | for (final noteGroup in notesByDate.entries) ...[
52 | if (noteGroup.value.isNotEmpty)
53 | Theme(
54 | data: ThemeData(
55 | brightness: appTheme
56 | .brightness), // Make checkbox match brightness
57 | child: CheckboxListTile(
58 | dense: true,
59 | checkColor: appTheme.buttonIconColor,
60 | activeColor: appTheme.primaryButtonColor,
61 | controlAffinity:
62 | ListTileControlAffinity.leading,
63 | title: Text(
64 | formatTimestamp(noteGroup.key, localization,
65 | includeDay: false, includeWeekDay: false),
66 | style: appTheme.titleTextStyle,
67 | ),
68 | value: noteGroup.value
69 | .where((e) => e.hasT())
70 | .length ==
71 | noteGroup.value
72 | .length, // Inform that all notes for that month are selected
73 | onChanged: (value) {
74 | // Shortcut to select or deselect all associated notes
75 | if (value) {
76 | for (final note in noteGroup.value)
77 | note.set(Selected());
78 | } else {
79 | for (final note in noteGroup.value)
80 | note.remove();
81 | }
82 | },
83 | )),
84 | buildNotesGridView(noteGroup.value, buildNoteCard)
85 | ]
86 | ],
87 | ),
88 | Positioned(
89 | bottom: 8,
90 | right: 8,
91 | child: FloatingActionButton(
92 | child: Icon(
93 | AppIcons.pencil,
94 | color: appTheme.buttonIconColor,
95 | ),
96 | backgroundColor: appTheme.primaryButtonColor,
97 | onPressed: () {
98 | // Open note form in creation mode
99 | entityManager
100 | .setUnique(NavigationEvent.push(Routes.createNote));
101 | },
102 | ),
103 | ),
104 | ],
105 | );
106 | });
107 | }
108 |
109 | Widget buildEmptyPage(Localization localization, BaseTheme appTheme) {
110 | return Center(
111 | child: RichText(
112 | text: TextSpan(
113 | text: '${localization.emptyNoteHintTitle}\n',
114 | style: appTheme.titleTextStyle,
115 | children: [
116 | TextSpan(
117 | text: localization.emptyNoteHintSubtitle,
118 | style: appTheme.subtitleTextStyle)
119 | ]),
120 | ),
121 | );
122 | }
123 |
124 | Widget buildNoteCard(Entity note) {
125 | return InkWell(
126 | onLongPress: () => toggleSelected(note),
127 | onTap: () {
128 | // If there's already a selection a single tap should select the note
129 | if (entityManager.getUniqueEntity().hasT())
130 | toggleSelected(note);
131 | else {
132 | // If nothing is selected open note form for editing instead
133 | entityManager
134 | ..setUniqueOnEntity(FeatureEntityTag(),
135 | note) // Mark this note as the one being edited
136 | ..setUnique(NavigationEvent.push(Routes.editNote));
137 | }
138 | },
139 | child: NoteCardWidget(
140 | noteEntity: note,
141 | ),
142 | );
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/lib/pages/pages.dart:
--------------------------------------------------------------------------------
1 | export 'package:notebulk/pages/splashScreenPage.dart';
2 | export 'package:notebulk/pages/noteListPage.dart';
3 | export 'package:notebulk/pages/reminderListPage.dart';
4 | export 'package:notebulk/pages/searchPage.dart';
5 | export 'package:notebulk/pages/archivePage.dart';
6 | export 'package:notebulk/pages/settingsPage.dart';
7 |
--------------------------------------------------------------------------------
/lib/pages/reminderListPage.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:kt_dart/kt.dart';
3 | import 'package:notebulk/ecs/ecs.dart';
4 | import 'package:notebulk/theme.dart';
5 | import 'package:notebulk/util.dart';
6 | import 'package:notebulk/widgets/cards.dart';
7 | import 'package:notebulk/widgets/util.dart';
8 |
9 | class ReminderListPage extends StatelessWidget {
10 | const ReminderListPage({Key key, this.entityManager}) : super(key: key);
11 |
12 | final EntityManager entityManager;
13 |
14 | @override
15 | Widget build(BuildContext context) {
16 | final localization =
17 | entityManager.getUniqueEntity().get();
18 | final appTheme =
19 | entityManager.getUniqueEntity().get().value;
20 |
21 | return GroupObservingWidget(
22 | matcher: Matchers.reminder,
23 | builder: (group, context) {
24 | final reminderList = group.entities;
25 | var currentReminders = [];
26 | var lateReminders = [];
27 | var completedReminders = [];
28 | final today = DateTime.now();
29 | final currentDay = today.day;
30 | final currentMonth = today.month;
31 | final currentYear = today.year;
32 | final currentDate =
33 | DateTime.utc(currentYear, currentMonth, currentDay);
34 |
35 | // Sort and group reminders into current, late and completed
36 | for (final reminder in reminderList) {
37 | final reminderDueDate = reminder.get().value;
38 |
39 | if (reminder.hasT()) {
40 | completedReminders.add(reminder);
41 | continue;
42 | }
43 |
44 | if (currentDate.isBefore(reminderDueDate)) {
45 | currentReminders.add(reminder);
46 | } else {
47 | lateReminders.add(reminder);
48 | }
49 | }
50 |
51 | currentReminders = KtList.from(currentReminders)
52 | .sortedByDescending((e) => e.get().value)
53 | .asList();
54 |
55 | lateReminders = KtList.from(lateReminders)
56 | .sortedByDescending((e) => e.get().value)
57 | .asList();
58 |
59 | completedReminders = KtList.from(completedReminders)
60 | .sortedByDescending((e) => e.get().value)
61 | .asList();
62 |
63 | return Stack(
64 | children: [
65 | ListView(
66 | primary: true,
67 | physics: BouncingScrollPhysics(),
68 | key: PageStorageKey('eventsScroll'),
69 | children: [
70 | if (currentReminders.isNotEmpty) ...[
71 | ListTile(
72 | title: Text(
73 | localization.currentRemindersLabel,
74 | style: appTheme.titleTextStyle,
75 | ),
76 | ),
77 | buildNotesGridView(currentReminders, buildReminderCard)
78 | ] else
79 | ListTile(
80 | title: Text(
81 | localization.currentRemindersLabel,
82 | style: appTheme.titleTextStyle,
83 | ),
84 | subtitle: Text(localization.currentRemindersEmpty,
85 | style: appTheme.subtitleTextStyle),
86 | ),
87 | Divider(),
88 | if (lateReminders.isNotEmpty) ...[
89 | ListTile(
90 | title: Text(
91 | localization.lateRemindersLabel,
92 | style: appTheme.titleTextStyle,
93 | ),
94 | ),
95 | buildNotesGridView(lateReminders, buildReminderCard)
96 | ] else
97 | ListTile(
98 | title: Text(
99 | localization.lateRemindersLabel,
100 | style: appTheme.titleTextStyle,
101 | ),
102 | subtitle: Text(localization.lateRemindersEmpty,
103 | style: appTheme.subtitleTextStyle),
104 | ),
105 | Divider(),
106 | if (completedReminders.isNotEmpty) ...[
107 | ListTile(
108 | title: Text(
109 | localization.completedRemindersLabel,
110 | style: appTheme.titleTextStyle,
111 | ),
112 | ),
113 | buildNotesGridView(completedReminders, buildReminderCard)
114 | ] else
115 | ListTile(
116 | title: Text(
117 | localization.completedRemindersLabel,
118 | style: appTheme.titleTextStyle,
119 | ),
120 | subtitle: Text(localization.completedRemindersEmpty,
121 | style: appTheme.subtitleTextStyle),
122 | )
123 | ],
124 | ),
125 | Positioned(
126 | bottom: 8,
127 | right: 8,
128 | child: FloatingActionButton(
129 | child: Icon(
130 | AppIcons.calendar,
131 | color: appTheme.buttonIconColor,
132 | ),
133 | backgroundColor: appTheme.primaryButtonColor,
134 | onPressed: () {
135 | // Open form to create a reminder
136 | entityManager
137 | .setUnique(NavigationEvent.push(Routes.createReminder));
138 | },
139 | ),
140 | )
141 | ],
142 | );
143 | });
144 | }
145 |
146 | Widget buildReminderCard(Entity reminder) {
147 | return InkWell(
148 | onLongPress: () {
149 | toggleSelected(reminder);
150 | },
151 | onTap: () {
152 | if (entityManager.getUniqueEntity().hasT()) {
153 | toggleSelected(reminder);
154 | } else {
155 | // Completed reminders can't be edited, so check first
156 | if (!reminder.hasT())
157 | entityManager
158 | ..setUniqueOnEntity(FeatureEntityTag(), reminder)
159 | ..setUnique(NavigationEvent.push(Routes.editReminder));
160 | }
161 | },
162 | child: ReminderCardWidget(
163 | reminderEntity: reminder,
164 | ),
165 | );
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/lib/pages/searchPage.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:notebulk/ecs/ecs.dart';
3 | import 'package:notebulk/theme.dart';
4 | import 'package:notebulk/util.dart';
5 | import 'package:notebulk/widgets/cards.dart';
6 | import 'package:notebulk/widgets/util.dart';
7 |
8 | class SearchPage extends StatelessWidget {
9 | const SearchPage({Key key, this.entityManager}) : super(key: key);
10 |
11 | final EntityManager entityManager;
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | final localization =
16 | entityManager.getUniqueEntity().get();
17 | final appTheme =
18 | entityManager.getUniqueEntity().get().value;
19 |
20 | return Padding(
21 | padding: EdgeInsets.all(16),
22 | child: ListView(
23 | primary: true,
24 | physics: BouncingScrollPhysics(),
25 | key: PageStorageKey('searchScroll'),
26 | children: [
27 | buildSearchField(localization, appTheme),
28 | SizedBox(
29 | height: 16,
30 | ),
31 | GroupObservingWidget(
32 | matcher: Matchers.searchResult,
33 | builder: (group, context) {
34 | final notesList = group.entities;
35 | final term = entityManager
36 | .getUniqueEntity()
37 | .get()
38 | ?.value;
39 |
40 | // Nothing to show
41 | if (term == null || term.isEmpty) return SizedBox();
42 |
43 | // No results so show hint as empty state
44 | if (notesList.isEmpty)
45 | return Center(
46 | child: RichText(
47 | text: TextSpan(
48 | text: '${localization.emptySearchHintTitle}\n',
49 | style: appTheme.titleTextStyle,
50 | children: [
51 | TextSpan(
52 | text: localization.emptySearchHintSubTitle,
53 | style: appTheme.subtitleTextStyle)
54 | ]),
55 | ),
56 | );
57 |
58 | return buildNotesGridView(notesList, buildNoteCard);
59 | },
60 | ),
61 | ],
62 | ),
63 | );
64 | }
65 |
66 | TextFormField buildSearchField(
67 | Localization localization, BaseTheme appTheme) {
68 | return TextFormField(
69 | key: ValueKey('SearchBar'),
70 | cursorColor: appTheme.primaryButtonColor,
71 | style: appTheme.biggerBodyTextStyle,
72 | decoration: InputDecoration(
73 | filled: true,
74 | fillColor: appTheme.selectedTabItemColor.withOpacity(0.3),
75 | contentPadding:
76 | const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
77 | border: OutlineInputBorder(
78 | borderSide: BorderSide.none,
79 | borderRadius: BorderRadius.circular(10),
80 | gapPadding: 0),
81 | hintText: localization.searchNotesHint,
82 | hintStyle: appTheme.subtitleTextStyle,
83 | prefixIcon: Icon(
84 | AppIcons.search,
85 | color: appTheme.primaryButtonColor,
86 | )),
87 | initialValue: entityManager
88 | .getUniqueEntity()
89 | .get()
90 | ?.value,
91 | onChanged: (value) {
92 | // Update search terms and tick but don't trigger a event yet
93 | entityManager.getUniqueEntity()
94 | ..set(SearchTerm(value))
95 | ..set(entityManager.getUniqueEntity().get());
96 | },
97 | onFieldSubmitted: (_) {
98 | // User submitted, trigger event now
99 | return entityManager.setUnique(PerformSearchEvent());
100 | },
101 | );
102 | }
103 |
104 | Widget buildNoteCard(Entity note) {
105 | return InkWell(
106 | onTap: () {
107 | // Open form to edit a found note
108 | entityManager
109 | ..setUniqueOnEntity(FeatureEntityTag(), note)
110 | ..setUnique(NavigationEvent.push(Routes.editNote));
111 | },
112 | child: NoteCardWidget(
113 | noteEntity: note,
114 | ),
115 | );
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/lib/pages/settingsPage.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:notebulk/ecs/ecs.dart';
3 | import 'package:notebulk/theme.dart';
4 | import 'package:notebulk/util.dart';
5 |
6 | class SettingsPage extends StatelessWidget {
7 | const SettingsPage({Key key, this.entityManager}) : super(key: key);
8 |
9 | final EntityManager entityManager;
10 |
11 | @override
12 | Widget build(BuildContext context) {
13 | final localization =
14 | entityManager.getUniqueEntity().get();
15 | final appTheme =
16 | entityManager.getUniqueEntity().get().value;
17 |
18 | return SingleChildScrollView(
19 | child: Column(
20 | mainAxisAlignment: MainAxisAlignment.start,
21 | children: [
22 | SwitchListTile(
23 | dense: true,
24 | activeColor: appTheme.primaryButtonColor,
25 | title: Text(
26 | localization.settingsDarkModeLabel,
27 | style: appTheme.titleTextStyle,
28 | ),
29 | value: appTheme.brightness == Brightness.dark,
30 | onChanged: (enabled) {
31 | // Toggle dark mode and persist the changes
32 | entityManager
33 | .getUniqueEntity()
34 | .set(AppTheme(enabled ? DarkTheme() : LightTheme()));
35 | entityManager.setUnique(PersistUserSettingsEvent());
36 | }),
37 | Divider(),
38 | ListTile(
39 | dense: true,
40 | title: Text(
41 | 'Backup',
42 | style: appTheme.titleTextStyle,
43 | ),
44 | ),
45 | FlatButton.icon(
46 | icon: Icon(
47 | AppIcons.download,
48 | color: appTheme.buttonIconColor,
49 | ),
50 | color: appTheme.primaryButtonColor,
51 | textColor: appTheme.buttonIconColor,
52 | label: Text(
53 | localization.settingsExportLabel,
54 | style: appTheme.actionableLabelStyle
55 | .copyWith(color: appTheme.buttonLabelColor),
56 | ),
57 | onPressed: () {
58 | // Trigger the event to export database backup
59 | entityManager.setUnique(ExportBackupEvent());
60 | },
61 | ),
62 | Text('WIP: Doesn\'t handle duplicates'),
63 | FlatButton.icon(
64 | icon: Icon(AppIcons.upload, color: appTheme.buttonIconColor),
65 | color: appTheme.primaryButtonColor,
66 | textColor: appTheme.buttonLabelColor,
67 | label: Text(
68 | localization.settingsImportLabel,
69 | style: appTheme.actionableLabelStyle
70 | .copyWith(color: appTheme.buttonLabelColor),
71 | ),
72 | onPressed: () {
73 | // Trigger the event to import database backup
74 | entityManager.setUnique(ImportBackupEvent());
75 | },
76 | ),
77 | SizedBox(
78 | height: 16,
79 | ),
80 | ],
81 | ),
82 | );
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/lib/pages/splashScreenPage.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:notebulk/theme.dart';
3 | import 'package:notebulk/widgets/util.dart';
4 |
5 | // Shown while the app is loading it's data
6 | class SplashScreenPage extends StatelessWidget {
7 | @override
8 | Widget build(BuildContext context) {
9 | return GradientBackground(
10 | appTheme: DarkTheme(),
11 | child: SizedBox.expand(
12 | child: Column(
13 | mainAxisAlignment: MainAxisAlignment.center,
14 | crossAxisAlignment: CrossAxisAlignment.center,
15 | children: [
16 | Image.asset(
17 | 'assets/icon-adaptive.png',
18 | fit: BoxFit.contain,
19 | height: MediaQuery.of(context).size.height * 0.3,
20 | width: MediaQuery.of(context).size.height * 0.3,
21 | ),
22 | Material(
23 | child: Text('Notebulk',
24 | style: TextStyle(
25 | fontSize: 56,
26 | fontWeight: FontWeight.w700,
27 | fontFamily: 'PalanquinDark',
28 | color: Colors.white)),
29 | ),
30 | ],
31 | ),
32 | ),
33 | );
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/lib/theme.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | export 'icons.dart';
3 |
4 | mixin _FontWeight {
5 | static FontWeight normal = FontWeight.w400;
6 | static FontWeight medium = FontWeight.w500;
7 | static FontWeight bold = FontWeight.w700;
8 | }
9 |
10 | abstract class BaseTheme {
11 | Brightness get brightness;
12 | Color get primaryColor;
13 | Color get accentColor;
14 | Color get appTitleColor;
15 | Color get appBarColor;
16 | Color get selectedTabItemColor;
17 | Color get otherTabItemColor;
18 | Color get settingsContainerColor;
19 | Color get settingsLabelContainerColor;
20 | Color get primaryButtonColor;
21 | Color get secondaryButtonColor;
22 | Color get tertiaryButtonColor;
23 | Color get buttonIconColor;
24 | Color get buttonLabelColor;
25 | List reminderPriorityColors = [
26 | Colors.blue,
27 | Colors.green,
28 | Colors.yellow,
29 | Colors.orange,
30 | Colors.red,
31 | Colors.black
32 | ];
33 | LinearGradient get backgroundGradient;
34 | Color get textFieldFillColor;
35 |
36 | static Color grey = Colors.grey;
37 | static Color lightGrey = Colors.grey.shade600;
38 | static Color darkGrey = Colors.grey.shade400;
39 | static Color darkestGrey = Color.fromRGBO(15, 15, 15, 1.0);
40 |
41 | TextStyle get baseStyle;
42 |
43 | TextStyle get appTitleTextStyle => TextStyle(
44 | color: appTitleColor,
45 | fontSize: 24,
46 | fontFamily: 'PalanquinDark',
47 | fontWeight: _FontWeight.bold);
48 |
49 | TextStyle get bodyTextStyle =>
50 | baseStyle.copyWith(fontSize: 12, fontWeight: _FontWeight.normal);
51 |
52 | TextStyle get biggerBodyTextStyle =>
53 | baseStyle.copyWith(fontSize: 14, fontWeight: _FontWeight.medium);
54 |
55 | TextStyle get titleTextStyle =>
56 | baseStyle.copyWith(fontSize: 16, fontWeight: _FontWeight.bold);
57 |
58 | TextStyle get subtitleTextStyle => baseStyle.copyWith(
59 | fontSize: 14, color: lightGrey, fontWeight: _FontWeight.medium);
60 |
61 | TextStyle get cardWidgetContentsTyle => bodyTextStyle;
62 |
63 | TextStyle get cardWidgetTodoItemStyle => bodyTextStyle;
64 |
65 | TextStyle get cardWidgetTagStyle => bodyTextStyle.copyWith(
66 | fontWeight: _FontWeight.medium, color: primaryButtonColor);
67 |
68 | TextStyle get cardWidgetTimestampStyle => bodyTextStyle.copyWith(
69 | color: brightness == Brightness.light ? lightGrey : darkGrey,
70 | fontWeight: _FontWeight.bold);
71 |
72 | TextStyle get formLabelStyle => biggerBodyTextStyle.copyWith(
73 | color: darkGrey,
74 | );
75 |
76 | TextStyle get actionableLabelStyle => subtitleTextStyle.copyWith(
77 | fontWeight: _FontWeight.bold, color: baseStyle.color);
78 | }
79 |
80 | class LightTheme extends BaseTheme {
81 | @override
82 | Color get primaryColor => Color(0xFFF6D365);
83 | @override
84 | Color get accentColor => Color(0xFFC83660);
85 | @override
86 | Brightness get brightness => Brightness.light;
87 | @override
88 | Color get appBarColor => Colors.white;
89 | @override
90 | Color get appTitleColor => Colors.black;
91 |
92 | @override
93 | LinearGradient get backgroundGradient => LinearGradient(
94 | colors: [Colors.white, primaryColor],
95 | begin: Alignment(0, -0.4),
96 | end: Alignment.bottomCenter,
97 | );
98 |
99 | @override
100 | Color get buttonIconColor => Colors.white;
101 |
102 | @override
103 | Color get buttonLabelColor => Colors.white;
104 |
105 | @override
106 | Color get otherTabItemColor => BaseTheme.lightGrey;
107 |
108 | @override
109 | Color get primaryButtonColor => accentColor;
110 |
111 | @override
112 | Color get secondaryButtonColor => BaseTheme.darkGrey;
113 |
114 | @override
115 | Color get selectedTabItemColor => accentColor;
116 |
117 | @override
118 | Color get settingsContainerColor => Colors.white;
119 |
120 | @override
121 | Color get settingsLabelContainerColor => Colors.blue;
122 |
123 | @override
124 | Color get tertiaryButtonColor => Colors.black;
125 |
126 | @override
127 | Color get textFieldFillColor => BaseTheme.lightGrey;
128 |
129 | @override
130 | TextStyle get formLabelStyle =>
131 | super.formLabelStyle.copyWith(color: baseStyle.color.withOpacity(0.75));
132 |
133 | @override
134 | TextStyle get baseStyle =>
135 | TextStyle(fontFamily: 'Palanquin', color: Colors.black, height: 1.2);
136 | }
137 |
138 | class DarkTheme extends BaseTheme {
139 | @override
140 | Color get primaryColor => Color(0xFFC83660);
141 | @override
142 | Color get accentColor => Color(0xFFF6D365);
143 | @override
144 | Brightness get brightness => Brightness.dark;
145 | @override
146 | Color get appBarColor => BaseTheme.darkestGrey;
147 | @override
148 | Color get appTitleColor => Colors.white;
149 |
150 | @override
151 | LinearGradient get backgroundGradient => LinearGradient(
152 | colors: [BaseTheme.darkestGrey, primaryColor],
153 | begin: Alignment(0, -0.4),
154 | end: Alignment.bottomCenter,
155 | );
156 |
157 | @override
158 | Color get buttonIconColor => Colors.black;
159 |
160 | @override
161 | Color get buttonLabelColor => Colors.black;
162 |
163 | @override
164 | Color get otherTabItemColor => BaseTheme.darkGrey;
165 |
166 | @override
167 | Color get primaryButtonColor => accentColor;
168 |
169 | @override
170 | Color get secondaryButtonColor => BaseTheme.darkGrey;
171 |
172 | @override
173 | Color get selectedTabItemColor => primaryColor;
174 |
175 | @override
176 | Color get settingsContainerColor => BaseTheme.darkestGrey;
177 |
178 | @override
179 | Color get settingsLabelContainerColor => primaryColor;
180 |
181 | @override
182 | Color get tertiaryButtonColor => Colors.white;
183 |
184 | @override
185 | Color get textFieldFillColor => BaseTheme.lightGrey;
186 |
187 | @override
188 | TextStyle get baseStyle =>
189 | TextStyle(fontFamily: 'Palanquin', color: Colors.white, height: 1.2);
190 |
191 | @override
192 | TextStyle get subtitleTextStyle =>
193 | super.subtitleTextStyle.copyWith(color: BaseTheme.darkGrey);
194 | }
195 |
196 | // Placeholder theme to properly display SplashScreen
197 | // TODO: Get rid of this hack
198 | class BlankTheme extends DarkTheme {
199 | @override
200 | LinearGradient get backgroundGradient =>
201 | LinearGradient(colors: [Colors.black, Colors.black]);
202 | }
203 |
--------------------------------------------------------------------------------
/lib/util.dart:
--------------------------------------------------------------------------------
1 | import 'package:entitas_ff/entitas_ff.dart';
2 | import 'package:flutter/foundation.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | // Format timestamp to match the app's aesthetic
6 | String formatTimestamp(DateTime timestamp, Localization localization,
7 | {bool includeDay = true, bool includeWeekDay = true}) {
8 | final day = timestamp.day;
9 | final weekDay = timestamp.weekday;
10 | final month = timestamp.month;
11 | final year = timestamp.year;
12 | final buffer = StringBuffer();
13 |
14 | if (includeWeekDay) {
15 | buffer.write('${localization.dayNames[weekDay - 1]}. ');
16 | }
17 |
18 | buffer.write('${localization.monthNames[month - 1]} ');
19 |
20 | if (includeDay) {
21 | buffer.write('$day, ');
22 | }
23 |
24 | buffer.write(year);
25 |
26 | return buffer.toString();
27 | }
28 |
29 | // Localization data, currently only Pt-Br and EN-US
30 | class Localization extends Component {
31 | final String emptyNoteHintTitle;
32 | final String emptyNoteHintSubtitle;
33 | final String emptyArchiveHintTitle;
34 | final String emptyArchiveHintSubtitle;
35 | final String searchNotesHint;
36 | final String emptySearchHintTitle;
37 | final String emptySearchHintSubTitle;
38 | final String settingsPageTitle;
39 | final String settingsColorLabel;
40 | final String settingsDarkModeLabel;
41 | final String completedRemindersLabel;
42 | final String settingsExportLabel;
43 | final String settingsImportLabel;
44 | final String createNoteFeatureTitle;
45 | final String createEventFeatureTitle;
46 | final String editNoteFeatureTitle;
47 | final String editEventFeatureTitle;
48 | final String saveChangesFeatureLabel;
49 | final String featureContentsLabel;
50 | final String featureContentsError;
51 | final String featureContentsHint;
52 | final String featureTodoHint;
53 | final String featureTagsHint;
54 | final String featureImageLabel;
55 | final String featureImageCamera;
56 | final String featureImageGallery;
57 | final String featureTodoLabel;
58 | final String featureReminderHint;
59 | final String featurePriorityLabel;
60 | final String lateRemindersLabel;
61 | final String featureTagsLabel;
62 | final String currentRemindersLabel;
63 | final String archiveActionLabel;
64 | final String restoreActionLabel;
65 | final String deleteActionLabel;
66 | final String completeActionLabel;
67 | final String hideActionLabel;
68 | final List dayNames;
69 | final List monthNames;
70 | final String importedAlert;
71 | final String exportedAlert;
72 | final List pageLabels;
73 | final String yes;
74 | final String no;
75 | final String cancel;
76 | final String accept;
77 | final String promptLeaveApp;
78 | final String promptLeaveUnsaved;
79 | final String currentRemindersEmpty,
80 | lateRemindersEmpty,
81 | completedRemindersEmpty;
82 |
83 | final String selectedLabel;
84 |
85 | factory Localization.en() => Localization._(
86 | yes: 'Yes',
87 | no: 'No',
88 | cancel: 'Cancel',
89 | accept: 'Accept',
90 | promptLeaveApp: 'Wanna close the app?',
91 | promptLeaveUnsaved:
92 | 'You have changes pending to be saved. Leave without makings changes?',
93 | settingsPageTitle: 'Settings',
94 | createNoteFeatureTitle: 'New note',
95 | createEventFeatureTitle: 'New reminder',
96 | editNoteFeatureTitle: 'Edit note',
97 | editEventFeatureTitle: 'Edit reminder',
98 | emptyNoteHintTitle: "You don't have any notes right now.",
99 | emptyNoteHintSubtitle: 'Create one using the button bellow.',
100 | emptyArchiveHintTitle: 'Currently empty',
101 | emptyArchiveHintSubtitle:
102 | 'Your archived notes will end up here and can be later restored.',
103 | emptySearchHintTitle: "Your search didn't find anything.",
104 | emptySearchHintSubTitle:
105 | 'Try other terms or fix spelling errors if any.',
106 | searchNotesHint: 'Search for multiple tags ie: Work important etc.',
107 | featureContentsHint: 'What is this note about?',
108 | featureContentsLabel: 'Contents',
109 | featureContentsError: "Can't be empty",
110 | currentRemindersEmpty: 'Nothing happening right now',
111 | lateRemindersEmpty: 'Everything\'s in order!',
112 | completedRemindersEmpty:
113 | 'Remember to complete or update your late reminders.',
114 | featureTagsHint: 'Ie: Work, Important, etc.',
115 | featureTodoHint:
116 | 'Make lists by breaking the line and mark as checked by inserting * in the end of an item ie: Buy rice*',
117 | featureImageCamera: 'Take a picture',
118 | featureImageGallery: 'Open gallery',
119 | featureImageLabel: 'Picture',
120 | currentRemindersLabel: 'Current',
121 | featureTagsLabel: 'Tags',
122 | lateRemindersLabel: 'Late',
123 | featureTodoLabel: 'Todo list',
124 | featurePriorityLabel: 'Priority',
125 | featureReminderHint: 'What you want to remember?',
126 | saveChangesFeatureLabel: 'Save',
127 | settingsColorLabel: 'Theme color',
128 | settingsDarkModeLabel: 'Dark mode',
129 | completedRemindersLabel: 'Completed',
130 | settingsExportLabel: 'Export notes',
131 | settingsImportLabel: 'Import notes',
132 | archiveActionLabel: 'Archive',
133 | restoreActionLabel: 'Restore',
134 | deleteActionLabel: 'Delete',
135 | hideActionLabel: 'Hide',
136 | completeActionLabel: 'Complete',
137 | importedAlert: 'Backup restored succesfuly!',
138 | exportedAlert: 'Backup exported to SD card',
139 | selectedLabel: 'Apply to selection:',
140 | pageLabels: const [
141 | 'My notes',
142 | 'Reminders',
143 | 'Search',
144 | 'Archive'
145 | ],
146 | dayNames: const [
147 | 'MON',
148 | 'TUE',
149 | 'WED',
150 | 'THU',
151 | 'FRI',
152 | 'SAT',
153 | 'SUN'
154 | ],
155 | monthNames: const [
156 | 'JANUARY',
157 | 'FEBRUARY',
158 | 'MARCH',
159 | 'APRIL',
160 | 'MAY',
161 | 'JUNE',
162 | 'JULY',
163 | 'AUGUST',
164 | 'SEPTEMBER',
165 | 'OCTOBER',
166 | 'NOVEMBER',
167 | 'DECEMBER'
168 | ]);
169 |
170 | factory Localization.ptBR() => Localization._(
171 | yes: 'Sim',
172 | no: 'Não',
173 | cancel: 'Cancelar',
174 | accept: 'Aceitar',
175 | promptLeaveApp: 'Deseja sair do app?',
176 | promptLeaveUnsaved:
177 | 'Você tem alterações pendentes à salvar. Quer Sair sem salvar as alterações?',
178 | emptySearchHintSubTitle:
179 | 'Tente outros termos ou procure por erros de digitação.',
180 | emptyArchiveHintSubtitle: 'Suas notas arquivadas irão aparecer aqui.',
181 | emptyNoteHintSubtitle: 'Crie uma usando o botão no canto inferior.',
182 | settingsPageTitle: 'Configurações',
183 | createNoteFeatureTitle: 'Criar nota',
184 | createEventFeatureTitle: 'Criar lembrete',
185 | editNoteFeatureTitle: 'Editar nota',
186 | editEventFeatureTitle: 'Editar lembrete',
187 | emptyNoteHintTitle: 'Você não possui nenhuma nota no momento',
188 | emptyArchiveHintTitle: 'Nenhuma nota arquivada',
189 | emptySearchHintTitle: 'Sua pesquisa não retornou resultados',
190 | searchNotesHint: 'Ex: Trabalho importante etc.',
191 | featureContentsHint: 'Sobre o que é essa nota?',
192 | featureContentsLabel: 'Conteúdo da nota',
193 | featureContentsError: 'Não pode ficar vazio.',
194 | currentRemindersEmpty: 'Nada acontecendo no momento',
195 | lateRemindersEmpty: 'Tudo em ordem!',
196 | completedRemindersEmpty:
197 | 'Não esqueça de completar ou atualizar os lembretes que atrasarem.',
198 | featureTagsHint:
199 | 'Separe as tags por vírgulas quando houver mais de uma ex: Trabalho, Importante, etc.',
200 | featureTodoHint:
201 | 'Crie items da lista quebrando a linha e marque como finalizado colocando * no final ex: Comprar arroz*',
202 | featureImageCamera: 'Tirar uma foto',
203 | featureImageGallery: 'Selecionar da galeria',
204 | featureImageLabel: 'Imagem',
205 | currentRemindersLabel: 'Atuais',
206 | featureTagsLabel: 'Tags',
207 | lateRemindersLabel: 'Em atraso',
208 | featureTodoLabel: 'Coisas a fazer',
209 | featurePriorityLabel: 'Prioridade',
210 | featureReminderHint: 'O que gostaria de lembrar?',
211 | saveChangesFeatureLabel: 'Salvar',
212 | settingsColorLabel: 'Cor do tema',
213 | settingsDarkModeLabel: 'Modo escuro',
214 | completedRemindersLabel: 'Concluídos',
215 | settingsExportLabel: 'Exportar notas',
216 | settingsImportLabel: 'Importar notas',
217 | archiveActionLabel: 'Arquivar',
218 | restoreActionLabel: 'Restaurar',
219 | deleteActionLabel: 'Excluir',
220 | hideActionLabel: 'Fechar',
221 | completeActionLabel: 'Concluir',
222 | importedAlert: 'Backup restaurado com sucesso!',
223 | exportedAlert: 'Backup exportado para o cartão SD',
224 | selectedLabel: 'Aplicar à seleção:',
225 | pageLabels: const [
226 | 'Notas',
227 | 'Lembretes',
228 | 'Pesquisa',
229 | 'Arquivo'
230 | ],
231 | dayNames: const [
232 | 'SEG',
233 | 'TER',
234 | 'QUA',
235 | 'QUI',
236 | 'SEX',
237 | 'SAB',
238 | 'DOM'
239 | ],
240 | monthNames: const [
241 | 'JANEIRO',
242 | 'FEVEREIRO',
243 | 'MARÇO',
244 | 'ABRIL',
245 | 'MAIO',
246 | 'JUNHO',
247 | 'JULHO',
248 | 'AGOSTO',
249 | 'SETEMBRO',
250 | 'OUTUBRO',
251 | 'NOVEMBRO',
252 | 'DEZEMBRO'
253 | ]);
254 |
255 | Localization._(
256 | {@required this.featureReminderHint,
257 | @required this.featurePriorityLabel,
258 | @required this.completeActionLabel,
259 | @required this.emptyNoteHintSubtitle,
260 | @required this.emptyArchiveHintSubtitle,
261 | @required this.emptySearchHintSubTitle,
262 | @required this.currentRemindersEmpty,
263 | @required this.lateRemindersEmpty,
264 | @required this.completedRemindersEmpty,
265 | @required this.yes,
266 | @required this.no,
267 | @required this.cancel,
268 | @required this.accept,
269 | @required this.promptLeaveApp,
270 | @required this.promptLeaveUnsaved,
271 | @required this.emptyNoteHintTitle,
272 | @required this.emptyArchiveHintTitle,
273 | @required this.searchNotesHint,
274 | @required this.emptySearchHintTitle,
275 | @required this.settingsPageTitle,
276 | @required this.settingsColorLabel,
277 | @required this.settingsDarkModeLabel,
278 | @required this.completedRemindersLabel,
279 | @required this.settingsExportLabel,
280 | @required this.settingsImportLabel,
281 | @required this.createNoteFeatureTitle,
282 | @required this.createEventFeatureTitle,
283 | @required this.editNoteFeatureTitle,
284 | @required this.editEventFeatureTitle,
285 | @required this.saveChangesFeatureLabel,
286 | @required this.featureContentsHint,
287 | @required this.featureContentsLabel,
288 | @required this.featureContentsError,
289 | @required this.featureTodoHint,
290 | @required this.featureTagsHint,
291 | @required this.featureImageLabel,
292 | @required this.featureImageCamera,
293 | @required this.featureImageGallery,
294 | @required this.featureTodoLabel,
295 | @required this.lateRemindersLabel,
296 | @required this.featureTagsLabel,
297 | @required this.currentRemindersLabel,
298 | @required this.archiveActionLabel,
299 | @required this.restoreActionLabel,
300 | @required this.deleteActionLabel,
301 | @required this.hideActionLabel,
302 | @required this.selectedLabel,
303 | @required this.importedAlert,
304 | @required this.exportedAlert,
305 | @required this.dayNames,
306 | @required this.monthNames,
307 | @required this.pageLabels});
308 | }
309 |
310 | // Route names, each must be unique
311 | class Routes {
312 | static const String mainScreen = 'mainPage';
313 | static const String createNote = 'createNote';
314 | static const String editNote = 'editNote';
315 | static const String splashScreen = 'splashScreen';
316 | static const String createReminder = 'createReminder';
317 | static const String editReminder = 'editReminder';
318 | }
319 |
--------------------------------------------------------------------------------
/lib/widgets/cards.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:entitas_ff/entitas_ff.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:notebulk/ecs/components.dart';
6 | import 'package:notebulk/theme.dart';
7 | import 'package:notebulk/util.dart';
8 | import 'package:tinycolor/tinycolor.dart';
9 |
10 | class NoteCardWidget extends StatelessWidget {
11 | const NoteCardWidget({Key key, this.noteEntity}) : super(key: key);
12 |
13 | final Entity noteEntity;
14 |
15 | @override
16 | Widget build(BuildContext context) {
17 | final contents = noteEntity.get().value;
18 | final timestamp = noteEntity.get()?.value ?? DateTime.now();
19 | final tags = noteEntity.get()?.value ?? [];
20 | final listItems = noteEntity.get()?.value ?? [];
21 | final picFile = noteEntity.get()?.value;
22 | final isSelected = noteEntity.hasT();
23 | final localization = EntityManagerProvider.of(context)
24 | .entityManager
25 | .getUniqueEntity()
26 | .get();
27 | final appTheme = EntityManagerProvider.of(context)
28 | .entityManager
29 | .getUniqueEntity()
30 | .get()
31 | .value;
32 |
33 | return Container(
34 | foregroundDecoration: BoxDecoration(
35 | color: isSelected
36 | ? appTheme.primaryButtonColor.withOpacity(0.75)
37 | : Colors.transparent),
38 | child: Card(
39 | clipBehavior: Clip.antiAlias,
40 | elevation: 4,
41 | color: appTheme.appBarColor,
42 | margin: EdgeInsets.all(0),
43 | child: Stack(
44 | children: [
45 | Padding(
46 | padding: EdgeInsets.all(12),
47 | child: Column(
48 | mainAxisAlignment: MainAxisAlignment.start,
49 | crossAxisAlignment: CrossAxisAlignment.start,
50 | children: [
51 | buildTimestamp(context, timestamp, localization, appTheme),
52 | SizedBox(
53 | height: 8,
54 | ),
55 | if (picFile != null) ...[
56 | buildPicField(picFile),
57 | SizedBox(
58 | height: 8,
59 | ),
60 | ],
61 | buildContentsField(context, contents, appTheme),
62 | if (listItems.isNotEmpty) ...[
63 | SizedBox(
64 | height: 8,
65 | ),
66 | buildListField(listItems, context, appTheme)
67 | ],
68 | if (tags.isNotEmpty) ...[
69 | Divider(),
70 | buildTags(tags, context, appTheme)
71 | ],
72 | ],
73 | ),
74 | ),
75 | ],
76 | ),
77 | ),
78 | );
79 | }
80 |
81 | Widget buildPicField(File picFile) {
82 | final margin = EdgeInsets.symmetric(vertical: 8);
83 |
84 | return Card(
85 | margin: margin,
86 | elevation: 2,
87 | child: Hero(
88 | tag: picFile.path,
89 | child: Image.file(
90 | picFile,
91 | fit: BoxFit.cover,
92 | ),
93 | ),
94 | );
95 | }
96 |
97 | Widget buildContentsField(
98 | BuildContext context, String contents, BaseTheme appTheme) {
99 | return Text(
100 | contents,
101 | textAlign: TextAlign.left,
102 | textWidthBasis: TextWidthBasis.longestLine,
103 | style: appTheme.cardWidgetContentsTyle,
104 | );
105 | }
106 |
107 | Widget buildListField(
108 | List listItems, BuildContext context, BaseTheme appTheme) {
109 | return Padding(
110 | padding: EdgeInsets.all(4),
111 | child: Column(
112 | mainAxisAlignment: MainAxisAlignment.start,
113 | crossAxisAlignment: CrossAxisAlignment.start,
114 | children: [
115 | for (int index = 0; index < listItems.length; index++)
116 | Padding(
117 | padding: EdgeInsets.all(4),
118 | child: Row(
119 | crossAxisAlignment: CrossAxisAlignment.start,
120 | children: [
121 | Expanded(
122 | child: Text(
123 | '• ${listItems[index].label}',
124 | softWrap: true,
125 | textAlign: TextAlign.left,
126 | style: appTheme.cardWidgetTodoItemStyle.copyWith(
127 | fontWeight: listItems[index].isChecked
128 | ? FontWeight.w500
129 | : FontWeight.w400,
130 | color: listItems[index].isChecked
131 | ? Colors.grey.shade600
132 | : appTheme.cardWidgetTodoItemStyle.color,
133 | decoration: listItems[index].isChecked
134 | ? TextDecoration.lineThrough
135 | : TextDecoration.none),
136 | ),
137 | ),
138 | ],
139 | ),
140 | ),
141 | ]),
142 | );
143 | }
144 |
145 | Widget buildTimestamp(BuildContext context, DateTime timestamp,
146 | Localization localization, BaseTheme appTheme) {
147 | return Text(
148 | formatTimestamp(timestamp, localization),
149 | style: appTheme.cardWidgetTimestampStyle,
150 | );
151 | }
152 |
153 | Widget buildTags(
154 | List tags, BuildContext context, BaseTheme appTheme) {
155 | final padding = EdgeInsets.symmetric(vertical: 4);
156 |
157 | return Padding(
158 | padding: padding,
159 | child: Wrap(
160 | spacing: 4,
161 | runSpacing: 4,
162 | children: [
163 | for (final tag in tags)
164 | Container(
165 | padding: const EdgeInsets.all(4),
166 | child: RichText(
167 | text:
168 | TextSpan(text: tag, style: appTheme.cardWidgetTagStyle)),
169 | )
170 | ],
171 | ),
172 | );
173 | }
174 | }
175 |
176 | class ReminderCardWidget extends StatelessWidget {
177 | const ReminderCardWidget({Key key, this.reminderEntity}) : super(key: key);
178 |
179 | final Entity reminderEntity;
180 |
181 | @override
182 | Widget build(BuildContext context) {
183 | final reminderPriority = reminderEntity.get().value.index;
184 | final contents = reminderEntity.get().value;
185 | final timestamp = reminderEntity.get()?.value ?? DateTime.now();
186 | final isSelected = reminderEntity.hasT();
187 | final localization = EntityManagerProvider.of(context)
188 | .entityManager
189 | .getUniqueEntity()
190 | .get();
191 | final appTheme = EntityManagerProvider.of(context)
192 | .entityManager
193 | .getUniqueEntity()
194 | .get()
195 | .value;
196 |
197 | return Container(
198 | foregroundDecoration: isSelected
199 | ? BoxDecoration(color: appTheme.primaryButtonColor.withOpacity(0.75))
200 | : null,
201 | child: Card(
202 | clipBehavior: Clip.antiAlias,
203 | elevation: 4,
204 | color: appTheme.reminderPriorityColors[reminderPriority],
205 | margin: EdgeInsets.all(0),
206 | child: Stack(
207 | children: [
208 | Padding(
209 | padding: EdgeInsets.all(12),
210 | child: Column(
211 | mainAxisAlignment: MainAxisAlignment.start,
212 | crossAxisAlignment: CrossAxisAlignment.start,
213 | children: [
214 | buildTimestamp(context, timestamp, localization, appTheme),
215 | SizedBox(
216 | height: 8,
217 | ),
218 | buildContentsField(context, contents, appTheme),
219 | ],
220 | ),
221 | ),
222 | ],
223 | ),
224 | ),
225 | );
226 | }
227 |
228 | Widget buildContentsField(
229 | BuildContext context, String contents, BaseTheme appTheme) {
230 | final priority = reminderEntity.get()?.value;
231 | final cardColor = priority != null
232 | ? appTheme.reminderPriorityColors[priority.index]
233 | : appTheme.appBarColor;
234 | final textColor =
235 | TinyColor(cardColor).isDark() ? Colors.white : Colors.black;
236 |
237 | return Text(
238 | contents,
239 | textAlign: TextAlign.left,
240 | maxLines: null,
241 | textWidthBasis: TextWidthBasis.longestLine,
242 | style: appTheme.cardWidgetContentsTyle.copyWith(color: textColor),
243 | );
244 | }
245 |
246 | Widget buildTimestamp(BuildContext context, DateTime date,
247 | Localization localization, BaseTheme appTheme) {
248 | final timestamp = formatTimestamp(date, localization);
249 | final priority = reminderEntity.get()?.value;
250 | final cardColor = priority != null
251 | ? appTheme.reminderPriorityColors[priority.index]
252 | : appTheme.appBarColor;
253 | final textColor =
254 | TinyColor(cardColor).isDark() ? Colors.white : Colors.black;
255 |
256 | return Text(timestamp,
257 | style: appTheme.cardWidgetTimestampStyle.copyWith(color: textColor));
258 | }
259 | }
260 |
--------------------------------------------------------------------------------
/lib/widgets/util.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui';
2 |
3 | import 'package:entitas_ff/entitas_ff.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
6 | import 'package:notebulk/ecs/components.dart';
7 | import 'package:notebulk/theme.dart';
8 | import 'package:notebulk/util.dart';
9 |
10 | Widget buildNotesGridView(
11 | List notes, Widget Function(Entity) buildNoteCard,
12 | [int gridCount = 2]) {
13 | return StaggeredGridView.countBuilder(
14 | shrinkWrap: true,
15 | physics: NeverScrollableScrollPhysics(),
16 | padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
17 | crossAxisCount: gridCount,
18 | itemCount: notes.length,
19 | mainAxisSpacing: 4,
20 | crossAxisSpacing: 4,
21 | itemBuilder: (context, index) => buildNoteCard(notes[index]),
22 | staggeredTileBuilder: (index) => StaggeredTile.fit(1),
23 | );
24 | }
25 |
26 | Widget buildNotesSliverGridView(
27 | List notes, Widget Function(Entity) buildNoteCard,
28 | [int count = 2]) {
29 | assert(count > 0, 'GridView must have a cross axis count greater than zero');
30 | return SliverPadding(
31 | padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
32 | sliver: SliverStaggeredGrid.countBuilder(
33 | crossAxisCount: count,
34 | itemCount: notes.length,
35 | mainAxisSpacing: 4,
36 | crossAxisSpacing: 4,
37 | itemBuilder: (context, index) => buildNoteCard(notes[index]),
38 | staggeredTileBuilder: (index) => StaggeredTile.fit(1),
39 | ),
40 | );
41 | }
42 |
43 | /*
44 | Widget buildNotesListView(
45 | List notes, Widget Function(Entity) buildNoteCard) {
46 | return ListView.builder(
47 | shrinkWrap: true,
48 | itemCount: notes.length,
49 | itemBuilder: (context, index) => buildNoteCard(notes[index]),
50 | );
51 | }
52 | */
53 |
54 | class TabBar extends StatelessWidget {
55 | const TabBar({
56 | @required this.onTap,
57 | @required this.items,
58 | Key key,
59 | this.appTheme,
60 | this.index = 0,
61 | this.prevIndex = 0,
62 | this.scaleIcon,
63 | this.colorIcon,
64 | }) : super(key: key);
65 |
66 | final Function(int) onTap;
67 | final List items;
68 | final int index, prevIndex;
69 | final Animation scaleIcon;
70 | final Animation colorIcon;
71 | final BaseTheme appTheme;
72 |
73 | @override
74 | Widget build(BuildContext context) {
75 | final tabWidth = MediaQuery.of(context).size.width / items.length - 2;
76 | final tabHeight = kTextTabBarHeight * 0.75;
77 |
78 | return Container(
79 | decoration: BoxDecoration(color: appTheme.appBarColor),
80 | height: kTextTabBarHeight,
81 | alignment: Alignment.center,
82 | padding: const EdgeInsets.only(top: 4),
83 | width: MediaQuery.of(context).size.width,
84 | child: Stack(
85 | children: [
86 | Row(
87 | mainAxisAlignment: MainAxisAlignment.center,
88 | children: [
89 | for (int i = 0; i < items.length; i++)
90 | InkWell(
91 | onTap: () => onTap(i),
92 | child: Column(
93 | children: [
94 | Container(
95 | height: tabHeight,
96 | width: tabWidth,
97 | alignment: Alignment.center,
98 | child: Text(
99 | items[i].label,
100 | textAlign: TextAlign.center,
101 | style: appTheme.biggerBodyTextStyle.copyWith(
102 | color: index == i
103 | ? colorIcon.value
104 | : appTheme.otherTabItemColor,
105 | ),
106 | ),
107 | ),
108 | ],
109 | ),
110 | ),
111 | ],
112 | ),
113 | Positioned(
114 | left: 4.0 +
115 | (lerpDouble(
116 | tabWidth * prevIndex, tabWidth * index, scaleIcon.value)),
117 | bottom: 0,
118 | child: Container(
119 | height: 2,
120 | color: appTheme.selectedTabItemColor,
121 | width: tabWidth,
122 | ),
123 | )
124 | ],
125 | ),
126 | );
127 | }
128 | }
129 |
130 | class TabItem {
131 | const TabItem({this.label});
132 |
133 | final String label;
134 | }
135 |
136 | class StatusBar extends StatelessWidget {
137 | final List Function(int) actions;
138 |
139 | const StatusBar({Key key, this.actions}) : super(key: key);
140 |
141 | @override
142 | Widget build(BuildContext context) {
143 | final entityManager = EntityManagerProvider.of(context).entityManager;
144 | final appTheme =
145 | entityManager.getUniqueEntity().get().value;
146 | final localization =
147 | entityManager.getUniqueEntity().get();
148 |
149 | return AnimatableEntityObservingWidget.extended(
150 | provider: (em) => em.getUniqueEntity(),
151 | startAnimating: false,
152 | curve: Curves.fastOutSlowIn,
153 | duration: Duration(milliseconds: 200),
154 | tweens: {
155 | 'size': Tween(begin: 0, end: kBottomNavigationBarHeight)
156 | },
157 | animateAdded: (c) =>
158 | c is Toggle ? EntityAnimation.forward : EntityAnimation.none,
159 | animateRemoved: (c) =>
160 | c is Toggle ? EntityAnimation.reverse : EntityAnimation.none,
161 | animateUpdated: (_, __) => EntityAnimation.none,
162 | builder: (statusEntity, animations, context) {
163 | return Stack(
164 | children: [
165 | SizedBox(
166 | height: kBottomNavigationBarHeight * animations['size'].value,
167 | width: MediaQuery.of(context).size.width,
168 | ),
169 | Positioned(
170 | bottom: 0,
171 | child: EntityObservingWidget(
172 | provider: (em) => em.getUniqueEntity(),
173 | builder: (e, _) => Container(
174 | color: appTheme.appBarColor,
175 | width: MediaQuery.of(context).size.width,
176 | height: animations['size'].value,
177 | child: statusEntity.hasT()
178 | ? Row(
179 | mainAxisAlignment: MainAxisAlignment.spaceEvenly,
180 | crossAxisAlignment: CrossAxisAlignment.center,
181 | children: [
182 | if (statusEntity.hasT())
183 | Text(
184 | statusEntity.get().value,
185 | style: appTheme.biggerBodyTextStyle,
186 | ),
187 | if (statusEntity.hasT()) ...[
188 | FlatButton(
189 | child: Text(localization.hideActionLabel),
190 | onPressed: () {
191 | statusEntity
192 | ..remove()
193 | ..remove();
194 | },
195 | )
196 | ] else
197 | ...actions?.call(e.get().value),
198 | ],
199 | )
200 | : SizedBox(
201 | width: 0,
202 | height: 0,
203 | ),
204 | ),
205 | ),
206 | ),
207 | ],
208 | );
209 | },
210 | );
211 | }
212 | }
213 |
214 | class ShouldLeavePromptDialog extends StatelessWidget {
215 | final String message, yesLabel, noLabel;
216 | final VoidCallback onYes, onNo;
217 | final BaseTheme appTheme;
218 |
219 | const ShouldLeavePromptDialog({
220 | @required this.message,
221 | @required this.onYes,
222 | @required this.onNo,
223 | this.appTheme,
224 | this.yesLabel,
225 | this.noLabel,
226 | Key key,
227 | }) : super(key: key);
228 |
229 | @override
230 | Widget build(BuildContext context) {
231 | final appTheme = this.appTheme ?? LightTheme();
232 | return AlertDialog(
233 | title: Text(
234 | message,
235 | style: appTheme.titleTextStyle,
236 | ),
237 | actions: [
238 | FlatButton(
239 | splashColor: appTheme.accentColor,
240 | child: Text(
241 | noLabel,
242 | style: appTheme.actionableLabelStyle,
243 | ),
244 | onPressed: () => Navigator.of(context).pop(false),
245 | ),
246 | FlatButton(
247 | splashColor: appTheme.accentColor,
248 | child: Text(
249 | yesLabel,
250 | style: appTheme.actionableLabelStyle
251 | .copyWith(color: appTheme.primaryButtonColor, fontSize: 16),
252 | ),
253 | onPressed: () => Navigator.of(context).pop(true),
254 | ),
255 | ],
256 | );
257 | }
258 | }
259 |
260 | class GradientBackground extends StatelessWidget {
261 | const GradientBackground({Key key, this.child, this.appTheme})
262 | : super(key: key);
263 |
264 | final BaseTheme appTheme;
265 | final Widget child;
266 |
267 | @override
268 | Widget build(BuildContext context) {
269 | return DecoratedBox(
270 | decoration: BoxDecoration(gradient: appTheme.backgroundGradient),
271 | child: child);
272 | }
273 | }
274 |
275 | /* class ClippableShadowPainter extends CustomPainter {
276 | ClippableShadowPainter({@required this.shadow, @required this.clipper});
277 |
278 | final Shadow shadow;
279 | final CustomClipper clipper;
280 |
281 | @override
282 | void paint(Canvas canvas, Size size) {
283 | final paint = shadow.toPaint();
284 | final clipPath = clipper.getClip(size).shift(shadow.offset);
285 | canvas.drawPath(clipPath, paint);
286 | }
287 |
288 | @override
289 | bool shouldRepaint(CustomPainter oldDelegate) {
290 | return true;
291 | }
292 | } */
293 |
--------------------------------------------------------------------------------
/linux/Makefile:
--------------------------------------------------------------------------------
1 | # Copyright 2018 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | # Executable name.
16 | BINARY_NAME=notebulk
17 | # The location of the flutter-desktop-embedding repository.
18 | FDE_ROOT=$(HOME)/Dev/flutter-desktop-embedding
19 | # The C++ code for the embedder application.
20 | SOURCES=notebulk.cc
21 |
22 | # Plugins to include from the flutter-desktop-embedding plugins/ directory.
23 | PLUGIN_NAMES=file_chooser
24 | # Additional plugins to include from the plugins/flutter_plugins subdirectory.
25 | FLUTTER_PLUGIN_NAMES=
26 |
27 |
28 | # Default build type. For a release build, set BUILD=release.
29 | # Currently this only sets NDEBUG, which is used to control the flags passed
30 | # to the Flutter engine in the example shell, and not the complation settings
31 | # (e.g., optimization level) of the C++ code.
32 | BUILD=debug
33 |
34 | # Configuration provided via flutter tool.
35 | include flutter/generated_config
36 |
37 | # Dependency locations
38 | FLUTTER_APP_CACHE_DIR=flutter/
39 | FLUTTER_APP_DIR=$(CURDIR)/..
40 | FLUTTER_APP_BUILD_DIR=$(FLUTTER_APP_DIR)/build
41 | PLUGINS_DIR=$(FDE_ROOT)/plugins
42 | FLUTTER_PLUGINS_DIR=$(PLUGINS_DIR)/flutter_plugins
43 |
44 | OUT_DIR=$(FLUTTER_APP_BUILD_DIR)/linux
45 |
46 | # Libraries
47 | FLUTTER_LIB_NAME=flutter_linux
48 | FLUTTER_LIB=$(FLUTTER_APP_CACHE_DIR)/lib$(FLUTTER_LIB_NAME).so
49 | # The name of each plugin library is the name of its directory with _plugin
50 | # appended. The exception is example_plugin (to avoid confusion with the
51 | # top-level example/ directory), so it's added here separately.
52 | PLUGIN_LIB_NAMES=$(foreach plugin,$(PLUGIN_NAMES) $(FLUTTER_PLUGIN_NAMES),$(plugin)_plugin)
53 | PLUGIN_LIBS=$(foreach plugin,$(PLUGIN_LIB_NAMES),$(OUT_DIR)/lib$(plugin).so)
54 | ALL_LIBS=$(FLUTTER_LIB) $(PLUGIN_LIBS)
55 |
56 | # Tools
57 | FLUTTER_BIN=$(FLUTTER_ROOT)/bin/flutter
58 |
59 | # Resources
60 | ICU_DATA_NAME=icudtl.dat
61 | ICU_DATA_SOURCE=$(FLUTTER_APP_CACHE_DIR)/$(ICU_DATA_NAME)
62 | FLUTTER_ASSETS_NAME=flutter_assets
63 | FLUTTER_ASSETS_SOURCE=$(FLUTTER_APP_BUILD_DIR)/$(FLUTTER_ASSETS_NAME)
64 |
65 | # Bundle structure
66 | BUNDLE_OUT_DIR=$(OUT_DIR)/$(BUILD)
67 | BUNDLE_DATA_DIR=$(BUNDLE_OUT_DIR)/data
68 | BUNDLE_LIB_DIR=$(BUNDLE_OUT_DIR)/lib
69 |
70 | BIN_OUT=$(BUNDLE_OUT_DIR)/$(BINARY_NAME)
71 | ICU_DATA_OUT=$(BUNDLE_DATA_DIR)/$(ICU_DATA_NAME)
72 | FLUTTER_LIB_OUT=$(BUNDLE_LIB_DIR)/lib$(FLUTTER_LIB_NAME).so
73 | ALL_LIBS_OUT=$(foreach lib,$(ALL_LIBS),$(BUNDLE_LIB_DIR)/$(notdir $(lib)))
74 |
75 | # Add relevant code from the wrapper library, which is intended to be statically
76 | # built into the client.
77 | WRAPPER_ROOT=$(FLUTTER_APP_CACHE_DIR)/cpp_client_wrapper
78 | WRAPPER_SOURCES= \
79 | $(WRAPPER_ROOT)/flutter_window_controller.cc \
80 | $(WRAPPER_ROOT)/plugin_registrar.cc \
81 | $(WRAPPER_ROOT)/engine_method_result.cc
82 | SOURCES+=$(WRAPPER_SOURCES)
83 |
84 | # Headers
85 | WRAPPER_INCLUDE_DIR=$(WRAPPER_ROOT)/include
86 | # The plugin builds place all published headers in a top-level include/.
87 | PLUGIN_INCLUDE_DIRS=$(OUT_DIR)/include
88 | INCLUDE_DIRS=$(FLUTTER_APP_CACHE_DIR) $(PLUGIN_INCLUDE_DIRS) \
89 | $(WRAPPER_INCLUDE_DIR)
90 |
91 | # Build settings
92 | CXX=clang++
93 | CXXFLAGS.release=-DNDEBUG
94 | CXXFLAGS=-std=c++14 -Wall -Werror $(CXXFLAGS.$(BUILD))
95 | CPPFLAGS=$(patsubst %,-I%,$(INCLUDE_DIRS))
96 | LDFLAGS=-L$(BUNDLE_LIB_DIR) \
97 | -l$(FLUTTER_LIB_NAME) \
98 | $(patsubst %,-l%,$(PLUGIN_LIB_NAMES)) \
99 | -Wl,-rpath=\$$ORIGIN/lib
100 |
101 | # Targets
102 |
103 | .PHONY: all
104 | all: $(BIN_OUT) bundle
105 |
106 | # This is a phony target because the flutter tool cannot describe
107 | # its inputs and outputs yet.
108 | .PHONY: sync
109 | sync: flutter/generated_config
110 | $(FLUTTER_ROOT)/packages/flutter_tools/bin/tool_backend.sh linux-x64 $(BUILD)
111 |
112 | .PHONY: bundle
113 | bundle: $(ICU_DATA_OUT) $(ALL_LIBS_OUT) bundleflutterassets
114 |
115 | $(BIN_OUT): $(SOURCES) $(ALL_LIBS_OUT)
116 | mkdir -p $(@D)
117 | $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(SOURCES) $(LDFLAGS) -o $@
118 |
119 | $(WRAPPER_SOURCES) $(FLUTTER_LIB) $(ICU_DATA_SOURCE) $(FLUTTER_ASSETS_SOURCE): \
120 | | sync
121 |
122 | # Implicit rules won't match phony targets, so list plugin builds explicitly.
123 | $(OUT_DIR)/libfile_chooser_plugin.so: | file_chooser
124 |
125 | .PHONY: $(PLUGIN_NAMES) example_plugin
126 | $(PLUGIN_NAMES) example_plugin:
127 | make -C $(PLUGINS_DIR)/$@/linux \
128 | OUT_DIR=$(OUT_DIR) FLUTTER_ROOT=$(FLUTTER_ROOT)
129 |
130 | .PHONY: $(FLUTTER_PLUGIN_NAMES)
131 | $(FLUTTER_PLUGIN_NAMES):
132 | make -C $(FLUTTER_PLUGINS_DIR)/$@/linux \
133 | OUT_DIR=$(OUT_DIR) FLUTTER_ROOT=$(FLUTTER_ROOT)
134 |
135 | # Plugin library bundling pattern.
136 | $(BUNDLE_LIB_DIR)/%: $(OUT_DIR)/%
137 | mkdir -p $(BUNDLE_LIB_DIR)
138 | cp $< $@
139 |
140 | $(FLUTTER_LIB_OUT): $(FLUTTER_LIB)
141 | mkdir -p $(@D)
142 | cp $< $@
143 |
144 | $(ICU_DATA_OUT): $(ICU_DATA_SOURCE)
145 | mkdir -p $(@D)
146 | cp $< $@
147 |
148 | # Fully re-copy the assets directory on each build to avoid having to keep a
149 | # comprehensive list of all asset files here, which would be fragile to changes
150 | # in the Flutter example (e.g., adding a new font to pubspec.yaml would require
151 | # changes here).
152 | .PHONY: bundleflutterassets
153 | bundleflutterassets: $(FLUTTER_ASSETS_SOURCE)
154 | mkdir -p $(BUNDLE_DATA_DIR)
155 | rsync -rpu --delete $(FLUTTER_ASSETS_SOURCE) $(BUNDLE_DATA_DIR)
156 |
157 | .PHONY: clean
158 | clean:
159 | rm -rf $(OUT_DIR); \
160 | cd $(FLUTTER_APP_DIR); \
161 | $(FLUTTER_BIN) clean
162 |
--------------------------------------------------------------------------------
/linux/notebulk.cc:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include
19 | #include
20 |
21 | #include