| @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android' | |||
| apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" | |||
| android { | |||
| compileSdkVersion 28 | |||
| compileSdkVersion 29 | |||
| sourceSets { | |||
| main.java.srcDirs += 'src/main/kotlin' | |||
| @@ -39,7 +39,7 @@ android { | |||
| defaultConfig { | |||
| // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). | |||
| applicationId "com.example.farm_tpf" | |||
| minSdkVersion 18 | |||
| minSdkVersion 21 | |||
| targetSdkVersion 28 | |||
| versionCode flutterVersionCode.toInteger() | |||
| versionName flutterVersionName | |||
| @@ -10,7 +10,8 @@ | |||
| <application | |||
| android:name="io.flutter.app.FlutterApplication" | |||
| android:label="farm_tpf" | |||
| android:icon="@mipmap/ic_launcher"> | |||
| android:icon="@mipmap/ic_launcher" | |||
| android:requestLegacyExternalStorage="true"> | |||
| <activity | |||
| android:name=".MainActivity" | |||
| android:launchMode="singleTop" | |||
| @@ -0,0 +1 @@ | |||
| include ':app' | |||
| @@ -3,7 +3,13 @@ PODS: | |||
| - Flutter | |||
| - MTBBarcodeScanner | |||
| - SwiftProtobuf | |||
| - camera (0.0.1): | |||
| - Flutter | |||
| - Flutter (1.0.0) | |||
| - flutter_plugin_android_lifecycle (0.0.1): | |||
| - Flutter | |||
| - image_picker (0.0.1): | |||
| - Flutter | |||
| - MTBBarcodeScanner (5.0.11) | |||
| - path_provider_linux (0.0.1): | |||
| - Flutter | |||
| @@ -16,15 +22,24 @@ PODS: | |||
| - shared_preferences_web (0.0.1): | |||
| - Flutter | |||
| - SwiftProtobuf (1.11.0) | |||
| - video_player (0.0.1): | |||
| - Flutter | |||
| - video_player_web (0.0.1): | |||
| - Flutter | |||
| DEPENDENCIES: | |||
| - barcode_scan (from `.symlinks/plugins/barcode_scan/ios`) | |||
| - camera (from `.symlinks/plugins/camera/ios`) | |||
| - Flutter (from `Flutter`) | |||
| - flutter_plugin_android_lifecycle (from `.symlinks/plugins/flutter_plugin_android_lifecycle/ios`) | |||
| - image_picker (from `.symlinks/plugins/image_picker/ios`) | |||
| - path_provider_linux (from `.symlinks/plugins/path_provider_linux/ios`) | |||
| - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) | |||
| - shared_preferences_linux (from `.symlinks/plugins/shared_preferences_linux/ios`) | |||
| - shared_preferences_macos (from `.symlinks/plugins/shared_preferences_macos/ios`) | |||
| - shared_preferences_web (from `.symlinks/plugins/shared_preferences_web/ios`) | |||
| - video_player (from `.symlinks/plugins/video_player/ios`) | |||
| - video_player_web (from `.symlinks/plugins/video_player_web/ios`) | |||
| SPEC REPOS: | |||
| trunk: | |||
| @@ -34,8 +49,14 @@ SPEC REPOS: | |||
| EXTERNAL SOURCES: | |||
| barcode_scan: | |||
| :path: ".symlinks/plugins/barcode_scan/ios" | |||
| camera: | |||
| :path: ".symlinks/plugins/camera/ios" | |||
| Flutter: | |||
| :path: Flutter | |||
| flutter_plugin_android_lifecycle: | |||
| :path: ".symlinks/plugins/flutter_plugin_android_lifecycle/ios" | |||
| image_picker: | |||
| :path: ".symlinks/plugins/image_picker/ios" | |||
| path_provider_linux: | |||
| :path: ".symlinks/plugins/path_provider_linux/ios" | |||
| shared_preferences: | |||
| @@ -46,10 +67,17 @@ EXTERNAL SOURCES: | |||
| :path: ".symlinks/plugins/shared_preferences_macos/ios" | |||
| shared_preferences_web: | |||
| :path: ".symlinks/plugins/shared_preferences_web/ios" | |||
| video_player: | |||
| :path: ".symlinks/plugins/video_player/ios" | |||
| video_player_web: | |||
| :path: ".symlinks/plugins/video_player_web/ios" | |||
| SPEC CHECKSUMS: | |||
| barcode_scan: a5c27959edfafaa0c771905bad0b29d6d39e4479 | |||
| camera: a0ca5080336f7af47b88436e5e26da3dee5568f0 | |||
| Flutter: 0e3d915762c693b495b44d77113d4970485de6ec | |||
| flutter_plugin_android_lifecycle: dc0b544e129eebb77a6bfb1239d4d1c673a60a35 | |||
| image_picker: 9c3312491f862b28d21ecd8fdf0ee14e601b3f09 | |||
| MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb | |||
| path_provider_linux: 4d630dc393e1f20364f3e3b4a2ff41d9674a84e4 | |||
| shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d | |||
| @@ -57,6 +85,8 @@ SPEC CHECKSUMS: | |||
| shared_preferences_macos: f3f29b71ccbb56bf40c9dd6396c9acf15e214087 | |||
| shared_preferences_web: 141cce0c3ed1a1c5bf2a0e44f52d31eeb66e5ea9 | |||
| SwiftProtobuf: f889fe5772f90ef7d7b8aac352d1fddf39650713 | |||
| video_player: 9cc823b1d9da7e8427ee591e8438bfbcde500e6e | |||
| video_player_web: da8cadb8274ed4f8dbee8d7171b420dedd437ce7 | |||
| PODFILE CHECKSUM: c34e2287a9ccaa606aeceab922830efb9a6ff69a | |||
| @@ -3,7 +3,7 @@ | |||
| archiveVersion = 1; | |||
| classes = { | |||
| }; | |||
| objectVersion = 50; | |||
| objectVersion = 51; | |||
| objects = { | |||
| /* Begin PBXBuildFile section */ | |||
| @@ -363,7 +363,7 @@ | |||
| buildSettings = { | |||
| ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; | |||
| CLANG_ENABLE_MODULES = YES; | |||
| CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; | |||
| CURRENT_PROJECT_VERSION = 1; | |||
| DEVELOPMENT_TEAM = C3DTD2JH94; | |||
| ENABLE_BITCODE = NO; | |||
| FRAMEWORK_SEARCH_PATHS = ( | |||
| @@ -502,7 +502,7 @@ | |||
| buildSettings = { | |||
| ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; | |||
| CLANG_ENABLE_MODULES = YES; | |||
| CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; | |||
| CURRENT_PROJECT_VERSION = 1; | |||
| DEVELOPMENT_TEAM = C3DTD2JH94; | |||
| ENABLE_BITCODE = NO; | |||
| FRAMEWORK_SEARCH_PATHS = ( | |||
| @@ -533,7 +533,7 @@ | |||
| buildSettings = { | |||
| ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; | |||
| CLANG_ENABLE_MODULES = YES; | |||
| CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; | |||
| CURRENT_PROJECT_VERSION = 1; | |||
| DEVELOPMENT_TEAM = C3DTD2JH94; | |||
| ENABLE_BITCODE = NO; | |||
| FRAMEWORK_SEARCH_PATHS = ( | |||
| @@ -19,9 +19,15 @@ | |||
| <key>CFBundleSignature</key> | |||
| <string>????</string> | |||
| <key>CFBundleVersion</key> | |||
| <string>$(FLUTTER_BUILD_NUMBER)</string> | |||
| <string>$(CURRENT_PROJECT_VERSION)</string> | |||
| <key>LSRequiresIPhoneOS</key> | |||
| <true/> | |||
| <key>NSCameraUsageDescription</key> | |||
| <string>Camera permission is required for barcode scanning.</string> | |||
| <key>NSMicrophoneUsageDescription</key> | |||
| <string>Use for record videos</string> | |||
| <key>NSPhotoLibraryUsageDescription</key> | |||
| <string>Get image for upload to server</string> | |||
| <key>UILaunchStoryboardName</key> | |||
| <string>LaunchScreen</string> | |||
| <key>UIMainStoryboardFile</key> | |||
| @@ -41,8 +47,5 @@ | |||
| </array> | |||
| <key>UIViewControllerBasedStatusBarAppearance</key> | |||
| <false/> | |||
| <!-- Camera Permission --> | |||
| <key>NSCameraUsageDescription</key> | |||
| <string>Camera permission is required for barcode scanning.</string> | |||
| </dict> | |||
| </plist> | |||
| @@ -0,0 +1,52 @@ | |||
| import 'package:flutter/material.dart'; | |||
| class RoundedRectIndicator extends Decoration { | |||
| final BoxPainter _painter; | |||
| RoundedRectIndicator({ | |||
| @required Color color, | |||
| @required double radius, | |||
| double padding = 0.0, | |||
| double weight = 3.0, | |||
| }) : _painter = _RectPainter(color, radius, padding, weight); | |||
| @override | |||
| BoxPainter createBoxPainter([onChanged]) { | |||
| return _painter; | |||
| } | |||
| } | |||
| class _RectPainter extends BoxPainter { | |||
| final Paint _paint; | |||
| final double radius; | |||
| final double padding; | |||
| final double weight; | |||
| final indicatorPaddingBottom = 4.0; | |||
| _RectPainter(Color color, this.radius, this.padding, this.weight) | |||
| : _paint = Paint() | |||
| ..color = color | |||
| ..isAntiAlias = true; | |||
| @override | |||
| void paint(Canvas canvas, Offset offset, ImageConfiguration cfg) { | |||
| var width = cfg.size.width; | |||
| var height = cfg.size.height; | |||
| var left = 0.0; | |||
| var top = height - indicatorPaddingBottom; | |||
| var right = width; | |||
| var bottom = height - weight - indicatorPaddingBottom; | |||
| //calculate offset | |||
| left = left + offset.dx + padding; | |||
| right = right + offset.dx - padding; | |||
| var rect = RRect.fromLTRBAndCorners(left, top, right, bottom, | |||
| topLeft: Radius.circular(radius), | |||
| bottomLeft: Radius.circular(radius), | |||
| bottomRight: Radius.circular(radius), | |||
| topRight: Radius.circular(radius)); | |||
| canvas.drawRRect(rect, _paint); | |||
| } | |||
| } | |||
| @@ -0,0 +1,360 @@ | |||
| import 'dart:io'; | |||
| import 'package:flutter/material.dart'; | |||
| import 'package:image_picker/image_picker.dart'; | |||
| import 'package:video_player/video_player.dart'; | |||
| class ActionNurseryScreen extends StatefulWidget { | |||
| @override | |||
| _ActionNurseryScreenState createState() => _ActionNurseryScreenState(); | |||
| } | |||
| class _ActionNurseryScreenState extends State<ActionNurseryScreen> { | |||
| PickedFile _imageFile; | |||
| dynamic _pickImageError; | |||
| bool isVideo = false; | |||
| VideoPlayerController _controller; | |||
| VideoPlayerController _toBeDisposed; | |||
| String _retrieveDataError; | |||
| final ImagePicker _picker = ImagePicker(); | |||
| final TextEditingController maxWidthController = TextEditingController(); | |||
| final TextEditingController maxHeightController = TextEditingController(); | |||
| final TextEditingController qualityController = TextEditingController(); | |||
| Future<void> _playVideo(PickedFile file) async { | |||
| if (file != null && mounted) { | |||
| await _disposeVideoController(); | |||
| _controller = VideoPlayerController.file(File(file.path)); | |||
| await _controller.setVolume(1.0); | |||
| await _controller.initialize(); | |||
| await _controller.setLooping(true); | |||
| await _controller.play(); | |||
| setState(() {}); | |||
| } | |||
| } | |||
| void _onImageButtonPressed(ImageSource source, {BuildContext context}) async { | |||
| if (_controller != null) { | |||
| await _controller.setVolume(0.0); | |||
| } | |||
| if (isVideo) { | |||
| final PickedFile file = await _picker.getVideo( | |||
| source: source, maxDuration: const Duration(seconds: 10)); | |||
| await _playVideo(file); | |||
| } else { | |||
| await _displayPickImageDialog(context, | |||
| (double maxWidth, double maxHeight, int quality) async { | |||
| try { | |||
| final pickedFile = await _picker.getImage( | |||
| source: source, | |||
| maxWidth: maxWidth, | |||
| maxHeight: maxHeight, | |||
| imageQuality: quality, | |||
| ); | |||
| setState(() { | |||
| _imageFile = pickedFile; | |||
| }); | |||
| } catch (e) { | |||
| setState(() { | |||
| _pickImageError = e; | |||
| }); | |||
| } | |||
| }); | |||
| } | |||
| } | |||
| @override | |||
| void deactivate() { | |||
| if (_controller != null) { | |||
| _controller.setVolume(0.0); | |||
| _controller.pause(); | |||
| } | |||
| super.deactivate(); | |||
| } | |||
| @override | |||
| void dispose() { | |||
| _disposeVideoController(); | |||
| maxWidthController.dispose(); | |||
| maxHeightController.dispose(); | |||
| qualityController.dispose(); | |||
| super.dispose(); | |||
| } | |||
| Future<void> _disposeVideoController() async { | |||
| if (_toBeDisposed != null) { | |||
| await _toBeDisposed.dispose(); | |||
| } | |||
| _toBeDisposed = _controller; | |||
| _controller = null; | |||
| } | |||
| Widget _previewVideo() { | |||
| final Text retrieveError = _getRetrieveErrorWidget(); | |||
| if (retrieveError != null) { | |||
| return retrieveError; | |||
| } | |||
| if (_controller == null) { | |||
| return const Text( | |||
| 'You have not yet picked a video', | |||
| textAlign: TextAlign.center, | |||
| ); | |||
| } | |||
| return Padding( | |||
| padding: const EdgeInsets.all(10.0), | |||
| child: AspectRatioVideo(_controller), | |||
| ); | |||
| } | |||
| Widget _previewImage() { | |||
| final Text retrieveError = _getRetrieveErrorWidget(); | |||
| if (retrieveError != null) { | |||
| return retrieveError; | |||
| } | |||
| if (_imageFile != null) { | |||
| return Image.file(File(_imageFile.path)); | |||
| } else if (_pickImageError != null) { | |||
| return Text( | |||
| 'Pick image error: $_pickImageError', | |||
| textAlign: TextAlign.center, | |||
| ); | |||
| } else { | |||
| return const Text( | |||
| 'You have not yet picked an image.', | |||
| textAlign: TextAlign.center, | |||
| ); | |||
| } | |||
| } | |||
| Future<void> retrieveLostData() async { | |||
| final LostData response = await _picker.getLostData(); | |||
| if (response.isEmpty) { | |||
| return; | |||
| } | |||
| if (response.file != null) { | |||
| if (response.type == RetrieveType.video) { | |||
| isVideo = true; | |||
| await _playVideo(response.file); | |||
| } else { | |||
| isVideo = false; | |||
| setState(() { | |||
| _imageFile = response.file; | |||
| }); | |||
| } | |||
| } else { | |||
| _retrieveDataError = response.exception.code; | |||
| } | |||
| } | |||
| @override | |||
| Widget build(BuildContext context) { | |||
| return Scaffold( | |||
| appBar: AppBar( | |||
| title: Text("Test"), | |||
| ), | |||
| body: Center( | |||
| //TODO : check default platform is android | |||
| child: 1 == 1 | |||
| ? FutureBuilder<void>( | |||
| future: retrieveLostData(), | |||
| builder: (BuildContext context, AsyncSnapshot<void> snapshot) { | |||
| switch (snapshot.connectionState) { | |||
| case ConnectionState.none: | |||
| case ConnectionState.waiting: | |||
| return const Text( | |||
| 'You have not yet picked an image.', | |||
| textAlign: TextAlign.center, | |||
| ); | |||
| case ConnectionState.done: | |||
| return isVideo ? _previewVideo() : _previewImage(); | |||
| default: | |||
| if (snapshot.hasError) { | |||
| return Text( | |||
| 'Pick image/video error: ${snapshot.error}}', | |||
| textAlign: TextAlign.center, | |||
| ); | |||
| } else { | |||
| return const Text( | |||
| 'You have not yet picked an image.', | |||
| textAlign: TextAlign.center, | |||
| ); | |||
| } | |||
| } | |||
| }, | |||
| ) | |||
| : (isVideo ? _previewVideo() : _previewImage()), | |||
| ), | |||
| floatingActionButton: Column( | |||
| mainAxisAlignment: MainAxisAlignment.end, | |||
| children: <Widget>[ | |||
| FloatingActionButton( | |||
| onPressed: () { | |||
| isVideo = false; | |||
| _onImageButtonPressed(ImageSource.gallery, context: context); | |||
| }, | |||
| heroTag: 'image0', | |||
| tooltip: 'Pick Image from gallery', | |||
| child: const Icon(Icons.photo_library), | |||
| ), | |||
| Padding( | |||
| padding: const EdgeInsets.only(top: 16.0), | |||
| child: FloatingActionButton( | |||
| onPressed: () { | |||
| isVideo = false; | |||
| _onImageButtonPressed(ImageSource.camera, context: context); | |||
| }, | |||
| heroTag: 'image1', | |||
| tooltip: 'Take a Photo', | |||
| child: const Icon(Icons.camera_alt), | |||
| ), | |||
| ), | |||
| Padding( | |||
| padding: const EdgeInsets.only(top: 16.0), | |||
| child: FloatingActionButton( | |||
| backgroundColor: Colors.red, | |||
| onPressed: () { | |||
| isVideo = true; | |||
| _onImageButtonPressed(ImageSource.gallery); | |||
| }, | |||
| heroTag: 'video0', | |||
| tooltip: 'Pick Video from gallery', | |||
| child: const Icon(Icons.video_library), | |||
| ), | |||
| ), | |||
| Padding( | |||
| padding: const EdgeInsets.only(top: 16.0), | |||
| child: FloatingActionButton( | |||
| backgroundColor: Colors.red, | |||
| onPressed: () { | |||
| isVideo = true; | |||
| _onImageButtonPressed(ImageSource.camera); | |||
| }, | |||
| heroTag: 'video1', | |||
| tooltip: 'Take a Video', | |||
| child: const Icon(Icons.videocam), | |||
| ), | |||
| ), | |||
| ], | |||
| ), | |||
| ); | |||
| } | |||
| Text _getRetrieveErrorWidget() { | |||
| if (_retrieveDataError != null) { | |||
| final Text result = Text(_retrieveDataError); | |||
| _retrieveDataError = null; | |||
| return result; | |||
| } | |||
| return null; | |||
| } | |||
| Future<void> _displayPickImageDialog( | |||
| BuildContext context, OnPickImageCallback onPick) async { | |||
| return showDialog( | |||
| context: context, | |||
| builder: (context) { | |||
| return AlertDialog( | |||
| title: Text('Add optional parameters'), | |||
| content: Column( | |||
| children: <Widget>[ | |||
| TextField( | |||
| controller: maxWidthController, | |||
| keyboardType: TextInputType.numberWithOptions(decimal: true), | |||
| decoration: | |||
| InputDecoration(hintText: "Enter maxWidth if desired"), | |||
| ), | |||
| TextField( | |||
| controller: maxHeightController, | |||
| keyboardType: TextInputType.numberWithOptions(decimal: true), | |||
| decoration: | |||
| InputDecoration(hintText: "Enter maxHeight if desired"), | |||
| ), | |||
| TextField( | |||
| controller: qualityController, | |||
| keyboardType: TextInputType.number, | |||
| decoration: | |||
| InputDecoration(hintText: "Enter quality if desired"), | |||
| ), | |||
| ], | |||
| ), | |||
| actions: <Widget>[ | |||
| FlatButton( | |||
| child: const Text('CANCEL'), | |||
| onPressed: () { | |||
| Navigator.of(context).pop(); | |||
| }, | |||
| ), | |||
| FlatButton( | |||
| child: const Text('PICK'), | |||
| onPressed: () { | |||
| double width = maxWidthController.text.isNotEmpty | |||
| ? double.parse(maxWidthController.text) | |||
| : null; | |||
| double height = maxHeightController.text.isNotEmpty | |||
| ? double.parse(maxHeightController.text) | |||
| : null; | |||
| int quality = qualityController.text.isNotEmpty | |||
| ? int.parse(qualityController.text) | |||
| : null; | |||
| onPick(width, height, quality); | |||
| Navigator.of(context).pop(); | |||
| }), | |||
| ], | |||
| ); | |||
| }); | |||
| } | |||
| } | |||
| typedef void OnPickImageCallback( | |||
| double maxWidth, double maxHeight, int quality); | |||
| class AspectRatioVideo extends StatefulWidget { | |||
| AspectRatioVideo(this.controller); | |||
| final VideoPlayerController controller; | |||
| @override | |||
| AspectRatioVideoState createState() => AspectRatioVideoState(); | |||
| } | |||
| class AspectRatioVideoState extends State<AspectRatioVideo> { | |||
| VideoPlayerController get controller => widget.controller; | |||
| bool initialized = false; | |||
| void _onVideoControllerUpdate() { | |||
| if (!mounted) { | |||
| return; | |||
| } | |||
| if (initialized != controller.value.initialized) { | |||
| initialized = controller.value.initialized; | |||
| setState(() {}); | |||
| } | |||
| } | |||
| @override | |||
| void initState() { | |||
| super.initState(); | |||
| controller.addListener(_onVideoControllerUpdate); | |||
| } | |||
| @override | |||
| void dispose() { | |||
| controller.removeListener(_onVideoControllerUpdate); | |||
| super.dispose(); | |||
| } | |||
| @override | |||
| Widget build(BuildContext context) { | |||
| if (initialized) { | |||
| return Center( | |||
| child: AspectRatio( | |||
| aspectRatio: controller.value?.aspectRatio, | |||
| child: VideoPlayer(controller), | |||
| ), | |||
| ); | |||
| } else { | |||
| return Container(); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,23 @@ | |||
| import 'package:farm_tpf/app.dart'; | |||
| import 'package:flutter/material.dart'; | |||
| class ActionPlantScreen extends StatefulWidget { | |||
| @override | |||
| _ActionPlantScreenState createState() => _ActionPlantScreenState(); | |||
| } | |||
| class _ActionPlantScreenState extends State<ActionPlantScreen> { | |||
| @override | |||
| void initState() { | |||
| super.initState(); | |||
| } | |||
| @override | |||
| Widget build(BuildContext context) { | |||
| return Scaffold( | |||
| appBar: AppBar( | |||
| title: Text("Plant"), | |||
| ), | |||
| ); | |||
| } | |||
| } | |||
| @@ -1,6 +1,7 @@ | |||
| import 'package:farm_tpf/authentication/bloc/authentication_bloc.dart'; | |||
| import 'package:farm_tpf/presentation/screens/actions/nursery/sc_nursery.dart'; | |||
| import 'package:farm_tpf/presentation/screens/actions/plant/sc_plant.dart'; | |||
| import 'package:farm_tpf/presentation/screens/plot_detail/sc_plot_detail.dart'; | |||
| import 'package:flutter/material.dart'; | |||
| import 'package:flutter_bloc/flutter_bloc.dart'; | |||
| class HomePage extends StatelessWidget { | |||
| static Route route() { | |||
| @@ -16,6 +17,24 @@ class HomePage extends StatelessWidget { | |||
| mainAxisSize: MainAxisSize.min, | |||
| children: <Widget>[ | |||
| Text("logged in."), | |||
| MaterialButton( | |||
| child: Text("Nursery action"), | |||
| onPressed: () { | |||
| Navigator.of(context).push( | |||
| MaterialPageRoute(builder: (_) => ActionNurseryScreen())); | |||
| }), | |||
| MaterialButton( | |||
| child: Text("Plant action"), | |||
| onPressed: () { | |||
| Navigator.of(context).push( | |||
| MaterialPageRoute(builder: (_) => ActionPlantScreen())); | |||
| }), | |||
| MaterialButton( | |||
| child: Text("Chi tiết lô"), | |||
| onPressed: () { | |||
| Navigator.of(context).push( | |||
| MaterialPageRoute(builder: (_) => PlotDetailScreen())); | |||
| }), | |||
| ], | |||
| ), | |||
| ), | |||
| @@ -0,0 +1,112 @@ | |||
| import 'package:farm_tpf/utils/const_color.dart'; | |||
| import 'package:farm_tpf/utils/const_string.dart'; | |||
| import 'package:flutter/material.dart'; | |||
| class PlotActionScreen extends StatefulWidget { | |||
| @override | |||
| _PlotActionScreenState createState() => _PlotActionScreenState(); | |||
| } | |||
| class _PlotActionScreenState extends State<PlotActionScreen> { | |||
| List<ActionType> actions = new List<ActionType>(); | |||
| @override | |||
| void initState() { | |||
| super.initState(); | |||
| actions.add(ActionType(plot_action_nursery, null, null)); | |||
| actions.add(ActionType(plot_action_plant, null, null)); | |||
| actions.add(ActionType(plot_action_crop_status, null, null)); | |||
| actions.add(ActionType(plot_action_environment_update, null, null)); | |||
| actions.add(ActionType(plot_action_dung, null, null)); | |||
| actions.add(ActionType(plot_action_spraying, null, null)); | |||
| actions.add(ActionType(plot_action_disease, null, null)); | |||
| actions.add(ActionType(plot_action_use_water, null, null)); | |||
| actions.add(ActionType(plot_action_other, null, null)); | |||
| actions.add(ActionType(plot_action_harvest, null, null)); | |||
| actions.add(ActionType(plot_action_finish, null, null)); | |||
| } | |||
| Widget _createShrimpUpdate(ActionType actionType) { | |||
| return GestureDetector( | |||
| onTap: () { | |||
| Navigator.of(context).push( | |||
| MaterialPageRoute(builder: (context) => actionType.listScreen)); | |||
| }, | |||
| child: Container( | |||
| height: 75, | |||
| margin: EdgeInsets.all(4.0), | |||
| padding: EdgeInsets.all(0.0), | |||
| decoration: BoxDecoration( | |||
| color: COLOR_CONST.WHITE, | |||
| borderRadius: BorderRadius.only( | |||
| topLeft: Radius.circular(8.0), | |||
| bottomLeft: Radius.circular(8.0), | |||
| bottomRight: Radius.circular(8.0), | |||
| topRight: Radius.circular(8.0)), | |||
| boxShadow: <BoxShadow>[ | |||
| BoxShadow( | |||
| color: COLOR_CONST.GRAY1.withOpacity(0.2), | |||
| offset: Offset(1.1, 1.1), | |||
| blurRadius: 10.0), | |||
| ], | |||
| ), | |||
| child: Stack( | |||
| children: <Widget>[ | |||
| Positioned( | |||
| top: -10, | |||
| right: -3, | |||
| child: (actionType.addScreen == null) | |||
| ? Container() | |||
| : IconButton( | |||
| icon: Icon( | |||
| Icons.add_circle, | |||
| size: 40, | |||
| color: Colors.green, | |||
| ), | |||
| onPressed: () { | |||
| Navigator.of(context).push(MaterialPageRoute( | |||
| builder: (context) => actionType.addScreen)); | |||
| })), | |||
| Positioned.fill( | |||
| child: Align( | |||
| alignment: Alignment.center, | |||
| child: Text( | |||
| actionType.actionName, | |||
| textAlign: TextAlign.center, | |||
| style: TextStyle( | |||
| fontWeight: FontWeight.w400, | |||
| fontSize: 16, | |||
| color: COLOR_CONST.BLACK2, | |||
| ), | |||
| )), | |||
| ), | |||
| ], | |||
| ), | |||
| )); | |||
| } | |||
| @override | |||
| Widget build(BuildContext context) { | |||
| return Scaffold( | |||
| body: GridView.count( | |||
| crossAxisCount: 2, | |||
| childAspectRatio: 16 / 6, | |||
| children: actions.map( | |||
| (item) { | |||
| return _createShrimpUpdate(item); | |||
| }, | |||
| ).toList()), | |||
| ); | |||
| } | |||
| } | |||
| class ActionType { | |||
| Widget addScreen; | |||
| Widget listScreen; | |||
| String actionName; | |||
| ActionType(String actionName, Widget addScreen, Widget listScreen) { | |||
| this.actionName = actionName; | |||
| this.addScreen = addScreen; | |||
| this.listScreen = listScreen; | |||
| } | |||
| } | |||
| @@ -0,0 +1,54 @@ | |||
| import 'package:farm_tpf/presentation/custom_widgets/widget_rounded_rect_indicator.dart'; | |||
| import 'package:farm_tpf/presentation/screens/plot_detail/sc_plot_action.dart'; | |||
| import 'package:farm_tpf/presentation/screens/plot_detail/sc_plot_information.dart'; | |||
| import 'package:farm_tpf/utils/const_color.dart'; | |||
| import 'package:flutter/material.dart'; | |||
| class PlotDetailScreen extends StatefulWidget { | |||
| @override | |||
| _PlotDetailScreenState createState() => _PlotDetailScreenState(); | |||
| } | |||
| class _PlotDetailScreenState extends State<PlotDetailScreen> { | |||
| @override | |||
| Widget build(BuildContext context) { | |||
| return Container( | |||
| color: COLOR_CONST.ITEM_BG, | |||
| child: SafeArea( | |||
| top: false, | |||
| bottom: true, | |||
| child: Scaffold( | |||
| appBar: AppBar( | |||
| centerTitle: true, | |||
| title: Text("Chi tiết lô"), | |||
| ), | |||
| body: DefaultTabController( | |||
| length: 2, | |||
| child: new Scaffold( | |||
| backgroundColor: COLOR_CONST.ITEM_BG, | |||
| body: TabBarView( | |||
| children: [PlotInformationScreen(), PlotActionScreen()], | |||
| ), | |||
| bottomNavigationBar: new TabBar( | |||
| tabs: [ | |||
| Tab( | |||
| text: "Thông tin", | |||
| ), | |||
| Tab( | |||
| text: "Canh tác", | |||
| ), | |||
| ], | |||
| labelColor: COLOR_CONST.DEFAULT, | |||
| unselectedLabelColor: COLOR_CONST.GRAY1_70, | |||
| indicatorSize: TabBarIndicatorSize.label, | |||
| indicator: RoundedRectIndicator( | |||
| color: COLOR_CONST.DEFAULT, | |||
| radius: 2, | |||
| padding: 22, | |||
| weight: 3.0), | |||
| ), | |||
| ), | |||
| ), | |||
| ))); | |||
| } | |||
| } | |||
| @@ -0,0 +1,15 @@ | |||
| import 'package:flutter/material.dart'; | |||
| class PlotInformationScreen extends StatefulWidget { | |||
| @override | |||
| _PlotInformationScreenState createState() => _PlotInformationScreenState(); | |||
| } | |||
| class _PlotInformationScreenState extends State<PlotInformationScreen> { | |||
| @override | |||
| Widget build(BuildContext context) { | |||
| return Container( | |||
| child: Text("Thông tin lô"), | |||
| ); | |||
| } | |||
| } | |||
| @@ -1 +1,12 @@ | |||
| const String app_name = "app name"; | |||
| const String plot_action_nursery = "Ươm"; | |||
| const String plot_action_plant = "Trồng"; | |||
| const String plot_action_crop_status = "Thực trạng cây trồng"; | |||
| const String plot_action_environment_update = "Cập nhật môi trường"; | |||
| const String plot_action_dung = "Bón phân"; | |||
| const String plot_action_spraying = "Phun thuốc BVTV"; | |||
| const String plot_action_disease = "Điều tra dịch hại"; | |||
| const String plot_action_use_water = "Sử dụng nước"; | |||
| const String plot_action_other = "Hoạt động khác"; | |||
| const String plot_action_harvest = "Thu hoạch"; | |||
| const String plot_action_finish = "Kết thúc canh tác"; | |||
| @@ -223,6 +223,13 @@ packages: | |||
| url: "https://pub.dartlang.org" | |||
| source: hosted | |||
| version: "6.0.1" | |||
| flutter_plugin_android_lifecycle: | |||
| dependency: "direct main" | |||
| description: | |||
| name: flutter_plugin_android_lifecycle | |||
| url: "https://pub.dartlang.org" | |||
| source: hosted | |||
| version: "1.0.8" | |||
| flutter_test: | |||
| dependency: "direct dev" | |||
| description: flutter | |||
| @@ -268,6 +275,13 @@ packages: | |||
| url: "https://pub.dartlang.org" | |||
| source: hosted | |||
| version: "0.14.0+3" | |||
| http: | |||
| dependency: transitive | |||
| description: | |||
| name: http | |||
| url: "https://pub.dartlang.org" | |||
| source: hosted | |||
| version: "0.12.2" | |||
| http_multi_server: | |||
| dependency: transitive | |||
| description: | |||
| @@ -289,6 +303,20 @@ packages: | |||
| url: "https://pub.dartlang.org" | |||
| source: hosted | |||
| version: "2.1.12" | |||
| image_picker: | |||
| dependency: "direct main" | |||
| description: | |||
| name: image_picker | |||
| url: "https://pub.dartlang.org" | |||
| source: hosted | |||
| version: "0.6.7+4" | |||
| image_picker_platform_interface: | |||
| dependency: transitive | |||
| description: | |||
| name: image_picker_platform_interface | |||
| url: "https://pub.dartlang.org" | |||
| source: hosted | |||
| version: "1.1.0" | |||
| intl: | |||
| dependency: transitive | |||
| description: | |||
| @@ -651,6 +679,27 @@ packages: | |||
| url: "https://pub.dartlang.org" | |||
| source: hosted | |||
| version: "2.0.8" | |||
| video_player: | |||
| dependency: "direct main" | |||
| description: | |||
| name: video_player | |||
| url: "https://pub.dartlang.org" | |||
| source: hosted | |||
| version: "0.10.11+2" | |||
| video_player_platform_interface: | |||
| dependency: transitive | |||
| description: | |||
| name: video_player_platform_interface | |||
| url: "https://pub.dartlang.org" | |||
| source: hosted | |||
| version: "2.1.0" | |||
| video_player_web: | |||
| dependency: transitive | |||
| description: | |||
| name: video_player_web | |||
| url: "https://pub.dartlang.org" | |||
| source: hosted | |||
| version: "0.1.3+2" | |||
| watcher: | |||
| dependency: transitive | |||
| description: | |||
| @@ -7,6 +7,9 @@ version: 1.0.0+1 | |||
| environment: | |||
| sdk: ">=2.7.0 <3.0.0" | |||
| module: | |||
| androidX: true | |||
| dependencies: | |||
| flutter: | |||
| sdk: flutter | |||
| @@ -22,6 +25,9 @@ dependencies: | |||
| pattern_formatter: ^1.0.2 | |||
| rxdart: ^0.23.0 | |||
| barcode_scan: ^3.0.1 | |||
| image_picker: ^0.6.2+3 | |||
| video_player: ^0.10.11+2 | |||
| flutter_plugin_android_lifecycle: ^1.0.4 | |||
| dev_dependencies: | |||
| flutter_test: | |||