Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade app UI and address old issues #85

Merged
merged 7 commits into from
Feb 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 83 additions & 20 deletions mobile/src/main/java/mycroft/ai/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,7 @@
package mycroft.ai

import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.SharedPreferences
import android.content.*
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
Expand Down Expand Up @@ -67,17 +62,18 @@ import mycroft.ai.shared.wear.Constants.MycroftSharedConstants.MYCROFT_WEAR_REQU

class MainActivity : AppCompatActivity() {
private val logTag = "Mycroft"
private val utterances = mutableListOf<MycroftUtterance>()
private val utterances = mutableListOf<Utterance>()
private val reqCodeSpeechInput = 100
private var maximumRetries = 1
private var currentItemPosition = -1

private var mycroftAdapter = MycroftAdapter(utterances)
private var isNetworkChangeReceiverRegistered = false
private var isWearBroadcastRevieverRegistered = false
private var launchedFromWidget = false
private var autoPromptForSpeech = false

private lateinit var ttsManager: TTSManager
private lateinit var mycroftAdapter: MycroftAdapter
private lateinit var wsip: String
private lateinit var sharedPref: SharedPreferences
private lateinit var networkChangeReceiver: NetworkChangeReceiver
Expand All @@ -95,8 +91,42 @@ class MainActivity : AppCompatActivity() {
loadPreferences()

ttsManager = TTSManager(this)
mycroftAdapter = MycroftAdapter(utterances, applicationContext, menuInflater)
mycroftAdapter.setOnLongItemClickListener(object: MycroftAdapter.OnLongItemClickListener {
override fun itemLongClicked(v: View, position: Int) {
currentItemPosition = position
v.showContextMenu()
}
})

kbMicSwitch.setOnCheckedChangeListener { _, isChecked ->
val editor = sharedPref.edit()
editor.putBoolean("kbMicSwitch", isChecked)
editor.apply()

if (isChecked) {
// Switch to mic
micButton.visibility = View.VISIBLE
utteranceInput.visibility = View.INVISIBLE
sendUtterance.visibility = View.INVISIBLE
} else {
// Switch to keyboard
micButton.visibility = View.INVISIBLE
utteranceInput.visibility = View.VISIBLE
sendUtterance.visibility = View.VISIBLE
}
}

micButton.setOnClickListener { promptSpeechInput() }
sendUtterance.setOnClickListener {
val utterance = utteranceInput.text.toString()
if (utterance != "") {
sendMessage(utterance)
utteranceInput.text.clear()
}
}

fab.setOnClickListener { promptSpeechInput() }
registerForContextMenu(cardList)

//attach a listener to check for changes in state
voxswitch.setOnCheckedChangeListener { _, isChecked ->
Expand Down Expand Up @@ -149,6 +179,32 @@ class MainActivity : AppCompatActivity() {
return consumed && super.onOptionsItemSelected(item)
}

override fun onContextItemSelected(item: MenuItem): Boolean {
super.onContextItemSelected(item)
if (item.itemId == R.id.user_resend) {
// Resend user utterance
sendMessage(utterances[currentItemPosition].utterance)
} else if (item.itemId == R.id.user_copy || item.itemId == R.id.mycroft_copy) {
// Copy utterance to clipboard
val clipboardManager = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val data = ClipData.newPlainText("text", utterances[currentItemPosition].utterance)
clipboardManager.primaryClip = data
showToast("Copied to clipboard")
} else if (item.itemId == R.id.mycroft_share) {
// Share utterance
val sendIntent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_TEXT, utterances[currentItemPosition].utterance)
type = "text/plain"
}
startActivity(Intent.createChooser(sendIntent, resources.getText(R.string.action_share)))
} else {
return super.onContextItemSelected(item)
}

return true
}

fun connectWebSocket() {
val uri = deriveURI()

Expand All @@ -160,8 +216,8 @@ class MainActivity : AppCompatActivity() {

override fun onMessage(s: String) {
// Log.i(TAG, s);
runOnUiThread(MessageParser(s, object : SafeCallback<MycroftUtterance> {
override fun call(param: MycroftUtterance) {
runOnUiThread(MessageParser(s, object : SafeCallback<Utterance> {
override fun call(param: Utterance) {
addData(param)
}
}))
Expand All @@ -180,7 +236,7 @@ class MainActivity : AppCompatActivity() {
}
}

private fun addData(mycroftUtterance: MycroftUtterance) {
private fun addData(mycroftUtterance: Utterance) {
utterances.add(mycroftUtterance)
mycroftAdapter.notifyItemInserted(utterances.size - 1)
if (voxswitch.isChecked) {
Expand Down Expand Up @@ -266,7 +322,7 @@ class MainActivity : AppCompatActivity() {
}
}

fun sendMessage(msg: String?) {
fun sendMessage(msg: String) {
// let's keep it simple eh?
//final String json = "{\"message_type\":\"recognizer_loop:utterance\", \"context\": null, \"metadata\": {\"utterances\": [\"" + msg + "\"]}}";
val json = "{\"data\": {\"utterances\": [\"$msg\"]}, \"type\": \"recognizer_loop:utterance\", \"context\": null}"
Expand All @@ -284,6 +340,7 @@ class MainActivity : AppCompatActivity() {
// Actions to do after 1 seconds
try {
webSocketClient!!.send(json)
addData(Utterance(msg, UtteranceFrom.USER))
} catch (exception: WebsocketNotConnectedException) {
showToast(resources.getString(R.string.websocket_closed))
}
Expand Down Expand Up @@ -373,20 +430,26 @@ class MainActivity : AppCompatActivity() {
connectWebSocket()
}

kbMicSwitch.isChecked = sharedPref.getBoolean("kbMicSwitch", true)
if (kbMicSwitch.isChecked) {
// Switch to mic
micButton.visibility = View.VISIBLE
utteranceInput.visibility = View.INVISIBLE
sendUtterance.visibility = View.INVISIBLE
} else {
// Switch to keyboard
micButton.visibility = View.INVISIBLE
utteranceInput.visibility = View.VISIBLE
sendUtterance.visibility = View.VISIBLE
}

// set app reader setting
voxswitch.isChecked = sharedPref.getBoolean("appReaderSwitch", true)

// determine if app reader should be visible
voxswitch.visibility = when {
sharedPref.getBoolean("displayAppReaderSwitch", true) -> View.VISIBLE
else -> View.INVISIBLE
}

maximumRetries = Integer.parseInt(sharedPref.getString("maximumRetries", "1"))
}

private fun checkIfLaunchedFromWidget(intent: Intent) {

val extras = getIntent().extras
if (extras != null) {
if (extras.containsKey("launchedFromWidget")) {
Expand Down
6 changes: 3 additions & 3 deletions mobile/src/main/java/mycroft/ai/MessageParser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import org.json.JSONObject

/**
* Specialised Runnable that parses the [JSONObject] in [.message]
* when run. If it contains a [MycroftUtterance] object, the callback
* when run. If it contains a [Utterance] object, the callback
* defined in [the constructor][.MessageParser] will
* be [called][SafeCallback.call] with that object as a parameter.
*
Expand All @@ -38,7 +38,7 @@ import org.json.JSONObject
* @author Philip Cohn-Cort
*/
internal class MessageParser(private val message: String,
private val callback: SafeCallback<MycroftUtterance>) : Runnable {
private val callback: SafeCallback<Utterance>) : Runnable {
private val logTag = "MessageParser"

override fun run() {
Expand All @@ -48,7 +48,7 @@ internal class MessageParser(private val message: String,
try {
val obj = JSONObject(message)
if (obj.optString("type") == "speak") {
val ret = MycroftUtterance(obj.getJSONObject("data").getString("utterance"))
val ret = Utterance(obj.getJSONObject("data").getString("utterance"), UtteranceFrom.MYCROFT)
callback.call(ret)
}
} catch (e: JSONException) {
Expand Down
2 changes: 0 additions & 2 deletions mobile/src/main/java/mycroft/ai/SettingsActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@

package mycroft.ai

import android.Manifest
import android.annotation.TargetApi
import android.content.Context
import android.content.Intent
Expand All @@ -33,7 +32,6 @@ import android.os.Bundle
import android.preference.ListPreference
import android.preference.Preference
import android.preference.PreferenceActivity
import android.preference.SwitchPreference
import android.support.v4.app.NavUtils
import android.preference.PreferenceFragment
import android.preference.PreferenceManager
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@
package mycroft.ai

/**
* Data class representing a response from Mycroft
* Data class representing a response from Mycroft or command from the user
*/
data class MycroftUtterance(val utterance: String)
data class Utterance(val utterance: String, val from: UtteranceFrom)
6 changes: 6 additions & 0 deletions mobile/src/main/java/mycroft/ai/UtterenceFrom.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package mycroft.ai

enum class UtteranceFrom(val id: Int) {
USER(0),
MYCROFT(1)
}
56 changes: 45 additions & 11 deletions mobile/src/main/java/mycroft/ai/adapters/MycroftAdapter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,36 +20,70 @@

package mycroft.ai.adapters

import android.content.Context
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import kotlinx.android.synthetic.main.card_layout.view.*
import android.view.*
import android.widget.AdapterView
import android.widget.Toast
import kotlinx.android.synthetic.main.user_card_layout.view.*

import mycroft.ai.MycroftUtterance
import mycroft.ai.Utterance
import mycroft.ai.R
import mycroft.ai.UtteranceFrom

/**
* Created by paul on 2016/06/22.
*/
class MycroftAdapter(private val utteranceList: List<MycroftUtterance>) : RecyclerView.Adapter<MycroftAdapter.UtteranceViewHolder>() {
class MycroftAdapter(private val utteranceList: List<Utterance>, private val ctx: Context, private val menuInflater: MenuInflater) : RecyclerView.Adapter<MycroftAdapter.UtteranceViewHolder>() {
var onLongClickListener: OnLongItemClickListener? = null

interface OnLongItemClickListener {
fun itemLongClicked(v: View, position: Int)
}

fun setOnLongItemClickListener(listener: OnLongItemClickListener) {
onLongClickListener = listener
}

override fun getItemCount(): Int {
return utteranceList.size
}

override fun onBindViewHolder(utteranceViewHolder: UtteranceViewHolder, i: Int) {
utteranceViewHolder.vUtterance.text = utteranceViewHolder.itemView.context
.getString(R.string.mycroft_utterance, utteranceList[i].utterance)
utteranceViewHolder.vUtterance.text = utteranceList[i].utterance
utteranceViewHolder.itemView.setOnLongClickListener {v ->
onLongClickListener?.itemLongClicked(v, i)
true
}
}

override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): UtteranceViewHolder {
val itemView = LayoutInflater.from(viewGroup.context).inflate(R.layout.card_layout, viewGroup, false)
val itemView = when (i) {
UtteranceFrom.MYCROFT.id -> LayoutInflater.from(viewGroup.context).inflate(R.layout.mycroft_card_layout, viewGroup, false)
UtteranceFrom.USER.id -> LayoutInflater.from(viewGroup.context).inflate(R.layout.user_card_layout, viewGroup, false)
else -> throw IndexOutOfBoundsException("No such view id $i")
}

return UtteranceViewHolder(itemView)
return UtteranceViewHolder(itemView, menuInflater, i)
}

class UtteranceViewHolder(v: View) : RecyclerView.ViewHolder(v) {
override fun getItemViewType(position: Int): Int {
val message = utteranceList[position]
return message.from.id
}

class UtteranceViewHolder(v: View, private val menuInflater: MenuInflater, private val i: Int) : RecyclerView.ViewHolder(v), View.OnCreateContextMenuListener {
val vUtterance = v.utterance

init {
v.setOnCreateContextMenuListener(this)
}

override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) {
when (i) {
UtteranceFrom.USER.id -> menuInflater.inflate(R.menu.menu_user_utterance_context, menu)
UtteranceFrom.MYCROFT.id -> menuInflater.inflate(R.menu.menu_mycroft_utterance_context, menu)
}
}
}
}
9 changes: 9 additions & 0 deletions mobile/src/main/res/drawable/ic_keyboard_black_24dp.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M20,5L4,5c-1.1,0 -1.99,0.9 -1.99,2L2,17c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,7c0,-1.1 -0.9,-2 -2,-2zM11,8h2v2h-2L11,8zM11,11h2v2h-2v-2zM8,8h2v2L8,10L8,8zM8,11h2v2L8,13v-2zM7,13L5,13v-2h2v2zM7,10L5,10L5,8h2v2zM16,17L8,17v-2h8v2zM16,13h-2v-2h2v2zM16,10h-2L14,8h2v2zM19,13h-2v-2h2v2zM19,10h-2L17,8h2v2z"/>
</vector>
9 changes: 9 additions & 0 deletions mobile/src/main/res/drawable/ic_mic_black_24dp.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12,14c1.66,0 2.99,-1.34 2.99,-3L15,5c0,-1.66 -1.34,-3 -3,-3S9,3.34 9,5v6c0,1.66 1.34,3 3,3zM17.3,11c0,3 -2.54,5.1 -5.3,5.1S6.7,14 6.7,11L5,11c0,3.41 2.72,6.23 6,6.72L11,21h2v-3.28c3.28,-0.48 6,-3.3 6,-6.72h-1.7z"/>
</vector>
9 changes: 9 additions & 0 deletions mobile/src/main/res/drawable/ic_send_black_24dp.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z"/>
</vector>
9 changes: 9 additions & 0 deletions mobile/src/main/res/drawable/ic_volume_off_black_24dp.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v2.21l2.45,2.45c0.03,-0.2 0.05,-0.41 0.05,-0.63zM19,12c0,0.94 -0.2,1.82 -0.54,2.64l1.51,1.51C20.63,14.91 21,13.5 21,12c0,-4.28 -2.99,-7.86 -7,-8.77v2.06c2.89,0.86 5,3.54 5,6.71zM4.27,3L3,4.27 7.73,9L3,9v6h4l5,5v-6.73l4.25,4.25c-0.67,0.52 -1.42,0.93 -2.25,1.18v2.06c1.38,-0.31 2.63,-0.95 3.69,-1.81L19.73,21 21,19.73l-9,-9L4.27,3zM12,4L9.91,6.09 12,8.18L12,4z"/>
</vector>
9 changes: 9 additions & 0 deletions mobile/src/main/res/drawable/ic_volume_up_black_24dp.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M3,9v6h4l5,5L12,4L7,9L3,9zM16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM14,3.23v2.06c2.89,0.86 5,3.54 5,6.71s-2.11,5.85 -5,6.71v2.06c4.01,-0.91 7,-4.49 7,-8.77s-2.99,-7.86 -7,-8.77z"/>
</vector>
9 changes: 9 additions & 0 deletions mobile/src/main/res/drawable/kb_to_mic.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:drawable="@drawable/ic_keyboard_black_24dp"
android:state_checked="true" />
<item
android:drawable="@drawable/ic_mic_black_24dp"
android:state_checked="false" />
</selector>
Loading