Implement ability to select featured badge to display on profile.
This commit is contained in:
parent
fb86fdfcd9
commit
e6b03b1a4a
12 changed files with 106 additions and 22 deletions
|
@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.badges
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import io.reactivex.rxjava3.core.Completable
|
import io.reactivex.rxjava3.core.Completable
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
|
import org.thoughtcrime.securesms.badges.models.Badge
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
import org.thoughtcrime.securesms.database.RecipientDatabase
|
import org.thoughtcrime.securesms.database.RecipientDatabase
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
|
@ -19,4 +20,13 @@ class BadgeRepository(context: Context) {
|
||||||
val recipientDatabase: RecipientDatabase = DatabaseFactory.getRecipientDatabase(context)
|
val recipientDatabase: RecipientDatabase = DatabaseFactory.getRecipientDatabase(context)
|
||||||
recipientDatabase.setBadges(Recipient.self().id, badges)
|
recipientDatabase.setBadges(Recipient.self().id, badges)
|
||||||
}.subscribeOn(Schedulers.io())
|
}.subscribeOn(Schedulers.io())
|
||||||
|
|
||||||
|
fun setFeaturedBadge(featuredBadge: Badge): Completable = Completable.fromAction {
|
||||||
|
val badges = Recipient.self().badges
|
||||||
|
val reOrderedBadges = listOf(featuredBadge) + (badges - featuredBadge)
|
||||||
|
ProfileUtil.uploadProfileWithBadges(context, reOrderedBadges)
|
||||||
|
|
||||||
|
val recipientDatabase: RecipientDatabase = DatabaseFactory.getRecipientDatabase(context)
|
||||||
|
recipientDatabase.setBadges(Recipient.self().id, reOrderedBadges)
|
||||||
|
}.subscribeOn(Schedulers.io())
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,16 @@ data class LargeBadge(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class EmptyModel : MappingModel<EmptyModel> {
|
||||||
|
override fun areItemsTheSame(newItem: EmptyModel): Boolean = true
|
||||||
|
override fun areContentsTheSame(newItem: EmptyModel): Boolean = true
|
||||||
|
}
|
||||||
|
|
||||||
|
class EmptyViewHolder(itemView: View) : MappingViewHolder<EmptyModel>(itemView) {
|
||||||
|
override fun bind(model: EmptyModel) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ViewHolder(itemView: View) : MappingViewHolder<Model>(itemView) {
|
class ViewHolder(itemView: View) : MappingViewHolder<Model>(itemView) {
|
||||||
|
|
||||||
private val badge: ImageView = itemView.findViewById(R.id.badge)
|
private val badge: ImageView = itemView.findViewById(R.id.badge)
|
||||||
|
@ -42,6 +52,7 @@ data class LargeBadge(
|
||||||
companion object {
|
companion object {
|
||||||
fun register(mappingAdapter: MappingAdapter) {
|
fun register(mappingAdapter: MappingAdapter) {
|
||||||
mappingAdapter.registerFactory(Model::class.java, MappingAdapter.LayoutFactory({ ViewHolder(it) }, R.layout.view_badge_bottom_sheet_dialog_fragment_page))
|
mappingAdapter.registerFactory(Model::class.java, MappingAdapter.LayoutFactory({ ViewHolder(it) }, R.layout.view_badge_bottom_sheet_dialog_fragment_page))
|
||||||
|
mappingAdapter.registerFactory(EmptyModel::class.java, MappingAdapter.LayoutFactory({ EmptyViewHolder(it) }, R.layout.view_badge_bottom_sheet_dialog_fragment_page))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package org.thoughtcrime.securesms.badges.self.featured
|
||||||
|
|
||||||
|
enum class SelectFeaturedBadgeEvent {
|
||||||
|
NO_BADGE_SELECTED,
|
||||||
|
FAILED_TO_UPDATE_PROFILE,
|
||||||
|
SAVE_SUCCESSFUL
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.badges.self.featured
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
|
@ -16,6 +17,7 @@ import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||||
import org.thoughtcrime.securesms.components.settings.configure
|
import org.thoughtcrime.securesms.components.settings.configure
|
||||||
|
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fragment which allows user to select one of their badges to be their "Featured" badge.
|
* Fragment which allows user to select one of their badges to be their "Featured" badge.
|
||||||
|
@ -28,17 +30,19 @@ class SelectFeaturedBadgeFragment : DSLSettingsFragment(
|
||||||
|
|
||||||
private val viewModel: SelectFeaturedBadgeViewModel by viewModels(factoryProducer = { SelectFeaturedBadgeViewModel.Factory(BadgeRepository(requireContext())) })
|
private val viewModel: SelectFeaturedBadgeViewModel by viewModels(factoryProducer = { SelectFeaturedBadgeViewModel.Factory(BadgeRepository(requireContext())) })
|
||||||
|
|
||||||
|
private val lifecycleDisposable = LifecycleDisposable()
|
||||||
|
|
||||||
private lateinit var scrollShadow: View
|
private lateinit var scrollShadow: View
|
||||||
|
private lateinit var save: View
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
scrollShadow = view.findViewById(R.id.scroll_shadow)
|
scrollShadow = view.findViewById(R.id.scroll_shadow)
|
||||||
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
val save: View = view.findViewById(R.id.save)
|
save = view.findViewById(R.id.save)
|
||||||
save.setOnClickListener {
|
save.setOnClickListener {
|
||||||
viewModel.save()
|
viewModel.save()
|
||||||
findNavController().popBackStack()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,7 +60,17 @@ class SelectFeaturedBadgeFragment : DSLSettingsFragment(
|
||||||
val previewView: View = requireView().findViewById(R.id.preview)
|
val previewView: View = requireView().findViewById(R.id.preview)
|
||||||
val previewViewHolder = FeaturedBadgePreview.ViewHolder(previewView)
|
val previewViewHolder = FeaturedBadgePreview.ViewHolder(previewView)
|
||||||
|
|
||||||
|
lifecycleDisposable.bindTo(viewLifecycleOwner.lifecycle)
|
||||||
|
lifecycleDisposable += viewModel.events.subscribe { event: SelectFeaturedBadgeEvent ->
|
||||||
|
when (event) {
|
||||||
|
SelectFeaturedBadgeEvent.NO_BADGE_SELECTED -> Toast.makeText(requireContext(), R.string.SelectFeaturedBadgeFragment__you_must_select_a_badge, Toast.LENGTH_LONG).show()
|
||||||
|
SelectFeaturedBadgeEvent.FAILED_TO_UPDATE_PROFILE -> Toast.makeText(requireContext(), R.string.SelectFeaturedBadgeFragment__failed_to_update_profile, Toast.LENGTH_LONG).show()
|
||||||
|
SelectFeaturedBadgeEvent.SAVE_SUCCESSFUL -> findNavController().popBackStack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
viewModel.state.observe(viewLifecycleOwner) { state ->
|
viewModel.state.observe(viewLifecycleOwner) { state ->
|
||||||
|
save.isEnabled = state.stage == SelectFeaturedBadgeState.Stage.READY
|
||||||
previewViewHolder.bind(FeaturedBadgePreview.Model(state.selectedBadge))
|
previewViewHolder.bind(FeaturedBadgePreview.Model(state.selectedBadge))
|
||||||
adapter.submitList(getConfiguration(state).toMappingModelList())
|
adapter.submitList(getConfiguration(state).toMappingModelList())
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,13 @@ package org.thoughtcrime.securesms.badges.self.featured
|
||||||
import org.thoughtcrime.securesms.badges.models.Badge
|
import org.thoughtcrime.securesms.badges.models.Badge
|
||||||
|
|
||||||
data class SelectFeaturedBadgeState(
|
data class SelectFeaturedBadgeState(
|
||||||
|
val stage: Stage = Stage.INIT,
|
||||||
val selectedBadge: Badge? = null,
|
val selectedBadge: Badge? = null,
|
||||||
val allUnlockedBadges: List<Badge> = listOf()
|
val allUnlockedBadges: List<Badge> = listOf()
|
||||||
)
|
) {
|
||||||
|
enum class Stage {
|
||||||
|
INIT,
|
||||||
|
READY,
|
||||||
|
SAVING
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,24 +3,37 @@ package org.thoughtcrime.securesms.badges.self.featured
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
|
import io.reactivex.rxjava3.core.Observable
|
||||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||||
import io.reactivex.rxjava3.kotlin.plusAssign
|
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||||
|
import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||||
|
import io.reactivex.rxjava3.subjects.PublishSubject
|
||||||
|
import org.signal.core.util.logging.Log
|
||||||
import org.thoughtcrime.securesms.badges.BadgeRepository
|
import org.thoughtcrime.securesms.badges.BadgeRepository
|
||||||
import org.thoughtcrime.securesms.badges.models.Badge
|
import org.thoughtcrime.securesms.badges.models.Badge
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.util.livedata.Store
|
import org.thoughtcrime.securesms.util.livedata.Store
|
||||||
|
|
||||||
class SelectFeaturedBadgeViewModel(repository: BadgeRepository) : ViewModel() {
|
private val TAG = Log.tag(SelectFeaturedBadgeViewModel::class.java)
|
||||||
|
|
||||||
|
class SelectFeaturedBadgeViewModel(private val repository: BadgeRepository) : ViewModel() {
|
||||||
|
|
||||||
private val store = Store(SelectFeaturedBadgeState())
|
private val store = Store(SelectFeaturedBadgeState())
|
||||||
|
private val eventSubject = PublishSubject.create<SelectFeaturedBadgeEvent>()
|
||||||
|
|
||||||
val state: LiveData<SelectFeaturedBadgeState> = store.stateLiveData
|
val state: LiveData<SelectFeaturedBadgeState> = store.stateLiveData
|
||||||
|
val events: Observable<SelectFeaturedBadgeEvent> = eventSubject.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
|
||||||
private val disposables = CompositeDisposable()
|
private val disposables = CompositeDisposable()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
store.update(Recipient.live(Recipient.self().id).liveDataResolved) { recipient, state ->
|
store.update(Recipient.live(Recipient.self().id).liveDataResolved) { recipient, state ->
|
||||||
state.copy(selectedBadge = recipient.badges.firstOrNull(), allUnlockedBadges = recipient.badges)
|
state.copy(
|
||||||
|
stage = if (state.stage == SelectFeaturedBadgeState.Stage.INIT) SelectFeaturedBadgeState.Stage.READY else state.stage,
|
||||||
|
selectedBadge = recipient.badges.firstOrNull(),
|
||||||
|
allUnlockedBadges = recipient.badges
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +42,22 @@ class SelectFeaturedBadgeViewModel(repository: BadgeRepository) : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun save() {
|
fun save() {
|
||||||
// TODO "Persist selection to database"
|
val snapshot = store.state
|
||||||
|
if (snapshot.selectedBadge == null) {
|
||||||
|
eventSubject.onNext(SelectFeaturedBadgeEvent.NO_BADGE_SELECTED)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
store.update { it.copy(stage = SelectFeaturedBadgeState.Stage.SAVING) }
|
||||||
|
disposables += repository.setFeaturedBadge(snapshot.selectedBadge).subscribeBy(
|
||||||
|
onComplete = {
|
||||||
|
eventSubject.onNext(SelectFeaturedBadgeEvent.SAVE_SUCCESSFUL)
|
||||||
|
},
|
||||||
|
onError = { error ->
|
||||||
|
Log.e(TAG, "Failed to update profile.", error)
|
||||||
|
eventSubject.onNext(SelectFeaturedBadgeEvent.FAILED_TO_UPDATE_PROFILE)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
|
|
|
@ -20,7 +20,7 @@ class BadgesOverviewViewModel(private val badgeRepository: BadgeRepository) : Vi
|
||||||
private val eventSubject = PublishSubject.create<BadgesOverviewEvent>()
|
private val eventSubject = PublishSubject.create<BadgesOverviewEvent>()
|
||||||
|
|
||||||
val state: LiveData<BadgesOverviewState> = store.stateLiveData
|
val state: LiveData<BadgesOverviewState> = store.stateLiveData
|
||||||
val events: Observable<BadgesOverviewEvent> = eventSubject
|
val events: Observable<BadgesOverviewEvent> = eventSubject.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
|
||||||
val disposables = CompositeDisposable()
|
val disposables = CompositeDisposable()
|
||||||
|
|
||||||
|
@ -36,7 +36,6 @@ class BadgesOverviewViewModel(private val badgeRepository: BadgeRepository) : Vi
|
||||||
|
|
||||||
fun setDisplayBadgesOnProfile(displayBadgesOnProfile: Boolean) {
|
fun setDisplayBadgesOnProfile(displayBadgesOnProfile: Boolean) {
|
||||||
disposables += badgeRepository.setVisibilityForAllBadges(displayBadgesOnProfile)
|
disposables += badgeRepository.setVisibilityForAllBadges(displayBadgesOnProfile)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(
|
.subscribe(
|
||||||
{
|
{
|
||||||
store.update { it.copy(stage = BadgesOverviewState.Stage.READY) }
|
store.update { it.copy(stage = BadgesOverviewState.Stage.READY) }
|
||||||
|
|
|
@ -32,6 +32,8 @@ class ViewBadgeBottomSheetDialogFragment : FixedRoundedCornerBottomSheetDialogFr
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
postponeEnterTransition()
|
||||||
|
|
||||||
val pager: ViewPager2 = view.findViewById(R.id.pager)
|
val pager: ViewPager2 = view.findViewById(R.id.pager)
|
||||||
val tabs: TabLayout = view.findViewById(R.id.tab_layout)
|
val tabs: TabLayout = view.findViewById(R.id.tab_layout)
|
||||||
val action: MaterialButton = view.findViewById(R.id.action)
|
val action: MaterialButton = view.findViewById(R.id.action)
|
||||||
|
@ -44,14 +46,17 @@ class ViewBadgeBottomSheetDialogFragment : FixedRoundedCornerBottomSheetDialogFr
|
||||||
|
|
||||||
LargeBadge.register(adapter)
|
LargeBadge.register(adapter)
|
||||||
pager.adapter = adapter
|
pager.adapter = adapter
|
||||||
|
adapter.submitList(listOf(LargeBadge.EmptyModel()))
|
||||||
|
|
||||||
TabLayoutMediator(tabs, pager) { _, _ ->
|
TabLayoutMediator(tabs, pager) { _, _ ->
|
||||||
}.attach()
|
}.attach()
|
||||||
|
|
||||||
pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
|
pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
|
||||||
override fun onPageSelected(position: Int) {
|
override fun onPageSelected(position: Int) {
|
||||||
|
if (adapter.getModel(position).map { it is LargeBadge.Model }.orElse(false)) {
|
||||||
viewModel.onPageSelected(position)
|
viewModel.onPageSelected(position)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
viewModel.state.observe(viewLifecycleOwner) { state ->
|
viewModel.state.observe(viewLifecycleOwner) { state ->
|
||||||
|
|
|
@ -26,4 +26,8 @@ class LifecycleDisposable : DefaultLifecycleObserver {
|
||||||
owner.lifecycle.removeObserver(this)
|
owner.lifecycle.removeObserver(this)
|
||||||
disposables.clear()
|
disposables.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
operator fun plusAssign(disposable: Disposable) {
|
||||||
|
add(disposable)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,9 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_marginTop="6dp"
|
android:layout_marginTop="6dp"
|
||||||
|
android:ellipsize="end"
|
||||||
android:lineSpacingExtra="4sp"
|
android:lineSpacingExtra="4sp"
|
||||||
|
android:lines="2"
|
||||||
android:textAlignment="center"
|
android:textAlignment="center"
|
||||||
android:textAppearance="@style/Signal.Text.Caption"
|
android:textAppearance="@style/Signal.Text.Caption"
|
||||||
app:layout_constraintEnd_toEndOf="@id/badge"
|
app:layout_constraintEnd_toEndOf="@id/badge"
|
||||||
|
|
|
@ -1,29 +1,25 @@
|
||||||
<?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"
|
<LinearLayout 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"
|
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"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/badge"
|
android:id="@+id/badge"
|
||||||
android:layout_width="200dp"
|
android:layout_width="200dp"
|
||||||
android:layout_height="200dp"
|
android:layout_height="200dp"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
android:contentDescription="@string/BadgesOverviewFragment__featured_badge"
|
android:contentDescription="@string/BadgesOverviewFragment__featured_badge"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
tools:src="@drawable/test_gradient" />
|
tools:src="@drawable/test_gradient" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/name"
|
android:id="@+id/name"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
android:textAppearance="@style/TextAppearance.Signal.Title2"
|
android:textAppearance="@style/TextAppearance.Signal.Title2"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/badge"
|
|
||||||
tools:text="Signal Sustainer" />
|
tools:text="Signal Sustainer" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
@ -40,9 +36,6 @@
|
||||||
android:paddingEnd="32dp"
|
android:paddingEnd="32dp"
|
||||||
android:textAlignment="center"
|
android:textAlignment="center"
|
||||||
android:textAppearance="@style/Signal.Text.Body"
|
android:textAppearance="@style/Signal.Text.Body"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/name"
|
|
||||||
tools:text="Paige supports Signal by making a monthly donation. Get your own badge by donating below." />
|
tools:text="Paige supports Signal by making a monthly donation. Get your own badge by donating below." />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</LinearLayout>
|
|
@ -3845,8 +3845,12 @@
|
||||||
|
|
||||||
|
|
||||||
<string name="BadgeSelectionFragment__select_badges">Select badges</string>
|
<string name="BadgeSelectionFragment__select_badges">Select badges</string>
|
||||||
|
|
||||||
<string name="SelectFeaturedBadgeFragment__preview">Preview</string>
|
<string name="SelectFeaturedBadgeFragment__preview">Preview</string>
|
||||||
<string name="SelectFeaturedBadgeFragment__select_a_badge">Select a badge</string>
|
<string name="SelectFeaturedBadgeFragment__select_a_badge">Select a badge</string>
|
||||||
|
<string name="SelectFeaturedBadgeFragment__you_must_select_a_badge">You must select a badge</string>
|
||||||
|
<string name="SelectFeaturedBadgeFragment__failed_to_update_profile">Failed to update profile</string>
|
||||||
|
|
||||||
<string name="ViewBadgeBottomSheetDialogFragment__become_a_sustainer">Become a sustainer</string>
|
<string name="ViewBadgeBottomSheetDialogFragment__become_a_sustainer">Become a sustainer</string>
|
||||||
|
|
||||||
<!-- EOF -->
|
<!-- EOF -->
|
||||||
|
|
Loading…
Add table
Reference in a new issue