Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,10 @@ private void navigateDiscover() {
// Click on the "Reader" tab and take a screenshot
clickOn(R.id.nav_reader);

waitForElementToBeDisplayedWithoutFailure(R.id.interests_fragment_container);
waitForElementToBeDisplayedWithoutFailure(R.id.fragment_container);

idleFor(2000);

swipeUpOnView(R.id.interests_fragment_container, (float) 1.15);

swipeUpOnView(R.id.fragment_container, (float) 0.5);

idleFor(2000);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,29 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.SwitchCompat
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.selection.toggleable
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStoreOwner
import com.google.android.material.bottomsheet.BottomSheetBehavior
Expand All @@ -14,8 +36,10 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.snackbar.Snackbar
import org.wordpress.android.R
import org.wordpress.android.WordPress
import org.wordpress.android.databinding.CommentNotificationsBottomSheetBinding
import org.wordpress.android.ui.compose.theme.AppThemeM3
import org.wordpress.android.ui.compose.unit.Margin
import org.wordpress.android.ui.reader.viewmodels.ConversationNotificationsViewModel
import org.wordpress.android.ui.reader.views.compose.ReaderSheetHandle
import org.wordpress.android.ui.utils.UiHelpers
import org.wordpress.android.viewmodel.ContextProvider
import org.wordpress.android.viewmodel.observeEvent
Expand All @@ -33,46 +57,53 @@ class CommentNotificationsBottomSheetFragment : BottomSheetDialogFragment() {
lateinit var uiHelpers: UiHelpers
private lateinit var viewModel: ConversationNotificationsViewModel

// kept in a field so the view model's status updates and the restored state reach the composition
private val isReceivingPushNotifications: MutableState<Boolean> = mutableStateOf(false)

// this avoids a weird animation when opening this dialog from activity that is using
// @style/WordPress.NoActionBar.TranslucentStatus
override fun getTheme(): Int {
return R.style.WordPress_BottomSheetDialogTheme_NonTranslucent
}

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.comment_notifications_bottom_sheet, container)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
isReceivingPushNotifications.value = savedInstanceState?.getBoolean(STATE_IS_RECEIVING_PUSH_NOTIFICATIONS)
?: requireArguments().getBoolean(ARG_IS_RECEIVING_PUSH_NOTIFICATIONS)
initViewModel(requireArguments().getBoolean(ARG_IS_ATTACHED_TO_FRAGMENT))

return ComposeView(requireContext()).apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
var isChecked by isReceivingPushNotifications
AppThemeM3 {
CommentNotificationsSheet(
isReceivingPushNotifications = isChecked,
onPushNotificationsToggled = { checked ->
isChecked = checked
viewModel.onChangePushNotificationsRequest(checked)
},
onUnfollowClick = viewModel::onUnfollowTapped
)
}
}
}
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

with(CommentNotificationsBottomSheetBinding.bind(view)) {
val isReceivingPushNotifications = requireArguments().getBoolean(ARG_IS_RECEIVING_PUSH_NOTIFICATIONS)
val isAttachedToFragment = requireArguments().getBoolean(ARG_IS_ATTACHED_TO_FRAGMENT)

initViewModel(isAttachedToFragment)

if (savedInstanceState == null) {
enablePushNotifications.isChecked = isReceivingPushNotifications
}

unfollowConversation.setOnClickListener {
viewModel.onUnfollowTapped()
}

enablePushNotifications.setOnClickListener {
viewModel.onChangePushNotificationsRequest((it as SwitchCompat).isChecked)
}

initObservers()
}
initObservers(view)

(dialog as? BottomSheetDialog)?.apply {
behavior.state = BottomSheetBehavior.STATE_EXPANDED
behavior.skipCollapsed = true
}
}

override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putBoolean(STATE_IS_RECEIVING_PUSH_NOTIFICATIONS, isReceivingPushNotifications.value)
}

private fun initViewModel(isAttachedToFragment: Boolean) {
viewModel = ViewModelProvider(
if (isAttachedToFragment) {
Expand All @@ -84,19 +115,19 @@ class CommentNotificationsBottomSheetFragment : BottomSheetDialogFragment() {
).get(ConversationNotificationsViewModel::class.java)
}

private fun CommentNotificationsBottomSheetBinding.initObservers() {
private fun initObservers(view: View) {
viewModel.snackbarEvents.observeEvent(viewLifecycleOwner, { messageHolder ->
if (!isAdded) return@observeEvent

WPSnackbar.make(
coordinator,
view,
uiHelpers.getTextOfUiString(contextProvider.getContext(), messageHolder.message),
Snackbar.LENGTH_LONG
).show()
})

viewModel.pushNotificationsStatusUpdate.observeEvent(viewLifecycleOwner, { isReceivingPushNotifications ->
enablePushNotifications.isChecked = isReceivingPushNotifications
viewModel.pushNotificationsStatusUpdate.observeEvent(viewLifecycleOwner, { isReceiving ->
isReceivingPushNotifications.value = isReceiving
})
}

Expand All @@ -108,6 +139,7 @@ class CommentNotificationsBottomSheetFragment : BottomSheetDialogFragment() {
companion object {
private const val ARG_IS_RECEIVING_PUSH_NOTIFICATIONS = "ARG_IS_RECEIVING_PUSH_NOTIFICATIONS"
private const val ARG_IS_ATTACHED_TO_FRAGMENT = "ARG_IS_ATTACHED_TO_FRAGMENT"
private const val STATE_IS_RECEIVING_PUSH_NOTIFICATIONS = "STATE_IS_RECEIVING_PUSH_NOTIFICATIONS"

@JvmStatic
fun newInstance(
Expand All @@ -123,3 +155,52 @@ class CommentNotificationsBottomSheetFragment : BottomSheetDialogFragment() {
}
}
}

@Composable
private fun CommentNotificationsSheet(
isReceivingPushNotifications: Boolean,
onPushNotificationsToggled: (Boolean) -> Unit,
onUnfollowClick: () -> Unit
) {
val sheetDescription = stringResource(R.string.reader_follow_comments_bottom_sheet_description)
Column(
modifier = Modifier
.fillMaxWidth()
.semantics { contentDescription = sheetDescription }
.padding(start = Margin.ExtraLarge.value, end = Margin.ExtraLarge.value, bottom = Margin.ExtraLarge.value)
) {
ReaderSheetHandle()
Text(
text = stringResource(R.string.reader_follow_comments_message),
style = MaterialTheme.typography.bodyLarge
)
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(Margin.ExtraLarge.value),
modifier = Modifier
.fillMaxWidth()
.padding(top = Margin.Large.value)
.toggleable(
value = isReceivingPushNotifications,
role = Role.Switch,
onValueChange = onPushNotificationsToggled
)
.padding(vertical = Margin.Large.value)
) {
Text(
text = stringResource(R.string.reader_follow_comments_enable_push_notifications),
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.weight(1f)
)
Switch(checked = isReceivingPushNotifications, onCheckedChange = null)
}
OutlinedButton(
onClick = onUnfollowClick,
modifier = Modifier
.fillMaxWidth()
.padding(top = Margin.Large.value)
) {
Text(stringResource(R.string.reader_follow_comments_unfollow_conversation))
}
}
}
Original file line number Diff line number Diff line change
@@ -1,46 +1,114 @@
package org.wordpress.android.ui.reader

import android.content.res.Configuration
import android.os.Bundle
import android.view.MenuItem
import org.wordpress.android.databinding.ReaderNoSiteToReblogBinding
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import org.wordpress.android.R
import org.wordpress.android.ui.ActivityLauncher
import org.wordpress.android.ui.compose.theme.AppThemeM3
import org.wordpress.android.ui.compose.unit.Margin
import org.wordpress.android.ui.main.BaseAppCompatActivity
import android.R as AndroidR
import org.wordpress.android.util.extensions.setContent

/*
* Serves as an intermediate screen where the user is informed that a site is needed for the reblog action
*/
class NoSiteToReblogActivity : BaseAppCompatActivity() {
// overridePendingTransition is deprecated in SDK 34 in favor of overrideActivityTransition, but the latter requires
// SDK 34. overridePendingTransition still works on Android 14 so using it should be safe for now.
@Suppress("DEPRECATION")
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)

val binding = ReaderNoSiteToReblogBinding.inflate(layoutInflater)

setContentView(binding.root)

setSupportActionBar(binding.includeToolbarMain.toolbarMain)

supportActionBar?.apply {
setDisplayShowTitleEnabled(false)
setDisplayHomeAsUpEnabled(true)
setContent {
AppThemeM3 {
NoSiteToReblogScreen(
onBack = onBackPressedDispatcher::onBackPressed,
onViewMySiteClick = ::onViewMySiteClicked
)
}
}
}

binding.noSiteToReblogView.button.setOnClickListener {
ActivityLauncher.viewMySiteInNewStack(this@NoSiteToReblogActivity)
setResult(RESULT_OK)
finish()
overridePendingTransition(0, 0)
}
// overridePendingTransition is deprecated in SDK 34 in favor of overrideActivityTransition, but the latter requires
// SDK 34. overridePendingTransition still works on Android 14 so using it should be safe for now.
@Suppress("DEPRECATION")
private fun onViewMySiteClicked() {
ActivityLauncher.viewMySiteInNewStack(this)
setResult(RESULT_OK)
finish()
overridePendingTransition(0, 0)
}
}

override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
AndroidR.id.home -> {
finish()
true
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun NoSiteToReblogScreen(onBack: () -> Unit, onViewMySiteClick: () -> Unit) {
Scaffold(
topBar = {
TopAppBar(
title = {},
navigationIcon = {
IconButton(onClick = onBack) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.back))
}
}
)
}
) { contentPadding ->
val isLandscape = LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxSize()
.padding(contentPadding)
.verticalScroll(rememberScrollState())
.padding(Margin.ExtraMediumLarge.value)
) {
if (!isLandscape) {
Image(
painter = painterResource(R.drawable.illustration_reader_empty),
contentDescription = null,
modifier = Modifier.padding(bottom = Margin.ExtraLarge.value)
)
}
Text(
text = stringResource(R.string.reader_no_blog_to_reblog_title),
style = MaterialTheme.typography.titleLarge,
textAlign = TextAlign.Center
)
Text(
text = stringResource(R.string.reader_no_blog_to_reblog_detail),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center,
modifier = Modifier.padding(top = Margin.Medium.value)
)
Button(onClick = onViewMySiteClick, modifier = Modifier.padding(top = Margin.ExtraMediumLarge.value)) {
Text(stringResource(R.string.reader_no_blog_to_reblog_action))
}
}
else -> super.onOptionsItemSelected(item)
}
}
Loading
Loading