Hình 3.12 User Manager
Ở trang UserManager, admin có thể xem thông tin của tất cả user đã đăng ký (tên, username, email và quyền truy cập của user đó).
Admin còn có thể điều hướng đến trang chỉnh sửa thông tin để chỉnh sửa thông tin hoặc quyền hạn của một người dùng.
Ngồi ra, khi admin click vào xóa mợt user, tài khoản đó sẽ không bị xóa nhưng quyền tryu cập sẽ được đưa về không đồng nghĩa với tài khoản đó bị vô hiệu.
Hình 3.13 User manager – Edit
Hình 3.14 User Image
Trang user Image, hiển thị tất cả các hình ảnh mà người dùng đã đăng tải trong các bài viết được chia sẻ, ngoài ra nếu dữ liệu quá nhiều admin có thể lọc được user nào đăng tải các ảnh nào bằng cách nhấp vào id mong muốn lọc.
Hình 3.15 User Image – Filter
Hình 3.16 Social Post Management
Social Post management sẽ hiển thị thông tin của các hình ảnh được tài khoản nào chia sẻ với nội dung như thế nào, số lươt yêu thích và thời gian đăng tải.
Ta có thể chọn lọc các bài viết dựa theo ID tài khoản đăng tải.
Ngoài ra với, ở trang social post management ta có thể chuyển đến liên kết của từng bài viết cụ thể và đọc chi tiết chúng cùng với các comment, được các user để lại
Hình 3.18 Read Post
Hình 3.19 Read Post – comment
Hình 3.20 Add Post
Hình 3.21 Friend List
Hình 3.22 Friend List – Filter
Ở trang friend list, cho ta thấy ai làm bạn (hoặc theo dõi) ai từ lúc nào và thông tin của người theo doi hoặc được theo dõi (ID, name) và ta có thể lọc lại danh sách theo id (hoặc tên) của người theo dõi (hoặc được theo dõi)
Hình 3.24 Comment
Hình 3.25 Comment – filter
Ở trang comment, ta có thể thấy được người dùng nào, đã bình luận ở bài viết nào với bình luận như thế nào, trong tương lại sẽ có bình luận đấy được bày tỏ cảm xúc như thế nào,..
Ngoài ra, ta có thể lọc theo tên tài khoản (hoặc id) người dùng này bình luận bao nhiêu lần hay Id của bài viết – bài viết này có bao nhiêu bình luận.
Chương 4 Xây dựng ứng dụng android
4.1 Thiết kế:
4.1.1 Logo ứng dụng:
Hình 4.26 Logo
4.1.2 Cấu trúc project:
Để dễ quản lý, ta sẽ tổ chức project thành các thành phần riêng biệt:
Model: (Gồm có 7 model), chứa các model dùng để xử lí các chuỗi json được trả về thông qua api
Screen: (Gồm 10 màn hình) Chứa các màn hình riêng biệt, mỗi file chứa cả code thực thi và code giao diện (dart) xen kẽ với nhau
o camera.dart: Xử lí các tác vụ cho camera và các bộ lọc AR với thư viện camera_deep_ar.
o image_enhance.dart: Chọn hình ảnh để có thể bắt đầu chỉnh sửa edit_screen.dart: Các chức năng chỉnh sửa hình ảnh
save_image.dart: Tác vụ sau khi hoàn thành chỉnh sửa ảnh (save hoặc share).
o home.dart: Show ra danh sách các bài/ hình ảnh đã được chia sẻ theo thứ tự từ mới đến cũ
post_read.dart: Đọc chi tiết của một bài viết, xem các bình luận upload_post.dart: Đăng tải/chia sẻ một hình ảnh như một bài viết
o library.dart: Các thông tin của người dùng, số lượng người theo dõi/ được theo dõi và các hình ảnh user đã đăng tải.
o login.dart: Trang đăng nhập dành cho user
SharedPreferences: (Chưa 1 file preferences) Chứa file của thư viện shared preferences, dùng để lưu trữ các biến cục bộ trên ứng dụng.
o UserPreferences.dart: Chứa các references giúp lưu trữ các biến cục bộ và có thể gọi ở mọi nơi trong projet bằng cách import và gọi thực thi init(). File main.dart: File khởi đầu để khởi chạy project/ứng dụng.
4.1.3 Yêu cầu ứng dụng:
- Có thể hoạt động offline - Có thể chỉnh sửa hình ảnh - User có thể đăng bài - User có thể xem thông tin
- Nếu chưa đăng nhập user vẫn có thể sử dụng các chức năng chụp ảnh, quay video với AR camera hay chỉnh sửa ảnh và lưu vào máy và xem các bài viết đã được chia sẻ nhưng không thể tương tác, và không thể coi library.
4.2 Sử dụng thư viện camera_deep_ar:
Để sử dụng thư viện, ta cần phải có các quyền truy cập vào hệ thống như camera, thư viện ảnh, chỉnh sửa, xóa,…
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.FLASHLIGHT" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-feature android:name="android.hardware.camera.front" /> <uses-feature android:name="android.hardware.microphone" /> <uses-feature android:name="android.hardware.camera2" /> <uses-feature android:name="android.hardware.camera.autofocus" android:required="true" tools:targetApi="eclair" /> <uses-feature android:name="android.hardware.camera.flash" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" />
Ta tiến hành mở file tạo file camera.dart và bắt đầu khai báo: Import thư viện:
import 'package:camera_deep_ar/camera_deep_ar.dart'; Khai báo controller để điều khiển:
CameraDeepArController cameraDeepArController;
Và tiến hành xây dựng ui kem với chức năng:
@override
Widget build(BuildContext context) { return MaterialApp(
home: Scaffold(
body: Stack(
children: [ CameraDeepAr(
onCameraReady: (isReady) {
_platformVersion = "Camera status $isReady";
setState(() {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content:
Text(_platformVersion)));});},
onImageCaptured: (path) {
_platformVersion = "Image Taken @ $path";
setState(() {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content:
Text(_platformVersion)));});},
onVideoRecorded: (path) {
_platformVersion = "Video Recorded @ $path";
isRecording = false; setState(() { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(_platformVersion))); });}, androidLicenceKey: apiKey, iosLicenceKey: null, cameraDeepArCallback: (c) async { cameraDeepArController = c; setState(() {}); }), Align( alignment: Alignment.topRight, child: Padding( padding: EdgeInsets.all(15), child: FloatingActionButton( backgroundColor: Colors.transparent, heroTag: 'change_camera', child: Icon(Icons.cameraswitch_outlined), onPressed: () async { if (isFrontCamera==false) { cameraDeepArController.switchCameraDirection( direction: CameraDirection.front); isFrontCamera = true; } else { cameraDeepArController.switchCameraDirection( direction: CameraDirection.back); isFrontCamera = false; }},),),), }
Tại đây, ở phần androidLicenceKey: .. ta sẽ đưa Key API mà ta đã lấy từ lúc đầu đưa vào đây, để sử dụng cho IOS thì ta sẽ tạo một key riêng từ trang web deepAR và đưa vào iosLicenceKey:
- Để đổi một mặt nạ(mask) sử dụng hàm .changeMask(int p);
cameraDeepArController.changeMask(p);
- Để đổi một màu nền (filter) sử dụng hàm .changeFilter(int p);
cameraDeepArController.changeFilter(p);
- Để đổi một hiệu ứng (effect) sử dụng hàm .changeEffect(int p);
cameraDeepArController.changeEffect(p);
- Để đổi hướng của camera ta làm như sau: Đổi thành camera trước (front):
cameraDeepArController.switchCameraDirection( direction: CameraDirection.front);
Đổi thành camera sau(back):
cameraDeepArController.switchCameraDirection( direction: CameraDirection.back);
- Để chụp ảnh dùng hàm snapPhoto();
cameraDeepArController.snapPhoto();
- Để bắt đầu hoặc kết thúc quay video ta thực hiện (bắt đầu - start: kết thúc - stop):
cameraDeepArController.startVideoRecording(); cameraDeepArController.stopVideoRecording();
- Để kiểm tra camera có sẵn sàng hay chưa: isCameraReady();
4.2.1 Tận dụng tối đa thư viện
Thư viện khi cài đặt về sẽ chỉ có thể sử dụng khoảng 6 masks, 2 fitlters và 2 effects (mặc định có sẵn trong thư viện là 18 masks, 6 filters và 5 effects) để tối đa được điều này ta phải chỉnh sửa một ít ở trong thư viện camera_deep_ar.
Đối với một vài ide để chuyển đến tệp tin có chứa hàm thực thi gốc, ta nhấn giữ controller rồi nhất vào một hàm bất kỳ của thư viện camera_deep_ar mà ta đang sử dụng, ide sẽ điều hướng chúng ta đên tệp tin có tên là camera_deep_ar.dart. Hoặc ta có thể truy cập theo đường dẫn:
flutter\.pub-
cache\hosted\pub.dartlang.org\camera_deep_ar0.0.1\lib\camera_deep_ar.dart Tại đây, ta tìm đến các hàm enum mask, enum filters, enum effects
Hình 4.28 camera upgrade
Có thể thấy có rất nhiều hiệu ứng khác nhau nhưng ở các hàm supportedMasks, supportedFilters, supportedEffects, lại khai báo ít hơn trong các danh sách enum có sẵn này. Việc ta cần làm chỉ là khai báo thêm các giá trị con cho các hàm như sau:
Hình 4.29 Supported
Và như thế chúng ta có thể sử dụng tối đa các tài nguyên có sẵn của thư viện Lưu ý: Tuyệt đối không chỉnh sửa nếu như không chắc chắn về việc đang làm.
4.3 Chỉnh sửa hình ảnh:
Mở file pubspec.yaml và cài đặt vào các thư viện trên:
image_picker: ^0.6.7+4 cached_network_image: ^2.2.0+1 image_editor: ^0.7.1 extended_image:git:url: git://github.com/codenameakshay/extended_image.git photo_view: ^0.9.2 gallery_saver: ^2.0.1
Tiếp đó đưa các tệp hoặc sao chép các mã code vào dự án và chỉnh sửa lại để phù hợp và có thể chạy với project. Kết quả thu được như sau:
Hình 4.30 Image_enhance
Về các chức năng, tai màn hình image_enhance, chúng ta có thể chọn một hình ảnh để bắt đầu chỉnh sửa, thư viện image_picker của flutter hỗ trợ cho chúng ta dễ dàng chọn và lưu ảnh trong một biến, sau đó chúng ta sẽ chuyển ảnh đó qua màn hình edit_screen để bắt đầu chỉnh sửa:
- Khai báo các thư viện:
import 'package:image_picker/image_picker.dart'; import 'package:extended_image/extended_image.dart'; import 'save_screen.dart';
import 'package:image_editor/image_editor.dart' hide ImageSource; import 'package:gallery_saver/gallery_saver.dart';
import 'package:photo_view/photo_view.dart'; - Chọn ảnh:
final pickedFile = await picker.getImage(source: ImageSource.gallery); - Chỉnh sửa ảnh:
Navigator.push( context,
CupertinoPageRoute(
builder: (context) => EditPhotoScreen( arguments: [_image],
), ),
),
Ở màn hình edit, ta sẽ thực hiện chỉnh sửa hình ảnh, thư viện image_editor hầu như đã hỗ trợ gần hết mọi thứ để chúng ta có thể chỉnh sửa ảnh cơ bản một cách đơn giản, mọi công việc chỉ cần gọi hàm và thực hiện:
- Xoay ảnh: Ta truyền vào hàm giá trị true hoặc false, nếu true, sẽ lật về bên phải (tức là 90o về bên phải) và nếu là false sẽ lật về bên trái (tức là 90o về bên trái), hoặc chúng ta có thể điều chỉnh code thay vì rotate(right) thành rotate(left) để có thể đổi hướng lật.
void rotate(bool right) { editorKey.currentState.rotate(right: right); } - Lật ảnh: void flip() { editorKey.currentState.flip(); }
Khi gọi hàm, ảnh sẽ đơn giản được lật ngược lại so với chiều ban đầu theo chiều ngang.
- Cắt ảnh: Thư viện extended_image của flutter cũng hỗ trợ đầy đủ cho các chức năng cắt ảnh:
o Lấy dữ liệu hình ảnh thô và trực tiếp được cắt từ ExtendedImageEditorState():
final Rect rect = state.getCropRect(); final Uint8List img = state.rawImageData;
o Chuẩn bị các cài đặt cắt ảnh:
final EditActionDetails action = state.editAction; final double radian = action.rotateAngle;
final bool flipHorizontal = action.flipY; final bool flipVertical = action.flipX; final Uint8List img = state.rawImageData;
final ImageEditorOption option = ImageEditorOption();
option.addOption(ClipOption.fromRect(rect));
option.addOption(FlipOption(horizontal: flipHorizontal, vertical: flipVertical));
if (action.hasRotateAngle) { option.addOption(RotateOption(radian.toInt())); } option.addOption(ColorOption.saturation(sat)); option.addOption(ColorOption.brightness(bright + 1)); option.addOption(ColorOption.contrast(con));
option.outputFormat = const OutputFormat.jpeg(100); o Cắt ảnh với editImage:
final Uint8List result = await ImageEditor.editImage(
image: img,imageEditorOption: option, );
Ảnh sau khi được chỉnh sửa sẻ ở dạng Image data và có thể sử dụng để lưu ảnh hoặc thực hiện bất cứ tác vụ nào khác cho chỉnh sửa ảnh.
- Đổi độ sang, độ tương phản, độ bão hòa:
Thư viện image_editor của flutter sử dụng ma trận 5x4 để chuyển đổi màu sắc và các thành phần của một bitmap ảnh. Ma trận có thể được truyền dưới mảng đơn:
o Khai báo màu mặc định: final defaultColorMatrix = const <double>[
1, 0, 0, 0, 0,
0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0
];
o Hàm dùng để tính toán màu bão hòa
List<double> calculateSaturationMatrix(double saturation) { final m = List<double>.from(defaultColorMatrix);
final R = 0.213 * invSat; final G = 0.715 * invSat; final B = 0.072 * invSat; m[0] = R + saturation; m[1] = G; m[2] = B; m[5] = R; m[6] = G + saturation; m[7] = B; m[10] = R; m[11] = G; m[12] = B + saturation; return m; }
o Tính toán độ tương phản:
List<double> calculateContrastMatrix(double contrast) { final m = List<double>.from(defaultColorMatrix); m[0] = contrast; m[6] = contrast; m[12] = contrast; return m; }
Ảnh sau khi được chỉnh sửa sẽ được chuyên sang màn hình dưới dạng file, từ đó ta có thể lưu trữ ảnh vào hệ thống hoặc chia sẻ.
Trước tiên để lưu hình ảnh, ta cần phải đổi tên hình ảnh đó để tránh bị trùng lặp
void renameImage() {
String ogPath = image.path;
List<String> ogPathList = ogPath.split('/');
String ogExt = ogPathList[ogPathList.length - 1].split('.')[1]; DateTime today = new DateTime.now();
String dateSlug = "${today.day.toString().padLeft(2, '0')}-${today.month.toString().padLeft(2, '0')}-${today.year.toString()}_${today.hour.toString().padLeft(2, '0')}-$ {today.minute.toString().padLeft(2, '0')}-${today.second.toString().padLeft(2, '0')}"; image = image.renameSync( "${ogPath.split('/image')[0]}/PhotoEditor_$dateSlug.$ogExt"); print(image.path); }
Sau đó sử dụng thư viên gallery_image để lưu hình ảnh vào thư viện:
Future saveImage() async { renameImage();
await GallerySaver.saveImage(image.path, albumName: "PhotoEditor"); setState(() {savedImage = true;});
}
Ảnh sẽ được lưu vào thư viện hình ảnh và được lưu trong album forlder PhotoEditor. Có thể đổi đích lưu trữ bằng cách đổi tên PhotoEditor bằng tên mong muốn.
4.4 Giao tiếp với back-end qua API:
Thư viện http của flutter là một thư viện mạnh mẽ có thể sử dụng để thực hiện các request lên server như GET, POST, PUT, DELETE,… http còn có thể hỗ trợ cho authorization với các token,….
Upload dữ liệu POST(/login):
Future<Login> signIn(String email, String password) async{
Map data ={'email': email,'password': password,};
Login jsonData;
var response = await http.post(url+'login', body: data); if(response.statusCode == 200){
jsonData = Login.fromJson(jsonDecode(response.body));
setState(() {Navigator.pop(context, true);});
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content:
Text(jsonData.message)));
}else print(response.body); return jsonData;
}
Sau khi nhận dữ liệu từ các textfield widget là email và password, ta gọi hàm login để tiến hành đăng nhập vào ứng dụng bằng tài khoản đã đăng ký.
Ta sẽ khai báo MAP data với dữ liệu là email và string, sau đó thực hiện request với url đến server và body chính là dữ liệu mà chúng ta đã nhập trước đó.
Dữ liệu được trả về sẽ nằm ở dạng json dưới dạng {
success : true,
message: Login success, token: <chuỗi token> }
Để có thể chuyển đoạn json này thành cấu trúc mà DART có thể hiểu được, ta sẽ tạo cho chúng một model với tên là Login.dart (có thể sử dụng website Json to Dart để thực hiện tạo tự động):
class Login{
final bool success;
final String message; final String token; Login({
this.success,
this.message, this.token, });
factory Login.fromJson(Map<String, dynamic> json){
return Login( success: json['success'], message: json['message'], token: json['token'], ); } }
Ở trên là đoạn mã để chuyển chuỗi json về kiểu dữ liệu mà dart có thể hiểu được và sử dụng:
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content:
Text(jsonData.message)));
Đối với các request khác ta cũng làm tương tự, chỉ cần khai báo cho gói http là sử dụng request mong muốn.
var response = await http.post(url+'login', body: data);
var response = await http.get('${UserPreferences.getBaseURL()}social');
Ngoài ra để thực hiện upload file hoặc hình ảnh ta sẽ sử dụng multipart
_uploadPost(File image, String text) async {
var stream = new http.ByteStream(Stream.castFrom(image.openRead()));
var length = await image.length();
var uri = Uri.parse('${UserPreferences.getBaseURL()}user/social-add'); var request = new http.MultipartRequest("POST", uri);
var multipart = new http.MultipartFile('image', stream, length, filename: basename(image.path));
Map<String, String> headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer ${UserPreferences.getToken()}',}; request.fields['contents'] = text;
request.headers.addAll(headers); request.files.add(multipart);
var response = await request.send(); print(response.statusCode); if(response.statusCode == 200){ response.stream.transform(utf8.decoder).listen((event) { print(event); }); } }
Chúng ta sẽ chuyển đổi file hình ảnh thành chuỗi bytestream, sau đó đưa vào request
var request = new http.MultipartRequest("POST", uri);
var multipart = new http.MultipartFile('image', stream, length, filename: basename(image.path));
Nếu có header yêu cầu token hoặc các yêu cầu khác ta có thể thêm vào:
request.headers.addAll(headers); request.files.add(multipart);
và sau đó thực hiện request và nhận json kết quả trả về.
4.5 Shared Preferences:
Đối với một số dữ liệu, thì việc lưu trữ ở local giúp ích rất nhiều cho việc sử dụng, và làm nhẹ các kết quả trả về hoặc giảm thời gian truy cập, hay giảm số lần request,..
Trong flutter ta có thể sử dụng shread Preferences với cú pháp gần giống với khi sử dụng trong android.
Tạo một file mang tên UserPreferences.dart ở trong mục Shared Preferences:
import 'package:shared_preferences/shared_preferences.dart'; class UserPreferences {
static SharedPreferences _preferences;
static const _keyLogin = '_isLogin'; static const _keyToken ='_token'; static const _BASE_URL = '_BASE_URL'; static const _userId = '_userId'; static const _userName = '_userName'; static Future init() async {
_preferences = await SharedPreferences.getInstance();
static Future setLoginStatus(bool _isLogin) async => await _preferences.setBool(_keyLogin, _isLogin);
static bool getLoginStatus() =>_preferences.getBool(_keyLogin); static Future setToken(String _token) async =>
await _preferences.setString(_keyToken, _token);
static String getToken() => _preferences.getString(_keyToken); static Future setBaseURL(String _URL) async =>
await _preferences.setString(_BASE_URL, _URL);
static String getBaseURL() => _preferences.getString(_BASE_URL); static Future setUserId(String id) async =>
await _preferences.setString('_userId', id);
static String getUserId() => _preferences.getString(_userId); static Future setUserName(String name) async =>
await _preferences.setString(_userName, name);
static String getUserName() => _preferences.getString(_userName);
}
Tệp này sẽ lưu trữ các thông tin như là token sau khi đăng nhập của user, kiểm tra ứng dụng đã được đăng nhập hay chưa, hay url, hay bất kỳ giá trị nào mà chúng ta mong muốn lưu trữ để sử dụng giúp ích cho hoạt động của ứng dụng.
Giống như android, để khai báo:
_preferences = await SharedPreferences.getInstance();
Để lưu trữ dữ liệu:
await _preferences.setBool(_keyLogin, _isLogin); Để lấy dữ liệu:
preferences.getBool(_keyLogin);
Chúng ta có thể lưu trữ và xem kiểu dữ liệu như: String, int, bool, double, string list Sau khi đã khai báo và định nghĩa các hàm cần thiết, để gọi các sharedpreferences ở các nơi khác trong project để sử dụng, ta chỉ cần import file UserPreferences vào như một thư hiện flutter:
import 'package:shared_preferences/shared_preferences.dart'; sau đó, gọi hàm init() trong hàm void initState();
_loadPreference() async { await UserPreferences.init(); } @override void initState() { super.initState(); _loadPreference(); }
Và sử dụng trong class:
await UserPreferences.setLoginStatus(_isLogin); await UserPreferences.setToken(_token);
await UserPreferences.setUserId(jsonData.message.split('_')[0]); await UserPreferences.setUserName(jsonData.message.split('_')[1]);
4.6 Hình ảnh kết quả:
4.6.1 Camera screen: