feat: add bottom nav bar

This commit is contained in:
Syaroful 2024-09-17 11:17:03 +07:00
parent b6987dd086
commit 0c4ab9995c
15 changed files with 575 additions and 22 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 KiB

View File

@ -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);

View File

@ -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,
);
}

View File

@ -1,5 +1,26 @@
class Extenstion {
static String capitalize(String s) {
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';
}
}

View File

@ -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<Widget> _screens = [
const HomeScreen(),
const Center(child: Text('Control')),
const Center(child: Text('Plants')),
const Center(child: Text('Settings')),
];
List<Widget> get screens => _screens;
}

View File

@ -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<DashboardProvider>(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',
),
],
),
);
});
}
}

View File

@ -0,0 +1,5 @@
import 'package:flutter/material.dart';
class HomeProvider extends ChangeNotifier {
final DateTime currentDate = DateTime.now();
}

View File

@ -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<HomeProvider>(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,
)
],
)
],
),
),
),
);
}
}

View File

@ -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)),
],
),
],
),
),
);
}
}

View File

@ -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),
],
),
),
);
}
}

View File

@ -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<CircleChart> createState() => _CircleChartState();
}
class _CircleChartState extends State<CircleChart> {
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: <Color>[
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<double>(
tween: Tween<double>(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<PieChartSectionData> _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)!;
}
}

View File

@ -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(
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 SplashScreen(),
home: const DashboardScreen(),
),
);
}
}

View File

@ -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

View File

@ -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