diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index 14420a6bb..b239af13c 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -1070,6 +1070,15 @@ class MainActivity : BaseActivity(), Logging, chooseMapStyle() return true } + R.id.preferences_quick_chat -> { + val fragmentManager: FragmentManager = supportFragmentManager + val fragmentTransaction: FragmentTransaction = fragmentManager.beginTransaction() + val nameFragment = QuickChatSettingsFragment() + fragmentTransaction.add(R.id.mainActivityLayout, nameFragment) + fragmentTransaction.addToBackStack(null) + fragmentTransaction.commit() + return true + } else -> super.onOptionsItemSelected(item) } } 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..0bef1c756 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/database/QuickChatActionRepository.kt @@ -0,0 +1,38 @@ +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(action: QuickChatAction) = withContext(Dispatchers.IO) { + quickChatActionDao.delete(action) + } + + suspend fun update(action: QuickChatAction) = withContext(Dispatchers.IO) { + quickChatActionDao.update(action) + } + + suspend fun setItemPosition(uuid: Long, newPos: Int) = withContext(Dispatchers.IO) { + quickChatActionDao.updateActionPosition(uuid, newPos) + } +} \ 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..8af931323 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/database/dao/QuickChatActionDao.kt @@ -0,0 +1,37 @@ +package com.geeksville.mesh.database.dao + +import androidx.room.* +import com.geeksville.mesh.database.entity.QuickChatAction +import kotlinx.coroutines.flow.Flow + +@Dao +interface QuickChatActionDao { + + @Query("Select * from quick_chat order by position asc") + 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) + + @Transaction + fun delete(action: QuickChatAction) { + _delete(action.uuid) + decrementPositionsAfter(action.position) + } + + @Update + fun update(action: QuickChatAction) + + @Query("Update quick_chat set position=:position WHERE uuid=:uuid") + fun updateActionPosition(uuid: Long, position: Int) + + @Query("Update quick_chat set position=position-1 where position>=:position") + fun decrementPositionsAfter(position: Int) + +} \ 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..f9cc237f6 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/database/entity/QuickChatAction.kt @@ -0,0 +1,19 @@ +package com.geeksville.mesh.database.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.Index +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, + @ColumnInfo(name="position") val position: Int) { + enum class Mode { + Append, + Instant, + } +} \ No newline at end of file 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 a0c620751..daf27fa2d 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -14,11 +14,14 @@ 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 import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -61,6 +64,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 +74,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 +91,11 @@ class UIViewModel @Inject constructor( _localConfig.value = config } } + viewModelScope.launch { + quickChatActionRepository.getAllActions().collect { actions -> + _quickChatActions.value = actions + } + } debug("ViewModel created") } @@ -445,5 +460,43 @@ class UIViewModel @Inject constructor( } } + fun addQuickChatAction(name: String, value: String, mode: QuickChatAction.Mode) { + viewModelScope.launch(Dispatchers.Main) { + val action = QuickChatAction(0, name, value, mode, _quickChatActions.value.size) + quickChatActionRepository.insert(action) + } + } + + fun deleteQuickChatAction(action: QuickChatAction) { + viewModelScope.launch(Dispatchers.Main) { + quickChatActionRepository.delete(action) + } + } + + 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, + action.position + ) + quickChatActionRepository.update(newAction) + } + } + + fun updateActionPositions(actions: List) { + viewModelScope.launch(Dispatchers.Main) { + for (position in actions.indices) { + quickChatActionRepository.setItemPosition(actions[position].uuid, position) + } + } + } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/DragManageAdapter.kt b/app/src/main/java/com/geeksville/mesh/ui/DragManageAdapter.kt new file mode 100644 index 000000000..913fa8a51 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/DragManageAdapter.kt @@ -0,0 +1,33 @@ +package com.geeksville.mesh.ui + +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.RecyclerView + +class DragManageAdapter(var adapter: SwapAdapter, dragDirs: Int, swipeDirs: Int) : + ItemTouchHelper.SimpleCallback(dragDirs, swipeDirs) { + interface SwapAdapter { + fun swapItems(fromPosition: Int, toPosition: Int) + fun commitSwaps() + } + + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean { + adapter.swapItems(viewHolder.absoluteAdapterPosition, target.absoluteAdapterPosition) + return true + } + + override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) { + super.onSelectedChanged(viewHolder, actionState) + + if (actionState == ItemTouchHelper.ACTION_STATE_IDLE) { + adapter.commitSwaps() + } + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + TODO("Not yet implemented") + } +} \ No newline at end of file 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 e925032ff..a6566ca72 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt @@ -3,25 +3,25 @@ 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.EditText -import android.widget.ImageView -import android.widget.TextView +import android.widget.* 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.UIViewModel @@ -57,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, _ -> @@ -293,9 +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 + 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 { + // This seems kinda hacky it might be better to replace with a recycler view + binding.quickChatLayout.removeAllViews() + for (action in actions) { + val button = Button(context) + button.setText(action.name) + button.isEnabled = isConnected + 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) + } + } + 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 new file mode 100644 index 000000000..dd60d5b0f --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/QuickChatActionAdapter.kt @@ -0,0 +1,68 @@ +package com.geeksville.mesh.ui + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.RecyclerView +import com.geeksville.mesh.R +import com.geeksville.mesh.database.entity.QuickChatAction + +class QuickChatActionAdapter internal constructor( + private val context: Context, + private val onEdit: (action: QuickChatAction) -> Unit, + private val repositionAction: (fromPos: Int, toPos: Int) -> Unit, + private val commitAction: () -> Unit, +) : RecyclerView.Adapter(), DragManageAdapter.SwapAdapter { + + private val inflater: LayoutInflater = LayoutInflater.from(context) + private var actions = emptyList() + + inner class ActionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val container: View = itemView.findViewById(R.id.quickChatActionContainer) + val actionName: TextView = itemView.findViewById(R.id.quickChatActionName) + val actionValue: TextView = itemView.findViewById(R.id.quickChatActionValue) + val actionEdit: View = itemView.findViewById(R.id.quickChatActionEdit) + val actionInstant: View = itemView.findViewById(R.id.quickChatActionInstant) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ActionViewHolder { + val itemView = inflater.inflate(R.layout.adapter_quick_chat_action_layout, parent, false) + return ActionViewHolder(itemView) + } + + override fun onBindViewHolder(holder: ActionViewHolder, position: Int) { + val current = actions[position] + holder.actionName.text = current.name + holder.actionValue.text = current.message + val isInstant = current.mode == QuickChatAction.Mode.Instant + holder.actionInstant.visibility = if (isInstant) View.VISIBLE else View.INVISIBLE + if (isInstant) { + holder.container.backgroundTintList = ContextCompat.getColorStateList(context, R.color.colorMyMsg) + } else { + holder.container.backgroundTintList = null + } + holder.actionEdit.setOnClickListener { + onEdit(current) + } + } + + internal fun setActions(actions: List) { + this.actions = actions + notifyDataSetChanged() + } + + override fun getItemCount() = actions.size + + override fun swapItems(fromPosition: Int, toPosition: Int) { + repositionAction(fromPosition, toPosition) + notifyItemMoved(fromPosition, toPosition) + } + + override fun commitSwaps() { + commitAction() + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/ui/QuickChatSettingsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/QuickChatSettingsFragment.kt new file mode 100644 index 000000000..56c53ed33 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/QuickChatSettingsFragment.kt @@ -0,0 +1,175 @@ +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 android.widget.ImageView +import androidx.core.content.ContextCompat +import androidx.core.view.isVisible +import androidx.core.widget.addTextChangedListener +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.asLiveData +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.LinearLayoutManager +import com.geeksville.android.Logging +import com.geeksville.mesh.R +import com.geeksville.mesh.database.entity.QuickChatAction +import com.geeksville.mesh.databinding.QuickChatSettingsFragmentBinding +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 +import java.util.* + +@AndroidEntryPoint +class QuickChatSettingsFragment : ScreenFragment("Quick Chat settings"), Logging { + private var _binding: QuickChatSettingsFragmentBinding? = null + + private val binding get() = _binding!! + + private val model: UIViewModel by activityViewModels() + + private lateinit var actions: List + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = QuickChatSettingsFragmentBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.quickChatSettingsCreateButton.setOnClickListener { + val builder = createEditDialog(requireContext(), "New quick chat") + + builder.builder.setPositiveButton("Add") { view, x -> + + val name = builder.nameInput.text.toString().trim() + val message = builder.messageInput.text.toString() + if (builder.isNotEmpty()) + model.addQuickChatAction( + name, message, + if (builder.modeSwitch.isChecked) QuickChatAction.Mode.Instant else QuickChatAction.Mode.Append + ) + } + + val dialog = builder.builder.create() + dialog.show() + } + + val quickChatActionAdapter = + QuickChatActionAdapter(requireContext(), { action: QuickChatAction -> + val builder = createEditDialog(requireContext(), "Edit quick chat") + builder.nameInput.setText(action.name) + builder.messageInput.setText(action.message) + val isInstant = action.mode == QuickChatAction.Mode.Instant + builder.modeSwitch.isChecked = isInstant + builder.instantImage.visibility = if (isInstant) View.VISIBLE else View.INVISIBLE + + builder.builder.setNegativeButton(R.string.delete) { _, _ -> + model.deleteQuickChatAction(action) + } + builder.builder.setPositiveButton(R.string.save_btn) { _, _ -> + 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() + }, { fromPos, toPos -> + Collections.swap(actions, fromPos, toPos) + }, { + model.updateActionPositions(actions) + }) + + val dragCallback = + DragManageAdapter(quickChatActionAdapter, ItemTouchHelper.UP or ItemTouchHelper.DOWN, 0) + val helper = ItemTouchHelper(dragCallback) + + binding.quickChatSettingsView.apply { + this.layoutManager = LinearLayoutManager(requireContext()) + this.adapter = quickChatActionAdapter + helper.attachToRecyclerView(this) + } + + model.quickChatActions.asLiveData().observe(viewLifecycleOwner) { actions -> + actions?.let { + quickChatActionAdapter.setActions(actions) + this.actions = actions + } + } + } + + data class DialogBuilder( + val builder: MaterialAlertDialogBuilder, + val nameInput: EditText, + val messageInput: EditText, + val modeSwitch: SwitchMaterial, + val instantImage: ImageView + ) { + fun isNotEmpty(): Boolean = nameInput.text.isNotEmpty() and messageInput.text.isNotEmpty() + } + + private fun getMessageName(message: String): String { + return if (message.length <= 3) { + message.uppercase() + } else { + buildString { + append(message.first().uppercase()) + append(message[message.length / 2].uppercase()) + append(message.last().uppercase()) + } + } + } + + private fun createEditDialog(context: Context, title: String): DialogBuilder { + val builder = MaterialAlertDialogBuilder(context) + builder.setTitle(title) + + val layout = + LayoutInflater.from(requireContext()).inflate(R.layout.dialog_add_quick_chat, null) + + val nameInput: EditText = layout.findViewById(R.id.addQuickChatName) + val messageInput: EditText = layout.findViewById(R.id.addQuickChatMessage) + val modeSwitch: SwitchMaterial = layout.findViewById(R.id.addQuickChatMode) + val instantImage: ImageView = layout.findViewById(R.id.addQuickChatInsant) + instantImage.visibility = if (modeSwitch.isChecked) View.VISIBLE else View.INVISIBLE + + var nameHasChanged = false + + modeSwitch.setOnCheckedChangeListener { _, _ -> + if (modeSwitch.isChecked) { + modeSwitch.setText(R.string.mode_instant) + instantImage.visibility = View.VISIBLE + } else { + modeSwitch.setText(R.string.mode_append) + instantImage.visibility = View.INVISIBLE + } + } + + messageInput.addTextChangedListener { text -> + if (!nameHasChanged) { + nameInput.setText(getMessageName(text.toString())) + } + } + + nameInput.addTextChangedListener { + if (nameInput.isFocused) nameHasChanged = true + } + + builder.setView(layout) + + return DialogBuilder(builder, nameInput, messageInput, modeSwitch, instantImage) + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_baseline_drag_handle_24.xml b/app/src/main/res/drawable/ic_baseline_drag_handle_24.xml new file mode 100644 index 000000000..e13f29fd3 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_drag_handle_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_edit_24.xml b/app/src/main/res/drawable/ic_baseline_edit_24.xml new file mode 100644 index 000000000..2cda9c112 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_edit_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_fast_forward_24.xml b/app/src/main/res/drawable/ic_baseline_fast_forward_24.xml new file mode 100644 index 000000000..ac50e268d --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_fast_forward_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/adapter_quick_chat_action_layout.xml b/app/src/main/res/layout/adapter_quick_chat_action_layout.xml new file mode 100644 index 000000000..d37bae544 --- /dev/null +++ b/app/src/main/res/layout/adapter_quick_chat_action_layout.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_add_quick_chat.xml b/app/src/main/res/layout/dialog_add_quick_chat.xml new file mode 100644 index 000000000..7a7521127 --- /dev/null +++ b/app/src/main/res/layout/dialog_add_quick_chat.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/messages_fragment.xml b/app/src/main/res/layout/messages_fragment.xml index 5cbbd5b92..db6c22f67 100644 --- a/app/src/main/res/layout/messages_fragment.xml +++ b/app/src/main/res/layout/messages_fragment.xml @@ -1,6 +1,7 @@ @@ -37,10 +38,30 @@ android:layout_height="0dp" android:layout_margin="8dp" android:contentDescription="@string/text_messages" - app:layout_constraintBottom_toTopOf="@+id/textInputLayout" + app:layout_constraintBottom_toTopOf="@+id/quickChatView" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/toolbar" /> + app:layout_constraintTop_toBottomOf="@id/toolbar" > + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml index 748e1b9bf..042f6b7b5 100644 --- a/app/src/main/res/menu/menu_main.xml +++ b/app/src/main/res/menu/menu_main.xml @@ -36,6 +36,10 @@ android:id="@+id/preferences_map_style" android:title="@string/preferences_map_style" app:showAsAction="withText" /> + Resend Shutdown Reboot + Message + Append to message + Instantly send