diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser index b3296f1..a3378ad 100644 Binary files a/.idea/caches/build_file_checksums.ser and b/.idea/caches/build_file_checksums.ser differ diff --git a/app/build.gradle b/app/build.gradle index ad2b5fe..9835795 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -43,9 +43,9 @@ dependencies { implementation "android.arch.lifecycle:viewmodel:1.1.1" // alternatively, just LiveData implementation "android.arch.lifecycle:livedata:1.1.1" - kapt "android.arch.lifecycle:compiler:1.1.1" - // Test helpers for LiveData testImplementation "android.arch.core:core-testing:1.1.1" + // For Bubbles that contain messages in MessageListActivity; remove when I create my own bubble + implementation 'com.github.lguipeng:BubbleView:1.0.1' } 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 d673de8..abf75cc 100644 --- a/app/src/main/java/com/example/markmoussa/meshchatapplication/ConversationListActivity.kt +++ b/app/src/main/java/com/example/markmoussa/meshchatapplication/ConversationListActivity.kt @@ -4,11 +4,11 @@ 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.Context import android.content.Intent +import android.content.SharedPreferences import android.support.v7.app.AppCompatActivity import android.os.Bundle import android.support.v4.widget.SwipeRefreshLayout @@ -25,7 +25,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,14 +33,14 @@ class ConversationListActivity : AppCompatActivity(), Store.Delegate, LifecycleO val hypeFramework = applicationContext as HypeLifeCycle - //TODO: START HERE + // 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 @@ -50,13 +50,13 @@ class ConversationListActivity : AppCompatActivity(), Store.Delegate, LifecycleO ItemClickSupport.addTo(mConversationListRecycler).setOnItemClickListener(object : ItemClickSupport.OnItemClickListener { override fun onItemClicked(recyclerView: RecyclerView, position: Int, v: View) { // when item (conversation) is clicked, go to that conversation's message list - Log.i("TEST: ", "This is a test, item number " + position.toString() + " picked.") +// Log.i("TEST: ", "This is a test, item number " + position.toString() + " picked.") val userIdentifier = mConversationList[position].user?.userIdentifier val contactStore = hypeFramework.getAllMessages()[userIdentifier] 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) @@ -97,50 +97,70 @@ 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()) { - // TODO: Implement User nickname when I enable setUserIdentifier on Hype SDK - currentlyOnline = x.key in hypeFramework.getAllOnlinePeers() - val nickname: String? - if(x.key in hypeFramework.getAllContacts()) { - // I get the actual user here. Consider just passing that into the conversation instead of - // creating a brand new one - nickname = hypeFramework.getAllContacts()[x.key]!!.nickname - Log.i("DEBUG", "Nickname is: $nickname") - } else { - nickname = null - } - mConversationList.add(Conversation(User(nickname, null, 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.i("DEBUG: ", "populateConversationList() returned: ") +// Log.d("ConversationListActivit", "populateConversationList() returned: ") for(x in mConversationList) { - Log.i("DEBUG", "User nickname: ${x.user?.nickname}, User Identifier: ${x.user?.userIdentifier}, user online: ${x.currentlyOnline}") + Log.d("ConversationListActivit", "User nickname: ${x.user?.nickname}, User Identifier: ${x.user?.userIdentifier}, user online: ${x.currentlyOnline}") } } - override fun onMessageAdded(store: Store, message: Message) { + + // TODO: Add option to delete conversation + + + override fun onMessageAdded(store: Store, message: Pair) { updateInterface() } @@ -158,6 +178,19 @@ class ConversationListActivity : AppCompatActivity(), Store.Delegate, LifecycleO } } + override fun onBackPressed() { + val sharedPreferences: SharedPreferences = applicationContext.getSharedPreferences("sp", Context.MODE_PRIVATE) + if(sharedPreferences.contains("USERNAME") && sharedPreferences.getString("USERNAME", null).isNotEmpty()) { + val intent = Intent(this, MainActivity::class.java) + intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP + intent.putExtra("Exit me", true) + startActivity(intent) + finish() + } else { + super.onBackPressed() + } + } + } 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..2a83c27 100644 --- a/app/src/main/java/com/example/markmoussa/meshchatapplication/ConversationListAdapter.kt +++ b/app/src/main/java/com/example/markmoussa/meshchatapplication/ConversationListAdapter.kt @@ -2,6 +2,7 @@ package com.example.markmoussa.meshchatapplication import android.content.Context import android.support.v7.widget.RecyclerView +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -57,8 +58,13 @@ class ConversationListAdapter(private val mContext: Context, private val mConver } else { usernameText.text = conversation.user.userIdentifier.toString() } - if(conversation.messageList != null) { - messagePreviewText.text = conversation.messageList.getMessageStringAtIndex(conversation.messageList.lastReadIndex) + if(!(conversation.messageList?.getMessages()!!.isEmpty())) { + + var messagePreview = conversation.messageList.getMessageStringAtIndex(conversation.messageList.getMessages().size - 1) + if(!(messagePreview.isNullOrEmpty()) && messagePreview!!.length > 40) { + messagePreview = messagePreview.substring(0, 33) + "..." + } + messagePreviewText.text = messagePreview } else { messagePreviewText.text = "" } @@ -66,11 +72,11 @@ 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 - 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 3c95141..8693d36 100644 --- a/app/src/main/java/com/example/markmoussa/meshchatapplication/HypeLifeCycle.kt +++ b/app/src/main/java/com/example/markmoussa/meshchatapplication/HypeLifeCycle.kt @@ -7,10 +7,6 @@ package com.example.markmoussa.meshchatapplication import android.app.Application import android.content.Context import android.content.SharedPreferences -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.net.Uri -import android.util.Base64 import android.util.Log import com.hypelabs.hype.Error import com.hypelabs.hype.Hype @@ -20,24 +16,24 @@ import com.hypelabs.hype.MessageInfo import com.hypelabs.hype.MessageObserver import com.hypelabs.hype.NetworkObserver import com.hypelabs.hype.StateObserver -import org.json.JSONObject import java.io.* -import java.nio.charset.Charset import kotlin.collections.HashMap import kotlin.properties.Delegates class HypeLifeCycle : StateObserver, NetworkObserver, MessageObserver, Application() { - // The onlinePeers object keeps track of message storage associated with each instance (peer) - var onlinePeers: MutableList by Delegates.observable(mutableListOf()) { - _, _, _ -> updateOnlinePeersFile() - } + // 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: 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()) { _, _, _ -> 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() } @@ -54,7 +50,15 @@ 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) @@ -68,10 +72,14 @@ class HypeLifeCycle : StateObserver, NetworkObserver, MessageObserver, Applicati } override fun onHypeStart() { + + // TODO: When you stop and start Hype again, for some reason the other users can't find you again + // it looks like this is due to Hype not having time to "lose" the instance before refinding it + Log.i(TAG, "Hype started!") Log.i(TAG, "Loading store from file") - readOnlinePeers() - readMessageDatabase() + messageDatabase = readMessageDatabase() + contactsDatabase = readContactsDatabase() } override fun onHypeStop(error: Error?) { @@ -83,7 +91,7 @@ class HypeLifeCycle : StateObserver, NetworkObserver, MessageObserver, Applicati } Log.i(TAG, String.format("Hype stopped [%s]", description)) - readOnlinePeers() + onlinePeers.clear() } override fun onHypeFailedStarting(error: Error) { @@ -140,18 +148,14 @@ class HypeLifeCycle : StateObserver, NetworkObserver, MessageObserver, Applicati Log.i(TAG, String.format("Hype got a message from: %s", instance.stringIdentifier)) - val store = getAllMessages()[instance.userIdentifier] - if(store != null) { - setMessageDatabase(instance.userIdentifier, store) - } else { - setMessageDatabase(instance.userIdentifier, Store(instance)) + var store = getAllMessages()[instance.userIdentifier] + if(store == null) { + store = Store(instance.userIdentifier) } // Storing the message triggers a reload update in the MessageList activity - store!!.add(message, this) + store.add(Pair(message.data.toString(charset("UTF-8")), false), this) + setMessageDatabase(instance.userIdentifier, store) - // Notify the conversationList activity to refresh the UI -// val conversationListActivity = ConversationListActivity() -// conversationListActivity.notifyOnlinePeersChanged() } override fun onHypeMessageFailedSending(messageInfo: MessageInfo, instance: Instance, error: Error) { @@ -177,7 +181,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() { @@ -191,14 +202,27 @@ 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 { + 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 } @@ -207,60 +231,48 @@ class HypeLifeCycle : StateObserver, NetworkObserver, MessageObserver, Applicati return contactsDatabase } - // adds online peer to onlinePeers and triggers updating online peers file in memory - private fun setAllOnlinePeers(userIdentifier: Long) { - onlinePeers.add(userIdentifier) + fun setAllMessages(messageDatabase: HashMap) { + this.messageDatabase = messageDatabase } + + // adds message to store and triggers updating store file in memory fun setMessageDatabase(userIdentifier: Long, store: Store) { messageDatabase[userIdentifier] = store + // TODO: For some reason, observable delegate not calling updateMessageDatabase() when message database is changed + // TODO: Figure out why and delete manual call once fixed + updateMessageDatabase() + // debugging + 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}") + } + } } // adds contact to contactsDatabase and triggers updating the contacts file in memory fun setContactsDatabase(userIdentifier: Long, user: User) { - contactsDatabase[userIdentifier] = user - } - - private fun readOnlinePeers(): MutableList { - val storeFile = File(dirPath, "storeFile") - if(!(storeFile.exists()) || storeFile.length() == 0.toLong()) { - storeFile.createNewFile() - return mutableListOf() - } else { - try { - val fis = FileInputStream(storeFile) - val ois = ObjectInputStream(fis) - val result = ois.readObject() as MutableList - ois.close() - Log.i("DEBUG: ", "readOnlinePeers() returned: " + result.toString()) - return result - } catch (e: Exception) { - e.printStackTrace() - } - } - return mutableListOf() - } + // 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 - fun updateOnlinePeersFile() { - try { - val storeFile = File(dirPath, "storeFile") - if(!(storeFile.exists())) { - storeFile.createNewFile() - } - val fos = FileOutputStream(storeFile) - val oos = ObjectOutputStream(fos) - oos.writeObject(onlinePeers) - oos.close() - } catch(e: Exception) { - e.printStackTrace() - } + // 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 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 { @@ -268,7 +280,15 @@ class HypeLifeCycle : StateObserver, NetworkObserver, MessageObserver, Applicati val ois = ObjectInputStream(fis) val result = ois.readObject() as HashMap ois.close() - Log.i("DEBUG: ", "readMessageDatabase() returned: " + result.toString()) + + // 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}") + } + } return result } catch (e: Exception) { e.printStackTrace() @@ -279,15 +299,29 @@ class HypeLifeCycle : StateObserver, NetworkObserver, MessageObserver, Applicati // updates the message database file whenever a new message is sent private fun updateMessageDatabase() { + // debugging + Log.d("HypeLifeCycle", "updateMessageDatabase() being called") try { 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)") } + // 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() + + // 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() } @@ -297,6 +331,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 { @@ -304,7 +339,7 @@ class HypeLifeCycle : StateObserver, NetworkObserver, MessageObserver, Applicati val ois = ObjectInputStream(fis) val result = ois.readObject() as HashMap ois.close() - Log.i("DEBUG: ", "readContactsDatabase() returned: " + result.toString()) + Log.d("HypeLifeCycle: ", "readContactsDatabase() returned: " + result.toString()) return result } catch (e: Exception) { e.printStackTrace() @@ -318,10 +353,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) { @@ -332,12 +369,13 @@ 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) - Log.i("DEBUG ", "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) { - setMessageDatabase(instance.userIdentifier, Store(instance)) - Log.i("DEBUG ", "New messageDatabase: " + getAllMessages().toString()) + Log.d("HypeLifeCycle", "Could not find userIdentifier in getAllMessages() (aka messageDatabase), therefore starting a brand new Store") + setMessageDatabase(instance.userIdentifier, Store(instance.userIdentifier)) + Log.d("HypeLifeCycle ", "New messageDatabase (from variable): " + getAllMessages().toString()) } if(!(readContactsDatabase().containsKey(instance.userIdentifier))) { // restoring User object from serialized byteArray @@ -346,20 +384,22 @@ class HypeLifeCycle : StateObserver, NetworkObserver, MessageObserver, Applicati val newUser: User = ois.readObject() as User // for now, userIdentifier in User object is null (because Hype SDK only allows for 255 bytes // and adding the userIdentifier exceeds the limit) - Log.i("DEBUG ", "NEWUSER OBJECT: ${newUser.toString()}") + 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) { // Cleaning up is always a good idea. It's not possible to communicate with instances // that were previously lost. - getAllOnlinePeers().remove(instance.userIdentifier) - setAllOnlinePeers(instance.userIdentifier) +// getOnlinePeers().remove(instance.userIdentifier) + + // debugging + Log.d("HypeLifeCycle", "Lost instance") + 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..cd51b1e 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,18 @@ class LifecycleObserverActivity(context: Context) : LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_STOP) fun onAppBackgrounded() { Log.i("DEBUG ", "ON APP BACKGROUNDED CALLED") - val hypeFramework = mContext as HypeLifeCycle - hypeFramework.requestHypeToStop() + // 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() { + // Commenting this part out for demo tomorrow +// val hypeFramework = mContext as HypeLifeCycle +// hypeFramework.requestHypeToStop() } diff --git a/app/src/main/java/com/example/markmoussa/meshchatapplication/MainActivity.kt b/app/src/main/java/com/example/markmoussa/meshchatapplication/MainActivity.kt index e2878b7..c85b593 100644 --- a/app/src/main/java/com/example/markmoussa/meshchatapplication/MainActivity.kt +++ b/app/src/main/java/com/example/markmoussa/meshchatapplication/MainActivity.kt @@ -5,7 +5,9 @@ package com.example.markmoussa.meshchatapplication */ import android.Manifest +import android.content.Context import android.content.Intent +import android.content.SharedPreferences import android.content.pm.PackageManager import android.support.v7.app.AppCompatActivity import android.os.Bundle @@ -33,5 +35,16 @@ class MainActivity : AppCompatActivity() { val intent = Intent(this, SignUpActivity::class.java) startActivity(intent) } + + val sharedPreferences: SharedPreferences = applicationContext.getSharedPreferences("sp", Context.MODE_PRIVATE) + if(sharedPreferences.contains("USERNAME") && sharedPreferences.getString("USERNAME", null).isNotEmpty()) { + val intent = Intent(this, ConversationListActivity::class.java) + startActivity(intent) + } + + // Companion code to onBackPressed for having the back button exit app instead of going back to SignUpActivity + if(intent.getBooleanExtra("Exit me", false)){ + finish() + } } } 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 e975cce..493351e 100644 --- a/app/src/main/java/com/example/markmoussa/meshchatapplication/MessageListActivity.kt +++ b/app/src/main/java/com/example/markmoussa/meshchatapplication/MessageListActivity.kt @@ -6,6 +6,8 @@ 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 @@ -13,57 +15,68 @@ import android.widget.Button import android.widget.EditText import com.hypelabs.hype.* import java.io.UnsupportedEncodingException -import java.util.* 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) - // getting the messages - populateMessageList() - var mMessageRecycler: RecyclerView? = null - var mMessageAdapter: MessageListAdapter? = null - mMessageRecycler = findViewById(R.id.recyclerview_message_list) as RecyclerView - mMessageAdapter = MessageListAdapter(this, mMessageList) - mMessageRecycler.layoutManager = LinearLayoutManager(this) - mMessageRecycler.adapter = mMessageAdapter - 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 - Log.i("DEBUG ", "Actionbar title would be: ${hypeFramework.getAllContacts()[userIdentifier]!!.nickname}") - 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 + val mSwipeRefreshLayout = findViewById(R.id.swiperefresh) + mSwipeRefreshLayout.setOnRefreshListener { + notifyMessageListChanged() + mSwipeRefreshLayout.isRefreshing = false + } } override fun onStart() { super.onStart() // checking to see which user chat they want, or if it's a new message - // TODO: made a new separate class for new messages for now, condense that into this class when I have everything set up val hypeFramework = applicationContext as HypeLifeCycle val userIdentifier = intent.getLongExtra("userIdentifier", 0) if(userIdentifier in hypeFramework.getAllMessages()) { val store = getStore() // debugging - Log.i("DEBUG ", "Store is this: ") + Log.d("MessageListActivity ", "Store is this: ") for(x in store.getMessages()) { - Log.i("DBEUG", x.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