Skip to content

Commit 725a932

Browse files
authored
Merge pull request #3 from ikhsan3adi/fix-web
Fix web & upgrade dependencies
2 parents b3cea72 + 030b6c5 commit 725a932

File tree

12 files changed

+461
-272
lines changed

12 files changed

+461
-272
lines changed

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@ Celenganku app clone using flutter with bloc state management
99

1010
This app can help you manage your savings. This app is offline and does not need to use mobile data.
1111

12+
## Live Flutter Web Demo
1213

13-
## Prerequisites
14+
> https://ikhsan3adi.github.io/flutter-celenganku-clone/
1415
15-
- Flutter SDK >= 3.7.0
16-
- Dart SDK >= 2.19.0
16+
## Prerequisites
1717

18+
- Flutter SDK >= 3.19.x
1819

1920
## Screenshots
2021

lib/features/add_wish/view/add_wish_screen.dart

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,28 @@ class AddWishScreen extends StatelessWidget {
1919
child: Column(
2020
crossAxisAlignment: CrossAxisAlignment.start,
2121
children: [
22-
const ImagePicker(),
22+
const ImagePickerWidget(),
2323
BlocBuilder<AddWishBloc, AddWishState>(
2424
builder: (context, state) {
2525
return MyTextFormField(
2626
labelText: 'Nama Tabungan',
2727
prefixIcon: Icons.notes,
2828
onChanged: (value) {
29-
context.read<AddWishBloc>().add(WishNameChanged(wishName: value ?? ''));
29+
context
30+
.read<AddWishBloc>()
31+
.add(WishNameChanged(wishName: value ?? ''));
3032
},
3133
validator: (value) {
3234
if (value!.isEmpty || value == '') {
33-
context.read<AddWishBloc>().add(const WishNameValidation(isNameValid: false));
35+
context
36+
.read<AddWishBloc>()
37+
.add(const WishNameValidation(isNameValid: false));
3438

3539
return 'Nama tabungan harus diisi';
3640
}
37-
context.read<AddWishBloc>().add(const WishNameValidation(isNameValid: true));
41+
context
42+
.read<AddWishBloc>()
43+
.add(const WishNameValidation(isNameValid: true));
3844
return null;
3945
},
4046
isValid: state.isNameValid,
@@ -49,15 +55,21 @@ class AddWishScreen extends StatelessWidget {
4955
prefixText: 'Rp.',
5056
isCurrency: true,
5157
onChanged: (value) {
52-
context.read<AddWishBloc>().add(WishSavingTargetChanged(value: value ?? '0'));
58+
context
59+
.read<AddWishBloc>()
60+
.add(WishSavingTargetChanged(value: value ?? '0'));
5361
},
5462
validator: (value) {
5563
if (value!.isEmpty || value == '') {
56-
context.read<AddWishBloc>().add(const WishTargetValidation(isSavingTargetValid: false));
64+
context.read<AddWishBloc>().add(
65+
const WishTargetValidation(
66+
isSavingTargetValid: false));
5767

5868
return 'Target tidak boleh kosong';
5969
}
60-
context.read<AddWishBloc>().add(const WishTargetValidation(isSavingTargetValid: true));
70+
context.read<AddWishBloc>().add(
71+
const WishTargetValidation(
72+
isSavingTargetValid: true));
6173
return null;
6274
},
6375
isValid: state.isSavingTargetValid,
@@ -67,7 +79,8 @@ class AddWishScreen extends StatelessWidget {
6779
const SizedBox(height: 10),
6880
Text(
6981
"Rencana Pengisian",
70-
style: theme.textTheme.titleSmall?.copyWith(color: theme.colorScheme.primary),
82+
style: theme.textTheme.titleSmall
83+
?.copyWith(color: theme.colorScheme.primary),
7184
),
7285
const SizedBox(height: 8),
7386
Row(
@@ -77,7 +90,8 @@ class AddWishScreen extends StatelessWidget {
7790
builder: (context, state) {
7891
return SegmentedButton<SavingPlan>(
7992
onSelectionChanged: (newSelection) {
80-
context.read<AddWishBloc>().add(WishSavingPlanChanged(savingPlan: newSelection.first));
93+
context.read<AddWishBloc>().add(WishSavingPlanChanged(
94+
savingPlan: newSelection.first));
8195
},
8296
showSelectedIcon: false,
8397
segments: const [
@@ -108,19 +122,30 @@ class AddWishScreen extends StatelessWidget {
108122
prefixText: 'Rp.',
109123
isCurrency: true,
110124
onChanged: (value) {
111-
context.read<AddWishBloc>().add(WishSavingNominalChanged(value: value ?? '0'));
125+
context
126+
.read<AddWishBloc>()
127+
.add(WishSavingNominalChanged(value: value ?? '0'));
112128
},
113129
validator: (value) {
114-
if (value!.isEmpty || value == '' || int.parse((value).replaceAll('.', '')) <= 0) {
115-
context.read<AddWishBloc>().add(const WishNominalValidation(isSavingNominalValid: false));
130+
if (value!.isEmpty ||
131+
value == '' ||
132+
int.parse((value).replaceAll('.', '')) <= 0) {
133+
context.read<AddWishBloc>().add(
134+
const WishNominalValidation(
135+
isSavingNominalValid: false));
116136

117137
return 'Target per ${Wish.savingPlanTimeName(state.newWish.savingPlan).toLowerCase()} tidak boleh kosong';
118-
} else if (int.parse((value).replaceAll('.', '')) >= state.newWish.savingTarget) {
119-
context.read<AddWishBloc>().add(const WishNominalValidation(isSavingNominalValid: false));
138+
} else if (int.parse((value).replaceAll('.', '')) >=
139+
state.newWish.savingTarget) {
140+
context.read<AddWishBloc>().add(
141+
const WishNominalValidation(
142+
isSavingNominalValid: false));
120143

121144
return 'Target per ${Wish.savingPlanTimeName(state.newWish.savingPlan).toLowerCase()} harus kurang dari target tabungan';
122145
}
123-
context.read<AddWishBloc>().add(const WishNominalValidation(isSavingNominalValid: true));
146+
context.read<AddWishBloc>().add(
147+
const WishNominalValidation(
148+
isSavingNominalValid: true));
124149
return null;
125150
},
126151
isValid: state.isSavingNominalValid,

lib/features/add_wish/widget/image_picker.dart

Lines changed: 67 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,49 +3,64 @@ import 'dart:io';
33
import 'package:celenganku_app_clone/features/add_wish/add_wish.dart';
44
import 'package:flutter/material.dart';
55
import 'package:flutter_bloc/flutter_bloc.dart';
6-
import 'package:image_cropper/image_cropper.dart' as cropper;
7-
import 'package:image_picker/image_picker.dart' as picker;
6+
import 'package:image_cropper/image_cropper.dart';
7+
import 'package:image_picker/image_picker.dart';
88

9-
class ImagePicker extends StatelessWidget {
10-
const ImagePicker({super.key});
9+
class ImagePickerWidget extends StatelessWidget {
10+
const ImagePickerWidget({super.key});
11+
12+
static ImagePicker imagePicker = ImagePicker();
13+
static ImageCropper imageCropper = ImageCropper();
1114

1215
/// Get from camera
1316
Future<String?> _getFromCamera() async {
14-
picker.ImagePicker imagePicker = picker.ImagePicker();
15-
16-
picker.XFile? image = await imagePicker.pickImage(source: picker.ImageSource.camera);
17+
try {
18+
XFile? image = await imagePicker.pickImage(source: ImageSource.camera);
1719

18-
if (image != null) {
19-
return await _cropImage(filePath: image.path);
20+
return image?.path;
21+
} catch (e) {
22+
debugPrint(e.toString());
23+
return null;
2024
}
21-
return null;
2225
}
2326

2427
/// Get from gallery
2528
Future<String?> _getFromGallery() async {
26-
picker.ImagePicker imagePicker = picker.ImagePicker();
27-
28-
picker.XFile? image = await imagePicker.pickImage(source: picker.ImageSource.gallery);
29+
try {
30+
XFile? image = await imagePicker.pickImage(source: ImageSource.gallery);
2931

30-
if (image != null) {
31-
return await _cropImage(filePath: image.path);
32+
return image?.path;
33+
} catch (e) {
34+
debugPrint(e.toString());
35+
return null;
3236
}
33-
return null;
3437
}
3538

3639
/// Crop Image
37-
Future<String?> _cropImage({required filePath}) async {
38-
cropper.CroppedFile? croppedImage = await cropper.ImageCropper().cropImage(
39-
sourcePath: filePath,
40-
maxWidth: 1280,
41-
aspectRatio: const cropper.CropAspectRatio(ratioX: 16, ratioY: 9),
42-
uiSettings: [
43-
cropper.AndroidUiSettings(toolbarTitle: 'Pangkas Foto'),
44-
cropper.IOSUiSettings(title: 'Pangkas Foto'),
45-
],
46-
);
40+
Future<String?> _cropImage(BuildContext context, {required filePath}) async {
41+
try {
42+
CroppedFile? croppedImage = await imageCropper.cropImage(
43+
sourcePath: filePath,
44+
maxWidth: 1280,
45+
aspectRatio: const CropAspectRatio(ratioX: 16, ratioY: 9),
46+
uiSettings: [
47+
AndroidUiSettings(toolbarTitle: 'Pangkas Foto'),
48+
IOSUiSettings(title: 'Pangkas Foto'),
49+
WebUiSettings(
50+
context: context,
51+
viewPort: const CroppieViewPort(
52+
width: 1280,
53+
height: 720,
54+
),
55+
),
56+
],
57+
);
4758

48-
return croppedImage?.path;
59+
return croppedImage?.path;
60+
} catch (e) {
61+
debugPrint(e.toString());
62+
return filePath;
63+
}
4964
}
5065

5166
@override
@@ -71,14 +86,22 @@ class ImagePicker extends StatelessWidget {
7186
),
7287
child: SizedBox.expand(
7388
child: Material(
74-
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
75-
color: imagePath != null ? Colors.transparent : theme.colorScheme.surfaceVariant,
89+
shape: RoundedRectangleBorder(
90+
borderRadius: BorderRadius.circular(10),
91+
),
92+
color: imagePath != null
93+
? Colors.transparent
94+
: theme.colorScheme.surfaceVariant,
7695
child: InkWell(
7796
borderRadius: BorderRadius.circular(10),
7897
onTap: () async {
7998
await showModalBottomSheet<void>(
8099
context: context,
81-
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(25))),
100+
shape: const RoundedRectangleBorder(
101+
borderRadius: BorderRadius.vertical(
102+
top: Radius.circular(25),
103+
),
104+
),
82105
builder: (_) => _selectImageSourceModal(context),
83106
);
84107
},
@@ -117,12 +140,16 @@ class ImagePicker extends StatelessWidget {
117140
titleText: 'Kamera',
118141
iconData: Icons.camera_alt_outlined,
119142
onTap: () async {
120-
String? path = await _getFromCamera();
143+
String? path = await _getFromCamera().then(
144+
(path) async => await _cropImage(context, filePath: path),
145+
);
121146

122147
if (path == null) return;
123148

124149
if (context.mounted) {
125-
context.read<AddWishBloc>().add(WishImageChanged(imagePath: path));
150+
context
151+
.read<AddWishBloc>()
152+
.add(WishImageChanged(imagePath: path));
126153
Navigator.pop(context);
127154
}
128155
},
@@ -131,12 +158,16 @@ class ImagePicker extends StatelessWidget {
131158
titleText: 'Gallery',
132159
iconData: Icons.photo_library_outlined,
133160
onTap: () async {
134-
String? path = await _getFromGallery();
161+
String? path = await _getFromGallery().then(
162+
(path) async => await _cropImage(context, filePath: path),
163+
);
135164

136165
if (path == null) return;
137166

138167
if (context.mounted) {
139-
context.read<AddWishBloc>().add(WishImageChanged(imagePath: path));
168+
context
169+
.read<AddWishBloc>()
170+
.add(WishImageChanged(imagePath: path));
140171
Navigator.pop(context);
141172
}
142173
},
@@ -168,7 +199,8 @@ class _ImageSourceListTile extends StatelessWidget {
168199
foregroundColor: theme.colorScheme.onPrimary,
169200
child: Icon(iconData),
170201
),
171-
title: Text(titleText, style: const TextStyle(fontWeight: FontWeight.bold)),
202+
title:
203+
Text(titleText, style: const TextStyle(fontWeight: FontWeight.bold)),
172204
onTap: onTap,
173205
);
174206
}

0 commit comments

Comments
 (0)