movemeet/movemeet

View on GitHub
app/src/main/java/com/sdp/movemeet/view/chat/ChatActivity.java

Summary

Maintainability
A
1 hr
Test Coverage
B
87%
package com.sdp.movemeet.view.chat;

import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ImageButton;
import android.widget.MultiAutoCompleteTextView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import com.firebase.ui.database.FirebaseRecyclerAdapter;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;
import com.google.firebase.firestore.DocumentSnapshot;
import com.google.firebase.storage.FirebaseStorage;
import com.google.firebase.storage.StorageReference;
import com.google.firebase.storage.UploadTask;
import com.sdp.movemeet.R;
import com.sdp.movemeet.backend.BackendManager;
import com.sdp.movemeet.backend.firebase.firebaseDB.FirebaseDBMessageManager;
import com.sdp.movemeet.backend.firebase.firestore.FirestoreUserManager;
import com.sdp.movemeet.backend.firebase.storage.StorageImageManager;
import com.sdp.movemeet.backend.providers.AuthenticationInstanceProvider;
import com.sdp.movemeet.backend.providers.BackendInstanceProvider;
import com.sdp.movemeet.backend.serialization.MessageSerializer;
import com.sdp.movemeet.backend.serialization.UserSerializer;
import com.sdp.movemeet.models.Image;
import com.sdp.movemeet.models.Message;
import com.sdp.movemeet.models.User;
import com.sdp.movemeet.utility.ImageHandler;
import com.sdp.movemeet.view.activity.ActivityDescriptionActivity;
import com.sdp.movemeet.view.home.LoginActivity;
import com.sdp.movemeet.view.navigation.Navigation;

import java.util.Date;

public class ChatActivity extends AppCompatActivity {

    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
    public static boolean enableNav = true;

    private static final String TAG = "ChatActivity";

    public static final String CHATS_CHILD = "chats";
    public static String DEFAULT_CHAT_CHILD = "default_chat";
    public static String CHAT_ROOM_ID;

    private static final int REQUEST_IMAGE = 2;

    public static final String LOADING_IMAGE_URL = "https://www.google.com/images/spin-32.gif";

    public static final String noMessageText = "no messageText";
    public static final String noImageUrl = "no imageUrl";

    // Firebase instance variables
    private FirebaseAuth fAuth;
    private FirebaseStorage fStorage;
    private StorageReference storageReference;
    private BackendManager<Image> imageBackendManager;
    private FirebaseDatabase database;
    private DatabaseReference chatRef;
    private DatabaseReference chatRoom;
    private FirebaseRecyclerAdapter<Message, MessageViewHolder> firebaseAdapter;

    private BackendManager<Message> messageManager;
    private BackendManager<User> userManager;

    private User user;

    private String userId, fullNameString, activityChatId, receivedActivityChatId, receivedActivityTitle, imagePath;

    private int initialMessageCounter = 0;

    private MultiAutoCompleteTextView messageInput;
    private ProgressBar chatLoader;
    private RecyclerView messageRecyclerView;
    private ImageButton btnSend;

    private TextView initialChatWelcomeMessage;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_chat);

        fAuth = AuthenticationInstanceProvider.getAuthenticationInstance();
        // The aim is to block any direct access to this page if the user is not logged in
        if (fAuth.getCurrentUser() == null) {
            startActivity(new Intent(getApplicationContext(), LoginActivity.class));
            finish();
        } else {
            userId = fAuth.getCurrentUser().getUid();
        }

        if (enableNav) new Navigation(this, R.id.nav_home).createDrawer();

        // Initializing Firebase Realtime Database
        database = BackendInstanceProvider.getDatabaseInstance();
        chatRef = database.getReference().child(CHATS_CHILD); // "chats" node reference in Firebase Realtime Database

        messageInput = findViewById(R.id.message_input_text);
        chatLoader = findViewById(R.id.chat_loader);
        messageRecyclerView = findViewById(R.id.message_recycler_view);
        btnSend = findViewById(R.id.button_send_message);
        initialChatWelcomeMessage = findViewById(R.id.initial_chat_welcome_message);

        messageManager = new FirebaseDBMessageManager(new MessageSerializer());

        fAuth = AuthenticationInstanceProvider.getAuthenticationInstance();
        fStorage = BackendInstanceProvider.getStorageInstance();
        storageReference = fStorage.getReference();

        userManager = new FirestoreUserManager(FirestoreUserManager.USERS_COLLECTION, new UserSerializer());
        getRegisteredUserData();

        Intent data = getIntent();
        settingUpChatRoom(data);

        // The rest of the onCreate is dedicated to add all the existing messages and listening for
        // new child entries under the "chats" path of the sport activity in our Firebase
        // Realtime Database. A new element for each message is automatically added to the UI.

        addExistingMessagesAndListenForNewMessages();
    }


    private void addExistingMessagesAndListenForNewMessages() {
        // Using the MessageAdapter class to create the overall view of the chat room
        firebaseAdapter = new MessageAdapter(chatRoom, userId, ChatActivity.this);

        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
        linearLayoutManager.setStackFromEnd(true);
        messageRecyclerView.setLayoutManager(linearLayoutManager);
        messageRecyclerView.setAdapter(firebaseAdapter);

        // Scrolling down when a new message arrives from the database
        firebaseAdapter.registerAdapterDataObserver(
                new MyScrollToBottomObserver(messageRecyclerView, firebaseAdapter, linearLayoutManager)
        );
    }


    private void settingUpChatRoom(Intent data) {
        // Create a new chat room for the sport activity in case it deosn't yet exist
        receivedActivityChatId = data.getStringExtra(ActivityDescriptionActivity.ACTIVITY_CHAT_ID);
        receivedActivityTitle = data.getStringExtra(ActivityDescriptionActivity.ACTIVITY_TITLE);
        if (receivedActivityChatId != null) {
            activityChatId = receivedActivityChatId;
            // Dynamically creating a new child under the branch "chats" in Firebase Realtime
            // Database with the value of "activityChatId" in case it doesn't exist yet
            CHAT_ROOM_ID = activityChatId;
        } else {
            CHAT_ROOM_ID = DEFAULT_CHAT_CHILD; // default general chat room
        }
        chatRoom = chatRef.child(CHAT_ROOM_ID);
        countMessagesInChatRoom();
    }


    private void countMessagesInChatRoom() {
        // Count the number of messages (children) in the current chatRoom. In case the chat room
        // doesn't contain any message a welcome message is displayed
        chatRoom.addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                for (DataSnapshot childDataSnapshot : dataSnapshot.getChildren()) {
                    Log.v(TAG, "childDataSnapshot.getKey(): " + childDataSnapshot.getKey());
                    initialMessageCounter += 1;
                }
                Log.v(TAG, "initialMessageCounter: " + initialMessageCounter);
                if (initialMessageCounter == 0) {
                    chatLoader.setVisibility(View.INVISIBLE);
                    initialChatWelcomeMessage.setText("Welcome to the chat of " + receivedActivityTitle + "! Feel free to initiate the discussion by sending your first message!");
                    initialChatWelcomeMessage.setVisibility(View.VISIBLE);
                }
            }

            @Override
            public void onCancelled(DatabaseError databaseError) {
                Log.v(TAG, "databaseError: " + databaseError);
            }
        });
    }


    public void getRegisteredUserData() {
        Task<DocumentSnapshot> document = (Task<DocumentSnapshot>) userManager.get(FirestoreUserManager.USERS_COLLECTION + "/" + userId);
        document.addOnCompleteListener(task -> {
            if (task.isSuccessful()) {
                DocumentSnapshot document1 = task.getResult();
                if (document1.exists()) {
                    UserSerializer userSerializer = new UserSerializer();
                    user = userSerializer.deserialize(document1.getData());
                    fullNameString = user.getFullName();
                } else {
                    Log.d(TAG, "No such document!");
                }
            } else {
                Log.d(TAG, "Get failed with: ", task.getException());
            }
        });
    }


    @Override
    public void onPause() {
        // Stop listening for updates from Firebase Realtime Database
        firebaseAdapter.stopListening();
        super.onPause();
    }


    @Override
    public void onResume() {
        // Start listening for updates from Firebase Realtime Database
        super.onResume();
        firebaseAdapter.startListening();
    }


    public void sendMessage(View view) {
        String userName = fullNameString;
        String messageText = messageInput.getText().toString();
        Message message = new Message(userName, messageText, userId, noImageUrl, Long.toString(new Date().getTime()));
        if (messageText.length() > 0) {
            Log.d(TAG, "message.getImageUrl(): " + message.getImageUrl());
            messageManager.add(message, chatRoom.toString().split(ImageHandler.PATH_SEPARATOR, 4)[3]);
            messageInput.setText("");
        } else {
            Toast.makeText(getApplicationContext(), "Empty message.", Toast.LENGTH_SHORT).show();
        }
    }


    public void sendImage(View view) {
        Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        intent.setType("image/*");
        startActivityForResult(intent, REQUEST_IMAGE);
    }


    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_IMAGE) {
            if (resultCode == RESULT_OK && data != null) {
                final Uri uri = data.getData();
                Log.d(TAG, "Uri: " + uri.toString());
                createTempMessage(uri, fullNameString, userId);
            }
        }
    }


    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
    // Making this method always public for testing and private otherwise
    public void createTempMessage(Uri uri, String fullNameString, String userId) {
        Message tempMessage = new Message(fullNameString, "Image loading...", userId, LOADING_IMAGE_URL, Long.toString(new Date().getTime()));
        chatRoom.push().setValue(tempMessage, new DatabaseReference.CompletionListener() {
            @Override
            public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) {
                if (databaseError != null) {
                    Log.w(TAG, "Unable to write message to database.", databaseError.toException());
                    return;
                }
                String key = databaseReference.getKey();
                imageBackendManager = new StorageImageManager();
                imagePath = CHATS_CHILD + ImageHandler.PATH_SEPARATOR + CHAT_ROOM_ID + ImageHandler.PATH_SEPARATOR
                        + key + ImageHandler.PATH_SEPARATOR + ImageHandler.CHAT_IMAGE_NAME;
                Image image = new Image(uri, null);
                UploadTask uploadTask = (UploadTask) imageBackendManager.add(image, imagePath);
                putImageInStorage(uploadTask, key);
            }
        });
    }


    private void putImageInStorage(UploadTask uploadTask, String key) {
        // Upload the image to Firebase Storage
        uploadTask.addOnSuccessListener(ChatActivity.this, new OnSuccessListener<UploadTask.TaskSnapshot>() {
            @Override
            public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
                // After the image loads, get a URI for the image
                // and add it to the message.
                taskSnapshot.getMetadata().getReference().getDownloadUrl()
                        .addOnSuccessListener(uri -> {
                            Message imageMessage = new Message(fullNameString, noMessageText, userId, uri.toString(), Long.toString(new Date().getTime()));
                            messageManager.set(imageMessage, chatRoom.toString().split(ImageHandler.PATH_SEPARATOR, 4)[3] + ImageHandler.PATH_SEPARATOR + key);
                        });
            }
        }).addOnFailureListener(this, e -> Log.w(TAG, "Image upload task was not successful.", e));

    }

}