Tại tác nhân này chỉ có thể xem thông tin thông qua một mã code do user chủ cho thuê cung cấp. Các thông bao gồm:
✓ Thông tin phòng đang ở.
✓ Danh sách hóa đơn của phòng (chưa thanh toán, đã thanh toán).
✓ Danh sách các thành viên ở cùng phòng.
2.3. Đặc tả các use case
2.3.1. Đăng nhập vào hệ thống
Đặc tả: Use case này cho phép đăng nhập vào hệ thống thông qua email và password mà người dùng đã tạo trước đó. Use case này không thể hiện cho cách đăng nhập qua phương thức khác (Google, Facebook).
Actor: Dành cho các tài khoản đăng kí trực tiếp qua ứng dụng không qua phương thức khác. Nói cách khác chỉ dành cho tài khoản của người cho thuê phòng
Sơ đồ 2.3. Mô tả sơ đồ tuần tự của quá trình đăng nhập thành công
Sơ đồ 2.5. Mô tả sơ đồ tuần tự của quá trình đăng nhập thất bại
2.3.2. Thống kê
Đặc tả: Khi user đăng nhập thành công, user sẽ sử dụng chức năng thống kê. Actor: Dành cho các tài khoản cho thuê phòng.
Sơ đồ 2.7. Mô tả sơ đồ tuần tự của quá trình thống kê
2.3.3. Tìm kiếm dữ liệu
Đặc tả: Khi user đăng nhập thành công, user sẽ sử dụng chức năng tìm kiếm tại màn hình chính của ứng dụng.
Actor: Dành cho các tài khoản cho người thuê phòng.
Sơ đồ 2.9. Mô tả sơ đồ hợp tác của quá trình thống kê
2.3.4. Xem thông tin tài khoản
Đặc tả: Khi user đăng nhập thành công, user sẽ sử dụng chức năng xem thông tin tài khoản tại màn hình DashBoard của ứng dụng.
Actor: Dành cho các tài khoản user người cho thuê phòng.
Sơ đồ 2.11. Mô tả sơ đồ tuần từ của quá trình xem thông tin tài khoản
2.3.5. Cập nhật dữ liệu
Đặc tả: Khi user đăng nhập thành công, user có thể tiến hành cập nhật dữ liệu của Firebase Realtime Database thông qua ứng dụng.
Actor: Dành cho các user người cho thuê phòng.
Sơ đồ 2.13. Mô tả sơ đồ tuần tự của quá trình cập nhật dữ liệu
2.4. Xây dựng cơ sở dữ liệu2.4.1. Các thực thể và các thuộc tính 2.4.1. Các thực thể và các thuộc tính User DetailBill Lodger Bill Service Room Hình 2.4. Mô tả các thực thể của hệ thống Bảng 2.4. Bảng mô tả các thuộc tính của thực thể
Thực thể
User
Thực thể
Lodger
Service
2.4.2. Sơ đồ ER
Sơ đồ 2.15. Sơ đồ ER
2.4.3. Sơ đồ quan hệ
Chương 3. Xây dựng phần mềm Motel Management
3.1. Danh sách các màn hình
Bảng 3.1. Danh sách các màn hình trong phần mềm Motel Management
STT Tên màn hình 1 activity_add_bill 2 activity_add_detail_bill 3 activity_add_service 4 activity_bill 5 activity_change_password 6 activity_dash_board 7 activity_detail_bill 8 activity_detail_lodger 9 activity_generate_qr_code 10 activity_info_room 11 activity_lodger 12 activity_login 13 activity_main 14 activity_profile 15 activity_register 16 activity_report 17 activity_reset_password 18 activity_room 19 activity_service 20 activity_splash_screen 21 activity_write_index
3.2. Màn hình chờ
Trong màn hình chờ này, hệ thống sẽ thực hiện tải dữ liệu mới nhất từ dịch vụ Firebase lên hệ thống. Tùy vào tốc độ mạng của người dùng mà tốc độ tải dữ liệu lên nhanh hay chậm.
Hình 3.1. Màn hình chờ của app Motel Management
Chi tiết màn hình như sau:
Bảng 3.2. Bảng mô tả màn hình chờ STT Tên 1 imgIconApp 2 txtTitleApp 3 4
Lưu ý: Các view trong tất cả màn hình là nhãn hoặc view đó chỉ hiển thị và không thay đổi thì sẽ không có tên.
Code xử lý tải dữ liệu. Dưới đây là quá trình tải dữ liệu của danh sách phòng. Các loại dữ liệu khác xử lý tương tự:
val rooms = Firebase.database.reference.child(Configs.TableName_Room)
rooms.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {listRooms.clear()
for (data: DataSnapshot in snapshot.children) {
val roomNew = data.getValue<Room>()
if (roomNew != null) { listRooms.add(roomNew) }
}
startActivity(Intent(this@SplashScreenActivity,
LoginActivity::class.java))
// finish()
}
override fun onCancelled(error: DatabaseError) { }
})
Khi người dùng không có kết nối mạng sẽ được dẫn đến màn hình đăng nhập và nhận được cảnh báo không có kết nối mạng như hình dưới đây:
Tuy nhiên nếu thiết bị đang chạy và bị ngắt kết nối đột ngột thì ứng dụng vẫn tiếp tục chạy. Các hoạt động sẽ diễn ra bình thường. Khi ứng dụng kết nối lại mạng sẽ được đồng bộ lại với dịch vụ Firebase. Đây là một tính năng rất hay của mảng dịch vụ Firebase Realtime Database.
3.3. Đăng nhập hệ thống
Đây là màn hình để người dùng truy cập vào hệ thống.
Hình 3.3. Màn hình đăng nhập hệ thống
Bảng 3.3. Bảng mô tả màn hình đăng nhập STT Tên 1 image_logo 2 3 edtUserName 4 edtPassword 6 btnResetPassword 7 btnLogin 8 9 btnRegister 10 11 btnSignInGoogle 12 btnSignInFacebook
Khi người dùng để trống một ô dữ liệu hệ thống sẽ hiển thị một thông báo nhắc nhở người
Code xử lý đầu vào như sau:
val userName:String=edtUserName.text.toString()
val password:String=edtPassword.text.toString()
if (TextUtils.isEmpty(userName)){
Toast.makeText(applicationContext, "Enter email address!",
Toast.LENGTH_SHORT).show() edtUserName.requestFocus()
return@setOnClickListener
}else if(TextUtils.isEmpty(password)){
Toast.makeText(applicationContext, "Enter Password address!",
Toast.LENGTH_SHORT).show() edtPassword.requestFocus()
return@setOnClickListener }else{
if (!isEmailValid(userName)){
Snackbar.make(it, "Error! Email not valid.", Snackbar.LENGTH_LONG) .setAction("Action", null).show()
edtUserName.requestFocus() }
}
Code xử lý đăng nhập thông qua email và mật khẩu như sau:
auth.signInWithEmailAndPassword(userName,
password).addOnCompleteListener(
this@LoginActivity ) { task ->
progressBar.visibility = View.GONE
if (!task.isSuccessful) {
Toast.makeText(
this@LoginActivity,
getString(R.string.auth_failed),
Toast.LENGTH_LONG
).show() } else {
Toast.makeText(
this@LoginActivity,
getString(R.string.auth_success),
Toast.LENGTH_LONG
).show()
startActivity(Intent(this@LoginActivity, MainActivity::class.java))
finish() }
Nếu người dùng có tài khoản mạng xã hội như Google, Facebook. Người dùng có thể đăng nhập thông qua các mạng xã hội này.
btnSignInFacebook.registerCallback( callbackManager,
object : FacebookCallback<LoginResult> {
override fun onSuccess(loginResult: LoginResult) {
Log.d(TAG, "facebook:onSuccess:$loginResult")
handleFacebookAccessToken(loginResult.accessToken)
}
override fun onCancel() {
Log.d(TAG, "facebook:onCancel")
// ...
}
override fun onError(error: FacebookException)
{ Log.d(TAG, "facebook:onError", error)
// ...
} })
btnSignInGoogle.setOnClickListener {
val signInIntent: Intent =
googleSignInClient.signInIntent
startActivityForResult(signInIntent, RC_SIGN_IN)
}
Giao diện khi đăng nhập qua kênh Google. Nếu tài khoản này chưa đăng nhập lần nào sẽ được hệ thống khởi tạo hai dịch vụ Nước, Điện trong bảng dữ liệu “Service”. Người dùng sẽ
Hình 3.5. Màn hình đăng nhập qua tài khoản Google
3.4. Màn hình đăng ký tài khoản
Tại màn hình dưới đây cho phép người dùng tạo một tài khoản mới. Người dùng cần cung cấp Email, Display name (tên hiển thị), Password (mật khẩu).
Bảng 3.4. Bảng mô tả màn hình đăng kí STT Tên 1 imgIconUser 2 3 edtUserName 4 edtDisplayName 5 edtPassword 6 btnRegister 7
Code xử lý đăng ký (đã kiểm tra các dữ liệu đầu vào) như sau:
auth.createUserWithEmailAndPassword(email, password)
.addOnCompleteListener(this) { task ->
progressBar.visibility = View.GONE
if (!task.isSuccessful) {
Snackbar.make(
it,
"Your email is already!. Try again, Please",
Snackbar.LENGTH_LONG
).show() } else {
if (hasAvatar){
val output = ByteArrayOutputStream()
val storeRef =
FirebaseStorage.getInstance().reference.child("avatar/$
{auth.currentUser?.uid }")
imageBitmap!!.compress(Bitmap.CompressFormat.JPEG, 100,
output)
val image = output.toByteArray()
val upload = storeRef.putBytes(image)
imgIconUser.visibility = View.INVISIBLE
upload.addOnCompleteListener { task ->
if (task.isSuccessful) {
storeRef.downloadUrl.addOnCompleteListener { urlTask ->
urlTask.result?.let{
imageUri = it
imgIconUser.visibility = View.VISIBLE
imgIconUser.setImageBitmap(imageBitmap)
}
} else {
Toast.makeText(this, "Upload Fail",
Toast.LENGTH_LONG).show() } } }else{ updateDisplayName(displayName) } }
Nếu người dùng cần thay đổi ảnh hình đại diện, có hai cách để thay đổi ảnh: Chụp ảnh mới, tải lên một ảnh có sẵn.
Hình 3.7. Màn hình lựa chọn phương thức cập nhật ảnh đại diện
Nếu người dùng tải lên một ảnh mới từ camera thì cần cung cấp quyền truy cập camera từ thiết bị:
Code xin quyền như sau:
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.CAMERA
) ==
PackageManager.PERMISSION_DENIED ) {
Toast.makeText(this, "Please allow permission access camera!",
Toast.LENGTH_LONG).show();
if (ActivityCompat.shouldShowRequestPermissionRationale( this,
Manifest.permission.CAMERA
)){
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.CAMERA), 1999
) }else{
val alert: AlertDialog.Builder= AlertDialog.Builder(this)
alert.setMessage("To use the camera, you need to allow camera permissions.You can follow these steps!\n\n1.Press button go to setting(app info).\n\n2.Choose Permission.\n\n3.Choose permission Camera & allow.")
val textView = TextView(this)
textView.text = "Error! Permission denied" textView.setPadding(20, 30, 20, 30)
textView.textSize = 20f
textView.setBackgroundColor(Color.parseColor("#0087FE"))
textView.setTextColor(Color.WHITE)
alert.setCustomTitle(textView)
alert.setNegativeButton("Cancel"){ dialogInterface, which ->
dialogInterface.dismiss()
}
alert.setPositiveButton("Go to setting"){ dialogInterface, which->
dialogInterface.dismiss()
val intent =
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
val uri = Uri.fromParts("package", packageName,
null) intent.data = uri
startActivity(intent)
}
val alertDialog: AlertDialog = alert.create()
alertDialog.setCancelable(false)
alertDialog.setOnShowListener {
Khi người dùng chọn Allow (cho phép) thiết bị sẽ mở giao diện chụp hình của người dùng. Tuy nhiên nếu sẽ có khi thiết bị người dùng chặn quyền truy cập Camera thì giao diện sẽ hiển thị một thông báo như màn hình dưới đây:
Hình 3.9. Màn hình thông báo lỗi khi người dùng chặn quyền truy cập camera
Nếu trường hợp đã có sẵn ảnh trong thiết bị chỉ cần sử chức năng thứ hai (Select profile picture) trong hình 3.7. Sau đó thiết bị sẽ hiển thị các ứng dụng quản lý tệp tin ví dụ như hình.
Hình 3.10. Màn hình cho người dùng lựa chọn ứng dụng quản lý file
Sau khi chọn ảnh thành công hệ thống sẽ hiển thị giao diện cắt ảnh. Khi người dùng thực hiện xong. Giao diện đăng ký sẽ quay trở lại và tải ảnh lên hệ thống ứng dụng.
Hình 3.11. Màn hình cắt ảnh trước khi tải lên ứng dụng
3.5. Màn hình quên mật khẩu
Trong quá trình sử dụng ứng dụng, người dùng có thể quên mất mật khẩu đăng nhập của bản thân. Với chức năng này người dùng có thể khôi phục mật khẩu của bản thân của mình.
Chi tiết màn hình như sau:
Bảng 3.5. Bảng mô tả màn hình khôi phục mật khẩu
STT Tên 1 2 imgResetPassword 3 txtResult 4 5 edtEmail 6 btnBack 7 btnResetPassword
Hình 3.13. Màn hình quên mật khẩu chờ nhập email
Code xử lý gửi email như sau:
auth.sendPasswordResetEmail(email).addOnCompleteListener(this@ResetPas swordActivity
) { task ->
if (task.isSuccessful) {
btnResetPassword.visibility=View.GONE
imgResetPassword.setImageResource(R.drawable.ic_the_boy_forgot_passwo
r d_normal)
txtResult.text="We have sent you instructions to reset your password!"
Snackbar.make(it, "We have sent you instructions to reset your password!", Snackbar.LENGTH_LONG)
.setAction("Action", null).show() }
Khi người dùng nhập email, hệ thống sẽ gửi một đường link trong hòm thư:
Hình 3.15. Email hệ thống - thiết lập mật khẩu mới
Khi người dùng nhấn vào link sẽ được chuyển sang giao diện đổi mật khẩu
3.6. Màn hình chính
Khi người dùng đăng nhập thành công, hệ thống sẽ hiển thị giao diện như sau:
Hình 3.17. Màn hình chính của ứng dụng Motel Management Bảng 3.6. Bảng mô tả màn hình chính STT Tên 1 2 3 rvRoom 4 fab
Nút Button (nút nhấn) gốc cuối màn hình có chức năng thêm phòng vào cơ sở dữ liệu. Giao diện thêm phòng sẽ được trình bày trong mục 3.12.
Hình 3.18. Phím chức năng thêm phòng
Code xử lý hiển thị màn hình thêm phòng:
fab.setOnClickListener {
startActivity(Intent(this, RoomActivity::class.java))
}
Code xử lý tải dữ liệu lên màn hình:
rvRoom.apply{
layoutManager =
LinearLayoutManager(this@MainActivity,
LinearLayoutManager.VERTICAL, false)
}
listRoom =
SplashScreenActivity.listRooms.filter{ p: Room -> (p.userId == auth.uid && !p.deleted) } as ArrayList<Room>
/GetDataAsyncTask().execute()
listRoomUnFull =arrayListOf()
roomAdapter = RoomAdapter(this,
listRoomUnFull) rvRoom.adapter = roomAdapter roomAdapter.addLoading()
Trong danh sách Phòng, mỗi một hàng thể như vậy sẽ thể hiện thông tin cơ bản về phòng.
Hình 3.19. Các hàng trong danh sách phòng của màn hình chính
Trong hàng đầu tiên thể hiện: tên phòng, giá tiền, tổng số người đang ở trọ, tổng các hóa đơn, trạng thái phòng. Và đặt biệt có một nhãn kế bên tên phòng thể hiện tổng số hóa đơn chưa thanh toán. Khi nhấn vào sẽ hiện giao diện hiển thị danh sách các hóa đơn chưa thanh toán. Giao diện này sẽ được đề cập ở mục 3.6.
Bảng 3.7. Mô tả chi tiết của từng dòng trong danh sách phòng
STT Nội dung 1 Phòng 3 2 108 3 1,600,000 đ 4 11 5 136 6 Active/ Empty
Trong một item nếu người dùng lướt sang trái hoặc phải sẽ có thêm các chức năng như hai hình dưới đây. Tuy nhiên nếu phòng đang trống thì chỉ có thể thực hiện kéo bên phải.
Hình 3.20. Hình ảnh chi tiết về hai chức năng khi lướt item qua bên trái
Các chức năng bao gồm:
➢ Icon đầu tiên: Chỉnh sửa thông tin phòng. Giao diện sẽ được trình bày tài mục 3.14.
➢ Icon thứ hai: Với biểu tượng thùng rác thể hiện chức năng xóa phòng ra khỏi cơ sở dữ liệu.
Hình 3.21. Hình ảnh chi tiết về hai chức năng khi lướt item qua bên phải
Các chức năng này chỉ hoạt động với các phòng có trạng thái Active (có người) bao gồm:
➢ Add Bill: Thêm một hóa đơn cho phòng chức năng này sẽ được giới thiệu ở mục 3.14.
➢ Write Index: Ghi chỉ số gồm điện, nước, tiền phòng của phòng. Chức năng này cũng sẽ được giới thiệu ở mục 3.15.
Để tăng trải nghiệm người dùng, tại giao diện này cung cấp chức năng tìm kiếm phòng theo tên phòng. Ví dụ hình dưới đây khi tìm kiếm tên phòng là “phòng” hệ thống sẽ lọc dữ liệu và hiển thị lại màn hình như sau:
Hình 3.22. Màn hình tìm kiếm phòng
Code xử lý tìm kiếm phòng:
var filterRoom: Filter = object : Filter() {
override fun performFiltering(charSequence: CharSequence): FilterResults {
var filterList:ArrayList<Room> =arrayListOf()
if (charSequence.isEmpty()){
filterList.addAll(listRoomFull) }else{
val charFilter:String=charSequence.toString().toLowerCase().trim()
for (room:Room in listRoomFull){
if (room.roomName.toLowerCase().contains(charFilter)){
filterList.add(room) } } }
val result=FilterResults()
result.values=filterList return result }
override fun publishResults(charSequence: CharSequence, filterResults:
FilterResults) { listRoom.clear()
listRoom.addAll(filterResults.values as List<Room>) notifyDataSetChanged() }
}
Ngoài các chức năng đã nêu trên, giao diện chính còn chứa các chức năng trong menu sẽ được trình bày chi tiết sau.
➢ Truy cập vào giao diện Dashboard (trang quản lý) của user – trình bày ở mục 3.7.
3.7. Màn hình khu vực quản lý chính – Dashboard
Khu vực này hiển thị gồm các chức năng sau: Thông tin tài khoản, Danh sách các hóa đơn, Danh sách dịch vụ, thống kê.
Bảng 3.8. Mô tả các chức năng trong màn hình dashboard STT Tên 1 imgAccount 2 imgBill 3 imgService 4 imgReport
Khi người dùng chọn chức năng thông tin tài khoản giao diện sẽ trả về kết quả như sau:
Chi tiết màn hình thông tin tài khoản:
Bảng 3.9. Bảng mô tả màn hình thông tin tài khoản
STT Tên 1 2 imgIconUser 3 4 5 6 7 8 9 10 11
12 13 14 15 16 17 18 btnUpdateProfile 19 btnClose
Khi người dùng chọn chức năng danh sách các hóa đơn giao diện sẽ hiển thị như sau:
Hình 3.25. Màn hình danh sách hóa đơn
Chi tiết màn hình như sau:
Bảng 3.10. Bảng mô tả danh sách hóa đơn
STT Tên
1
2
3 rvBill
Code xử lý tải dữ liệu lên màn hình tương tự như mục 3.6.
Tại màn hình này cung cấp danh sách tất cả các hóa đơn có trong cơ sở dữ liệu thuộc quyền quản lý của user hiện tại. Trong danh sách hóa đơn nếu tồn tại lớn hơn hoặc một hóa đơn. Danh sách hóa đơn sẽ luôn chứa một dòng là chứa tiêu đề (với mục đích tiêu đề này là để gom các hóa có cùng một tháng lại với nhau) và n dòng các hóa đơn (n là tổng số hóa đơn của tháng đó). Ví
dụ nếu một tài khoản ABC có tổng cộng là 100 hóa đơn. Trong đó tổng số hóa đơn tháng 5, 6, 7 lần lượt là 11, 22, 67. Dựa vào dữ liệu tính toán được như sau:
➢ Tổng số dòng là 103.
➢ Số dòng chứa tiêu đề là 3.
➢ Số dòng chứa các hóa đơn là 100.
Bảng 3.11. Mô tả dữ liệu item tiêu đề hóa đơn
STT Nội dung
1 Month 12/2020
2 47,528,569đ
3 17 bill
Tiếp theo một dòng thể hiện một số thông tin hóa đơn.
Bảng 3.12. Mô tả dữ liệu trong item hóa đơn STT Nội dung 1 Service Rác 2 12/2020 09:16 3 Phòng 2 4 Paid 5 20,000 đ
Nếu như màn hình trên là thể hiện danh hóa đơn thì khi người dùng chọn chức năng danh sách dịch vụ. Trong giao diện dưới đây thể hiện tất cả các dịch vụ thuộc một tài khoản tài khoản. Giao diện sẽ hiển thị như sau: