From 88390a01cee70d55b10b4c629a6eef41a3006e5b Mon Sep 17 00:00:00 2001 From: kicap Date: Sat, 10 Aug 2024 07:35:00 +0800 Subject: [PATCH] first commit --- .env | 4 + .gitignore | 51 ++ .metadata | 45 ++ README.md | 16 + analysis_options.yaml | 29 + assets/logo.png | Bin 0 -> 39819 bytes lib/app/app.dart | 48 ++ lib/app/app.locator.dart | 41 + lib/app/app.logger.dart | 159 ++++ lib/app/app.router.dart | 221 +++++ lib/app/core/custom_base_view_model.dart | 25 + lib/app/themes/app_colors.dart | 30 + lib/app/themes/app_text.dart | 44 + lib/app/themes/app_theme.dart | 124 +++ lib/main.dart | 55 ++ lib/model/data_model.dart | 28 + lib/services/http_services.dart | 95 +++ lib/services/my_easyloading.dart | 39 + lib/services/my_notification.dart | 42 + lib/services/my_socket_io_client.dart | 54 ++ lib/services/other_function.dart | 145 ++++ .../views/nav_bar/log_data/log_data_view.dart | 110 +++ .../nav_bar/log_data/log_data_view_model.dart | 33 + .../nav_bar/monitoring/monitoring_view.dart | 119 +++ .../monitoring/monitoring_view_model.dart | 13 + lib/ui/views/nav_bar/nav_bar_view.dart | 83 ++ lib/ui/views/nav_bar/nav_bar_view_model.dart | 100 +++ .../splash_screen/splash_screen_view.dart | 61 ++ .../splash_screen_view_model.dart | 23 + lib/ui/widgets/my_button.dart | 34 + lib/ui/widgets/my_textformfield.dart | 91 +++ pubspec.lock | 757 ++++++++++++++++++ pubspec.yaml | 101 +++ test/widget_test.dart | 30 + 34 files changed, 2850 insertions(+) create mode 100644 .env create mode 100644 .gitignore create mode 100644 .metadata create mode 100644 README.md create mode 100644 analysis_options.yaml create mode 100644 assets/logo.png create mode 100644 lib/app/app.dart create mode 100644 lib/app/app.locator.dart create mode 100644 lib/app/app.logger.dart create mode 100644 lib/app/app.router.dart create mode 100755 lib/app/core/custom_base_view_model.dart create mode 100755 lib/app/themes/app_colors.dart create mode 100644 lib/app/themes/app_text.dart create mode 100755 lib/app/themes/app_theme.dart create mode 100644 lib/main.dart create mode 100644 lib/model/data_model.dart create mode 100644 lib/services/http_services.dart create mode 100644 lib/services/my_easyloading.dart create mode 100644 lib/services/my_notification.dart create mode 100644 lib/services/my_socket_io_client.dart create mode 100644 lib/services/other_function.dart create mode 100644 lib/ui/views/nav_bar/log_data/log_data_view.dart create mode 100644 lib/ui/views/nav_bar/log_data/log_data_view_model.dart create mode 100644 lib/ui/views/nav_bar/monitoring/monitoring_view.dart create mode 100644 lib/ui/views/nav_bar/monitoring/monitoring_view_model.dart create mode 100644 lib/ui/views/nav_bar/nav_bar_view.dart create mode 100644 lib/ui/views/nav_bar/nav_bar_view_model.dart create mode 100644 lib/ui/views/splash_screen/splash_screen_view.dart create mode 100644 lib/ui/views/splash_screen/splash_screen_view_model.dart create mode 100644 lib/ui/widgets/my_button.dart create mode 100644 lib/ui/widgets/my_textformfield.dart create mode 100644 pubspec.lock create mode 100644 pubspec.yaml create mode 100644 test/widget_test.dart diff --git a/.env b/.env new file mode 100644 index 0000000..6a660df --- /dev/null +++ b/.env @@ -0,0 +1,4 @@ +# url = 'http://192.168.20.45:3005/' +# api_url = 'http://192.168.20.45:3005/' +url = 'https://banjir-notif.kicap-karan.com/' +api_url = 'https://banjir-notif.kicap-karan.com/' \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ca54655 --- /dev/null +++ b/.gitignore @@ -0,0 +1,51 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release + +/android +/ios +/linux +/macos +/windows +/web diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..e94891d --- /dev/null +++ b/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled. + +version: + revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + channel: stable + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + - platform: android + create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + - platform: ios + create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + - platform: linux + create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + - platform: macos + create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + - platform: web + create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + - platform: windows + create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/README.md b/README.md new file mode 100644 index 0000000..05516a6 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# flood_app + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..61b6c4d --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/assets/logo.png b/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..3152e8f91383d57c5a73a693d1f9c9dd3b03e066 GIT binary patch literal 39819 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 setupLocator({ + String? environment, + EnvironmentFilter? environmentFilter, +}) async { +// Register environments + locator.registerEnvironment( + environment: environment, environmentFilter: environmentFilter); + +// Register dependencies + locator.registerLazySingleton(() => NavigationService()); + locator.registerLazySingleton(() => DialogService()); + locator.registerLazySingleton(() => SnackbarService()); + locator.registerLazySingleton(() => BottomSheetService()); + locator.registerLazySingleton(() => MyEasyLoading()); + locator.registerLazySingleton(() => MyHttpServices()); + locator.registerLazySingleton(() => OtherFunction()); + locator.registerLazySingleton(() => MySocketIoClient()); + locator.registerLazySingleton(() => MyNotification()); +} diff --git a/lib/app/app.logger.dart b/lib/app/app.logger.dart new file mode 100644 index 0000000..26ab3f9 --- /dev/null +++ b/lib/app/app.logger.dart @@ -0,0 +1,159 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ************************************************************************** +// StackedLoggerGenerator +// ************************************************************************** + +// ignore_for_file: avoid_print, depend_on_referenced_packages + +/// Maybe this should be generated for the user as well? +/// +/// import 'package:customer_app/services/stackdriver/stackdriver_service.dart'; +import 'package:flutter/foundation.dart'; +import 'package:logger/logger.dart'; + +class SimpleLogPrinter extends LogPrinter { + final String className; + final bool printCallingFunctionName; + final bool printCallStack; + final List exludeLogsFromClasses; + final String? showOnlyClass; + + SimpleLogPrinter( + this.className, { + this.printCallingFunctionName = true, + this.printCallStack = false, + this.exludeLogsFromClasses = const [], + this.showOnlyClass, + }); + + @override + List log(LogEvent event) { + var color = PrettyPrinter.levelColors[event.level]; + var emoji = PrettyPrinter.levelEmojis[event.level]; + var methodName = _getMethodName(); + + var methodNameSection = + printCallingFunctionName && methodName != null ? ' | $methodName' : ''; + var stackLog = event.stackTrace.toString(); + var output = + '$emoji $className$methodNameSection - ${event.message}${event.error != null ? '\nERROR: ${event.error}\n' : ''}${printCallStack ? '\nSTACKTRACE:\n$stackLog' : ''}'; + + if (exludeLogsFromClasses + .any((excludeClass) => className == excludeClass) || + (showOnlyClass != null && className != showOnlyClass)) return []; + + final pattern = RegExp('.{1,800}'); // 800 is the size of each chunk + List result = []; + + for (var line in output.split('\n')) { + result.addAll(pattern.allMatches(line).map((match) { + if (kReleaseMode) { + return match.group(0)!; + } else { + return color!(match.group(0)!); + } + })); + } + + return result; + } + + String? _getMethodName() { + try { + final currentStack = StackTrace.current; + final formattedStacktrace = _formatStackTrace(currentStack, 3); + if (kIsWeb) { + final classNameParts = _splitClassNameWords(className); + return _findMostMatchedTrace(formattedStacktrace!, classNameParts) + .split(' ') + .last; + } else { + final realFirstLine = formattedStacktrace + ?.firstWhere((line) => line.contains(className), orElse: () => ""); + + final methodName = realFirstLine?.replaceAll('$className.', ''); + return methodName; + } + } catch (e) { + // There's no deliberate function call from our code so we return null; + return null; + } + } + + List _splitClassNameWords(String className) { + return className + .split(RegExp(r'(?=[A-Z])')) + .map((e) => e.toLowerCase()) + .toList(); + } + + /// When the faulty word exists in the begging this method will not be very usefull + String _findMostMatchedTrace( + List stackTraces, List keyWords) { + String match = stackTraces.firstWhere( + (trace) => _doesTraceContainsAllKeywords(trace, keyWords), + orElse: () => ''); + if (match.isEmpty) { + match = _findMostMatchedTrace( + stackTraces, keyWords.sublist(0, keyWords.length - 1)); + } + return match; + } + + bool _doesTraceContainsAllKeywords(String stackTrace, List keywords) { + final formattedKeywordsAsRegex = RegExp(keywords.join('.*')); + return stackTrace.contains(formattedKeywordsAsRegex); + } +} + +final stackTraceRegex = RegExp(r'#[0-9]+[\s]+(.+) \(([^\s]+)\)'); + +List? _formatStackTrace(StackTrace stackTrace, int methodCount) { + var lines = stackTrace.toString().split('\n'); + + var formatted = []; + var count = 0; + for (var line in lines) { + var match = stackTraceRegex.matchAsPrefix(line); + if (match != null) { + if (match.group(2)!.startsWith('package:logger')) { + continue; + } + var newLine = ("${match.group(1)}"); + formatted.add(newLine.replaceAll('', '()')); + if (++count == methodCount) { + break; + } + } else { + formatted.add(line); + } + } + + if (formatted.isEmpty) { + return null; + } else { + return formatted; + } +} + +Logger getLogger( + String className, { + bool printCallingFunctionName = true, + bool printCallstack = false, + List exludeLogsFromClasses = const [], + String? showOnlyClass, +}) { + return Logger( + printer: SimpleLogPrinter( + className, + printCallingFunctionName: printCallingFunctionName, + printCallStack: printCallstack, + showOnlyClass: showOnlyClass, + exludeLogsFromClasses: exludeLogsFromClasses, + ), + output: MultiOutput([ + if (!kReleaseMode) ConsoleOutput(), + ]), + ); +} diff --git a/lib/app/app.router.dart b/lib/app/app.router.dart new file mode 100644 index 0000000..9b9ad41 --- /dev/null +++ b/lib/app/app.router.dart @@ -0,0 +1,221 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ************************************************************************** +// StackedNavigatorGenerator +// ************************************************************************** + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:flood_app/ui/views/nav_bar/log_data/log_data_view.dart' as _i6; +import 'package:flood_app/ui/views/nav_bar/monitoring/monitoring_view.dart' + as _i5; +import 'package:flood_app/ui/views/nav_bar/nav_bar_view.dart' as _i3; +import 'package:flood_app/ui/views/splash_screen/splash_screen_view.dart' + as _i2; +import 'package:flutter/material.dart' as _i4; +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart' as _i1; +import 'package:stacked_services/stacked_services.dart' as _i7; + +class Routes { + static const splashScreenView = '/'; + + static const navBarView = '/nav-bar-view'; + + static const all = { + splashScreenView, + navBarView, + }; +} + +class StackedRouter extends _i1.RouterBase { + final _routes = <_i1.RouteDef>[ + _i1.RouteDef( + Routes.splashScreenView, + page: _i2.SplashScreenView, + ), + _i1.RouteDef( + Routes.navBarView, + page: _i3.NavBarView, + ), + ]; + + final _pagesMap = { + _i2.SplashScreenView: (data) { + return _i4.MaterialPageRoute( + builder: (context) => const _i2.SplashScreenView(), + settings: data, + ); + }, + _i3.NavBarView: (data) { + return _i4.MaterialPageRoute( + builder: (context) => const _i3.NavBarView(), + settings: data, + ); + }, + }; + + @override + List<_i1.RouteDef> get routes => _routes; + + @override + Map get pagesMap => _pagesMap; +} + +class NavBarViewRoutes { + static const monitoringView = 'monitoring-view'; + + static const logDataView = 'log-data-view'; + + static const all = { + monitoringView, + logDataView, + }; +} + +class NavBarViewRouter extends _i1.RouterBase { + final _routes = <_i1.RouteDef>[ + _i1.RouteDef( + NavBarViewRoutes.monitoringView, + page: _i5.MonitoringView, + ), + _i1.RouteDef( + NavBarViewRoutes.logDataView, + page: _i6.LogDataView, + ), + ]; + + final _pagesMap = { + _i5.MonitoringView: (data) { + return _i4.MaterialPageRoute( + builder: (context) => const _i5.MonitoringView(), + settings: data, + ); + }, + _i6.LogDataView: (data) { + return _i4.MaterialPageRoute( + builder: (context) => const _i6.LogDataView(), + settings: data, + ); + }, + }; + + @override + List<_i1.RouteDef> get routes => _routes; + + @override + Map get pagesMap => _pagesMap; +} + +extension NavigatorStateExtension on _i7.NavigationService { + Future navigateToSplashScreenView([ + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + ]) async { + return navigateTo(Routes.splashScreenView, + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + Future navigateToNavBarView([ + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + ]) async { + return navigateTo(Routes.navBarView, + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + Future navigateToNestedMonitoringViewInNavBarViewRouter([ + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + ]) async { + return navigateTo(NavBarViewRoutes.monitoringView, + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + Future navigateToNestedLogDataViewInNavBarViewRouter([ + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + ]) async { + return navigateTo(NavBarViewRoutes.logDataView, + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + Future replaceWithSplashScreenView([ + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + ]) async { + return replaceWith(Routes.splashScreenView, + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + Future replaceWithNavBarView([ + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + ]) async { + return replaceWith(Routes.navBarView, + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + Future replaceWithNestedMonitoringViewInNavBarViewRouter([ + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + ]) async { + return replaceWith(NavBarViewRoutes.monitoringView, + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + Future replaceWithNestedLogDataViewInNavBarViewRouter([ + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + ]) async { + return replaceWith(NavBarViewRoutes.logDataView, + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } +} diff --git a/lib/app/core/custom_base_view_model.dart b/lib/app/core/custom_base_view_model.dart new file mode 100755 index 0000000..9010567 --- /dev/null +++ b/lib/app/core/custom_base_view_model.dart @@ -0,0 +1,25 @@ +import 'package:stacked/stacked.dart'; +import 'package:stacked_services/stacked_services.dart'; + +import '../../services/http_services.dart'; +import '../../services/my_easyloading.dart'; +import '../../services/my_notification.dart'; +import '../../services/my_socket_io_client.dart'; +import '../../services/other_function.dart'; +import '../app.locator.dart'; + +class CustomBaseViewModel extends BaseViewModel { + final dialogService = locator(); + final navigationService = locator(); + final bottomSheetService = locator(); + final snackbarService = locator(); + final otherFunction = locator(); + final socketIoClient = locator(); + final httpService = locator(); + final easyLoading = locator(); + final myNotification = locator(); + + void back() { + navigationService.back(); + } +} diff --git a/lib/app/themes/app_colors.dart b/lib/app/themes/app_colors.dart new file mode 100755 index 0000000..7e87204 --- /dev/null +++ b/lib/app/themes/app_colors.dart @@ -0,0 +1,30 @@ +import 'dart:ui'; + +const Color mainColor = Color.fromARGB(255, 6, 238, 48); +const Color secondaryColor = Color(0xFFB72025); +const Color dangerColor = Color(0xFFFF4B68); +const Color warningColor = Color(0xFFFBFFA3); +const Color lightColor = Color(0xFFF4FAFE); +const Color lightGreyColor = Color(0xFFFCFCFC); +const Color stockColor = Color(0xFFEEF3F6); + +const Color backgroundColor = Color(0xFFE5E5E5); +const Color backgroundColor3 = Color(0xFFF6F7F8); + +const Color orangeColor = Color.fromARGB(255, 250, 145, 84); +const Color blueColor = Color(0xFF026AA2); +const Color greenColor = Color(0xFF2ABB52); +const Color redColor = Color(0xFFED1717); +const Color greyBlueColor = Color(0xFF363F72); + +const Color fontColor = Color(0xFF101828); +const Color fontSecondaryColor = Color(0xFF667085); +const Color fontParagraphColor = Color(0xFFB2B2B2); +const Color fontGrey = Color(0xFF1C1C1C); + +const Color mainGrey = Color(0xFF8991A4); +const Color secondaryGrey = Color(0xFFD0D5DD); +const Color thirdGrey = Color(0xFFF2F4F7); +const Color fourthGrey = Color(0xFF5C5C5C); +const Color fifthGrey = Color(0xFFEBEBEB); +const Color sixthGrey = Color(0xFF151515); diff --git a/lib/app/themes/app_text.dart b/lib/app/themes/app_text.dart new file mode 100644 index 0000000..092d154 --- /dev/null +++ b/lib/app/themes/app_text.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; + +import 'app_colors.dart'; + +const regularTextStyle = TextStyle( + fontFamily: 'Arial', + fontSize: 14, + fontWeight: FontWeight.w400, + color: fontColor); + +const italicTextStyle = TextStyle( + fontFamily: 'Arial', + fontSize: 14, + color: fontColor, + fontStyle: FontStyle.italic, +); + +const mediumTextStyle = TextStyle( + fontFamily: 'Arial', + fontSize: 14, + fontWeight: FontWeight.w500, + color: fontColor, +); + +const semiBoldTextStyle = TextStyle( + fontFamily: 'Arial', + fontSize: 14, + fontWeight: FontWeight.w600, + color: fontColor, +); + +const boldTextStyle = TextStyle( + fontFamily: 'Arial', + fontSize: 14, + fontWeight: FontWeight.w700, + color: fontColor, +); + +const extraBoldTextStyle = TextStyle( + fontFamily: 'Arial', + fontSize: 14, + fontWeight: FontWeight.w800, + color: fontColor, +); diff --git a/lib/app/themes/app_theme.dart b/lib/app/themes/app_theme.dart new file mode 100755 index 0000000..eefa661 --- /dev/null +++ b/lib/app/themes/app_theme.dart @@ -0,0 +1,124 @@ +import 'package:flutter/material.dart'; + +import 'app_colors.dart'; +import 'app_text.dart'; + +ThemeData appTheme = ThemeData( + useMaterial3: true, + primaryColor: mainColor, + scaffoldBackgroundColor: Colors.white, + canvasColor: Colors.white, + fontFamily: 'Poppins', + appBarTheme: AppBarTheme( + elevation: 0, + titleTextStyle: boldTextStyle.copyWith(fontSize: 16, color: fontGrey), + centerTitle: true, + ), + textTheme: TextTheme( + displayLarge: regularTextStyle.copyWith(fontSize: 32), + displayMedium: regularTextStyle.copyWith(fontSize: 20), + displaySmall: regularTextStyle.copyWith(fontSize: 18), + ), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + backgroundColor: mainColor, + foregroundColor: Colors.white, + disabledBackgroundColor: mainColor.withOpacity(.3), + minimumSize: const Size(double.maxFinite, 58), + textStyle: boldTextStyle, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + shadowColor: Colors.transparent, + elevation: 0, + ), + ), + outlinedButtonTheme: OutlinedButtonThemeData( + style: OutlinedButton.styleFrom( + textStyle: boldTextStyle, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: const BorderSide( + color: mainColor, + width: 1, + ), + foregroundColor: mainColor, + // disabledForegroundColor: mainColor.withOpacity(.3), + minimumSize: const Size(double.maxFinite, 58), + ), + ), + textButtonTheme: TextButtonThemeData( + style: TextButton.styleFrom( + foregroundColor: mainColor, + disabledForegroundColor: mainColor.withOpacity(.3), + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + textStyle: semiBoldTextStyle, + shadowColor: Colors.transparent, + ), + ), + iconTheme: const IconThemeData( + color: mainColor, + ), + listTileTheme: ListTileThemeData( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + checkboxTheme: CheckboxThemeData( + fillColor: MaterialStateProperty.all(mainColor), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4), + ), + side: const BorderSide( + color: secondaryGrey, + width: 1, + ), + ), + radioTheme: RadioThemeData( + fillColor: MaterialStateProperty.all(mainColor), + ), + tabBarTheme: TabBarTheme( + labelColor: mainColor, + unselectedLabelColor: secondaryGrey, + labelStyle: boldTextStyle.copyWith(fontSize: 16), + unselectedLabelStyle: mediumTextStyle.copyWith(fontSize: 16), + ), + chipTheme: ChipThemeData( + backgroundColor: Colors.white, + disabledColor: Colors.white, + selectedColor: Colors.white, + secondarySelectedColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), + side: const BorderSide(color: fifthGrey), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + ), + labelStyle: regularTextStyle.copyWith(fontSize: 12, color: fontGrey), + secondaryLabelStyle: + regularTextStyle.copyWith(fontSize: 12, color: secondaryColor), + deleteIconColor: fontGrey, + showCheckmark: false, + ), + popupMenuTheme: PopupMenuThemeData( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + side: const BorderSide( + color: fifthGrey, + width: 1, + ), + ), + ), + colorScheme: const ColorScheme.light( + primary: mainColor, + secondary: secondaryColor, + onPrimary: Colors.white, + onSecondary: Colors.white, + error: dangerColor, + onError: dangerColor, + background: backgroundColor, + ).copyWith(background: Colors.white), +); diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..004578f --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,55 @@ +import 'dart:io'; + +import 'package:flood_app/app/app.router.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:flutter_easyloading/flutter_easyloading.dart'; +import 'package:intl/date_symbol_data_local.dart'; +import 'package:stacked_services/stacked_services.dart'; + +import 'app/app.locator.dart'; +import 'app/themes/app_theme.dart'; + +Future main() async { + await initializeDateFormatting('id_ID'); + WidgetsFlutterBinding.ensureInitialized(); + HttpOverrides.global = MyHttpOverrides(); + await dotenv.load(fileName: ".env"); + await setupAllLocator(); + runApp(const MyApp()); + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Electric Monitoring', + theme: appTheme, + debugShowCheckedModeBanner: false, + navigatorKey: StackedService.navigatorKey, + onGenerateRoute: StackedRouter().onGenerateRoute, + builder: EasyLoading.init(), + // home: const MyHomePage(title: 'Flutter Demo Home Page'), + ); + } +} + +Future setupAllLocator() async { + await setupLocator(); + // setupDialogUi(); + // setupBottomSheetUi(); + // setupSnackbarUi(); +} + +class MyHttpOverrides extends HttpOverrides { + @override + HttpClient createHttpClient(SecurityContext? context) { + return super.createHttpClient(context) + ..badCertificateCallback = + (X509Certificate cert, String host, int port) => true; + } +} diff --git a/lib/model/data_model.dart b/lib/model/data_model.dart new file mode 100644 index 0000000..2d60ec0 --- /dev/null +++ b/lib/model/data_model.dart @@ -0,0 +1,28 @@ +import '../app/app.locator.dart'; +import '../services/other_function.dart'; + +class DataModel { + final _myFunction = locator(); + int? no; + String? waterHeight; + int? status; + String? createdAt; + + DataModel({this.no, this.waterHeight, this.status, this.createdAt}); + + DataModel.fromJson(Map json) { + no = json['no']; + waterHeight = json['water_height']; + status = json['status']; + createdAt = _myFunction.formatDateString2(json['created_at']); + } + + Map toJson() { + final Map data = {}; + data['no'] = no; + data['water_height'] = waterHeight; + data['status'] = status; + data['created_at'] = createdAt; + return data; + } +} diff --git a/lib/services/http_services.dart b/lib/services/http_services.dart new file mode 100644 index 0000000..66e8ae2 --- /dev/null +++ b/lib/services/http_services.dart @@ -0,0 +1,95 @@ +import 'package:dio/dio.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:stacked_services/stacked_services.dart'; + +import '../app/app.locator.dart'; +import '../app/app.logger.dart'; + +class MyHttpServices { + final _log = getLogger('MyHttpServices'); + final _snackbarService = locator(); + final _options = BaseOptions( + baseUrl: dotenv.env['api_url']!, + connectTimeout: const Duration(seconds: 120), + receiveTimeout: const Duration(seconds: 120), + ); + + late Dio _dio; + + MyHttpServices() { + _dio = Dio(_options); + } + + Future get(String path) async { + try { + return await _dio.get(path); + } on DioException catch (e) { + String response = e.response != null + ? e.response!.data['message'].toString() + : e.toString(); + _log.e('ini errornya: $response'); + _snackbarService.showSnackbar( + message: response, + title: 'Error', + duration: const Duration(milliseconds: 1000), + ); + rethrow; + } + } + + Future postWithFormData(String path, FormData formData) async { + try { + return await _dio.post(path, data: formData); + } on DioException catch (e) { + String response = e.response != null + ? e.response!.data['message'].toString() + : e.toString(); + _log.e('ini errornya: $response'); + _snackbarService.showSnackbar( + message: response, + title: 'Error', + duration: const Duration(milliseconds: 1000), + ); + rethrow; + } + } + + // putWithFormData + Future putWithFormData(String path, FormData formData) async { + try { + return await _dio.put(path, data: formData); + } on DioException catch (e) { + String response = e.response != null + ? e.response!.data['message'].toString() + : e.toString(); + _log.e('ini errornya: $response'); + _snackbarService.showSnackbar( + message: response, + title: 'Error', + duration: const Duration(milliseconds: 1000), + ); + rethrow; + } + } + + // // delete + // Future delete(String path, FormData data) async { + // try { + // // log.i('path: $path'); + // return await _dio.delete( + // path, + // data: data, + // // encoding: Encoding.getByName('utf-8'), + // options: Options( + // headers: { + // 'Content-Type': 'application/x-www-form-urlencoded', + // }, + // ), + // ); + // } on DioError catch (e) { + // log.e(e.message); + // log.e(e.response); + // rethrow; + // } + // } +} diff --git a/lib/services/my_easyloading.dart b/lib/services/my_easyloading.dart new file mode 100644 index 0000000..98198c9 --- /dev/null +++ b/lib/services/my_easyloading.dart @@ -0,0 +1,39 @@ +import 'package:flutter_easyloading/flutter_easyloading.dart'; + +class MyEasyLoading { + showLoading() { + EasyLoading.show( + status: 'loading...', + maskType: EasyLoadingMaskType.black, + dismissOnTap: false, + ); + } + + dismiss() { + EasyLoading.dismiss(); + } + + customLoading(String message) { + EasyLoading.show( + status: message, + maskType: EasyLoadingMaskType.black, + dismissOnTap: false, + ); + } + + showSuccess(String message) { + EasyLoading.showSuccess(message); + } + + showError(String message) { + EasyLoading.showError(message); + } + + showInfo(String message) { + EasyLoading.showInfo(message); + } + + showProgress(double progress, String status) { + EasyLoading.showProgress(progress, status: status); + } +} diff --git a/lib/services/my_notification.dart b/lib/services/my_notification.dart new file mode 100644 index 0000000..272734c --- /dev/null +++ b/lib/services/my_notification.dart @@ -0,0 +1,42 @@ +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; + +class MyNotification { + static Future initialize( + FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin) async { + var androidInitialize = + const AndroidInitializationSettings('mipmap/ic_launcher'); + var iOSInitialize = const DarwinInitializationSettings(); + var initializeSettings = + InitializationSettings(android: androidInitialize, iOS: iOSInitialize); + await flutterLocalNotificationsPlugin.initialize(initializeSettings); + } + + Future showNotification( + {var id = 0, + var title, + var body, + var payload, + required FlutterLocalNotificationsPlugin + flutterLocalNotificationsPlugin}) async { + AndroidNotificationDetails androidPlatformChannelSpecifics = + const AndroidNotificationDetails( + '07eff3c8-e3d7-4386-b8a1-e6588cd9fbb5', // channelId + 'channel_name', + sound: RawResourceAndroidNotificationSound('notification_fuck'), + importance: Importance.max, + priority: Priority.high, + ); + + var iOSPlatformChannelSpecifics = const DarwinNotificationDetails(); + var platformChannelSpecifics = NotificationDetails( + android: androidPlatformChannelSpecifics, + iOS: iOSPlatformChannelSpecifics); + await flutterLocalNotificationsPlugin.show( + id, + title, + body, + platformChannelSpecifics, + payload: payload, + ); + } +} diff --git a/lib/services/my_socket_io_client.dart b/lib/services/my_socket_io_client.dart new file mode 100644 index 0000000..817e4a1 --- /dev/null +++ b/lib/services/my_socket_io_client.dart @@ -0,0 +1,54 @@ +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:socket_io_client/socket_io_client.dart'; + +import '../app/app.logger.dart'; + +class MySocketIoClient { + final log = getLogger('MySocketIoClient'); + final String _url = dotenv.env['url']!; + double waterHeight = 0; + int warningLevel = 0; + int dangerLevel = 0; + String status = '...'; + static final MySocketIoClient _instance = MySocketIoClient._internal(); + factory MySocketIoClient() => _instance; + MySocketIoClient._internal(); + + int notif = 0; + + late Socket _socket; + Socket get socket => _socket; + + Future init() async { + try { + _socket = io(_url, { + 'transports': ['websocket'], + 'autoConnect': false, + }); + _socket.connect(); + log.i('socket connected'); + } catch (e) { + log.e('error : $e'); + } + } + + Future emit(String event, dynamic data) async { + _socket.emit(event, data); + } + + Future on(String event, Function(dynamic) callback) async { + _socket.on(event, callback); + } + + Future off(String event) async { + _socket.off(event); + } + + Future disconnect() async { + _socket.disconnect(); + } + + Future connect() async { + _socket.connect(); + } +} diff --git a/lib/services/other_function.dart b/lib/services/other_function.dart new file mode 100644 index 0000000..6f0df75 --- /dev/null +++ b/lib/services/other_function.dart @@ -0,0 +1,145 @@ +import 'package:intl/intl.dart'; + +class OtherFunction { + int umur(String tanggalLahir) { + // change tanggalLahir to DateTime + DateTime date = DateTime.parse(tanggalLahir); + // get current date + DateTime now = DateTime.now(); + // get difference in year + int year = now.year - date.year; + return year; + } + + String commaFormat(int number) { + final formatter = NumberFormat('#,###'); + return formatter.format(number); + } + + String changeMonth(String month) { + switch (month) { + case 'Januari': + return '01'; + case 'Februari': + return '02'; + case 'Maret': + return '03'; + case 'April': + return '04'; + case 'Mei': + return '05'; + case 'Juni': + return '06'; + case 'Juli': + return '07'; + case 'Agustus': + return '08'; + case 'September': + return '09'; + case 'Oktober': + return '10'; + case 'November': + return '11'; + case 'Desember': + return '12'; + default: + return ''; + } + } + + String changeMonthYear(String s) { + // get the last 2 digits + String month = s.substring(s.length - 2); + // get the first 4 digits + String year = s.substring(0, 4); + // return the month and year + switch (month) { + case '01': + return 'Januari $year'; + case '02': + return 'Februari $year'; + case '03': + return 'Maret $year'; + case '04': + return 'April $year'; + case '05': + return 'Mei $year'; + case '06': + return 'Juni $year'; + case '07': + return 'Juli $year'; + case '08': + return 'Agustus $year'; + case '09': + return 'September $year'; + case '10': + return 'Oktober $year'; + case '11': + return 'November $year'; + case '12': + return 'Desember $year'; + default: + return ''; + } + } + + String getDayOfWeek(String date) { + DateTime dateTime = DateTime.parse(date); + List daysOfWeek = [ + 'Senin', + 'Selasa', + 'Rabu', + 'Kamis', + 'Jumat', + 'Sabtu', + 'Minggu' + ]; + return daysOfWeek[dateTime.weekday - 1]; + } + + String formatDateString(String dateString) { + // Remove the "T" and replace it with " | " + String formattedString = dateString.replaceAll('T', '\n'); + + // Remove the ".000Z" + formattedString = formattedString.replaceAll('.000Z', ''); + + // Parse the input string to DateTime object + DateTime dateTime = DateTime.parse(dateString); + + // Get the day of the week in Indonesian + String dayOfWeek = DateFormat.EEEE('id_ID').format(dateTime); + + // Add the day of the week to the formatted string + formattedString = '$formattedString\n$dayOfWeek'; + + return formattedString; + } + + String formatDateString2(String dateString) { + DateTime dateTime = DateTime.parse(dateString); + + // Adjust for the timezone if needed (this example assumes UTC) + dateTime = dateTime.toLocal(); + + // Format the DateTime object to match your database format + String dbDate = DateFormat('yyyy-MM-dd HH:mm:ss').format(dateTime); + + return dbDate; + } + + String timeNameRemover(String time) { + List parts = time.split(' '); + String timePart = parts[0]; + + // Split the time part into hours, minutes, and seconds + List timeComponents = timePart.split(':'); + String hours = timeComponents[0]; + String minutes = timeComponents[1]; + + // Create the new time string without seconds and with a period instead of a colon + String newTimeStr = '$hours.$minutes'; + + return newTimeStr; + } +} diff --git a/lib/ui/views/nav_bar/log_data/log_data_view.dart b/lib/ui/views/nav_bar/log_data/log_data_view.dart new file mode 100644 index 0000000..d8a1844 --- /dev/null +++ b/lib/ui/views/nav_bar/log_data/log_data_view.dart @@ -0,0 +1,110 @@ +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; + +import '../../../../app/themes/app_text.dart'; +import './log_data_view_model.dart'; + +class LogDataView extends StatelessWidget { + const LogDataView({super.key}); + + @override + Widget build(BuildContext context) { + return ViewModelBuilder.reactive( + viewModelBuilder: () => LogDataViewModel(), + onViewModelReady: (LogDataViewModel model) async { + await model.init(); + }, + builder: ( + BuildContext context, + LogDataViewModel model, + Widget? child, + ) { + return Scaffold( + body: Padding( + padding: const EdgeInsets.all(30), + child: model.isBusy + ? const Center( + child: CircularProgressIndicator(), + ) + : Table( + border: TableBorder.all(), + children: [ + const TableRow( + children: [ + TableCell( + child: Center( + child: Text( + 'Waktu', + style: boldTextStyle, + ), + ), + ), + TableCell( + child: Center( + child: Text( + 'Status', + style: boldTextStyle, + ), + ), + ), + TableCell( + child: Center( + child: Text( + 'Ketinggian Air', + style: boldTextStyle, + ), + ), + ), + ], + ), + ...model.dataList.map((data) { + return TableRow( + children: [ + TableCell( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, + ), + child: Text( + data.createdAt!, + style: regularTextStyle, + textAlign: TextAlign.center, + ), + ), + ), + TableCell( + child: Padding( + padding: + const EdgeInsets.symmetric(horizontal: 10), + child: Text( + data.status! == 0 + ? "Air Dalam \nTahap Normal" + : data.status! == 1 + ? "Air Dalam \nTahap Warning" + : "Air Dalam \nTahap Danger", + style: regularTextStyle, + textAlign: TextAlign.center, + ), + ), + ), + TableCell( + child: Center( + child: Text( + data.status == 2 + ? "${data.waterHeight!} m" + : "-", + style: regularTextStyle, + ), + ), + ), + ], + ); + }), + ], + ), + ), + ); + }, + ); + } +} diff --git a/lib/ui/views/nav_bar/log_data/log_data_view_model.dart b/lib/ui/views/nav_bar/log_data/log_data_view_model.dart new file mode 100644 index 0000000..c526086 --- /dev/null +++ b/lib/ui/views/nav_bar/log_data/log_data_view_model.dart @@ -0,0 +1,33 @@ +import 'package:flood_app/model/data_model.dart'; + +import '../../../../app/app.logger.dart'; +import '../../../../app/core/custom_base_view_model.dart'; + +class LogDataViewModel extends CustomBaseViewModel { + final log = getLogger('LogDataViewModel'); + List dataList = []; + + Future init() async { + await getData(null); + } + + getData(String? date) async { + setBusy(true); + try { + // wait 2 seconds + await Future.delayed(const Duration(seconds: 2)); + var response = await httpService.get(""); + var data = response.data; + data = data['data']; + log.i(data); + for (var i = 0; i < data.length; i++) { + dataList.add(DataModel.fromJson(data[i])); + } + notifyListeners(); + } catch (e) { + log.e(e); + } finally { + setBusy(false); + } + } +} diff --git a/lib/ui/views/nav_bar/monitoring/monitoring_view.dart b/lib/ui/views/nav_bar/monitoring/monitoring_view.dart new file mode 100644 index 0000000..33def56 --- /dev/null +++ b/lib/ui/views/nav_bar/monitoring/monitoring_view.dart @@ -0,0 +1,119 @@ +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; + +import '../../../../app/themes/app_colors.dart'; +import '../../../../app/themes/app_text.dart'; +import './monitoring_view_model.dart'; + +class MonitoringView extends StatelessWidget { + const MonitoringView({super.key}); + + @override + Widget build(BuildContext context) { + return ViewModelBuilder.reactive( + viewModelBuilder: () => MonitoringViewModel(), + onViewModelReady: (MonitoringViewModel model) async { + await model.init(); + }, + builder: ( + BuildContext context, + MonitoringViewModel model, + Widget? child, + ) { + return Scaffold( + body: Padding( + padding: const EdgeInsets.all(30), + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Notif Banjir", + style: boldTextStyle.copyWith(fontSize: 40), + ), + const SizedBox(height: 20), + const Image( + image: AssetImage("assets/logo.png"), + width: 125, + height: 125, + ), + const SizedBox(height: 20), + Container( + padding: const EdgeInsets.all(20), + width: double.infinity, + decoration: BoxDecoration( + color: mainColor, + borderRadius: BorderRadius.circular(10.0), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TheText( + title: 'Status', + text: model.socketIoClient.status, + ), + TheText( + title: 'Warning Level', + text: model.socketIoClient.status == "..." + ? "No Data" + : model.socketIoClient.warningLevel == 0 + ? 'Air Belum Mencapai Tahap Warning' + : "Air Mencapai Tahap Warning", + ), + TheText( + title: 'Danger Level', + text: model.socketIoClient.status == "..." + ? "No Data" + : model.socketIoClient.dangerLevel == 0 + ? 'Air Belum Mencapai Tahap Danger' + : "Air Mencapai Tahap Danger", + ), + TheText( + title: 'Water Height', + text: model.socketIoClient.dangerLevel == 1 + ? '${model.socketIoClient.waterHeight} m' + : '-', + ), + ], + ), + ), + ], + ), + ), + ), + ); + }, + ); + } +} + +class TheText extends StatelessWidget { + const TheText({ + super.key, + required this.title, + required this.text, + }); + + final String title; + final String text; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(bottom: 10), + child: RichText( + text: TextSpan( + text: '$title : ', + style: boldTextStyle, + children: [ + TextSpan( + text: text, + style: regularTextStyle, + ), + ], + ), + ), + ); + } +} diff --git a/lib/ui/views/nav_bar/monitoring/monitoring_view_model.dart b/lib/ui/views/nav_bar/monitoring/monitoring_view_model.dart new file mode 100644 index 0000000..ddc9f16 --- /dev/null +++ b/lib/ui/views/nav_bar/monitoring/monitoring_view_model.dart @@ -0,0 +1,13 @@ +import '../../../../app/app.logger.dart'; +import '../../../../app/core/custom_base_view_model.dart'; + +class MonitoringViewModel extends CustomBaseViewModel { + final log = getLogger('MonitoringViewModel'); + + Future init() async { + while (true) { + notifyListeners(); + await Future.delayed(const Duration(seconds: 1)); + } + } +} diff --git a/lib/ui/views/nav_bar/nav_bar_view.dart b/lib/ui/views/nav_bar/nav_bar_view.dart new file mode 100644 index 0000000..d8af648 --- /dev/null +++ b/lib/ui/views/nav_bar/nav_bar_view.dart @@ -0,0 +1,83 @@ +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; +import 'package:stacked_services/stacked_services.dart'; +import 'package:stylish_bottom_bar/stylish_bottom_bar.dart'; + +import '../../../app/app.router.dart'; +import '../../../app/themes/app_colors.dart'; +import '../../../app/themes/app_text.dart'; +import './nav_bar_view_model.dart'; + +class NavBarView extends StatelessWidget { + const NavBarView({super.key}); + + @override + Widget build(BuildContext context) { + return ViewModelBuilder.reactive( + viewModelBuilder: () => NavBarViewModel(), + onViewModelReady: (NavBarViewModel model) async { + await model.init(); + }, + builder: ( + BuildContext context, + NavBarViewModel model, + Widget? child, + ) { + return WillPopScope( + onWillPop: () async { + return false; + }, + child: Scaffold( + appBar: AppBar( + backgroundColor: mainColor, + title: Text( + model.bottomNavBarList[model.currentIndex]['name'], + style: boldTextStyle, + ), + ), + body: ExtendedNavigator( + navigatorKey: StackedService.nestedNavigationKey(3), + router: NavBarViewRouter(), + initialRoute: NavBarViewRoutes.monitoringView, + ), + bottomNavigationBar: StylishBottomBar( + items: [ + for (var item in model.bottomNavBarList) + BottomBarItem( + icon: Icon(item['icon'], + color: model.currentIndex == + model.bottomNavBarList.indexOf(item) + ? sixthGrey + : backgroundColor), + title: Text( + item['name'], + style: regularTextStyle.copyWith( + color: model.currentIndex == + model.bottomNavBarList.indexOf(item) + ? sixthGrey + : mainGrey, + ), + ), + backgroundColor: model.currentIndex == + model.bottomNavBarList.indexOf(item) + ? fontColor + : mainGrey, + ), + ], + currentIndex: model.currentIndex, + hasNotch: true, + backgroundColor: mainColor, + onTap: (value) { + model.handleNavigation(value); + }, + option: BubbleBarOptions( + barStyle: BubbleBarStyle.horizontal, + bubbleFillStyle: BubbleFillStyle.fill, + opacity: 0.3), + ), + ), + ); + }, + ); + } +} diff --git a/lib/ui/views/nav_bar/nav_bar_view_model.dart b/lib/ui/views/nav_bar/nav_bar_view_model.dart new file mode 100644 index 0000000..ccb077c --- /dev/null +++ b/lib/ui/views/nav_bar/nav_bar_view_model.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:stacked/stacked.dart'; +import 'package:stacked_services/stacked_services.dart'; + +import '../../../app/app.locator.dart'; +import '../../../app/app.logger.dart'; +import '../../../app/app.router.dart'; +import '../../../services/my_notification.dart'; +import '../../../services/my_socket_io_client.dart'; + +final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = + FlutterLocalNotificationsPlugin(); + +class NavBarViewModel extends IndexTrackingViewModel { + final log = getLogger('NavBarViewModel'); + final _navigationService = locator(); + final _socketIoClient = locator(); + final _myNotification = locator(); + + final _bottomNavBarList = [ + { + 'name': 'Real Time', + 'icon': Icons.home_outlined, + }, + { + 'name': 'Log Data', + 'icon': Icons.list_alt_outlined, + }, + ]; + + List> get bottomNavBarList => _bottomNavBarList; + + final List _views = [ + NavBarViewRoutes.monitoringView, + NavBarViewRoutes.logDataView, + ]; + + Future init() async { + _socketIoClient.on('data', (data) { + // log.i('data : $data'); + var waterHeight = data['water_height']; + _socketIoClient.waterHeight = waterHeight is int + ? waterHeight.toDouble() + : waterHeight is double + ? waterHeight + : double.parse(waterHeight as String); + + _socketIoClient.warningLevel = data['warning_level']; + _socketIoClient.dangerLevel = data['danger_level']; + + if (_socketIoClient.dangerLevel == 1) { + _socketIoClient.status = + "Bahaya , Peringatan Banjir, Air Melewati Batas"; + if (_socketIoClient.notif < 2) { + _myNotification.showNotification( + id: 1, + title: 'Peringatan Banjir', + body: 'Air Melewati Batas', + payload: 'payload', + flutterLocalNotificationsPlugin: flutterLocalNotificationsPlugin, + ); + _socketIoClient.notif = 2; + } + } else if (_socketIoClient.warningLevel == 1) { + _socketIoClient.status = + "Peringatan Banjir, Air Dalam Skala 4:5 atau lebih"; + if (_socketIoClient.notif == 0) { + _myNotification.showNotification( + id: 2, + title: 'Peringatan Banjir', + body: 'Air Dalam Skala 4:5 atau lebih', + payload: 'payload', + flutterLocalNotificationsPlugin: flutterLocalNotificationsPlugin, + ); + _socketIoClient.notif = 1; + } + } else { + _socketIoClient.status = "Normal"; + _socketIoClient.notif = 0; + } + + notifyListeners(); + }); + } + + void handleNavigation(int index) { + log.d("handleNavigation: $index"); + log.d("currentIndex: $currentIndex"); + + if (currentIndex == index) return; + + setIndex(index); + // header = _bottomNavBarList[index]['header'] as String; + _navigationService.navigateTo( + _views[index], + id: 3, + ); + } +} diff --git a/lib/ui/views/splash_screen/splash_screen_view.dart b/lib/ui/views/splash_screen/splash_screen_view.dart new file mode 100644 index 0000000..4351338 --- /dev/null +++ b/lib/ui/views/splash_screen/splash_screen_view.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; + +import '../../../app/themes/app_text.dart'; +import './splash_screen_view_model.dart'; + +class SplashScreenView extends StatelessWidget { + const SplashScreenView({super.key}); + + @override + Widget build(BuildContext context) { + return ViewModelBuilder.nonReactive( + viewModelBuilder: () => SplashScreenViewModel(), + onViewModelReady: (SplashScreenViewModel model) async { + await model.init(); + }, + builder: ( + BuildContext context, + SplashScreenViewModel model, + Widget? child, + ) { + return Scaffold( + // backgroundColor: mainColor, + body: Column( + children: [ + const SizedBox(), + Expanded( + child: Center( + // show the logo.png + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Image( + image: AssetImage("assets/logo.png"), + width: 200, + height: 200, + ), + const SizedBox(height: 10), + Text( + "Notif Banjir", + style: boldTextStyle.copyWith( + fontSize: 20, + ), + ) + ], + ), + ), + ), + const Text( + "Made with Flutter and Passion By Kk", + textAlign: TextAlign.center, + style: regularTextStyle, + ), + const SizedBox(height: 15), + ], + ), + ); + }, + ); + } +} diff --git a/lib/ui/views/splash_screen/splash_screen_view_model.dart b/lib/ui/views/splash_screen/splash_screen_view_model.dart new file mode 100644 index 0000000..b5551a1 --- /dev/null +++ b/lib/ui/views/splash_screen/splash_screen_view_model.dart @@ -0,0 +1,23 @@ +import 'package:flood_app/services/my_notification.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; + +import '../../../app/app.logger.dart'; +import '../../../app/app.router.dart'; +import '../../../app/core/custom_base_view_model.dart'; + +final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = + FlutterLocalNotificationsPlugin(); + +class SplashScreenViewModel extends CustomBaseViewModel { + final log = getLogger('SplashScreenViewModel'); + Future init() async { + await Future.delayed(const Duration(seconds: 2)); + // navigate to login page + // ignore: use_build_context_synchronously + MyNotification.initialize(flutterLocalNotificationsPlugin); + socketIoClient.init(); + // socketIoClient.connect(); + + navigationService.replaceWith(Routes.navBarView); + } +} diff --git a/lib/ui/widgets/my_button.dart b/lib/ui/widgets/my_button.dart new file mode 100644 index 0000000..13fb767 --- /dev/null +++ b/lib/ui/widgets/my_button.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; + +import '../../app/themes/app_colors.dart'; + +class MyButton extends StatelessWidget { + const MyButton({ + Key? key, + required this.text, + this.onPressed, + }) : super(key: key); + + final String text; + final VoidCallback? onPressed; + + @override + Widget build(BuildContext context) { + return ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: mainColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(30), + ), + ), + onPressed: onPressed, + child: Text( + text, + style: const TextStyle( + color: backgroundColor, + fontSize: 18, + ), + ), + ); + } +} diff --git a/lib/ui/widgets/my_textformfield.dart b/lib/ui/widgets/my_textformfield.dart new file mode 100644 index 0000000..fe069bb --- /dev/null +++ b/lib/ui/widgets/my_textformfield.dart @@ -0,0 +1,91 @@ +import 'package:flutter/material.dart'; + +import '../../app/themes/app_colors.dart'; + +class MyTextFormField extends StatelessWidget { + const MyTextFormField({ + Key? key, + this.labelText, + this.hintText, + this.obscureText, + this.validator, + this.suffixIcon, + this.prefixIcon, + this.focusNode, + this.controller, + this.maxLines = 1, + this.onEditingComplete, + this.readOnly = false, + this.onTap, + this.keyboardType = TextInputType.text, + this.initialValue, + this.enabled = true, + this.maxLength, + }) : super(key: key); + + final String? labelText; + final String? hintText; + final bool? obscureText; + final FormFieldValidator? validator; + final Widget? suffixIcon; + final Widget? prefixIcon; + final FocusNode? focusNode; + final TextEditingController? controller; + final int maxLines; + final VoidCallback? onEditingComplete; + final bool readOnly; + final VoidCallback? onTap; + final TextInputType keyboardType; + final String? initialValue; + final bool enabled; + final int? maxLength; + + @override + Widget build(BuildContext context) { + return TextFormField( + maxLength: maxLength, + enabled: enabled, + initialValue: initialValue, + onEditingComplete: onEditingComplete, + maxLines: maxLines, + controller: controller, + focusNode: focusNode, + obscureText: obscureText ?? false, + readOnly: readOnly, + onTap: onTap, + keyboardType: keyboardType, + decoration: InputDecoration( + prefixIcon: prefixIcon, + suffixIcon: suffixIcon, + enabledBorder: const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(25)), + borderSide: BorderSide( + color: mainColor, + ), + ), + focusedBorder: const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(25)), + borderSide: BorderSide( + color: mainColor, + ), + ), + focusedErrorBorder: const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(25)), + borderSide: BorderSide( + color: dangerColor, + ), + ), + errorBorder: const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(25)), + borderSide: BorderSide( + color: dangerColor, + ), + ), + labelText: labelText, + hintText: hintText, + labelStyle: const TextStyle(color: fontColor), + ), + validator: validator, + ); + } +} diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..c5b7778 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,757 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a + url: "https://pub.dev" + source: hosted + version: "61.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 + url: "https://pub.dev" + source: hosted + version: "5.13.0" + args: + dependency: transitive + description: + name: args + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + url: "https://pub.dev" + source: hosted + version: "2.5.0" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + build: + dependency: transitive + description: + name: build + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + build_config: + dependency: transitive + description: + name: build_config + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + url: "https://pub.dev" + source: hosted + version: "1.1.1" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" + url: "https://pub.dev" + source: hosted + version: "4.0.1" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22" + url: "https://pub.dev" + source: hosted + version: "2.4.9" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799" + url: "https://pub.dev" + source: hosted + version: "7.3.0" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb + url: "https://pub.dev" + source: hosted + version: "8.9.2" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev" + source: hosted + version: "2.0.3" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 + url: "https://pub.dev" + source: hosted + version: "4.10.0" + collection: + dependency: transitive + description: + name: collection + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + url: "https://pub.dev" + source: hosted + version: "1.17.1" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d + url: "https://pub.dev" + source: hosted + version: "1.0.6" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + dbus: + dependency: transitive + description: + name: dbus + sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac" + url: "https://pub.dev" + source: hosted + version: "0.7.10" + dio: + dependency: "direct main" + description: + name: dio + sha256: e17f6b3097b8c51b72c74c9f071a605c47bcc8893839bd66732457a5ebe73714 + url: "https://pub.dev" + source: hosted + version: "5.5.0+1" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "36c5b2d79eb17cdae41e974b7a8284fec631651d2a6f39a8a2ff22327e90aeac" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_dotenv: + dependency: "direct main" + description: + name: flutter_dotenv + sha256: "9357883bdd153ab78cbf9ffa07656e336b8bbb2b5a3ca596b0b27e119f7c7d77" + url: "https://pub.dev" + source: hosted + version: "5.1.0" + flutter_easyloading: + dependency: "direct main" + description: + name: flutter_easyloading + sha256: ba21a3c883544e582f9cc455a4a0907556714e1e9cf0eababfcb600da191d17c + url: "https://pub.dev" + source: hosted + version: "3.0.5" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + url: "https://pub.dev" + source: hosted + version: "2.0.3" + flutter_local_notifications: + dependency: "direct main" + description: + name: flutter_local_notifications + sha256: c500d5d9e7e553f06b61877ca6b9c8b92c570a4c8db371038702e8ce57f8a50f + url: "https://pub.dev" + source: hosted + version: "17.2.2" + flutter_local_notifications_linux: + dependency: transitive + description: + name: flutter_local_notifications_linux + sha256: c49bd06165cad9beeb79090b18cd1eb0296f4bf4b23b84426e37dd7c027fc3af + url: "https://pub.dev" + source: hosted + version: "4.0.1" + flutter_local_notifications_platform_interface: + dependency: transitive + description: + name: flutter_local_notifications_platform_interface + sha256: "85f8d07fe708c1bdcf45037f2c0109753b26ae077e9d9e899d55971711a4ea66" + url: "https://pub.dev" + source: hosted + version: "7.2.0" + flutter_spinkit: + dependency: transitive + description: + name: flutter_spinkit + sha256: d2696eed13732831414595b98863260e33e8882fc069ee80ec35d4ac9ddb0472 + url: "https://pub.dev" + source: hosted + version: "5.2.1" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + freezed_annotation: + dependency: transitive + description: + name: freezed_annotation + sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 + url: "https://pub.dev" + source: hosted + version: "2.4.4" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + get: + dependency: transitive + description: + name: get + sha256: "2ba20a47c8f1f233bed775ba2dd0d3ac97b4cf32fc17731b3dfc672b06b0e92a" + url: "https://pub.dev" + source: hosted + version: "4.6.5" + get_it: + dependency: transitive + description: + name: get_it + sha256: e6017ce7fdeaf218dc51a100344d8cb70134b80e28b760f8bb23c242437bafd7 + url: "https://pub.dev" + source: hosted + version: "7.6.7" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + graphs: + dependency: transitive + description: + name: graphs + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + url: "https://pub.dev" + source: hosted + version: "2.3.1" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + intl: + dependency: "direct main" + description: + name: intl + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + url: "https://pub.dev" + source: hosted + version: "0.19.0" + io: + dependency: transitive + description: + name: io + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + lints: + dependency: transitive + description: + name: lints + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + logger: + dependency: transitive + description: + name: logger + sha256: "7ad7215c15420a102ec687bb320a7312afd449bac63bfb1c60d9787c27b9767f" + url: "https://pub.dev" + source: hosted + version: "1.4.0" + logging: + dependency: transitive + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + url: "https://pub.dev" + source: hosted + version: "0.12.15" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + url: "https://pub.dev" + source: hosted + version: "0.2.0" + meta: + dependency: transitive + description: + name: meta + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + mime: + dependency: transitive + description: + name: mime + sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + url: "https://pub.dev" + source: hosted + version: "1.0.4" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + path: + dependency: transitive + description: + name: path + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + url: "https://pub.dev" + source: hosted + version: "1.8.3" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 + url: "https://pub.dev" + source: hosted + version: "5.4.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + provider: + dependency: transitive + description: + name: provider + sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c + url: "https://pub.dev" + source: hosted + version: "6.1.2" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + recase: + dependency: transitive + description: + name: recase + sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213 + url: "https://pub.dev" + source: hosted + version: "4.1.0" + shelf: + dependency: transitive + description: + name: shelf + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + url: "https://pub.dev" + source: hosted + version: "1.4.1" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + socket_io_client: + dependency: "direct main" + description: + name: socket_io_client + sha256: ede469f3e4c55e8528b4e023bdedbc20832e8811ab9b61679d1ba3ed5f01f23b + url: "https://pub.dev" + source: hosted + version: "2.0.3+1" + socket_io_common: + dependency: transitive + description: + name: socket_io_common + sha256: "2ab92f8ff3ebbd4b353bf4a98bee45cc157e3255464b2f90f66e09c4472047eb" + url: "https://pub.dev" + source: hosted + version: "2.0.3" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + url: "https://pub.dev" + source: hosted + version: "1.9.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + url: "https://pub.dev" + source: hosted + version: "1.11.0" + stacked: + dependency: "direct main" + description: + name: stacked + sha256: ed19ecdc2dcc682b9be9c7e34646e603c0f770437a914b15c7d2d13391c92a09 + url: "https://pub.dev" + source: hosted + version: "3.4.3" + stacked_generator: + dependency: "direct dev" + description: + name: stacked_generator + sha256: c141baf56cb7168dfde142d3578118c7bf914be9fb62bc8593344e97894ac8f9 + url: "https://pub.dev" + source: hosted + version: "1.5.1" + stacked_services: + dependency: "direct main" + description: + name: stacked_services + sha256: df2780a026fd1d3671aceaa9d7cae9e8d15d577c71ef29d7dd6f79a0a289e77e + url: "https://pub.dev" + source: hosted + version: "0.9.12" + stacked_shared: + dependency: transitive + description: + name: stacked_shared + sha256: "26e11dcfe23df81d565d0180eb5bcf4742efed066ba3328623b458f21a82b346" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + stylish_bottom_bar: + dependency: "direct main" + description: + name: stylish_bottom_bar + sha256: ca72557a5bd8f44caae9017eb3a73002e9189d7a9d2fac598fa55be13724f32b + url: "https://pub.dev" + source: hosted + version: "1.1.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + url: "https://pub.dev" + source: hosted + version: "0.5.1" + timezone: + dependency: transitive + description: + name: timezone + sha256: "2236ec079a174ce07434e89fcd3fcda430025eb7692244139a9cf54fdcf1fc7d" + url: "https://pub.dev" + source: hosted + version: "0.9.4" + timing: + dependency: transitive + description: + name: timing + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + universal_io: + dependency: transitive + description: + name: universal_io + sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + watcher: + dependency: transitive + description: + name: watcher + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + url: "https://pub.dev" + source: hosted + version: "2.4.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + url: "https://pub.dev" + source: hosted + version: "1.0.4" + xml: + dependency: transitive + description: + name: xml + sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" + url: "https://pub.dev" + source: hosted + version: "6.3.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" +sdks: + dart: ">=3.0.0 <4.0.0" + flutter: ">=3.10.0" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..fd151a2 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,101 @@ +name: flood_app +description: A new Flutter project. +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: '>=3.0.0 <4.0.0' + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + stacked: ^3.0.1 + stacked_services: ^0.9.8 + flutter_dotenv: + dio: + intl: + flutter_easyloading: + stylish_bottom_bar: ^1.0.0 + socket_io_client: + flutter_local_notifications: + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^2.0.0 + build_runner: + stacked_generator: + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + assets: + - .env + - assets/logo.png + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/test/widget_test.dart b/test/widget_test.dart new file mode 100644 index 0000000..75ec9de --- /dev/null +++ b/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:flood_app/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +}