diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index fa2c342af5..2624d97b6a 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -36,6 +36,16 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/kotlin/com/zulip/flutter/AndroidIntentEventListener.kt b/android/app/src/main/kotlin/com/zulip/flutter/AndroidIntentEventListener.kt
new file mode 100644
index 0000000000..9151f63da2
--- /dev/null
+++ b/android/app/src/main/kotlin/com/zulip/flutter/AndroidIntentEventListener.kt
@@ -0,0 +1,112 @@
+package com.zulip.flutter
+
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.provider.OpenableColumns
+
+class AndroidIntentEventListener : AndroidIntentEventsStreamHandler() {
+ private var eventSink: PigeonEventSink? = null
+ private val buffer = mutableListOf()
+
+ override fun onListen(p0: Any?, sink: PigeonEventSink) {
+ eventSink = sink
+ buffer.forEach { eventSink!!.success(it) }
+ }
+
+ private fun onEvent(event: AndroidIntentEvent) {
+ if (eventSink != null) {
+ eventSink?.success(event)
+ } else {
+ buffer.add(event)
+ }
+ }
+
+ fun handleSend(context: Context, intent: Intent) {
+ val intentAction = intent.action
+ assert(
+ intentAction == Intent.ACTION_SEND
+ || intentAction == Intent.ACTION_SEND_MULTIPLE
+ )
+
+ // EXTRA_TEXT and EXTRA_STREAM are the text and file components of the
+ // content, respectively. The ACTION_SEND{,_MULTIPLE} docs say
+ // "either" / "or" will be present:
+ // https://developer.android.com/reference/android/content/Intent#ACTION_SEND
+ // But empirically both can be present, commonly, so we accept that form,
+ // interpreting it as an intent to share both kinds of data.
+ //
+ // Empirically, sometimes EXTRA_TEXT isn't something we think needs to be
+ // shared, like the URL of a file that's present in EXTRA_STREAM… but we
+ // shrug and include it anyway because we don't want to second-guess other
+ // apps' decisions about what to include; it's their responsibility.
+
+ val extraText = intent.getStringExtra(Intent.EXTRA_TEXT)
+ val extraStream = when (intentAction) {
+ Intent.ACTION_SEND -> {
+ var extraStream: List? = null
+ // TODO(android-sdk-33) Remove the use of deprecated API.
+ @Suppress("DEPRECATION") val url = intent.getParcelableExtra(Intent.EXTRA_STREAM)
+ if (url != null) {
+ extraStream = listOf(getIntentSharedFile(context, url))
+ }
+ extraStream
+ }
+
+ Intent.ACTION_SEND_MULTIPLE -> {
+ var extraStream: MutableList? = null
+ // TODO(android-sdk-33) Remove the use of deprecated API.
+ @Suppress("DEPRECATION") val urls =
+ intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM)
+ if (urls != null) {
+ extraStream = mutableListOf()
+ for (url in urls) {
+ val sharedFile = getIntentSharedFile(context, url)
+ extraStream.add(sharedFile)
+ }
+ }
+ extraStream
+ }
+
+ else -> throw IllegalArgumentException("Unexpected value for intent.action: $intentAction")
+ }
+
+ if (extraText == null && extraStream == null) {
+ throw Exception("Got unexpected ACTION_SEND* intent, with neither EXTRA_TEXT nor EXTRA_STREAM")
+ }
+
+ onEvent(
+ AndroidIntentSendEvent(
+ action = intentAction,
+ extraText = extraText,
+ extraStream = extraStream,
+ )
+ )
+ }
+}
+
+// A helper function to retrieve the shared file from the `content://` URL
+// from the ACTION_SEND{_MULTIPLE} intent.
+fun getIntentSharedFile(context: Context, url: Uri): IntentSharedFile {
+ val contentResolver = context.contentResolver
+ val mimeType = contentResolver.getType(url)
+ val name = contentResolver.query(url, null, null, null, null)?.use { cursor ->
+ cursor.moveToFirst()
+ val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
+ cursor.getString(nameIndex)
+ } ?: ("unknown." + (mimeType?.split('/')?.last() ?: "bin"))
+
+ class ResolverFailedException(msg: String) : RuntimeException(msg)
+
+ val bytes = (contentResolver.openInputStream(url)
+ ?: throw ResolverFailedException("resolver.open… failed"))
+ .use { inputStream ->
+ inputStream.readBytes()
+ }
+
+ return IntentSharedFile(
+ name = name,
+ mimeType = mimeType,
+ bytes = bytes
+ )
+}
diff --git a/android/app/src/main/kotlin/com/zulip/flutter/AndroidIntents.g.kt b/android/app/src/main/kotlin/com/zulip/flutter/AndroidIntents.g.kt
new file mode 100644
index 0000000000..7529e632c6
--- /dev/null
+++ b/android/app/src/main/kotlin/com/zulip/flutter/AndroidIntents.g.kt
@@ -0,0 +1,203 @@
+// Autogenerated from Pigeon (v25.5.0), do not edit directly.
+// See also: https://pub.dev/packages/pigeon
+@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
+
+package com.zulip.flutter
+
+import android.util.Log
+import io.flutter.plugin.common.BasicMessageChannel
+import io.flutter.plugin.common.BinaryMessenger
+import io.flutter.plugin.common.EventChannel
+import io.flutter.plugin.common.MessageCodec
+import io.flutter.plugin.common.StandardMethodCodec
+import io.flutter.plugin.common.StandardMessageCodec
+import java.io.ByteArrayOutputStream
+import java.nio.ByteBuffer
+private object AndroidIntentsPigeonUtils {
+ fun deepEquals(a: Any?, b: Any?): Boolean {
+ if (a is ByteArray && b is ByteArray) {
+ return a.contentEquals(b)
+ }
+ if (a is IntArray && b is IntArray) {
+ return a.contentEquals(b)
+ }
+ if (a is LongArray && b is LongArray) {
+ return a.contentEquals(b)
+ }
+ if (a is DoubleArray && b is DoubleArray) {
+ return a.contentEquals(b)
+ }
+ if (a is Array<*> && b is Array<*>) {
+ return a.size == b.size &&
+ a.indices.all{ deepEquals(a[it], b[it]) }
+ }
+ if (a is List<*> && b is List<*>) {
+ return a.size == b.size &&
+ a.indices.all{ deepEquals(a[it], b[it]) }
+ }
+ if (a is Map<*, *> && b is Map<*, *>) {
+ return a.size == b.size && a.all {
+ (b as Map).containsKey(it.key) &&
+ deepEquals(it.value, b[it.key])
+ }
+ }
+ return a == b
+ }
+
+}
+
+/** Generated class from Pigeon that represents data sent in messages. */
+data class IntentSharedFile (
+ val name: String,
+ val mimeType: String? = null,
+ val bytes: ByteArray
+)
+ {
+ companion object {
+ fun fromList(pigeonVar_list: List): IntentSharedFile {
+ val name = pigeonVar_list[0] as String
+ val mimeType = pigeonVar_list[1] as String?
+ val bytes = pigeonVar_list[2] as ByteArray
+ return IntentSharedFile(name, mimeType, bytes)
+ }
+ }
+ fun toList(): List {
+ return listOf(
+ name,
+ mimeType,
+ bytes,
+ )
+ }
+ override fun equals(other: Any?): Boolean {
+ if (other !is IntentSharedFile) {
+ return false
+ }
+ if (this === other) {
+ return true
+ }
+ return AndroidIntentsPigeonUtils.deepEquals(toList(), other.toList()) }
+
+ override fun hashCode(): Int = toList().hashCode()
+}
+
+/**
+ * Generated class from Pigeon that represents data sent in messages.
+ * This class should not be extended by any user class outside of the generated file.
+ */
+sealed class AndroidIntentEvent
+/** Generated class from Pigeon that represents data sent in messages. */
+data class AndroidIntentSendEvent (
+ val action: String,
+ val extraText: String? = null,
+ val extraStream: List? = null
+) : AndroidIntentEvent()
+ {
+ companion object {
+ fun fromList(pigeonVar_list: List): AndroidIntentSendEvent {
+ val action = pigeonVar_list[0] as String
+ val extraText = pigeonVar_list[1] as String?
+ val extraStream = pigeonVar_list[2] as List?
+ return AndroidIntentSendEvent(action, extraText, extraStream)
+ }
+ }
+ fun toList(): List {
+ return listOf(
+ action,
+ extraText,
+ extraStream,
+ )
+ }
+ override fun equals(other: Any?): Boolean {
+ if (other !is AndroidIntentSendEvent) {
+ return false
+ }
+ if (this === other) {
+ return true
+ }
+ return AndroidIntentsPigeonUtils.deepEquals(toList(), other.toList()) }
+
+ override fun hashCode(): Int = toList().hashCode()
+}
+private open class AndroidIntentsPigeonCodec : StandardMessageCodec() {
+ override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
+ return when (type) {
+ 129.toByte() -> {
+ return (readValue(buffer) as? List)?.let {
+ IntentSharedFile.fromList(it)
+ }
+ }
+ 130.toByte() -> {
+ return (readValue(buffer) as? List)?.let {
+ AndroidIntentSendEvent.fromList(it)
+ }
+ }
+ else -> super.readValueOfType(type, buffer)
+ }
+ }
+ override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
+ when (value) {
+ is IntentSharedFile -> {
+ stream.write(129)
+ writeValue(stream, value.toList())
+ }
+ is AndroidIntentSendEvent -> {
+ stream.write(130)
+ writeValue(stream, value.toList())
+ }
+ else -> super.writeValue(stream, value)
+ }
+ }
+}
+
+val AndroidIntentsPigeonMethodCodec = StandardMethodCodec(AndroidIntentsPigeonCodec())
+
+
+private class AndroidIntentsPigeonStreamHandler(
+ val wrapper: AndroidIntentsPigeonEventChannelWrapper
+) : EventChannel.StreamHandler {
+ var pigeonSink: PigeonEventSink? = null
+
+ override fun onListen(p0: Any?, sink: EventChannel.EventSink) {
+ pigeonSink = PigeonEventSink(sink)
+ wrapper.onListen(p0, pigeonSink!!)
+ }
+
+ override fun onCancel(p0: Any?) {
+ pigeonSink = null
+ wrapper.onCancel(p0)
+ }
+}
+
+interface AndroidIntentsPigeonEventChannelWrapper {
+ open fun onListen(p0: Any?, sink: PigeonEventSink) {}
+
+ open fun onCancel(p0: Any?) {}
+}
+
+class PigeonEventSink(private val sink: EventChannel.EventSink) {
+ fun success(value: T) {
+ sink.success(value)
+ }
+
+ fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
+ sink.error(errorCode, errorMessage, errorDetails)
+ }
+
+ fun endOfStream() {
+ sink.endOfStream()
+ }
+}
+
+abstract class AndroidIntentEventsStreamHandler : AndroidIntentsPigeonEventChannelWrapper {
+ companion object {
+ fun register(messenger: BinaryMessenger, streamHandler: AndroidIntentEventsStreamHandler, instanceName: String = "") {
+ var channelName: String = "dev.flutter.pigeon.zulip.AndroidIntentsEventChannelApi.androidIntentEvents"
+ if (instanceName.isNotEmpty()) {
+ channelName += ".$instanceName"
+ }
+ val internalStreamHandler = AndroidIntentsPigeonStreamHandler(streamHandler)
+ EventChannel(messenger, channelName, AndroidIntentsPigeonMethodCodec).setStreamHandler(internalStreamHandler)
+ }
+ }
+}
+
diff --git a/android/app/src/main/kotlin/com/zulip/flutter/MainActivity.kt b/android/app/src/main/kotlin/com/zulip/flutter/MainActivity.kt
index 1829456362..cad696eecf 100644
--- a/android/app/src/main/kotlin/com/zulip/flutter/MainActivity.kt
+++ b/android/app/src/main/kotlin/com/zulip/flutter/MainActivity.kt
@@ -1,6 +1,42 @@
package com.zulip.flutter
+import android.content.Intent
import io.flutter.embedding.android.FlutterActivity
+import io.flutter.embedding.engine.FlutterEngine
-class MainActivity: FlutterActivity() {
+class MainActivity : FlutterActivity() {
+ private var androidIntentEventListener: AndroidIntentEventListener? = null
+
+ override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
+ super.configureFlutterEngine(flutterEngine)
+
+ androidIntentEventListener = AndroidIntentEventListener()
+ AndroidIntentEventsStreamHandler.register(
+ flutterEngine.dartExecutor.binaryMessenger,
+ androidIntentEventListener!!
+ )
+ maybeHandleIntent(intent)
+ }
+
+ override fun onNewIntent(intent: Intent) {
+ if (maybeHandleIntent(intent)) {
+ return
+ }
+ super.onNewIntent(intent)
+ }
+
+ /** Returns true just if we did handle the intent. */
+ private fun maybeHandleIntent(intent: Intent?): Boolean {
+ intent ?: return false
+ when (intent.action) {
+ // Share-to-Zulip
+ Intent.ACTION_SEND, Intent.ACTION_SEND_MULTIPLE -> {
+ androidIntentEventListener!!.handleSend(this, intent)
+ return true
+ }
+
+ // For other intents, let Flutter handle it.
+ else -> return false
+ }
+ }
}
diff --git a/assets/l10n/app_en.arb b/assets/l10n/app_en.arb
index 77200c01eb..8d6328faef 100644
--- a/assets/l10n/app_en.arb
+++ b/assets/l10n/app_en.arb
@@ -995,6 +995,10 @@
"@channelsPageTitle": {
"description": "Title for the page with a list of subscribed channels."
},
+ "sharePageTitle": "Share",
+ "@sharePageTitle": {
+ "description": "Title for the page about sharing content received from other apps."
+ },
"channelsEmptyPlaceholder": "You are not subscribed to any channels yet.",
"@channelsEmptyPlaceholder": {
"description": "Centered text on the 'Channels' page saying that there is no content to show."
@@ -1224,6 +1228,14 @@
"@errorReactionRemovingFailedTitle": {
"description": "Error title when removing a message reaction fails"
},
+ "errorSharingTitle": "Failed to share content",
+ "@errorSharingTitle": {
+ "description": "Error title when sharing content received from other apps fails"
+ },
+ "errorSharingAccountNotLoggedIn": "There is no account logged in. Please log in to an account and try again.",
+ "@errorSharingAccountNotLoggedIn": {
+ "description": "Error title when sharing content received from other apps fails, when there is no account logged in"
+ },
"emojiReactionsMore": "more",
"@emojiReactionsMore": {
"description": "Label for a button opening the emoji picker."
diff --git a/lib/generated/l10n/zulip_localizations.dart b/lib/generated/l10n/zulip_localizations.dart
index 8165cd0701..0035b3b557 100644
--- a/lib/generated/l10n/zulip_localizations.dart
+++ b/lib/generated/l10n/zulip_localizations.dart
@@ -1487,6 +1487,12 @@ abstract class ZulipLocalizations {
/// **'Channels'**
String get channelsPageTitle;
+ /// Title for the page about sharing content received from other apps.
+ ///
+ /// In en, this message translates to:
+ /// **'Share'**
+ String get sharePageTitle;
+
/// Centered text on the 'Channels' page saying that there is no content to show.
///
/// In en, this message translates to:
@@ -1799,6 +1805,18 @@ abstract class ZulipLocalizations {
/// **'Removing reaction failed'**
String get errorReactionRemovingFailedTitle;
+ /// Error title when sharing content received from other apps fails
+ ///
+ /// In en, this message translates to:
+ /// **'Failed to share content'**
+ String get errorSharingTitle;
+
+ /// Error title when sharing content received from other apps fails, when there is no account logged in
+ ///
+ /// In en, this message translates to:
+ /// **'There is no account logged in. Please log in to an account and try again.'**
+ String get errorSharingAccountNotLoggedIn;
+
/// Label for a button opening the emoji picker.
///
/// In en, this message translates to:
diff --git a/lib/generated/l10n/zulip_localizations_ar.dart b/lib/generated/l10n/zulip_localizations_ar.dart
index 5ff9981001..968bdfa514 100644
--- a/lib/generated/l10n/zulip_localizations_ar.dart
+++ b/lib/generated/l10n/zulip_localizations_ar.dart
@@ -811,6 +811,9 @@ class ZulipLocalizationsAr extends ZulipLocalizations {
@override
String get channelsPageTitle => 'Channels';
+ @override
+ String get sharePageTitle => 'Share';
+
@override
String get channelsEmptyPlaceholder =>
'You are not subscribed to any channels yet.';
@@ -999,6 +1002,13 @@ class ZulipLocalizationsAr extends ZulipLocalizations {
@override
String get errorReactionRemovingFailedTitle => 'Removing reaction failed';
+ @override
+ String get errorSharingTitle => 'Failed to share content';
+
+ @override
+ String get errorSharingAccountNotLoggedIn =>
+ 'There is no account logged in. Please log in to an account and try again.';
+
@override
String get emojiReactionsMore => 'more';
diff --git a/lib/generated/l10n/zulip_localizations_de.dart b/lib/generated/l10n/zulip_localizations_de.dart
index 8d0826bac9..de07e7874b 100644
--- a/lib/generated/l10n/zulip_localizations_de.dart
+++ b/lib/generated/l10n/zulip_localizations_de.dart
@@ -832,6 +832,9 @@ class ZulipLocalizationsDe extends ZulipLocalizations {
@override
String get channelsPageTitle => 'Kanäle';
+ @override
+ String get sharePageTitle => 'Share';
+
@override
String get channelsEmptyPlaceholder => 'Du hast noch keine Kanäle abonniert.';
@@ -1026,6 +1029,13 @@ class ZulipLocalizationsDe extends ZulipLocalizations {
String get errorReactionRemovingFailedTitle =>
'Entfernen der Reaktion fehlgeschlagen';
+ @override
+ String get errorSharingTitle => 'Failed to share content';
+
+ @override
+ String get errorSharingAccountNotLoggedIn =>
+ 'There is no account logged in. Please log in to an account and try again.';
+
@override
String get emojiReactionsMore => 'mehr';
diff --git a/lib/generated/l10n/zulip_localizations_en.dart b/lib/generated/l10n/zulip_localizations_en.dart
index bffdb9f9a9..ec641b7797 100644
--- a/lib/generated/l10n/zulip_localizations_en.dart
+++ b/lib/generated/l10n/zulip_localizations_en.dart
@@ -811,6 +811,9 @@ class ZulipLocalizationsEn extends ZulipLocalizations {
@override
String get channelsPageTitle => 'Channels';
+ @override
+ String get sharePageTitle => 'Share';
+
@override
String get channelsEmptyPlaceholder =>
'You are not subscribed to any channels yet.';
@@ -999,6 +1002,13 @@ class ZulipLocalizationsEn extends ZulipLocalizations {
@override
String get errorReactionRemovingFailedTitle => 'Removing reaction failed';
+ @override
+ String get errorSharingTitle => 'Failed to share content';
+
+ @override
+ String get errorSharingAccountNotLoggedIn =>
+ 'There is no account logged in. Please log in to an account and try again.';
+
@override
String get emojiReactionsMore => 'more';
diff --git a/lib/generated/l10n/zulip_localizations_fr.dart b/lib/generated/l10n/zulip_localizations_fr.dart
index 5001c79eed..32ef7b72c4 100644
--- a/lib/generated/l10n/zulip_localizations_fr.dart
+++ b/lib/generated/l10n/zulip_localizations_fr.dart
@@ -811,6 +811,9 @@ class ZulipLocalizationsFr extends ZulipLocalizations {
@override
String get channelsPageTitle => 'Channels';
+ @override
+ String get sharePageTitle => 'Share';
+
@override
String get channelsEmptyPlaceholder =>
'You are not subscribed to any channels yet.';
@@ -999,6 +1002,13 @@ class ZulipLocalizationsFr extends ZulipLocalizations {
@override
String get errorReactionRemovingFailedTitle => 'Removing reaction failed';
+ @override
+ String get errorSharingTitle => 'Failed to share content';
+
+ @override
+ String get errorSharingAccountNotLoggedIn =>
+ 'There is no account logged in. Please log in to an account and try again.';
+
@override
String get emojiReactionsMore => 'more';
diff --git a/lib/generated/l10n/zulip_localizations_it.dart b/lib/generated/l10n/zulip_localizations_it.dart
index 25a5c4e999..8149552491 100644
--- a/lib/generated/l10n/zulip_localizations_it.dart
+++ b/lib/generated/l10n/zulip_localizations_it.dart
@@ -826,6 +826,9 @@ class ZulipLocalizationsIt extends ZulipLocalizations {
@override
String get channelsPageTitle => 'Canali';
+ @override
+ String get sharePageTitle => 'Share';
+
@override
String get channelsEmptyPlaceholder =>
'Non sei ancora iscritto ad alcun canale.';
@@ -1021,6 +1024,13 @@ class ZulipLocalizationsIt extends ZulipLocalizations {
String get errorReactionRemovingFailedTitle =>
'Rimozione della reazione non riuscita';
+ @override
+ String get errorSharingTitle => 'Failed to share content';
+
+ @override
+ String get errorSharingAccountNotLoggedIn =>
+ 'There is no account logged in. Please log in to an account and try again.';
+
@override
String get emojiReactionsMore => 'altro';
diff --git a/lib/generated/l10n/zulip_localizations_ja.dart b/lib/generated/l10n/zulip_localizations_ja.dart
index e39ebe4377..ca2eee8cb3 100644
--- a/lib/generated/l10n/zulip_localizations_ja.dart
+++ b/lib/generated/l10n/zulip_localizations_ja.dart
@@ -808,6 +808,9 @@ class ZulipLocalizationsJa extends ZulipLocalizations {
@override
String get channelsPageTitle => 'Channels';
+ @override
+ String get sharePageTitle => 'Share';
+
@override
String get channelsEmptyPlaceholder =>
'You are not subscribed to any channels yet.';
@@ -996,6 +999,13 @@ class ZulipLocalizationsJa extends ZulipLocalizations {
@override
String get errorReactionRemovingFailedTitle => 'Removing reaction failed';
+ @override
+ String get errorSharingTitle => 'Failed to share content';
+
+ @override
+ String get errorSharingAccountNotLoggedIn =>
+ 'There is no account logged in. Please log in to an account and try again.';
+
@override
String get emojiReactionsMore => 'more';
diff --git a/lib/generated/l10n/zulip_localizations_nb.dart b/lib/generated/l10n/zulip_localizations_nb.dart
index d08ca0eaf0..52e6e335f2 100644
--- a/lib/generated/l10n/zulip_localizations_nb.dart
+++ b/lib/generated/l10n/zulip_localizations_nb.dart
@@ -811,6 +811,9 @@ class ZulipLocalizationsNb extends ZulipLocalizations {
@override
String get channelsPageTitle => 'Channels';
+ @override
+ String get sharePageTitle => 'Share';
+
@override
String get channelsEmptyPlaceholder =>
'You are not subscribed to any channels yet.';
@@ -999,6 +1002,13 @@ class ZulipLocalizationsNb extends ZulipLocalizations {
@override
String get errorReactionRemovingFailedTitle => 'Removing reaction failed';
+ @override
+ String get errorSharingTitle => 'Failed to share content';
+
+ @override
+ String get errorSharingAccountNotLoggedIn =>
+ 'There is no account logged in. Please log in to an account and try again.';
+
@override
String get emojiReactionsMore => 'more';
diff --git a/lib/generated/l10n/zulip_localizations_pl.dart b/lib/generated/l10n/zulip_localizations_pl.dart
index 9b856a6aae..bd04f2db82 100644
--- a/lib/generated/l10n/zulip_localizations_pl.dart
+++ b/lib/generated/l10n/zulip_localizations_pl.dart
@@ -822,6 +822,9 @@ class ZulipLocalizationsPl extends ZulipLocalizations {
@override
String get channelsPageTitle => 'Kanały';
+ @override
+ String get sharePageTitle => 'Share';
+
@override
String get channelsEmptyPlaceholder => 'Nie śledzisz żadnego z kanałów.';
@@ -1012,6 +1015,13 @@ class ZulipLocalizationsPl extends ZulipLocalizations {
String get errorReactionRemovingFailedTitle =>
'Usuwanie reakcji bez powodzenia';
+ @override
+ String get errorSharingTitle => 'Failed to share content';
+
+ @override
+ String get errorSharingAccountNotLoggedIn =>
+ 'There is no account logged in. Please log in to an account and try again.';
+
@override
String get emojiReactionsMore => 'więcej';
diff --git a/lib/generated/l10n/zulip_localizations_ru.dart b/lib/generated/l10n/zulip_localizations_ru.dart
index 848b02eefb..c7506d8ce9 100644
--- a/lib/generated/l10n/zulip_localizations_ru.dart
+++ b/lib/generated/l10n/zulip_localizations_ru.dart
@@ -825,6 +825,9 @@ class ZulipLocalizationsRu extends ZulipLocalizations {
@override
String get channelsPageTitle => 'Каналы';
+ @override
+ String get sharePageTitle => 'Share';
+
@override
String get channelsEmptyPlaceholder =>
'Вы еще не подписаны ни на один канал.';
@@ -1016,6 +1019,13 @@ class ZulipLocalizationsRu extends ZulipLocalizations {
@override
String get errorReactionRemovingFailedTitle => 'Не удалось удалить реакцию';
+ @override
+ String get errorSharingTitle => 'Failed to share content';
+
+ @override
+ String get errorSharingAccountNotLoggedIn =>
+ 'There is no account logged in. Please log in to an account and try again.';
+
@override
String get emojiReactionsMore => 'еще';
diff --git a/lib/generated/l10n/zulip_localizations_sk.dart b/lib/generated/l10n/zulip_localizations_sk.dart
index 96e9e0c542..82bbfb077a 100644
--- a/lib/generated/l10n/zulip_localizations_sk.dart
+++ b/lib/generated/l10n/zulip_localizations_sk.dart
@@ -813,6 +813,9 @@ class ZulipLocalizationsSk extends ZulipLocalizations {
@override
String get channelsPageTitle => 'Kanály';
+ @override
+ String get sharePageTitle => 'Share';
+
@override
String get channelsEmptyPlaceholder =>
'You are not subscribed to any channels yet.';
@@ -1001,6 +1004,13 @@ class ZulipLocalizationsSk extends ZulipLocalizations {
@override
String get errorReactionRemovingFailedTitle => 'Odobranie reakcie zlyhalo';
+ @override
+ String get errorSharingTitle => 'Failed to share content';
+
+ @override
+ String get errorSharingAccountNotLoggedIn =>
+ 'There is no account logged in. Please log in to an account and try again.';
+
@override
String get emojiReactionsMore => 'viac';
diff --git a/lib/generated/l10n/zulip_localizations_sl.dart b/lib/generated/l10n/zulip_localizations_sl.dart
index bdb56cf44d..e9cf3035f4 100644
--- a/lib/generated/l10n/zulip_localizations_sl.dart
+++ b/lib/generated/l10n/zulip_localizations_sl.dart
@@ -837,6 +837,9 @@ class ZulipLocalizationsSl extends ZulipLocalizations {
@override
String get channelsPageTitle => 'Kanali';
+ @override
+ String get sharePageTitle => 'Share';
+
@override
String get channelsEmptyPlaceholder => 'Niste še naročeni na noben kanal.';
@@ -1028,6 +1031,13 @@ class ZulipLocalizationsSl extends ZulipLocalizations {
String get errorReactionRemovingFailedTitle =>
'Reakcije ni bilo mogoče odstraniti';
+ @override
+ String get errorSharingTitle => 'Failed to share content';
+
+ @override
+ String get errorSharingAccountNotLoggedIn =>
+ 'There is no account logged in. Please log in to an account and try again.';
+
@override
String get emojiReactionsMore => 'več';
diff --git a/lib/generated/l10n/zulip_localizations_uk.dart b/lib/generated/l10n/zulip_localizations_uk.dart
index 0c2f49363e..9ff5bbd8cf 100644
--- a/lib/generated/l10n/zulip_localizations_uk.dart
+++ b/lib/generated/l10n/zulip_localizations_uk.dart
@@ -825,6 +825,9 @@ class ZulipLocalizationsUk extends ZulipLocalizations {
@override
String get channelsPageTitle => 'Канали';
+ @override
+ String get sharePageTitle => 'Share';
+
@override
String get channelsEmptyPlaceholder => 'Ви ще не підписані на жодний канал.';
@@ -1016,6 +1019,13 @@ class ZulipLocalizationsUk extends ZulipLocalizations {
@override
String get errorReactionRemovingFailedTitle => 'Не вдалося видалити реакцію';
+ @override
+ String get errorSharingTitle => 'Failed to share content';
+
+ @override
+ String get errorSharingAccountNotLoggedIn =>
+ 'There is no account logged in. Please log in to an account and try again.';
+
@override
String get emojiReactionsMore => 'більше';
diff --git a/lib/generated/l10n/zulip_localizations_zh.dart b/lib/generated/l10n/zulip_localizations_zh.dart
index fdfd2966b5..a19ca6ebfb 100644
--- a/lib/generated/l10n/zulip_localizations_zh.dart
+++ b/lib/generated/l10n/zulip_localizations_zh.dart
@@ -811,6 +811,9 @@ class ZulipLocalizationsZh extends ZulipLocalizations {
@override
String get channelsPageTitle => 'Channels';
+ @override
+ String get sharePageTitle => 'Share';
+
@override
String get channelsEmptyPlaceholder =>
'You are not subscribed to any channels yet.';
@@ -999,6 +1002,13 @@ class ZulipLocalizationsZh extends ZulipLocalizations {
@override
String get errorReactionRemovingFailedTitle => 'Removing reaction failed';
+ @override
+ String get errorSharingTitle => 'Failed to share content';
+
+ @override
+ String get errorSharingAccountNotLoggedIn =>
+ 'There is no account logged in. Please log in to an account and try again.';
+
@override
String get emojiReactionsMore => 'more';
diff --git a/lib/host/android_intents.dart b/lib/host/android_intents.dart
new file mode 100644
index 0000000000..6bd1e60de5
--- /dev/null
+++ b/lib/host/android_intents.dart
@@ -0,0 +1 @@
+export 'android_intents.g.dart';
diff --git a/lib/host/android_intents.g.dart b/lib/host/android_intents.g.dart
new file mode 100644
index 0000000000..1e0b6e5e1a
--- /dev/null
+++ b/lib/host/android_intents.g.dart
@@ -0,0 +1,174 @@
+// Autogenerated from Pigeon (v25.5.0), do not edit directly.
+// See also: https://pub.dev/packages/pigeon
+// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers
+
+import 'dart:async';
+import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;
+
+import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
+import 'package:flutter/services.dart';
+bool _deepEquals(Object? a, Object? b) {
+ if (a is List && b is List) {
+ return a.length == b.length &&
+ a.indexed
+ .every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]));
+ }
+ if (a is Map && b is Map) {
+ return a.length == b.length && a.entries.every((MapEntry