mirror of
https://github.com/flutter/samples.git
synced 2025-11-08 13:58:47 +00:00
Adding Rally App to Flutter Samples (#135)
This commit is contained in:
committed by
Andrew Brogdon
parent
3348c2f2dd
commit
c056b754a2
299
LICENSE
299
LICENSE
@@ -25,3 +25,302 @@
|
|||||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Copyright (c) 2014, Vaibhav Singh (design) and Rosetta Type Foundry s.r.o. (post-production).
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
http://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|||||||
70
material_studies/rally/.gitignore
vendored
Normal file
70
material_studies/rally/.gitignore
vendored
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# Miscellaneous
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
.atom/
|
||||||
|
.buildlog/
|
||||||
|
.history
|
||||||
|
.svn/
|
||||||
|
|
||||||
|
# IntelliJ related
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Visual Studio Code related
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
**/doc/api/
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins
|
||||||
|
.packages
|
||||||
|
.pub-cache/
|
||||||
|
.pub/
|
||||||
|
/build/
|
||||||
|
|
||||||
|
# Android related
|
||||||
|
**/android/**/gradle-wrapper.jar
|
||||||
|
**/android/.gradle
|
||||||
|
**/android/captures/
|
||||||
|
**/android/gradlew
|
||||||
|
**/android/gradlew.bat
|
||||||
|
**/android/local.properties
|
||||||
|
**/android/**/GeneratedPluginRegistrant.java
|
||||||
|
|
||||||
|
# iOS/XCode related
|
||||||
|
**/ios/**/*.mode1v3
|
||||||
|
**/ios/**/*.mode2v3
|
||||||
|
**/ios/**/*.moved-aside
|
||||||
|
**/ios/**/*.pbxuser
|
||||||
|
**/ios/**/*.perspectivev3
|
||||||
|
**/ios/**/*sync/
|
||||||
|
**/ios/**/.sconsign.dblite
|
||||||
|
**/ios/**/.tags*
|
||||||
|
**/ios/**/.vagrant/
|
||||||
|
**/ios/**/DerivedData/
|
||||||
|
**/ios/**/Icon?
|
||||||
|
**/ios/**/Pods/
|
||||||
|
**/ios/**/.symlinks/
|
||||||
|
**/ios/**/profile
|
||||||
|
**/ios/**/xcuserdata
|
||||||
|
**/ios/.generated/
|
||||||
|
**/ios/Flutter/App.framework
|
||||||
|
**/ios/Flutter/Flutter.framework
|
||||||
|
**/ios/Flutter/Generated.xcconfig
|
||||||
|
**/ios/Flutter/app.flx
|
||||||
|
**/ios/Flutter/app.zip
|
||||||
|
**/ios/Flutter/flutter_assets/
|
||||||
|
**/ios/ServiceDefinitions.json
|
||||||
|
**/ios/Runner/GeneratedPluginRegistrant.*
|
||||||
|
|
||||||
|
# Exceptions to above rules.
|
||||||
|
!**/ios/**/default.mode1v3
|
||||||
|
!**/ios/**/default.mode2v3
|
||||||
|
!**/ios/**/default.pbxuser
|
||||||
|
!**/ios/**/default.perspectivev3
|
||||||
|
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
||||||
37
material_studies/rally/README.md
Normal file
37
material_studies/rally/README.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Rally
|
||||||
|
|
||||||
|
A Flutter sample app based on the Material study Rally (a hypothetical, personal finance app). It
|
||||||
|
showcases custom tabs, custom painted widgets, and custom animations.
|
||||||
|
|
||||||
|
For info on the Rally Material Study, see: https://material.io/design/material-studies/rally.html
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
|
||||||
|
* Show how to customize a tab bar.
|
||||||
|
* Show how to create reusable custom widgets with composition and custom painting.
|
||||||
|
* Show how to create an app with tabs and child navigation screens.
|
||||||
|
|
||||||
|
## The important bits
|
||||||
|
|
||||||
|
### `/charts/*`
|
||||||
|
|
||||||
|
These are the custom charts. The circle chart and line chart are custom painters,
|
||||||
|
while the single vertical bar chart is a simple composition of boxes.
|
||||||
|
|
||||||
|
### `/sections/*`
|
||||||
|
|
||||||
|
These are the main sections for the tab views and the child screen with the details and line chart.
|
||||||
|
The financial entity is a reusable screen that is the base for the accounts, bills, and budgets
|
||||||
|
screens.
|
||||||
|
|
||||||
|
## Questions/issues
|
||||||
|
|
||||||
|
If you have a general question about any of the techniques you see in
|
||||||
|
the sample, the best places to go are:
|
||||||
|
|
||||||
|
* [The FlutterDev Google Group](https://groups.google.com/forum/#!forum/flutter-dev)
|
||||||
|
* [The Flutter Gitter channel](https://gitter.im/flutter/flutter)
|
||||||
|
* [StackOverflow](https://stackoverflow.com/questions/tagged/flutter)
|
||||||
|
|
||||||
|
If you run into an issue with the sample itself, please file an issue
|
||||||
|
in the [main Flutter repo](https://github.com/flutter/flutter/issues).
|
||||||
30
material_studies/rally/analysis_options.yaml
Normal file
30
material_studies/rally/analysis_options.yaml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
include: package:pedantic/analysis_options.1.7.0.yaml
|
||||||
|
|
||||||
|
analyzer:
|
||||||
|
strong-mode:
|
||||||
|
implicit-casts: false
|
||||||
|
implicit-dynamic: false
|
||||||
|
|
||||||
|
linter:
|
||||||
|
rules:
|
||||||
|
- avoid_types_on_closure_parameters
|
||||||
|
- avoid_void_async
|
||||||
|
- await_only_futures
|
||||||
|
- camel_case_types
|
||||||
|
- cancel_subscriptions
|
||||||
|
- close_sinks
|
||||||
|
- constant_identifier_names
|
||||||
|
- control_flow_in_finally
|
||||||
|
- empty_statements
|
||||||
|
- hash_and_equals
|
||||||
|
- implementation_imports
|
||||||
|
- non_constant_identifier_names
|
||||||
|
- package_api_docs
|
||||||
|
- package_names
|
||||||
|
- package_prefixed_library_names
|
||||||
|
- test_types_in_equals
|
||||||
|
- throw_in_finally
|
||||||
|
- unnecessary_brace_in_string_interps
|
||||||
|
- unnecessary_getters_setters
|
||||||
|
- unnecessary_new
|
||||||
|
- unnecessary_statements
|
||||||
BIN
material_studies/rally/assets/logo.png
Normal file
BIN
material_studies/rally/assets/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.1 KiB |
BIN
material_studies/rally/assets/thumb.png
Normal file
BIN
material_studies/rally/assets/thumb.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
BIN
material_studies/rally/fonts/Eczar-Bold.ttf
Executable file
BIN
material_studies/rally/fonts/Eczar-Bold.ttf
Executable file
Binary file not shown.
BIN
material_studies/rally/fonts/Eczar-Regular.ttf
Executable file
BIN
material_studies/rally/fonts/Eczar-Regular.ttf
Executable file
Binary file not shown.
BIN
material_studies/rally/fonts/Eczar-SemiBold.ttf
Executable file
BIN
material_studies/rally/fonts/Eczar-SemiBold.ttf
Executable file
Binary file not shown.
BIN
material_studies/rally/fonts/RobotoCondensed-Bold.ttf
Executable file
BIN
material_studies/rally/fonts/RobotoCondensed-Bold.ttf
Executable file
Binary file not shown.
BIN
material_studies/rally/fonts/RobotoCondensed-Light.ttf
Executable file
BIN
material_studies/rally/fonts/RobotoCondensed-Light.ttf
Executable file
Binary file not shown.
BIN
material_studies/rally/fonts/RobotoCondensed-Regular.ttf
Executable file
BIN
material_studies/rally/fonts/RobotoCondensed-Regular.ttf
Executable file
Binary file not shown.
85
material_studies/rally/lib/app.dart
Normal file
85
material_studies/rally/lib/app.dart
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
// Copyright 2019-present the Flutter authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
import 'package:rally/colors.dart';
|
||||||
|
import 'package:rally/home.dart';
|
||||||
|
import 'package:rally/login.dart';
|
||||||
|
|
||||||
|
/// The RallyApp is a MaterialApp with a theme and 2 routes.
|
||||||
|
///
|
||||||
|
/// The home route is the main page with tabs for sub pages.
|
||||||
|
/// The login route is the initial route.
|
||||||
|
class RallyApp extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MaterialApp(
|
||||||
|
title: 'Rally',
|
||||||
|
theme: _buildRallyTheme(),
|
||||||
|
home: HomePage(),
|
||||||
|
initialRoute: '/login',
|
||||||
|
routes: {'/login': (context) => LoginPage()},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ThemeData _buildRallyTheme() {
|
||||||
|
final ThemeData base = ThemeData.dark();
|
||||||
|
return ThemeData(
|
||||||
|
scaffoldBackgroundColor: RallyColors.primaryBackground,
|
||||||
|
primaryColor: RallyColors.primaryBackground,
|
||||||
|
textTheme: _buildRallyTextTheme(base.textTheme),
|
||||||
|
inputDecorationTheme: InputDecorationTheme(
|
||||||
|
labelStyle:
|
||||||
|
TextStyle(color: RallyColors.gray, fontWeight: FontWeight.w500),
|
||||||
|
filled: true,
|
||||||
|
fillColor: RallyColors.inputBackground,
|
||||||
|
focusedBorder: InputBorder.none,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TextTheme _buildRallyTextTheme(TextTheme base) {
|
||||||
|
return base
|
||||||
|
.copyWith(
|
||||||
|
body1: base.body1.copyWith(
|
||||||
|
fontFamily: 'Roboto Condensed',
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
body2: base.body2.copyWith(
|
||||||
|
fontFamily: 'Eczar',
|
||||||
|
fontSize: 40,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
letterSpacing: 1.4,
|
||||||
|
),
|
||||||
|
button: base.button.copyWith(
|
||||||
|
fontFamily: 'Roboto Condensed',
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
letterSpacing: 2.8,
|
||||||
|
),
|
||||||
|
headline: base.body2.copyWith(
|
||||||
|
fontFamily: 'Eczar',
|
||||||
|
fontSize: 40,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
letterSpacing: 1.4,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.apply(
|
||||||
|
displayColor: Colors.white,
|
||||||
|
bodyColor: Colors.white,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
193
material_studies/rally/lib/charts/line_chart.dart
Normal file
193
material_studies/rally/lib/charts/line_chart.dart
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
// Copyright 2019-present the Flutter authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
import 'package:rally/colors.dart';
|
||||||
|
import 'package:rally/data.dart';
|
||||||
|
|
||||||
|
class RallyLineChart extends StatelessWidget {
|
||||||
|
RallyLineChart({this.events = const []}) : assert(events != null);
|
||||||
|
|
||||||
|
final List<DetailedEventData> events;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return CustomPaint(painter: RallyLineChartPainter(context, events));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RallyLineChartPainter extends CustomPainter {
|
||||||
|
RallyLineChartPainter(this.context, this.events);
|
||||||
|
|
||||||
|
final BuildContext context;
|
||||||
|
|
||||||
|
// Events to plot on the line as points.
|
||||||
|
final List<DetailedEventData> events;
|
||||||
|
|
||||||
|
// Number of days to plot.
|
||||||
|
// This is hardcoded to reflect the dummy data, but would be dynamic in a real
|
||||||
|
// app.
|
||||||
|
final int numDays = 52;
|
||||||
|
|
||||||
|
// Beginning of window. The end is this plus numDays.
|
||||||
|
// This is hardcoded to reflect the dummy data, but would be dynamic in a real
|
||||||
|
// app.
|
||||||
|
final DateTime startDate = DateTime.utc(2018, 12, 1);
|
||||||
|
|
||||||
|
// Ranges uses to lerp the pixel points.
|
||||||
|
// This is hardcoded to reflect the dummy data, but would be dynamic in a real
|
||||||
|
// app.
|
||||||
|
final double maxAmount = 3000.0; // minAmount is assumed to be 0.0
|
||||||
|
|
||||||
|
// The number of milliseconds in a day. This is the inherit period fot the
|
||||||
|
// points in this line.
|
||||||
|
static const int millisInDay = 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
|
// Amount to shift the tick drawing by so that the sunday ticks do not start
|
||||||
|
// on the edge.
|
||||||
|
final int tickShift = 3;
|
||||||
|
|
||||||
|
// Arbitrary unit of space for absolute positioned painting.
|
||||||
|
final double space = 16.0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
double ticksTop = size.height - space * 5;
|
||||||
|
double labelsTop = size.height - space * 2;
|
||||||
|
_drawLine(
|
||||||
|
canvas,
|
||||||
|
Rect.fromLTWH(0.0, 0.0, size.width, ticksTop),
|
||||||
|
);
|
||||||
|
_drawXAxisTicks(
|
||||||
|
canvas,
|
||||||
|
Rect.fromLTWH(0.0, ticksTop, size.width, labelsTop - ticksTop),
|
||||||
|
);
|
||||||
|
_drawXAxisLabels(
|
||||||
|
canvas,
|
||||||
|
Rect.fromLTWH(0.0, labelsTop, size.width, size.height - labelsTop),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(CustomPainter oldDelegate) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _drawLine(Canvas canvas, Rect rect) {
|
||||||
|
final linePaint = Paint()
|
||||||
|
..color = RallyColors.accountColor(2)
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = 2.0;
|
||||||
|
|
||||||
|
// Arbitrary value for the first point. In a real app, a wider range of
|
||||||
|
// points would be used that go beyond the boundaries of the screen.
|
||||||
|
double lastAmount = 800.0;
|
||||||
|
|
||||||
|
// Try changing this value between 1, 7, 15, etc.
|
||||||
|
int smoothing = 7;
|
||||||
|
|
||||||
|
// Align the points with equal deltas (1 day) as a cumulative sum.
|
||||||
|
int startMillis = startDate.millisecondsSinceEpoch;
|
||||||
|
final points = [
|
||||||
|
Offset(0.0, (maxAmount - lastAmount) / maxAmount * rect.height)
|
||||||
|
];
|
||||||
|
for (int i = 0; i < numDays + smoothing; i++) {
|
||||||
|
int endMillis = startMillis + millisInDay * 1;
|
||||||
|
final filteredEvents = events.where((e) {
|
||||||
|
return startMillis <= e.date.millisecondsSinceEpoch &&
|
||||||
|
e.date.millisecondsSinceEpoch <= endMillis;
|
||||||
|
}).toList();
|
||||||
|
lastAmount += filteredEvents.fold<num>(0.0, (sum, e) => sum + e.amount);
|
||||||
|
double x = i / numDays * rect.width;
|
||||||
|
double y = (maxAmount - lastAmount) / maxAmount * rect.height;
|
||||||
|
points.add(Offset(x, y));
|
||||||
|
startMillis = endMillis;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Path path = Path();
|
||||||
|
path.moveTo(points[0].dx, points[0].dy);
|
||||||
|
for (int i = 1; i < points.length - smoothing; i += smoothing) {
|
||||||
|
double x1 = points[i].dx;
|
||||||
|
double y1 = points[i].dy;
|
||||||
|
double x2 = (x1 + points[i + smoothing].dx) / 2;
|
||||||
|
double y2 = (y1 + points[i + smoothing].dy) / 2;
|
||||||
|
path.quadraticBezierTo(x1, y1, x2, y2);
|
||||||
|
}
|
||||||
|
canvas.drawPath(path, linePaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw the X-axis increment markers at constant width intervals.
|
||||||
|
void _drawXAxisTicks(Canvas canvas, Rect rect) {
|
||||||
|
double dayTop = (rect.top + rect.bottom) / 2;
|
||||||
|
for (int i = 0; i < numDays; i++) {
|
||||||
|
double x = rect.width / numDays * i;
|
||||||
|
canvas.drawRect(
|
||||||
|
Rect.fromPoints(
|
||||||
|
Offset(x, i % 7 == tickShift ? rect.top : dayTop),
|
||||||
|
Offset(x, rect.bottom),
|
||||||
|
),
|
||||||
|
Paint()
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = 1.0
|
||||||
|
..color = RallyColors.gray25,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set X-axis labels under the X-axis increment markers.
|
||||||
|
void _drawXAxisLabels(Canvas canvas, Rect rect) {
|
||||||
|
final selectedLabelStyle = Theme.of(context).textTheme.body1.copyWith(
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
);
|
||||||
|
final unselectedLabelStyle = Theme.of(context).textTheme.body1.copyWith(
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: RallyColors.gray25,
|
||||||
|
);
|
||||||
|
|
||||||
|
final leftLabel = TextPainter(
|
||||||
|
text: TextSpan(
|
||||||
|
text: 'AUGUST 2019',
|
||||||
|
style: unselectedLabelStyle,
|
||||||
|
),
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
);
|
||||||
|
leftLabel.layout();
|
||||||
|
leftLabel.paint(canvas, Offset(rect.left + space / 2, rect.center.dy));
|
||||||
|
|
||||||
|
final centerLabel = TextPainter(
|
||||||
|
text: TextSpan(text: 'SEPTEMBER 2019', style: selectedLabelStyle),
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
);
|
||||||
|
centerLabel.layout();
|
||||||
|
final double x = (rect.width - centerLabel.width) / 2;
|
||||||
|
final double y = rect.center.dy;
|
||||||
|
centerLabel.paint(canvas, Offset(x, y));
|
||||||
|
|
||||||
|
final rightLabel = TextPainter(
|
||||||
|
text: TextSpan(
|
||||||
|
text: 'OCTOBER 2019',
|
||||||
|
style: unselectedLabelStyle,
|
||||||
|
),
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
);
|
||||||
|
rightLabel.layout();
|
||||||
|
rightLabel.paint(
|
||||||
|
canvas,
|
||||||
|
Offset(rect.right - centerLabel.width - space / 2, rect.center.dy),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
239
material_studies/rally/lib/charts/pie_chart.dart
Normal file
239
material_studies/rally/lib/charts/pie_chart.dart
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
// Copyright 2019-present the Flutter authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
import 'package:rally/colors.dart';
|
||||||
|
import 'package:rally/data.dart';
|
||||||
|
import 'package:rally/formatters.dart';
|
||||||
|
|
||||||
|
/// A colored piece of the [RallyPieChart].
|
||||||
|
class RallyPieChartSegment {
|
||||||
|
final Color color;
|
||||||
|
final double value;
|
||||||
|
|
||||||
|
const RallyPieChartSegment({this.color, this.value});
|
||||||
|
}
|
||||||
|
|
||||||
|
List<RallyPieChartSegment> buildSegmentsFromAccountItems(
|
||||||
|
List<AccountData> items) {
|
||||||
|
return List<RallyPieChartSegment>.generate(
|
||||||
|
items.length,
|
||||||
|
(i) => RallyPieChartSegment(
|
||||||
|
color: RallyColors.accountColor(i),
|
||||||
|
value: items[i].primaryAmount,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<RallyPieChartSegment> buildSegmentsFromBillItems(List<BillData> items) {
|
||||||
|
return List<RallyPieChartSegment>.generate(
|
||||||
|
items.length,
|
||||||
|
(i) => RallyPieChartSegment(
|
||||||
|
color: RallyColors.billColor(i),
|
||||||
|
value: items[i].primaryAmount,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<RallyPieChartSegment> buildSegmentsFromBudgetItems(
|
||||||
|
List<BudgetData> items) {
|
||||||
|
return List<RallyPieChartSegment>.generate(
|
||||||
|
items.length,
|
||||||
|
(i) => RallyPieChartSegment(
|
||||||
|
color: RallyColors.budgetColor(i),
|
||||||
|
value: items[i].primaryAmount - items[i].amountUsed,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An animated circular pie chart to represent pieces of a whole, which can
|
||||||
|
/// have empty space.
|
||||||
|
class RallyPieChart extends StatefulWidget {
|
||||||
|
RallyPieChart(
|
||||||
|
{this.heroLabel, this.heroAmount, this.wholeAmount, this.segments});
|
||||||
|
|
||||||
|
final String heroLabel;
|
||||||
|
final double heroAmount;
|
||||||
|
final double wholeAmount;
|
||||||
|
final List<RallyPieChartSegment> segments;
|
||||||
|
|
||||||
|
_RallyPieChartState createState() => _RallyPieChartState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RallyPieChartState extends State<RallyPieChart>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
AnimationController controller;
|
||||||
|
Animation<double> animation;
|
||||||
|
|
||||||
|
@override
|
||||||
|
initState() {
|
||||||
|
super.initState();
|
||||||
|
controller = AnimationController(
|
||||||
|
duration: const Duration(milliseconds: 600), vsync: this);
|
||||||
|
animation = CurvedAnimation(
|
||||||
|
parent: TweenSequence(<TweenSequenceItem<double>>[
|
||||||
|
TweenSequenceItem(tween: Tween(begin: 0.0, end: 0.0), weight: 1.0),
|
||||||
|
TweenSequenceItem(tween: Tween(begin: 0.0, end: 1.0), weight: 1.5),
|
||||||
|
]).animate(controller),
|
||||||
|
curve: Curves.decelerate);
|
||||||
|
controller.forward();
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _AnimatedRallyPieChart(
|
||||||
|
animation: animation,
|
||||||
|
centerLabel: widget.heroLabel,
|
||||||
|
centerAmount: widget.heroAmount,
|
||||||
|
total: widget.wholeAmount,
|
||||||
|
segments: widget.segments,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AnimatedRallyPieChart extends AnimatedWidget {
|
||||||
|
_AnimatedRallyPieChart({
|
||||||
|
Key key,
|
||||||
|
this.animation,
|
||||||
|
this.centerLabel,
|
||||||
|
this.centerAmount,
|
||||||
|
this.total,
|
||||||
|
this.segments,
|
||||||
|
}) : super(key: key, listenable: animation);
|
||||||
|
|
||||||
|
final Animation<double> animation;
|
||||||
|
final String centerLabel;
|
||||||
|
final double centerAmount;
|
||||||
|
final double total;
|
||||||
|
final List<RallyPieChartSegment> segments;
|
||||||
|
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final labelTextStyle = Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.body1
|
||||||
|
.copyWith(fontSize: 14.0, letterSpacing: 0.5);
|
||||||
|
|
||||||
|
return DecoratedBox(
|
||||||
|
decoration: _RallyPieChartOutlineDecoration(
|
||||||
|
maxFraction: animation.value, total: total, segments: segments),
|
||||||
|
child: SizedBox(
|
||||||
|
height: 300.0,
|
||||||
|
child: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
centerLabel,
|
||||||
|
style: labelTextStyle,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
Formatters.usdWithSign.format(centerAmount),
|
||||||
|
style: Theme.of(context).textTheme.headline,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RallyPieChartOutlineDecoration extends Decoration {
|
||||||
|
_RallyPieChartOutlineDecoration(
|
||||||
|
{this.maxFraction, this.total, this.segments});
|
||||||
|
|
||||||
|
final double maxFraction;
|
||||||
|
final double total;
|
||||||
|
final List<RallyPieChartSegment> segments;
|
||||||
|
|
||||||
|
@override
|
||||||
|
BoxPainter createBoxPainter([onChanged]) {
|
||||||
|
return _RallyPieChartOutlineBoxPainter(
|
||||||
|
maxFraction: maxFraction,
|
||||||
|
wholeAmount: total,
|
||||||
|
segments: segments,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RallyPieChartOutlineBoxPainter extends BoxPainter {
|
||||||
|
_RallyPieChartOutlineBoxPainter(
|
||||||
|
{this.maxFraction, this.wholeAmount, this.segments});
|
||||||
|
|
||||||
|
final double maxFraction;
|
||||||
|
final double wholeAmount;
|
||||||
|
final List<RallyPieChartSegment> segments;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
|
||||||
|
// Create two padded rects to draw arcs in: one for colored arcs and one for
|
||||||
|
// inner bg arc.
|
||||||
|
const double strokeWidth = 4.0;
|
||||||
|
final outerRadius =
|
||||||
|
min(configuration.size.width, configuration.size.height) / 2;
|
||||||
|
final outerRect = Rect.fromCircle(
|
||||||
|
center: configuration.size.center(Offset.zero),
|
||||||
|
radius: outerRadius - strokeWidth * 3.0);
|
||||||
|
final innerRect = Rect.fromCircle(
|
||||||
|
center: configuration.size.center(Offset.zero),
|
||||||
|
radius: outerRadius - strokeWidth * 4.0);
|
||||||
|
|
||||||
|
// Paint each arc with spacing.
|
||||||
|
double cummulativeSpace = 0.0;
|
||||||
|
double cummulativeTotal = 0.0;
|
||||||
|
const double wholeRadians = (2.0 * pi);
|
||||||
|
const double spaceRadians = wholeRadians / 180.0;
|
||||||
|
final wholeMinusSpacesRadians =
|
||||||
|
wholeRadians - (segments.length * spaceRadians);
|
||||||
|
for (RallyPieChartSegment segment in segments) {
|
||||||
|
final paint = Paint()..color = segment.color;
|
||||||
|
final start = maxFraction *
|
||||||
|
((cummulativeTotal / wholeAmount * wholeMinusSpacesRadians) +
|
||||||
|
cummulativeSpace) -
|
||||||
|
pi / 2.0;
|
||||||
|
final sweep =
|
||||||
|
maxFraction * (segment.value / wholeAmount * wholeMinusSpacesRadians);
|
||||||
|
canvas.drawArc(outerRect, start, sweep, true, paint);
|
||||||
|
cummulativeTotal += segment.value;
|
||||||
|
cummulativeSpace += spaceRadians;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paint any remaining space black (e.g. budget amount remaining).
|
||||||
|
double remaining = wholeAmount - cummulativeTotal;
|
||||||
|
if (remaining > 0) {
|
||||||
|
final paint = Paint()..color = Colors.black;
|
||||||
|
final start = maxFraction *
|
||||||
|
((cummulativeTotal / wholeAmount * wholeMinusSpacesRadians) +
|
||||||
|
spaceRadians * segments.length) -
|
||||||
|
pi / 2.0;
|
||||||
|
final sweep = maxFraction *
|
||||||
|
(remaining / wholeAmount * wholeMinusSpacesRadians - spaceRadians);
|
||||||
|
canvas.drawArc(outerRect, start, sweep, true, paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paint a smaller inner circle to cover the painted arcs, so they are
|
||||||
|
// display as segments.
|
||||||
|
Paint bgPaint = Paint()..color = RallyColors.primaryBackground;
|
||||||
|
canvas.drawArc(innerRect, 0.0, 2.0 * pi, true, bgPaint);
|
||||||
|
}
|
||||||
|
}
|
||||||
45
material_studies/rally/lib/charts/vertical_fraction_bar.dart
Normal file
45
material_studies/rally/lib/charts/vertical_fraction_bar.dart
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
// Copyright 2019-present the Flutter authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
class VerticalFractionBar extends StatelessWidget {
|
||||||
|
VerticalFractionBar({this.color, this.fraction});
|
||||||
|
|
||||||
|
final Color color;
|
||||||
|
final double fraction;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SizedBox(
|
||||||
|
height: 32.0,
|
||||||
|
width: 4.0,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: (1 - fraction) * 32.0,
|
||||||
|
child: Container(
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: fraction * 32.0,
|
||||||
|
child: Container(color: color),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
69
material_studies/rally/lib/colors.dart
Normal file
69
material_studies/rally/lib/colors.dart
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
// Copyright 2019-present the Flutter authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Most color assignments in Rally are not like the the typical color
|
||||||
|
/// assignments that are common in other apps. Instead of primarily mapping to
|
||||||
|
/// component type and part, they are assigned round robin based on layout.
|
||||||
|
class RallyColors {
|
||||||
|
static const accountColors = [
|
||||||
|
Color(0xFF005D57),
|
||||||
|
Color(0xFF04B97F),
|
||||||
|
Color(0xFF37EFBA),
|
||||||
|
Color(0xFF007D51),
|
||||||
|
];
|
||||||
|
|
||||||
|
static const billColors = [
|
||||||
|
Color(0xFFFFDC78),
|
||||||
|
Color(0xFFFF6951),
|
||||||
|
Color(0xFFFFD7D0),
|
||||||
|
Color(0xFFFFAC12),
|
||||||
|
];
|
||||||
|
|
||||||
|
static const budgetColors = [
|
||||||
|
Color(0xFFB2F2FF),
|
||||||
|
Color(0xFFB15DFF),
|
||||||
|
Color(0xFF72DEFF),
|
||||||
|
Color(0xFF0082FB),
|
||||||
|
];
|
||||||
|
|
||||||
|
static const gray = Color(0xFFD8D8D8);
|
||||||
|
static const gray60 = Color(0x99D8D8D8);
|
||||||
|
static const gray25 = Color(0x40D8D8D8);
|
||||||
|
static const white60 = Color(0x99FFFFFF);
|
||||||
|
static const primaryBackground = Color(0xFF33333D);
|
||||||
|
static const inputBackground = Color(0xFF26282F);
|
||||||
|
static const cardBackground = Color(0x03FEFEFE);
|
||||||
|
|
||||||
|
/// Convenience method to get a single account color with position i.
|
||||||
|
static Color accountColor(int i) {
|
||||||
|
return cycledColor(accountColors, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience method to get a single bill color with position i.
|
||||||
|
static Color billColor(int i) {
|
||||||
|
return cycledColor(billColors, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience method to get a single budget color with position i.
|
||||||
|
static Color budgetColor(int i) {
|
||||||
|
return cycledColor(budgetColors, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a color from a list that is considered to be infinitely repeating.
|
||||||
|
static Color cycledColor(List<Color> colors, int i) {
|
||||||
|
return colors[i % colors.length];
|
||||||
|
}
|
||||||
|
}
|
||||||
223
material_studies/rally/lib/data.dart
Normal file
223
material_studies/rally/lib/data.dart
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
// Copyright 2019-present the Flutter authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
/// Calculates the sum of the primary amounts of a list of [AccountData].
|
||||||
|
double sumAccountDataPrimaryAmount(List<AccountData> items) {
|
||||||
|
return items.fold(
|
||||||
|
0,
|
||||||
|
(sum, next) => sum + next.primaryAmount,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculates the sum of the primary amounts of a list of [BillData].
|
||||||
|
double sumBillDataPrimaryAmount(List<BillData> items) {
|
||||||
|
return items.fold(
|
||||||
|
0,
|
||||||
|
(sum, next) => sum + next.primaryAmount,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculates the sum of the primary amounts of a list of [BudgetData].
|
||||||
|
double sumBudgetDataPrimaryAmount(List<BudgetData> items) {
|
||||||
|
return items.fold(
|
||||||
|
0,
|
||||||
|
(sum, next) => sum + next.primaryAmount,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculates the sum of the amounts used of a list of [BudgetData].
|
||||||
|
double sumBudgetDataAmountUsed(List<BudgetData> items) {
|
||||||
|
return items.fold(
|
||||||
|
0.0,
|
||||||
|
(sum, next) => sum + next.amountUsed,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A data model for an account.
|
||||||
|
///
|
||||||
|
/// The [primaryAmount] is the balance of the account in USD.
|
||||||
|
class AccountData {
|
||||||
|
const AccountData({this.name, this.primaryAmount, this.accountNumber});
|
||||||
|
|
||||||
|
/// The display name of this entity.
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
// The primary amount or value of this entity.
|
||||||
|
final double primaryAmount;
|
||||||
|
|
||||||
|
/// The full displayable account number.
|
||||||
|
final String accountNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A data model for a bill.
|
||||||
|
///
|
||||||
|
/// The [primaryAmount] is the amount due in USD.
|
||||||
|
class BillData {
|
||||||
|
const BillData({this.name, this.primaryAmount, this.dueDate});
|
||||||
|
|
||||||
|
/// The display name of this entity.
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
// The primary amount or value of this entity.
|
||||||
|
final double primaryAmount;
|
||||||
|
|
||||||
|
/// The due date of this bill.
|
||||||
|
final String dueDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A data model for a budget.
|
||||||
|
///
|
||||||
|
/// The [primaryAmount] is the budget cap in USD.
|
||||||
|
class BudgetData {
|
||||||
|
const BudgetData({this.name, this.primaryAmount, this.amountUsed});
|
||||||
|
|
||||||
|
/// The display name of this entity.
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
// The primary amount or value of this entity.
|
||||||
|
final double primaryAmount;
|
||||||
|
|
||||||
|
/// Amount of the budget that is consumed or used.
|
||||||
|
final double amountUsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
class DetailedEventData {
|
||||||
|
const DetailedEventData({
|
||||||
|
this.title,
|
||||||
|
this.date,
|
||||||
|
this.amount,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
final DateTime date;
|
||||||
|
final double amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Class to return dummy data lists.
|
||||||
|
///
|
||||||
|
/// In a real app, this might be replaced with some asynchronous service.
|
||||||
|
class DummyDataService {
|
||||||
|
static List<AccountData> getAccountDataList() {
|
||||||
|
return [
|
||||||
|
AccountData(
|
||||||
|
name: 'Checking',
|
||||||
|
primaryAmount: 2215.13,
|
||||||
|
accountNumber: '1234561234',
|
||||||
|
),
|
||||||
|
AccountData(
|
||||||
|
name: 'Home Savings',
|
||||||
|
primaryAmount: 8678.88,
|
||||||
|
accountNumber: '8888885678',
|
||||||
|
),
|
||||||
|
AccountData(
|
||||||
|
name: 'Car Savings',
|
||||||
|
primaryAmount: 987.48,
|
||||||
|
accountNumber: '8888889012',
|
||||||
|
),
|
||||||
|
AccountData(
|
||||||
|
name: 'Vacation',
|
||||||
|
primaryAmount: 253.0,
|
||||||
|
accountNumber: '1231233456',
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<DetailedEventData> getDetailedEventItems() {
|
||||||
|
return [
|
||||||
|
DetailedEventData(
|
||||||
|
title: 'Genoe', date: DateTime.utc(2019, 1, 24), amount: -16.54),
|
||||||
|
DetailedEventData(
|
||||||
|
title: 'Fortnightly Subscribe',
|
||||||
|
date: DateTime.utc(2019, 1, 5),
|
||||||
|
amount: -12.54),
|
||||||
|
DetailedEventData(
|
||||||
|
title: 'Circle Cash', date: DateTime.utc(2019, 1, 5), amount: 365.65),
|
||||||
|
DetailedEventData(
|
||||||
|
title: 'Crane Hospitality',
|
||||||
|
date: DateTime.utc(2019, 1, 4),
|
||||||
|
amount: -705.13),
|
||||||
|
DetailedEventData(
|
||||||
|
title: 'ABC Payroll',
|
||||||
|
date: DateTime.utc(2018, 12, 15),
|
||||||
|
amount: 1141.43),
|
||||||
|
DetailedEventData(
|
||||||
|
title: 'Shrine', date: DateTime.utc(2018, 12, 15), amount: -88.88),
|
||||||
|
DetailedEventData(
|
||||||
|
title: 'Foodmates', date: DateTime.utc(2018, 12, 4), amount: -11.69),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<BillData> getBillDataList() {
|
||||||
|
return [
|
||||||
|
BillData(
|
||||||
|
name: 'RedPay Credit',
|
||||||
|
primaryAmount: 45.36,
|
||||||
|
dueDate: 'Jan 29',
|
||||||
|
),
|
||||||
|
BillData(
|
||||||
|
name: 'Rent',
|
||||||
|
primaryAmount: 1200.0,
|
||||||
|
dueDate: 'Feb 9',
|
||||||
|
),
|
||||||
|
BillData(
|
||||||
|
name: 'TabFine Credit',
|
||||||
|
primaryAmount: 87.33,
|
||||||
|
dueDate: 'Feb 22',
|
||||||
|
),
|
||||||
|
BillData(
|
||||||
|
name: 'ABC Loans',
|
||||||
|
primaryAmount: 400.0,
|
||||||
|
dueDate: 'Feb 29',
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<BudgetData> getBudgetDataList() {
|
||||||
|
return [
|
||||||
|
BudgetData(
|
||||||
|
name: 'Coffee Shops',
|
||||||
|
primaryAmount: 70.0,
|
||||||
|
amountUsed: 45.49,
|
||||||
|
),
|
||||||
|
BudgetData(
|
||||||
|
name: 'Groceries',
|
||||||
|
primaryAmount: 170.0,
|
||||||
|
amountUsed: 16.45,
|
||||||
|
),
|
||||||
|
BudgetData(
|
||||||
|
name: 'Restaurants',
|
||||||
|
primaryAmount: 170.0,
|
||||||
|
amountUsed: 123.25,
|
||||||
|
),
|
||||||
|
BudgetData(
|
||||||
|
name: 'Clothing',
|
||||||
|
primaryAmount: 70.0,
|
||||||
|
amountUsed: 19.45,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<String> getSettingsTitles() {
|
||||||
|
return [
|
||||||
|
'Manage Accounts',
|
||||||
|
'Tax Documents',
|
||||||
|
'Passcode and Touch ID',
|
||||||
|
'Notifications',
|
||||||
|
'Personal Information',
|
||||||
|
'Paperless Settings',
|
||||||
|
'Find ATMs',
|
||||||
|
'Help',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
344
material_studies/rally/lib/finance.dart
Normal file
344
material_studies/rally/lib/finance.dart
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
// Copyright 2019-present the Flutter authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
import 'package:rally/charts/pie_chart.dart';
|
||||||
|
import 'package:rally/charts/line_chart.dart';
|
||||||
|
import 'package:rally/charts/vertical_fraction_bar.dart';
|
||||||
|
import 'package:rally/colors.dart';
|
||||||
|
import 'package:rally/data.dart';
|
||||||
|
import 'package:rally/formatters.dart';
|
||||||
|
|
||||||
|
class FinancialEntityView extends StatelessWidget {
|
||||||
|
FinancialEntityView({
|
||||||
|
this.heroLabel,
|
||||||
|
this.heroAmount,
|
||||||
|
this.wholeAmount,
|
||||||
|
this.segments,
|
||||||
|
this.financialEntityCards,
|
||||||
|
}) : assert(segments.length == financialEntityCards.length);
|
||||||
|
|
||||||
|
/// The amounts to assign each item.
|
||||||
|
///
|
||||||
|
/// This list must have the same length as [colors].
|
||||||
|
final List<RallyPieChartSegment> segments;
|
||||||
|
final String heroLabel;
|
||||||
|
final double heroAmount;
|
||||||
|
final double wholeAmount;
|
||||||
|
final List<FinancialEntityCategoryView> financialEntityCards;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
RallyPieChart(
|
||||||
|
heroLabel: heroLabel,
|
||||||
|
heroAmount: heroAmount,
|
||||||
|
wholeAmount: wholeAmount,
|
||||||
|
segments: segments,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 1.0,
|
||||||
|
child: Container(
|
||||||
|
color: Color(0xA026282F),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListView(shrinkWrap: true, children: financialEntityCards)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A reusable widget to show balance information of a single entity as a card.
|
||||||
|
class FinancialEntityCategoryView extends StatelessWidget {
|
||||||
|
const FinancialEntityCategoryView({
|
||||||
|
@required this.indicatorColor,
|
||||||
|
@required this.indicatorFraction,
|
||||||
|
@required this.title,
|
||||||
|
@required this.subtitle,
|
||||||
|
@required this.amount,
|
||||||
|
@required this.suffix,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Color indicatorColor;
|
||||||
|
final double indicatorFraction;
|
||||||
|
final String title;
|
||||||
|
final String subtitle;
|
||||||
|
final double amount;
|
||||||
|
final Widget suffix;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return FlatButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute<FinancialEntityCategoryDetailsPage>(
|
||||||
|
builder: (context) => FinancialEntityCategoryDetailsPage(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: SizedBox(
|
||||||
|
height: 68,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(left: 12, right: 12),
|
||||||
|
child: VerticalFractionBar(
|
||||||
|
color: indicatorColor,
|
||||||
|
fraction: indicatorFraction,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.body1
|
||||||
|
.copyWith(fontSize: 16.0),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
subtitle,
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.body1
|
||||||
|
.copyWith(color: RallyColors.gray60),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Spacer(),
|
||||||
|
Text(
|
||||||
|
'\$ ' + Formatters.usd.format(amount),
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.body2
|
||||||
|
.copyWith(fontSize: 20.0, color: RallyColors.gray),
|
||||||
|
),
|
||||||
|
SizedBox(width: 32.0, child: suffix),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Divider(
|
||||||
|
height: 1,
|
||||||
|
indent: 16,
|
||||||
|
endIndent: 16,
|
||||||
|
color: Color(0xAA282828),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Data model for [FinancialEntityCategoryView].
|
||||||
|
class FinancialEntityCategoryModel {
|
||||||
|
final Color indicatorColor;
|
||||||
|
final double indicatorFraction;
|
||||||
|
final String title;
|
||||||
|
final String subtitle;
|
||||||
|
final double usdAmount;
|
||||||
|
final Widget suffix;
|
||||||
|
|
||||||
|
const FinancialEntityCategoryModel(
|
||||||
|
this.indicatorColor,
|
||||||
|
this.indicatorFraction,
|
||||||
|
this.title,
|
||||||
|
this.subtitle,
|
||||||
|
this.usdAmount,
|
||||||
|
this.suffix,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
FinancialEntityCategoryView buildFinancialEntityFromAccountData(
|
||||||
|
AccountData model,
|
||||||
|
int i,
|
||||||
|
) {
|
||||||
|
return FinancialEntityCategoryView(
|
||||||
|
suffix: Icon(Icons.chevron_right, color: Colors.grey),
|
||||||
|
title: model.name,
|
||||||
|
subtitle: '• • • • • • ${model.accountNumber.substring(6)}',
|
||||||
|
indicatorColor: RallyColors.accountColor(i),
|
||||||
|
indicatorFraction: 1.0,
|
||||||
|
amount: model.primaryAmount,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
FinancialEntityCategoryView buildFinancialEntityFromBillData(
|
||||||
|
BillData model,
|
||||||
|
int i,
|
||||||
|
) {
|
||||||
|
return FinancialEntityCategoryView(
|
||||||
|
suffix: Icon(Icons.chevron_right, color: Colors.grey),
|
||||||
|
title: model.name,
|
||||||
|
subtitle: model.dueDate,
|
||||||
|
indicatorColor: RallyColors.billColor(i),
|
||||||
|
indicatorFraction: 1.0,
|
||||||
|
amount: model.primaryAmount,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
FinancialEntityCategoryView buildFinancialEntityFromBudgetData(
|
||||||
|
BudgetData item,
|
||||||
|
int i,
|
||||||
|
BuildContext context,
|
||||||
|
) {
|
||||||
|
return FinancialEntityCategoryView(
|
||||||
|
suffix: Text(' LEFT',
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.body1
|
||||||
|
.copyWith(color: RallyColors.gray60, fontSize: 10.0)),
|
||||||
|
title: item.name,
|
||||||
|
subtitle: Formatters.usdWithSign.format(item.amountUsed) +
|
||||||
|
' / ' +
|
||||||
|
Formatters.usdWithSign.format(item.primaryAmount),
|
||||||
|
indicatorColor: RallyColors.budgetColor(i),
|
||||||
|
indicatorFraction: item.amountUsed / item.primaryAmount,
|
||||||
|
amount: item.primaryAmount - item.amountUsed,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<FinancialEntityCategoryView> buildAccountDataListViews(
|
||||||
|
List<AccountData> items) {
|
||||||
|
return List<FinancialEntityCategoryView>.generate(
|
||||||
|
items.length, (i) => buildFinancialEntityFromAccountData(items[i], i));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<FinancialEntityCategoryView> buildBillDataListViews(List<BillData> items) {
|
||||||
|
return List<FinancialEntityCategoryView>.generate(
|
||||||
|
items.length, (i) => buildFinancialEntityFromBillData(items[i], i));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<FinancialEntityCategoryView> buildBudgetDataListViews(
|
||||||
|
List<BudgetData> items, BuildContext context) {
|
||||||
|
return [
|
||||||
|
for (var i = 0; i < items.length; i++)
|
||||||
|
buildFinancialEntityFromBudgetData(items[i], i, context)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
class FinancialEntityCategoryDetailsPage extends StatelessWidget {
|
||||||
|
final List<DetailedEventData> items =
|
||||||
|
DummyDataService.getDetailedEventItems();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final List<_DetailedEventCard> cards = items
|
||||||
|
.map((i) => _DetailedEventCard(
|
||||||
|
title: i.title,
|
||||||
|
subtitle: Formatters.date.format(i.date),
|
||||||
|
amount: i.amount,
|
||||||
|
))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
elevation: 0.0,
|
||||||
|
centerTitle: true,
|
||||||
|
title: Text(
|
||||||
|
'Checking',
|
||||||
|
style: Theme.of(context).textTheme.body1.copyWith(fontSize: 18.0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: Column(children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 200.0,
|
||||||
|
width: double.infinity,
|
||||||
|
child: RallyLineChart(events: items)),
|
||||||
|
Flexible(
|
||||||
|
child: ListView(shrinkWrap: true, children: cards),
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DetailedEventCard extends StatelessWidget {
|
||||||
|
const _DetailedEventCard({
|
||||||
|
@required this.title,
|
||||||
|
@required this.subtitle,
|
||||||
|
@required this.amount,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
final String subtitle;
|
||||||
|
final double amount;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return FlatButton(
|
||||||
|
onPressed: () {},
|
||||||
|
child: SizedBox(
|
||||||
|
height: 68.0,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 67.0,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.body1
|
||||||
|
.copyWith(fontSize: 16.0),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
subtitle,
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.body1
|
||||||
|
.copyWith(color: RallyColors.gray60),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Spacer(),
|
||||||
|
Text(
|
||||||
|
'\$${Formatters.usd.format(amount)}',
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.body2
|
||||||
|
.copyWith(fontSize: 20.0, color: RallyColors.gray),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
child: SizedBox(
|
||||||
|
height: 1.0,
|
||||||
|
child: Container(
|
||||||
|
color: Color(0xAA282828),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
21
material_studies/rally/lib/formatters.dart
Normal file
21
material_studies/rally/lib/formatters.dart
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
// Copyright 2019-present the Flutter authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
class Formatters {
|
||||||
|
static final NumberFormat usd = NumberFormat.currency(name: '');
|
||||||
|
static final NumberFormat usdWithSign = NumberFormat.currency(name: '\$');
|
||||||
|
static final DateFormat date = DateFormat('MM-dd-yy');
|
||||||
|
}
|
||||||
221
material_studies/rally/lib/home.dart
Normal file
221
material_studies/rally/lib/home.dart
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
// Copyright 2019-present the Flutter authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:rally/tabs/accounts.dart';
|
||||||
|
import 'package:rally/tabs/bills.dart';
|
||||||
|
import 'package:rally/tabs/budgets.dart';
|
||||||
|
import 'package:rally/tabs/overview.dart';
|
||||||
|
import 'package:rally/tabs/settings.dart';
|
||||||
|
|
||||||
|
const int tabCount = 5;
|
||||||
|
|
||||||
|
class HomePage extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_HomePageState createState() => _HomePageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HomePageState extends State<HomePage>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
TabController _tabController;
|
||||||
|
|
||||||
|
_HomePageState() {
|
||||||
|
_tabController = TabController(length: tabCount, vsync: this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
print('_HomePageState initState');
|
||||||
|
|
||||||
|
_tabController.addListener(() {
|
||||||
|
if (_tabController.indexIsChanging &&
|
||||||
|
_tabController.previousIndex != _tabController.index) {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
dispose() {
|
||||||
|
_tabController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
body: SafeArea(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Theme(
|
||||||
|
// This theme effectively removes the default visual touch
|
||||||
|
// feedback for tapping a tab, which is replaced with a custom
|
||||||
|
// animation.
|
||||||
|
data: theme.copyWith(
|
||||||
|
splashColor: Colors.transparent,
|
||||||
|
highlightColor: Colors.transparent,
|
||||||
|
),
|
||||||
|
child: TabBar(
|
||||||
|
// Setting isScrollable to true prevents the tabs from being
|
||||||
|
// wrapped in [Expanded] widgets, which allows for more
|
||||||
|
// flexible sizes and size animations among tabs.
|
||||||
|
isScrollable: true,
|
||||||
|
labelPadding: EdgeInsets.zero,
|
||||||
|
tabs: _buildTabs(theme),
|
||||||
|
controller: _tabController,
|
||||||
|
// This removes the tab indicator.
|
||||||
|
indicatorColor: Colors.transparent,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: TabBarView(
|
||||||
|
controller: _tabController,
|
||||||
|
children: _buildTabViews(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildTabs(ThemeData theme) {
|
||||||
|
return <Widget>[
|
||||||
|
_buildTab(theme, Icons.pie_chart, 'OVERVIEW', 0),
|
||||||
|
_buildTab(theme, Icons.attach_money, 'ACCOUNTS', 1),
|
||||||
|
_buildTab(theme, Icons.money_off, 'BILLS', 2),
|
||||||
|
_buildTab(theme, Icons.table_chart, 'BUDGETS', 3),
|
||||||
|
_buildTab(theme, Icons.settings, 'SETTINGS', 4),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildTabViews() {
|
||||||
|
return [
|
||||||
|
OverviewView(),
|
||||||
|
AccountsView(),
|
||||||
|
BillsView(),
|
||||||
|
BudgetsView(),
|
||||||
|
SettingsView(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTab(
|
||||||
|
ThemeData theme,
|
||||||
|
IconData iconData,
|
||||||
|
String title,
|
||||||
|
int index,
|
||||||
|
) {
|
||||||
|
return _RallyTab(
|
||||||
|
theme.textTheme.button,
|
||||||
|
Icon(iconData),
|
||||||
|
title,
|
||||||
|
_tabController.index == index,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RallyTab extends StatefulWidget {
|
||||||
|
final TextStyle style;
|
||||||
|
final Text titleText;
|
||||||
|
final Icon icon;
|
||||||
|
final bool isExpanded;
|
||||||
|
|
||||||
|
_RallyTab(TextStyle style, Icon icon, String title, bool isExpanded)
|
||||||
|
: this.style = style,
|
||||||
|
this.titleText = Text(title, style: style),
|
||||||
|
this.icon = icon,
|
||||||
|
this.isExpanded = isExpanded;
|
||||||
|
|
||||||
|
_RallyTabState createState() => _RallyTabState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RallyTabState extends State<_RallyTab>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
Animation<double> _titleSizeAnimation;
|
||||||
|
Animation<double> _titleFadeAnimation;
|
||||||
|
Animation<double> _iconFadeAnimation;
|
||||||
|
AnimationController _controller;
|
||||||
|
|
||||||
|
@override
|
||||||
|
initState() {
|
||||||
|
super.initState();
|
||||||
|
_controller = AnimationController(
|
||||||
|
duration: Duration(milliseconds: 200),
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
_titleSizeAnimation = _controller.view;
|
||||||
|
_titleFadeAnimation = _controller.drive(CurveTween(curve: Curves.easeOut));
|
||||||
|
_iconFadeAnimation = _controller.drive(Tween(begin: 0.6, end: 1));
|
||||||
|
if (widget.isExpanded) {
|
||||||
|
_controller.value = 1.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(_RallyTab oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
if (widget.isExpanded) {
|
||||||
|
_controller.forward();
|
||||||
|
} else {
|
||||||
|
_controller.reverse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
// Calculate the width of each unexpanded tab by counting the number of
|
||||||
|
// units and dividing it into the screen width. Each unexpanded tab is 1
|
||||||
|
// unit, and there is always 1 expanded tab which is 1 unit + any extra
|
||||||
|
// space determined by the multiplier.
|
||||||
|
final double width = MediaQuery.of(context).size.width;
|
||||||
|
final double expandedTitleWidthMultiplier = 2;
|
||||||
|
final double unitWidth = width / (tabCount + expandedTitleWidthMultiplier);
|
||||||
|
|
||||||
|
return SizedBox(
|
||||||
|
height: 56,
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
FadeTransition(
|
||||||
|
child: SizedBox(
|
||||||
|
width: unitWidth,
|
||||||
|
child: widget.icon,
|
||||||
|
),
|
||||||
|
opacity: _iconFadeAnimation,
|
||||||
|
),
|
||||||
|
FadeTransition(
|
||||||
|
child: SizeTransition(
|
||||||
|
child: SizedBox(
|
||||||
|
width: unitWidth * expandedTitleWidthMultiplier,
|
||||||
|
child: Center(child: widget.titleText),
|
||||||
|
),
|
||||||
|
axis: Axis.horizontal,
|
||||||
|
axisAlignment: -1,
|
||||||
|
sizeFactor: _titleSizeAnimation,
|
||||||
|
),
|
||||||
|
opacity: _titleFadeAnimation,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
dispose() {
|
||||||
|
_controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
68
material_studies/rally/lib/login.dart
Normal file
68
material_studies/rally/lib/login.dart
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
// Copyright 2019-present the Flutter authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class LoginPage extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_LoginPageState createState() => _LoginPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LoginPageState extends State<LoginPage> {
|
||||||
|
final _usernameController = TextEditingController();
|
||||||
|
final _passwordController = TextEditingController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: SafeArea(
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
child: ListView(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 64),
|
||||||
|
child: SizedBox(
|
||||||
|
height: 160,
|
||||||
|
child: Image.asset('assets/logo.png'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextField(
|
||||||
|
controller: _usernameController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'Username',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 12),
|
||||||
|
TextField(
|
||||||
|
controller: _passwordController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'Password',
|
||||||
|
),
|
||||||
|
obscureText: true,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 120,
|
||||||
|
child: Image.asset('assets/thumb.png'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
18
material_studies/rally/lib/main.dart
Normal file
18
material_studies/rally/lib/main.dart
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
// Copyright 2019-present the Flutter authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:rally/app.dart';
|
||||||
|
|
||||||
|
void main() => runApp(RallyApp());
|
||||||
37
material_studies/rally/lib/tabs/accounts.dart
Normal file
37
material_studies/rally/lib/tabs/accounts.dart
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
// Copyright 2019-present the Flutter authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
import 'package:rally/data.dart';
|
||||||
|
import 'package:rally/finance.dart';
|
||||||
|
import 'package:rally/charts/pie_chart.dart';
|
||||||
|
|
||||||
|
/// A page that shows a summary of accounts.
|
||||||
|
class AccountsView extends StatelessWidget {
|
||||||
|
final List<AccountData> items = DummyDataService.getAccountDataList();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
double balanceTotal = sumAccountDataPrimaryAmount(items);
|
||||||
|
return FinancialEntityView(
|
||||||
|
heroLabel: 'Total',
|
||||||
|
heroAmount: balanceTotal,
|
||||||
|
segments: buildSegmentsFromAccountItems(items),
|
||||||
|
wholeAmount: balanceTotal,
|
||||||
|
financialEntityCards: buildAccountDataListViews(items),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
43
material_studies/rally/lib/tabs/bills.dart
Normal file
43
material_studies/rally/lib/tabs/bills.dart
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
// Copyright 2019-present the Flutter authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
import 'package:rally/data.dart';
|
||||||
|
import 'package:rally/finance.dart';
|
||||||
|
import 'package:rally/charts/pie_chart.dart';
|
||||||
|
|
||||||
|
/// A page that shows a summary of bills.
|
||||||
|
class BillsView extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_BillsViewState createState() => _BillsViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BillsViewState extends State<BillsView>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
final List<BillData> items = DummyDataService.getBillDataList();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
double dueTotal = sumBillDataPrimaryAmount(items);
|
||||||
|
return FinancialEntityView(
|
||||||
|
heroLabel: 'Due',
|
||||||
|
heroAmount: dueTotal,
|
||||||
|
segments: buildSegmentsFromBillItems(items),
|
||||||
|
wholeAmount: dueTotal,
|
||||||
|
financialEntityCards: buildBillDataListViews(items),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
43
material_studies/rally/lib/tabs/budgets.dart
Normal file
43
material_studies/rally/lib/tabs/budgets.dart
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
// Copyright 2019-present the Flutter authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
import 'package:rally/charts/pie_chart.dart';
|
||||||
|
import 'package:rally/data.dart';
|
||||||
|
import 'package:rally/finance.dart';
|
||||||
|
|
||||||
|
class BudgetsView extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_BudgetsViewState createState() => _BudgetsViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BudgetsViewState extends State<BudgetsView>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
final List<BudgetData> items = DummyDataService.getBudgetDataList();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
double capTotal = sumBudgetDataPrimaryAmount(items);
|
||||||
|
double usedTotal = sumBudgetDataAmountUsed(items);
|
||||||
|
return FinancialEntityView(
|
||||||
|
heroLabel: 'Left',
|
||||||
|
heroAmount: capTotal - usedTotal,
|
||||||
|
segments: buildSegmentsFromBudgetItems(items),
|
||||||
|
wholeAmount: capTotal,
|
||||||
|
financialEntityCards: buildBudgetDataListViews(items, context),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
151
material_studies/rally/lib/tabs/overview.dart
Normal file
151
material_studies/rally/lib/tabs/overview.dart
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
// Copyright 2019-present the Flutter authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
import 'package:rally/colors.dart';
|
||||||
|
import 'package:rally/data.dart';
|
||||||
|
import 'package:rally/finance.dart';
|
||||||
|
import 'package:rally/formatters.dart';
|
||||||
|
|
||||||
|
/// A page that shows a status overview.
|
||||||
|
class OverviewView extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_OverviewViewState createState() => _OverviewViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _OverviewViewState extends State<OverviewView> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final accountDataList = DummyDataService.getAccountDataList();
|
||||||
|
final billDataList = DummyDataService.getBillDataList();
|
||||||
|
final budgetDataList = DummyDataService.getBudgetDataList();
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
|
child: ListView(children: [
|
||||||
|
_AlertsView(),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
_FinancialView(
|
||||||
|
title: 'Accounts',
|
||||||
|
total: sumAccountDataPrimaryAmount(accountDataList),
|
||||||
|
financialItemViews: buildAccountDataListViews(accountDataList),
|
||||||
|
),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
_FinancialView(
|
||||||
|
title: 'Bills',
|
||||||
|
total: sumBillDataPrimaryAmount(billDataList),
|
||||||
|
financialItemViews: buildBillDataListViews(billDataList),
|
||||||
|
),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
_FinancialView(
|
||||||
|
title: 'Budgets',
|
||||||
|
total: sumBudgetDataPrimaryAmount(budgetDataList),
|
||||||
|
financialItemViews: buildBudgetDataListViews(budgetDataList, context),
|
||||||
|
),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AlertsView extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.only(left: 16, top: 4, bottom: 4),
|
||||||
|
color: RallyColors.cardBackground,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text('Alerts'),
|
||||||
|
FlatButton(
|
||||||
|
onPressed: () {},
|
||||||
|
child: Text('SEE ALL'),
|
||||||
|
textColor: Colors.white,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Container(color: RallyColors.primaryBackground, height: 1),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
'Heads up, you’ve used up 90% of your Shopping budget for '
|
||||||
|
'this month.'),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 100,
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.topRight,
|
||||||
|
child: IconButton(
|
||||||
|
onPressed: () {},
|
||||||
|
icon: Icon(Icons.sort, color: RallyColors.white60),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FinancialView extends StatelessWidget {
|
||||||
|
_FinancialView({this.title, this.total, this.financialItemViews});
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
final double total;
|
||||||
|
final List<FinancialEntityCategoryView> financialItemViews;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
return Container(
|
||||||
|
color: RallyColors.cardBackground,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.all(16),
|
||||||
|
child: Text(title),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(left: 16, right: 16),
|
||||||
|
child: Text(
|
||||||
|
Formatters.usdWithSign.format(total),
|
||||||
|
style: theme.textTheme.body2.copyWith(
|
||||||
|
fontSize: 44.0,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
...financialItemViews.sublist(0, min(financialItemViews.length, 3)),
|
||||||
|
FlatButton(
|
||||||
|
child: Text('SEE ALL'),
|
||||||
|
textColor: Colors.white,
|
||||||
|
onPressed: () {},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
54
material_studies/rally/lib/tabs/settings.dart
Normal file
54
material_studies/rally/lib/tabs/settings.dart
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
// Copyright 2019-present the Flutter authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
import 'package:rally/data.dart';
|
||||||
|
|
||||||
|
class SettingsView extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_SettingsViewState createState() => _SettingsViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SettingsViewState extends State<SettingsView> {
|
||||||
|
List<Widget> items = DummyDataService.getSettingsTitles()
|
||||||
|
.map((title) => _SettingsItem(title))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ListView(children: items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SettingsItem extends StatelessWidget {
|
||||||
|
_SettingsItem(this.title);
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return FlatButton(
|
||||||
|
textColor: Colors.white,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 60,
|
||||||
|
child: Row(children: <Widget>[
|
||||||
|
Text(title),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
onPressed: () {},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
39
material_studies/rally/pubspec.yaml
Normal file
39
material_studies/rally/pubspec.yaml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
name: rally
|
||||||
|
description: Rally
|
||||||
|
version: 1.0.0+1
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: ">=2.2.2 <3.0.0"
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
flutter:
|
||||||
|
sdk: flutter
|
||||||
|
cupertino_icons: ^0.1.2
|
||||||
|
intl: ^0.15.7
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
flutter_test:
|
||||||
|
sdk: flutter
|
||||||
|
|
||||||
|
flutter:
|
||||||
|
uses-material-design: true
|
||||||
|
assets:
|
||||||
|
- assets/logo.png
|
||||||
|
- assets/thumb.png
|
||||||
|
fonts:
|
||||||
|
- family: Roboto Condensed
|
||||||
|
fonts:
|
||||||
|
- asset: fonts/RobotoCondensed-Light.ttf
|
||||||
|
weight: 400
|
||||||
|
- asset: fonts/RobotoCondensed-Regular.ttf
|
||||||
|
weight: 500
|
||||||
|
- asset: fonts/RobotoCondensed-Bold.ttf
|
||||||
|
weight: 700
|
||||||
|
- family: Eczar
|
||||||
|
fonts:
|
||||||
|
- asset: fonts/Eczar-Regular.ttf
|
||||||
|
weight: 400
|
||||||
|
- asset: fonts/Eczar-SemiBold.ttf
|
||||||
|
weight: 600
|
||||||
|
- asset: fonts/Eczar-Bold.ttf
|
||||||
|
weight: 700
|
||||||
18
material_studies/rally/test/widget_test.dart
Normal file
18
material_studies/rally/test/widget_test.dart
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
// This is a basic Flutter widget test.
|
||||||
|
//
|
||||||
|
// To perform an interaction with a widget in your test, use the WidgetTester
|
||||||
|
// utility that Flutter provides. For example, you can send tap and scroll
|
||||||
|
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||||
|
// tree, read text, and verify that the values of widget properties are correct.
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import 'package:rally/app.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('Smoke test', (tester) async {
|
||||||
|
await tester.pumpWidget(RallyApp());
|
||||||
|
expect(find.byType(MaterialApp), findsOneWidget);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ declare -a PROJECT_NAMES=(
|
|||||||
"platform_view_swift" \
|
"platform_view_swift" \
|
||||||
"provider_counter" \
|
"provider_counter" \
|
||||||
"provider_shopper" \
|
"provider_shopper" \
|
||||||
|
"material_studies/rally" \
|
||||||
"material_studies/shrine" \
|
"material_studies/shrine" \
|
||||||
"veggieseasons" \
|
"veggieseasons" \
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user