Xây dựng ứng dụng chat trong android với firebase

81 136 1
Xây dựng ứng dụng chat trong android với firebase

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

BỘ GIÁO DỤC VÀ ĐÀO TẠO TRƯỜNG ĐẠI HỌC DÂN LẬP HẢI PHÒNG -o0o - ISO 9001:2015 ĐỒ ÁN TỐT NGHIỆP NGHÀNH CÔNG NGHỆ THÔNG TIN HẢI PHÒNG 2020 BỘ GIÁO DỤC VÀ ĐÀO TẠO TRƯỜNG ĐẠI HỌC DÂN LẬP HẢI PHÒNG -o0o - XÂY DỰNG ỨNG DỤNG CHAT TRONG ANDROID VỚI FIREBASE ĐỒ ÁN TỐT NGHIỆP ĐẠI HỌC HỆ CHÍNH QUY Ngành: Cơng nghệ Thơng tin HẢI PHỊNG 2020 BỘ GIÁO DỤC VÀ ĐÀO TẠO TRƯỜNG ĐẠI HỌC DÂN LẬP HẢI PHÒNG -o0o - XÂY DỰNG ỨNG DỤNG CHAT TRONG ANDROID VỚI FIREBASE ĐỒ ÁN TỐT NGHIỆP ĐẠI HỌC HỆ CHÍNH QUY Ngành: Cơng nghệ Thông tin Sinh viên thực hiện: Bùi Văn Minh Giáo viên hướng dẫn: Th.S Phùng Anh Tuấn Mã sinh viên: 1412101076 HẢI PHÒNG 2020 BỘ GIÁO DỤC VÀ ĐÀO TẠO CỘNG HÒA XÃ HỘI CHỦ NGHĨA VIỆT NAM TRƯỜNG ĐẠI HỌC DÂN LẬP HẢI PHÒNG Độc lập - Tự - Hạnh phúc -o0o NHIỆM VỤ THIẾT KẾ TỐT NGHIỆP Sinh viên: Bùi Văn Minh Mã số: 1412101076 Lớp: CT1801 Ngành: Công nghệ Thông tin Tên đề tài: Xây dựng ứng dụng chat Android với Firebase NHIỆM VỤ ĐỀ TÀI Nội dung yêu cầu cần giải nhiệm vụ đề tài tốt nghiệp a Nội dung: Tìm hiểu hệ điều hành android Tìm hiểu mơi trường lập trình Android Studio Tìm hiểu tảng hỗ trợ cho lập trình di động firebase Tìm hiểu kỹ thuật lập trình ứng dụng chat android với firebase Xây dựng chương trình chat điện thoại b Các yêu cầu cần giải Sử dụng công cụ Android Studio để viết chương trình chạy thiết bị android Tìm hiểu kiến thức tảng firebase Xây dựng ứng dụng chat android với firebase có chức năng: + Đăng ký, đăng nhập + Hiển thị danh sách tài khoản + Chỉnh sửa profile + Chat kênh riêng tư + Xóa tin nhắn chat + Thoat khỏi hệ thống Đóng gói chương trình thành file *.apk cho phép cài đặt chạy thiết bị android thật Các số liệu cần thiết để thiết kế, tính tốn …………………………………………………………………………… Địa điểm thực tập Trường Đại học Dân lập Hải Phòng CÁN BỘ HƯỚNG DẪN ĐỀ TÀI TỐT NGHIỆP Người hướng dẫn thứ nhất: Họ tên: Phùng Anh Tuấn Học hàm, học vị: Thạc sỹ Cơ quan công tác: Trường Đại học Dân lập Hải Phịng Nội dung hướng dẫn:  Tìm hiểu hệ điều hành android  Tìm hiểu mơi trường lập trình Android Studio  Tìm hiểu tảng hỗ trợ cho lập trình di động firebase  Tìm hiểu kỹ thuật lập trình ứng dụng chat android với firebase  Xây dựng ứng dụng chat chạy thiết bị ảo điện thoại thật Người hướng dẫn thứ hai: Họ tên: Học hàm, học vị: Cơ quan công tác: Nội dung hướng dẫn: Đề tài tốt nghiệp giao ngày 14 tháng 10 năm 2019 Yêu cầu phải hoàn thành trước ngày 10 tháng 01 năm 2020 Đã nhận nhiệm vụ: Đ.T.T.N Đã nhận nhiệm vụ: Đ.T.T.N Sinh viên Cán hướng dẫn Đ.T.T.N Hải Phòng, ngày tháng năm 2020 Hiệu trưởng GS.TS.NGưT Trần Hữu Nghị CỘNG HÒA XÃ HỘI CHỦ NGHĨA VIỆT NAM Độc lập - Tự - Hạnh phúc PHIẾU NHẬN XÉT CỦA GIẢNG VIÊN HƯỚNG DẪN TỐT NGHIỆP Họ tên giảng viên: ……………………………………………………………………… Đơn vị công tác: …………………………………………………………………………… Họ tên sinh viên: ……………………………… Ngành:……………………………… Nội dung hướng dẫn: …………………………………………………………………… …………………………………………………………………………………………… Tinh thần thái độ sinh viên trình làm đề tài tốt nghiệp Đánh giá chất lượng đồ án/khóa luận (so với nội dung yêu cầu đề nhiệm vụ Đ.T T.N mặt lý luận, thực tiễn, tính tốn số liệu…) Ý kiến giảng viên hướng dẫn tốt nghiệp Đạt Khơng đạt Điểm:…………………………………… Hải Phịng, ngày … tháng … năm 2020 Giảng viên hướng dẫn (Ký ghi rõ họ tên) CỘNG HÒA XÃ HỘI CHỦ NGHĨA VIỆT NAM Độc lập - Tự - Hạnh phúc PHIẾU NHẬN XÉT CỦA GIẢNG VIÊN CHẤM PHẢN BIỆN Họ tên giảng viên:……………………………………………………………… Đơn vị công tác: …………………………………………………………………… Họ tên sinh viên: ……………………………… Ngành: …………………… Đề tài tốt nghiệp: …………………………………………………………………… …………………………………………………………………………………………… Phần nhận xét giảng viên chấm phản biện Những mặt hạn chế Ý kiến giảng viên chấm phản biện Đượ bảo vệ Khơng bảo vệ Điểm:…………………………… Hải Phịng, ngày …… tháng … năm 2020 Giảng viên chấm phản biện (Ký ghi rõ họ tên) LỜI CẢM ƠN Để đồ án đạt kết ngày hôm lời em xin chân thành cảm ơn đến tất người hỗ trợ giúp đỡ em mặt kiến thức tinh thần để hoàn thành đồ án Em xin chân thành cảm ơn thầy cô giáo khoa Công nghệ thông tin tồn thể thầy giáo trường Đại học Dân Lập Hải Phòng giúp đỡ truyền đạt cho em kiến thức quý báu năm học trường Đặc biệt em xin cảm ơn thầy Ths Phùng Anh Tuấn người hướng dẫn bảo trực tiếp em để hoàn thành đồ án thời gian qua Với điều kiện thời gian có hạn kinh nghiệm hạn chế sinh viên, đồ án em khơng thể tránh khỏi thiếu sót Em mong nhận bảo , đóng góp ý kiến thầy để em có điều kiện bổ sung, hồn thiện chương trình để áp dụng vào thực tế sau Em xin chân thành cảm ơn! Sinh viên Bùi Văn Vũ MỤC LỤC CHƯƠNG 1: GIỚI THIỆU VỀ HỆ ĐIỀU HÀNH ANDROID .5 1.1 Giới thiệu hệ điều hành android 1.1.1 Hệ điều hành 1.1.2 Hệ điều hành Android 1.2 Sự đời lịch sử phát triển 1.2.1 Sự đời Android 1.2.2 Lịch sử phát triển Android 1.3 Các phiên Android 1.3.1 Phiên Android 1.0 1.3.2 Phiên Android 1.5: CupCake 1.3.3 Phiên Android 1.6: Donut .9 1.3.4 Phiên Android 2.0 phiên Android 2.1: Eclair 1.3.5 Phiên Android 2.2: Froyo 1.3.6 Phiên Android 2.3: Gingerbread 10 1.3.7 Phiên Android 3.0: Honeycomb 10 1.3.8 Phiên Android 4.0: Ice Cream Sandwich 10 1.3.9 Phiên Android 4.1: Jelly Bean 10 1.3.10 Hệ điều hành Android 4.4: KitKat 11 1.3.11 Hệ điều hành Android 5.0: Lollipop 11 1.3.12 Phiên Android 6.0: Marshmallow 11 1.3.13 Phiên Android 7.0:nougat .11 1.3.14 Phiên 8.0:Oreo 12 1.3.15 Phiên Android P 12 1.4 Kiến trúc hệ điều hành Android 12 1.4.1 Linux kernel 13 1.4.2 Tầng Library Android Runtime 14 1.4.3 Tầng Application Framework 15 1.4.4 Tầng ứng dụng 16 CHƯƠNG 2: MÔI TRƯỜNG LẬP TRÌNH ANDROID STUDIO .18 2.1 Sơ lược Android Studio 18 Ở tạo khởi tạo Constructor để đẩy liệu lên tạo Constructor rỗng để nhận liệu Bình thường sử dụng ArrayAdapter có sẵn, listview thị liệu đoạn text dòng, dịng có text image view khơng dùng mà phải custom lại liệu hiển thị được, tạo class AdapterUser Tiếp theo hiển thị tất tài khoản UserFragmentt.java Tại khai báo biến sau: RecyclerView recyclerView; AdapterUser adapterUser; List userList; FirebaseAuth mAuth; Ở xây dựng hàm getAllUser để hiển thị danh sách người dùng lên Recycler View private void getAllUsers() { //get current user final FirebaseUser fUser= FirebaseAuth.getInstance().getCurrentUser(); //Get path of database named "Users" containing users info DatabaseReference ref= FirebaseDatabase.getInstance().getReference("Users"); ref.addValueEventListener(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot dataSnapshot) { userList.clear(); for (DataSnapshot ds: dataSnapshot.getChildren()){ ModeIUser modeIUser=ds.getValue(ModeIUser.class); //get all users except currently signed in user if (!modeIUser.getUid().equals(fUser.getUid())){ userList.add(modeIUser); //adapter adapterUser=new AdapterUser(getActivity(),userList); //set adapter to recycler view recyclerView.setAdapter(adapterUser); } } } @Override public void onCancelled(@NonNull DatabaseError databaseError) { } }); } 59 3.3.4 Chat kênh riêng tư Để chat hai tài khoản với nhau, hiển thị nội dung tin nhắn, hiển thị thời gian thực ảnh đại diện gửi cần tạo class ModelChat để lưu trữ liệu gồm có thuộc tính sau: String message,receiver,sender,timestamp; boolean isSeen; boolean redelete; Các thuộc tính class ModelChat gồm có:  message: nội dung tin nhắn  receiver: Id người nhận  sender: Id người gửi  timestamp: thời gian gửi/nhận tin nhắn  isSeen: xem  redelete: xóa tin nhắn người gửi Ở tạo khởi tạo Constructor để đẩy liệu lên tạo Constructor rỗng để nhận liệu Bình thường sử dụng ArrayAdapter có sẵn, listview thị liệu đoạn text dòng, dòng có text image view khơng dùng mà phải custom lại liệu hiển thị được, tạo class AdapterChat Khi chạm vào tài khoản mà muốn chat hệ thống chuyển sang giao diện chat myHolder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(context,ChatActivity.class); intent.putExtra("hisUid",hisUID); context.startActivity(intent); } 60 Trong trình chuyển đổi hai giao diện đồng thời hisUid người muốn chat sang bên ChatActivity Ở bên ChatActivity muốn lấy hiUid sử dụng câu lệnh sau: Intent intent = getIntent(); hisUid = intent.getStringExtra("hisUid"); Ở bên giao diện chat bắt kiện cho nút gửi tin nhắn Nếu liệu ô EditText mà rỗng hiển thị thơng báo "cannot send the empty message" Ngược lại gọi tới hàm sendMessage(message): private void sendMessage(String message) { DatabaseReference databaseReference=FirebaseDatabase.getInstance().getReference(); String timestamp=String.valueOf(System.currentTimeMillis()); HashMaphashMap=new HashMap(); hashMap.put("sender",myUid); hashMap.put("receiver",hisUid); hashMap.put("message",message); hashMap.put("timestamp",timestamp); hashMap.put("isSeen",false); hashMap.put("redelete",true); databaseReference.child("Chats").push().setValue(hashMap); //reset edittext messageEt.setText(""); } Sau liệu đẩy lên Database ta hiển thị nội dung tin nhắn, ảnh người gửi thời gian thực nhận gửi tin nhắn hàm đây: private void readMessages() { chatList = new ArrayList(); DatabaseReference dbRef= FirebaseDatabase.getInstance().getReference("Chats"); dbRef.addValueEventListener(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot dataSnapshot) { chatList.clear(); for (DataSnapshot ds: dataSnapshot.getChildren()){ ModelChat chat = ds.getValue(ModelChat.class); if ((chat.getReceiver().equals(myUid)&& chat.getSender().equals(hisUid)&&ds.child("redelete").getValue().equals(true))|| chat.getReceiver().equals(hisUid)&&chat.getSender().equals(myUid)) { chatList.add(chat); } adapterChat=new AdapterChat(ChatActivity.this,chatList,hisImage); adapterChat.notifyDataSetChanged(); //set adpter to recyclerview recyclerView.setAdapter(adapterChat); 61 } } @Override public void onCancelled(@NonNull DatabaseError databaseError) { } }); } Ở khai báo khởi tạo DatabaseReference để truy xuất vào root Chats Sau gọi biến vừa khai báo thêm phương thức addValueEventListener(new ValueEventListener()) khởi tạo cho nó, tự phát sinh phương thức cho gồm có onDataChange onCancelled Trong hàm onDataChange có thay đổi giá trị root Chats trả hàm onDataChange có tham số dataSnapshot Dùng vòng lặp for để lấy tất giá trị root Chats Ở kiểm tra điều kiện id người nhận mà myUid id người gửi hisUid trường "redelete" có giá trị true, id người nhận hisUid id người gửi myUid thêm giá trị nội dung chat vào chatList Sau đẩy vào adapterChat sau đẩy hiển thị recyclerView 3.3.5 Xóa tin nhắn chat Muốn xóa tin nhắn bắt kiện click vào messageLayout: myHoder.messageLayout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle("Xóa"); builder.setMessage("Bạn có chắn muốn xóa tin nhắn này?"); // delete button builder.setPositiveButton("Xóa", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int which) { deleteMessage(i); } }); // cancel delete button builder.setNegativeButton("Hủy", new DialogInterface.OnClickListener() { @Override 62 public void onClick(DialogInterface dialogInterface, int which) { / dimiss dialog dialogInterface.dismiss(); } }); / create and show dialog builder.create().show(); } }); Tạo button "Delete" "No" bắt kiện button "Delete" gọi hàm deleteMessage(i) private void deleteMessage(int position) { final String myUID = FirebaseAuth.getInstance().getCurrentUser().getUid(); String timestamp=chatList.get(position).getTimestamp(); DatabaseReference dbRef = FirebaseDatabase.getInstance().getReference("Chats"); Query query = dbRef.orderByChild("timestamp").equalTo(timestamp); query.addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot dataSnapshot) { for (DataSnapshot ds: dataSnapshot.getChildren()){ if (ds.child("sender").getValue().equals(myUID)){ DatabaseReference databaseReference = FirebaseDatabase.getInstance().getReference("Chats"); HashMap hashMap = new HashMap(); hashMap.put("senddelete",false); ds.getRef().updateChildren(hashMap); Toast.makeText(context, "Đã xóa ", Toast.LENGTH_SHORT).show(); } else if (ds.child("receiver").getValue().equals(myUID)){ DatabaseReference databaseReference = FirebaseDatabase.getInstance().getReference("Chats"); HashMap hashMap = new HashMap(); hashMap.put("redelete",false); ds.getRef().updateChildren(hashMap); Toast.makeText(context, "Đã xóa ", Toast.LENGTH_SHORT).show(); } } } @Override public void onCancelled(@NonNull DatabaseError databaseError) { } }); Xóa tin nhắn gửi đi: Lấy id người dùng thời, lấy thời gian chat tin nhắn muốn xóa ứng dụng so sánh với thời gian tin nhắn database 63 xem có hay không id người gửi với myUid cho giá trị senddelete = false xóa tin nhắn Xóa tin nhắn người khác gửi tới cho mình: Nếu Id người nhận có với myUid, thời gian tin nhắn muốn xóa chatList với thời gian tin nhắn database chuyển giá trị "redelete" true sau update lại vào database xóa tin nhắn 3.3.6 Đăng xuất Muốn đăng xuất khỏi hệ thống cần chạm vào phần đăng xuất góc bên trái ứng dụng để đăng xuât Hệ thống tự động gọi tới phương thức mAuth.signOut() để đăng xuất 3.4 Bài tốn Trao đổi thơng tin với hàng ngày nhu cầu tất yếu người Cuộc cách mạng công nghiệp 4.0 ngày thu hẹp khoảng cách người với ứng dụng mạng xã hội, chat trực tuyến Tuy 64 nhiên việc sử dụng dịch vụ để chat với thiết bị di động cịn có nhiều điểm bất tiện dung lượng cài ứng dụng đặt lớn, tiêu tốn nhiều nhớ ram xử lý gửi nhận tin nhắn chưa thực thi theo thời gian thực RealTime Firebase Database Google Firebase sở liệu thời gian thực có khả xử lý liệu đủ nhanh cho kết phản hồi gần tức thời Ứng dụng chat thời gian thực dựa RealTime Firebase Database giúp người nhận thông tin quan trọng từ phía đối tác 3.5 Sơ đồ chức Sơ đồ phân rã chức chương trình chat Hình 3.5.1: Sơ đồ chức chương trình chat 3.6 Thiết kế liệu 3.6.1 Bảng liệu người dùng STT Tên trường Kiểu liệu name String Độ rộng Mô tả Tên người dùng 65 email String Địa email search String Tìm kiếm phone String Số điện thoại image String đường dẫn hình ảnh cover String đường dẫn ảnh uid String Id người dùng Hình 3.6.1: Dữ liệu người dùng 3.6.2 Bảng thông tin nội dung tin nhắn STT Tên trường Kiểu liệu Độ rộng Mô tả message String nội dung tin nhắn receiver String Id người nhận sender String id người gửi timestamp String thời gian tin nhắn isSeen boolean xem/chưa xem redelete boolean xóa tin nhắn người gửi Hình 3.6.2: Thơng tin nội dung tin nhắn 3.7 Giao diện chương trình 3.7.1 Giao diện Khi ứng dụng khỏi động Giao diện hiển thị hình bên 66 Hình 3.7.1: Giao diện chương trình 3.7.2 Giao diện đăng ký Sau chạm vào nút "ĐĂNG KÝ" giao diện chương trình chương trình chuyển qua giao diện đăng ký hình bên Hình 3.7.2: Giao diện đăng ký tài khoản 67 Sau người dùng nhập thông tin email password người dùng ấn nút "Đăng ký" Đăng ký thành công giao diện chuyển qua hình thơng tin người dùng 3.7.3 Giao diện đăng nhập Sau có tài khoản chạm vào nút "ĐĂNG NHẬP" giao diện chương trình chương trình chuyển qua giao diện đăng nhập hình bên Hình 3.7.3: Giao diện đăng nhập tài khoản Người dùng nhập email password sau ấn "ĐĂNG NHẬP" thành cơng hệ thống chuyển qua giao diện thông tin người dùng 3.7.4 Giao diện thông tin người dùng Giao diện thông tin người dùng sau đăng nhập đăng ký thành công Sau chạm vào nút chỉnh sửa thông tin cá nhân, hình hiển thị ra, lựa chọn chỉnh sửa ảnh đại diện, ảnh nền, tên, số điện thoại 68 Hình 3.7.4: Giao diện thơng tin người dùng Để chỉnh sửa ảnh đại diện chạm vào " Edit Profile picture" (1) sau hình bên Chọn ảnh thư viện ảnh camera chụp ảnh Việc chỉnh sửa anh "Edit cover Photo" (2) tương tự Để chỉnh sửa tên người dùng chạm vào "Edit Name" (3) sau giao diện để nhập thông tin 69 Người dùng cần nhập tên cần chỉnh sửa ô nhập liệu chạm vào "UPDATE" tên người dùng thêm chỉnh sửa Còn muốn hủy thao tác chạm vào "CANCEL" Với việc chỉnh sửa số điện thoại người dùng tương tự chỉnh sửa tên 3.7.5 Giao diện danh sách người dùng Muốn hiển thị danh sách người dùng chạm vào biểu tượng bên để hiển thị tất danh sách người dùng, giúp hiển thị tất tài khoản để tìm kiếm lựa chọn người mà muốn chat Hình.3.7.5: Giao diện danh sách người dùng 70 3.7.6 Giao diện chat Khi chạm vào người dùng danh sách người dùng mà muốn gửi tin nhắn chương trình chuyển đến giao diện chat Hình.3.7.6: Giao diện Chat Ở giao diện chat người dùng nhập nội dung vào ô tin nhắn sau chạm vào biểu tượng gửi tin nhắn nội dung tin nhắn gửi thành công 71 KẾT LUẬN Sau thời gian tìm hiểu đề tài: "Xây dựng ứng dụng chat android với firebase" em thực nôi dung đề tài theo yêu cầu đặt Với mục tiêu tìm hiểu hệ điều hành Android, tìm hiểu mơi trường lập trình Android Studio tìm hiểu kỹ thuật để xây dựng ứng dụng hoàn chỉnh thiết bị thật Thơng qua việc tìm hiểu nắm bắt lý thuyết hệ điều hành Android, bước đầu xây dựng thành cơng chương trình thực nghiệm với chức  Trong trình tìm hiểu lý thuyết hệ điều hành Android xây dựng ứng dụng em thu kết sau:  Hiểu lớp đối tượng, phương thức hỗ trợ lập trình hệ điều hành Android  Hiểu dịch vụ Firebase  Xây dựng ứng dụng với chức năng: Đăng ký, đăng nhập, hiển thị danh sách người dùng, hiển thị chỉnh sửa profile, chat kênh riêng tư  Đóng gói ứng dụng thành file.apk cho phép cài đặt điện thoại Android  Đưa thành công ứng dụng lên Google Play cho phép người dùng tìm hiểu cài đặt thiết bị thật Do thời gian có hạn với kiến thức thân hạn chế nên em chưa tìm hiểu sâu kiến thức liên quan tới hệ điều hành Android nên chương trình em nhiều hạn chế, em cố gắng phát triển thêm để chương trình thân thiện, dễ sử dụng có nhiều chức để áp dụng vào đời sống 72 TÀI LIỆU THAM KHẢO [1] https://vi.wikipedia.org/wiki/Android_(hệ_điều_hành) [2] https://fptshop.com.vn/tin-tuc/danh-gia/lich-su-phat-trien-cua-android-hedieu-hanh-di-dong-pho-bien-nhat-the-gioi-55019 [3] http://androidtmc.blogspot.com/2015/08/android-tutorial-for-beginnerspart-1.html [4] https://xaiandroid.com/huong-dan-tao-may-ao-android-bang-genymotiontren-may-tinh.html [5].https://androidcoban.com/tong-quan-ve-firebase-google-phien-banmoi.html 73 ... hành android  Tìm hiểu mơi trường lập trình Android Studio  Tìm hiểu tảng hỗ trợ cho lập trình di động firebase  Tìm hiểu kỹ thuật lập trình ứng dụng chat android với firebase  Xây dựng ứng dụng. .. cho lập trình di động firebase Tìm hiểu kỹ thuật lập trình ứng dụng chat android với firebase Xây dựng chương trình chat điện thoại b Các yêu cầu cần giải Sử dụng công cụ Android Studio để viết... thiết bị android Tìm hiểu kiến thức tảng firebase Xây dựng ứng dụng chat android với firebase có chức năng: + Đăng ký, đăng nhập + Hiển thị danh sách tài khoản + Chỉnh sửa profile + Chat kênh

Ngày đăng: 27/08/2020, 18:32

Từ khóa liên quan

Tài liệu cùng người dùng

Tài liệu liên quan