mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
This reverts commit 399aebc4b7
.
It contained Apache 2.0 licensed files, which aren't allowed in flutter/flutter
This commit is contained in:
parent
ce8e2bb7cf
commit
2c7af1e24e
@ -11,7 +11,6 @@ export 'fortnightly/fortnightly.dart';
|
|||||||
export 'images_demo.dart';
|
export 'images_demo.dart';
|
||||||
export 'material/material.dart';
|
export 'material/material.dart';
|
||||||
export 'pesto_demo.dart';
|
export 'pesto_demo.dart';
|
||||||
export 'rally_demo.dart';
|
|
||||||
export 'shrine_demo.dart';
|
export 'shrine_demo.dart';
|
||||||
export 'transformations/transformations_demo.dart';
|
export 'transformations/transformations_demo.dart';
|
||||||
export 'typography_demo.dart';
|
export 'typography_demo.dart';
|
||||||
|
@ -1,88 +0,0 @@
|
|||||||
// 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_gallery/demo/rally/colors.dart';
|
|
||||||
import 'package:flutter_gallery/demo/rally/home.dart';
|
|
||||||
import 'package:flutter_gallery/demo/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: <String, WidgetBuilder>{
|
|
||||||
'/login': (BuildContext context) => LoginPage(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ThemeData _buildRallyTheme() {
|
|
||||||
final ThemeData base = ThemeData.dark();
|
|
||||||
return ThemeData(
|
|
||||||
scaffoldBackgroundColor: RallyColors.primaryBackground,
|
|
||||||
primaryColor: RallyColors.primaryBackground,
|
|
||||||
textTheme: _buildRallyTextTheme(base.textTheme),
|
|
||||||
inputDecorationTheme: InputDecorationTheme(
|
|
||||||
labelStyle: const 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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,187 +0,0 @@
|
|||||||
// 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_gallery/demo/rally/colors.dart';
|
|
||||||
import 'package:flutter_gallery/demo/rally/data.dart';
|
|
||||||
|
|
||||||
class RallyLineChart extends StatelessWidget {
|
|
||||||
const RallyLineChart({ this.events = const <DetailedEventData>[] }) : assert(events != null);
|
|
||||||
|
|
||||||
final List<DetailedEventData> events;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return CustomPaint(painter: RallyLineChartPainter(Theme.of(context).textTheme.body1, events));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class RallyLineChartPainter extends CustomPainter {
|
|
||||||
RallyLineChartPainter(this.labelStyle, this.events);
|
|
||||||
|
|
||||||
final TextStyle labelStyle;
|
|
||||||
|
|
||||||
// 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; // minAmount is assumed to be 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;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void paint(Canvas canvas, Size size) {
|
|
||||||
final double ticksTop = size.height - space * 5;
|
|
||||||
final double labelsTop = size.height - space * 2;
|
|
||||||
_drawLine(
|
|
||||||
canvas,
|
|
||||||
Rect.fromLTWH(0, 0, size.width, ticksTop),
|
|
||||||
);
|
|
||||||
_drawXAxisTicks(
|
|
||||||
canvas,
|
|
||||||
Rect.fromLTWH(0, ticksTop, size.width, labelsTop - ticksTop),
|
|
||||||
);
|
|
||||||
_drawXAxisLabels(
|
|
||||||
canvas,
|
|
||||||
Rect.fromLTWH(0, labelsTop, size.width, size.height - labelsTop),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Since we're only using fixed dummy data, we can set this to false. In a
|
|
||||||
// real app we would have the data as part of the state and repaint when it's
|
|
||||||
// changed.
|
|
||||||
@override
|
|
||||||
bool shouldRepaint(CustomPainter oldDelegate) => false;
|
|
||||||
|
|
||||||
void _drawLine(Canvas canvas, Rect rect) {
|
|
||||||
final Paint linePaint = Paint()
|
|
||||||
..color = RallyColors.accountColor(2)
|
|
||||||
..style = PaintingStyle.stroke
|
|
||||||
..strokeWidth = 2;
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
// Try changing this value between 1, 7, 15, etc.
|
|
||||||
const int smoothing = 7;
|
|
||||||
|
|
||||||
// Align the points with equal deltas (1 day) as a cumulative sum.
|
|
||||||
int startMillis = startDate.millisecondsSinceEpoch;
|
|
||||||
final List<Offset> points = <Offset>[
|
|
||||||
Offset(0, (maxAmount - lastAmount) / maxAmount * rect.height)
|
|
||||||
];
|
|
||||||
for (int i = 0; i < numDays + smoothing; i++) {
|
|
||||||
final int endMillis = startMillis + millisInDay * 1;
|
|
||||||
final List<DetailedEventData> filteredEvents = events.where(
|
|
||||||
(DetailedEventData e) {
|
|
||||||
return startMillis <= e.date.millisecondsSinceEpoch && e.date.millisecondsSinceEpoch <= endMillis;
|
|
||||||
},
|
|
||||||
).toList();
|
|
||||||
lastAmount += sumOf<DetailedEventData>(filteredEvents, (DetailedEventData e) => e.amount);
|
|
||||||
final double x = i / numDays * rect.width;
|
|
||||||
final 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) {
|
|
||||||
final double x1 = points[i].dx;
|
|
||||||
final double y1 = points[i].dy;
|
|
||||||
final double x2 = (x1 + points[i + smoothing].dx) / 2;
|
|
||||||
final 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) {
|
|
||||||
for (int i = 0; i < numDays; i++) {
|
|
||||||
final double x = rect.width / numDays * i;
|
|
||||||
canvas.drawRect(
|
|
||||||
Rect.fromPoints(
|
|
||||||
Offset(x, i % 7 == tickShift ? rect.top : rect.center.dy),
|
|
||||||
Offset(x, rect.bottom),
|
|
||||||
),
|
|
||||||
Paint()
|
|
||||||
..style = PaintingStyle.stroke
|
|
||||||
..strokeWidth = 1
|
|
||||||
..color = RallyColors.gray25,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set X-axis labels under the X-axis increment markers.
|
|
||||||
void _drawXAxisLabels(Canvas canvas, Rect rect) {
|
|
||||||
final TextStyle selectedLabelStyle = labelStyle.copyWith(
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
);
|
|
||||||
final TextStyle unselectedLabelStyle = labelStyle.copyWith(
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
color: RallyColors.gray25,
|
|
||||||
);
|
|
||||||
|
|
||||||
final TextPainter 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 TextPainter 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 TextPainter 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),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,258 +0,0 @@
|
|||||||
// 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_gallery/demo/rally/colors.dart';
|
|
||||||
import 'package:flutter_gallery/demo/rally/data.dart';
|
|
||||||
import 'package:flutter_gallery/demo/rally/formatters.dart';
|
|
||||||
|
|
||||||
/// A colored piece of the [RallyPieChart].
|
|
||||||
class RallyPieChartSegment {
|
|
||||||
const RallyPieChartSegment({ this.color, this.value });
|
|
||||||
|
|
||||||
final Color color;
|
|
||||||
final double value;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<RallyPieChartSegment> buildSegmentsFromAccountItems(
|
|
||||||
List<AccountData> items) {
|
|
||||||
return List<RallyPieChartSegment>.generate(
|
|
||||||
items.length,
|
|
||||||
(int i) {
|
|
||||||
return RallyPieChartSegment(
|
|
||||||
color: RallyColors.accountColor(i),
|
|
||||||
value: items[i].primaryAmount,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<RallyPieChartSegment> buildSegmentsFromBillItems(List<BillData> items) {
|
|
||||||
return List<RallyPieChartSegment>.generate(
|
|
||||||
items.length,
|
|
||||||
(int i) {
|
|
||||||
return RallyPieChartSegment(
|
|
||||||
color: RallyColors.billColor(i),
|
|
||||||
value: items[i].primaryAmount,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<RallyPieChartSegment> buildSegmentsFromBudgetItems(
|
|
||||||
List<BudgetData> items) {
|
|
||||||
return List<RallyPieChartSegment>.generate(
|
|
||||||
items.length,
|
|
||||||
(int i) {
|
|
||||||
return 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 {
|
|
||||||
const RallyPieChart({ this.heroLabel, this.heroAmount, this.wholeAmount, this.segments });
|
|
||||||
|
|
||||||
final String heroLabel;
|
|
||||||
final double heroAmount;
|
|
||||||
final double wholeAmount;
|
|
||||||
final List<RallyPieChartSegment> segments;
|
|
||||||
|
|
||||||
@override
|
|
||||||
_RallyPieChartState createState() => _RallyPieChartState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _RallyPieChartState extends State<RallyPieChart>
|
|
||||||
with SingleTickerProviderStateMixin {
|
|
||||||
AnimationController controller;
|
|
||||||
Animation<double> animation;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
controller = AnimationController(
|
|
||||||
duration: const Duration(milliseconds: 600),
|
|
||||||
vsync: this,
|
|
||||||
);
|
|
||||||
animation = CurvedAnimation(
|
|
||||||
parent: TweenSequence<double>(<TweenSequenceItem<double>>[
|
|
||||||
TweenSequenceItem<double>(
|
|
||||||
tween: Tween<double>(begin: 0, end: 0),
|
|
||||||
weight: 1,
|
|
||||||
),
|
|
||||||
TweenSequenceItem<double>(
|
|
||||||
tween: Tween<double>(begin: 0, end: 1),
|
|
||||||
weight: 1.5,
|
|
||||||
),
|
|
||||||
]).animate(controller),
|
|
||||||
curve: Curves.decelerate);
|
|
||||||
controller.forward();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
controller.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return _AnimatedRallyPieChart(
|
|
||||||
animation: animation,
|
|
||||||
centerLabel: widget.heroLabel,
|
|
||||||
centerAmount: widget.heroAmount,
|
|
||||||
total: widget.wholeAmount,
|
|
||||||
segments: widget.segments,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _AnimatedRallyPieChart extends AnimatedWidget {
|
|
||||||
const _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;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final TextStyle labelTextStyle = Theme.of(context).textTheme.body1.copyWith(
|
|
||||||
fontSize: 14,
|
|
||||||
letterSpacing: 0.5,
|
|
||||||
);
|
|
||||||
|
|
||||||
return DecoratedBox(
|
|
||||||
decoration: _RallyPieChartOutlineDecoration(
|
|
||||||
maxFraction: animation.value,
|
|
||||||
total: total,
|
|
||||||
segments: segments,
|
|
||||||
),
|
|
||||||
child: SizedBox(
|
|
||||||
height: 300,
|
|
||||||
child: Center(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: <Widget>[
|
|
||||||
Text(
|
|
||||||
centerLabel,
|
|
||||||
style: labelTextStyle,
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
usdWithSignFormat.format(centerAmount),
|
|
||||||
style: Theme.of(context).textTheme.headline,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _RallyPieChartOutlineDecoration extends Decoration {
|
|
||||||
const _RallyPieChartOutlineDecoration({this.maxFraction, this.total, this.segments});
|
|
||||||
|
|
||||||
final double maxFraction;
|
|
||||||
final double total;
|
|
||||||
final List<RallyPieChartSegment> segments;
|
|
||||||
|
|
||||||
@override
|
|
||||||
BoxPainter createBoxPainter([VoidCallback 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;
|
|
||||||
static const double wholeRadians = 2 * pi;
|
|
||||||
static const double spaceRadians = wholeRadians / 180;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
|
|
||||||
// Create two padded reacts to draw arcs in: one for colored arcs and one for
|
|
||||||
// inner bg arc.
|
|
||||||
const double strokeWidth = 4;
|
|
||||||
final double outerRadius = min(
|
|
||||||
configuration.size.width,
|
|
||||||
configuration.size.height,
|
|
||||||
) / 2;
|
|
||||||
final Rect outerRect = Rect.fromCircle(
|
|
||||||
center: configuration.size.center(Offset.zero),
|
|
||||||
radius: outerRadius - strokeWidth * 3,
|
|
||||||
);
|
|
||||||
final Rect innerRect = Rect.fromCircle(
|
|
||||||
center: configuration.size.center(Offset.zero),
|
|
||||||
radius: outerRadius - strokeWidth * 4,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Paint each arc with spacing.
|
|
||||||
double cumulativeSpace = 0;
|
|
||||||
double cumulativeTotal = 0;
|
|
||||||
for (RallyPieChartSegment segment in segments) {
|
|
||||||
final Paint paint = Paint()..color = segment.color;
|
|
||||||
final double startAngle = _calculateStartAngle(cumulativeTotal, cumulativeSpace);
|
|
||||||
final double sweepAngle = _calculateSweepAngle(segment.value, 0);
|
|
||||||
canvas.drawArc(outerRect, startAngle, sweepAngle, true, paint);
|
|
||||||
cumulativeTotal += segment.value;
|
|
||||||
cumulativeSpace += spaceRadians;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Paint any remaining space black (e.g. budget amount remaining).
|
|
||||||
final double remaining = wholeAmount - cumulativeTotal;
|
|
||||||
if (remaining > 0) {
|
|
||||||
final Paint paint = Paint()..color = Colors.black;
|
|
||||||
final double startAngle = _calculateStartAngle(cumulativeTotal, spaceRadians * segments.length);
|
|
||||||
final double sweepAngle = _calculateSweepAngle(remaining, -spaceRadians);
|
|
||||||
canvas.drawArc(outerRect, startAngle, sweepAngle, true, paint);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Paint a smaller inner circle to cover the painted arcs, so they are
|
|
||||||
// display as segments.
|
|
||||||
final Paint bgPaint = Paint()..color = RallyColors.primaryBackground;
|
|
||||||
canvas.drawArc(innerRect, 0, 2 * pi, true, bgPaint);
|
|
||||||
}
|
|
||||||
|
|
||||||
double _calculateAngle(double amount, double offset) {
|
|
||||||
final double wholeMinusSpacesRadians = wholeRadians - (segments.length * spaceRadians);
|
|
||||||
return maxFraction * (amount / wholeAmount * wholeMinusSpacesRadians + offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
double _calculateStartAngle(double total, double offset) => _calculateAngle(total, offset) - pi / 2;
|
|
||||||
|
|
||||||
double _calculateSweepAngle(double total, double offset) => _calculateAngle(total, offset);
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
// 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 VerticalFractionBar extends StatelessWidget {
|
|
||||||
const VerticalFractionBar({ this.color, this.fraction });
|
|
||||||
|
|
||||||
final Color color;
|
|
||||||
final double fraction;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return SizedBox(
|
|
||||||
height: 32,
|
|
||||||
width: 4,
|
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
SizedBox(
|
|
||||||
height: (1 - fraction) * 32,
|
|
||||||
child: Container(
|
|
||||||
color: Colors.black,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
height: fraction * 32,
|
|
||||||
child: Container(color: color),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,69 +0,0 @@
|
|||||||
// 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:ui';
|
|
||||||
|
|
||||||
/// 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 List<Color> accountColors = <Color>[
|
|
||||||
Color(0xFF005D57),
|
|
||||||
Color(0xFF04B97F),
|
|
||||||
Color(0xFF37EFBA),
|
|
||||||
Color(0xFF007D51),
|
|
||||||
];
|
|
||||||
|
|
||||||
static const List<Color> billColors = <Color>[
|
|
||||||
Color(0xFFFFDC78),
|
|
||||||
Color(0xFFFF6951),
|
|
||||||
Color(0xFFFFD7D0),
|
|
||||||
Color(0xFFFFAC12),
|
|
||||||
];
|
|
||||||
|
|
||||||
static const List<Color> budgetColors = <Color>[
|
|
||||||
Color(0xFFB2F2FF),
|
|
||||||
Color(0xFFB15DFF),
|
|
||||||
Color(0xFF72DEFF),
|
|
||||||
Color(0xFF0082FB),
|
|
||||||
];
|
|
||||||
|
|
||||||
static const Color gray = Color(0xFFD8D8D8);
|
|
||||||
static const Color gray60 = Color(0x99D8D8D8);
|
|
||||||
static const Color gray25 = Color(0x40D8D8D8);
|
|
||||||
static const Color white60 = Color(0x99FFFFFF);
|
|
||||||
static const Color primaryBackground = Color(0xFF33333D);
|
|
||||||
static const Color inputBackground = Color(0xFF26282F);
|
|
||||||
static const Color 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];
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,228 +0,0 @@
|
|||||||
// 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) => sumOf<AccountData>(items, (AccountData item) => item.primaryAmount);
|
|
||||||
|
|
||||||
/// Calculates the sum of the primary amounts of a list of [BillData].
|
|
||||||
double sumBillDataPrimaryAmount(List<BillData> items) => sumOf<BillData>(items, (BillData item) => item.primaryAmount);
|
|
||||||
|
|
||||||
/// Calculates the sum of the primary amounts of a list of [BudgetData].
|
|
||||||
double sumBudgetDataPrimaryAmount(List<BudgetData> items) => sumOf<BudgetData>(items, (BudgetData item) => item.primaryAmount);
|
|
||||||
|
|
||||||
/// Calculates the sum of the amounts used of a list of [BudgetData].
|
|
||||||
double sumBudgetDataAmountUsed(List<BudgetData> items) => sumOf<BudgetData>(items, (BudgetData item) => item.amountUsed);
|
|
||||||
|
|
||||||
/// Utility function to sum up values in a list.
|
|
||||||
double sumOf<T>(List<T> list, double getValue(T elt)) {
|
|
||||||
double sum = 0;
|
|
||||||
for (T elt in list)
|
|
||||||
sum += getValue(elt);
|
|
||||||
return sum;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// 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>[
|
|
||||||
const AccountData(
|
|
||||||
name: 'Checking',
|
|
||||||
primaryAmount: 2215.13,
|
|
||||||
accountNumber: '1234561234',
|
|
||||||
),
|
|
||||||
const AccountData(
|
|
||||||
name: 'Home Savings',
|
|
||||||
primaryAmount: 8678.88,
|
|
||||||
accountNumber: '8888885678',
|
|
||||||
),
|
|
||||||
const AccountData(
|
|
||||||
name: 'Car Savings',
|
|
||||||
primaryAmount: 987.48,
|
|
||||||
accountNumber: '8888889012',
|
|
||||||
),
|
|
||||||
const AccountData(
|
|
||||||
name: 'Vacation',
|
|
||||||
primaryAmount: 253,
|
|
||||||
accountNumber: '1231233456',
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
static List<DetailedEventData> getDetailedEventItems() {
|
|
||||||
return <DetailedEventData>[
|
|
||||||
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>[
|
|
||||||
const BillData(
|
|
||||||
name: 'RedPay Credit',
|
|
||||||
primaryAmount: 45.36,
|
|
||||||
dueDate: 'Jan 29',
|
|
||||||
),
|
|
||||||
const BillData(
|
|
||||||
name: 'Rent',
|
|
||||||
primaryAmount: 1200,
|
|
||||||
dueDate: 'Feb 9',
|
|
||||||
),
|
|
||||||
const BillData(
|
|
||||||
name: 'TabFine Credit',
|
|
||||||
primaryAmount: 87.33,
|
|
||||||
dueDate: 'Feb 22',
|
|
||||||
),
|
|
||||||
const BillData(
|
|
||||||
name: 'ABC Loans',
|
|
||||||
primaryAmount: 400,
|
|
||||||
dueDate: 'Feb 29',
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
static List<BudgetData> getBudgetDataList() {
|
|
||||||
return <BudgetData>[
|
|
||||||
const BudgetData(
|
|
||||||
name: 'Coffee Shops',
|
|
||||||
primaryAmount: 70,
|
|
||||||
amountUsed: 45.49,
|
|
||||||
),
|
|
||||||
const BudgetData(
|
|
||||||
name: 'Groceries',
|
|
||||||
primaryAmount: 170,
|
|
||||||
amountUsed: 16.45,
|
|
||||||
),
|
|
||||||
const BudgetData(
|
|
||||||
name: 'Restaurants',
|
|
||||||
primaryAmount: 170,
|
|
||||||
amountUsed: 123.25,
|
|
||||||
),
|
|
||||||
const BudgetData(
|
|
||||||
name: 'Clothing',
|
|
||||||
primaryAmount: 70,
|
|
||||||
amountUsed: 19.45,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
static List<String> getSettingsTitles() {
|
|
||||||
return <String>[
|
|
||||||
'Manage Accounts',
|
|
||||||
'Tax Documents',
|
|
||||||
'Passcode and Touch ID',
|
|
||||||
'Notifications',
|
|
||||||
'Personal Information',
|
|
||||||
'Paperless Settings',
|
|
||||||
'Find ATMs',
|
|
||||||
'Help',
|
|
||||||
'Sign out',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,330 +0,0 @@
|
|||||||
// 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_gallery/demo/rally/charts/pie_chart.dart';
|
|
||||||
import 'package:flutter_gallery/demo/rally/charts/line_chart.dart';
|
|
||||||
import 'package:flutter_gallery/demo/rally/charts/vertical_fraction_bar.dart';
|
|
||||||
import 'package:flutter_gallery/demo/rally/colors.dart';
|
|
||||||
import 'package:flutter_gallery/demo/rally/data.dart';
|
|
||||||
import 'package:flutter_gallery/demo/rally/formatters.dart';
|
|
||||||
|
|
||||||
class FinancialEntityView extends StatelessWidget {
|
|
||||||
const FinancialEntityView({
|
|
||||||
this.heroLabel,
|
|
||||||
this.heroAmount,
|
|
||||||
this.wholeAmount,
|
|
||||||
this.segments,
|
|
||||||
this.financialEntityCards,
|
|
||||||
}) : assert(segments.length == financialEntityCards.length);
|
|
||||||
|
|
||||||
/// The amounts to assign each item.
|
|
||||||
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: <Widget>[
|
|
||||||
RallyPieChart(
|
|
||||||
heroLabel: heroLabel,
|
|
||||||
heroAmount: heroAmount,
|
|
||||||
wholeAmount: wholeAmount,
|
|
||||||
segments: segments,
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
height: 1,
|
|
||||||
child: Container(
|
|
||||||
color: const 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: (BuildContext context) => FinancialEntityCategoryDetailsPage(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: SizedBox(
|
|
||||||
height: 68,
|
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
Expanded(
|
|
||||||
child: Row(
|
|
||||||
children: <Widget>[
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 12, right: 12),
|
|
||||||
child: VerticalFractionBar(
|
|
||||||
color: indicatorColor,
|
|
||||||
fraction: indicatorFraction,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: <Widget>[
|
|
||||||
Text(
|
|
||||||
title,
|
|
||||||
style: Theme.of(context).textTheme.body1.copyWith(fontSize: 16),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
subtitle,
|
|
||||||
style: Theme.of(context).textTheme.body1.copyWith(color: RallyColors.gray60),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
Text(
|
|
||||||
'\$ ' + usdFormat.format(amount),
|
|
||||||
style: Theme.of(context).textTheme.body2.copyWith(fontSize: 20, color: RallyColors.gray),
|
|
||||||
),
|
|
||||||
SizedBox(width: 32, child: suffix),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Divider(
|
|
||||||
height: 1,
|
|
||||||
indent: 16,
|
|
||||||
endIndent: 16,
|
|
||||||
color: Color(0xAA282828),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Data model for [FinancialEntityCategoryView].
|
|
||||||
class FinancialEntityCategoryModel {
|
|
||||||
const FinancialEntityCategoryModel(
|
|
||||||
this.indicatorColor,
|
|
||||||
this.indicatorFraction,
|
|
||||||
this.title,
|
|
||||||
this.subtitle,
|
|
||||||
this.usdAmount,
|
|
||||||
this.suffix,
|
|
||||||
);
|
|
||||||
|
|
||||||
final Color indicatorColor;
|
|
||||||
final double indicatorFraction;
|
|
||||||
final String title;
|
|
||||||
final String subtitle;
|
|
||||||
final double usdAmount;
|
|
||||||
final Widget suffix;
|
|
||||||
}
|
|
||||||
|
|
||||||
FinancialEntityCategoryView buildFinancialEntityFromAccountData(
|
|
||||||
AccountData model,
|
|
||||||
int accountDataIndex,
|
|
||||||
) {
|
|
||||||
return FinancialEntityCategoryView(
|
|
||||||
suffix: const Icon(Icons.chevron_right, color: Colors.grey),
|
|
||||||
title: model.name,
|
|
||||||
subtitle: '• • • • • • ${model.accountNumber.substring(6)}',
|
|
||||||
indicatorColor: RallyColors.accountColor(accountDataIndex),
|
|
||||||
indicatorFraction: 1,
|
|
||||||
amount: model.primaryAmount,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
FinancialEntityCategoryView buildFinancialEntityFromBillData(
|
|
||||||
BillData model,
|
|
||||||
int billDataInex,
|
|
||||||
) {
|
|
||||||
return FinancialEntityCategoryView(
|
|
||||||
suffix: const Icon(Icons.chevron_right, color: Colors.grey),
|
|
||||||
title: model.name,
|
|
||||||
subtitle: model.dueDate,
|
|
||||||
indicatorColor: RallyColors.billColor(billDataInex),
|
|
||||||
indicatorFraction: 1,
|
|
||||||
amount: model.primaryAmount,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
FinancialEntityCategoryView buildFinancialEntityFromBudgetData(
|
|
||||||
BudgetData item,
|
|
||||||
int budgetDataIndex,
|
|
||||||
BuildContext context,
|
|
||||||
) {
|
|
||||||
final String amountUsed = usdWithSignFormat.format(item.amountUsed);
|
|
||||||
final String primaryAmount = usdWithSignFormat.format(item.primaryAmount);
|
|
||||||
|
|
||||||
return FinancialEntityCategoryView(
|
|
||||||
suffix: Text(
|
|
||||||
' LEFT',
|
|
||||||
style: Theme.of(context).textTheme.body1.copyWith(color: RallyColors.gray60, fontSize: 10),
|
|
||||||
),
|
|
||||||
title: item.name,
|
|
||||||
subtitle: amountUsed + ' / ' + primaryAmount,
|
|
||||||
indicatorColor: RallyColors.budgetColor(budgetDataIndex),
|
|
||||||
indicatorFraction: item.amountUsed / item.primaryAmount,
|
|
||||||
amount: item.primaryAmount - item.amountUsed,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<FinancialEntityCategoryView> buildAccountDataListViews(
|
|
||||||
List<AccountData> items) {
|
|
||||||
return List<FinancialEntityCategoryView>.generate(
|
|
||||||
items.length,
|
|
||||||
(int i) => buildFinancialEntityFromAccountData(items[i], i),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<FinancialEntityCategoryView> buildBillDataListViews(List<BillData> items) {
|
|
||||||
return List<FinancialEntityCategoryView>.generate(
|
|
||||||
items.length,
|
|
||||||
(int i) => buildFinancialEntityFromBillData(items[i], i),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<FinancialEntityCategoryView> buildBudgetDataListViews(
|
|
||||||
List<BudgetData> items, BuildContext context) {
|
|
||||||
return <FinancialEntityCategoryView>[
|
|
||||||
for (int 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((DetailedEventData detailedEventData) {
|
|
||||||
return _DetailedEventCard(
|
|
||||||
title: detailedEventData.title,
|
|
||||||
subtitle: dateFormat.format(detailedEventData.date),
|
|
||||||
amount: detailedEventData.amount,
|
|
||||||
);
|
|
||||||
}).toList();
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
elevation: 0,
|
|
||||||
centerTitle: true,
|
|
||||||
title: Text(
|
|
||||||
'Checking',
|
|
||||||
style: Theme.of(context).textTheme.body1.copyWith(fontSize: 18),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
SizedBox(
|
|
||||||
height: 200,
|
|
||||||
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) {
|
|
||||||
final TextTheme textTheme = Theme.of(context).textTheme;
|
|
||||||
return FlatButton(
|
|
||||||
onPressed: () {},
|
|
||||||
child: SizedBox(
|
|
||||||
height: 68,
|
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
SizedBox(
|
|
||||||
height: 67,
|
|
||||||
child: Row(
|
|
||||||
children: <Widget>[
|
|
||||||
Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: <Widget>[
|
|
||||||
Text(
|
|
||||||
title,
|
|
||||||
style: textTheme.body1.copyWith(fontSize: 16),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
subtitle,
|
|
||||||
style: textTheme.body1.copyWith(color: RallyColors.gray60),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
Text(
|
|
||||||
'\$${usdFormat.format(amount)}',
|
|
||||||
style: textTheme.body2.copyWith(fontSize: 20, color: RallyColors.gray),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
child: SizedBox(
|
|
||||||
height: 1,
|
|
||||||
child: Container(
|
|
||||||
color: const Color(0xAA282828),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
// 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';
|
|
||||||
|
|
||||||
NumberFormat usdFormat = NumberFormat.currency(name: '');
|
|
||||||
NumberFormat usdWithSignFormat = NumberFormat.currency(name: '\$');
|
|
||||||
DateFormat dateFormat = DateFormat('MM-dd-yy');
|
|
@ -1,206 +0,0 @@
|
|||||||
// 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_gallery/demo/rally/tabs/accounts.dart';
|
|
||||||
import 'package:flutter_gallery/demo/rally/tabs/bills.dart';
|
|
||||||
import 'package:flutter_gallery/demo/rally/tabs/budgets.dart';
|
|
||||||
import 'package:flutter_gallery/demo/rally/tabs/overview.dart';
|
|
||||||
import 'package:flutter_gallery/demo/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;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_tabController = TabController(length: tabCount, vsync: this)
|
|
||||||
..addListener(() {
|
|
||||||
// Set state to make sure that the [_RallyTab] widgets get updated when changing tabs.
|
|
||||||
setState(() { });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_tabController.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final ThemeData theme = Theme.of(context);
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
body: SafeArea(
|
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
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 hides the tab indicator.
|
|
||||||
indicatorColor: Colors.transparent,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: TabBarView(
|
|
||||||
controller: _tabController,
|
|
||||||
children: _buildTabViews(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Widget> _buildTabs(ThemeData theme) {
|
|
||||||
return <Widget>[
|
|
||||||
_RallyTab(theme, Icons.pie_chart, 'OVERVIEW', 0, _tabController),
|
|
||||||
_RallyTab(theme, Icons.attach_money, 'ACCOUNTS', 1, _tabController),
|
|
||||||
_RallyTab(theme, Icons.money_off, 'BILLS', 2, _tabController),
|
|
||||||
_RallyTab(theme, Icons.table_chart, 'BUDGETS', 3, _tabController),
|
|
||||||
_RallyTab(theme, Icons.settings, 'SETTINGS', 4, _tabController),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Widget> _buildTabViews() {
|
|
||||||
return <Widget>[
|
|
||||||
OverviewView(),
|
|
||||||
AccountsView(),
|
|
||||||
BillsView(),
|
|
||||||
BudgetsView(),
|
|
||||||
SettingsView(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _RallyTab extends StatefulWidget {
|
|
||||||
_RallyTab(
|
|
||||||
ThemeData theme,
|
|
||||||
IconData iconData,
|
|
||||||
String title,
|
|
||||||
int tabIndex,
|
|
||||||
TabController tabController,
|
|
||||||
) : titleText = Text(title, style: theme.textTheme.button),
|
|
||||||
isExpanded = tabController.index == tabIndex,
|
|
||||||
icon = Icon(iconData);
|
|
||||||
|
|
||||||
final Text titleText;
|
|
||||||
final Icon icon;
|
|
||||||
final bool isExpanded;
|
|
||||||
|
|
||||||
@override
|
|
||||||
_RallyTabState createState() => _RallyTabState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _RallyTabState extends State<_RallyTab>
|
|
||||||
with SingleTickerProviderStateMixin {
|
|
||||||
Animation<double> _titleSizeAnimation;
|
|
||||||
Animation<double> _titleFadeAnimation;
|
|
||||||
Animation<double> _iconFadeAnimation;
|
|
||||||
AnimationController _controller;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_controller = AnimationController(
|
|
||||||
duration: const Duration(milliseconds: 200),
|
|
||||||
vsync: this,
|
|
||||||
);
|
|
||||||
_titleSizeAnimation = _controller.view;
|
|
||||||
_titleFadeAnimation = _controller.drive(CurveTween(curve: Curves.easeOut));
|
|
||||||
_iconFadeAnimation = _controller.drive(Tween<double>(begin: 0.6, end: 1));
|
|
||||||
if (widget.isExpanded) {
|
|
||||||
_controller.value = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didUpdateWidget(_RallyTab oldWidget) {
|
|
||||||
super.didUpdateWidget(oldWidget);
|
|
||||||
if (widget.isExpanded) {
|
|
||||||
_controller.forward();
|
|
||||||
} else {
|
|
||||||
_controller.reverse();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
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;
|
|
||||||
const 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
|
|
||||||
void dispose() {
|
|
||||||
_controller.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,91 +0,0 @@
|
|||||||
// 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 TextEditingController _usernameController = TextEditingController();
|
|
||||||
final TextEditingController _passwordController = TextEditingController();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
elevation: 0,
|
|
||||||
leading: IconButton(
|
|
||||||
icon: const BackButtonIcon(),
|
|
||||||
tooltip: MaterialLocalizations.of(context).backButtonTooltip,
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context, rootNavigator: true).pop();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: SafeArea(
|
|
||||||
child: GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
child: ListView(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
|
||||||
children: <Widget>[
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 64),
|
|
||||||
child: SizedBox(
|
|
||||||
height: 160,
|
|
||||||
child: Image.asset(
|
|
||||||
'logo.png',
|
|
||||||
package: 'rally_assets',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextField(
|
|
||||||
controller: _usernameController,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: 'Username',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
TextField(
|
|
||||||
controller: _passwordController,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: 'Password',
|
|
||||||
),
|
|
||||||
obscureText: true,
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
height: 120,
|
|
||||||
child: Image.asset(
|
|
||||||
'thumb.png',
|
|
||||||
package: 'rally_assets',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_usernameController.dispose();
|
|
||||||
_passwordController.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
// 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/widgets.dart';
|
|
||||||
import 'package:flutter_gallery/demo/rally/app.dart';
|
|
||||||
|
|
||||||
void main() => runApp(RallyApp());
|
|
@ -1,36 +0,0 @@
|
|||||||
// 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/widgets.dart';
|
|
||||||
|
|
||||||
import 'package:flutter_gallery/demo/rally/data.dart';
|
|
||||||
import 'package:flutter_gallery/demo/rally/finance.dart';
|
|
||||||
import 'package:flutter_gallery/demo/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) {
|
|
||||||
final double balanceTotal = sumAccountDataPrimaryAmount(items);
|
|
||||||
return FinancialEntityView(
|
|
||||||
heroLabel: 'Total',
|
|
||||||
heroAmount: balanceTotal,
|
|
||||||
segments: buildSegmentsFromAccountItems(items),
|
|
||||||
wholeAmount: balanceTotal,
|
|
||||||
financialEntityCards: buildAccountDataListViews(items),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
// 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/widgets.dart';
|
|
||||||
import 'package:flutter_gallery/demo/rally/data.dart';
|
|
||||||
import 'package:flutter_gallery/demo/rally/finance.dart';
|
|
||||||
import 'package:flutter_gallery/demo/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) {
|
|
||||||
final double dueTotal = sumBillDataPrimaryAmount(items);
|
|
||||||
return FinancialEntityView(
|
|
||||||
heroLabel: 'Due',
|
|
||||||
heroAmount: dueTotal,
|
|
||||||
segments: buildSegmentsFromBillItems(items),
|
|
||||||
wholeAmount: dueTotal,
|
|
||||||
financialEntityCards: buildBillDataListViews(items),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
// 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/widgets.dart';
|
|
||||||
|
|
||||||
import 'package:flutter_gallery/demo/rally/charts/pie_chart.dart';
|
|
||||||
import 'package:flutter_gallery/demo/rally/data.dart';
|
|
||||||
import 'package:flutter_gallery/demo/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) {
|
|
||||||
final double capTotal = sumBudgetDataPrimaryAmount(items);
|
|
||||||
final double usedTotal = sumBudgetDataAmountUsed(items);
|
|
||||||
return FinancialEntityView(
|
|
||||||
heroLabel: 'Left',
|
|
||||||
heroAmount: capTotal - usedTotal,
|
|
||||||
segments: buildSegmentsFromBudgetItems(items),
|
|
||||||
wholeAmount: capTotal,
|
|
||||||
financialEntityCards: buildBudgetDataListViews(items, context),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,151 +0,0 @@
|
|||||||
// 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_gallery/demo/rally/colors.dart';
|
|
||||||
import 'package:flutter_gallery/demo/rally/data.dart';
|
|
||||||
import 'package:flutter_gallery/demo/rally/finance.dart';
|
|
||||||
import 'package:flutter_gallery/demo/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 List<AccountData> accountDataList = DummyDataService.getAccountDataList();
|
|
||||||
final List<BillData> billDataList = DummyDataService.getBillDataList();
|
|
||||||
final List<BudgetData> budgetDataList = DummyDataService.getBudgetDataList();
|
|
||||||
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
||||||
child: ListView(
|
|
||||||
children: <Widget>[
|
|
||||||
_AlertsView(),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
_FinancialView(
|
|
||||||
title: 'Accounts',
|
|
||||||
total: sumAccountDataPrimaryAmount(accountDataList),
|
|
||||||
financialItemViews: buildAccountDataListViews(accountDataList),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
_FinancialView(
|
|
||||||
title: 'Bills',
|
|
||||||
total: sumBillDataPrimaryAmount(billDataList),
|
|
||||||
financialItemViews: buildBillDataListViews(billDataList),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
_FinancialView(
|
|
||||||
title: 'Budgets',
|
|
||||||
total: sumBudgetDataPrimaryAmount(budgetDataList),
|
|
||||||
financialItemViews:
|
|
||||||
buildBudgetDataListViews(budgetDataList, context),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _AlertsView extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.only(left: 16, top: 4, bottom: 4),
|
|
||||||
color: RallyColors.cardBackground,
|
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: <Widget>[
|
|
||||||
const Text('Alerts'),
|
|
||||||
FlatButton(
|
|
||||||
onPressed: () {},
|
|
||||||
child: const Text('SEE ALL'),
|
|
||||||
textColor: Colors.white,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Container(color: RallyColors.primaryBackground, height: 1),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: <Widget>[
|
|
||||||
const 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 {
|
|
||||||
const _FinancialView({this.title, this.total, this.financialItemViews});
|
|
||||||
|
|
||||||
final String title;
|
|
||||||
final double total;
|
|
||||||
final List<FinancialEntityCategoryView> financialItemViews;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final ThemeData theme = Theme.of(context);
|
|
||||||
return Container(
|
|
||||||
color: RallyColors.cardBackground,
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: <Widget>[
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Text(title),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 16, right: 16),
|
|
||||||
child: Text(
|
|
||||||
usdWithSignFormat.format(total),
|
|
||||||
style: theme.textTheme.body2.copyWith(
|
|
||||||
fontSize: 44,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
...financialItemViews.sublist(0, min(financialItemViews.length, 3)),
|
|
||||||
FlatButton(
|
|
||||||
child: const Text('SEE ALL'),
|
|
||||||
textColor: Colors.white,
|
|
||||||
onPressed: () {},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
// 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_gallery/demo/rally/data.dart';
|
|
||||||
import 'package:flutter_gallery/demo/rally/login.dart';
|
|
||||||
|
|
||||||
class SettingsView extends StatefulWidget {
|
|
||||||
@override
|
|
||||||
_SettingsViewState createState() => _SettingsViewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SettingsViewState extends State<SettingsView> {
|
|
||||||
List<Widget> items = DummyDataService.getSettingsTitles()
|
|
||||||
.map((String title) => _SettingsItem(title))
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return ListView(children: items);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SettingsItem extends StatelessWidget {
|
|
||||||
const _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: () {
|
|
||||||
Navigator.push<void>(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute<void>(builder: (BuildContext context) => LoginPage()),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
// Copyright 2019 The Chromium Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_gallery/demo/rally/app.dart';
|
|
||||||
|
|
||||||
class RallyDemo extends StatelessWidget {
|
|
||||||
const RallyDemo({ Key key }) : super(key: key);
|
|
||||||
|
|
||||||
static const String routeName = '/rally'; // Used by the Gallery app.
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) => RallyApp();
|
|
||||||
}
|
|
@ -139,14 +139,6 @@ List<GalleryDemo> _buildGalleryDemos() {
|
|||||||
routeName: PestoDemo.routeName,
|
routeName: PestoDemo.routeName,
|
||||||
buildRoute: (BuildContext context) => const PestoDemo(),
|
buildRoute: (BuildContext context) => const PestoDemo(),
|
||||||
),
|
),
|
||||||
GalleryDemo(
|
|
||||||
title: 'Rally',
|
|
||||||
subtitle: 'A personal finance app',
|
|
||||||
icon: GalleryIcons.data_table,
|
|
||||||
category: _kDemos,
|
|
||||||
routeName: RallyDemo.routeName,
|
|
||||||
buildRoute: (BuildContext context) => const RallyDemo(),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Style
|
// Style
|
||||||
GalleryDemo(
|
GalleryDemo(
|
||||||
|
@ -17,7 +17,6 @@ dependencies:
|
|||||||
video_player: 0.10.2+5
|
video_player: 0.10.2+5
|
||||||
scoped_model: 1.0.1
|
scoped_model: 1.0.1
|
||||||
shrine_images: 1.1.2
|
shrine_images: 1.1.2
|
||||||
rally_assets: 1.0.0
|
|
||||||
|
|
||||||
# Also update dev/benchmarks/complex_layout/pubspec.yaml
|
# Also update dev/benchmarks/complex_layout/pubspec.yaml
|
||||||
# and dev/benchmarks/macrobenchmarks/pubspec.yaml
|
# and dev/benchmarks/macrobenchmarks/pubspec.yaml
|
||||||
@ -204,8 +203,6 @@ flutter:
|
|||||||
- packages/shrine_images/35-0.jpg
|
- packages/shrine_images/35-0.jpg
|
||||||
- packages/shrine_images/36-0.jpg
|
- packages/shrine_images/36-0.jpg
|
||||||
- packages/shrine_images/37-0.jpg
|
- packages/shrine_images/37-0.jpg
|
||||||
- packages/rally_assets/logo.png
|
|
||||||
- packages/rally_assets/thumb.png
|
|
||||||
|
|
||||||
fonts:
|
fonts:
|
||||||
- family: Raleway
|
- family: Raleway
|
||||||
@ -267,21 +264,5 @@ flutter:
|
|||||||
- asset: packages/flutter_gallery_assets/fonts/merriweather/Merriweather-Italic.ttf
|
- asset: packages/flutter_gallery_assets/fonts/merriweather/Merriweather-Italic.ttf
|
||||||
- asset: packages/flutter_gallery_assets/fonts/merriweather/Merriweather-Regular.ttf
|
- asset: packages/flutter_gallery_assets/fonts/merriweather/Merriweather-Regular.ttf
|
||||||
- asset: packages/flutter_gallery_assets/fonts/merriweather/Merriweather-Light.ttf
|
- asset: packages/flutter_gallery_assets/fonts/merriweather/Merriweather-Light.ttf
|
||||||
- family: Roboto Condensed
|
|
||||||
fonts:
|
|
||||||
- asset: packages/rally_assets/RobotoCondensed-Light.ttf
|
|
||||||
weight: 400
|
|
||||||
- asset: packages/rally_assets/RobotoCondensed-Regular.ttf
|
|
||||||
weight: 500
|
|
||||||
- asset: packages/rally_assets/RobotoCondensed-Bold.ttf
|
|
||||||
weight: 700
|
|
||||||
- family: Eczar
|
|
||||||
fonts:
|
|
||||||
- asset: packages/rally_assets/Eczar-Regular.ttf
|
|
||||||
weight: 400
|
|
||||||
- asset: packages/rally_assets/Eczar-SemiBold.ttf
|
|
||||||
weight: 600
|
|
||||||
- asset: packages/rally_assets/Eczar-Bold.ttf
|
|
||||||
weight: 700
|
|
||||||
|
|
||||||
# PUBSPEC CHECKSUM: de7e
|
# PUBSPEC CHECKSUM: 1e1b
|
||||||
|
Loading…
Reference in New Issue
Block a user