compose: dispenser: Migrate dispensers logic to compose

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
Aayush Gupta
2025-12-10 15:42:39 +08:00
parent 6da40a5d9f
commit e6c1b5446b
16 changed files with 432 additions and 316 deletions

View File

@@ -0,0 +1,78 @@
/*
* SPDX-FileCopyrightText: 2025 The Calyx Institute
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.aurora.store.compose.composable
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import com.aurora.store.R
/**
* Composable to display dispenser URL in a list
* @param modifier The modifier to be applied to the composable
* @param url URL of the dispenser
* @param onClick Callback when this URL is clicked
* @param onClear Callback when the clear button is clicked
*/
@Composable
fun DispenserListItem(
modifier: Modifier = Modifier,
url: String,
onClick: () -> Unit = {},
onClear: () -> Unit = {}
) {
Row(
modifier = modifier
.fillMaxWidth()
.clickable(onClick = onClick)
.padding(dimensionResource(R.dimen.padding_small)),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(
modifier = Modifier.weight(1F),
horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_small))
) {
Icon(
painter = painterResource(R.drawable.ic_server),
contentDescription = null
)
Text(
text = url,
style = MaterialTheme.typography.bodyLarge,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
TextButton(onClick = onClear) {
Text(
text = stringResource(R.string.remove),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
}
@Preview(showBackground = true)
@Composable
private fun DispenserListItemPreview() {
DispenserListItem(url = "https://auroraoss.com/api/auth")
}

View File

@@ -24,6 +24,7 @@ import com.aurora.store.compose.ui.blacklist.BlacklistScreen
import com.aurora.store.compose.ui.commons.PermissionRationaleScreen
import com.aurora.store.compose.ui.details.AppDetailsScreen
import com.aurora.store.compose.ui.dev.DevProfileScreen
import com.aurora.store.compose.ui.dispenser.DispenserScreen
import com.aurora.store.compose.ui.downloads.DownloadsScreen
import com.aurora.store.compose.ui.favourite.FavouriteScreen
import com.aurora.store.compose.ui.onboarding.OnboardingScreen
@@ -135,6 +136,10 @@ fun NavDisplay(startDestination: NavKey) {
onNavigateToSplash = { activity?.startActivity(splashIntent) }
)
}
entry<Screen.Dispenser> {
DispenserScreen(onNavigateUp = ::onNavigateUp)
}
}
)
}

View File

@@ -54,4 +54,7 @@ sealed class Screen : NavKey, Parcelable {
@Serializable
data object Spoof : Screen()
@Serializable
data object Dispenser : Screen()
}

View File

@@ -0,0 +1,140 @@
/*
* SPDX-FileCopyrightText: 2025 The Calyx Institute
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.aurora.store.compose.ui.dispenser
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.aurora.extensions.copyToClipBoard
import com.aurora.store.R
import com.aurora.store.compose.composable.DispenserListItem
import com.aurora.store.compose.composable.Error
import com.aurora.store.compose.composable.TopAppBar
import com.aurora.store.compose.preview.PreviewTemplate
import com.aurora.store.viewmodel.dispenser.DispenserViewModel
@Composable
fun DispenserScreen(onNavigateUp: () -> Unit, viewModel: DispenserViewModel = hiltViewModel()) {
val dispensers by viewModel.dispensers.collectAsStateWithLifecycle()
var shouldShowInputDialog by rememberSaveable { mutableStateOf(false) }
if (shouldShowInputDialog) {
InputDispenserDialog(
onAdd = { url ->
viewModel.addDispenser(url)
shouldShowInputDialog = false
},
onDismiss = { shouldShowInputDialog = false }
)
}
var shouldRemoveDispenser : String? by rememberSaveable { mutableStateOf(null) }
shouldRemoveDispenser?.let { url ->
RemoveDispenserDialog(
url = url,
onRemove = {
viewModel.removeDispenser(url)
shouldRemoveDispenser = null
},
onDismiss = { shouldRemoveDispenser = null }
)
}
ScreenContent(
dispensers = dispensers,
onNavigateUp = onNavigateUp,
onAddDispenser = { shouldShowInputDialog = true },
onRemoveDispenser = { url -> shouldRemoveDispenser = url }
)
}
@Composable
private fun ScreenContent(
onNavigateUp: () -> Unit = {},
dispensers: Set<String> = emptySet(),
onAddDispenser: () -> Unit = {},
onRemoveDispenser: (url: String) -> Unit = {}
) {
val context = LocalContext.current
Scaffold(
topBar = {
TopAppBar(
title = stringResource(R.string.pref_dispenser_title),
onNavigateUp = onNavigateUp
)
},
floatingActionButton = {
if (dispensers.isNotEmpty()) {
FloatingActionButton(onClick = onAddDispenser) {
Icon(
painter = painterResource(R.drawable.ic_add),
contentDescription = stringResource(R.string.add_dispenser_title)
)
}
}
}
) { paddingValues ->
if (dispensers.isEmpty()) {
Error(
modifier = Modifier.padding(paddingValues),
painter = painterResource(R.drawable.ic_server),
message = stringResource(R.string.no_dispensers_available),
actionMessage = stringResource(R.string.add_dispenser_title),
onAction = onAddDispenser
)
} else {
LazyColumn(
modifier = Modifier
.padding(paddingValues)
.fillMaxSize()
.padding(vertical = dimensionResource(R.dimen.padding_medium))
) {
items(items = dispensers.toList(), key = { url -> url }) { url ->
DispenserListItem(
url = url,
onClick = { context.copyToClipBoard(url) },
onClear = { onRemoveDispenser(url) }
)
}
}
}
}
}
@Preview
@Composable
private fun DispenserScreenPreview() {
val dispensers = List(10) { "https://auroraoss.com/api/auth/$it" }
PreviewTemplate {
ScreenContent(dispensers = dispensers.toSet())
}
}
@Preview
@Composable
private fun DispenserScreenEmptyPreview() {
PreviewTemplate {
ScreenContent()
}
}

View File

@@ -0,0 +1,93 @@
/*
* SPDX-FileCopyrightText: 2025 The Calyx Institute
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.aurora.store.compose.ui.dispenser
import android.util.Patterns
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.aurora.store.R
import kotlinx.coroutines.android.awaitFrame
/**
* Dialog for adding a new token dispenser
* @param onAdd Callback on adding a new dispenser
* @param onDismiss Callback on dismiss
*/
@Composable
fun InputDispenserDialog(onAdd: (url: String) -> Unit = {}, onDismiss: () -> Unit = {}) {
val focusManager = LocalFocusManager.current
val focusRequester = remember { FocusRequester() }
var url by remember { mutableStateOf(TextFieldValue()) }
LaunchedEffect(focusRequester) {
awaitFrame()
focusRequester.requestFocus()
}
AlertDialog(
title = { Text(text = stringResource(R.string.add_dispenser_title)) },
text = {
Column(
verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_medium))
) {
Text(text = stringResource(R.string.add_dispenser_summary))
OutlinedTextField(
modifier = Modifier
.fillMaxWidth()
.focusRequester(focusRequester),
value = url,
placeholder = { Text(text = stringResource(R.string.add_dispenser_hint)) },
onValueChange = { url = it },
shape = RoundedCornerShape(10.dp),
singleLine = true,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
)
}
},
onDismissRequest = onDismiss,
confirmButton = {
TextButton(
onClick = { onAdd(url.text) },
enabled = Patterns.WEB_URL.matcher(url.text).matches()
) {
Text(text = stringResource(R.string.add))
}
},
dismissButton = {
TextButton(onClick = onDismiss) {
Text(text = stringResource(android.R.string.cancel))
}
}
)
}
@Preview
@Composable
private fun InputDispenserDialogPreview() {
InputDispenserDialog()
}

View File

@@ -0,0 +1,46 @@
/*
* SPDX-FileCopyrightText: 2025 The Calyx Institute
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.aurora.store.compose.ui.dispenser
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.aurora.store.R
/**
* Dialog for removing a token dispenser
* @param onRemove Callback on removing a dispenser
* @param onDismiss Callback on dismiss
*/
@Composable
fun RemoveDispenserDialog(url: String, onRemove: () -> Unit = {}, onDismiss: () -> Unit = {}) {
AlertDialog(
title = { Text(text = stringResource(R.string.remove_dispenser_title)) },
text = {
Text(text = stringResource(R.string.remove_dispenser_summary, url))
},
onDismissRequest = onDismiss,
confirmButton = {
TextButton(onClick = onRemove) {
Text(text = stringResource(R.string.remove))
}
},
dismissButton = {
TextButton(onClick = onDismiss) {
Text(text = stringResource(android.R.string.cancel))
}
}
)
}
@Preview
@Composable
private fun RemoveDispenserDialogPreview() {
RemoveDispenserDialog(url = "https://auroraoss.com/api/auth/")
}

View File

@@ -1,34 +0,0 @@
package com.aurora.store.view.epoxy.views
import android.content.Context
import android.util.AttributeSet
import com.airbnb.epoxy.CallbackProp
import com.airbnb.epoxy.ModelProp
import com.airbnb.epoxy.ModelView
import com.aurora.store.databinding.ViewDispenserBinding
@ModelView(
autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT,
baseModelClass = BaseModel::class
)
class DispenserView @JvmOverloads constructor(
context: Context?,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : BaseView<ViewDispenserBinding>(context, attrs, defStyleAttr) {
@ModelProp
fun url(url: String) {
binding.url.text = url
}
@CallbackProp
fun copy(onClickListener: OnClickListener?) {
binding.url.setOnClickListener(onClickListener)
}
@CallbackProp
fun clear(onClickListener: OnClickListener?) {
binding.btnAction.setOnClickListener(onClickListener)
}
}

View File

@@ -1,75 +0,0 @@
package com.aurora.store.view.ui.dispenser
import android.content.SharedPreferences
import android.os.Bundle
import android.view.View
import androidx.navigation.fragment.findNavController
import com.aurora.extensions.copyToClipBoard
import com.aurora.store.R
import com.aurora.store.databinding.FragmentDispenserBinding
import com.aurora.store.util.Preferences
import com.aurora.store.util.Preferences.PREFERENCE_DISPENSER_URLS
import com.aurora.store.view.epoxy.views.DispenserViewModel_
import com.aurora.store.view.ui.commons.BaseFragment
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class DispenserFragment : BaseFragment<FragmentDispenserBinding>(),
SharedPreferences.OnSharedPreferenceChangeListener {
private val dispensers: Set<String>
get() = Preferences.getStringSet(requireContext(), PREFERENCE_DISPENSER_URLS)
private lateinit var sharedPreferences: SharedPreferences
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedPreferences = Preferences.getPrefs(view.context)
sharedPreferences.registerOnSharedPreferenceChangeListener(this)
binding.toolbar.setNavigationOnClickListener { findNavController().navigateUp() }
binding.addFab.setOnClickListener {
findNavController().navigate(R.id.inputDispenserDialog)
}
setupDispensers()
}
override fun onDestroyView() {
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
super.onDestroyView()
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
if (key == PREFERENCE_DISPENSER_URLS) setupDispensers()
}
private fun setupDispensers() {
if (dispensers.isNotEmpty()) {
binding.noDispensersTextView.visibility = View.GONE
binding.epoxyRecycler.visibility = View.VISIBLE
binding.epoxyRecycler.withModels {
setFilterDuplicates(true)
dispensers.forEachIndexed { index, url ->
add(
DispenserViewModel_()
.id(index)
.url(url)
.copy { _ -> requireContext().copyToClipBoard(url) }
.clear { _ ->
findNavController().navigate(
DispenserFragmentDirections
.actionDispenserFragmentToRemoveDispenserDialog(url)
)
}
)
}
}
} else {
binding.epoxyRecycler.visibility = View.GONE
binding.noDispensersTextView.visibility = View.VISIBLE
}
}
}

View File

@@ -1,58 +0,0 @@
package com.aurora.store.view.ui.dispenser
import android.app.Dialog
import android.os.Bundle
import android.util.Patterns
import android.view.WindowManager
import androidx.fragment.app.DialogFragment
import com.aurora.extensions.showKeyboard
import com.aurora.extensions.toast
import com.aurora.store.R
import com.aurora.store.util.Preferences
import com.aurora.store.util.Preferences.PREFERENCE_DISPENSER_URLS
import com.aurora.store.util.save
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textfield.TextInputLayout
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class InputDispenserDialog: DialogFragment() {
private val textInputLayout: TextInputLayout?
get() = dialog?.findViewById(R.id.textInputLayout)
private val dispensers: Set<String>
get() = Preferences.getStringSet(requireContext(), PREFERENCE_DISPENSER_URLS)
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val view = layoutInflater.inflate(R.layout.dialog_text_input_edit_text, null)
return MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.add_dispenser_title)
.setMessage(R.string.add_dispenser_summary)
.setView(view)
.setPositiveButton(getString(R.string.add)) { _, _ -> saveDispenserUrl() }
.setNegativeButton(getString(android.R.string.cancel)) { _, _ -> dialog?.dismiss()}
.create()
}
override fun onResume() {
super.onResume()
textInputLayout?.editText?.apply {
hint = requireContext().getString(R.string.add_dispenser_hint)
showKeyboard()
}
dialog?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
}
private fun saveDispenserUrl() {
val url = textInputLayout?.editText?.text?.toString()
if (!url.isNullOrEmpty() && Patterns.WEB_URL.matcher(url).matches()) {
val newSet = dispensers.toMutableSet().apply {
add(url)
}
save(PREFERENCE_DISPENSER_URLS, newSet)
} else {
toast(R.string.add_dispenser_error)
}
}
}

View File

@@ -1,37 +0,0 @@
package com.aurora.store.view.ui.dispenser
import android.app.Dialog
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.navigation.fragment.navArgs
import com.aurora.store.R
import com.aurora.store.util.Preferences
import com.aurora.store.util.Preferences.PREFERENCE_DISPENSER_URLS
import com.aurora.store.util.save
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class RemoveDispenserDialog : DialogFragment() {
private val args: RemoveDispenserDialogArgs by navArgs()
private val dispensers: Set<String>
get() = Preferences.getStringSet(requireContext(), PREFERENCE_DISPENSER_URLS)
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.remove_dispenser_title)
.setMessage(getString(R.string.remove_dispenser_summary, args.url))
.setPositiveButton(getString(R.string.remove)) { _, _ -> removeDispenserUrl() }
.setNegativeButton(getString(android.R.string.cancel)) { _, _ -> dialog?.dismiss() }
.create()
}
private fun removeDispenserUrl() {
val newSet = dispensers.toMutableSet().apply {
remove(args.url)
}
save(PREFERENCE_DISPENSER_URLS, newSet)
}
}

View File

@@ -25,9 +25,11 @@ import androidx.appcompat.widget.Toolbar
import androidx.navigation.fragment.findNavController
import androidx.preference.Preference
import androidx.preference.SwitchPreferenceCompat
import com.aurora.extensions.navigate
import com.aurora.extensions.runOnUiThread
import com.aurora.extensions.toast
import com.aurora.store.R
import com.aurora.store.compose.navigation.Screen
import com.aurora.store.util.PackageUtil
import com.aurora.store.util.Preferences
import com.aurora.store.util.Preferences.PREFERENCE_MICROG_AUTH
@@ -45,7 +47,7 @@ class NetworkPreference : BasePreferenceFragment() {
findPreference<Preference>(Preferences.PREFERENCE_DISPENSER_URLS)?.apply {
setOnPreferenceClickListener {
findNavController().navigate(R.id.dispenserFragment)
requireContext().navigate(Screen.Dispenser)
true
}
}

View File

@@ -0,0 +1,63 @@
/*
* SPDX-FileCopyrightText: 2025 The Calyx Institute
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.aurora.store.viewmodel.dispenser
import android.content.Context
import android.content.SharedPreferences
import androidx.core.content.edit
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.aurora.store.util.Preferences
import com.aurora.store.util.Preferences.PREFERENCE_DISPENSER_URLS
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.stateIn
import javax.inject.Inject
import kotlin.collections.toMutableSet
@HiltViewModel
class DispenserViewModel @Inject constructor(
@ApplicationContext private val context: Context
) : ViewModel() {
private val sharedPreferences = Preferences.getPrefs(context)
private var _dispensers: Set<String>
set(value) = sharedPreferences.edit { putStringSet(PREFERENCE_DISPENSER_URLS, value) }
get() = sharedPreferences.getStringSet(PREFERENCE_DISPENSER_URLS, emptySet()) ?: emptySet()
val dispensers: StateFlow<Set<String>>
get() {
return callbackFlow {
val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, changedKey ->
if (changedKey == PREFERENCE_DISPENSER_URLS) trySend(_dispensers)
}
trySend(_dispensers)
sharedPreferences.registerOnSharedPreferenceChangeListener(listener)
awaitClose {
sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener)
}
}.stateIn(viewModelScope, SharingStarted.Eagerly, emptySet())
}
fun addDispenser(url: String) {
_dispensers = _dispensers.toMutableSet().apply {
add(url)
}
}
fun removeDispenser(url: String) {
_dispensers = _dispensers.toMutableSet().apply {
remove(url)
}
}
}

View File

@@ -1,58 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.ui.dispenser.DispenserFragment">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/actionBarSize"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navigationIcon="@drawable/ic_arrow_back"
app:title="@string/pref_dispenser_title" />
<TextView
android:id="@+id/no_dispensers_text_view"
style="@style/AuroraTextStyle.Subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/no_dispensers_available"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar" />
<com.airbnb.epoxy.EpoxyRecyclerView
android:id="@+id/epoxy_recycler"
android:layout_width="match_parent"
android:layout_height="0dp"
android:clipToPadding="false"
android:overScrollMode="never"
android:padding="@dimen/padding_normal"
android:scrollbars="none"
app:itemSpacing="@dimen/margin_small"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar"
tools:listitem="@layout/view_dispenser" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/add_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/margin_large"
android:contentDescription="@string/action_search"
app:backgroundTint="?colorAccent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="@drawable/ic_add"
app:tint="@android:color/white" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:selectableItemBackground"
android:orientation="horizontal"
android:padding="@dimen/padding_small">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/url"
style="@style/AuroraTextStyle.Subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toStartOf="@id/btn_action"
android:drawableStart="@drawable/ic_server"
android:drawablePadding="@dimen/padding_small"
tools:text="https://auroraoss.com/api/auth" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_action"
style="@style/Widget.Material3.Button.TextButton.Dialog.Flush"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginStart="@dimen/margin_small"
android:text="@string/remove" />
</RelativeLayout>

View File

@@ -189,15 +189,6 @@
android:name="com.aurora.store.view.ui.preferences.installation.InstallerFragment"
android:label="InstallerFragment"
tools:layout="@layout/fragment_installer" />
<fragment
android:id="@+id/dispenserFragment"
android:name="com.aurora.store.view.ui.dispenser.DispenserFragment"
android:label="DispenserFragment"
tools:layout="@layout/fragment_dispenser">
<action
android:id="@+id/action_dispenserFragment_to_removeDispenserDialog"
app:destination="@id/removeDispenserDialog" />
</fragment>
<dialog
android:id="@+id/appMenuSheet"
android:name="com.aurora.store.view.ui.sheets.AppMenuSheet"
@@ -216,18 +207,6 @@
android:id="@+id/moreDialogFragment"
android:name="com.aurora.store.view.ui.commons.MoreDialogFragment"
android:label="MoreDialogFragment" />
<dialog
android:id="@+id/inputDispenserDialog"
android:name="com.aurora.store.view.ui.dispenser.InputDispenserDialog"
android:label="@string/add_dispenser_title" />
<dialog
android:id="@+id/removeDispenserDialog"
android:name="com.aurora.store.view.ui.dispenser.RemoveDispenserDialog"
android:label="@string/remove_dispenser_title">
<argument
android:name="url"
app:argType="string" />
</dialog>
<dialog
android:id="@+id/proxyURLDialog"
android:name="com.aurora.store.view.ui.preferences.network.ProxyURLDialog"

View File

@@ -438,7 +438,7 @@
<string name="add_dispenser_summary">Add a token dispenser to Aurora Store. Token dispensers provide account credentials to Aurora Store for logging in anonymously.</string>
<string name="add_dispenser_error">Invalid URL</string>
<string name="add_dispenser_hint">Dispenser URL</string>
<string name="remove_dispenser_title">Remove dispenser</string>
<string name="remove_dispenser_title">Remove dispenser?</string>
<string name="remove_dispenser_summary">Do you wish to remove the dispenser \"<xliff:g id="url">%1$s</xliff:g>\"?</string>
<string name="add">Add</string>
<string name="remove">Remove</string>