mirror of https://github.com/flutter/samples.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
466 lines
14 KiB
466 lines
14 KiB
// Copyright 2019 The Flutter team. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import 'dart:math' as math;
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
|
|
import 'package:gallery/data/gallery_options.dart';
|
|
import 'package:gallery/l10n/gallery_localizations.dart';
|
|
import 'package:gallery/layout/adaptive.dart';
|
|
import 'package:gallery/layout/text_scale.dart';
|
|
import 'package:gallery/studies/rally/charts/line_chart.dart';
|
|
import 'package:gallery/studies/rally/charts/pie_chart.dart';
|
|
import 'package:gallery/studies/rally/charts/vertical_fraction_bar.dart';
|
|
import 'package:gallery/studies/rally/colors.dart';
|
|
import 'package:gallery/studies/rally/data.dart';
|
|
import 'package:gallery/studies/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) {
|
|
final maxWidth = pieChartMaxSize + (cappedTextScale(context) - 1.0) * 100.0;
|
|
return LayoutBuilder(builder: (context, constraints) {
|
|
return Column(
|
|
children: [
|
|
ConstrainedBox(
|
|
constraints: BoxConstraints(
|
|
// We decrease the max height to ensure the [RallyPieChart] does
|
|
// not take up the full height when it is smaller than
|
|
// [kPieChartMaxSize].
|
|
maxHeight: math.min(
|
|
constraints.biggest.shortestSide * 0.9,
|
|
maxWidth,
|
|
),
|
|
),
|
|
child: RallyPieChart(
|
|
heroLabel: heroLabel,
|
|
heroAmount: heroAmount,
|
|
wholeAmount: wholeAmount,
|
|
segments: segments,
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
Container(
|
|
height: 1,
|
|
constraints: BoxConstraints(maxWidth: maxWidth),
|
|
color: RallyColors.inputBackground,
|
|
),
|
|
Container(
|
|
constraints: BoxConstraints(maxWidth: maxWidth),
|
|
color: RallyColors.cardBackground,
|
|
child: Column(
|
|
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.semanticsLabel,
|
|
@required this.amount,
|
|
@required this.suffix,
|
|
});
|
|
|
|
final Color indicatorColor;
|
|
final double indicatorFraction;
|
|
final String title;
|
|
final String subtitle;
|
|
final String semanticsLabel;
|
|
final String amount;
|
|
final Widget suffix;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final textTheme = Theme.of(context).textTheme;
|
|
return Semantics.fromProperties(
|
|
properties: SemanticsProperties(
|
|
button: true,
|
|
label: semanticsLabel,
|
|
),
|
|
excludeSemantics: true,
|
|
child: FlatButton(
|
|
onPressed: () {
|
|
Navigator.push(
|
|
context,
|
|
MaterialPageRoute<FinancialEntityCategoryDetailsPage>(
|
|
builder: (context) => FinancialEntityCategoryDetailsPage(),
|
|
),
|
|
);
|
|
},
|
|
child: Column(
|
|
children: [
|
|
Container(
|
|
padding: EdgeInsets.symmetric(vertical: 16),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
alignment: Alignment.center,
|
|
height: 32 + 60 * (cappedTextScale(context) - 1),
|
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
|
child: VerticalFractionBar(
|
|
color: indicatorColor,
|
|
fraction: indicatorFraction,
|
|
),
|
|
),
|
|
Expanded(
|
|
child: Wrap(
|
|
alignment: WrapAlignment.spaceBetween,
|
|
crossAxisAlignment: WrapCrossAlignment.center,
|
|
children: [
|
|
Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
title,
|
|
style: textTheme.body1.copyWith(fontSize: 16),
|
|
),
|
|
Text(
|
|
subtitle,
|
|
style: textTheme.body1
|
|
.copyWith(color: RallyColors.gray60),
|
|
),
|
|
],
|
|
),
|
|
Text(
|
|
amount,
|
|
style: textTheme.body2.copyWith(
|
|
fontSize: 20,
|
|
color: RallyColors.gray,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Container(
|
|
constraints: BoxConstraints(minWidth: 32),
|
|
padding: EdgeInsetsDirectional.only(start: 12),
|
|
child: suffix,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const Divider(
|
|
height: 1,
|
|
indent: 16,
|
|
endIndent: 16,
|
|
color: RallyColors.dividerColor,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 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,
|
|
BuildContext context,
|
|
) {
|
|
final amount = usdWithSignFormat(context).format(model.primaryAmount);
|
|
final shortAccountNumber = model.accountNumber.substring(6);
|
|
return FinancialEntityCategoryView(
|
|
suffix: const Icon(Icons.chevron_right, color: Colors.grey),
|
|
title: model.name,
|
|
subtitle: '• • • • • • $shortAccountNumber',
|
|
semanticsLabel: GalleryLocalizations.of(context).rallyAccountAmount(
|
|
model.name,
|
|
shortAccountNumber,
|
|
amount,
|
|
),
|
|
indicatorColor: RallyColors.accountColor(accountDataIndex),
|
|
indicatorFraction: 1,
|
|
amount: amount,
|
|
);
|
|
}
|
|
|
|
FinancialEntityCategoryView buildFinancialEntityFromBillData(
|
|
BillData model,
|
|
int billDataIndex,
|
|
BuildContext context,
|
|
) {
|
|
final amount = usdWithSignFormat(context).format(model.primaryAmount);
|
|
return FinancialEntityCategoryView(
|
|
suffix: const Icon(Icons.chevron_right, color: Colors.grey),
|
|
title: model.name,
|
|
subtitle: model.dueDate,
|
|
semanticsLabel: GalleryLocalizations.of(context).rallyBillAmount(
|
|
model.name,
|
|
model.dueDate,
|
|
amount,
|
|
),
|
|
indicatorColor: RallyColors.billColor(billDataIndex),
|
|
indicatorFraction: 1,
|
|
amount: amount,
|
|
);
|
|
}
|
|
|
|
FinancialEntityCategoryView buildFinancialEntityFromBudgetData(
|
|
BudgetData model,
|
|
int budgetDataIndex,
|
|
BuildContext context,
|
|
) {
|
|
final amountUsed = usdWithSignFormat(context).format(model.amountUsed);
|
|
final primaryAmount = usdWithSignFormat(context).format(model.primaryAmount);
|
|
final amount =
|
|
usdWithSignFormat(context).format(model.primaryAmount - model.amountUsed);
|
|
|
|
return FinancialEntityCategoryView(
|
|
suffix: Text(
|
|
GalleryLocalizations.of(context).rallyFinanceLeft,
|
|
style: Theme.of(context)
|
|
.textTheme
|
|
.body1
|
|
.copyWith(color: RallyColors.gray60, fontSize: 10),
|
|
),
|
|
title: model.name,
|
|
subtitle: amountUsed + ' / ' + primaryAmount,
|
|
semanticsLabel: GalleryLocalizations.of(context).rallyBudgetAmount(
|
|
model.name,
|
|
model.amountUsed,
|
|
model.primaryAmount,
|
|
amount,
|
|
),
|
|
indicatorColor: RallyColors.budgetColor(budgetDataIndex),
|
|
indicatorFraction: model.amountUsed / model.primaryAmount,
|
|
amount: amount,
|
|
);
|
|
}
|
|
|
|
List<FinancialEntityCategoryView> buildAccountDataListViews(
|
|
List<AccountData> items,
|
|
BuildContext context,
|
|
) {
|
|
return List<FinancialEntityCategoryView>.generate(
|
|
items.length,
|
|
(i) => buildFinancialEntityFromAccountData(items[i], i, context),
|
|
);
|
|
}
|
|
|
|
List<FinancialEntityCategoryView> buildBillDataListViews(
|
|
List<BillData> items,
|
|
BuildContext context,
|
|
) {
|
|
return List<FinancialEntityCategoryView>.generate(
|
|
items.length,
|
|
(i) => buildFinancialEntityFromBillData(items[i], i, context),
|
|
);
|
|
}
|
|
|
|
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 isDesktop = isDisplayDesktop(context);
|
|
|
|
return ApplyTextOptions(
|
|
child: Scaffold(
|
|
appBar: AppBar(
|
|
elevation: 0,
|
|
centerTitle: true,
|
|
title: Text(
|
|
GalleryLocalizations.of(context).rallyAccountDataChecking,
|
|
style: Theme.of(context).textTheme.body1.copyWith(fontSize: 18),
|
|
),
|
|
),
|
|
body: Column(
|
|
children: [
|
|
SizedBox(
|
|
height: 200,
|
|
width: double.infinity,
|
|
child: RallyLineChart(events: items),
|
|
),
|
|
Expanded(
|
|
child: Padding(
|
|
padding: isDesktop ? EdgeInsets.all(40) : EdgeInsets.zero,
|
|
child: ListView(
|
|
shrinkWrap: true,
|
|
children: [
|
|
for (DetailedEventData detailedEventData in items)
|
|
_DetailedEventCard(
|
|
title: detailedEventData.title,
|
|
date: detailedEventData.date,
|
|
amount: detailedEventData.amount,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _DetailedEventCard extends StatelessWidget {
|
|
const _DetailedEventCard({
|
|
@required this.title,
|
|
@required this.date,
|
|
@required this.amount,
|
|
});
|
|
|
|
final String title;
|
|
final DateTime date;
|
|
final double amount;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final isDesktop = isDisplayDesktop(context);
|
|
return FlatButton(
|
|
onPressed: () {},
|
|
padding: EdgeInsets.symmetric(horizontal: 16),
|
|
child: Column(
|
|
children: [
|
|
Container(
|
|
padding: EdgeInsets.symmetric(vertical: 16),
|
|
width: double.infinity,
|
|
child: isDesktop
|
|
? Row(
|
|
children: [
|
|
Expanded(
|
|
flex: 1,
|
|
child: _EventTitle(title: title),
|
|
),
|
|
_EventDate(date: date),
|
|
Expanded(
|
|
flex: 1,
|
|
child: Align(
|
|
alignment: AlignmentDirectional.centerEnd,
|
|
child: _EventAmount(amount: amount),
|
|
),
|
|
),
|
|
],
|
|
)
|
|
: Wrap(
|
|
alignment: WrapAlignment.spaceBetween,
|
|
children: [
|
|
Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_EventTitle(title: title),
|
|
_EventDate(date: date),
|
|
],
|
|
),
|
|
_EventAmount(amount: amount),
|
|
],
|
|
),
|
|
),
|
|
SizedBox(
|
|
height: 1,
|
|
child: Container(
|
|
color: RallyColors.dividerColor,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _EventAmount extends StatelessWidget {
|
|
const _EventAmount({Key key, @required this.amount}) : super(key: key);
|
|
|
|
final double amount;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final textTheme = Theme.of(context).textTheme;
|
|
return Text(
|
|
usdWithSignFormat(context).format(amount),
|
|
style: textTheme.body2.copyWith(fontSize: 20, color: RallyColors.gray),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _EventDate extends StatelessWidget {
|
|
const _EventDate({Key key, @required this.date}) : super(key: key);
|
|
|
|
final DateTime date;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final textTheme = Theme.of(context).textTheme;
|
|
return Text(
|
|
shortDateFormat(context).format(date),
|
|
semanticsLabel: longDateFormat(context).format(date),
|
|
style: textTheme.body1.copyWith(color: RallyColors.gray60),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _EventTitle extends StatelessWidget {
|
|
const _EventTitle({Key key, @required this.title}) : super(key: key);
|
|
|
|
final String title;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final textTheme = Theme.of(context).textTheme;
|
|
return Text(
|
|
title,
|
|
style: textTheme.body1.copyWith(fontSize: 16),
|
|
);
|
|
}
|
|
}
|