diff --git a/lib/DriveHelper.dart b/lib/DriveHelper.dart index 48bfd31..d83301b 100644 --- a/lib/DriveHelper.dart +++ b/lib/DriveHelper.dart @@ -12,30 +12,30 @@ import 'package:package_info_plus/package_info_plus.dart' show PackageInfo; import 'package:url_launcher/url_launcher.dart' show launch; class DriveHelper { - String logFileID; // FileID of log file - String appFolderID; // FileID of the folder the log file is in - DriveApi driveApi; // Google Drive API - GoogleSignInAccount account; // Google user account - String version; // Version of app for about - GoogleSignIn signInScopes; // For signing out + late String logFileID; // FileID of log file + late String appFolderID; // FileID of the folder the log file is in + late DriveApi driveApi; // Google Drive API + late GoogleSignInAccount account; // Google user account + late String version; // Version of app for about + late GoogleSignIn signInScopes; // For signing out DriveHelper(); GoogleUserCircleAvatar getAvatar() => GoogleUserCircleAvatar(identity: account); - String getDisplayName() => account.displayName; + String getDisplayName() => account.displayName!; String getEmail() => account.email; void showLogFile() => launch("https://docs.google.com/spreadsheets/d/$logFileID/"); - Future signInWithGoogle( + Future signInWithGoogle( GoogleSignIn googleDriveSignIn, ) async { // Silent sign in doesn't require user input - GoogleSignInAccount account = await googleDriveSignIn.signInSilently(); + GoogleSignInAccount? account = await googleDriveSignIn.signInSilently(); if (account == null) { print("Silent sign in failed"); account = await googleDriveSignIn.signIn(); @@ -58,7 +58,14 @@ class DriveHelper { signInScopes = GoogleSignIn.standard( scopes: [DriveApi.driveFileScope], ); - account = await signInWithGoogle(signInScopes).catchError((e) => throw e); + GoogleSignInAccount? testAccount = + await signInWithGoogle(signInScopes).catchError((e) => throw e); + + if (testAccount == null) { + throw "Google user account null"; + } else { + account = testAccount; + } // Then initialise all the variables @@ -112,19 +119,18 @@ class DriveHelper { spaces: 'drive'); // Use name to search and make sure it isn't in the bin - if (search.files.length == 0) { - return null; + if (search.files!.length == 0) { + throw "File not found"; } - return search.files[0].id; + return search.files![0].id!; } - Future exportFileData(String fileID) async { - Media fileMedia = await driveApi.files + Future exportFileData(String fileID) async { + Media? fileMedia = await driveApi.files .export(fileID, "text/csv", downloadOptions: DownloadOptions.fullMedia); - String fileData; - - await fileMedia.stream.listen((event) { + String? fileData; + await fileMedia!.stream.listen((event) { fileData = String.fromCharCodes(event); }).asFuture(); return fileData; @@ -146,11 +152,7 @@ class DriveHelper { ); await driveApi.files .update(new File(), logFileID, uploadMedia: media) - .onError((error, stackTrace) { - print("Error: $error was caught by DriveHelper"); - print("This was the stack trace when the exception was caught: "); - print(stackTrace); - return null; - }); // Update the existing file with the new data + .onError((error, stackTrace) => + throw "File update failed with $error"); // Update the existing file with the new data } } diff --git a/lib/FileAppendDialog.dart b/lib/FileAppendDialog.dart index c1c6cbe..68a98d2 100644 --- a/lib/FileAppendDialog.dart +++ b/lib/FileAppendDialog.dart @@ -1,12 +1,12 @@ import 'package:bp_logger/DriveHelper.dart'; import 'package:flutter/material.dart'; -import 'package:animated_check/animated_check.dart'; +import 'animated_check.dart'; class FileAppendDialog extends StatefulWidget { const FileAppendDialog({ - Key key, - @required this.text, - @required this.driveHelper, + Key? key, + required this.text, + required this.driveHelper, }) : super(key: key); final String text; @@ -18,8 +18,8 @@ class FileAppendDialog extends StatefulWidget { class _FileAppendDialogState extends State with SingleTickerProviderStateMixin { - AnimationController _animationController; - Animation _animation; + late AnimationController _animationController; + late Animation _animation; String title = ""; @override diff --git a/lib/HomePage.dart b/lib/HomePage.dart index 29289d6..82168c3 100644 --- a/lib/HomePage.dart +++ b/lib/HomePage.dart @@ -9,7 +9,7 @@ import 'LogOutDialog.dart'; // For logging out import "DriveHelper.dart"; // Backend stuff class HomePage extends StatefulWidget { - const HomePage({Key key, @required this.driveHelper}) : super(key: key); + const HomePage({Key? key, required this.driveHelper}) : super(key: key); final DriveHelper driveHelper; @override @@ -20,7 +20,7 @@ class _HomePageState extends State { static DateTime date = new DateTime.now(); var textFieldController1 = TextEditingController(); // Diastolic var textFieldController2 = TextEditingController(); // Systolic - DriveHelper driveHelper; // "Backend" interface + DriveHelper? driveHelper; // "Backend" interface // To get 3/4ths of the screen to display the drawer to a suitable width on all devices double _getDrawerWidth(context) { @@ -41,11 +41,11 @@ class _HomePageState extends State { // Datepicker for selecting the date _selectDate(BuildContext context) async { - final DateTime picked = await showDatePicker( + final DateTime? picked = await showDatePicker( context: context, initialDate: date, - firstDate: DateTime(2015), - lastDate: DateTime(3000), + firstDate: DateTime.now().subtract(Duration(days: 7)), + lastDate: DateTime.now(), ); if (picked != null && picked != date) { // Only update if user picked and isn"t the same as before @@ -76,14 +76,14 @@ class _HomePageState extends State { child: Container( height: _getDrawerWidth(context) / 2.5, width: _getDrawerWidth(context) / 2.5, - child: driveHelper.getAvatar(), + child: driveHelper!.getAvatar(), ), ), // Name of user FittedBox( fit: BoxFit.scaleDown, child: Text( - driveHelper.getDisplayName(), + driveHelper!.getDisplayName(), style: Theme.of(context).textTheme.headline4, ), ), @@ -91,7 +91,7 @@ class _HomePageState extends State { FittedBox( fit: BoxFit.scaleDown, child: Text( - driveHelper.getEmail(), + driveHelper!.getEmail(), style: Theme.of(context).textTheme.headline6, ), ) @@ -107,7 +107,7 @@ class _HomePageState extends State { showDialog( context: context, builder: (BuildContext context) { - return LogOutDialog(logOut: driveHelper.signOut); + return LogOutDialog(logOut: driveHelper!.signOut); }, ); }, @@ -116,7 +116,7 @@ class _HomePageState extends State { ListTile( leading: Icon(Icons.insert_drive_file_outlined), title: Text("Access file"), - onTap: driveHelper.showLogFile, + onTap: driveHelper!.showLogFile, ), // For opening the support page ListTile( @@ -135,7 +135,7 @@ class _HomePageState extends State { builder: (BuildContext context) { return AboutDialog( applicationName: "BP Logger", - applicationVersion: "Version ${driveHelper.version}", + applicationVersion: "Version ${driveHelper!.version}", children: [ Padding( padding: EdgeInsets.only(left: 20), @@ -150,7 +150,7 @@ class _HomePageState extends State { text: "in Github", style: Theme.of(context) .textTheme - .caption + .caption! .apply( color: Colors.blue, ), @@ -272,7 +272,7 @@ class _HomePageState extends State { builder: (BuildContext context) { return FileAppendDialog( text: text, - driveHelper: driveHelper, + driveHelper: driveHelper!, ); }, barrierDismissible: false, diff --git a/lib/LogOutDialog.dart b/lib/LogOutDialog.dart index 77f1f8c..d11c2de 100644 --- a/lib/LogOutDialog.dart +++ b/lib/LogOutDialog.dart @@ -1,10 +1,7 @@ import 'package:flutter/material.dart'; // UI class LogOutDialog extends StatefulWidget { - const LogOutDialog({ - Key key, - @required this.logOut, - }) : super(key: key); + const LogOutDialog({Key? key, required this.logOut}) : super(key: key); final Future Function() logOut; @override @@ -18,7 +15,8 @@ class _LogOutDialogState extends State { title: Text( "Logout instructions", textAlign: TextAlign.center, - style: Theme.of(context).textTheme.headline4.apply(fontSizeFactor: 0.9), + style: + Theme.of(context).textTheme.headline4!.apply(fontSizeFactor: 0.9), ), content: Text( "To logout, follow the steps below\n 1. Click the logout button below\n 2. Close the website/app in the task switcher\n 3. Open the app again and sign in", diff --git a/lib/animated_check.dart b/lib/animated_check.dart new file mode 100644 index 0000000..81cd0a9 --- /dev/null +++ b/lib/animated_check.dart @@ -0,0 +1,119 @@ +// https://github.com/chrisedg87/flutter_animated_check +// MIT License +// By chrisedg87 +// Copied for null safety purposes + +import 'dart:ui'; + +import 'package:flutter/material.dart'; + +class AnimatedCheck extends StatefulWidget { + AnimatedCheck({ + Key? key, + required this.progress, + required this.size, + this.color, + this.strokeWidth, + }) : super(key: key); + + final Animation progress; + final double size; // The size of the checkmark + final Color? color; // The primary color of the checkmark + final double? strokeWidth; // The width of the checkmark stroke + + @override + State createState() => AnimatedCheckState(); +} + +class AnimatedCheckState extends State + with SingleTickerProviderStateMixin { + @override + Widget build(BuildContext context) { + return CustomPaint( + foregroundPainter: AnimatedPathPainter( + widget.progress, + widget.color ?? Theme.of(context).primaryColor, + widget.strokeWidth, + ), + child: new SizedBox( + width: widget.size, + height: widget.size, + ), + ); + } +} + +class AnimatedPathPainter extends CustomPainter { + AnimatedPathPainter( + this._animation, + this._color, + this.strokeWidth, + ) : super(repaint: _animation); + + final Animation _animation; + final Color _color; + final double? strokeWidth; + + Path _createAnyPath(Size size) { + return Path() + ..moveTo(0.27083 * size.width, 0.54167 * size.height) + ..lineTo(0.41667 * size.width, 0.68750 * size.height) + ..lineTo(0.75000 * size.width, 0.35417 * size.height); + } + + Path createAnimatedPath(Path originalPath, double animationPercent) { + final totalLength = originalPath + .computeMetrics() + .fold(0.0, (double prev, PathMetric metric) => prev + metric.length); + + final currentLength = totalLength * animationPercent; + + return extractPathUntilLength(originalPath, currentLength); + } + + Path extractPathUntilLength(Path originalPath, double length) { + var currentLength = 0.0; + + final path = new Path(); + + var metricsIterator = originalPath.computeMetrics().iterator; + + while (metricsIterator.moveNext()) { + var metric = metricsIterator.current; + + var nextLength = currentLength + metric.length; + + final isLastSegment = nextLength > length; + if (isLastSegment) { + final remainingLength = length - currentLength; + final pathSegment = metric.extractPath(0.0, remainingLength); + + path.addPath(pathSegment, Offset.zero); + break; + } else { + final pathSegment = metric.extractPath(0.0, metric.length); + path.addPath(pathSegment, Offset.zero); + } + + currentLength = nextLength; + } + + return path; + } + + @override + void paint(Canvas canvas, Size size) { + final animationPercent = this._animation.value; + final path = createAnimatedPath(_createAnyPath(size), animationPercent); + final Paint paint = Paint(); + + paint.color = _color; + paint.style = PaintingStyle.stroke; + paint.strokeWidth = strokeWidth ?? size.width * 0.06; + + canvas.drawPath(path, paint); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; +} diff --git a/pubspec.lock b/pubspec.lock index 325ae5f..bcaec92 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -8,13 +8,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.0" - animated_check: - dependency: "direct main" - description: - name: animated_check - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" async: dependency: transitive description: @@ -141,7 +134,7 @@ packages: name: http url: "https://pub.dartlang.org" source: hosted - version: "0.13.1" + version: "0.13.3" http_parser: dependency: transitive description: @@ -183,42 +176,42 @@ packages: name: package_info_plus url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.0.1" package_info_plus_linux: dependency: transitive description: name: package_info_plus_linux url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.0.1" package_info_plus_macos: dependency: transitive description: name: package_info_plus_macos url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.0.1" package_info_plus_web: dependency: transitive description: name: package_info_plus_web url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.0.1" package_info_plus_windows: dependency: transitive description: name: package_info_plus_windows url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.0.1" path: dependency: transitive description: @@ -414,5 +407,5 @@ packages: source: hosted version: "0.2.0" sdks: - dart: ">=2.12.0 <3.0.0" + dart: ">=2.12.3 <3.0.0" flutter: ">=1.22.0" diff --git a/pubspec.yaml b/pubspec.yaml index 637b488..efe9952 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,10 +4,10 @@ description: Logs BP values in Google Drive as a Sheets file publish_to: "none" # The following defines the version and build number for your application. -version: 1.1.9 +version: 1.2.0 environment: - sdk: ">=2.11.0" + sdk: ">=2.12.3" dependencies: flutter: @@ -16,8 +16,7 @@ dependencies: googleapis: ^2.0.0 # For Google Drive and Google sign in google_sign_in: ^5.0.2 # For signing in to Google url_launcher: ^6.0.3 # For opening the log file - package_info_plus: ^1.0.0 # For getting version for web - animated_check: ^1.0.1 # For file write dialog + package_info_plus: ^1.0.1 # For getting version for web google_fonts: ^2.0.0 # For monospaced font dev_dependencies: