Skip to content

Commit 080487b

Browse files
committed
compose: Support images from keyboard for Android
Fixes: #419 Fixes: #1173 Signed-off-by: Zixuan James Li <[email protected]>
1 parent 2e675c4 commit 080487b

11 files changed

+200
-0
lines changed

assets/l10n/app_en.arb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,14 @@
458458
"@topicValidationErrorMandatoryButEmpty": {
459459
"description": "Topic validation error when topic is required but was empty."
460460
},
461+
"errorContentNotInsertedTitle": "Content not inserted",
462+
"@errorContentNotInsertedTitle": {
463+
"description": "Title for error dialog when an attempt to insert rich content failed."
464+
},
465+
"errorContentToInsertIsEmpty": "The file to be inserted is empty or cannot be accessed.",
466+
"@errorContentToInsertIsEmpty": {
467+
"description": "Error message when the rich content to be inserted is empty or cannot be accessed."
468+
},
461469
"errorInvalidResponse": "The server sent an invalid response",
462470
"@errorInvalidResponse": {
463471
"description": "Error message when an API call returned an invalid response."

lib/generated/l10n/zulip_localizations.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,18 @@ abstract class ZulipLocalizations {
723723
/// **'Topics are required in this organization.'**
724724
String get topicValidationErrorMandatoryButEmpty;
725725

726+
/// Title for error dialog when an attempt to insert rich content failed.
727+
///
728+
/// In en, this message translates to:
729+
/// **'Content not inserted'**
730+
String get errorContentNotInsertedTitle;
731+
732+
/// Error message when the rich content to be inserted is empty or cannot be accessed.
733+
///
734+
/// In en, this message translates to:
735+
/// **'The file to be inserted is empty or cannot be accessed.'**
736+
String get errorContentToInsertIsEmpty;
737+
726738
/// Error message when an API call returned an invalid response.
727739
///
728740
/// In en, this message translates to:

lib/generated/l10n/zulip_localizations_ar.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,12 @@ class ZulipLocalizationsAr extends ZulipLocalizations {
357357
@override
358358
String get topicValidationErrorMandatoryButEmpty => 'Topics are required in this organization.';
359359

360+
@override
361+
String get errorContentNotInsertedTitle => 'Content not inserted';
362+
363+
@override
364+
String get errorContentToInsertIsEmpty => 'The file to be inserted is empty or cannot be accessed.';
365+
360366
@override
361367
String get errorInvalidResponse => 'The server sent an invalid response';
362368

lib/generated/l10n/zulip_localizations_en.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,12 @@ class ZulipLocalizationsEn extends ZulipLocalizations {
357357
@override
358358
String get topicValidationErrorMandatoryButEmpty => 'Topics are required in this organization.';
359359

360+
@override
361+
String get errorContentNotInsertedTitle => 'Content not inserted';
362+
363+
@override
364+
String get errorContentToInsertIsEmpty => 'The file to be inserted is empty or cannot be accessed.';
365+
360366
@override
361367
String get errorInvalidResponse => 'The server sent an invalid response';
362368

lib/generated/l10n/zulip_localizations_ja.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,12 @@ class ZulipLocalizationsJa extends ZulipLocalizations {
357357
@override
358358
String get topicValidationErrorMandatoryButEmpty => 'Topics are required in this organization.';
359359

360+
@override
361+
String get errorContentNotInsertedTitle => 'Content not inserted';
362+
363+
@override
364+
String get errorContentToInsertIsEmpty => 'The file to be inserted is empty or cannot be accessed.';
365+
360366
@override
361367
String get errorInvalidResponse => 'The server sent an invalid response';
362368

lib/generated/l10n/zulip_localizations_nb.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,12 @@ class ZulipLocalizationsNb extends ZulipLocalizations {
357357
@override
358358
String get topicValidationErrorMandatoryButEmpty => 'Topics are required in this organization.';
359359

360+
@override
361+
String get errorContentNotInsertedTitle => 'Content not inserted';
362+
363+
@override
364+
String get errorContentToInsertIsEmpty => 'The file to be inserted is empty or cannot be accessed.';
365+
360366
@override
361367
String get errorInvalidResponse => 'The server sent an invalid response';
362368

lib/generated/l10n/zulip_localizations_pl.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,12 @@ class ZulipLocalizationsPl extends ZulipLocalizations {
357357
@override
358358
String get topicValidationErrorMandatoryButEmpty => 'Wątki są wymagane przez tę organizację.';
359359

360+
@override
361+
String get errorContentNotInsertedTitle => 'Content not inserted';
362+
363+
@override
364+
String get errorContentToInsertIsEmpty => 'The file to be inserted is empty or cannot be accessed.';
365+
360366
@override
361367
String get errorInvalidResponse => 'Nieprawidłowa odpowiedź serwera';
362368

lib/generated/l10n/zulip_localizations_ru.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,12 @@ class ZulipLocalizationsRu extends ZulipLocalizations {
357357
@override
358358
String get topicValidationErrorMandatoryButEmpty => 'Темы обязательны в этой организации.';
359359

360+
@override
361+
String get errorContentNotInsertedTitle => 'Content not inserted';
362+
363+
@override
364+
String get errorContentToInsertIsEmpty => 'The file to be inserted is empty or cannot be accessed.';
365+
360366
@override
361367
String get errorInvalidResponse => 'Получен недопустимый ответ сервера';
362368

lib/generated/l10n/zulip_localizations_sk.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,12 @@ class ZulipLocalizationsSk extends ZulipLocalizations {
357357
@override
358358
String get topicValidationErrorMandatoryButEmpty => 'Topics are required in this organization.';
359359

360+
@override
361+
String get errorContentNotInsertedTitle => 'Content not inserted';
362+
363+
@override
364+
String get errorContentToInsertIsEmpty => 'The file to be inserted is empty or cannot be accessed.';
365+
360366
@override
361367
String get errorInvalidResponse => 'Server poslal nesprávnu odpoveď';
362368

lib/widgets/compose_box.dart

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:app_settings/app_settings.dart';
44
import 'package:flutter/material.dart';
55
import 'package:flutter/services.dart';
66
import 'package:mime/mime.dart';
7+
import 'package:path/path.dart' as path;
78

89
import '../api/exception.dart';
910
import '../api/model/model.dart';
@@ -366,6 +367,40 @@ class _ContentInputState extends State<_ContentInput> with WidgetsBindingObserve
366367
}
367368
}
368369

370+
void _handleContentInserted(KeyboardInsertedContent content) async {
371+
if (content.data == null || content.data!.isEmpty) {
372+
// As of writing, the engine implementation never leaves `content.data` as
373+
// `null`, but ideally it should be when the data cannot be read for
374+
// errors.
375+
//
376+
// When `content.data` is empty, the data is not literally empty — this
377+
// can also happen when the data can't be read from the input stream
378+
// provided by the Android SDK because of an IO exception.
379+
//
380+
// See Flutter engine implementation that prepares this data:
381+
// https://github.com/flutter/flutter/blob/0ffc4ce00ea7bb912e379adf39354644eab2c17e/engine/src/flutter/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java#L497-L548
382+
// TODO(upstream): improve the API for this
383+
final zulipLocalizations = ZulipLocalizations.of(context);
384+
showErrorDialog(
385+
context: context,
386+
title: zulipLocalizations.errorContentNotInsertedTitle,
387+
message: zulipLocalizations.errorContentToInsertIsEmpty);
388+
return;
389+
}
390+
391+
final file = _File(
392+
content: Stream.fromIterable([content.data!]),
393+
length: content.data!.length,
394+
filename: path.basename(content.uri),
395+
mimeType: content.mimeType);
396+
397+
await _uploadFiles(
398+
context: context,
399+
contentController: widget.controller.content,
400+
contentFocusNode: widget.controller.contentFocusNode,
401+
files: [file]);
402+
}
403+
369404
static double maxHeight(BuildContext context) {
370405
final clampingTextScaler = MediaQuery.textScalerOf(context)
371406
.clamp(maxScaleFactor: 1.5);
@@ -409,6 +444,8 @@ class _ContentInputState extends State<_ContentInput> with WidgetsBindingObserve
409444
child: TextField(
410445
controller: widget.controller.content,
411446
focusNode: widget.controller.contentFocusNode,
447+
contentInsertionConfiguration: ContentInsertionConfiguration(
448+
onContentInserted: _handleContentInserted),
412449
// Let the content show through the `contentPadding` so that
413450
// our [InsetShadowBox] can fade it smoothly there.
414451
clipBehavior: Clip.none,

0 commit comments

Comments
 (0)