mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-21 03:28:47 +00:00
Convert NotificationProfilesSettingsFragment to compose.
This commit is contained in:
@@ -1,103 +1,264 @@
|
|||||||
package org.thoughtcrime.securesms.components.settings.app.notifications.profiles
|
package org.thoughtcrime.securesms.components.settings.app.notifications.profiles
|
||||||
|
|
||||||
import android.os.Bundle
|
import androidx.compose.foundation.Image
|
||||||
import android.view.View
|
import androidx.compose.foundation.background
|
||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.res.vectorResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import org.signal.core.util.concurrent.LifecycleDisposable
|
import org.signal.core.ui.compose.DayNightPreviews
|
||||||
|
import org.signal.core.ui.compose.Previews
|
||||||
|
import org.signal.core.ui.compose.Rows
|
||||||
|
import org.signal.core.ui.compose.Scaffolds
|
||||||
|
import org.signal.core.ui.compose.Texts
|
||||||
|
import org.signal.core.ui.compose.horizontalGutters
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.components.emoji.EmojiUtil
|
import org.thoughtcrime.securesms.components.emoji.EmojiStrings
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
import org.thoughtcrime.securesms.components.settings.app.notifications.profiles.models.NotificationProfileRow
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
import org.thoughtcrime.securesms.compose.ComposeFragment
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsIcon
|
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
|
||||||
import org.thoughtcrime.securesms.components.settings.NO_TINT
|
|
||||||
import org.thoughtcrime.securesms.components.settings.app.notifications.profiles.models.NoNotificationProfiles
|
|
||||||
import org.thoughtcrime.securesms.components.settings.app.notifications.profiles.models.NotificationProfilePreference
|
|
||||||
import org.thoughtcrime.securesms.components.settings.configure
|
|
||||||
import org.thoughtcrime.securesms.components.settings.conversation.preferences.LargeIconClickPreference
|
|
||||||
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
|
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
|
||||||
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfiles
|
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfileId
|
||||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfileSchedule
|
||||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Primary entry point for Notification Profiles. When user has no profiles, shows empty state, otherwise shows
|
* Primary entry point for Notification Profiles. When user has no profiles, shows empty state, otherwise shows
|
||||||
* all current profiles.
|
* all current profiles.
|
||||||
*/
|
*/
|
||||||
class NotificationProfilesFragment : DSLSettingsFragment() {
|
class NotificationProfilesFragment : ComposeFragment() {
|
||||||
|
|
||||||
private val viewModel: NotificationProfilesViewModel by viewModels(
|
private val viewModel: NotificationProfilesViewModel by viewModels(
|
||||||
factoryProducer = { NotificationProfilesViewModel.Factory() }
|
factoryProducer = { NotificationProfilesViewModel.Factory() }
|
||||||
)
|
)
|
||||||
|
|
||||||
private val lifecycleDisposable = LifecycleDisposable()
|
@Composable
|
||||||
private var toolbar: Toolbar? = null
|
override fun FragmentContent() {
|
||||||
|
val state by viewModel.state.collectAsStateWithLifecycle(initialValue = NotificationProfilesState(profiles = emptyList()))
|
||||||
|
val callback = remember { DefaultNotificationProfilesScreenCallback() }
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
NotificationProfilesScreen(
|
||||||
super.onViewCreated(view, savedInstanceState)
|
state = state,
|
||||||
|
callbacks = callback
|
||||||
toolbar = view.findViewById(R.id.toolbar)
|
|
||||||
|
|
||||||
lifecycleDisposable.bindTo(viewLifecycleOwner.lifecycle)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroyView() {
|
|
||||||
super.onDestroyView()
|
|
||||||
toolbar = null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun bindAdapter(adapter: MappingAdapter) {
|
|
||||||
NoNotificationProfiles.register(adapter)
|
|
||||||
LargeIconClickPreference.register(adapter)
|
|
||||||
NotificationProfilePreference.register(adapter)
|
|
||||||
|
|
||||||
lifecycleDisposable += viewModel.getProfiles()
|
|
||||||
.subscribe { profiles ->
|
|
||||||
if (profiles.isEmpty()) {
|
|
||||||
toolbar?.title = ""
|
|
||||||
} else {
|
|
||||||
toolbar?.setTitle(R.string.NotificationsSettingsFragment__notification_profiles)
|
|
||||||
}
|
|
||||||
adapter.submitList(getConfiguration(profiles).toMappingModelList())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getConfiguration(profiles: List<NotificationProfile>): DSLConfiguration {
|
|
||||||
return configure {
|
|
||||||
if (profiles.isEmpty()) {
|
|
||||||
customPref(
|
|
||||||
NoNotificationProfiles.Model(
|
|
||||||
onClick = { findNavController().safeNavigate(R.id.action_notificationProfilesFragment_to_editNotificationProfileFragment) }
|
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class DefaultNotificationProfilesScreenCallback : NotificationProfilesScreenCallback {
|
||||||
|
override fun onNavigationClick() {
|
||||||
|
requireActivity().onBackPressedDispatcher.onBackPressed()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateNewProfile() {
|
||||||
|
findNavController().safeNavigate(R.id.action_notificationProfilesFragment_to_editNotificationProfileFragment)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onProfileClick(profileId: Long) {
|
||||||
|
findNavController().safeNavigate(NotificationProfilesFragmentDirections.actionNotificationProfilesFragmentToNotificationProfileDetailsFragment(profileId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NotificationProfilesScreen(
|
||||||
|
state: NotificationProfilesState,
|
||||||
|
callbacks: NotificationProfilesScreenCallback
|
||||||
|
) {
|
||||||
|
val title = if (state.profiles.isEmpty()) {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
stringResource(R.string.NotificationsSettingsFragment__notification_profiles)
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffolds.Settings(
|
||||||
|
title = title,
|
||||||
|
onNavigationClick = callbacks::onNavigationClick,
|
||||||
|
navigationIcon = ImageVector.vectorResource(R.drawable.symbol_arrow_start_24),
|
||||||
|
navigationContentDescription = stringResource(R.string.Material3SearchToolbar__close)
|
||||||
|
) { paddingValues ->
|
||||||
|
if (state.profiles.isEmpty()) {
|
||||||
|
NoNotificationProfilesEmpty(
|
||||||
|
onCreateProfileClick = callbacks::onCreateNewProfile,
|
||||||
|
modifier = Modifier.padding(paddingValues)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
sectionHeaderPref(R.string.NotificationProfilesFragment__profiles)
|
LazyColumn(
|
||||||
|
modifier = Modifier.padding(paddingValues)
|
||||||
customPref(
|
) {
|
||||||
LargeIconClickPreference.Model(
|
item {
|
||||||
title = DSLSettingsText.from(R.string.NotificationProfilesFragment__new_profile),
|
Texts.SectionHeader(
|
||||||
icon = DSLSettingsIcon.from(R.drawable.add_to_a_group, NO_TINT),
|
text = stringResource(R.string.NotificationProfilesFragment__profiles)
|
||||||
onClick = { findNavController().safeNavigate(R.id.action_notificationProfilesFragment_to_editNotificationProfileFragment) }
|
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Rows.TextRow(
|
||||||
|
text = {
|
||||||
|
Text(text = stringResource(R.string.NotificationProfilesFragment__new_profile))
|
||||||
|
},
|
||||||
|
icon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = ImageVector.vectorResource(R.drawable.symbol_plus_24),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(40.dp)
|
||||||
|
.background(color = MaterialTheme.colorScheme.secondaryContainer, shape = CircleShape)
|
||||||
|
.padding(8.dp)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClick = callbacks::onCreateNewProfile
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
state.profiles.sortedDescending().forEach { profile ->
|
||||||
|
item {
|
||||||
|
NotificationProfileRow(
|
||||||
|
profile = profile,
|
||||||
|
isActiveProfile = profile == state.activeProfile,
|
||||||
|
onClick = callbacks::onProfileClick
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun NoNotificationProfilesEmpty(
|
||||||
|
onCreateProfileClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.horizontalGutters(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Spacer(modifier = Modifier.height(96.dp))
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(88.dp)
|
||||||
|
.background(
|
||||||
|
color = Color(AvatarColor.A100.colorInt()),
|
||||||
|
shape = CircleShape
|
||||||
|
)
|
||||||
|
.padding(20.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(R.drawable.ic_sleeping_face),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(20.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.NotificationProfilesFragment__notification_profiles),
|
||||||
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 24.dp)
|
||||||
)
|
)
|
||||||
|
|
||||||
val activeProfile: NotificationProfile? = NotificationProfiles.getActiveProfile(profiles)
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
profiles.sortedDescending().forEach { profile ->
|
|
||||||
customPref(
|
Text(
|
||||||
NotificationProfilePreference.Model(
|
text = stringResource(R.string.NotificationProfilesFragment__create_a_profile_to_receive_notifications_and_calls_only_from_the_people_and_groups_you_want_to_hear_from),
|
||||||
title = DSLSettingsText.from(profile.name),
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
summary = if (profile == activeProfile) DSLSettingsText.from(NotificationProfiles.getActiveProfileDescription(requireContext(), profile)) else null,
|
textAlign = TextAlign.Center,
|
||||||
icon = if (profile.emoji.isNotEmpty()) EmojiUtil.convertToDrawable(requireContext(), profile.emoji)?.let { DSLSettingsIcon.from(it) } else DSLSettingsIcon.from(R.drawable.ic_moon_24, NO_TINT),
|
modifier = Modifier
|
||||||
color = profile.color,
|
.fillMaxWidth()
|
||||||
onClick = {
|
.padding(horizontal = 24.dp)
|
||||||
findNavController().safeNavigate(NotificationProfilesFragmentDirections.actionNotificationProfilesFragmentToNotificationProfileDetailsFragment(profile.id))
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = onCreateProfileClick,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(R.string.NotificationProfilesFragment__create_profile))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@DayNightPreviews
|
||||||
|
@Composable
|
||||||
|
fun NotificationProfilesScreenPreview() {
|
||||||
|
Previews.Preview {
|
||||||
|
val profile = remember {
|
||||||
|
NotificationProfile(
|
||||||
|
id = 1L,
|
||||||
|
name = "Test Profile",
|
||||||
|
emoji = EmojiStrings.AUDIO,
|
||||||
|
createdAt = System.currentTimeMillis(),
|
||||||
|
schedule = NotificationProfileSchedule(
|
||||||
|
id = 1
|
||||||
|
),
|
||||||
|
notificationProfileId = NotificationProfileId(UUID.randomUUID())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationProfilesScreen(
|
||||||
|
state = NotificationProfilesState(
|
||||||
|
profiles = listOf(
|
||||||
|
profile
|
||||||
|
),
|
||||||
|
activeProfile = null
|
||||||
|
),
|
||||||
|
callbacks = NotificationProfilesScreenCallback.Empty
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@DayNightPreviews
|
||||||
|
@Composable
|
||||||
|
private fun NoNotificationProfilesEmptyPreview() {
|
||||||
|
Previews.Preview {
|
||||||
|
NoNotificationProfilesEmpty(
|
||||||
|
onCreateProfileClick = {}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface NotificationProfilesScreenCallback {
|
||||||
|
fun onNavigationClick()
|
||||||
|
fun onCreateNewProfile()
|
||||||
|
fun onProfileClick(profileId: Long)
|
||||||
|
|
||||||
|
object Empty : NotificationProfilesScreenCallback {
|
||||||
|
override fun onNavigationClick() = Unit
|
||||||
|
override fun onCreateNewProfile() = Unit
|
||||||
|
override fun onProfileClick(profileId: Long) = Unit
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package org.thoughtcrime.securesms.components.settings.app.notifications.profiles
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
|
||||||
|
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfiles
|
||||||
|
|
||||||
|
data class NotificationProfilesState(
|
||||||
|
val profiles: List<NotificationProfile>,
|
||||||
|
val activeProfile: NotificationProfile? = NotificationProfiles.getActiveProfile(profiles)
|
||||||
|
)
|
||||||
@@ -2,16 +2,16 @@ package org.thoughtcrime.securesms.components.settings.app.notifications.profile
|
|||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
import kotlinx.coroutines.flow.Flow
|
||||||
import io.reactivex.rxjava3.core.Flowable
|
import kotlinx.coroutines.flow.map
|
||||||
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
|
import kotlinx.coroutines.reactive.asFlow
|
||||||
|
import kotlinx.coroutines.rx3.asFlow
|
||||||
|
|
||||||
class NotificationProfilesViewModel(private val repository: NotificationProfilesRepository) : ViewModel() {
|
class NotificationProfilesViewModel(private val repository: NotificationProfilesRepository) : ViewModel() {
|
||||||
|
|
||||||
fun getProfiles(): Flowable<List<NotificationProfile>> {
|
val state: Flow<NotificationProfilesState> = repository.getProfiles()
|
||||||
return repository.getProfiles()
|
.asFlow()
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.map { profiles -> NotificationProfilesState(profiles = profiles) }
|
||||||
}
|
|
||||||
|
|
||||||
class Factory() : ViewModelProvider.Factory {
|
class Factory() : ViewModelProvider.Factory {
|
||||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.components.settings.app.notifications.profiles.models
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
import android.widget.ImageView
|
|
||||||
import com.airbnb.lottie.SimpleColorFilter
|
|
||||||
import org.thoughtcrime.securesms.R
|
|
||||||
import org.thoughtcrime.securesms.components.settings.PreferenceModel
|
|
||||||
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
|
|
||||||
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
|
|
||||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
|
||||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingViewHolder
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DSL custom preference for showing no profiles/empty state.
|
|
||||||
*/
|
|
||||||
object NoNotificationProfiles {
|
|
||||||
|
|
||||||
fun register(adapter: MappingAdapter) {
|
|
||||||
adapter.registerFactory(Model::class.java, LayoutFactory({ ViewHolder(it) }, R.layout.notification_profiles_empty))
|
|
||||||
}
|
|
||||||
|
|
||||||
class Model(val onClick: () -> Unit) : PreferenceModel<Model>() {
|
|
||||||
override fun areItemsTheSame(newItem: Model): Boolean = true
|
|
||||||
}
|
|
||||||
|
|
||||||
class ViewHolder(itemView: View) : MappingViewHolder<Model>(itemView) {
|
|
||||||
|
|
||||||
private val icon: ImageView = findViewById(R.id.notification_profiles_empty_icon)
|
|
||||||
private val button: View = findViewById(R.id.notification_profiles_empty_create_profile)
|
|
||||||
|
|
||||||
override fun bind(model: Model) {
|
|
||||||
icon.background.colorFilter = SimpleColorFilter(AvatarColor.A100.colorInt())
|
|
||||||
button.setOnClickListener { model.onClick() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +1,51 @@
|
|||||||
package org.thoughtcrime.securesms.components.settings.app.notifications.profiles.models
|
package org.thoughtcrime.securesms.components.settings.app.notifications.profiles.models
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Switch
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.vectorResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import com.airbnb.lottie.SimpleColorFilter
|
import com.airbnb.lottie.SimpleColorFilter
|
||||||
|
import com.google.accompanist.drawablepainter.rememberDrawablePainter
|
||||||
import com.google.android.material.materialswitch.MaterialSwitch
|
import com.google.android.material.materialswitch.MaterialSwitch
|
||||||
|
import org.signal.core.ui.compose.DayNightPreviews
|
||||||
|
import org.signal.core.ui.compose.Previews
|
||||||
|
import org.signal.core.ui.compose.horizontalGutters
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
|
import org.thoughtcrime.securesms.components.emoji.EmojiUtil
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsIcon
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsIcon
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||||
import org.thoughtcrime.securesms.components.settings.PreferenceModel
|
import org.thoughtcrime.securesms.components.settings.PreferenceModel
|
||||||
import org.thoughtcrime.securesms.components.settings.PreferenceViewHolder
|
import org.thoughtcrime.securesms.components.settings.PreferenceViewHolder
|
||||||
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
|
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
|
||||||
|
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
|
||||||
|
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfileId
|
||||||
|
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfileSchedule
|
||||||
|
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfiles
|
||||||
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
|
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
|
||||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
import org.thoughtcrime.securesms.util.visible
|
import org.thoughtcrime.securesms.util.visible
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DSL custom preference for showing Notification Profile rows.
|
* DSL custom preference for showing Notification Profile rows.
|
||||||
@@ -48,3 +82,122 @@ object NotificationProfilePreference {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NotificationProfileRow(
|
||||||
|
profile: NotificationProfile,
|
||||||
|
isActiveProfile: Boolean = false,
|
||||||
|
showSwitch: Boolean = false,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
onClick: (Long) -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable(onClick = { onClick(profile.id) })
|
||||||
|
.horizontalGutters()
|
||||||
|
.padding(vertical = 16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(40.dp)
|
||||||
|
.background(
|
||||||
|
color = Color(profile.color.colorInt()),
|
||||||
|
shape = CircleShape
|
||||||
|
)
|
||||||
|
.padding(8.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
if (profile.emoji.isNotEmpty()) {
|
||||||
|
val emojiDrawable = remember(profile.emoji) { EmojiUtil.convertToDrawable(context, profile.emoji) }
|
||||||
|
|
||||||
|
Image(
|
||||||
|
painter = rememberDrawablePainter(drawable = emojiDrawable),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(24.dp)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Image(
|
||||||
|
imageVector = ImageVector.vectorResource(R.drawable.ic_moon_24),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(24.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = profile.name,
|
||||||
|
style = MaterialTheme.typography.bodyLarge
|
||||||
|
)
|
||||||
|
|
||||||
|
val summary = remember(isActiveProfile) {
|
||||||
|
if (isActiveProfile) {
|
||||||
|
NotificationProfiles.getActiveProfileDescription(context, profile)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (summary != null) {
|
||||||
|
Text(
|
||||||
|
text = summary,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showSwitch) {
|
||||||
|
Switch(
|
||||||
|
checked = isActiveProfile,
|
||||||
|
onCheckedChange = { onClick(profile.id) },
|
||||||
|
enabled = enabled
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@DayNightPreviews
|
||||||
|
@Composable
|
||||||
|
private fun NotificationProfileRowPreview() {
|
||||||
|
Previews.Preview {
|
||||||
|
Column {
|
||||||
|
NotificationProfileRow(
|
||||||
|
profile = NotificationProfile(
|
||||||
|
id = 1L,
|
||||||
|
name = "Work",
|
||||||
|
createdAt = 0L,
|
||||||
|
schedule = NotificationProfileSchedule(
|
||||||
|
id = 1L
|
||||||
|
),
|
||||||
|
emoji = "",
|
||||||
|
notificationProfileId = NotificationProfileId(UUID.randomUUID())
|
||||||
|
),
|
||||||
|
onClick = {}
|
||||||
|
)
|
||||||
|
|
||||||
|
NotificationProfileRow(
|
||||||
|
profile = NotificationProfile(
|
||||||
|
id = 1L,
|
||||||
|
name = "Sleep",
|
||||||
|
createdAt = 0L,
|
||||||
|
schedule = NotificationProfileSchedule(
|
||||||
|
id = 1L
|
||||||
|
),
|
||||||
|
emoji = "",
|
||||||
|
notificationProfileId = NotificationProfileId(UUID.randomUUID())
|
||||||
|
),
|
||||||
|
onClick = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,64 +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"
|
|
||||||
tools:viewBindingIgnore="true"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:padding="24dp">
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageView
|
|
||||||
android:id="@+id/notification_profiles_empty_icon"
|
|
||||||
android:layout_width="88dp"
|
|
||||||
android:layout_height="88dp"
|
|
||||||
android:layout_marginTop="96dp"
|
|
||||||
android:background="@drawable/tinted_circle"
|
|
||||||
android:padding="20dp"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:srcCompat="@drawable/ic_sleeping_face"
|
|
||||||
tools:backgroundTint="#E3E3FE" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/notification_profiles_empty_title"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="24dp"
|
|
||||||
android:layout_marginTop="20dp"
|
|
||||||
android:layout_marginEnd="24dp"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="@string/NotificationProfilesFragment__notification_profiles"
|
|
||||||
android:textAppearance="@style/TextAppearance.Signal.Title1"
|
|
||||||
android:hyphenationFrequency="normal"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/notification_profiles_empty_icon" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/notification_profiles_empty_description"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="24dp"
|
|
||||||
android:layout_marginTop="12dp"
|
|
||||||
android:layout_marginEnd="24dp"
|
|
||||||
android:gravity="center"
|
|
||||||
android:textAppearance="@style/TextAppearance.Signal.Body1"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/notification_profiles_empty_title"
|
|
||||||
android:text="@string/NotificationProfilesFragment__create_a_profile_to_receive_notifications_and_calls_only_from_the_people_and_groups_you_want_to_hear_from" />
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
|
||||||
android:id="@+id/notification_profiles_empty_create_profile"
|
|
||||||
style="@style/Signal.Widget.Button.Large.Primary"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/NotificationProfilesFragment__create_profile"
|
|
||||||
app:layout_constraintVertical_bias="1"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/notification_profiles_empty_description" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
Reference in New Issue
Block a user