Related
I have SfCartesianChart and trackball inside, when I try to click or move a mouse on the SfCartesianChart then nothing happens, my trackball doesn't appear.
It appears only one second then immediately disappears, I fond out it by clicking and moving a mouse on SfCartesianChart almost a minute...
How to fix that?
This is my SfCartesianChart, there is no trackball when I click or move on blue dots:
And this is my code:
class HomeWidget extends StatefulWidget {
const HomeWidget({super.key});
#override
State<HomeWidget> createState() => _HomeWidgetState();
}
class _HomeWidgetState extends State<HomeWidget> {
late TabController _tabController;
void initState() {
_tabController = TabController(vsync: this, length: 3);
super.initState();
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SizedBox(
height: 220.h,
child: TabBarView(
controller: _tabController,
children: [
DaysChart(
monitoringData: state.chartData,
precision: 1
),
DaysChart(
monitoringData: state.chartData,
precision: 2
),
DaysChart(
monitoringData: state.chartData,
precision: 3
),
],
)
);
}
}
Chartdata looks like this:
state.chartData = List<TimeSeriesValues> dayMonitoringData = [
TimeSeriesValues(DateTime(2007, 2, 1, 8, 40), 30),
TimeSeriesValues(DateTime(2007, 2, 1, 12, 40), 80),
TimeSeriesValues(DateTime(2007, 2, 1, 18, 40), 50),
];
And TimeSeriesValues class:
class TimeSeriesValues {
final DateTime time;
final int values;
TimeSeriesValues(this.time, this.values);
}
And this is DaysChart:
class DaysChart extends StatefulWidget {
const DaysChart({
Key? key,
required this.monitoringData,
required this.precision,
}) : super(key: key);
final List<TimeSeriesValues> monitoringData;
final int precision;
#override
State<DaysChart> createState() => _DaysChartState();
}
class _DaysChartState extends State<DaysChart> {
late TrackballBehavior _trackballBehavior;
#override
void initState() {
_trackballBehavior = TrackballBehavior(
enable: true,
shouldAlwaysShow: true,
lineColor: const Color(0xFF454545),
activationMode: ActivationMode.singleTap,
tooltipDisplayMode: TrackballDisplayMode.nearestPoint,
tooltipSettings: const InteractiveTooltip(
arrowLength: 0,
arrowWidth: 0,
canShowMarker: false,
color: Colors.transparent,
),
builder: (context, TrackballDetails trackballDetails) {
var tag = Localizations.maybeLocaleOf(context)?.toLanguageTag();
return SizedBox(
height: 50,
child: Column(
children: [
Text(
"${trackballDetails.point!.yValue.round().toString()}%",
style: TextStyle(
color: Colors.white,
fontSize: 16.sp,
)
),
Text(
DateFormat.MMMMd(tag).format(trackballDetails.point!.x),
style: TextStyle(
color: Colors.white,
fontSize: 10.sp,
),
)
],
)
);
}
);
super.initState();
}
#override
Widget build(BuildContext context) {
final List<double> stops = <double>[];
stops.add(0.1);
stops.add(1.0);
return SizedBox(
height: 190.h,
width: 320.w,
child: SfCartesianChart(
plotAreaBorderWidth: 0,
plotAreaBorderColor: Colors.white24,
trackballBehavior: _trackballBehavior,
primaryXAxis: DateTimeCategoryAxis(
majorTickLines: const MajorTickLines(width: 0),
axisLine: const AxisLine(
color: Colors.white24,
dashArray: <double>[5,5]
),
minimum: widget.monitoringData.first.time,
maximum: widget.monitoringData.last.time,
intervalType: widget.precision == 1
? DateTimeIntervalType.minutes
: widget.precision == 2
? DateTimeIntervalType.days
: DateTimeIntervalType.months,
dateFormat: widget.precision == 1
? DateFormat.Hm()
: widget.precision == 2
? DateFormat.E()
: DateFormat.MMMd(),
borderColor: Colors.transparent,
majorGridLines: const MajorGridLines(
width: 0.5,
color: Colors.transparent,
),
),
primaryYAxis: NumericAxis(
majorGridLines: const MajorGridLines(width: 0.5, color: Colors.white24, dashArray: <double>[5, 5]),
majorTickLines: const MajorTickLines(width: 0),
axisLine: const AxisLine(
width: 0
),
labelStyle: const TextStyle(
fontSize: 0
),
minimum: 0,
maximum: 100
),
series: <ChartSeries<TimeSeriesValues, DateTime>>[
AreaSeries<TimeSeriesValues, DateTime>(
borderWidth: 2,
animationDuration: 0,
borderColor: const Color(0xFF409CFF),
dataSource: widget.monitoringData,
markerSettings: const MarkerSettings(
isVisible: true,
color: Color(0xFF409CFF),
height: 11,
width: 11,
borderWidth: 3,
borderColor: Colors.transparent,
),
xValueMapper: (TimeSeriesValues sales, _) => sales.time,
yValueMapper: (TimeSeriesValues sales, _) => sales.values,
gradient: LinearGradient(
colors: const [Color(0xFF121212), Color(0xFF10273F)],
stops: stops,
begin: Alignment.bottomCenter,
end: Alignment.topCenter),
)
]
);
}
Flutter keeps firing the api request, 200+ x sometimes and this makes the app really slow. I have tried async memorizer already, but this doesn't work. Do you have any idea how I can prevent multiple api requests with this code?
I'm getting an redscreen also when the page is loading. The error says Null check operator used on a null value.
I don't understand what I'm doing wrong. Tips & ideas are welcome.
class ProfileScreen extends StatefulWidget {
// ignore: deprecated_member_use_from_same_package
final ProfileNavigationEnum profileNavigationEnum;
final String? otherUserId;
final String? profileUrl;
final String? coverUrl;
const ProfileScreen({
Key? key,
this.otherUserId,
this.profileUrl,
this.coverUrl,
this.profileNavigationEnum = ProfileNavigationEnum.FROM_FEED,
}) : super(key: key);
#override
_ProfileScreenState createState() => _ProfileScreenState();
}
class _ProfileScreenState extends State<ProfileScreen>
with SingleTickerProviderStateMixin {
ProfileCubit? _profileCubit;
UserPostCubit? userPostCubit;
UserMediaCubit? userMediaCubit;
UserLikesCubit? userLikesCubit;
TabController? tabController;
Size? size;
#override
void initState() {
_profileCubit = BlocProvider.of<ProfileCubit>(context);
tabController = TabController(length: 3, vsync: this);
userPostCubit = getIt<UserPostCubit>();
userMediaCubit = getIt<UserMediaCubit>();
userLikesCubit = getIt<UserLikesCubit>();
_profileCubit!.profileEntity.listen((event) {
userLikesCubit!.userId = event.id;
userMediaCubit!.userId = event.id;
userPostCubit!.userId = event.id;
});
super.initState();
_profileCubit!
.getUserProfile(widget.otherUserId, widget.coverUrl, widget.profileUrl);
}
double textSizePred = 0.0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: BlocConsumer<ProfileCubit, CommonUIState>(
listener: (context, state) => state.maybeWhen(
orElse: () => null,
success: (state) => ProfileListener.success(state, context),
error: (e) => ProfileListener.error(e!, context),
),
builder: (_, state) {
return state.when(
initial: () => LoadingBar(),
success: (s) => getHomeWidget(),
loading: () => LoadingBar(),
error: (e) => Center(
child: NoDataFoundScreen(
onTapButton: context.router.root.pop,
icon: AppIcons.personOption(
color: AppColors.colorPrimary, size: 40),
title: 'Profile Not found',
message: e!.contains("invalid")
? LocaleKeys
.sorry_we_cannot_find_the_page_you_are_looking_for_if_you_still_ne
.tr()
: e,
buttonText: LocaleKeys.go_back.tr(),
),
),
);
},
),
);
}
Widget getHomeWidget() {
return StreamBuilder<ProfileEntity>(
stream: _profileCubit!.profileEntity,
builder: (context, snapshot) {
return DefaultTabController(
length: 3,
child: SafeArea(
child: NestedScrollView(
headerSliverBuilder: (context, value) {
return [
SliverAppBar(
automaticallyImplyLeading: false,
leading: null,
systemOverlayStyle: SystemUiOverlayStyle.light,
elevation: 0.0,
expandedHeight: calculateHeight(
context: context,
item: snapshot.data!,
) as double?,
floating: true,
pinned: true,
actions: [
IconButton(
icon: const Icon(Icons.edit),
onPressed: () async {
await openMediaPicker(
context,
(media) async {
_profileCubit!.changeProfileEntity(
snapshot.data!
.copyWith(backgroundImage: media),
);
await _profileCubit!
.updateProfileCover(media);
},
mediaType: MediaTypeEnum.IMAGE,
allowCropping: true,
);
}).toVisibility(widget.otherUserId == null)
],
// title: Text('Profile'),
backgroundColor: Colors.white,
flexibleSpace: FlexibleSpaceBar(
collapseMode: CollapseMode.pin,
background: snapshot.data == null
? Container()
: TopAppBar(
otherUserId: snapshot.data!.id,
otherUser: widget.otherUserId != null,
profileEntity: snapshot.data,
profileNavigationEnum:
widget.profileNavigationEnum,
onSizeAborted: (size) {
setState(() {
textSizePred = size;
});
},
),
),
// posts,media,likes row
bottom: PreferredSize(
child: Stack(
children: [
Positioned.fill(
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
border: Border(
bottom: BorderSide(
color: Colors.grey,
width: 0.2,
),
),
),
),
),
TabBar(
indicatorWeight: 1,
indicatorSize: TabBarIndicatorSize.label,
labelPadding: const EdgeInsets.all(0),
labelStyle: TextStyle(
fontFamily: 'CeraPro',
fontWeight: FontWeight.w500,
),
unselectedLabelStyle: TextStyle(
fontFamily: 'CeraPro',
fontWeight: FontWeight.bold,
),
tabs: [
Tab(
text: LocaleKeys.posts.tr(),
).toContainer(
alignment: Alignment.center,
decoration: const BoxDecoration(
color: Colors.white,
border: Border(
top: BorderSide(
color: Colors.grey,
width: 0.2,
),
),
),
),
Tab(
text: LocaleKeys.media.tr(),
).toContainer(
alignment: Alignment.center,
decoration: const BoxDecoration(
color: Colors.white,
border: Border(
top: BorderSide(
color: Colors.grey,
width: 0.2,
),
),
),
),
Tab(
text: LocaleKeys.likes.tr(),
).toContainer(
alignment: Alignment.center,
decoration: const BoxDecoration(
color: Colors.white,
border: Border(
top: BorderSide(
color: Colors.grey,
width: 0.2,
),
),
),
),
],
)
],
),
preferredSize: const Size(500, 56),
),
),
];
},
body: TabBarView(
children: [
/// Add the block widget here thhree times
Container(
child: RefreshIndicator(
onRefresh: () {
userPostCubit!.onRefresh();
return Future.value();
},
child: PostPaginationWidget(
isComeHome: false,
isFromProfileSearch: true,
isPrivateAccount: (value) {
_profileCubit!.isPrivateUser = value;
},
isSliverList: false,
noDataFoundScreen: NoDataFoundScreen(
buttonText: LocaleKeys.go_to_the_homepage.tr(),
title: LocaleKeys.no_posts_yet.tr(),
message: "",
onTapButton: () {
context.router.root.pop();
},
),
pagingController: userPostCubit!.pagingController,
onTapLike: userPostCubit!.likeUnlikePost,
onOptionItemTap:
(PostOptionsEnum postOptionsEnum, int index) =>
userPostCubit!.onOptionItemSelected(
context, postOptionsEnum, index),
onTapRepost: userPostCubit!.repost,
),
),
),
Container(
child: RefreshIndicator(
onRefresh: () {
userMediaCubit!.onRefresh();
return Future.value();
},
child: PostPaginationWidget(
isComeHome: false,
isPrivateAccount: (value) {
_profileCubit!.isPrivateUser = value;
},
isSliverList: false,
noDataFoundScreen: NoDataFoundScreen(
title: LocaleKeys.no_media_yet.tr(),
icon: AppIcons.imageIcon(height: 35, width: 35),
buttonText: LocaleKeys.go_to_the_homepage.tr(),
message: "",
onTapButton: () {
context.router.root.pop();
// BlocProvider.of<FeedCubit>(context).changeCurrentPage(ScreenType.home());
// context.router.root.push(Routes.createPost);
},
),
pagingController: userMediaCubit!.pagingController,
onTapLike: userMediaCubit!.likeUnlikePost,
onOptionItemTap: (PostOptionsEnum postOptionsEnum,
int index) async =>
await userMediaCubit!.onOptionItemSelected(
context, postOptionsEnum, index),
onTapRepost: userMediaCubit!.repost,
),
)),
Container(
child: RefreshIndicator(
onRefresh: () {
userLikesCubit!.onRefresh();
return Future.value();
},
child: PostPaginationWidget(
isComeHome: false,
isPrivateAccount: (value) {
_profileCubit!.isPrivateUser = value;
},
isSliverList: false,
noDataFoundScreen: NoDataFoundScreen(
title: LocaleKeys.no_likes_yet.tr(),
icon: AppIcons.likeOption(
size: 35, color: AppColors.colorPrimary),
buttonText: LocaleKeys.go_to_the_homepage.tr(),
message: LocaleKeys
.you_don_t_have_any_favorite_posts_yet_all_posts_that_you_like_wil
.tr(),
onTapButton: () {
context.router.root.pop();
// BlocProvider.of<FeedCubit>(context).changeCurrentPage(ScreenType.home());
// context.router.root.push(Routes.createPost);
},
),
pagingController: userLikesCubit!.pagingController,
onTapLike: userLikesCubit!.likeUnlikePost,
onOptionItemTap:
(PostOptionsEnum postOptionsEnum, int index) =>
userLikesCubit!.onOptionItemSelected(
context,
postOptionsEnum,
index,
),
onTapRepost: userLikesCubit!.repost,
),
)),
],
),
),
),
);
});
}
num calculateHeight({
required BuildContext context,
required ProfileEntity item,
}) {
print('INCHES: ${context.diagonalInches}');
bool isSmallInches = context.diagonalInches <= 4.7;
bool hasWebsite = item.website != null && item.website!.isNotEmpty;
final height = context.getScreenHeight;
final defaultHeight = isSmallInches ? height * .6 : height * .47;
final websiteHeight = hasWebsite ? height * .03 : 0;
final sizeBoxHeight = textSizePred != 0.0 ? 10.h : 0;
return textSizePred + defaultHeight + websiteHeight + sizeBoxHeight;
}
}
/// helps to determine from where user navigated to profile
/// so that on back press of the profile screen we can go back the correct page
/// we're using this because according to the UI we will have the keep the bottom navigation bar under the profile page
enum ProfileNavigationEnum {
FROM_BOOKMARKS,
FROM_FEED,
FROM_SEARCH,
FROM_VIEW_POST,
FROM_MY_PROFILE,
FROM_OTHER_PROFILE,
FROM_MESSAGES,
FROM_NOTIFICATION
} ```
I'm currently building a patient tracking application where patients can save their blood pressures any time they measure. And from the data they enter, a graph is built. On the same graph, I'm showing both diastole and systole. What I want is to change graph line's color if it falls below or goes beyond a threshold value. For ex. for diastole if it falls below 60 or goes beyond 100, I want the line to turn red from that point until a new data is entered which is between 60-100.
Sorry if the code is hard to read but it's still in the prototype phase. Here is my current code:
class Graph extends StatefulWidget {
#override
_GraphState createState() => _GraphState();
}
class _GraphState extends State<Graph> {
final kTansiyon = [80, 70, 63, 76, 82, 90];
final kTimes = [7, 13.6, 15.8, 17, 20, 22];
final bTansiyon = [121, 136, 117, 120, 110, 112];
final bTimes = [10, 11, 12, 20, 21, 22];
List<FlSpot> createSpots(List<int> tansiyon, List<int> times) {
List<FlSpot> retVal = [];
if (tansiyon.length != times.length) {
return null;
} else {
for (int i = 0; i < tansiyon.length; i++) {
retVal.add(FlSpot(
times.elementAt(i).toDouble(), tansiyon.elementAt(i).toDouble()));
}
}
return retVal;
}
List<Color> kgradientColors = [
const Color(0xff23b6e6),
];
List<Color> bgradientColors = [Colors.grey];
#override
Widget build(BuildContext context) {
return LineChart(
LineChartData(
minX: 0,
minY: 0,
maxX: 24,
maxY: 150,
titlesData: FlTitlesData(
show: true,
bottomTitles: SideTitles(
showTitles: true,
reservedSize: 35,
getTextStyles: (BuildContext context, value) => const TextStyle(
color: Colors.grey,
fontWeight: FontWeight.bold,
fontSize: 10),
getTitles: (value) {
switch (value.toInt()) {
case 4:
return '4:00';
break;
case 8:
return '8:00';
break;
case 12:
return '12:00';
break;
case 16:
return '16:00';
break;
case 20:
return '20:00';
break;
case 24:
return '00:00';
break;
}
return '';
}),
leftTitles: SideTitles(
showTitles: true,
getTextStyles: (BuildContext context, value) => const TextStyle(
color: Colors.grey,
fontWeight: FontWeight.bold,
fontSize: 10),
getTitles: (value) {
if (value % 10 == 0) {
return '${value.toInt()}';
}
return '';
},
),
),
gridData: FlGridData(
show: false,
drawVerticalLine: true,
getDrawingHorizontalLine: (value) {
return FlLine(
color: const Color(0xff37434d),
strokeWidth: 1,
);
},
getDrawingVerticalLine: (value) {
return FlLine(
color: const Color(0xff37434d),
strokeWidth: 1,
);
},
),
borderData: FlBorderData(
show: true,
border: Border.all(color: const Color(0xff37434d), width: 1),
),
lineBarsData: [
LineChartBarData(
spots: [
FlSpot(9, 80),
FlSpot(12, 77),
FlSpot(15, 80),
FlSpot(18, 90),
FlSpot(21, 62),
],
isCurved: true,
colors: kgradientColors,
barWidth: 5,
isStrokeCapRound: true,
dotData: FlDotData(
show: false,
),
belowBarData: BarAreaData(
show: true,
colors: kgradientColors
.map((color) => color.withOpacity(0.3))
.toList(),
),
),
LineChartBarData(
spots: [
FlSpot(7, 120),
FlSpot(9, 130),
FlSpot(11, 116),
FlSpot(22, 128),
FlSpot(23, 123),
],
isCurved: true,
colors: bgradientColors,
barWidth: 5,
isStrokeCapRound: true,
dotData: FlDotData(
show: false,
),
belowBarData: BarAreaData(
show: true,
colors: bgradientColors
.map((color) => color.withOpacity(0.3))
.toList(),
),
),
LineChartBarData(
spots: [],
isCurved: true,
colors: bgradientColors,
barWidth: 5,
isStrokeCapRound: true,
dotData: FlDotData(
show: false,
),
belowBarData: BarAreaData(
show: true,
colors: bgradientColors
.map((color) => color.withOpacity(0.3))
.toList(),
),
)
]),
);
}
}
And this is how it looks at the moment:
graph
Thanks in advance.
So this is the current code for one of the lines:
LineChartBarData(
spots: [
FlSpot(9, 80),
FlSpot(12, 77),
FlSpot(15, 80),
FlSpot(18, 90),
FlSpot(21, 62),
],
isCurved: true,
colors: kgradientColors,
barWidth: 5,
isStrokeCapRound: true,
dotData: FlDotData(
show: false,
),
belowBarData: BarAreaData(
show: true,
colors: kgradientColors
.map((color) => color.withOpacity(0.3))
.toList(),
),
),
The first thing you would need to do is to factor out the placeholder values for the spots into a variable we can reference in order to determine what line color to use.
List<FlSpot> spots = [
FlSpot(9, 80),
FlSpot(12, 77),
FlSpot(15, 80),
FlSpot(18, 90),
FlSpot(21, 62),
];
Then instead of kgradientColors lets define two different color sets, one to use for a red line and one for the blue line.
List<Color> redColors = [
Colors.red,
];
List<Color> blueColors = [
Colors.blue,
];
Then we can define a helper function that picks the correct color set based on if the last value in spots is between 60 and 100:
List<Color> get lineColors => spots.last.y <= 100 && spots.last.y >= 60
? blueColors
: redColors;
Then use it in the LineChartBarData:
LineChartBarData(
spots: spots,
isCurved: true,
colors: lineColors,
barWidth: 5,
isStrokeCapRound: true,
dotData: FlDotData(
show: false,
),
belowBarData: BarAreaData(
show: true,
colors: lineColors
.map((color) => color.withOpacity(0.3))
.toList(),
),
),
I am working on project which requires to add bar chart in the pdf(if possible customizable pdf). I am trying to add using below code but it didn't work
void _printScreen() {
doc.addPage(pw.MultiPage(
pageFormat: PdfPageFormat.letter
.copyWith(marginBottom: 1.5 * PdfPageFormat.cm),
header: (pw.Context context) {
if (context.pageNumber == 1) {
return null;
}
return pw.Container(
alignment: pw.Alignment.centerRight,
margin:
const pw.EdgeInsets.only(bottom: 3.0 * PdfPageFormat.mm),
padding:
const pw.EdgeInsets.only(bottom: 3.0 * PdfPageFormat.mm),
decoration: const pw.BoxDecoration(
border: pw.Border(
bottom: pw.BorderSide(
color: PdfColors.grey,
width: 0.5,
)),
),
child: pw.Text('Report',
style: pw.Theme.of(context)
.defaultTextStyle
.copyWith(color: PdfColors.grey)));
},
footer: (pw.Context context) {
return pw.Container(
alignment: pw.Alignment.centerRight,
margin: const pw.EdgeInsets.only(top: 1.0 * PdfPageFormat.cm),
child: pw.Text(
'Page ${context.pageNumber} of ${context.pagesCount}',
style: pw.Theme.of(context)
.defaultTextStyle
.copyWith(color: PdfColors.grey)));
},
build: (pw.Context context) => [
pw.Center(
child: pw.Expanded(
child: pw.Image(image),
),
)
]));
//save PDF
final String dir = (await getExternalStorageDirectory()).path;
final String path =
'$dir/myreport${DateTime.now().toIso8601String()}.pdf';
final File file = File(path);
await file.writeAsBytes(await doc.save());
snackbarMessage(
message: "Report Saved Successfully.",
icon: FontAwesomeIcons.infoCircle);
return doc.save();
});
}
if anyone implemeted something like this(Adding graph in pdf) please share your answer..Thank you in advance :)
Hello I actually figure it out by adding an "image" of the chat i want inside the pdf. You need these packages to accomplish that
flutter_email_sender: ^5.0.2
fl_chart: ^0.46.0
screenshot: ^1.2.3
pdf: ^3.3.0
path_provider: ^2.0.9
permission_handler: ^9.2.0
Some of them are needed for my example below. My example creates a pdf (saves it to the device) and opens the email to sent it to a user.
you are also going to need these permissions in your manifest
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
pdf_page.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:path_provider/path_provider.dart';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
import 'dart:io';
import 'package:flutter_email_sender/flutter_email_sender.dart';
import 'package:screenshot/screenshot.dart';
import 'package:test_project_pdf/chart_for_pdf.dart';
class PdfPage extends StatelessWidget {
const PdfPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return TextButton(
onPressed: () async {
if (await Permission.storage.isGranted) {
await _pdfResults(context);
} else {
await [Permission.storage].request();
if (await Permission.storage.isGranted) {
await _pdfResults(context);
} else {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text("Need permissions"),
));
}
}
},
child: Center(
child: Container(
padding: const EdgeInsets.all(15),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(32)),
child: const Text(
"Send e-mail",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 18),
)),
),
);
}
Future _pdfResults(context) async {
var pdf = pw.Document();
ScreenshotController screenshotController = ScreenshotController();
final bytes = await screenshotController.captureFromWidget(MediaQuery(data: const MediaQueryData(), child: ChartForPdf()));
pdf.addPage(
pw.MultiPage(
pageFormat: PdfPageFormat.a4,
margin: const pw.EdgeInsets.all(32),
build: (pw.Context context) {
return <pw.Widget>[
pw.Column(
children: [
pw.Center(
child: pw.Container(
height: 700,
width: 1080,
child: pw.Expanded(
child: pw.Image(pw.MemoryImage(bytes)),
),
),
),
],
),
];
},
),
);
await savePdf(pdf, context);
}
Future<String> savePdf(pw.Document pdf, var context) async {
var path;
late File file;
Directory directory;
if (Platform.isAndroid) {
path = (await getExternalStorageDirectory())!.path;
file = File("$path/best_pdf.pdf");
} else if (Platform.isIOS) {
path = await getApplicationDocumentsDirectory();
directory = await Directory("${path.path}/best_pdfs").create();
file = File("${directory.path}/best_pdf.pdf");
}
if (await file.exists()) {
try {
await file.delete();
} on Exception catch (e) {
print(e);
}
}
await file.writeAsBytes(await pdf.save());
await send(file.path, context);
return file.path;
}
Future<void> send(String path, var context) async {
final Email email = Email(
subject: "Mood Chart",
recipients: [""],
attachmentPaths: [path],
isHTML: false,
);
try {
await FlutterEmailSender.send(email);
// platformResponse = 'Επιτυχία';
} on PlatformException catch (error) {
if (error.message == "No email clients found!") {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text("Device has no email"),
duration: Duration(seconds: 2)));
}
}
}
}
chart_for_pdf.dart
import 'dart:math';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
class ChartForPdf extends StatelessWidget {
final Color barBackgroundColor = const Color(0xff72d8bf);
final Duration animDuration = const Duration(milliseconds: 250);
int touchedIndex = -1;
#override
Widget build(BuildContext context) {
return AspectRatio(
aspectRatio: 1,
child: Card(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(18)),
color: const Color(0xff81e5cd),
child: Stack(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
const Text(
'Mingguan',
style: TextStyle(
color: Color(0xff0f4a3c),
fontSize: 24,
fontWeight: FontWeight.bold),
),
const SizedBox(
height: 4,
),
const Text(
'Grafik konsumsi kalori',
style: TextStyle(
color: Color(0xff379982),
fontSize: 18,
fontWeight: FontWeight.bold),
),
const SizedBox(
height: 38,
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: BarChart( mainBarData(),
swapAnimationDuration: animDuration,
),
),
),
const SizedBox(
height: 12,
),
],
),
),
],
),
),
);
}
BarChartGroupData makeGroupData(
int x,
double y, {
bool isTouched = false,
Color barColor = Colors.white,
double width = 22,
List<int> showTooltips = const [],
}) {
return BarChartGroupData(
x: x,
barRods: [
BarChartRodData(
toY: isTouched ? y + 1 : y,
colors: isTouched ? [Colors.yellow] : [barColor],
width: width,
borderSide: isTouched
? BorderSide(color: Colors.yellow, width: 1)
: const BorderSide(color: Colors.white, width: 0),
backDrawRodData: BackgroundBarChartRodData(
show: true,
toY: 20,
colors: [barBackgroundColor],
),
),
],
showingTooltipIndicators: showTooltips,
);
}
List<BarChartGroupData> showingGroups() => List.generate(7, (i) {
switch (i) {
case 0:
return makeGroupData(0, 5, isTouched: i == touchedIndex);
case 1:
return makeGroupData(1, 6.5, isTouched: i == touchedIndex);
case 2:
return makeGroupData(2, 5, isTouched: i == touchedIndex);
case 3:
return makeGroupData(3, 7.5, isTouched: i == touchedIndex);
case 4:
return makeGroupData(4, 9, isTouched: i == touchedIndex);
case 5:
return makeGroupData(5, 11.5, isTouched: i == touchedIndex);
case 6:
return makeGroupData(6, 6.5, isTouched: i == touchedIndex);
default:
return throw Error();
}
});
BarChartData mainBarData() {
return BarChartData(
barTouchData: BarTouchData(
touchTooltipData: BarTouchTooltipData(
tooltipBgColor: Colors.blueGrey,
getTooltipItem: (group, groupIndex, rod, rodIndex) {
String weekDay;
switch (group.x.toInt()) {
case 0:
weekDay = 'Monday';
break;
case 1:
weekDay = 'Tuesday';
break;
case 2:
weekDay = 'Wednesday';
break;
case 3:
weekDay = 'Thursday';
break;
case 4:
weekDay = 'Friday';
break;
case 5:
weekDay = 'Saturday';
break;
case 6:
weekDay = 'Sunday';
break;
default:
throw Error();
}
return BarTooltipItem(
weekDay + '\n',
const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 18,
),
children: <TextSpan>[
TextSpan(
text: (rod.toY - 1).toString(),
style: const TextStyle(
color: Colors.yellow,
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
],
);
}),
),
titlesData: FlTitlesData(
show: true,
rightTitles: SideTitles(showTitles: false),
topTitles: SideTitles(showTitles: false),
bottomTitles: SideTitles(
showTitles: true,
getTextStyles: (context, value) => const TextStyle(
color: Colors.white, fontWeight: FontWeight.bold, fontSize: 14),
margin: 16,
getTitles: (double value) {
switch (value.toInt()) {
case 0:
return 'M';
case 1:
return 'T';
case 2:
return 'W';
case 3:
return 'T';
case 4:
return 'F';
case 5:
return 'S';
case 6:
return 'S';
default:
return '';
}
},
),
leftTitles: SideTitles(
showTitles: false,
),
),
borderData: FlBorderData(
show: false,
),
barGroups: showingGroups(),
gridData: FlGridData(show: false),
);
}
BarChartData randomData() {
return BarChartData(
barTouchData: BarTouchData(
enabled: false,
),
titlesData: FlTitlesData(
show: true,
bottomTitles: SideTitles(
showTitles: true,
getTextStyles: (context, value) => const TextStyle(
color: Colors.white, fontWeight: FontWeight.bold, fontSize: 14),
margin: 16,
getTitles: (double value) {
switch (value.toInt()) {
case 0:
return 'M';
case 1:
return 'T';
case 2:
return 'W';
case 3:
return 'T';
case 4:
return 'F';
case 5:
return 'S';
case 6:
return 'S';
default:
return '';
}
},
),
leftTitles: SideTitles(
showTitles: false,
),
topTitles: SideTitles(
showTitles: false,
),
rightTitles: SideTitles(
showTitles: false,
)),
borderData: FlBorderData(
show: false,
),
barGroups: List.generate(7, (i) {
switch (i) {
case 0:
return makeGroupData(0, Random().nextInt(15).toDouble() + 6,
barColor: Colors.blue);
case 1:
return makeGroupData(1, Random().nextInt(15).toDouble() + 6,
barColor: Colors.amber);
case 2:
return makeGroupData(2, Random().nextInt(15).toDouble() + 6,
barColor: Colors.deepOrange);
case 3:
return makeGroupData(3, Random().nextInt(15).toDouble() + 6,
barColor: Colors.pink);
case 4:
return makeGroupData(4, Random().nextInt(15).toDouble() + 6,
barColor: Colors.red);
case 5:
return makeGroupData(5, Random().nextInt(15).toDouble() + 6,
barColor: Colors.green);
case 6:
return makeGroupData(6, Random().nextInt(15).toDouble() + 6,
barColor: Colors.blueGrey);
default:
return throw Error();
}
}),
gridData: FlGridData(show: false),
);
}
}
main.dart
import 'package:flutter/material.dart';
import 'package:test_project_pdf/pdf_page.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const Scaffold(
body: Center (child: PdfPage()),
),
);
}
}
I want to mark the event once every 4 days.
example today is the 1st, mark the event will be available on the 5th, 10th, 15th, 20th, 25th, 30th, etc.
if today is the 3rd, the event will be available on the 8th, 13th, 18th, etc.
how does that function work?
I use this calendar plugin
https://pub.dev/packages/flutter_calendar_carousel
Following is the function to mark event manually:
EventList<Event> _markedDateMap = new EventList<Event>();
build(){
_calendarCarouselNoHeader = CalendarCarousel<Event>(
...
markedDatesMap: _markedDateMap,
...
),
}
#override
void initState() {
_markedDateMap.add(
new DateTime(2020, 2, 26),
new Event(
date: new DateTime(2020, 2, 26),
title: 'Event 5',
icon: _eventIcon,
));
_markedDateMap.add(
new DateTime(2020, 2, 26),
new Event(
date: new DateTime(2020, 2, 26),
title: 'Event 5',
icon: _eventIcon,
));
super.initState();
}
Any anwser will appreciated.
You can copy paste run full code below
working demo show when pass start date time with
addMarker(DateTime(2020, 2, 01));
addMarker(DateTime(2020, 2, 03));
code snippet
addMarker(DateTime startEventDateTime) {
var eventDateTime = startEventDateTime;
for(int i=0; i<5; i++) {
if(eventDateTime.day == 1) {
eventDateTime = eventDateTime.add(Duration(days: (4)));
} else {
eventDateTime = eventDateTime.add(Duration(days: (5)));
}
print(eventDateTime.toLocal());
_markedDateMap.add(
eventDateTime,
Event(
date: eventDateTime,
title: 'Event $i',
icon: _eventIcon,
));
}
}
working demo
full code
import 'package:flutter/material.dart';
import 'package:flutter_calendar_carousel/flutter_calendar_carousel.dart'
show CalendarCarousel;
import 'package:flutter_calendar_carousel/classes/event.dart';
import 'package:flutter_calendar_carousel/classes/event_list.dart';
import 'package:intl/intl.dart' show DateFormat;
void main() => runApp( MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'dooboolab flutter calendar',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Calendar Carousel Example'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
DateTime _currentDate = DateTime(2020, 2, 17);
DateTime _currentDate2 = DateTime(2020, 2, 17);
String _currentMonth = DateFormat.yMMM().format(DateTime(2020, 2, 17));
DateTime _targetDateTime = DateTime(2020, 2, 17);
// List<DateTime> _markedDate = [DateTime(2018, 9, 20), DateTime(2018, 10, 11)];
static Widget _eventIcon = Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(1000)),
border: Border.all(color: Colors.blue, width: 2.0)),
child: Icon(
Icons.person,
color: Colors.amber,
),
);
EventList<Event> _markedDateMap = EventList<Event>();
CalendarCarousel _calendarCarousel, _calendarCarouselNoHeader;
#override
void initState() {
addMarker(DateTime(2020, 2, 03));
super.initState();
}
addMarker(DateTime startEventDateTime) {
var eventDateTime = startEventDateTime;
for(int i=0; i<5; i++) {
if(eventDateTime.day == 1) {
eventDateTime = eventDateTime.add(Duration(days: (4)));
} else {
eventDateTime = eventDateTime.add(Duration(days: (5)));
}
print(eventDateTime.toLocal());
_markedDateMap.add(
eventDateTime,
Event(
date: eventDateTime,
title: 'Event $i',
icon: _eventIcon,
));
}
}
#override
Widget build(BuildContext context) {
/// Example with custom icon
_calendarCarousel = CalendarCarousel<Event>(
onDayPressed: (DateTime date, List<Event> events) {
this.setState(() => _currentDate = date);
events.forEach((event) => print(event.title));
},
weekendTextStyle: TextStyle(
color: Colors.red,
),
thisMonthDayBorderColor: Colors.grey,
// weekDays: null, /// for pass null when you do not want to render weekDays
headerText: 'Custom Header',
// markedDates: _markedDate,
weekFormat: true,
markedDatesMap: _markedDateMap,
height: 200.0,
selectedDateTime: _currentDate2,
showIconBehindDayText: true,
// daysHaveCircularBorder: false, /// null for not rendering any border, true for circular border, false for rectangular border
customGridViewPhysics: NeverScrollableScrollPhysics(),
markedDateShowIcon: true,
markedDateIconMaxShown: 2,
selectedDayTextStyle: TextStyle(
color: Colors.yellow,
),
todayTextStyle: TextStyle(
color: Colors.blue,
),
markedDateIconBuilder: (event) {
return event.icon;
},
minSelectedDate: _currentDate.subtract(Duration(days: 360)),
maxSelectedDate: _currentDate.add(Duration(days: 360)),
todayButtonColor: Colors.transparent,
todayBorderColor: Colors.green,
markedDateMoreShowTotal:
false, // null for not showing hidden events indicator
// markedDateIconMargin: 9,
// markedDateIconOffset: 3,
);
/// Example Calendar Carousel without header and custom prev & next button
_calendarCarouselNoHeader = CalendarCarousel<Event>(
todayBorderColor: Colors.green,
onDayPressed: (DateTime date, List<Event> events) {
this.setState(() => _currentDate2 = date);
events.forEach((event) => print(event.title));
},
daysHaveCircularBorder: true,
showOnlyCurrentMonthDate: false,
weekendTextStyle: TextStyle(
color: Colors.red,
),
thisMonthDayBorderColor: Colors.grey,
weekFormat: false,
// firstDayOfWeek: 4,
markedDatesMap: _markedDateMap,
height: 420.0,
selectedDateTime: _currentDate2,
targetDateTime: _targetDateTime,
customGridViewPhysics: NeverScrollableScrollPhysics(),
markedDateCustomShapeBorder: CircleBorder(
side: BorderSide(color: Colors.yellow)
),
markedDateCustomTextStyle: TextStyle(
fontSize: 18,
color: Colors.blue,
),
showHeader: false,
// markedDateIconBuilder: (event) {
// return Container(
// color: Colors.blue,
// );
// },
todayTextStyle: TextStyle(
color: Colors.blue,
),
todayButtonColor: Colors.yellow,
selectedDayTextStyle: TextStyle(
color: Colors.yellow,
),
minSelectedDate: _currentDate.subtract(Duration(days: 360)),
maxSelectedDate: _currentDate.add(Duration(days: 360)),
prevDaysTextStyle: TextStyle(
fontSize: 16,
color: Colors.pinkAccent,
),
inactiveDaysTextStyle: TextStyle(
color: Colors.tealAccent,
fontSize: 16,
),
onCalendarChanged: (DateTime date) {
this.setState(() {
_targetDateTime = date;
_currentMonth = DateFormat.yMMM().format(_targetDateTime);
});
},
onDayLongPressed: (DateTime date) {
print('long pressed date $date');
},
);
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
//custom icon
Container(
margin: EdgeInsets.symmetric(horizontal: 16.0),
child: _calendarCarousel,
), // This trailing comma makes auto-formatting nicer for build methods.
//custom icon without header
Container(
margin: EdgeInsets.only(
top: 30.0,
bottom: 16.0,
left: 16.0,
right: 16.0,
),
child: Row(
children: <Widget>[
Expanded(
child: Text(
_currentMonth,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 24.0,
),
)),
FlatButton(
child: Text('PREV'),
onPressed: () {
setState(() {
_targetDateTime = DateTime(_targetDateTime.year, _targetDateTime.month -1);
_currentMonth = DateFormat.yMMM().format(_targetDateTime);
});
},
),
FlatButton(
child: Text('NEXT'),
onPressed: () {
setState(() {
_targetDateTime = DateTime(_targetDateTime.year, _targetDateTime.month +1);
_currentMonth = DateFormat.yMMM().format(_targetDateTime);
});
},
)
],
),
),
Container(
margin: EdgeInsets.symmetric(horizontal: 16.0),
child: _calendarCarouselNoHeader,
), //
],
),
));
}
}