feature: ver y eliminar sesiones
This commit is contained in:
parent
a1c627ca59
commit
484f92e531
4
.idea/assetWizardSettings.xml
generated
4
.idea/assetWizardSettings.xml
generated
@ -308,7 +308,7 @@
|
|||||||
<PersistentState>
|
<PersistentState>
|
||||||
<option name="values">
|
<option name="values">
|
||||||
<map>
|
<map>
|
||||||
<entry key="url" value="file:/$USER_HOME$/AppData/Local/Android/Sdk/icons/material/materialicons/file_upload/baseline_file_upload_24.xml" />
|
<entry key="url" value="file:/$USER_HOME$/AppData/Local/Android/Sdk/icons/material/materialicons/password/baseline_password_24.xml" />
|
||||||
</map>
|
</map>
|
||||||
</option>
|
</option>
|
||||||
</PersistentState>
|
</PersistentState>
|
||||||
@ -318,7 +318,7 @@
|
|||||||
</option>
|
</option>
|
||||||
<option name="values">
|
<option name="values">
|
||||||
<map>
|
<map>
|
||||||
<entry key="outputName" value="baseline_file_upload_24" />
|
<entry key="outputName" value="baseline_password_24" />
|
||||||
<entry key="sourceFile" value="C:\Users\erike\Downloads\face-kiss-wink-heart-solid.svg" />
|
<entry key="sourceFile" value="C:\Users\erike\Downloads\face-kiss-wink-heart-solid.svg" />
|
||||||
</map>
|
</map>
|
||||||
</option>
|
</option>
|
||||||
|
|||||||
@ -0,0 +1,85 @@
|
|||||||
|
package com.isolaatti.common.generic_items_list
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||||
|
import com.isolaatti.databinding.GenericListItemBinding
|
||||||
|
|
||||||
|
class GenericItemsListRecyclerViewAdapter<T>(
|
||||||
|
private val onClick: ((item: GenericListItem<T>) -> Unit),
|
||||||
|
private val onItemsSelectedCountUpdate: ((count: Int) -> Unit)? = null,
|
||||||
|
private val onDeleteMode: ((enabled: Boolean) -> Unit)? = null,
|
||||||
|
) : ListAdapter<GenericListItem<T>, GenericItemsListRecyclerViewAdapter.GenericItemViewHolder>(getDiffCallback<T>()) {
|
||||||
|
class GenericItemViewHolder(val genericListItemBinding: GenericListItemBinding) : ViewHolder(genericListItemBinding.root)
|
||||||
|
|
||||||
|
|
||||||
|
var deleteMode: Boolean = false
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
if(!value) {
|
||||||
|
currentList.forEach { it.delete = false }
|
||||||
|
}
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSelectedItems(): List<GenericListItem<T>> {
|
||||||
|
return currentList.filter { it.delete }
|
||||||
|
}
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun <T> getDiffCallback(): DiffUtil.ItemCallback<GenericListItem<T>>{
|
||||||
|
return object: DiffUtil.ItemCallback<GenericListItem<T>>() {
|
||||||
|
override fun areItemsTheSame(oldItem: GenericListItem<T>, newItem: GenericListItem<T>): Boolean {
|
||||||
|
return oldItem.id == newItem.id
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItem: GenericListItem<T>, newItem: GenericListItem<T>): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GenericItemViewHolder {
|
||||||
|
return GenericItemViewHolder(GenericListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: GenericItemViewHolder, position: Int) {
|
||||||
|
val item = getItem(position)
|
||||||
|
holder.genericListItemBinding.title.text = item.title
|
||||||
|
holder.genericListItemBinding.subtitle.text = item.subtitle
|
||||||
|
|
||||||
|
holder.genericListItemBinding.root.isEnabled = !item.disabled
|
||||||
|
holder.genericListItemBinding.checkbox.isEnabled = !item.disabled
|
||||||
|
|
||||||
|
if(deleteMode) {
|
||||||
|
holder.genericListItemBinding.checkbox.visibility = View.VISIBLE
|
||||||
|
holder.genericListItemBinding.root.setOnClickListener {
|
||||||
|
holder.genericListItemBinding.checkbox.isChecked = !holder.genericListItemBinding.checkbox.isChecked
|
||||||
|
}
|
||||||
|
holder.genericListItemBinding.checkbox.setOnCheckedChangeListener { buttonView, isChecked ->
|
||||||
|
item.delete = isChecked
|
||||||
|
onItemsSelectedCountUpdate?.invoke(currentList.count { it.delete })
|
||||||
|
}
|
||||||
|
holder.genericListItemBinding.checkbox.isChecked = item.delete
|
||||||
|
holder.genericListItemBinding.root.setOnLongClickListener(null)
|
||||||
|
} else {
|
||||||
|
holder.genericListItemBinding.checkbox.visibility = View.GONE
|
||||||
|
holder.genericListItemBinding.checkbox.isChecked = false
|
||||||
|
holder.genericListItemBinding.root.setOnClickListener {
|
||||||
|
onClick(item)
|
||||||
|
}
|
||||||
|
holder.genericListItemBinding.root.setOnLongClickListener {
|
||||||
|
item.delete = true
|
||||||
|
onDeleteMode?.invoke(true)
|
||||||
|
onItemsSelectedCountUpdate?.invoke(currentList.count { it.delete })
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
package com.isolaatti.common.generic_items_list
|
||||||
|
|
||||||
|
data class GenericListItem <T>(
|
||||||
|
val id: T,
|
||||||
|
val title: String,
|
||||||
|
val subtitle: String = ""
|
||||||
|
) {
|
||||||
|
var delete: Boolean = false
|
||||||
|
var disabled: Boolean = false
|
||||||
|
}
|
||||||
@ -33,6 +33,7 @@ class AccountSettingsRepositoryImpl @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getSessions(): Flow<Resource<List<SessionsDto.SessionDto>>> = flow {
|
override fun getSessions(): Flow<Resource<List<SessionsDto.SessionDto>>> = flow {
|
||||||
|
emit(Resource.Loading())
|
||||||
try {
|
try {
|
||||||
val response = accountSettingsApi.getSessions().awaitResponse()
|
val response = accountSettingsApi.getSessions().awaitResponse()
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,53 @@
|
|||||||
|
package com.isolaatti.settings.presentation
|
||||||
|
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.isolaatti.settings.data.remote.SessionsDto
|
||||||
|
import com.isolaatti.settings.domain.AccountSettingsRepository
|
||||||
|
import com.isolaatti.utils.Resource
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class SessionsViewModel @Inject constructor(private val accountSettingsRepository: AccountSettingsRepository) : ViewModel() {
|
||||||
|
|
||||||
|
val sessions: MutableLiveData<List<SessionsDto.SessionDto>> = MutableLiveData()
|
||||||
|
val loading: MutableLiveData<Boolean> = MutableLiveData()
|
||||||
|
|
||||||
|
fun getSessions() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
accountSettingsRepository.getSessions().onEach {
|
||||||
|
|
||||||
|
when(it) {
|
||||||
|
is Resource.Error -> {
|
||||||
|
loading.postValue(false)
|
||||||
|
}
|
||||||
|
is Resource.Loading -> {
|
||||||
|
loading.postValue(true)
|
||||||
|
}
|
||||||
|
is Resource.Success -> {
|
||||||
|
loading.postValue(false)
|
||||||
|
sessions.postValue(it.data!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.flowOn(Dispatchers.IO).launchIn(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteSessions(ids: List<String>) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
accountSettingsRepository.signOutSessions(ids).onEach {
|
||||||
|
val newSessionsList = sessions.value?.filter { !ids.contains(it.id) }
|
||||||
|
if(newSessionsList != null) {
|
||||||
|
sessions.postValue(newSessionsList!!)
|
||||||
|
}
|
||||||
|
}.flowOn(Dispatchers.IO).launchIn(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,22 +1,142 @@
|
|||||||
package com.isolaatti.settings.ui
|
package com.isolaatti.settings.ui
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.ActionMode
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.widget.LinearLayout
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import com.isolaatti.R
|
||||||
|
import com.isolaatti.common.generic_items_list.GenericItemsListRecyclerViewAdapter
|
||||||
|
import com.isolaatti.common.generic_items_list.GenericListItem
|
||||||
import com.isolaatti.databinding.FragmentSettingsSessionsBinding
|
import com.isolaatti.databinding.FragmentSettingsSessionsBinding
|
||||||
|
import com.isolaatti.settings.presentation.SessionsViewModel
|
||||||
|
import com.isolaatti.utils.Resource
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
class SessionsFragment : Fragment() {
|
class SessionsFragment : Fragment() {
|
||||||
lateinit var viewBinding: FragmentSettingsSessionsBinding
|
lateinit var viewBinding: FragmentSettingsSessionsBinding
|
||||||
|
private val viewModel: SessionsViewModel by viewModels()
|
||||||
|
private var adapter: GenericItemsListRecyclerViewAdapter<String>? = null
|
||||||
|
|
||||||
|
private val contextBarCallback: ActionMode.Callback = object: ActionMode.Callback {
|
||||||
|
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||||
|
requireActivity().menuInflater.inflate(R.menu.images_context_menu, menu)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
|
||||||
|
return when(item?.itemId) {
|
||||||
|
R.id.delete_item -> {
|
||||||
|
showDeleteDialog()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||||
|
adapter?.deleteMode = false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private var actionMode: ActionMode? = null
|
||||||
|
|
||||||
|
private fun showDeleteDialog() {
|
||||||
|
val sessionsToDelete = adapter?.getSelectedItems()
|
||||||
|
MaterialAlertDialogBuilder(requireContext())
|
||||||
|
.setTitle(R.string.delete_sessions)
|
||||||
|
.setMessage(R.string.delete_sessions_conf_dialog_message)
|
||||||
|
.setPositiveButton(R.string.yes_continue) { _, _ ->
|
||||||
|
val ids = sessionsToDelete?.map { it.id }
|
||||||
|
if(ids != null) {
|
||||||
|
viewModel.deleteSessions(ids)
|
||||||
|
}
|
||||||
|
actionMode?.finish()
|
||||||
|
}
|
||||||
|
.setNegativeButton(R.string.no, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View {
|
||||||
viewBinding = FragmentSettingsSessionsBinding.inflate(inflater)
|
viewBinding = FragmentSettingsSessionsBinding.inflate(inflater)
|
||||||
|
|
||||||
return viewBinding.root
|
return viewBinding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
adapter = GenericItemsListRecyclerViewAdapter(
|
||||||
|
onClick = {},
|
||||||
|
onItemsSelectedCountUpdate = {
|
||||||
|
actionMode?.title = getString(R.string.sessions_selected_count, it)
|
||||||
|
actionMode?.menu?.findItem(R.id.delete_item)?.isEnabled = it > 0
|
||||||
|
},
|
||||||
|
onDeleteMode = {
|
||||||
|
adapter?.deleteMode = it
|
||||||
|
actionMode = requireActivity().startActionMode(contextBarCallback)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
viewBinding.recycler.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
|
||||||
|
viewBinding.recycler.adapter = adapter
|
||||||
|
setupObservers()
|
||||||
|
setupListeners()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
viewModel.getSessions()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupObservers() {
|
||||||
|
viewModel.sessions.observe(viewLifecycleOwner) { resource ->
|
||||||
|
viewBinding.loading.visibility = View.GONE
|
||||||
|
viewBinding.swipeToRefresh.isRefreshing = false
|
||||||
|
adapter?.submitList(resource?.map { sessionDto ->
|
||||||
|
val item = GenericListItem(
|
||||||
|
sessionDto.id,
|
||||||
|
title = "${if(sessionDto.current) getString(R.string.current) else ""} ${sessionDto.userAgent}",
|
||||||
|
subtitle = "${sessionDto.ip} - ${sessionDto.date.format(
|
||||||
|
DateTimeFormatter.ISO_DATE_TIME)}")
|
||||||
|
item.disabled = sessionDto.current
|
||||||
|
item
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.loading.observe(viewLifecycleOwner) {loading ->
|
||||||
|
if(!viewBinding.swipeToRefresh.isRefreshing) {
|
||||||
|
viewBinding.loading.visibility = if(loading) View.VISIBLE else View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupListeners() {
|
||||||
|
viewBinding.toolbar.setNavigationOnClickListener {
|
||||||
|
findNavController().popBackStack()
|
||||||
|
}
|
||||||
|
|
||||||
|
viewBinding.swipeToRefresh.setOnRefreshListener {
|
||||||
|
viewModel.getSessions()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -1,17 +1,40 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<TextView
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:id="@+id/textView6"
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:title="@string/sessions"
|
||||||
|
app:navigationIcon="@drawable/baseline_arrow_back_24"/>
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
|
android:id="@+id/swipe_to_refresh"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recycler"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
/>
|
||||||
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||||
|
android:id="@+id/loading"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Sessions"
|
android:layout_gravity="center"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
android:indeterminate="true"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
android:visibility="gone"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
tools:visibility="visible"/>
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
53
app/src/main/res/layout/generic_list_item.xml
Normal file
53
app/src/main/res/layout/generic_list_item.xml
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="70dp"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginVertical="4dp"
|
||||||
|
style="@style/Widget.Material3.CardView.Filled"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_margin="6dp">
|
||||||
|
|
||||||
|
<com.google.android.material.checkbox.MaterialCheckBox
|
||||||
|
android:id="@+id/checkbox"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:lines="1"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/subtitle"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/checkbox"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:text="Title" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/subtitle"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:lines="1"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/checkbox"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/title"
|
||||||
|
tools:text="Subtitle" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
11
app/src/main/res/menu/sessions_context_menu.xml
Normal file
11
app/src/main/res/menu/sessions_context_menu.xml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<item
|
||||||
|
android:id="@+id/delete_item"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:showAsAction="always"
|
||||||
|
android:icon="@drawable/baseline_delete_24"
|
||||||
|
android:title="@string/delete" />
|
||||||
|
</menu>
|
||||||
@ -152,4 +152,8 @@
|
|||||||
<string name="change_password">Change password</string>
|
<string name="change_password">Change password</string>
|
||||||
<string name="password_description">A password allows you to sign in securely. Keep this password safe.</string>
|
<string name="password_description">A password allows you to sign in securely. Keep this password safe.</string>
|
||||||
<string name="sessions">Sessions</string>
|
<string name="sessions">Sessions</string>
|
||||||
|
<string name="sessions_selected_count">Sessions selected: %d</string>
|
||||||
|
<string name="delete_sessions">Delete sessions</string>
|
||||||
|
<string name="delete_sessions_conf_dialog_message">The selected sessions will become invalid, sign out those devices.</string>
|
||||||
|
<string name="current">Current</string>
|
||||||
</resources>
|
</resources>
|
||||||
Loading…
x
Reference in New Issue
Block a user