blob: 635502be8bef591666c19395d513706b8950d753 [file] [log] [blame]
// 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/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: 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,
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 List<_DetailedEventCard> cards = items.map((detailedEventData) {
return _DetailedEventCard(
title: detailedEventData.title,
date: detailedEventData.date,
amount: detailedEventData.amount,
);
}).toList();
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: ListView(shrinkWrap: true, children: cards),
),
],
),
),
);
}
}
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 textTheme = Theme.of(context).textTheme;
return FlatButton(
onPressed: () {},
padding: EdgeInsets.symmetric(horizontal: 16),
child: Column(
children: [
Container(
padding: EdgeInsets.symmetric(vertical: 16),
width: double.infinity,
child: Wrap(
alignment: WrapAlignment.spaceBetween,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: textTheme.body1.copyWith(fontSize: 16),
),
Text(
shortDateFormat(context).format(date),
semanticsLabel: longDateFormat(context).format(date),
style:
textTheme.body1.copyWith(color: RallyColors.gray60),
),
],
),
Text(
usdWithSignFormat(context).format(amount),
style: textTheme.body2
.copyWith(fontSize: 20, color: RallyColors.gray),
),
],
),
),
SizedBox(
height: 1,
child: Container(
color: const Color(0xAA282828),
),
),
],
),
);
}
}