diff --git a/app/src/main/java/com/geeksville/mesh/database/DatabaseModule.kt b/app/src/main/java/com/geeksville/mesh/database/DatabaseModule.kt index 1f2732ddf..f849637b7 100644 --- a/app/src/main/java/com/geeksville/mesh/database/DatabaseModule.kt +++ b/app/src/main/java/com/geeksville/mesh/database/DatabaseModule.kt @@ -2,6 +2,7 @@ package com.geeksville.mesh.database import android.app.Application import com.geeksville.mesh.database.dao.PacketDao +import com.geeksville.mesh.database.dao.QuickChatActionDao import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -20,4 +21,9 @@ class DatabaseModule { fun providePacketDao(database: MeshtasticDatabase): PacketDao { return database.packetDao() } + + @Provides + fun provideQuickChatActionDao(database: MeshtasticDatabase): QuickChatActionDao { + return database.quickChatActionDao() + } } \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/database/MeshtasticDatabase.kt b/app/src/main/java/com/geeksville/mesh/database/MeshtasticDatabase.kt index 6d11eb1c3..b49e628f8 100644 --- a/app/src/main/java/com/geeksville/mesh/database/MeshtasticDatabase.kt +++ b/app/src/main/java/com/geeksville/mesh/database/MeshtasticDatabase.kt @@ -5,11 +5,14 @@ import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase import com.geeksville.mesh.database.dao.PacketDao +import com.geeksville.mesh.database.dao.QuickChatActionDao import com.geeksville.mesh.database.entity.Packet +import com.geeksville.mesh.database.entity.QuickChatAction -@Database(entities = [Packet::class], version = 1, exportSchema = false) +@Database(entities = [Packet::class, QuickChatAction::class], version = 2, exportSchema = false) abstract class MeshtasticDatabase : RoomDatabase() { abstract fun packetDao(): PacketDao + abstract fun quickChatActionDao(): QuickChatActionDao companion object { fun getDatabase(context: Context): MeshtasticDatabase { diff --git a/app/src/main/java/com/geeksville/mesh/database/QuickChatActionRepository.kt b/app/src/main/java/com/geeksville/mesh/database/QuickChatActionRepository.kt new file mode 100644 index 000000000..b95098175 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/database/QuickChatActionRepository.kt @@ -0,0 +1,34 @@ +package com.geeksville.mesh.database + +import com.geeksville.mesh.database.dao.QuickChatActionDao +import com.geeksville.mesh.database.entity.QuickChatAction +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.withContext +import javax.inject.Inject + +class QuickChatActionRepository @Inject constructor(private val quickChatDaoLazy: dagger.Lazy) { + private val quickChatActionDao by lazy { + quickChatDaoLazy.get() + } + + suspend fun getAllActions(): Flow> = withContext(Dispatchers.IO) { + quickChatActionDao.getAll() + } + + suspend fun insert(action: QuickChatAction) = withContext(Dispatchers.IO) { + quickChatActionDao.insert(action) + } + + suspend fun deleteAll() = withContext(Dispatchers.IO) { + quickChatActionDao.deleteAll() + } + + suspend fun delete(uuid: Long) = withContext(Dispatchers.IO) { + quickChatActionDao.delete(uuid) + } + + suspend fun update(action:QuickChatAction) = withContext(Dispatchers.IO) { + quickChatActionDao.update(action) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/database/dao/QuickChatActionDao.kt b/app/src/main/java/com/geeksville/mesh/database/dao/QuickChatActionDao.kt new file mode 100644 index 000000000..138b1131b --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/database/dao/QuickChatActionDao.kt @@ -0,0 +1,28 @@ +package com.geeksville.mesh.database.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.Query +import androidx.room.Update +import com.geeksville.mesh.database.entity.QuickChatAction +import kotlinx.coroutines.flow.Flow + +@Dao +interface QuickChatActionDao { + + @Query("Select * from quick_chat") + fun getAll(): Flow> + + @Insert + fun insert(action: QuickChatAction) + + @Query("Delete from quick_chat") + fun deleteAll() + + @Query("Delete from quick_chat where uuid=:uuid") + fun delete(uuid: Long) + + @Update + fun update(action: QuickChatAction) + +} \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/database/entity/QuickChatAction.kt b/app/src/main/java/com/geeksville/mesh/database/entity/QuickChatAction.kt new file mode 100644 index 000000000..26da5ecb9 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/database/entity/QuickChatAction.kt @@ -0,0 +1,17 @@ +package com.geeksville.mesh.database.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "quick_chat") +data class QuickChatAction( + @PrimaryKey(autoGenerate = true) val uuid: Long, + @ColumnInfo(name="name") val name: String, + @ColumnInfo(name="message") val message: String, + @ColumnInfo(name="mode") val mode: Mode) { + enum class Mode { + Append, + Instant, + } +} \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/model/QuickChatAction.kt b/app/src/main/java/com/geeksville/mesh/model/QuickChatAction.kt deleted file mode 100644 index 96e251512..000000000 --- a/app/src/main/java/com/geeksville/mesh/model/QuickChatAction.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.geeksville.mesh.model - -data class QuickChatAction( - val name: String, - val message: String, - val mode: Mode) { - enum class Mode { - Append, - Instant, - } -} diff --git a/app/src/main/java/com/geeksville/mesh/model/UIState.kt b/app/src/main/java/com/geeksville/mesh/model/UIState.kt index d513efd43..10ae8200d 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -14,7 +14,9 @@ import androidx.lifecycle.viewModelScope import com.geeksville.android.Logging import com.geeksville.mesh.* import com.geeksville.mesh.database.PacketRepository +import com.geeksville.mesh.database.QuickChatActionRepository import com.geeksville.mesh.database.entity.Packet +import com.geeksville.mesh.database.entity.QuickChatAction import com.geeksville.mesh.repository.datastore.LocalConfigRepository import com.geeksville.mesh.service.MeshService import com.geeksville.mesh.util.positionToMeter @@ -61,6 +63,7 @@ class UIViewModel @Inject constructor( private val app: Application, private val packetRepository: PacketRepository, private val localConfigRepository: LocalConfigRepository, + private val quickChatActionRepository: QuickChatActionRepository, private val preferences: SharedPreferences ) : ViewModel(), Logging { @@ -70,6 +73,12 @@ class UIViewModel @Inject constructor( private val _localConfig = MutableLiveData() val localConfig: LiveData get() = _localConfig + private val _quickChatActions = + MutableStateFlow>( + emptyList() + ) + val quickChatActions: StateFlow> = _quickChatActions + init { viewModelScope.launch { packetRepository.getAllPackets().collect { packets -> @@ -81,6 +90,11 @@ class UIViewModel @Inject constructor( _localConfig.value = config } } + viewModelScope.launch { + quickChatActionRepository.getAllActions().collect { actions -> + _quickChatActions.value = actions + } + } debug("ViewModel created") } @@ -445,12 +459,34 @@ class UIViewModel @Inject constructor( } } - private val _quickChatActions = mutableListOf() - val quickChatActions: List get() = _quickChatActions - fun addQuickChatAction(name: String, value: String, mode: QuickChatAction.Mode) { - val action = QuickChatAction(name, value, mode) - _quickChatActions.add(action) + viewModelScope.launch(Dispatchers.Main) { + val action = QuickChatAction(0, name, value, mode) + quickChatActionRepository.insert(action) + } + } + + fun deleteQuickChatAction(action: QuickChatAction) { + viewModelScope.launch(Dispatchers.Main) { + quickChatActionRepository.delete(action.uuid) + } + } + + fun updateQuickChatAction( + action: QuickChatAction, + name: String?, + message: String?, + mode: QuickChatAction.Mode? + ) { + viewModelScope.launch(Dispatchers.Main) { + val newAction = QuickChatAction( + action.uuid, + name ?: action.name, + message ?: action.message, + mode ?: action.mode + ) + quickChatActionRepository.update(newAction) + } } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt index c51d48c31..084d33a71 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt @@ -3,7 +3,6 @@ package com.geeksville.mesh.ui import android.graphics.Color import android.graphics.drawable.GradientDrawable import android.os.Bundle -import android.text.InputType import android.view.* import android.view.inputmethod.EditorInfo import android.widget.* @@ -11,18 +10,20 @@ import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ActionMode import androidx.cardview.widget.CardView import androidx.core.content.ContextCompat +import androidx.core.view.allViews import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.fragment.app.setFragmentResultListener +import androidx.lifecycle.asLiveData import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.geeksville.android.Logging import com.geeksville.mesh.DataPacket import com.geeksville.mesh.MessageStatus import com.geeksville.mesh.R +import com.geeksville.mesh.database.entity.QuickChatAction import com.geeksville.mesh.databinding.AdapterMessageLayoutBinding import com.geeksville.mesh.databinding.MessagesFragmentBinding -import com.geeksville.mesh.model.QuickChatAction import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.service.MeshService import com.google.android.material.chip.Chip @@ -56,6 +57,8 @@ class MessagesFragment : Fragment(), Logging { private val model: UIViewModel by activityViewModels() + private var isConnected = false + // Allows textMultiline with IME_ACTION_SEND private fun EditText.onActionSend(func: () -> Unit) { setOnEditorActionListener { _, actionId, _ -> @@ -292,34 +295,45 @@ class MessagesFragment : Fragment(), Logging { // If connection state _OR_ myID changes we have to fix our ability to edit outgoing messages model.connectionState.observe(viewLifecycleOwner) { connectionState -> // If we don't know our node ID and we are offline don't let user try to send - val connected = connectionState == MeshService.ConnectionState.CONNECTED - binding.textInputLayout.isEnabled = connected - binding.sendButton.isEnabled = connected - } - - for (action in model.quickChatActions) { - val button = Button(context) - button.setText(action.name) - if (action.mode == QuickChatAction.Mode.Instant) { - button.backgroundTintList = ContextCompat.getColorStateList(requireActivity(), R.color.colorMyMsg) - - } - button.setOnClickListener { - if (action.mode == QuickChatAction.Mode.Append) { - val originalText = binding.messageInputText.text ?: "" - val needsSpace = !originalText.endsWith(' ') && originalText.isNotEmpty() - val newText = buildString { - append(originalText) - if (needsSpace) append(' ') - append(action.message) - } - binding.messageInputText.setText(newText) - binding.messageInputText.setSelection(newText.length) - } else { - model.messagesState.sendMessage(action.message, contactId) + isConnected = connectionState == MeshService.ConnectionState.CONNECTED + binding.textInputLayout.isEnabled = isConnected + binding.sendButton.isEnabled = isConnected + for (subView: View in binding.quickChatLayout.allViews) { + if (subView is Button) { + subView.isEnabled = isConnected + } + } + } + + model.quickChatActions.asLiveData().observe(viewLifecycleOwner) { actions -> + actions?.let { + for (action in actions) { + val button = Button(context) + button.setText(action.name) + button.isEnabled = isConnected + if (action.mode == QuickChatAction.Mode.Instant) { + //button.setBackgroundColor(Color.rgb(200, 200, 200)) + button.backgroundTintList = ContextCompat.getColorStateList(requireActivity(), R.color.colorMyMsg) + + } + button.setOnClickListener { + if (action.mode == QuickChatAction.Mode.Append) { + val originalText = binding.messageInputText.text ?: "" + val needsSpace = !originalText.endsWith(' ') && originalText.isNotEmpty() + val newText = buildString { + append(originalText) + if (needsSpace) append(' ') + append(action.message) + } + binding.messageInputText.setText(newText) + binding.messageInputText.setSelection(newText.length) + } else { + model.messagesState.sendMessage(action.message, contactId) + } + } + binding.quickChatLayout.addView(button) } } - binding.quickChatLayout.addView(button) } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/QuickChatActionAdapter.kt b/app/src/main/java/com/geeksville/mesh/ui/QuickChatActionAdapter.kt index d774e31f1..e4061f812 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/QuickChatActionAdapter.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/QuickChatActionAdapter.kt @@ -8,7 +8,7 @@ import android.view.ViewGroup import android.widget.TextView import androidx.recyclerview.widget.RecyclerView import com.geeksville.mesh.R -import com.geeksville.mesh.model.QuickChatAction +import com.geeksville.mesh.database.entity.QuickChatAction class QuickChatActionAdapter internal constructor( context: Context, diff --git a/app/src/main/java/com/geeksville/mesh/ui/QuickChatSettingsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/QuickChatSettingsFragment.kt index c84ae6beb..bcb7a3a19 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/QuickChatSettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/QuickChatSettingsFragment.kt @@ -1,14 +1,22 @@ package com.geeksville.mesh.ui +import android.content.Context import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.EditText +import androidx.core.widget.addTextChangedListener import androidx.fragment.app.activityViewModels +import androidx.lifecycle.asLiveData +import androidx.recyclerview.widget.LinearLayoutManager import com.geeksville.android.Logging -import com.geeksville.mesh.databinding.AdvancedSettingsBinding +import com.geeksville.mesh.R import com.geeksville.mesh.databinding.QuickChatSettingsFragmentBinding +import com.geeksville.mesh.database.entity.QuickChatAction import com.geeksville.mesh.model.UIViewModel +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.google.android.material.switchmaterial.SwitchMaterial import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint @@ -41,23 +49,17 @@ class QuickChatSettingsFragment : ScreenFragment("Quick Chat settings"), Logging val name = builder.nameInput.text.toString().trim() val message = builder.messageInput.text.toString() - if (name.isNotEmpty() and message.isNotEmpty()) + if (builder.isNotEmpty()) model.addQuickChatAction( name, message, if (builder.modeSwitch.isChecked) QuickChatAction.Mode.Instant else QuickChatAction.Mode.Append ) - // TODO - } - builder.builder.setNegativeButton("Cancel") { _, _ -> - // TODO } val dialog = builder.builder.create() - dialog.getButton(0).isEnabled = false dialog.show() } - model.addQuickChatAction("TST", "Test", QuickChatAction.Mode.Append) val quickChatActionAdapter = QuickChatActionAdapter(requireContext()) { action: QuickChatAction -> val builder = createEditDialog(requireContext(), "Edit quick chat") @@ -65,9 +67,18 @@ class QuickChatSettingsFragment : ScreenFragment("Quick Chat settings"), Logging builder.messageInput.setText(action.message) builder.modeSwitch.isChecked = action.mode == QuickChatAction.Mode.Instant - builder.builder.setNegativeButton(R.string.cancel) { _, _ -> } + builder.builder.setNegativeButton(R.string.delete) { _, _ -> + model.deleteQuickChatAction(action) + } builder.builder.setPositiveButton(R.string.save_btn) { _, _ -> - // TODO + if (builder.isNotEmpty()) { + model.updateQuickChatAction( + action, + builder.nameInput.text.toString(), + builder.messageInput.text.toString(), + if (builder.modeSwitch.isChecked) QuickChatAction.Mode.Instant else QuickChatAction.Mode.Append + ) + } } val dialog = builder.builder.create() dialog.show() @@ -78,7 +89,10 @@ class QuickChatSettingsFragment : ScreenFragment("Quick Chat settings"), Logging this.adapter = quickChatActionAdapter } - quickChatActionAdapter.setActions(model.quickChatActions) + model.quickChatActions.asLiveData().observe(viewLifecycleOwner) { actions -> + actions?.let { quickChatActionAdapter.setActions(actions) } + } + Log.d(TAG, "viewCreation done") } @@ -87,7 +101,9 @@ class QuickChatSettingsFragment : ScreenFragment("Quick Chat settings"), Logging val nameInput: EditText, val messageInput: EditText, val modeSwitch: SwitchMaterial - ) + ) { + fun isNotEmpty(): Boolean = nameInput.text.isNotEmpty() and messageInput.text.isNotEmpty() + } private fun getMessageName(message: String): String { return if (message.length <= 3) { @@ -128,7 +144,6 @@ class QuickChatSettingsFragment : ScreenFragment("Quick Chat settings"), Logging if (nameInput.isFocused) nameHasChanged = true } - // TODO: Don't enable positive button until there is name and message builder.setView(layout) return DialogBuilder(builder, nameInput, messageInput, modeSwitch)