From 3f699f574249b3a8a4bd93eff1d37262a17e1455 Mon Sep 17 00:00:00 2001 From: Mark Moussa Date: Tue, 17 Apr 2018 21:11:55 -0400 Subject: [PATCH 1/6] Changed Store messages type from Message to Pair in order to pass along value on whether the message was sent from host or from other user --- .../ConversationListActivity.kt | 2 +- .../ConversationListAdapter.kt | 2 +- .../meshchatapplication/HypeLifeCycle.kt | 6 +-- .../MessageListActivity.kt | 39 +++++++++++------- .../meshchatapplication/MessageListAdapter.kt | 29 +++++++------ .../meshchatapplication/NewMessageActivity.kt | 4 +- .../markmoussa/meshchatapplication/Store.kt | 10 ++--- .../markmoussa/meshchatapplication/User.kt | 8 ++-- .../main/res/drawable/default_user_image.png | Bin 0 -> 5929 bytes .../main/res/layout/item_message_received.xml | 9 ++-- 10 files changed, 56 insertions(+), 53 deletions(-) create mode 100644 app/src/main/res/drawable/default_user_image.png diff --git a/app/src/main/java/com/example/markmoussa/meshchatapplication/ConversationListActivity.kt b/app/src/main/java/com/example/markmoussa/meshchatapplication/ConversationListActivity.kt index a1aac6a..49d0b6e 100644 --- a/app/src/main/java/com/example/markmoussa/meshchatapplication/ConversationListActivity.kt +++ b/app/src/main/java/com/example/markmoussa/meshchatapplication/ConversationListActivity.kt @@ -142,7 +142,7 @@ class ConversationListActivity : AppCompatActivity(), Store.Delegate, LifecycleO // TODO: Add option to delete conversation - override fun onMessageAdded(store: Store, message: Message) { + override fun onMessageAdded(store: Store, message: Pair) { updateInterface() } diff --git a/app/src/main/java/com/example/markmoussa/meshchatapplication/ConversationListAdapter.kt b/app/src/main/java/com/example/markmoussa/meshchatapplication/ConversationListAdapter.kt index 9a7c8c2..f8d4877 100644 --- a/app/src/main/java/com/example/markmoussa/meshchatapplication/ConversationListAdapter.kt +++ b/app/src/main/java/com/example/markmoussa/meshchatapplication/ConversationListAdapter.kt @@ -66,7 +66,7 @@ class ConversationListAdapter(private val mContext: Context, private val mConver //timeText.text = DateUtils.formatDateTime(message.getCreatedAt(), HOUR_IN_MILLIS, FORMAT_SHOW_TIME) timestampText.text = Date().toString() // TODO: Figure out how to pull profile pic, or if not, then what profile pic should be (if there should be one at all) - if(conversation.user.profileUrl != null) { + if(conversation.user.profileUri != null) { // do nothing } val hypeFramework = mContext.applicationContext as HypeLifeCycle diff --git a/app/src/main/java/com/example/markmoussa/meshchatapplication/HypeLifeCycle.kt b/app/src/main/java/com/example/markmoussa/meshchatapplication/HypeLifeCycle.kt index f3ffef2..c1480b2 100644 --- a/app/src/main/java/com/example/markmoussa/meshchatapplication/HypeLifeCycle.kt +++ b/app/src/main/java/com/example/markmoussa/meshchatapplication/HypeLifeCycle.kt @@ -147,7 +147,7 @@ class HypeLifeCycle : StateObserver, NetworkObserver, MessageObserver, Applicati store = Store(instance) } // Storing the message triggers a reload update in the MessageList activity - store.add(message, this) + store.add(Pair(message, false), this) setMessageDatabase(instance.userIdentifier, store) } @@ -231,7 +231,7 @@ class HypeLifeCycle : StateObserver, NetworkObserver, MessageObserver, Applicati Log.d("HypeLifeCycle ", "new messageDatabase (from file) is: ${messageDatabase.entries.toString()}") for(x in messageDatabase.values) { for(y in x.getMessages()) { - Log.d("HypeLifeCycle", "Store contents (from new messageDatabase (from file)): ${y.data.toString(charset("UTF-8"))}") + Log.d("HypeLifeCycle", "Store contents (from new messageDatabase (from file)): ${y.first.data.toString(charset("UTF-8"))}") } } } @@ -291,7 +291,7 @@ class HypeLifeCycle : StateObserver, NetworkObserver, MessageObserver, Applicati Log.d("HypeLifeCycle ", "reading messageDatabase (from file) is: ${result.entries.toString()}") for(x in result.values) { for(y in x.getMessages()) { - Log.d("HypeLifeCycle", "Store contents (from reading messageDatabase (from file)): ${y.data.toString(charset("UTF-8"))}") + Log.d("HypeLifeCycle", "Store contents (from reading messageDatabase (from file)): ${y.first.data.toString(charset("UTF-8"))}") } } return result diff --git a/app/src/main/java/com/example/markmoussa/meshchatapplication/MessageListActivity.kt b/app/src/main/java/com/example/markmoussa/meshchatapplication/MessageListActivity.kt index d57542d..8f77e20 100644 --- a/app/src/main/java/com/example/markmoussa/meshchatapplication/MessageListActivity.kt +++ b/app/src/main/java/com/example/markmoussa/meshchatapplication/MessageListActivity.kt @@ -18,29 +18,36 @@ import java.io.UnsupportedEncodingException class MessageListActivity : AppCompatActivity(), Store.Delegate { - private var mMessageList: MutableList = mutableListOf() + private var mMessageList: MutableList> = mutableListOf() private lateinit var mMessageAdapter: MessageListAdapter override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_message_list) - mMessageAdapter = MessageListAdapter(this, mMessageList) - var mMessageRecycler: RecyclerView? = null - mMessageRecycler = findViewById(R.id.recyclerview_message_list) as RecyclerView - mMessageRecycler.layoutManager = LinearLayoutManager(this) - mMessageRecycler.adapter = mMessageAdapter - // getting the messages - notifyMessageListChanged() val hypeFramework = applicationContext as HypeLifeCycle val userIdentifier = intent.getLongExtra("userIdentifier", 0) - // Setting actionbar with name of user + val contactsList = hypeFramework.getAllContacts() + // Setting profileUri to default user image in case it doesn't exist + // TODO: This way of setting the default profile pic might not be the best but it works; look into making more efficient later + var profileUri: String? = "/Users/markmoussa/AndroidStudioProjects/MeshChatApplication/app/src/main/res/drawable/default_user_image.png" + // Setting actionbar with name of user and getting profile pic path if(userIdentifier != 0.toLong()) { - if(userIdentifier in hypeFramework.getAllContacts()) { + if(userIdentifier in contactsList) { val actionBar = supportActionBar - actionBar!!.title = hypeFramework.getAllContacts()[userIdentifier]!!.nickname + actionBar!!.title = contactsList[userIdentifier]!!.nickname + + profileUri = contactsList[userIdentifier]?.profileUri } } + mMessageAdapter = MessageListAdapter(this, mMessageList, profileUri) + var mMessageRecycler: RecyclerView? = null + mMessageRecycler = findViewById(R.id.recyclerview_message_list) + mMessageRecycler.layoutManager = LinearLayoutManager(this) + mMessageRecycler.adapter = mMessageAdapter + // getting the messages + notifyMessageListChanged() + // hackish fix at the messages not appearing when notifyDataSetChanged() called. Need to fix later // also need to fix same thing for ConversationListActivity @@ -63,7 +70,7 @@ class MessageListActivity : AppCompatActivity(), Store.Delegate { // debugging Log.d("MessageListActivity ", "Store is this: ") for(x in store.getMessages()) { - Log.i("DBEUG", x.data.toString()) + Log.i("DBEUG", x.first.data.toString()) } store.delegate = this store.lastReadIndex = store.getMessages().size @@ -86,11 +93,11 @@ class MessageListActivity : AppCompatActivity(), Store.Delegate { Log.v(this@MessageListActivity::class.simpleName, "Send Message") val message = sendMessage(text, store.instance) chatBox.setText("") - store.add(message, this) + store.add(Pair(message, true), this) // debugging Log.d("MessageListActivity ", "Updated store is this: ") for(x in store.getMessages()) { - Log.d("MessageListActivity", x.data.toString(charset("UTF-8"))) + Log.d("MessageListActivity", x.first.data.toString(charset("UTF-8"))) } } catch(e: UnsupportedEncodingException) { e.printStackTrace() @@ -107,7 +114,7 @@ class MessageListActivity : AppCompatActivity(), Store.Delegate { Log.d("MessageListActivity: ", "populateMessageList() returned: ") if(!(mMessageList.isEmpty())) { for(x in mMessageList) { - Log.d("MessageListActivity", "message: ${x.data.toString(charset("UTF-8"))}") + Log.d("MessageListActivity", "message: ${x.first.data.toString(charset("UTF-8"))}") } } else { Log.d("MessageListActivity ", "Message List is empty at the moment") @@ -136,7 +143,7 @@ class MessageListActivity : AppCompatActivity(), Store.Delegate { return Hype.send(data, instance, true) } - override fun onMessageAdded(store: Store, message: Message) { + override fun onMessageAdded(store: Store, message: Pair) { Log.v("ONMESSAGEADDED: ", "onMessageAdded called in MessageListActivity") notifyMessageListChanged() } diff --git a/app/src/main/java/com/example/markmoussa/meshchatapplication/MessageListAdapter.kt b/app/src/main/java/com/example/markmoussa/meshchatapplication/MessageListAdapter.kt index 17cedee..334b3f7 100644 --- a/app/src/main/java/com/example/markmoussa/meshchatapplication/MessageListAdapter.kt +++ b/app/src/main/java/com/example/markmoussa/meshchatapplication/MessageListAdapter.kt @@ -5,6 +5,7 @@ package com.example.markmoussa.meshchatapplication */ import android.content.Context +import android.graphics.BitmapFactory import android.support.v7.widget.RecyclerView import android.text.format.DateUtils import android.text.format.DateUtils.* @@ -20,7 +21,7 @@ import java.io.ByteArrayInputStream // BaseMessage is specific to SendBird, fix later -class MessageListAdapter(private val mContext: Context, private val mMessageList: List) : RecyclerView.Adapter() { +class MessageListAdapter(private val mContext: Context, private val mMessageList: List>, private val profileUri: String?) : RecyclerView.Adapter() { override fun getItemCount(): Int { return mMessageList.size @@ -39,12 +40,7 @@ class MessageListAdapter(private val mContext: Context, private val mMessageList // SendBird function // message.getSender().getUserId().equals(SendBird.getCurrentUser().getUserId()) - // TODO: Create logic for this -// val hypeFramework = mContext.applicationContext as HypeLifeCycle -// if(message.identifier.equals()) { -// -// } - return if (true) { + return if (message.second) { // If the current user is the sender of the message VIEW_TYPE_MESSAGE_SENT } else { @@ -79,14 +75,12 @@ class MessageListAdapter(private val mContext: Context, private val mMessageList val message = mMessageList[position] when (holder.itemViewType) { - VIEW_TYPE_MESSAGE_SENT -> (holder as SentMessageHolder).bind(message) - VIEW_TYPE_MESSAGE_RECEIVED -> (holder as ReceivedMessageHolder).bind(message) + VIEW_TYPE_MESSAGE_SENT -> (holder as SentMessageHolder).bind(message.first) + VIEW_TYPE_MESSAGE_RECEIVED -> (holder as ReceivedMessageHolder).bind(message.first) } } private inner class SentMessageHolder internal constructor(itemView: View) : RecyclerView.ViewHolder(itemView) { - // debugging - // internal var messageText: TextView = itemView.findViewById(R.id.text_message_body) internal var messageText: BubbleTextView = itemView.findViewById(R.id.bubbleTextView) internal var timeText: TextView = itemView.findViewById(R.id.text_message_time) @@ -112,8 +106,13 @@ class MessageListAdapter(private val mContext: Context, private val mMessageList // UserMessage is specific to SendBird, fix later internal fun bind(message: Message) { - messageText.text = message.data.toString() + messageText.text = message.data.toString(charset("UTF-8")) Log.i("DEBUG ", "Message text from adapter: ${messageText.text}") + if(profileUri == null) { + profileImage.setImageResource(R.drawable.default_user_image) + } else { + profileImage.setImageBitmap(BitmapFactory.decodeFile(profileUri)) + } // Format the stored timestamp into a readable String using method. // TODO: Replace HOUR_IN_MILLIS and FORMAT_SHOW_TIME to get metadata from Hype Messages @@ -124,12 +123,12 @@ class MessageListAdapter(private val mContext: Context, private val mMessageList // TODO: Figure out how to do this without using SendBird // Insert the profile image from the URL into the ImageView. - // ImageUtils.displayRoundImageFromUrl(mContext, message.getSender().getProfileUrl(), profileImage) + // ImageUtils.displayRoundImageFromUrl(mContext, message.getSender().getProfileUri(), profileImage) } } companion object { - private val VIEW_TYPE_MESSAGE_SENT = 1 - private val VIEW_TYPE_MESSAGE_RECEIVED = 2 + private const val VIEW_TYPE_MESSAGE_SENT = 1 + private const val VIEW_TYPE_MESSAGE_RECEIVED = 2 } } \ No newline at end of file diff --git a/app/src/main/java/com/example/markmoussa/meshchatapplication/NewMessageActivity.kt b/app/src/main/java/com/example/markmoussa/meshchatapplication/NewMessageActivity.kt index 83e5572..6522226 100644 --- a/app/src/main/java/com/example/markmoussa/meshchatapplication/NewMessageActivity.kt +++ b/app/src/main/java/com/example/markmoussa/meshchatapplication/NewMessageActivity.kt @@ -34,7 +34,7 @@ class NewMessageActivity : AppCompatActivity(), Store.Delegate { Log.v(this@NewMessageActivity::class.simpleName, "Send Message") val message = sendMessage(text, store.instance) chatBox.setText("") - store.add(message, this) + store.add(Pair(message, true), this) } catch(e: UnsupportedEncodingException) { e.printStackTrace() } @@ -57,7 +57,7 @@ class NewMessageActivity : AppCompatActivity(), Store.Delegate { return Hype.send(data, instance, false) } - override fun onMessageAdded(store: Store, message: Message) { + override fun onMessageAdded(store: Store, message: Pair) { this.runOnUiThread { val recyclerView = findViewById(R.id.recyclerview_message_list) as RecyclerView diff --git a/app/src/main/java/com/example/markmoussa/meshchatapplication/Store.kt b/app/src/main/java/com/example/markmoussa/meshchatapplication/Store.kt index b883c56..6253c17 100644 --- a/app/src/main/java/com/example/markmoussa/meshchatapplication/Store.kt +++ b/app/src/main/java/com/example/markmoussa/meshchatapplication/Store.kt @@ -13,7 +13,7 @@ import java.lang.ref.WeakReference import java.util.Vector class Store(val instance: Instance): Serializable { - private var messages: Vector = Vector() + private var messages: Vector> = Vector() var lastReadIndex: Int = 0 private var delegateWeakReference: WeakReference? = null @@ -26,7 +26,7 @@ class Store(val instance: Instance): Serializable { interface Delegate { - fun onMessageAdded(store: Store, message: Message) + fun onMessageAdded(store: Store, message: Pair) } init { @@ -35,7 +35,7 @@ class Store(val instance: Instance): Serializable { // need the context in order to be able to access setAllOnlinePeers function which lives in HypeLifeCycle // because Stores is a singleton class - fun add(message: Message, context: Context) { + fun add(message: Pair, context: Context) { getMessages().add(message) val hypeFramework = context.applicationContext as HypeLifeCycle @@ -46,7 +46,7 @@ class Store(val instance: Instance): Serializable { } - fun getMessages(): Vector { + fun getMessages(): Vector> { return messages } @@ -65,7 +65,7 @@ class Store(val instance: Instance): Serializable { } fun getMessageAtIndex(index: Int): Message { - return messages[index] + return messages[index].first } // TODO: write a function to update the stores value's last read index (here and in HypeLifeCycle) diff --git a/app/src/main/java/com/example/markmoussa/meshchatapplication/User.kt b/app/src/main/java/com/example/markmoussa/meshchatapplication/User.kt index d846c39..b3061d2 100644 --- a/app/src/main/java/com/example/markmoussa/meshchatapplication/User.kt +++ b/app/src/main/java/com/example/markmoussa/meshchatapplication/User.kt @@ -1,8 +1,6 @@ package com.example.markmoussa.meshchatapplication import android.graphics.Bitmap -import com.hypelabs.hype.Instance -import org.json.JSONObject import java.io.ByteArrayOutputStream import java.io.ObjectOutputStream import java.io.Serializable @@ -12,15 +10,15 @@ import java.io.Serializable // link: https://github.com/Hype-Labs/HypeChatDemo.android // This version with a constructor used simply so we can generate dummy data to populate conversations -data class User(val nickname: String?, val profileUrl: String?, val userIdentifier: Long?): Serializable { +data class User(val nickname: String?, val profileUri: String?, val userIdentifier: Long?): Serializable { private var profilePicBitmap: Bitmap? = null // This secondary constructor is used when we need to send the profilePic itself to another user to be able to store it - constructor(nickname: String?, profileUrl: String?, userIdentifier: Long?, profilePicBitmap: Bitmap?) : this(nickname, profileUrl, userIdentifier) { + constructor(nickname: String?, profileUri: String?, userIdentifier: Long?, profilePicBitmap: Bitmap?) : this(nickname, profileUri, userIdentifier) { this.profilePicBitmap = profilePicBitmap } override fun toString(): String { - return "{ nickname: $nickname; profileUrl: $profileUrl; userIdentifier: $userIdentifier; profilePicBitmap: $profilePicBitmap }" + return "{ nickname: $nickname; profileUri: $profileUri; userIdentifier: $userIdentifier; profilePicBitmap: $profilePicBitmap }" } fun serializeUser(): ByteArray { diff --git a/app/src/main/res/drawable/default_user_image.png b/app/src/main/res/drawable/default_user_image.png new file mode 100644 index 0000000000000000000000000000000000000000..c4ee4c66d692b7bf2fbfc45082ad2724e732031d GIT binary patch literal 5929 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/item_message_received.xml b/app/src/main/res/layout/item_message_received.xml index bf647fb..6733e8e 100644 --- a/app/src/main/res/layout/item_message_received.xml +++ b/app/src/main/res/layout/item_message_received.xml @@ -7,14 +7,13 @@ android:layout_height="wrap_content" android:paddingTop="8dp"> - + + + @@ -73,6 +72,6 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/image_message_profile" app:layout_constraintTop_toTopOf="@+id/text_message_time" - app:layout_constraintVertical_bias="0.282" /> + app:layout_constraintVertical_bias="0.512" /> \ No newline at end of file From a39e3bf9e2a7b9dfac7e223e513eec07037a8171 Mon Sep 17 00:00:00 2001 From: Mark Moussa Date: Wed, 18 Apr 2018 16:21:44 -0400 Subject: [PATCH 2/6] in the middle of fixing messages not saving bug --- .idea/caches/build_file_checksums.ser | Bin 586 -> 586 bytes app/build.gradle | 3 + .../ConversationListActivity.kt | 13 +-- .../meshchatapplication/HypeLifeCycle.kt | 90 +++++++++++++----- .../MessageListActivity.kt | 5 + .../meshchatapplication/MessageListAdapter.kt | 1 - .../markmoussa/meshchatapplication/Store.kt | 5 +- .../res/drawable/rounded_rectangle_orange.xml | 25 ----- .../res/drawable/rounded_rectangle_purple.xml | 25 ----- .../main/res/layout/item_message_received.xml | 25 +---- app/src/main/res/layout/item_message_sent.xml | 16 +--- 11 files changed, 87 insertions(+), 121 deletions(-) delete mode 100644 app/src/main/res/drawable/rounded_rectangle_orange.xml delete mode 100644 app/src/main/res/drawable/rounded_rectangle_purple.xml diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser index c9ab7caaaf26c65fae38d602b4281d409a187cec..49eff94a7b63dea3b3c4ac86239e343f43dc92da 100644 GIT binary patch delta 33 rcmV++0N($~1j+=Em;}~Z7J#vwodFT9^);8f-+{W!^8Q by Delegates.observable(mutableListOf()) { _, _, _ -> updateOnlinePeersFile() } + // The messageDatabase keeps track of the users and their previous messages via saving their userIdentifiers and a Store private var messageDatabase: HashMap by Delegates.observable(hashMapOf()) { _, _, _ -> updateMessageDatabase() } + // contactsDatabase saves previously before seen users to save sending data back and forth via their userIdentifiers and User objects private var contactsDatabase: HashMap by Delegates.observable(hashMapOf()) { _, _, _ -> updateContactsDatabase() } @@ -72,8 +70,9 @@ class HypeLifeCycle : StateObserver, NetworkObserver, MessageObserver, Applicati override fun onHypeStart() { Log.i(TAG, "Hype started!") Log.i(TAG, "Loading store from file") - readOnlinePeers() - readMessageDatabase() + onlinePeers = readOnlinePeers() + messageDatabase = readMessageDatabase() + contactsDatabase = readContactsDatabase() } override fun onHypeStop(error: Error?) { @@ -189,8 +188,9 @@ class HypeLifeCycle : StateObserver, NetworkObserver, MessageObserver, Applicati // setting onlinePeers to read file here instead of at the top because readOnlinePeers() needs // dirPath in order to proceed, and dirPath is still null when onlinePeers is instantiated - onlinePeers = readOnlinePeers() - messageDatabase = readMessageDatabase() +// onlinePeers = readOnlinePeers() +// messageDatabase = readMessageDatabase() +// contactsDatabase = readContactsDatabase() } fun getAllOnlinePeers(): MutableList { @@ -228,22 +228,30 @@ class HypeLifeCycle : StateObserver, NetworkObserver, MessageObserver, Applicati // TODO: Figure out why and delete manual call once fixed updateMessageDatabase() // debugging - Log.d("HypeLifeCycle ", "new messageDatabase (from file) is: ${messageDatabase.entries.toString()}") + Log.d("HypeLifeCycle ", "new messageDatabase (from variable) is: ${messageDatabase.entries.toString()}") for(x in messageDatabase.values) { for(y in x.getMessages()) { - Log.d("HypeLifeCycle", "Store contents (from new messageDatabase (from file)): ${y.first.data.toString(charset("UTF-8"))}") + Log.d("HypeLifeCycle", "Store contents (from new messageDatabase (from variable)): ${y.first.data.toString(charset("UTF-8"))}") } } } // adds contact to contactsDatabase and triggers updating the contacts file in memory fun setContactsDatabase(userIdentifier: Long, user: User) { - contactsDatabase[userIdentifier] = user + // TODO: Hackish fix since I can't send over userIdentifier without the bytes going over its limit + // Fix once Hype's SDK allows for greater than 255 bytes sent on announcement + val newUser = User(user.nickname, user.profileUri, userIdentifier) + contactsDatabase[userIdentifier] = newUser + + // TODO: For some reason, observable delegate not calling updateContactsDatabase() when message database is changed + // TODO: Figure out why and delete manual call once fixed + updateContactsDatabase() } private fun readOnlinePeers(): MutableList { val storeFile = File(dirPath, "storeFile") if(!(storeFile.exists()) || storeFile.length() == 0.toLong()) { + Log.d("HypeLifeCycle", "File for online peers does not exist; creating new one (from read)") storeFile.createNewFile() return mutableListOf() } else { @@ -266,6 +274,7 @@ class HypeLifeCycle : StateObserver, NetworkObserver, MessageObserver, Applicati val storeFile = File(dirPath, "storeFile") if(!(storeFile.exists())) { storeFile.createNewFile() + Log.d("HypeLifeCycle", "File for online peers does not exist; creating new one (from update)") } val fos = FileOutputStream(storeFile) val oos = ObjectOutputStream(fos) @@ -276,19 +285,35 @@ class HypeLifeCycle : StateObserver, NetworkObserver, MessageObserver, Applicati } } + inline fun Gson.fromJson(json: String) = this.fromJson(json, object: TypeToken() {}.type) private fun readMessageDatabase(): HashMap { val messageDatabaseFile = File(dirPath, "messageDatabase") if(!(messageDatabaseFile.exists()) || messageDatabaseFile.length() == 0.toLong()) { messageDatabaseFile.createNewFile() + if(!(messageDatabaseFile.exists())) { + Log.d("HypeLifeCycle", "File for message database does not exist; creating new one (from read)") + } else { + Log.d("HypeLifeCycle", "messageDatabaseFile.length() == 0") + } return hashMapOf() } else { try { +// val fis = FileInputStream(messageDatabaseFile) +// val ois = ObjectInputStream(fis) +// val result = ois.readObject() as HashMap +// ois.close() + + // Using GSON library instead of ObjectOutputStream for now because Instance (found in Store) is not Serializable at the moment val fis = FileInputStream(messageDatabaseFile) val ois = ObjectInputStream(fis) - val result = ois.readObject() as HashMap + val jsonHashMapResult = ois.readObject().toString() ois.close() // debugging + Log.d("HypeLifeCycle", "jsonHashMapResult: $jsonHashMapResult") + val result: HashMap = Gson().fromJson>(jsonHashMapResult) + // debugging Log.d("HypeLifeCycle ", "reading messageDatabase (from file) is: ${result.entries.toString()}") + for(x in result.values) { for(y in x.getMessages()) { Log.d("HypeLifeCycle", "Store contents (from reading messageDatabase (from file)): ${y.first.data.toString(charset("UTF-8"))}") @@ -310,16 +335,32 @@ class HypeLifeCycle : StateObserver, NetworkObserver, MessageObserver, Applicati val messageDatabaseFile = File(dirPath, "messageDatabase") if(!(messageDatabaseFile.exists())) { messageDatabaseFile.createNewFile() + Log.d("HypeLifeCycle", "File for message database does not exist; creating new one (from update)") } // TODO: DO THIS FIRST - figure out how to serialize messageDatabase // TODO: since Instance from Hype SDK not serializable, it won't let me serialize all of messageDatabase - // by that logic, I should probably check up on contactsDatabase as well +// val fos = FileOutputStream(messageDatabaseFile) +// val oos = ObjectOutputStream(fos) +// oos.writeObject(messageDatabase) +// // debugging +// Log.d("HypeLifeCycle", "Right after writing the file, the new file is: ${readMessageDatabase()}") +// oos.close() + + // Using GSON library instead of ObjectOutputStream for now because Instance (found in Store) is not Serializable at the moment val fos = FileOutputStream(messageDatabaseFile) + val gsonSerializer = Gson().toJson(messageDatabase) val oos = ObjectOutputStream(fos) - oos.writeObject(messageDatabase) + oos.writeObject(gsonSerializer) // debugging - Log.d("HypeLifeCycle", "Right after writing the file, the new file is: ${readMessageDatabase()}") + Log.d("HypeLifeCycle", "Right after writing the messageDatabase file, the new file is: ${readMessageDatabase()}") + + // Checking if the file is empty, meaning the messageDatabase didn't write properly + // debugging + if(messageDatabaseFile.length() == 0.toLong()) { + Log.d("HypeLifeCycle", "The messageDatabaseFile is still empty after writing; this means there's a problem in updateMessageDatabase()") + } oos.close() + } catch(e: Exception) { e.printStackTrace() } @@ -329,6 +370,7 @@ class HypeLifeCycle : StateObserver, NetworkObserver, MessageObserver, Applicati val contactsFile = File(dirPath, "contactsFile") if(!(contactsFile.exists()) || contactsFile.length() == 0.toLong()) { contactsFile.createNewFile() + Log.d("HypeLifeCycle", "File for contacts database does not exist; creating new one (from read)") return hashMapOf() } else { try { @@ -350,10 +392,12 @@ class HypeLifeCycle : StateObserver, NetworkObserver, MessageObserver, Applicati val contactsFile = File(dirPath, "contactsFile") if(!(contactsFile.exists())) { contactsFile.createNewFile() + Log.d("HypeLifeCycle", "File for contacts database does not exist; creating new one (from update)") } val fos = FileOutputStream(contactsFile) val oos = ObjectOutputStream(fos) oos.writeObject(contactsDatabase) + Log.d("HypeLifeCycle", "Right after writing the contacts file, the new file is: ${readContactsDatabase()}") oos.close() } catch(e: Exception) { @@ -368,8 +412,9 @@ class HypeLifeCycle : StateObserver, NetworkObserver, MessageObserver, Applicati setAllOnlinePeers(instance.userIdentifier, true) Log.d("HypeLifeCycle ", "New onlinePeers: " + getAllOnlinePeers().toString()) if(getAllMessages()[instance.userIdentifier] == null) { + Log.d("HypeLifeCycle", "Could not find userIdentifier in getAllMessages() (aka messageDatabase), therefore starting a brand new Store") setMessageDatabase(instance.userIdentifier, Store(instance)) - Log.d("HypeLifeCycle ", "New messageDatabase: " + getAllMessages().toString()) + Log.d("HypeLifeCycle ", "New messageDatabase (from variable): " + getAllMessages().toString()) } if(!(readContactsDatabase().containsKey(instance.userIdentifier))) { // restoring User object from serialized byteArray @@ -380,11 +425,10 @@ class HypeLifeCycle : StateObserver, NetworkObserver, MessageObserver, Applicati // and adding the userIdentifier exceeds the limit) Log.d("HypeLifeCycle ", "NEWUSER OBJECT: ${newUser.toString()}") setContactsDatabase(instance.userIdentifier, newUser) + } else { + Log.d("HypeLifeCycle", "User recognized in ContactsDatabase, no need to set anything new") } -// // Notify the conversationList activity to refresh the UI -// val conversationListActivity = ConversationListActivity() -// conversationListActivity.notifyOnlinePeersChanged() } fun removeFromResolvedInstancesMap(instance: Instance) { diff --git a/app/src/main/java/com/example/markmoussa/meshchatapplication/MessageListActivity.kt b/app/src/main/java/com/example/markmoussa/meshchatapplication/MessageListActivity.kt index 8f77e20..564640f 100644 --- a/app/src/main/java/com/example/markmoussa/meshchatapplication/MessageListActivity.kt +++ b/app/src/main/java/com/example/markmoussa/meshchatapplication/MessageListActivity.kt @@ -160,6 +160,11 @@ class MessageListActivity : AppCompatActivity(), Store.Delegate { private fun getStore(): Store { val hypeFramework = applicationContext as HypeLifeCycle val userIdentifier = intent.getLongExtra("userIdentifier", 0) + Log.d("MessageListActivity", "Getting the store for specific user in messageListActivity's getStore() returns this: ${hypeFramework.getAllMessages()[userIdentifier]!!}") + Log.d("MessageListActivity", "The messages within the store for the aforementioned thing is: ") + for(x in hypeFramework.getAllMessages()[userIdentifier]!!.getMessages()) { + Log.d("MessageListActivity", x.first.data.toString(charset("UTF-8"))) + } return hypeFramework.getAllMessages()[userIdentifier]!! } diff --git a/app/src/main/java/com/example/markmoussa/meshchatapplication/MessageListAdapter.kt b/app/src/main/java/com/example/markmoussa/meshchatapplication/MessageListAdapter.kt index 334b3f7..f2824d3 100644 --- a/app/src/main/java/com/example/markmoussa/meshchatapplication/MessageListAdapter.kt +++ b/app/src/main/java/com/example/markmoussa/meshchatapplication/MessageListAdapter.kt @@ -99,7 +99,6 @@ class MessageListAdapter(private val mContext: Context, private val mMessageList internal var messageText: BubbleTextView = itemView.findViewById(R.id.bubbleTextView) internal var timeText: TextView = itemView.findViewById(R.id.text_message_time) - internal var nameText: TextView = itemView.findViewById(R.id.text_message_name) internal var profileImage: ImageView = itemView.findViewById(R.id.image_message_profile) as ImageView diff --git a/app/src/main/java/com/example/markmoussa/meshchatapplication/Store.kt b/app/src/main/java/com/example/markmoussa/meshchatapplication/Store.kt index 6253c17..f640492 100644 --- a/app/src/main/java/com/example/markmoussa/meshchatapplication/Store.kt +++ b/app/src/main/java/com/example/markmoussa/meshchatapplication/Store.kt @@ -13,6 +13,8 @@ import java.lang.ref.WeakReference import java.util.Vector class Store(val instance: Instance): Serializable { + // The Boolean in the Pair represents whether that message was sent from host or user + // true = sent from host (aka your message) false = sent from other user (the one you're chatting with) private var messages: Vector> = Vector() var lastReadIndex: Int = 0 private var delegateWeakReference: WeakReference? = null @@ -36,7 +38,6 @@ class Store(val instance: Instance): Serializable { // need the context in order to be able to access setAllOnlinePeers function which lives in HypeLifeCycle // because Stores is a singleton class fun add(message: Pair, context: Context) { - getMessages().add(message) val hypeFramework = context.applicationContext as HypeLifeCycle hypeFramework.setMessageDatabase(instance.userIdentifier, this) @@ -60,7 +61,7 @@ class Store(val instance: Instance): Serializable { // this means there's no message at this index and therefore need to return null return null } - return messages[index].toString() + return messages[index].first.toString() } diff --git a/app/src/main/res/drawable/rounded_rectangle_orange.xml b/app/src/main/res/drawable/rounded_rectangle_orange.xml deleted file mode 100644 index 648e585..0000000 --- a/app/src/main/res/drawable/rounded_rectangle_orange.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/rounded_rectangle_purple.xml b/app/src/main/res/drawable/rounded_rectangle_purple.xml deleted file mode 100644 index a54c333..0000000 --- a/app/src/main/res/drawable/rounded_rectangle_purple.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/item_message_received.xml b/app/src/main/res/layout/item_message_received.xml index 6733e8e..39bcdfd 100644 --- a/app/src/main/res/layout/item_message_received.xml +++ b/app/src/main/res/layout/item_message_received.xml @@ -18,29 +18,6 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> - - - - diff --git a/app/src/main/res/layout/item_message_sent.xml b/app/src/main/res/layout/item_message_sent.xml index 136f091..d860c70 100644 --- a/app/src/main/res/layout/item_message_sent.xml +++ b/app/src/main/res/layout/item_message_sent.xml @@ -7,20 +7,6 @@ android:layout_height="wrap_content" android:paddingTop="8dp"> - - Date: Wed, 18 Apr 2018 21:01:27 -0400 Subject: [PATCH 3/6] Updated store and onlinePeers and fixed messageDatabase --- app/src/main/AndroidManifest.xml | 1 - .../meshchatapplication/Conversation.kt | 3 +- .../ConversationListActivity.kt | 77 ++++---- .../ConversationListAdapter.kt | 2 +- .../meshchatapplication/HypeLifeCycle.kt | 171 +++++++++--------- .../LifecycleObserverActivity.kt | 10 +- .../MessageListActivity.kt | 44 +++-- .../meshchatapplication/MessageListAdapter.kt | 10 +- .../meshchatapplication/NewMessageActivity.kt | 102 ----------- .../markmoussa/meshchatapplication/Store.kt | 27 ++- .../main/res/layout/activity_new_message.xml | 44 ----- app/src/main/res/layout/item_conversation.xml | 1 + app/src/main/res/layout/item_message_sent.xml | 3 +- 13 files changed, 197 insertions(+), 298 deletions(-) delete mode 100644 app/src/main/java/com/example/markmoussa/meshchatapplication/NewMessageActivity.kt delete mode 100644 app/src/main/res/layout/activity_new_message.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7e4e2a8..1106d15 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -43,7 +43,6 @@ - \ No newline at end of file diff --git a/app/src/main/java/com/example/markmoussa/meshchatapplication/Conversation.kt b/app/src/main/java/com/example/markmoussa/meshchatapplication/Conversation.kt index c54383f..9393e67 100644 --- a/app/src/main/java/com/example/markmoussa/meshchatapplication/Conversation.kt +++ b/app/src/main/java/com/example/markmoussa/meshchatapplication/Conversation.kt @@ -1,5 +1,6 @@ package com.example.markmoussa.meshchatapplication +import com.hypelabs.hype.Instance import java.util.* /** @@ -8,4 +9,4 @@ import java.util.* // This version with a constructor used simply so we can generate dummy data to populate conversations -class Conversation(val user: User?, val timeStamp: Date?, val messageList: Store?, val currentlyOnline: Boolean) \ No newline at end of file +class Conversation(val user: User?, val timeStamp: Date?, val messageList: Store?, val instance: Instance?, val currentlyOnline: Boolean) \ No newline at end of file diff --git a/app/src/main/java/com/example/markmoussa/meshchatapplication/ConversationListActivity.kt b/app/src/main/java/com/example/markmoussa/meshchatapplication/ConversationListActivity.kt index d1d406f..eda9ce6 100644 --- a/app/src/main/java/com/example/markmoussa/meshchatapplication/ConversationListActivity.kt +++ b/app/src/main/java/com/example/markmoussa/meshchatapplication/ConversationListActivity.kt @@ -4,9 +4,7 @@ package com.example.markmoussa.meshchatapplication * Created by markmoussa on 2/24/18. */ -import android.arch.lifecycle.Lifecycle import android.arch.lifecycle.LifecycleObserver -import android.arch.lifecycle.OnLifecycleEvent import android.arch.lifecycle.ProcessLifecycleOwner import android.content.Intent import android.support.v7.app.AppCompatActivity @@ -25,7 +23,7 @@ import com.hypelabs.hype.Message class ConversationListActivity : AppCompatActivity(), Store.Delegate, LifecycleObserver { var mConversationList: MutableList = mutableListOf() - lateinit var lifeCycleObserver: LifecycleObserverActivity + private lateinit var lifeCycleObserver: LifecycleObserverActivity override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -33,13 +31,14 @@ class ConversationListActivity : AppCompatActivity(), Store.Delegate, LifecycleO val hypeFramework = applicationContext as HypeLifeCycle + // This tells us ProcessLifecycleOwner.get().lifecycle.addObserver(LifecycleObserverActivity(this).also { lifeCycleObserver = it}) populateConversationList() var mConversationListRecycler: RecyclerView? = null var mConversationListAdapter: ConversationListAdapter? = null - mConversationListRecycler = findViewById(R.id.reyclerview_conversation_list) + mConversationListRecycler = findViewById(R.id.reyclerview_conversation_list) mConversationListRecycler!!.layoutManager = LinearLayoutManager(this) mConversationListAdapter = ConversationListAdapter(this, mConversationList) mConversationListRecycler.adapter = mConversationListAdapter @@ -55,7 +54,7 @@ class ConversationListActivity : AppCompatActivity(), Store.Delegate, LifecycleO contactStore?.delegate = this@ConversationListActivity val intent = Intent(this@ConversationListActivity, MessageListActivity::class.java) intent.putExtra("userIdentifier", userIdentifier) - if(userIdentifier in hypeFramework.getAllOnlinePeers()) { + if(userIdentifier in hypeFramework.getOnlinePeers()) { intent.putExtra("online", true) } else { intent.putExtra("online", false) @@ -96,41 +95,57 @@ class ConversationListActivity : AppCompatActivity(), Store.Delegate, LifecycleO } } - override fun onCreateOptionsMenu(menu: Menu?): Boolean { - menuInflater.inflate(R.menu.menu_conversation_list, menu) - return true - } - - override fun onOptionsItemSelected(item: MenuItem?): Boolean { - when(item!!.itemId) { - R.id.newMessageMenuButton -> { - val intent = Intent(this, NewMessageActivity::class.java) - startActivity(intent) - return true - } - else -> return super.onOptionsItemSelected(item) +// override fun onCreateOptionsMenu(menu: Menu?): Boolean { +// menuInflater.inflate(R.menu.menu_conversation_list, menu) +// return true +// } +// +// override fun onOptionsItemSelected(item: MenuItem?): Boolean { +// when(item!!.itemId) { +// R.id.newMessageMenuButton -> { +// val intent = Intent(this, NewMessageActivity::class.java) +// startActivity(intent) +// return true +// } +// else -> return super.onOptionsItemSelected(item) +// } +// } + + // This function initializes conversation list for the first time. + // Making separate function because this one returns the list, populateConversationList + // modifies it (so that the changes can be detected by the adapter) + private fun initializeConversationList(): MutableList { + val hypeFramework = applicationContext as HypeLifeCycle + val messageList = hypeFramework.getAllMessages() + val contactsList = hypeFramework.getAllContacts() + val onlinePeers = hypeFramework.getOnlinePeers() + val conversationList = mutableListOf() + for(x in messageList) { + conversationList.add(Conversation(contactsList[x.key], null, x.value, if(contactsList.containsKey(x.key)) onlinePeers[x.key] else null, onlinePeers.containsKey(x.key))) } + return conversationList } - private fun populateConversationList() { // creating conversations list mConversationList.clear() val hypeFramework = applicationContext as HypeLifeCycle var currentlyOnline: Boolean val contactsList = hypeFramework.getAllContacts() + val onlinePeers = hypeFramework.getOnlinePeers() + Log.d("ConversationListActivit", "Length of hypeFramework.getAllMessages(): ${hypeFramework.getAllMessages().size}") for(x in hypeFramework.getAllMessages()) { - currentlyOnline = x.key in hypeFramework.getAllOnlinePeers() - val nickname: String? - if(x.key in contactsList.keys) { - // I get the actual user here. Consider just passing that into the conversation instead of - // creating a brand new one - nickname = contactsList[x.key]!!.nickname -// Log.d("ConversationListActivit", "Nickname is: $nickname") - } else { - nickname = null - } - mConversationList.add(Conversation(contactsList[x.key], null, x.value, currentlyOnline)) + currentlyOnline = onlinePeers.containsKey(x.key) +// val nickname: String? +// if(contactsList.containsKey(x.key)) { +// // I get the actual user here. Consider just passing that into the conversation instead of +// // creating a brand new one +// nickname = contactsList[x.key]!!.nickname +//// Log.d("ConversationListActivit", "Nickname is: $nickname") +// } else { +// nickname = null +// } + mConversationList.add(Conversation(contactsList[x.key], null, x.value, if(currentlyOnline) onlinePeers[x.key] else null, currentlyOnline)) } // Debugging // Log.d("ConversationListActivit", "populateConversationList() returned: ") @@ -143,7 +158,7 @@ class ConversationListActivity : AppCompatActivity(), Store.Delegate, LifecycleO // TODO: Add option to delete conversation - override fun onMessageAdded(store: Store, message: Pair) { + override fun onMessageAdded(store: Store, message: Pair) { updateInterface() } diff --git a/app/src/main/java/com/example/markmoussa/meshchatapplication/ConversationListAdapter.kt b/app/src/main/java/com/example/markmoussa/meshchatapplication/ConversationListAdapter.kt index f8d4877..9ce0287 100644 --- a/app/src/main/java/com/example/markmoussa/meshchatapplication/ConversationListAdapter.kt +++ b/app/src/main/java/com/example/markmoussa/meshchatapplication/ConversationListAdapter.kt @@ -70,7 +70,7 @@ class ConversationListAdapter(private val mContext: Context, private val mConver // do nothing } val hypeFramework = mContext.applicationContext as HypeLifeCycle - if(conversation.user.userIdentifier in hypeFramework.getAllOnlinePeers()) { + if(conversation.user.userIdentifier in hypeFramework.getOnlinePeers()) { onlineStatusIcon.setImageResource(android.R.drawable.presence_online) } else { onlineStatusIcon.setImageResource(android.R.drawable.presence_offline) diff --git a/app/src/main/java/com/example/markmoussa/meshchatapplication/HypeLifeCycle.kt b/app/src/main/java/com/example/markmoussa/meshchatapplication/HypeLifeCycle.kt index f9f6c66..7d5b017 100644 --- a/app/src/main/java/com/example/markmoussa/meshchatapplication/HypeLifeCycle.kt +++ b/app/src/main/java/com/example/markmoussa/meshchatapplication/HypeLifeCycle.kt @@ -9,7 +9,6 @@ import android.content.Context import android.content.SharedPreferences import android.util.Log import com.google.gson.Gson -import com.google.gson.GsonBuilder import com.hypelabs.hype.Error import com.hypelabs.hype.Hype import com.hypelabs.hype.Instance @@ -26,11 +25,30 @@ import com.google.gson.reflect.TypeToken class HypeLifeCycle : StateObserver, NetworkObserver, MessageObserver, Application() { + + /* TODO: Try doing this: + * Make Instance transient in Store, since we don't really need to serialize and save it anyways + * since it changes every time + * Then make onlinePeers have a hashMap with key: userIdentifier and value: Instance + * Then pass the Instance of the user (most likely as an intent extra) from ConversationListActivity + * to MessageListActivity, and have Store use that instead + * May find at the end that you don't even need to have Instance in the Store constructor + * TODO: I don't even need onlinePeers file since the onlinePeers is only for the duration of the app runtime. + * Delete and replace with standard getters and setters + * + * If that doesn't solve the problem, it might be that Message is causing serialization issues + * in which case, as a hotfix until I figure out why Hype hasn't allowed Serialization for these + * Make the Store messages variable carry the Messages data (aka the text itself) instead of the + * Hype Message class, since we don't really need the Message object when saving things anyways + * + * If that still doesn't work, might have to write custom serializer/deserializer for Store instead + */ + + // TODO: DO THIS SECOND - Figure out why these observables aren't working sometimes (such as messageDatabase in setMessageDatabase() + // TODO: Consider merging messageDatabase and contactsDatabase (can do HashMap>) // The onlinePeers object keeps track of message storage associated with each instance (peer) via their userIdentifiers - private var onlinePeers: MutableList by Delegates.observable(mutableListOf()) { - _, _, _ -> updateOnlinePeersFile() - } + private var onlinePeers: HashMap = hashMapOf() // The messageDatabase keeps track of the users and their previous messages via saving their userIdentifiers and a Store private var messageDatabase: HashMap by Delegates.observable(hashMapOf()) { @@ -54,7 +72,16 @@ class HypeLifeCycle : StateObserver, NetworkObserver, MessageObserver, Applicati // Generate an app identifier in the HypeLabs dashboard (https://hypelabs.io/apps/), // by creating a new app. Copy the given identifier here. + + // ChatApplication Hype.setAppIdentifier("f370ac17") + + // MeshNetworkApplication + // Hype.setAppIdentifier("9a96baaa") + + // MeshNetworkApp2 + Hype.setAppIdentifier("b056a7af") + val sharedPreferences: SharedPreferences = applicationContext.getSharedPreferences("sp", Context.MODE_PRIVATE) val userIdentifier = sharedPreferences.getInt("USER_IDENTIFIER", Hype.DefaultUserIdentifier) Hype.setUserIdentifier(userIdentifier) @@ -70,7 +97,6 @@ class HypeLifeCycle : StateObserver, NetworkObserver, MessageObserver, Applicati override fun onHypeStart() { Log.i(TAG, "Hype started!") Log.i(TAG, "Loading store from file") - onlinePeers = readOnlinePeers() messageDatabase = readMessageDatabase() contactsDatabase = readContactsDatabase() } @@ -84,7 +110,7 @@ class HypeLifeCycle : StateObserver, NetworkObserver, MessageObserver, Applicati } Log.i(TAG, String.format("Hype stopped [%s]", description)) - readOnlinePeers() + onlinePeers.clear() } override fun onHypeFailedStarting(error: Error) { @@ -143,10 +169,10 @@ class HypeLifeCycle : StateObserver, NetworkObserver, MessageObserver, Applicati var store = getAllMessages()[instance.userIdentifier] if(store == null) { - store = Store(instance) + store = Store(instance, instance.userIdentifier) } // Storing the message triggers a reload update in the MessageList activity - store.add(Pair(message, false), this) + store.add(Pair(message.data.toString(charset("UTF-8")), false), this) setMessageDatabase(instance.userIdentifier, store) } @@ -174,7 +200,14 @@ class HypeLifeCycle : StateObserver, NetworkObserver, MessageObserver, Applicati override fun onHypeRequestAccessToken(i: Int): String { // Access the app settings (https://hypelabs.io/apps/) to find an access token to use here. - return "903cbdd53f59e2f771cbf2a9429c91" + // This one is for chatApplication +// return "903cbdd53f59e2f771cbf2a9429c91" + + // MeshNetworkApplication +// return "4e5936b294a88cf2" + + // MeshNetworkApp2 + return "4e5936b294a88cf2" } override fun onCreate() { @@ -193,10 +226,22 @@ class HypeLifeCycle : StateObserver, NetworkObserver, MessageObserver, Applicati // contactsDatabase = readContactsDatabase() } - fun getAllOnlinePeers(): MutableList { + fun getOnlinePeers(): HashMap { return onlinePeers } + // adds online peer to onlinePeers and triggers updating online peers file in memory + private fun setOnlinePeers(userIdentifier: Long, instance: Instance, addOrRemove: Boolean) { + // true = add, false = remove + if(addOrRemove) { + if(!(onlinePeers.contains(userIdentifier))) { + onlinePeers[userIdentifier] = instance + } + } else { + onlinePeers.remove(userIdentifier) + } + } + fun getAllMessages(): HashMap { return messageDatabase } @@ -209,17 +254,7 @@ class HypeLifeCycle : StateObserver, NetworkObserver, MessageObserver, Applicati this.messageDatabase = messageDatabase } - // adds online peer to onlinePeers and triggers updating online peers file in memory - private fun setAllOnlinePeers(userIdentifier: Long, addOrRemove: Boolean) { - // true = add, false = remove - if(addOrRemove) { - if(!(onlinePeers.contains(userIdentifier))) { - onlinePeers.add(userIdentifier) - } - } else { - onlinePeers.remove(userIdentifier) - } - } + // adds message to store and triggers updating store file in memory fun setMessageDatabase(userIdentifier: Long, store: Store) { @@ -231,7 +266,7 @@ class HypeLifeCycle : StateObserver, NetworkObserver, MessageObserver, Applicati Log.d("HypeLifeCycle ", "new messageDatabase (from variable) is: ${messageDatabase.entries.toString()}") for(x in messageDatabase.values) { for(y in x.getMessages()) { - Log.d("HypeLifeCycle", "Store contents (from new messageDatabase (from variable)): ${y.first.data.toString(charset("UTF-8"))}") + Log.d("HypeLifeCycle", "Store contents (from new messageDatabase (from variable)): ${y.first}") } } } @@ -248,43 +283,6 @@ class HypeLifeCycle : StateObserver, NetworkObserver, MessageObserver, Applicati updateContactsDatabase() } - private fun readOnlinePeers(): MutableList { - val storeFile = File(dirPath, "storeFile") - if(!(storeFile.exists()) || storeFile.length() == 0.toLong()) { - Log.d("HypeLifeCycle", "File for online peers does not exist; creating new one (from read)") - storeFile.createNewFile() - return mutableListOf() - } else { - try { - val fis = FileInputStream(storeFile) - val ois = ObjectInputStream(fis) - val result: MutableList = ois.readObject() as MutableList - ois.close() - Log.d("HypeLifeCycle: ", "readOnlinePeers() returned: " + result.toString()) - return result - } catch (e: Exception) { - e.printStackTrace() - } - } - return mutableListOf() - } - - fun updateOnlinePeersFile() { - try { - val storeFile = File(dirPath, "storeFile") - if(!(storeFile.exists())) { - storeFile.createNewFile() - Log.d("HypeLifeCycle", "File for online peers does not exist; creating new one (from update)") - } - val fos = FileOutputStream(storeFile) - val oos = ObjectOutputStream(fos) - oos.writeObject(onlinePeers) - oos.close() - } catch(e: Exception) { - e.printStackTrace() - } - } - inline fun Gson.fromJson(json: String) = this.fromJson(json, object: TypeToken() {}.type) private fun readMessageDatabase(): HashMap { val messageDatabaseFile = File(dirPath, "messageDatabase") @@ -298,25 +296,25 @@ class HypeLifeCycle : StateObserver, NetworkObserver, MessageObserver, Applicati return hashMapOf() } else { try { -// val fis = FileInputStream(messageDatabaseFile) -// val ois = ObjectInputStream(fis) -// val result = ois.readObject() as HashMap -// ois.close() - - // Using GSON library instead of ObjectOutputStream for now because Instance (found in Store) is not Serializable at the moment val fis = FileInputStream(messageDatabaseFile) val ois = ObjectInputStream(fis) - val jsonHashMapResult = ois.readObject().toString() + val result = ois.readObject() as HashMap ois.close() - // debugging - Log.d("HypeLifeCycle", "jsonHashMapResult: $jsonHashMapResult") - val result: HashMap = Gson().fromJson>(jsonHashMapResult) + +// // Using GSON library instead of ObjectOutputStream for now because Instance (found in Store) is not Serializable at the moment +// val fis = FileInputStream(messageDatabaseFile) +// val ois = ObjectInputStream(fis) +// val jsonHashMapResult = ois.readObject().toString() +// ois.close() +// // debugging +// Log.d("HypeLifeCycle", "jsonHashMapResult: $jsonHashMapResult") +// val result: HashMap = Gson().fromJson>(jsonHashMapResult) // debugging Log.d("HypeLifeCycle ", "reading messageDatabase (from file) is: ${result.entries.toString()}") for(x in result.values) { for(y in x.getMessages()) { - Log.d("HypeLifeCycle", "Store contents (from reading messageDatabase (from file)): ${y.first.data.toString(charset("UTF-8"))}") + Log.d("HypeLifeCycle", "Store contents (from reading messageDatabase (from file)): ${y.first}") } } return result @@ -339,18 +337,19 @@ class HypeLifeCycle : StateObserver, NetworkObserver, MessageObserver, Applicati } // TODO: DO THIS FIRST - figure out how to serialize messageDatabase // TODO: since Instance from Hype SDK not serializable, it won't let me serialize all of messageDatabase -// val fos = FileOutputStream(messageDatabaseFile) -// val oos = ObjectOutputStream(fos) -// oos.writeObject(messageDatabase) -// // debugging -// Log.d("HypeLifeCycle", "Right after writing the file, the new file is: ${readMessageDatabase()}") -// oos.close() - - // Using GSON library instead of ObjectOutputStream for now because Instance (found in Store) is not Serializable at the moment val fos = FileOutputStream(messageDatabaseFile) - val gsonSerializer = Gson().toJson(messageDatabase) val oos = ObjectOutputStream(fos) - oos.writeObject(gsonSerializer) + oos.writeObject(messageDatabase) + // debugging + Log.d("HypeLifeCycle", "Right after writing the file, the new file is: ${readMessageDatabase()}") + oos.close() + + // Using GSON library instead of ObjectOutputStream for now because Instance (found in Store) is not Serializable at the moment +// val fos = FileOutputStream(messageDatabaseFile) +// val gsonSerializer = Gson().toJson(messageDatabase) +// +// val oos = ObjectOutputStream(fos) +// oos.writeObject(gsonSerializer) // debugging Log.d("HypeLifeCycle", "Right after writing the messageDatabase file, the new file is: ${readMessageDatabase()}") @@ -408,12 +407,12 @@ class HypeLifeCycle : StateObserver, NetworkObserver, MessageObserver, Applicati private fun addToResolvedInstancesMap(instance: Instance) { // Instances should be strongly kept by some data structure. Their identifiers // are useful for keeping track of which instances are ready to communicate. - getAllOnlinePeers().add(instance.userIdentifier) - setAllOnlinePeers(instance.userIdentifier, true) - Log.d("HypeLifeCycle ", "New onlinePeers: " + getAllOnlinePeers().toString()) + getOnlinePeers().put(instance.userIdentifier, instance) + setOnlinePeers(instance.userIdentifier, instance, true) + Log.d("HypeLifeCycle ", "New onlinePeers: " + getOnlinePeers().toString()) if(getAllMessages()[instance.userIdentifier] == null) { Log.d("HypeLifeCycle", "Could not find userIdentifier in getAllMessages() (aka messageDatabase), therefore starting a brand new Store") - setMessageDatabase(instance.userIdentifier, Store(instance)) + setMessageDatabase(instance.userIdentifier, Store(instance, instance.userIdentifier)) Log.d("HypeLifeCycle ", "New messageDatabase (from variable): " + getAllMessages().toString()) } if(!(readContactsDatabase().containsKey(instance.userIdentifier))) { @@ -434,11 +433,11 @@ class HypeLifeCycle : StateObserver, NetworkObserver, MessageObserver, Applicati fun removeFromResolvedInstancesMap(instance: Instance) { // Cleaning up is always a good idea. It's not possible to communicate with instances // that were previously lost. -// getAllOnlinePeers().remove(instance.userIdentifier) +// getOnlinePeers().remove(instance.userIdentifier) // debugging Log.d("HypeLifeCycle", "Lost instance") - setAllOnlinePeers(instance.userIdentifier, false) + setOnlinePeers(instance.userIdentifier, instance,false) // Notify the conversationList activity to refresh the UI // val conversationListActivity = ConversationListActivity() diff --git a/app/src/main/java/com/example/markmoussa/meshchatapplication/LifecycleObserverActivity.kt b/app/src/main/java/com/example/markmoussa/meshchatapplication/LifecycleObserverActivity.kt index a16a242..37ae452 100644 --- a/app/src/main/java/com/example/markmoussa/meshchatapplication/LifecycleObserverActivity.kt +++ b/app/src/main/java/com/example/markmoussa/meshchatapplication/LifecycleObserverActivity.kt @@ -27,9 +27,17 @@ class LifecycleObserverActivity(context: Context) : LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_STOP) fun onAppBackgrounded() { Log.i("DEBUG ", "ON APP BACKGROUNDED CALLED") + + // Commenting this out because if Hype stops when app backgrounded, messages would only get sent + // when the user has the app open + // val hypeFramework = mContext as HypeLifeCycle + // hypeFramework.requestHypeToStop() + } + + @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) + fun onAppDestroyed() { val hypeFramework = mContext as HypeLifeCycle hypeFramework.requestHypeToStop() - } diff --git a/app/src/main/java/com/example/markmoussa/meshchatapplication/MessageListActivity.kt b/app/src/main/java/com/example/markmoussa/meshchatapplication/MessageListActivity.kt index 564640f..8830566 100644 --- a/app/src/main/java/com/example/markmoussa/meshchatapplication/MessageListActivity.kt +++ b/app/src/main/java/com/example/markmoussa/meshchatapplication/MessageListActivity.kt @@ -7,6 +7,7 @@ package com.example.markmoussa.meshchatapplication import android.support.v7.app.AppCompatActivity import android.os.Bundle import android.support.v4.widget.SwipeRefreshLayout +import android.support.v7.app.AlertDialog import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.RecyclerView import android.util.Log @@ -18,7 +19,7 @@ import java.io.UnsupportedEncodingException class MessageListActivity : AppCompatActivity(), Store.Delegate { - private var mMessageList: MutableList> = mutableListOf() + private var mMessageList: MutableList> = mutableListOf() private lateinit var mMessageAdapter: MessageListAdapter override fun onCreate(savedInstanceState: Bundle?) { @@ -70,13 +71,13 @@ class MessageListActivity : AppCompatActivity(), Store.Delegate { // debugging Log.d("MessageListActivity ", "Store is this: ") for(x in store.getMessages()) { - Log.i("DBEUG", x.first.data.toString()) + Log.i("DEBUG", x.first) } store.delegate = this store.lastReadIndex = store.getMessages().size - val chatBox = findViewById(R.id.edittext_chatbox) as EditText - val sendButton = findViewById