Skip to content
Merged
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 @@ -4,10 +4,10 @@ import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.ContentTransform
import androidx.compose.animation.SizeTransform
import androidx.compose.animation.core.keyframes
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.shrinkVertically
import androidx.compose.animation.togetherWith
import androidx.compose.ui.unit.IntSize

Expand All @@ -30,12 +30,20 @@ private const val A_QUARTER = 4
* as well as the size transformation.
*/
fun <T> AnimatedContentTransitionScope<T>.bannerSlideInSlideOutAnimationSpec(): ContentTransform {
val enter = fadeIn() + slideInVertically()
val exit = fadeOut() + slideOutVertically()
return enter togetherWith exit using SizeTransform { initialSize, targetSize ->
keyframes {
IntSize(width = targetSize.width, height = initialSize.height) at durationMillis / A_QUARTER
IntSize(width = targetSize.width, height = targetSize.height)
val enter = fadeIn() + expandVertically()
val exit = fadeOut() + shrinkVertically()
return (enter togetherWith exit) using SizeTransform { initialSize, targetSize ->
this.contentAlignment
if (targetState != null) {
keyframes {
IntSize(width = targetSize.width, height = initialSize.height) at durationMillis / A_QUARTER
IntSize(width = targetSize.width, height = targetSize.height)
}
} else {
keyframes {
IntSize(width = initialSize.width, height = initialSize.height) at durationMillis / A_QUARTER
IntSize(width = initialSize.width, height = 0)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,12 @@
import androidx.core.os.BundleCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import app.k9mail.core.ui.legacy.designsystem.atom.icon.Icons;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.fsck.k9.activity.compose.MessageComposeInAppNotificationFragment;
import net.thunderbird.core.android.account.LegacyAccountDto;
import app.k9mail.legacy.di.DI;
import net.thunderbird.core.android.account.Identity;
Expand Down Expand Up @@ -121,6 +124,8 @@
import com.google.android.material.textview.MaterialTextView;
import net.thunderbird.core.android.account.MessageFormat;
import net.thunderbird.core.android.contact.ContactIntentHelper;
import net.thunderbird.core.featureflag.FeatureFlagProvider;
import net.thunderbird.core.featureflag.compat.FeatureFlagProviderCompat;
import net.thunderbird.core.preference.GeneralSettingsManager;
import net.thunderbird.core.ui.theme.manager.ThemeManager;
import net.thunderbird.feature.search.legacy.LocalMessageSearch;
Expand Down Expand Up @@ -202,6 +207,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
private final Contacts contacts = DI.get(Contacts.class);

private final CameraCaptureHandler cameraCaptureHandler = DI.get(CameraCaptureHandler.class);
private final FeatureFlagProvider featureFlagProvider = DI.get(FeatureFlagProvider.class);

private QuotedMessagePresenter quotedMessagePresenter;
private MessageLoaderHelper messageLoaderHelper;
Expand Down Expand Up @@ -313,6 +319,8 @@ public void onCreate(Bundle savedInstanceState) {
return;
}

initializeInAppNotificationFragment();

chooseIdentityView = findViewById(R.id.identity);
chooseIdentityView.setOnClickListener(this);

Expand Down Expand Up @@ -1733,6 +1741,34 @@ private void initializeActionBar() {
actionBar.setDisplayHomeAsUpEnabled(true);
}

private void initializeInAppNotificationFragment() {
if (FeatureFlagProviderCompat
.provide(featureFlagProvider, "display_in_app_notifications")
.isDisabledOrUnavailable()) {
return;
}

if (account == null) {
Log.w("Can't initialize in-app notifications. Account is currently null");
return;
}
final FragmentManager fragmentManager = getSupportFragmentManager();
final Fragment currentFragment = fragmentManager
.findFragmentByTag(MessageComposeInAppNotificationFragment.FRAGMENT_TAG);

if (currentFragment != null) {
return;
}

final MessageComposeInAppNotificationFragment inAppNotificationFragment =
MessageComposeInAppNotificationFragment.newInstance(account.getUuid());
fragmentManager
.beginTransaction()
.add(R.id.message_compose_in_app_notifications_container, inAppNotificationFragment,
MessageComposeInAppNotificationFragment.FRAGMENT_TAG)
.commit();
}

// TODO We miss callbacks for this listener if they happens while we are paused!
public MessagingListener messagingListener = new SimpleMessagingListener() {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package com.fsck.k9.activity.compose

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import com.google.android.material.snackbar.Snackbar
import kotlinx.collections.immutable.persistentSetOf
import net.thunderbird.core.logging.Logger
import net.thunderbird.core.ui.theme.api.FeatureThemeProvider
import net.thunderbird.feature.account.AccountId
import net.thunderbird.feature.account.AccountIdFactory
import net.thunderbird.feature.notification.api.ui.InAppNotificationHost
import net.thunderbird.feature.notification.api.ui.action.NotificationAction
import net.thunderbird.feature.notification.api.ui.host.DisplayInAppNotificationFlag
import net.thunderbird.feature.notification.api.ui.host.visual.SnackbarVisual
import net.thunderbird.feature.notification.api.ui.style.SnackbarDuration
import org.koin.android.ext.android.inject

private const val TAG = "MessageComposeInAppNotificationFragment"

class MessageComposeInAppNotificationFragment : Fragment() {
private val themeProvider: FeatureThemeProvider by inject()
private val logger: Logger by inject()
private var parentView: View? = null
private var accountId: AccountId? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let { arg ->
accountId = requireNotNull(arg.getString(ARG_ACCOUNT_ID)?.let { AccountIdFactory.of(it) }) {
"Argument $ARG_ACCOUNT_ID is required"
}
}
}

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
ComposeView(requireContext()).apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
parentView = container
setContent {
themeProvider.WithTheme {
InAppNotificationHost(
onActionClick = ::onNotificationActionClick,
enabled = persistentSetOf(
DisplayInAppNotificationFlag.BannerGlobalNotifications,
DisplayInAppNotificationFlag.SnackbarNotifications,
),
onSnackbarNotificationEvent = ::onSnackbarInAppNotificationEvent,
eventFilter = { event ->
val accountUuid = event.notification.accountUuid
accountUuid != null && accountUuid == accountId?.asRaw()
},
)
}
}
}

override fun onDestroyView() {
super.onDestroyView()
parentView = null
}

private suspend fun onSnackbarInAppNotificationEvent(visual: SnackbarVisual) {
parentView?.let { view ->
val (message, action, duration) = visual
Snackbar.make(
view,
message,
when (duration) {
SnackbarDuration.Short -> Snackbar.LENGTH_SHORT
SnackbarDuration.Long -> Snackbar.LENGTH_LONG
SnackbarDuration.Indefinite -> Snackbar.LENGTH_INDEFINITE
},
).apply {
if (action != null) {
setAction(action.resolveTitle()) {
// TODO.
}
}
}.show()
}
}

private fun onNotificationActionClick(action: NotificationAction) {
logger.verbose(TAG) { "onNotificationActionClick() called with: action = $action" }
}

companion object {
private const val ARG_ACCOUNT_ID = "MessageComposeInAppNotificationFragment_account_id"
const val FRAGMENT_TAG = "MessageComposeInAppNotificationFragment"

fun newInstance(accountId: AccountId): MessageComposeInAppNotificationFragment =
MessageComposeInAppNotificationFragment().apply {
arguments = bundleOf(ARG_ACCOUNT_ID to accountId.asRaw())
}

@JvmStatic
fun newInstance(accountUuid: String): MessageComposeInAppNotificationFragment =
newInstance(AccountIdFactory.of(accountUuid))
}
}
27 changes: 20 additions & 7 deletions legacy/ui/legacy/src/main/res/layout/message_compose.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,26 @@

<include layout="@layout/toolbar" />

<ViewStub
android:id="@+id/message_compose_content"
android:layout="@layout/message_compose_content"
<LinearLayout
android:id="@+id/message_compose_container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
tools:visibility="visible"
/>
android:layout_height="match_parent"
android:orientation="vertical"
>

<FrameLayout
android:id="@+id/message_compose_in_app_notifications_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>

<ViewStub
android:id="@+id/message_compose_content"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout="@layout/message_compose_content"
tools:visibility="visible"
/>
</LinearLayout>
</LinearLayout>
Loading