diff --git a/agrilink_vocpro/assets/images/green_house_image.jpg b/agrilink_vocpro/assets/images/green_house_image.jpg new file mode 100644 index 0000000..090af99 Binary files /dev/null and b/agrilink_vocpro/assets/images/green_house_image.jpg differ diff --git a/agrilink_vocpro/lib/core/constant/app_color.dart b/agrilink_vocpro/lib/core/constant/app_color.dart index 714dd44..db96314 100644 --- a/agrilink_vocpro/lib/core/constant/app_color.dart +++ b/agrilink_vocpro/lib/core/constant/app_color.dart @@ -1,12 +1,13 @@ import 'package:flutter/material.dart'; class AppColor { - static const Color primary = Color(0xFF00B14F); - static const Color greenLight = Color(0xFFE6F4F1); - static const Color greenDark = Color(0xFF00913A); + static const Color primary = Color(0xFF227267); + static const Color darkGreen = Color(0xFF16423C); + static const Color secondary = Color(0xFF399C8F); + static const Color ternary = Color(0xFF48BBAC); static const Color greenText = Color(0xFF00B14F); - static const Color greenTextLight = Color(0xFFE6F4F1); + static const Color greenTextLight = Color(0xFFE9EFEC); static const Color greenTextDark = Color(0xFF00913A); static const Color textDisable = Color(0xFFBDBDBD); diff --git a/agrilink_vocpro/lib/core/constant/app_theme.dart b/agrilink_vocpro/lib/core/constant/app_theme.dart index 0285677..0cdc498 100644 --- a/agrilink_vocpro/lib/core/constant/app_theme.dart +++ b/agrilink_vocpro/lib/core/constant/app_theme.dart @@ -1 +1,36 @@ -class AppTheme {} +import 'package:flutter/material.dart'; + +class AppTheme { + static TextStyle headline1 = const TextStyle( + fontSize: 32, + fontWeight: FontWeight.w500, + letterSpacing: 1, + color: Colors.black, + ); + static TextStyle titleLarge = const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Colors.black, + ); + static TextStyle titleMedium = const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Colors.black, + ); + static TextStyle titleSmall = const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Colors.black, + ); + + static TextStyle labelMedium = const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Colors.black, + ); + static TextStyle labelSmall = const TextStyle( + fontSize: 10, + fontWeight: FontWeight.w400, + color: Colors.grey, + ); +} diff --git a/agrilink_vocpro/lib/core/extension/extention.dart b/agrilink_vocpro/lib/core/extension/extention.dart index b67f92f..095604d 100644 --- a/agrilink_vocpro/lib/core/extension/extention.dart +++ b/agrilink_vocpro/lib/core/extension/extention.dart @@ -1,5 +1,26 @@ -class Extenstion { - static String capitalize(String s) { - return s[0].toUpperCase() + s.substring(1); +import 'package:intl/intl.dart'; + +String capitalize(String s) { + return s[0].toUpperCase() + s.substring(1); +} + +String dateFormater(String date) { + final DateTime dateTime = DateTime.parse(date); + final DateFormat formatter = DateFormat('dd MMM yyyy'); + return formatter.format(dateTime); +} + +String getGreeting(String time) { + DateTime parsedTime = DateTime.parse(time); + int hour = parsedTime.hour; + + if (hour >= 5 && hour < 12) { + return 'Selamat Pagi'; + } else if (hour >= 12 && hour < 15) { + return 'Selamat Siang'; + } else if (hour >= 15 && hour < 18) { + return 'Selamat Sore'; + } else { + return 'Selamat Malam'; } -} \ No newline at end of file +} diff --git a/agrilink_vocpro/lib/features/auth/view/login_screen.dart b/agrilink_vocpro/lib/features/auth/view/login_screen.dart new file mode 100644 index 0000000..e69de29 diff --git a/agrilink_vocpro/lib/features/dashboard/provider/dashboard_provider.dart b/agrilink_vocpro/lib/features/dashboard/provider/dashboard_provider.dart new file mode 100644 index 0000000..2d2c99e --- /dev/null +++ b/agrilink_vocpro/lib/features/dashboard/provider/dashboard_provider.dart @@ -0,0 +1,23 @@ +import 'package:agrilink_vocpro/features/home/view/home_screen.dart'; +import 'package:flutter/material.dart'; + +class DashboardProvider extends ChangeNotifier { + int _currentIndex = 0; + int get currentIndex => _currentIndex; + + final date = DateTime.now(); + + void setCurrentIndex(int index) { + _currentIndex = index; + notifyListeners(); + } + + final List _screens = [ + const HomeScreen(), + const Center(child: Text('Control')), + const Center(child: Text('Plants')), + const Center(child: Text('Settings')), + ]; + + List get screens => _screens; +} diff --git a/agrilink_vocpro/lib/features/dashboard/view/dashboard_screen.dart b/agrilink_vocpro/lib/features/dashboard/view/dashboard_screen.dart new file mode 100644 index 0000000..08f98bc --- /dev/null +++ b/agrilink_vocpro/lib/features/dashboard/view/dashboard_screen.dart @@ -0,0 +1,51 @@ +import 'package:agrilink_vocpro/features/dashboard/provider/dashboard_provider.dart'; +import 'package:bootstrap_icons/bootstrap_icons.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import '../../../core/constant/app_color.dart'; + +class DashboardScreen extends StatelessWidget { + const DashboardScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Consumer(builder: (context, provider, child) { + return Scaffold( + body: provider.screens[provider.currentIndex], + bottomNavigationBar: BottomNavigationBar( + iconSize: 24, + selectedFontSize: 12, + selectedItemColor: AppColor.secondary, + unselectedFontSize: 12, + backgroundColor: Colors.white, + type: BottomNavigationBarType.fixed, + currentIndex: provider.currentIndex, + onTap: provider.setCurrentIndex, + items: const [ + BottomNavigationBarItem( + icon: Icon(BootstrapIcons.house), + activeIcon: Icon(BootstrapIcons.house_fill), + label: 'Home', + ), + BottomNavigationBarItem( + icon: Icon(BootstrapIcons.toggle_off), + activeIcon: Icon(BootstrapIcons.toggle_on), + label: 'Control', + ), + BottomNavigationBarItem( + icon: Icon(BootstrapIcons.flower1), + activeIcon: Icon(BootstrapIcons.flower1), + label: 'Plants', + ), + BottomNavigationBarItem( + icon: Icon(BootstrapIcons.gear), + activeIcon: Icon(BootstrapIcons.gear_fill), + label: 'Setting', + ), + ], + ), + ); + }); + } +} diff --git a/agrilink_vocpro/lib/features/home/provider/home_provider.dart b/agrilink_vocpro/lib/features/home/provider/home_provider.dart new file mode 100644 index 0000000..438e0da --- /dev/null +++ b/agrilink_vocpro/lib/features/home/provider/home_provider.dart @@ -0,0 +1,5 @@ +import 'package:flutter/material.dart'; + +class HomeProvider extends ChangeNotifier { + final DateTime currentDate = DateTime.now(); +} diff --git a/agrilink_vocpro/lib/features/home/view/home_screen.dart b/agrilink_vocpro/lib/features/home/view/home_screen.dart new file mode 100644 index 0000000..dc54f4f --- /dev/null +++ b/agrilink_vocpro/lib/features/home/view/home_screen.dart @@ -0,0 +1,145 @@ +import 'package:agrilink_vocpro/core/constant/app_color.dart'; +import 'package:agrilink_vocpro/core/constant/app_theme.dart'; +import 'package:agrilink_vocpro/core/extension/extention.dart'; +import 'package:agrilink_vocpro/features/home/provider/home_provider.dart'; +import 'package:agrilink_vocpro/features/home/widgets/data_display_widget.dart'; +import 'package:agrilink_vocpro/features/humidity/view/humidity_screen.dart'; +import 'package:bootstrap_icons/bootstrap_icons.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class HomeScreen extends StatelessWidget { + const HomeScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text( + 'Home', + style: AppTheme.titleMedium, + ), + centerTitle: true, + backgroundColor: Colors.white, + ), + body: SafeArea( + child: RefreshIndicator( + onRefresh: () async { + await Future.delayed(const Duration(seconds: 1)); + }, + child: ListView( + padding: const EdgeInsets.all(16), + children: [ + Text( + '${getGreeting(DateTime.now().toString())}, Fikril', + style: AppTheme.titleLarge, + ), + const SizedBox(height: 12), + Consumer(builder: (context, provider, child) { + return Container( + padding: const EdgeInsets.all(16), + height: MediaQuery.of(context).size.height * 0.17, + decoration: BoxDecoration( + color: AppColor.secondary, + image: const DecorationImage( + image: + AssetImage('assets/images/green_house_image.jpg'), + fit: BoxFit.cover), + borderRadius: BorderRadius.circular(16), + ), + child: Column( + children: [ + Container( + decoration: BoxDecoration( + color: AppColor.ternary.withAlpha(200), + borderRadius: BorderRadius.circular(32), + ), + padding: const EdgeInsets.symmetric( + vertical: 8, horizontal: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Fikril's Greenhouse", + style: AppTheme.labelMedium + .copyWith(color: Colors.white)), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: AppColor.primary, + borderRadius: BorderRadius.circular(16), + ), + child: Text( + dateFormater(DateTime.now().toString()), + style: AppTheme.labelMedium + .copyWith(color: Colors.white), + ), + ), + ], + ), + ), + ], + ), + ); + }), + const SizedBox(height: 16), + Text('Recent Activity', style: AppTheme.titleMedium), + const SizedBox(height: 16), + GridView( + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 16, + mainAxisSpacing: 16, + ), + children: [ + DataDisplayerWidget( + title: 'Humidity', + subtitle: 'kelembaban udara', + value: '60', + unit: '%', + icon: BootstrapIcons.droplet_half, + textColor: Colors.white, + color: AppColor.secondary, + iconColor: Colors.white, + onTap: () async { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const HumidityScreen())); + }, + ), + const DataDisplayerWidget( + title: 'Temperature', + subtitle: 'suhu greenhouse', + value: '28', + unit: '°C', + icon: BootstrapIcons.thermometer_half, + color: Colors.white, + ), + const DataDisplayerWidget( + title: 'Light', + subtitle: 'intensitas cahaya', + value: '1000', + unit: 'lux', + icon: BootstrapIcons.sun, + color: Colors.white, + ), + const DataDisplayerWidget( + title: 'Soil Moisture', + subtitle: 'kelembaban tanah', + value: '40', + unit: '%', + icon: Icons.water_outlined, + color: Colors.white, + ) + ], + ) + ], + ), + ), + ), + ); + } +} diff --git a/agrilink_vocpro/lib/features/home/widgets/data_display_widget.dart b/agrilink_vocpro/lib/features/home/widgets/data_display_widget.dart new file mode 100644 index 0000000..aac1391 --- /dev/null +++ b/agrilink_vocpro/lib/features/home/widgets/data_display_widget.dart @@ -0,0 +1,82 @@ +import 'package:agrilink_vocpro/core/constant/app_theme.dart'; +import 'package:flutter/material.dart'; + +class DataDisplayerWidget extends StatelessWidget { + const DataDisplayerWidget({ + super.key, + required this.title, + required this.subtitle, + required this.value, + required this.icon, + required this.color, + this.iconColor = Colors.teal, + this.textColor = Colors.black, + required this.unit, + this.onTap, + }); + + final String title; + final String subtitle; + final String value; + final IconData icon; + final Color color; + final Color iconColor; + final Color textColor; + final String unit; + final VoidCallback? onTap; + + @override + Widget build(BuildContext context) { + return ElevatedButton( + onPressed: () { + onTap != null ? onTap!() : null; + }, + style: ElevatedButton.styleFrom( + backgroundColor: color, + padding: const EdgeInsets.all(12), // Padding di dalam button + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), // Bentuk sudut yang bundar + ), + elevation: 20, // Efek bayangan + shadowColor: Colors.grey.withOpacity(0.2), + ), + child: SizedBox( + height: + MediaQuery.of(context).size.height * 0.2, // Mengatur tinggi button + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon(icon, color: iconColor, size: 32), + const SizedBox(height: 8), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: AppTheme.labelMedium.copyWith(color: textColor), + ), + Text( + subtitle, + style: AppTheme.labelSmall + .copyWith(color: textColor.withOpacity(0.5)), + ), + ], + ), + const Spacer(), + Row( + children: [ + Text( + value, + style: AppTheme.headline1.copyWith(color: textColor), + ), + const SizedBox(width: 4), + Text(unit, + style: AppTheme.titleMedium.copyWith(color: textColor)), + ], + ), + ], + ), + ), + ); + } +} diff --git a/agrilink_vocpro/lib/features/humidity/view/humidity_screen.dart b/agrilink_vocpro/lib/features/humidity/view/humidity_screen.dart new file mode 100644 index 0000000..e77e78d --- /dev/null +++ b/agrilink_vocpro/lib/features/humidity/view/humidity_screen.dart @@ -0,0 +1,44 @@ +import 'package:agrilink_vocpro/core/constant/app_color.dart'; +import 'package:agrilink_vocpro/core/constant/app_theme.dart'; +import 'package:agrilink_vocpro/features/humidity/widgets/circle_chart.dart'; +import 'package:bootstrap_icons/bootstrap_icons.dart'; +import 'package:flutter/material.dart'; + +class HumidityScreen extends StatelessWidget { + const HumidityScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Humidity', style: AppTheme.titleLarge), + centerTitle: true, + backgroundColor: Colors.white, + actions: const [ + Padding( + padding: EdgeInsets.only(right: 16), + child: Icon( + BootstrapIcons.droplet_half, + color: Colors.blue, + ), + ) + ], + ), + body: SafeArea( + child: ListView( + children: [ + SizedBox( + height: MediaQuery.of(context).size.height * 0.05, + ), + const SizedBox( + height: 320, + width: double.infinity, + child: CircleChart(humidityPercentage: 60.5), + ), + const SizedBox(height: 16), + ], + ), + ), + ); + } +} diff --git a/agrilink_vocpro/lib/features/humidity/widgets/circle_chart.dart b/agrilink_vocpro/lib/features/humidity/widgets/circle_chart.dart new file mode 100644 index 0000000..aafa5c1 --- /dev/null +++ b/agrilink_vocpro/lib/features/humidity/widgets/circle_chart.dart @@ -0,0 +1,110 @@ +import 'package:bootstrap_icons/bootstrap_icons.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; + +class CircleChart extends StatefulWidget { + const CircleChart({super.key, required this.humidityPercentage}); + + final double humidityPercentage; + + @override + State createState() => _CircleChartState(); +} + +class _CircleChartState extends State { + double currentPercentage = 0; + @override + void initState() { + super.initState(); + Future.delayed(Duration.zero, () async { + for (double i = 0; i <= widget.humidityPercentage; i++) { + await Future.delayed(const Duration(milliseconds: 25), () { + setState(() { + currentPercentage = i; + }); + }); + } + }); + } + + @override + Widget build(BuildContext context) { + return Center( + child: Stack( + alignment: Alignment.center, + children: [ + ShaderMask( + shaderCallback: (Rect bounds) { + return RadialGradient( + center: Alignment.center, + radius: 0.6, + colors: [ + Colors.white, + _getAnimatedColor(currentPercentage) + ], + tileMode: TileMode.mirror, + ).createShader(bounds); + }, + child: PieChart( + PieChartData( + sections: _createSections(currentPercentage), + centerSpaceRadius: MediaQuery.of(context).size.width * 0.25, + sectionsSpace: 0, + startDegreeOffset: 270, + borderData: FlBorderData(show: false), + ), + ), + ), + TweenAnimationBuilder( + tween: Tween(begin: 0, end: currentPercentage), + duration: const Duration(milliseconds: 300), + builder: (context, value, child) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(BootstrapIcons.droplet_fill, + size: 32, color: _getAnimatedColor(value)), + Text( + '${value.toStringAsFixed(0)}%', // Animated percentage text + style: const TextStyle( + fontSize: 28, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + ), + ], + ); + }, + ), + ], + ), + ); + } + + List _createSections(double humidityPercentage) { + return [ + PieChartSectionData( + color: Colors.white, + value: humidityPercentage, + title: '', + radius: 50, // Size of the pie slice + titleStyle: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + PieChartSectionData( + color: Colors.white24, + value: 100 - humidityPercentage, + title: '', + radius: 50, + ), + ]; + } + + Color _getAnimatedColor(double percentage) { + return Color.lerp( + Colors.green, Colors.blue, percentage / widget.humidityPercentage)!; + } +} diff --git a/agrilink_vocpro/lib/main.dart b/agrilink_vocpro/lib/main.dart index b2a0e96..7f21b6a 100644 --- a/agrilink_vocpro/lib/main.dart +++ b/agrilink_vocpro/lib/main.dart @@ -1,5 +1,9 @@ -import 'package:agrilink_vocpro/features/splash/view/splash_screen.dart'; +import 'package:agrilink_vocpro/features/dashboard/provider/dashboard_provider.dart'; +import 'package:agrilink_vocpro/features/dashboard/view/dashboard_screen.dart'; +import 'package:agrilink_vocpro/features/home/provider/home_provider.dart'; +import 'package:agrilink_vocpro/features/home/view/home_screen.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; void main() { runApp(const MyApp()); @@ -11,14 +15,21 @@ class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { - return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - scaffoldBackgroundColor: Colors.white, - colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal), - useMaterial3: true, + return MultiProvider( + providers: [ + ChangeNotifierProvider(create: (context) => HomeProvider()), + ChangeNotifierProvider(create: (context) => DashboardProvider()), + ], + child: MaterialApp( + debugShowCheckedModeBanner: false, + title: 'Flutter Demo', + theme: ThemeData( + scaffoldBackgroundColor: Colors.white, + colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal), + useMaterial3: true, + ), + home: const DashboardScreen(), ), - home: const SplashScreen(), ); } } diff --git a/agrilink_vocpro/pubspec.lock b/agrilink_vocpro/pubspec.lock index 902b6c4..72be173 100644 --- a/agrilink_vocpro/pubspec.lock +++ b/agrilink_vocpro/pubspec.lock @@ -17,6 +17,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + bootstrap_icons: + dependency: "direct main" + description: + name: bootstrap_icons + sha256: c3f19c363ceadf5493532108c6db7e3112eef62a371a1b1eb5c67968d95a1aef + url: "https://pub.dev" + source: hosted + version: "1.11.3" characters: dependency: transitive description: @@ -65,6 +73,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + equatable: + dependency: transitive + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" fake_async: dependency: transitive description: @@ -89,6 +105,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" + fl_chart: + dependency: "direct main" + description: + name: fl_chart + sha256: "94307bef3a324a0d329d3ab77b2f0c6e5ed739185ffc029ed28c0f9b019ea7ef" + url: "https://pub.dev" + source: hosted + version: "0.69.0" flutter: dependency: "direct main" description: flutter diff --git a/agrilink_vocpro/pubspec.yaml b/agrilink_vocpro/pubspec.yaml index c8c62e8..87c73e6 100644 --- a/agrilink_vocpro/pubspec.yaml +++ b/agrilink_vocpro/pubspec.yaml @@ -2,7 +2,7 @@ name: agrilink_vocpro 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 +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 @@ -31,7 +31,6 @@ 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.8 @@ -41,6 +40,8 @@ dependencies: dio: ^5.7.0 go_router: ^14.2.7 flutter_screenutil: ^5.9.3 + bootstrap_icons: ^1.11.3 + fl_chart: ^0.69.0 dev_dependencies: flutter_test: @@ -58,15 +59,15 @@ dev_dependencies: # 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: - # - images/a_dot_burr.jpeg + assets: + - assets/images/ + - assets/icons/ # - images/a_dot_ham.jpeg # An image asset can refer to one or more resolution-specific "variants", see