diff --git a/README.md b/README.md index 429dfeb1..4cd160ba 100644 --- a/README.md +++ b/README.md @@ -92,4 +92,20 @@ iOS Production ### Central log library: -Can be used for logging different details. All methods are static thus no instance of Console is required. Console.info() method will print info in blue color font, Console.success() method will print log in green color font, Console.warning() method will print log in yellow color font, and Console.danger() method will print log in red color font. The files reside in `lib/vaahextendflutter/log/` folder. \ No newline at end of file +Can be used for logging different details. All methods are static thus no instance of Console is required. Console.info() method will print info in blue color font, Console.success() method will print log in green color font, Console.warning() method will print log in yellow color font, and Console.danger() method will print log in red color font. The files reside in `lib/vaahextendflutter/log/` folder. + +### Environment and Version Tag + +Environment and Version Tag can be seen on every page unless you set `showEnvAndVersionTag` for your Environment configuration in `env.dart` file. You can change color of tag by setting `envAndVersionTagColor` variable for your Environment configuration. + +NOTE: `Remember showEnvAndVersionTag for production should always be false in Environment configuration in `env.dart` file.` +```dart + 'production': defaultConfig.copyWith( + ... + showEnvAndVersionTag: false, + ), +``` + +NOTE: Whenever you create a new screen/ page, wrap the body with `TagWrapper` class. + +You can use alignment and margin properties for achieving desired results using TagWrapper. \ No newline at end of file diff --git a/lib/env.dart b/lib/env.dart index 9642ef26..de594dfd 100644 --- a/lib/env.dart +++ b/lib/env.dart @@ -1,7 +1,9 @@ import 'dart:io'; +import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:team/vaahextendflutter/log/console.dart'; + +import 'vaahextendflutter/log/console.dart'; // After changing any const you will need to restart the app (Hot-reload won't work). @@ -9,7 +11,7 @@ import 'package:team/vaahextendflutter/log/console.dart'; const String version = '1.0.0'; // version format 1.0.0 (major.minor.patch) const String build = '2022100901'; // build no format 'YYYYMMDDNUMBER' -EnvironmentConfig defaultConfig = EnvironmentConfig( +EnvironmentConfig defaultConfig = const EnvironmentConfig( envType: 'default', version: version, build: build, @@ -18,6 +20,8 @@ EnvironmentConfig defaultConfig = EnvironmentConfig( analyticsId: '', enableConsoleLogs: true, enableLocalLogs: true, + showEnvAndVersionTag: true, + envAndVersionTagColor: Colors.red, ); // To add new configuration add new key, value pair in envConfigs @@ -38,6 +42,7 @@ Map envConfigs = { envType: 'production', enableConsoleLogs: false, enableLocalLogs: false, + showEnvAndVersionTag: false, ), }; @@ -65,16 +70,18 @@ class EnvController extends GetxController { } class EnvironmentConfig { - String envType; - String version; - String build; - String baseUrl; - String apiBaseUrl; - String analyticsId; - bool enableConsoleLogs; - bool enableLocalLogs; + final String envType; + final String version; + final String build; + final String baseUrl; + final String apiBaseUrl; + final String analyticsId; + final bool enableConsoleLogs; + final bool enableLocalLogs; + final bool showEnvAndVersionTag; + final Color envAndVersionTagColor; - EnvironmentConfig({ + const EnvironmentConfig({ required this.envType, required this.version, required this.build, @@ -83,6 +90,8 @@ class EnvironmentConfig { required this.analyticsId, required this.enableConsoleLogs, required this.enableLocalLogs, + required this.showEnvAndVersionTag, + required this.envAndVersionTagColor, }); EnvironmentConfig copyWith({ @@ -94,6 +103,8 @@ class EnvironmentConfig { String? analyticsId, bool? enableConsoleLogs, bool? enableLocalLogs, + bool? showEnvAndVersionTag, + Color? envAndVersionTagColor, }) { return EnvironmentConfig( envType: envType ?? this.envType, @@ -104,6 +115,9 @@ class EnvironmentConfig { analyticsId: analyticsId ?? this.analyticsId, enableConsoleLogs: enableConsoleLogs ?? this.enableConsoleLogs, enableLocalLogs: enableLocalLogs ?? this.enableLocalLogs, + showEnvAndVersionTag: showEnvAndVersionTag ?? this.showEnvAndVersionTag, + envAndVersionTagColor: + envAndVersionTagColor ?? this.envAndVersionTagColor, ); } } diff --git a/lib/main.dart b/lib/main.dart index 32cd9204..a97d41d6 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,6 +3,8 @@ import 'package:get/get.dart'; import 'env.dart'; import 'vaahextendflutter/log/console.dart'; +import 'vaahextendflutter/base/base_stateful.dart'; +import 'vaahextendflutter/tag/tag.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); @@ -42,7 +44,7 @@ class TeamHomePage extends StatefulWidget { State createState() => _TeamHomePageState(); } -class _TeamHomePageState extends State { +class _TeamHomePageState extends BaseStateful { late EnvController envController; @override @@ -53,11 +55,15 @@ class _TeamHomePageState extends State { @override Widget build(BuildContext context) { + super.build(context); return Scaffold( appBar: AppBar(), - body: Center( - child: Text( - '${envController.config.envType} ${envController.config.version}+${envController.config.build}'), + body: const TagWrapper( + alignment: Alignment.topCenter, + margin: EdgeInsets.all(10), + child: Center( + child: Text('Webreinvent'), + ), ), ); } diff --git a/lib/vaahextendflutter/base/base_stateful.dart b/lib/vaahextendflutter/base/base_stateful.dart new file mode 100644 index 00000000..b8bdb087 --- /dev/null +++ b/lib/vaahextendflutter/base/base_stateful.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; + +import '../services/screen_util.dart'; + +abstract class BaseStateful extends State + with DynamicSize { + // Context valid to create providers + @mustCallSuper + @protected + void initDependencies(BuildContext context) { + // eg. Connectivity controller (Which checks network connectivity) + } + + @protected + void afterFirstBuild(BuildContext context) {} + + @mustCallSuper + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + afterFirstBuild(context); + }); + } + + @override + void setState(VoidCallback fn) { + if (mounted) { + super.setState(fn); + } + } + + @mustCallSuper + @override + Widget build(BuildContext context) { + initDependencies(context); + initDynamicSize(context); + return const SizedBox(); + } +} diff --git a/lib/vaahextendflutter/base/base_stateless.dart b/lib/vaahextendflutter/base/base_stateless.dart new file mode 100644 index 00000000..fb407dcb --- /dev/null +++ b/lib/vaahextendflutter/base/base_stateless.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import '../services/screen_util.dart'; + +abstract class BaseStateless extends StatelessWidget with DynamicSize { + const BaseStateless({super.key}); + + // Context valid to create controllers + @mustCallSuper + @protected + void initDependencies(BuildContext context) { + // eg. Connectivity controller + } + + @protected + void afterFirstBuild(BuildContext context) {} + + @mustCallSuper + @override + Widget build(BuildContext context) { + initDependencies(context); + initDynamicSize(context); + WidgetsBinding.instance.addPostFrameCallback((_) { + afterFirstBuild(context); + }); + return const SizedBox(); + } +} diff --git a/lib/vaahextendflutter/log/console.dart b/lib/vaahextendflutter/log/console.dart index e7a9c7be..bb153aa4 100644 --- a/lib/vaahextendflutter/log/console.dart +++ b/lib/vaahextendflutter/log/console.dart @@ -1,6 +1,7 @@ import 'package:colorize/colorize.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; + import '../../env.dart'; class Console { diff --git a/lib/vaahextendflutter/services/date_time_extension.dart b/lib/vaahextendflutter/services/date_time_extension.dart new file mode 100644 index 00000000..c3f5209d --- /dev/null +++ b/lib/vaahextendflutter/services/date_time_extension.dart @@ -0,0 +1,229 @@ +// import 'package:flutter/material.dart'; +// // import intl package + +// enum TimeZone { utc, local } + +// // Extension for DateTime +// extension DateTimeExtension on DateTime { +// String toTime(BuildContext context) => +// TimeOfDay.fromDateTime(asLocal()).format(context); + +// // Return DateTime with zero millisecond and microsecond +// DateTime resetMillisecond() { +// return DateTime(year, month, day, hour, minute, second); +// } + +// DateTime daysBefore(int days) => subtract(Duration(days: days)); + +// DateTime daysAfter(int days) => add(Duration(days: days)); + +// DateTime nextDayStart() => onlyDate().daysAfter(1); + +// DateTime localTimeToday() => DateTime.now().let( +// (DateTime now) => DateTime( +// now.year, +// now.month, +// now.day, +// hour, +// minute, +// second, +// millisecond, +// microsecond, +// ), +// ); + +// DateTime onlyDate() => +// isUtc ? DateTime.utc(year, month, day) : DateTime(year, month, day); + +// DateTime onlyMonth() => +// isUtc ? DateTime.utc(year, month) : DateTime(year, month); + +// DateTime onlyTime([int? hourArg, int? minuteArg]) => +// DateTime.utc(1970, 1, 1, hourArg ?? hour, minuteArg ?? minute, 0, 0, 0); + +// DateTime utcTimeFirstDaySinceEpoch() => +// DateTime.utc(1970, 1, 1, hour, minute, second, millisecond, microsecond); + +// // Convert local time as current utc +// // DateTime.now() = 2021-01-25 18:49:03.049422 +// // DateTime.asUtc() = 2021-01-25 18:49:03.049422 +// // DateTime.toUtc() = 2021-01-25 11:49:03.056208Z +// DateTime asUtc() => isUtc +// ? this +// : DateTime.utc( +// year, +// month, +// day, +// hour, +// minute, +// second, +// millisecond, +// microsecond, +// ); + +// DateTime asLocal() => !isUtc +// ? this +// : DateTime( +// year, +// month, +// day, +// hour, +// minute, +// second, +// millisecond, +// microsecond, +// ); + +// // Convert DateTime to String +// // Month(short) DD HH:MM AM/PM +// String toDateTimeString() { +// return DateFormat('MMM dd h:mm a').format(this); +// } + +// // Day, Month DD / HH:MM am/pm +// String toFullDateTimeString() { +// return DateFormat('EEEE, MMMM dd / h:mm a').format(this); +// } + +// // HH:MM AM +// String toHmaa() { +// return DateFormat('hh:mm aaaa').format(this); +// } + +// // HH:MM:SS +// String toHHMMSS() { +// return DateFormat('hh:mm:ss').format(this); +// } + +// // Month DD +// String toMMMMdd() { +// return DateFormat('MMMM dd').format(this); +// } + +// // Month(short) DD +// String toMMMdd() { +// return DateFormat('MMM dd').format(this); +// } + +// // YYYY-MM-DD +// String toDateOfBirth() { +// return DateFormat('yyyy-MM-dd').format(this); +// } + +// // To query format: YYYY-MM-DD +// String toQueryFormat() { +// return DateFormat('yyyy-MM-dd').format(this); +// } +// } + +// // Extension for DateTime from String +// extension DateTimeStringExtendsion on String { +// // Check Null or Empty +// bool get isNullOrEmpty => isNull || isEmpty; + +// // Convert to DateTime by pattern +// // https://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html +// DateTime toDateTime({String pattern = 'yyyy-MM-dd HH:mm:ss'}) { +// return isNullOrEmpty +// ? null +// : DateFormat(pattern).parse(this, true).toLocal(); +// } + +// String safe([String supplier()]) => +// this ?? (supplier == null ? '' : supplier()); + +// String zeroPrefix(int count) { +// if (length >= count) { +// return this; +// } else { +// String builder = ''; +// for (int i = 0; i < count - length; i++) { +// builder += '0'; +// } +// builder += this; +// return builder; +// } +// } + +// int parseInt() => int.tryParse(this); + +// double parseDouble() => double.tryParse(this); + +// String truncate(int limit) { +// return length > limit +// ? '${substring(0, min(length, limit)).trim()}...' +// : this; +// } +// } + +// // Extension for duration +// extension DurationExtension on Duration { +// Duration safe([Duration supplier()]) => +// this ?? (supplier == null ? Duration.zero : supplier()); + +// String format() { +// return toString().split('.').first.padLeft(8, '0'); +// } + +// // Add zero padding +// String _twoDigits(int n) { +// if (n >= 10) { +// return '$n'; +// } +// return '0${max(n, 0)}'; +// } + +// // hours:minutes:seconds +// String toHms() { +// final String twoDigitHours = _twoDigits(inHours.remainder(24) as int); +// final String twoDigitMinutes = _twoDigits(inMinutes.remainder(60) as int); +// final String twoDigitSeconds = _twoDigits(inSeconds.remainder(60) as int); +// return '$twoDigitHours:$twoDigitMinutes:$twoDigitSeconds'; +// } + +// // minutes:seconds +// String toMs() { +// final String twoDigitMinutes = _twoDigits(inMinutes.remainder(60) as int); +// final String twoDigitSeconds = _twoDigits(inSeconds.remainder(60) as int); +// return '$twoDigitMinutes:$twoDigitSeconds'; +// } +// } + +// // Extension for int +// extension DateExtensions on int { +// DateTime localDateTime() => +// DateTime.fromMillisecondsSinceEpoch(this, isUtc: false); + +// DateTime utcDateTime() => +// DateTime.fromMillisecondsSinceEpoch(this, isUtc: true); + +// DateTime asDateTime({TimeZone from = TimeZone.utc}) { +// switch (from) { +// case TimeZone.local: +// return localDateTime(); +// case TimeZone.utc: +// default: +// return utcDateTime(); +// } +// } + +// DateTime asLocal({TimeZone from = TimeZone.utc}) => +// asDateTime(from: from).asLocal(); + +// String toTime(BuildContext context, {TimeZone from = TimeZone.utc}) => +// asDateTime(from: from).toTime(context); + +// int localTimeToday({TimeZone from = TimeZone.utc}) => +// asDateTime(from: from).localTimeToday().millisecondsSinceEpoch; + +// int onlyDate({TimeZone from = TimeZone.utc}) => +// asDateTime(from: from).onlyDate().millisecondsSinceEpoch; + +// int onlyTime({TimeZone from = TimeZone.utc}) => +// asDateTime(from: from).utcTimeFirstDaySinceEpoch().millisecondsSinceEpoch; + +// int utcTimeFirstDaySinceEpoch({TimeZone from = TimeZone.utc}) => +// asDateTime(from: from).utcTimeFirstDaySinceEpoch().millisecondsSinceEpoch; + +// Duration asDuration() => Duration(milliseconds: this); +// } diff --git a/lib/vaahextendflutter/services/screen_util.dart b/lib/vaahextendflutter/services/screen_util.dart new file mode 100644 index 00000000..ca094aeb --- /dev/null +++ b/lib/vaahextendflutter/services/screen_util.dart @@ -0,0 +1,47 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +// Usage: +// class _MyAppState extends State with DynamicSize { +// @override +// Widget build(BuildContext context) { +// initDynamicSize(context); +// return Container(); +// } +// } + +mixin DynamicSize { + // check for logical size of the device https://www.ios-resolution.com/ + void initDynamicSize(BuildContext context) { + ScreenUtil.init( + context, + designSize: const Size( + 390, // iPhone 13 logical width (in points) + 844, // iPhone 13 logical height (in points) + ), + ); + } +} + +// to import this file use the class given bellow, most of the IDE will automatically import this file. +class ImportScreenUtil {} + +// Extension for screen util +// To set differnt font size and different height and width of element based on devices. +// eg. fontSize: 20.spExt +extension SizeExtension on num { + // setWidth + double get wExt => w.toDouble(); + + // setHeight + double get hExt => h.toDouble(); + + // Set sp with default allowFontScalingSelf = false + double get spExt => sp.toDouble(); + + // % of screen width + double get swExt => sw.toDouble(); + + // % of screen height + double get shExt => sh.toDouble(); +} diff --git a/lib/vaahextendflutter/tag/tag.dart b/lib/vaahextendflutter/tag/tag.dart new file mode 100644 index 00000000..901fdf10 --- /dev/null +++ b/lib/vaahextendflutter/tag/tag.dart @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +import '../../env.dart'; +import '../services/screen_util.dart'; + +class TagWrapper extends StatelessWidget { + final Widget child; + final EdgeInsets? margin; + final AlignmentGeometry? alignment; + + const TagWrapper({ + super.key, + required this.child, + this.margin, + this.alignment, + }); + + @override + Widget build(BuildContext context) { + bool showEnvAndVersionTag = false; + EnvController? envController; + bool envControllerExists = Get.isRegistered(); + if (envControllerExists) { + envController = Get.find(); + showEnvAndVersionTag = envController.config.showEnvAndVersionTag; + } + return showEnvAndVersionTag + ? Stack( + children: [ + child, + wrapAlignment( + alignment: alignment, + child: Container( + margin: margin, + child: tagWidget( + color: Colors.red, + envType: envController!.config.envType, + version: envController.config.version, + build: envController.config.build, + ), + ), + ), + ], + ) + : child; + } +} + +Widget tagWidget({ + required Color color, + required String envType, + required String version, + required String build, +}) { + return Container( + padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 4), + decoration: BoxDecoration( + border: Border.all( + color: Colors.red, + width: 2, + ), + borderRadius: BorderRadius.circular(20), + ), + child: Text( + '$envType $version +$build', + style: TextStyle( + fontSize: 12.spExt, + color: color, + fontWeight: FontWeight.bold, + ), + ), + ); +} + +Widget wrapAlignment({AlignmentGeometry? alignment, required Widget child}) { + if (alignment != null) { + return Align( + alignment: alignment, + child: child, + ); + } + return child; +} diff --git a/pubspec.lock b/pubspec.lock index bb494082..db6d198f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -69,6 +69,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.1" + flutter_screenutil: + dependency: "direct main" + description: + name: flutter_screenutil + url: "https://pub.dartlang.org" + source: hosted + version: "5.5.4" flutter_test: dependency: "direct dev" description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index ec6e6ae7..57616f12 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,6 +15,7 @@ dependencies: cupertino_icons: ^1.0.2 get: ^4.6.5 colorize: ^3.0.0 + flutter_screenutil: ^5.5.4 dev_dependencies: flutter_test: