Skip to content

Commit 031173a

Browse files
committed
feat(notifications): add in-app notification to message compose
1 parent 1bbbf11 commit 031173a

3 files changed

Lines changed: 165 additions & 7 deletions

File tree

legacy/ui/legacy/src/main/java/com/fsck/k9/activity/MessageCompose.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,12 @@
4949
import androidx.core.os.BundleCompat;
5050
import androidx.core.view.ViewCompat;
5151
import androidx.core.view.WindowInsetsCompat;
52+
import androidx.fragment.app.Fragment;
53+
import androidx.fragment.app.FragmentManager;
5254
import app.k9mail.core.ui.legacy.designsystem.atom.icon.Icons;
5355
import com.bumptech.glide.Glide;
5456
import com.bumptech.glide.load.engine.DiskCacheStrategy;
57+
import com.fsck.k9.activity.compose.MessageComposeInAppNotificationFragment;
5558
import net.thunderbird.core.android.account.LegacyAccountDto;
5659
import app.k9mail.legacy.di.DI;
5760
import net.thunderbird.core.android.account.Identity;
@@ -121,6 +124,8 @@
121124
import com.google.android.material.textview.MaterialTextView;
122125
import net.thunderbird.core.android.account.MessageFormat;
123126
import net.thunderbird.core.android.contact.ContactIntentHelper;
127+
import net.thunderbird.core.featureflag.FeatureFlagProvider;
128+
import net.thunderbird.core.featureflag.compat.FeatureFlagProviderCompat;
124129
import net.thunderbird.core.preference.GeneralSettingsManager;
125130
import net.thunderbird.core.ui.theme.manager.ThemeManager;
126131
import net.thunderbird.feature.search.legacy.LocalMessageSearch;
@@ -202,6 +207,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
202207
private final Contacts contacts = DI.get(Contacts.class);
203208

204209
private final CameraCaptureHandler cameraCaptureHandler = DI.get(CameraCaptureHandler.class);
210+
private final FeatureFlagProvider featureFlagProvider = DI.get(FeatureFlagProvider.class);
205211

206212
private QuotedMessagePresenter quotedMessagePresenter;
207213
private MessageLoaderHelper messageLoaderHelper;
@@ -313,6 +319,8 @@ public void onCreate(Bundle savedInstanceState) {
313319
return;
314320
}
315321

322+
initializeInAppNotificationFragment();
323+
316324
chooseIdentityView = findViewById(R.id.identity);
317325
chooseIdentityView.setOnClickListener(this);
318326

@@ -1733,6 +1741,34 @@ private void initializeActionBar() {
17331741
actionBar.setDisplayHomeAsUpEnabled(true);
17341742
}
17351743

1744+
private void initializeInAppNotificationFragment() {
1745+
if (FeatureFlagProviderCompat
1746+
.provide(featureFlagProvider, "display_in_app_notifications")
1747+
.isDisabledOrUnavailable()) {
1748+
return;
1749+
}
1750+
1751+
if (account == null) {
1752+
Log.w("Can't initialize in-app notifications. Account is currently null");
1753+
return;
1754+
}
1755+
final FragmentManager fragmentManager = getSupportFragmentManager();
1756+
final Fragment currentFragment = fragmentManager
1757+
.findFragmentByTag(MessageComposeInAppNotificationFragment.FRAGMENT_TAG);
1758+
1759+
if (currentFragment != null) {
1760+
return;
1761+
}
1762+
1763+
final MessageComposeInAppNotificationFragment inAppNotificationFragment =
1764+
MessageComposeInAppNotificationFragment.newInstance(account.getUuid());
1765+
fragmentManager
1766+
.beginTransaction()
1767+
.add(R.id.message_compose_in_app_notifications_container, inAppNotificationFragment,
1768+
MessageComposeInAppNotificationFragment.FRAGMENT_TAG)
1769+
.commit();
1770+
}
1771+
17361772
// TODO We miss callbacks for this listener if they happens while we are paused!
17371773
public MessagingListener messagingListener = new SimpleMessagingListener() {
17381774

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package com.fsck.k9.activity.compose
2+
3+
import android.os.Bundle
4+
import android.view.LayoutInflater
5+
import android.view.View
6+
import android.view.ViewGroup
7+
import androidx.compose.animation.animateContentSize
8+
import androidx.compose.ui.Modifier
9+
import androidx.compose.ui.platform.ComposeView
10+
import androidx.compose.ui.platform.ViewCompositionStrategy
11+
import androidx.core.os.bundleOf
12+
import androidx.fragment.app.Fragment
13+
import com.google.android.material.snackbar.Snackbar
14+
import kotlinx.collections.immutable.persistentSetOf
15+
import net.thunderbird.core.logging.Logger
16+
import net.thunderbird.core.ui.theme.api.FeatureThemeProvider
17+
import net.thunderbird.feature.account.AccountId
18+
import net.thunderbird.feature.account.AccountIdFactory
19+
import net.thunderbird.feature.notification.api.ui.InAppNotificationHost
20+
import net.thunderbird.feature.notification.api.ui.action.NotificationAction
21+
import net.thunderbird.feature.notification.api.ui.host.DisplayInAppNotificationFlag
22+
import net.thunderbird.feature.notification.api.ui.host.visual.SnackbarVisual
23+
import net.thunderbird.feature.notification.api.ui.style.SnackbarDuration
24+
import org.koin.android.ext.android.inject
25+
26+
private const val TAG = "MessageComposeInAppNotificationFragment"
27+
28+
class MessageComposeInAppNotificationFragment : Fragment() {
29+
private val themeProvider: FeatureThemeProvider by inject()
30+
private val logger: Logger by inject()
31+
private var parentView: View? = null
32+
private var accountId: AccountId? = null
33+
34+
override fun onCreate(savedInstanceState: Bundle?) {
35+
super.onCreate(savedInstanceState)
36+
arguments?.let { arg ->
37+
accountId = requireNotNull(arg.getString(ARG_ACCOUNT_ID)?.let { AccountIdFactory.of(it) }) {
38+
"Argument $ARG_ACCOUNT_ID is required"
39+
}
40+
}
41+
}
42+
43+
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
44+
ComposeView(requireContext()).apply {
45+
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
46+
parentView = container
47+
setContent {
48+
themeProvider.WithTheme {
49+
InAppNotificationHost(
50+
onActionClick = ::onNotificationActionClick,
51+
enabled = persistentSetOf(
52+
DisplayInAppNotificationFlag.BannerGlobalNotifications,
53+
DisplayInAppNotificationFlag.SnackbarNotifications,
54+
),
55+
onSnackbarNotificationEvent = ::onSnackbarInAppNotificationEvent,
56+
eventFilter = { event ->
57+
val accountUuid = event.notification.accountUuid
58+
accountUuid != null && accountUuid == accountId?.asRaw()
59+
},
60+
modifier = Modifier.animateContentSize(),
61+
)
62+
}
63+
}
64+
}
65+
66+
override fun onDestroyView() {
67+
super.onDestroyView()
68+
parentView = null
69+
}
70+
71+
private suspend fun onSnackbarInAppNotificationEvent(visual: SnackbarVisual) {
72+
parentView?.let { view ->
73+
val (message, action, duration) = visual
74+
Snackbar.make(
75+
view,
76+
message,
77+
when (duration) {
78+
SnackbarDuration.Short -> Snackbar.LENGTH_SHORT
79+
SnackbarDuration.Long -> Snackbar.LENGTH_LONG
80+
SnackbarDuration.Indefinite -> Snackbar.LENGTH_INDEFINITE
81+
},
82+
).apply {
83+
if (action != null) {
84+
setAction(action.resolveTitle()) {
85+
// TODO.
86+
}
87+
}
88+
}.show()
89+
}
90+
}
91+
92+
private fun onNotificationActionClick(action: NotificationAction) {
93+
logger.verbose(TAG) { "onNotificationActionClick() called with: action = $action" }
94+
}
95+
96+
companion object {
97+
private const val ARG_ACCOUNT_ID = "MessageComposeInAppNotificationFragment_account_id"
98+
const val FRAGMENT_TAG = "MessageComposeInAppNotificationFragment"
99+
100+
fun newInstance(accountId: AccountId): MessageComposeInAppNotificationFragment =
101+
MessageComposeInAppNotificationFragment().apply {
102+
arguments = bundleOf(ARG_ACCOUNT_ID to accountId.asRaw())
103+
}
104+
105+
@JvmStatic
106+
fun newInstance(accountUuid: String): MessageComposeInAppNotificationFragment =
107+
newInstance(AccountIdFactory.of(accountUuid))
108+
}
109+
}

legacy/ui/legacy/src/main/res/layout/message_compose.xml

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,26 @@
1010

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

13-
<ViewStub
14-
android:id="@+id/message_compose_content"
15-
android:layout="@layout/message_compose_content"
13+
<LinearLayout
14+
android:id="@+id/message_compose_container"
1615
android:layout_width="match_parent"
17-
android:layout_height="0dp"
18-
android:layout_weight="1"
19-
tools:visibility="visible"
20-
/>
16+
android:layout_height="match_parent"
17+
android:orientation="vertical"
18+
>
2119

20+
<FrameLayout
21+
android:id="@+id/message_compose_in_app_notifications_container"
22+
android:layout_width="match_parent"
23+
android:layout_height="wrap_content"
24+
/>
25+
26+
<ViewStub
27+
android:id="@+id/message_compose_content"
28+
android:layout_width="match_parent"
29+
android:layout_height="0dp"
30+
android:layout_weight="1"
31+
android:layout="@layout/message_compose_content"
32+
tools:visibility="visible"
33+
/>
34+
</LinearLayout>
2235
</LinearLayout>

0 commit comments

Comments
 (0)