Merge pull request #518 from GetStream/feat/localization

feat(ui, repo): stream_chat_localizations [CDS-318]
This commit is contained in:
Salvatore Giordano
2021-07-28 12:24:51 +02:00
committed by GitHub
120 changed files with 5875 additions and 247 deletions
+1
View File
@@ -21,6 +21,7 @@ jobs:
ui
doc
repo
localization
requireScope: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+3
View File
@@ -48,6 +48,9 @@ This package provides business logic to fetch common things required for integra
### [stream_chat_flutter](https://github.com/GetStream/stream-chat-flutter/tree/master/packages/stream_chat_flutter)
This library includes both a low-level chat SDK and a set of reusable and customizable UI components.
### [stream_chat_localizations](https://github.com/GetStream/stream-chat-flutter/tree/master/packages/stream_chat_localizations)
This library includes a set of localization files for the Flutter UI components.
## Flutter Chat Tutorial
The best place to start is the [Flutter Chat Tutorial](https://getstream.io/chat/flutter/tutorial/).
+4 -4
View File
@@ -13,7 +13,7 @@ scripts:
analyze:
run: |
melos exec -c 4 --ignore="*example*" -- \
melos exec -c 5 --ignore="*example*" -- \
dart analyze --fatal-infos .
description: |
Run `dart analyze` in all packages.
@@ -26,7 +26,7 @@ scripts:
lint:pub:
run: |
melos exec -c 4 --no-private --ignore="*example*" -- \
melos exec -c 5 --no-private --ignore="*example*" -- \
pub publish --dry-run
description: |
Run `pub publish --dry-run` in all packages.
@@ -56,7 +56,7 @@ scripts:
dir-exists: test
test:flutter:
run: melos exec -c 3 --fail-fast -- "flutter test --coverage"
run: melos exec -c 4 --fail-fast -- "flutter test --coverage"
description: Run Flutter tests for a specific package in this project.
select-package:
flutter: true
@@ -64,7 +64,7 @@ scripts:
coverage:ignore-file:
run: |
melos exec -c 4 --fail-fast -- "\$MELOS_ROOT_PATH/.github/workflows/scripts/remove-from-coverage.sh"
melos exec -c 5 --fail-fast -- "\$MELOS_ROOT_PATH/.github/workflows/scripts/remove-from-coverage.sh"
description: Removes all the ignored files from the coverage report.
select-package:
dir-exists: coverage
-1
View File
@@ -15,7 +15,6 @@
- [Flutter Chat Tutorial](https://getstream.io/chat/flutter/tutorial/)
- [Chat UI Kit](https://getstream.io/chat/ui-kit/)
### Changelog
Check out the [changelog on pub.dev](https://pub.dev/packages/stream_chat_flutter/changelog) to see the latest changes in the package.
@@ -1,11 +1,8 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_persistence/stream_chat_persistence.dart';
/// A chat-persisted StreamChatClient
final chatPersistentClient = StreamChatPersistenceClient(
logLevel: Level.INFO,
);
import 'package:stream_chat_localizations/stream_chat_localizations.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
@@ -15,7 +12,7 @@ void main() async {
final client = StreamChatClient(
's2dxdhpxd94g',
logLevel: Level.INFO,
)..chatPersistenceClient = chatPersistentClient;
);
/// Set the current user and connect the websocket. In a production
/// scenario, this should be done using a backend to generate a user token
@@ -72,6 +69,13 @@ class MyApp extends StatelessWidget {
Widget build(BuildContext context) => MaterialApp(
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
supportedLocales: const [
Locale('en'),
Locale('hi'),
Locale('fr'),
Locale('it'),
],
localizationsDelegates: GlobalStreamChatLocalizations.delegates,
builder: (context, widget) => StreamChat(
client: client,
child: widget,
@@ -33,6 +33,8 @@ dependencies:
# path: ../../stream_chat_flutter_core
stream_chat_flutter:
path: ../
stream_chat_localizations:
path: ../../stream_chat_localizations
stream_chat_persistence:
path: ../../stream_chat_persistence
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/upload_progress_indicator.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Widget to build in progress
typedef InProgressBuilder = Widget Function(BuildContext, int, int);
@@ -226,7 +227,7 @@ class _FailedState extends StatelessWidget {
horizontal: 12,
),
child: Text(
'UPLOAD ERROR',
context.translations.uploadErrorLabel,
style: theme.textTheme.footnote.copyWith(
color: theme.colorTheme.barsBg,
),
@@ -7,9 +7,9 @@ import 'package:stream_chat_flutter/src/upload_progress_indicator.dart';
import 'package:stream_chat_flutter/src/utils.dart';
import 'package:stream_chat_flutter/src/video_thumbnail_image.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
// ignore: always_use_package_imports
import 'attachment_widget.dart';
import 'package:stream_chat_flutter/src/attachment/attachment_widget.dart';
/// Widget for displaying file attachments
class FileAttachment extends AttachmentWidget {
@@ -76,7 +76,7 @@ class FileAttachment extends AttachmentWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
attachment.title ?? 'File',
attachment.title ?? context.translations.fileText,
style: StreamChatTheme.of(context).textTheme.bodyBold,
maxLines: 1,
overflow: TextOverflow.ellipsis,
@@ -286,7 +286,10 @@ class FileAttachment extends AttachmentWidget {
progressIndicatorColor: theme.colorTheme.accentPrimary,
),
success: () => Text(fileSize(size), style: textStyle),
failed: (_) => Text('UPLOAD ERROR', style: textStyle),
failed: (_) => Text(
context.translations.uploadErrorLabel,
style: textStyle,
),
);
}
}
@@ -5,6 +5,7 @@ import 'package:stream_chat_flutter/src/attachment/attachment_widget.dart';
import 'package:stream_chat_flutter/src/visible_footnote.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Widget for showing a GIF attachment
class GiphyAttachment extends AttachmentWidget {
@@ -72,9 +73,9 @@ class GiphyAttachment extends AttachmentWidget {
children: [
StreamSvgIcon.giphyIcon(),
const SizedBox(width: 8),
const Text(
'Giphy',
style: TextStyle(fontWeight: FontWeight.bold),
Text(
context.translations.giphyLabel,
style: const TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(width: 8),
if (attachment.title != null)
@@ -135,7 +136,7 @@ class GiphyAttachment extends AttachmentWidget {
});
},
child: Text(
'Cancel',
context.translations.cancelLabel.toLowerCase(),
style: StreamChatTheme.of(context)
.textTheme
.bodyBold
@@ -167,7 +168,7 @@ class GiphyAttachment extends AttachmentWidget {
});
},
child: Text(
'Shuffle',
context.translations.shuffleLabel,
style: StreamChatTheme.of(context)
.textTheme
.bodyBold
@@ -200,7 +201,7 @@ class GiphyAttachment extends AttachmentWidget {
});
},
child: Text(
'Send',
context.translations.sendLabel,
style: TextStyle(
color: StreamChatTheme.of(context)
.colorTheme
@@ -315,7 +316,7 @@ class GiphyAttachment extends AttachmentWidget {
size: 16,
),
Text(
'GIPHY',
context.translations.giphyLabel.toUpperCase(),
style: TextStyle(
color:
StreamChatTheme.of(context).colorTheme.barsBg,
@@ -48,7 +48,7 @@ class AttachmentActionsModal extends StatelessWidget {
child: _buildPage(context),
);
Widget _buildPage(context) {
Widget _buildPage(BuildContext context) {
final theme = StreamChatTheme.of(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.end,
@@ -69,7 +69,7 @@ class AttachmentActionsModal extends StatelessWidget {
children: [
_buildButton(
context,
'Reply',
context.translations.replyLabel,
StreamSvgIcon.iconCurveLineLeftUp(
size: 24,
color: theme.colorTheme.textLowEmphasis,
@@ -80,7 +80,7 @@ class AttachmentActionsModal extends StatelessWidget {
),
_buildButton(
context,
'Show in Chat',
context.translations.showInChatLabel,
StreamSvgIcon.eye(
size: 24,
color: theme.colorTheme.textHighEmphasis,
@@ -89,8 +89,9 @@ class AttachmentActionsModal extends StatelessWidget {
),
_buildButton(
context,
// ignore: lines_longer_than_80_chars
'Save ${message.attachments[currentIndex].type == 'video' ? 'Video' : 'Image'}',
message.attachments[currentIndex].type == 'video'
? context.translations.saveVideoLabel
: context.translations.saveImageLabel,
StreamSvgIcon.iconSave(
size: 24,
color: theme.colorTheme.textLowEmphasis,
@@ -142,7 +143,7 @@ class AttachmentActionsModal extends StatelessWidget {
message.user?.id)
_buildButton(
context,
'Delete',
context.translations.deleteLabel.capitalize(),
StreamSvgIcon.delete(
size: 24,
color: theme.colorTheme.accentError,
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/channel_info.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Bottom Sheet with options
class ChannelBottomSheet extends StatefulWidget {
@@ -149,7 +150,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
color: _streamChatThemeData.colorTheme.textLowEmphasis,
),
),
title: 'View Info',
title: context.translations.viewInfoLabel,
onTap: widget.onViewInfoTap,
),
if (!channel.isDistinct)
@@ -160,7 +161,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
color: _streamChatThemeData.colorTheme.textLowEmphasis,
),
),
title: 'Leave Group',
title: context.translations.leaveGroupLabel,
onTap: () async {
setState(() {
_showActions = false;
@@ -179,7 +180,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
color: _streamChatThemeData.colorTheme.accentError,
),
),
title: 'Delete Conversation',
title: context.translations.deleteConversationLabel,
titleColor: _streamChatThemeData.colorTheme.accentError,
onTap: () async {
setState(() {
@@ -198,7 +199,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
color: _streamChatThemeData.colorTheme.textLowEmphasis,
),
),
title: 'Cancel',
title: context.translations.cancelLabel,
onTap: () {
Navigator.pop(context);
},
@@ -219,10 +220,10 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
Future<void> _showDeleteDialog() async {
final res = await showConfirmationDialog(
context,
title: 'Delete Conversation',
okText: 'DELETE',
question: 'Are you sure you want to delete this conversation?',
cancelText: 'CANCEL',
title: context.translations.deleteConversationLabel,
okText: context.translations.deleteLabel,
question: context.translations.deleteConversationQuestion,
cancelText: context.translations.cancelLabel,
icon: StreamSvgIcon.delete(
color: _streamChatThemeData.colorTheme.accentError,
),
@@ -237,10 +238,10 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
Future<void> _showLeaveDialog() async {
final res = await showConfirmationDialog(
context,
title: 'Leave conversation',
okText: 'LEAVE',
question: 'Are you sure you want to leave this conversation?',
cancelText: 'CANCEL',
title: context.translations.leaveConversationLabel,
okText: context.translations.leaveLabel,
question: context.translations.leaveConversationQuestion,
cancelText: context.translations.cancelLabel,
icon: StreamSvgIcon.userRemove(
color: _streamChatThemeData.colorTheme.accentError,
),
@@ -6,6 +6,7 @@ import 'package:stream_chat_flutter/src/info_tile.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_header.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_header_paint.png)
@@ -121,14 +122,14 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
switch (status) {
case ConnectionStatus.connected:
statusString = 'Connected';
statusString = context.translations.connectedLabel;
showStatus = false;
break;
case ConnectionStatus.connecting:
statusString = 'Reconnecting...';
statusString = context.translations.reconnectingLabel;
break;
case ConnectionStatus.disconnected:
statusString = 'Disconnected';
statusString = context.translations.disconnectedLabel;
break;
}
@@ -2,6 +2,7 @@ import 'package:collection/collection.dart' show IterableExtension;
import 'package:flutter/material.dart';
import 'package:jiffy/jiffy.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Widget which shows channel info
class ChannelInfo extends StatelessWidget {
@@ -55,10 +56,13 @@ class ChannelInfo extends StatelessWidget {
) {
Widget? alternativeWidget;
if (channel.memberCount != null && channel.memberCount! > 2) {
var text = '${channel.memberCount} Members';
final memberCount = channel.memberCount;
if (memberCount != null && memberCount > 2) {
var text = context.translations.membersCountText(memberCount);
final watcherCount = channel.state?.watcherCount ?? 0;
if (watcherCount > 0) text += ' $watcherCount Online';
if (watcherCount > 0) {
text += ' ${context.translations.watchersCountText(watcherCount)}';
}
alternativeWidget = Text(
text,
style: StreamChatTheme.of(context)
@@ -75,12 +79,13 @@ class ChannelInfo extends StatelessWidget {
if (otherMember != null) {
if (otherMember.user?.online == true) {
alternativeWidget = Text(
'Online',
context.translations.userOnlineText,
style: textStyle,
);
} else {
alternativeWidget = Text(
'Last seen ${Jiffy(otherMember.user?.lastActive).fromNow()}',
'${context.translations.userLastOnlineText} '
'${Jiffy(otherMember.user?.lastActive).fromNow()}',
style: textStyle,
);
}
@@ -111,7 +116,7 @@ class ChannelInfo extends StatelessWidget {
),
const SizedBox(width: 10),
Text(
'Searching for Network',
context.translations.searchingForNetworkText,
style: textStyle,
),
],
@@ -125,7 +130,7 @@ class ChannelInfo extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Offline...',
context.translations.offlineLabel,
style: textStyle,
),
TextButton(
@@ -141,7 +146,7 @@ class ChannelInfo extends StatelessWidget {
..closeConnection()
..openConnection(),
child: Text(
'Try Again',
context.translations.tryAgainLabel,
style: textStyle?.copyWith(
color: StreamChatTheme.of(context).colorTheme.accentPrimary,
),
@@ -5,6 +5,7 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:stream_chat_flutter/src/stream_neumorphic_button.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Widget builder for title
typedef TitleBuilder = Widget Function(
@@ -103,21 +104,20 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
switch (status) {
case ConnectionStatus.connected:
statusString = 'Connected';
statusString = context.translations.connectedLabel;
showStatus = false;
break;
case ConnectionStatus.connecting:
statusString = 'Reconnecting...';
statusString = context.translations.reconnectingLabel;
break;
case ConnectionStatus.disconnected:
statusString = 'Disconnected';
statusString = context.translations.disconnectedLabel;
break;
}
final chatThemeData = StreamChatTheme.of(context);
return InfoTile(
// ignore: avoid_bool_literals_in_conditional_expressions
showMessage: showConnectionStateTile ? showStatus : false,
showMessage: showConnectionStateTile && showStatus,
message: statusString,
child: AppBar(
textTheme: Theme.of(context).textTheme,
@@ -207,7 +207,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
Widget _buildConnectedTitleState(BuildContext context) {
final chatThemeData = StreamChatTheme.of(context);
return Text(
'Stream Chat',
context.translations.streamChatLabel,
style: chatThemeData.textTheme.headlineBold.copyWith(
color: chatThemeData.colorTheme.textHighEmphasis,
),
@@ -226,7 +226,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
),
const SizedBox(width: 10),
Text(
'Searching for Network',
context.translations.searchingForNetworkText,
style: StreamChatTheme.of(context)
.channelListHeaderTheme
.title
@@ -247,7 +247,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Offline...',
context.translations.offlineLabel,
style: chatThemeData.channelListHeaderTheme.title?.copyWith(
fontSize: 16,
fontWeight: FontWeight.bold,
@@ -258,7 +258,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
..closeConnection()
..openConnection(),
child: Text(
'Try Again',
context.translations.tryAgainLabel,
style: chatThemeData.channelListHeaderTheme.title?.copyWith(
fontSize: 16,
fontWeight: FontWeight.bold,
@@ -7,6 +7,7 @@ import 'package:stream_chat_flutter/src/channel_bottom_sheet.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Callback called when tapping on a channel
typedef ChannelTapCallback = void Function(Channel, Widget?);
@@ -301,7 +302,7 @@ class _ChannelListViewState extends State<ChannelListView> {
Padding(
padding: const EdgeInsets.all(8),
child: Text(
'Lets start chatting!',
context.translations.letsStartChattingLabel,
style: chatThemeData.textTheme.headline,
),
),
@@ -311,7 +312,7 @@ class _ChannelListViewState extends State<ChannelListView> {
horizontal: 52,
),
child: Text(
'How about sending your first message to a friend?',
context.translations.sendingFirstMessageLabel,
textAlign: TextAlign.center,
style: chatThemeData.textTheme.body.copyWith(
color: chatThemeData.colorTheme.textLowEmphasis,
@@ -330,7 +331,7 @@ class _ChannelListViewState extends State<ChannelListView> {
child: TextButton(
onPressed: widget.onStartChatPressed,
child: Text(
'Start a chat',
context.translations.startAChatLabel,
style: chatThemeData.textTheme.bodyBold.copyWith(
color: chatThemeData.colorTheme.accentPrimary,
),
@@ -466,9 +467,9 @@ class _ChannelListViewState extends State<ChannelListView> {
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text.rich(
const TextSpan(
TextSpan(
children: [
WidgetSpan(
const WidgetSpan(
child: Padding(
padding: EdgeInsets.only(
right: 2,
@@ -476,14 +477,14 @@ class _ChannelListViewState extends State<ChannelListView> {
child: Icon(Icons.error_outline),
),
),
TextSpan(text: 'Error loading channels'),
TextSpan(text: context.translations.loadingChannelsError),
],
),
style: Theme.of(context).textTheme.headline6,
),
TextButton(
onPressed: () => _channelListController.loadData!(),
child: const Text('Retry'),
child: Text(context.translations.retryLabel),
),
],
),
@@ -561,17 +562,16 @@ class _ChannelListViewState extends State<ChannelListView> {
),
onTap: widget.onDeletePressed != null
? () {
widget.onDeletePressed!(channel);
widget.onDeletePressed?.call(channel);
}
: () async {
final res = await showConfirmationDialog(
context,
title: 'Delete Conversation',
okText: 'DELETE',
title: context.translations.deleteConversationLabel,
question:
// ignore: lines_longer_than_80_chars
'Are you sure you want to delete this conversation?',
cancelText: 'CANCEL',
context.translations.deleteConversationQuestion,
okText: context.translations.deleteLabel,
cancelText: context.translations.cancelLabel,
icon: StreamSvgIcon.delete(
color: chatThemeData.colorTheme.accentError,
),
@@ -668,7 +668,7 @@ class _ChannelListViewState extends State<ChannelListView> {
child: Padding(
padding: const EdgeInsets.all(16),
child: Text(
'Error loading channels',
context.translations.loadingChannelsError,
style: theme.textTheme.body.copyWith(
color: Colors.white,
),
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// It shows the current [Channel] name using a [Text] widget.
///
@@ -44,8 +45,10 @@ class ChannelName extends StatelessWidget {
) =>
LayoutBuilder(
builder: (context, constraints) {
var title = 'No title';
if (extraData['name'] == null) {
var title = context.translations.noTitleText;
if (extraData['name'] != null) {
title = extraData['name'];
} else {
final otherMembers = members
?.where((member) => member.userId != client.currentUser!.id);
if (otherMembers?.length == 1) {
@@ -71,8 +74,6 @@ class ChannelName extends StatelessWidget {
title = '${currentMembers.map((e) => e.user?.name).join(', ')} '
'${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}';
}
} else {
title = extraData['name'];
}
return Text(
@@ -6,6 +6,7 @@ import 'package:jiffy/jiffy.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_preview.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_preview_paint.png)
@@ -173,7 +174,7 @@ class ChannelPreview extends StatelessWidget {
startOfDay
.subtract(const Duration(days: 1))
.millisecondsSinceEpoch) {
stringDate = 'Yesterday';
stringDate = context.translations.yesterdayLabel;
} else if (startOfDay.difference(lastMessageAt).inDays < 7) {
stringDate = Jiffy(lastMessageAt.toLocal()).EEEE;
} else {
@@ -198,7 +199,7 @@ class ChannelPreview extends StatelessWidget {
size: 16,
),
Text(
' Channel is muted',
' ${context.translations.channelIsMutedText}',
style: chatThemeData.channelPreviewTheme.subtitle,
),
],
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:jiffy/jiffy.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// It shows a date divider depending on the date difference
class DateDivider extends StatelessWidget {
@@ -24,10 +25,10 @@ class DateDivider extends StatelessWidget {
String dayInfo;
if (Jiffy(createdAt).isSame(now, Units.DAY)) {
dayInfo = 'Today';
dayInfo = context.translations.todayLabel;
} else if (Jiffy(createdAt)
.isSame(now.subtract(const Duration(days: 1)), Units.DAY)) {
dayInfo = 'Yesterday';
dayInfo = context.translations.yesterdayLabel;
} else if (Jiffy(createdAt).isAfter(
now.subtract(const Duration(days: 7)),
Units.DAY,
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Widget to display deleted message
class DeletedMessage extends StatelessWidget {
@@ -49,7 +50,7 @@ class DeletedMessage extends StatelessWidget {
horizontal: 16,
),
child: Text(
'Message deleted',
context.translations.messageDeletedLabel,
style: messageTheme.messageText?.copyWith(
fontStyle: FontStyle.italic,
color: messageTheme.createdAt?.color,
@@ -2,6 +2,7 @@ import 'package:characters/characters.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/emoji/emoji.dart';
import 'package:stream_chat_flutter/src/localization/translations.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
final _emojiChars = Emoji.chars();
@@ -9,7 +10,8 @@ final _emojiChars = Emoji.chars();
/// String extension
extension StringExtension on String {
/// Returns the capitalized string
String capitalize() => '${this[0].toUpperCase()}${substring(1)}';
String capitalize() =>
'${this[0].toUpperCase()}${substring(1).toLowerCase()}';
/// Returns whether the string contains only emoji's or not.
///
@@ -103,6 +105,11 @@ extension BuildContextX on BuildContext {
// ignore: public_member_api_docs
double get textScaleFactor =>
MediaQuery.maybeOf(this)?.textScaleFactor ?? 1.0;
/// Retrieves current translations according to locale
/// Defaults to [DefaultTranslations]
Translations get translations =>
StreamChatLocalizations.of(this) ?? DefaultTranslations.instance;
}
/// Extension on [BorderRadius]
@@ -4,12 +4,12 @@ import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:chewie/chewie.dart';
import 'package:flutter/material.dart';
import 'package:jiffy/jiffy.dart';
import 'package:photo_view/photo_view.dart';
import 'package:stream_chat_flutter/src/gallery_footer.dart';
import 'package:stream_chat_flutter/src/gallery_header.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:video_player/video_player.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Return action for coming back from pages
enum ReturnActionType {
@@ -182,9 +182,10 @@ class _FullScreenMediaState extends State<FullScreenMedia>
children: [
GalleryHeader(
userName: widget.userName,
sentAt:
// ignore: lines_longer_than_80_chars
'Sent ${getDay(widget.message.createdAt.toLocal())} at ${Jiffy(widget.message.createdAt.toLocal()).format('HH:mm')}',
sentAt: context.translations.sentAtText(
date: widget.message.createdAt,
time: widget.message.createdAt,
),
onBackPressed: () {
Navigator.of(context).pop();
},
@@ -197,7 +198,7 @@ class _FullScreenMediaState extends State<FullScreenMedia>
);
},
),
if (widget.message.type != 'ephemeral')
if (!widget.message.isEphemeral)
GalleryFooter(
currentPage: _currentPage,
totalPages: widget.mediaAttachments.length,
@@ -222,22 +223,6 @@ class _FullScreenMediaState extends State<FullScreenMedia>
),
);
String getDay(DateTime dateTime) {
final now = DateTime.now();
if (DateTime(dateTime.year, dateTime.month, dateTime.day) ==
DateTime(now.year, now.month, now.day)) {
return 'today';
} else if (DateTime(now.year, now.month, now.day)
.difference(dateTime)
.inHours <
24) {
return 'yesterday';
} else {
return 'on ${Jiffy(dateTime).MMMd}';
}
}
@override
void dispose() async {
for (final package in videoPackages.values) {
@@ -11,6 +11,7 @@ import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/src/video_thumbnail_image.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Footer widget for media display
class GalleryFooter extends StatefulWidget implements PreferredSizeWidget {
@@ -135,7 +136,9 @@ class _GalleryFooterState extends State<GalleryFooter> {
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
'${widget.currentPage + 1} of ${widget.totalPages}',
'${widget.currentPage + 1} '
'${context.translations.ofText} '
'${widget.totalPages}',
style: galleryFooterThemeData.titleTextStyle,
),
],
@@ -191,7 +194,7 @@ class _GalleryFooterState extends State<GalleryFooter> {
child: Padding(
padding: const EdgeInsets.all(16),
child: Text(
'Photos',
context.translations.photosLabel,
style:
galleryFooterThemeData.bottomSheetPhotosTextStyle,
),
@@ -67,7 +67,7 @@ class GalleryHeader extends StatelessWidget implements PreferredSizeWidget {
: const SizedBox(),
backgroundColor: galleryHeaderThemeData.backgroundColor,
actions: <Widget>[
if (message.type != 'ephemeral')
if (!message.isEphemeral)
IconButton(
icon: StreamSvgIcon.iconMenuPoint(
color: galleryHeaderThemeData.iconMenuPointColor,
@@ -78,7 +78,7 @@ class GalleryHeader extends StatelessWidget implements PreferredSizeWidget {
),
],
centerTitle: true,
title: message.type != 'ephemeral'
title: !message.isEphemeral
? InkWell(
onTap: onTitleTap,
child: SizedBox(
@@ -0,0 +1,36 @@
import 'package:flutter/widgets.dart';
import 'package:stream_chat_flutter/src/localization/translations.dart'
show Translations;
/// Defines the localized resource values used by the StreamChatFlutter widgets.
///
/// See also:
///
/// * [GlobalStreamChatLocalizations], which provides stream chat localizations
/// for many languages.
abstract class StreamChatLocalizations implements Translations {
/// The `StreamChatLocalizations` from the closest [Localizations] instance
/// that encloses the given context.
///
/// If no [StreamChatLocalizations] are available in the given `context`, this
/// method returns null.
///
/// This method is just a convenient shorthand for:
/// `Localizations.of<StreamChatLocalizations>(
/// context,
/// StreamChatLocalizations
/// )`.
///
/// References to the localized resources defined by this class are typically
/// written in terms of this method. For example:
///
/// ```dart
/// tooltip: StreamChatLocalizations.of(context).streamChatLabel,
/// ```
static StreamChatLocalizations? of(BuildContext context) =>
Localizations.of<StreamChatLocalizations>(
context,
StreamChatLocalizations,
);
}
@@ -0,0 +1,667 @@
import 'package:jiffy/jiffy.dart';
import 'package:stream_chat_flutter/src/connection_status_builder.dart';
import 'package:stream_chat_flutter/src/message_input.dart';
import 'package:stream_chat_flutter/src/message_list_view.dart';
import 'package:stream_chat_flutter/src/message_search_list_view.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'
show User;
/// Translation strings for the stream chat widgets
abstract class Translations {
/// The error shown when [launchURL] fails
String get launchUrlError;
/// The error shown when loading users fails
String get loadingUsersError;
/// The label for "retry" button
String get retryLabel;
/// The label for showing no users
String get noUsersLabel;
/// The text for showing user is online
String get userOnlineText;
/// The text for showing the last online of the user
String get userLastOnlineText;
/// The text shown when [users] starts typing
String userTypingText(Iterable<User> users);
/// The label for "thread reply"
String get threadReplyLabel;
/// The text for showing if the message is only visible to you
String get onlyVisibleToYouText;
/// The text for showing the thread reply count
String threadReplyCountText(int count);
/// The text for showing the attachments upload progress
String attachmentsUploadProgressText({
required int remaining,
required int total,
});
/// The text for showing who pinned the message
String pinnedByUserText({
required User pinnedBy,
required User currentUser,
});
/// The text for showing there are empty messages
String get emptyMessagesText;
/// The text for showing generic error
String get genericErrorText;
/// The error shown when loading messages fails
String get loadingMessagesError;
/// The text for showing the result count in [MessageSearchListView]
String resultCountText(int count);
/// The text for showing the message is deleted
String get messageDeletedText;
/// The label for message deleted
String get messageDeletedLabel;
/// The label for message reactions
String get messageReactionsLabel;
/// The text for showing there are no chats
String get emptyChatMessagesText;
/// The text for showing the thread separator in case [MessageListView]
/// contains a parent message
String threadSeparatorText(int replyCount);
/// The label for "connected" in [ConnectionStatusBuilder]
String get connectedLabel;
/// The label for "disconnected" in [ConnectionStatusBuilder]
String get disconnectedLabel;
/// The label for "reconnecting" in [ConnectionStatusBuilder]
String get reconnectingLabel;
/// The label for also send as direct message "checkbox"" in [MessageInput]
String get alsoSendAsDirectMessageLabel;
/// The label for search Gif
String get searchGifLabel;
/// The label for add a comment or send in case of
/// attachments inside [MessageInput]
String get addACommentOrSendLabel;
/// The label for write a message in [MessageInput]
String get writeAMessageLabel;
/// The label for instant commands in [MessageInput]
String get instantCommandsLabel;
/// The error shown in case the fi"le is too large even after compression
/// while uploading via [MessageInput]
String fileTooLargeAfterCompressionError(double limitInMB);
/// The error shown in case the file is too large
/// while uploading via [MessageInput]
String fileTooLargeError(double limitInMB);
/// The text for showing the query while searching for emojis
String emojiMatchingQueryText(String query);
/// The label for "add a file"
String get addAFileLabel;
/// The label for "upload a photo"
String get uploadAPhotoLabel;
/// The label for "upload a video"
String get uploadAVideoLabel;
/// The label for "photo from camera"
String get photoFromCameraLabel;
/// The label for "video from camera"
String get videoFromCameraLabel;
/// The label for "upload a file"
String get uploadAFileLabel;
/// The error shown when something went wrong
String get somethingWentWrongError;
/// The label for "OK"
String get okLabel;
/// The label for "add more files"
String get addMoreFilesLabel;
/// The message shown for asking photo and video access permission
String get enablePhotoAndVideoAccessMessage;
/// The message shown for asking gallery access permission
String get allowGalleryAccessMessage;
/// The label for "flag message"
String get flagMessageLabel;
/// The question asked while showing flag message dialog
String get flagMessageQuestion;
/// The label for "Flag"
String get flagLabel;
/// The label for "Cancel"
String get cancelLabel;
/// The label for successful message flag
String get flagMessageSuccessfulLabel;
/// The text for showing the message if successfully flagged
String get flagMessageSuccessfulText;
/// The label for "delete message"
String get deleteMessageLabel;
/// The question asked while showing delete message dialog
String get deleteMessageQuestion;
/// The label for "Delete"
String get deleteLabel;
/// The text for showing the operation could not be completed
String get operationCouldNotBeCompletedText;
/// The label for "Reply"
String get replyLabel;
/// The text for showing pin/un-pin functionality in [MessageWidget]
/// based on [pinned]
String togglePinUnpinText({required bool pinned});
/// The text for showing delete/retry-delete based on [isDeleteFailed]
String toggleDeleteRetryDeleteMessageText({required bool isDeleteFailed});
/// The label for "copy message"
String get copyMessageLabel;
/// The label for "edit message"
String get editMessageLabel;
/// The text for showing resend/resend-edited message
/// based on [isUpdateFailed]
String toggleResendOrResendEditedMessage({required bool isUpdateFailed});
/// The label for "Photos"
String get photosLabel;
/// The text for showing on which [date] and [time] the message was sent
String sentAtText({required DateTime date, required DateTime time});
/// The label for "Today"
String get todayLabel;
/// The label for "Yesterday"
String get yesterdayLabel;
/// The text for showing the channel is muted
String get channelIsMutedText;
/// The text for showing there is no title
String get noTitleText;
/// The label for "let's start chatting"
String get letsStartChattingLabel;
/// The label for sending the first message
String get sendingFirstMessageLabel;
/// The label for "start a chat"
String get startAChatLabel;
/// The error shown when loading channel fails
String get loadingChannelsError;
/// The label for "Delete conversation"
String get deleteConversationLabel;
/// The question asked while showing delete conversation dialog
String get deleteConversationQuestion;
/// The label for "Stream Chat"
String get streamChatLabel;
/// The text for showing searching for network
String get searchingForNetworkText;
/// The label for "Offline"
String get offlineLabel;
/// The label for "Try again"
String get tryAgainLabel;
/// The text for showing the members count based on [count]
String membersCountText(int count);
/// The text for showing the watchers count based on [count]
String watchersCountText(int count);
/// The label for "View Info"
String get viewInfoLabel;
/// The label for "Leave Group"
String get leaveGroupLabel;
/// The label for "Leave"
String get leaveLabel;
/// The label for "Leave conversation"
String get leaveConversationLabel;
/// The question asked while showing leave conversation dialog
String get leaveConversationQuestion;
/// The label for "Show in chat"
String get showInChatLabel;
/// The label for "Save Image"
String get saveImageLabel;
/// The label for "Save Video"
String get saveVideoLabel;
/// The label for "Upload Error"
String get uploadErrorLabel;
/// The label for "Giphy"
String get giphyLabel;
/// The label for "Shuffle"
String get shuffleLabel;
/// The label for "Send"
String get sendLabel;
/// The label for "With"
String get withText;
/// The text shown for "In"
String get inText;
/// The text shown for "You"
String get youText;
/// The text shown for "Of"
String get ofText;
/// The text shown for "File"
String get fileText;
/// The label for "Reply to message"
String get replyToMessageLabel;
}
/// Default implementation of Translation strings for the stream chat widgets
class DefaultTranslations implements Translations {
const DefaultTranslations._();
/// Singleton instance of [DefaultTranslations]
static const instance = DefaultTranslations._();
@override
String get launchUrlError => 'Cannot launch the url';
@override
String get loadingUsersError => 'Error loading users';
@override
String get noUsersLabel => 'There are no users currently';
@override
String get retryLabel => 'Retry';
@override
String get userLastOnlineText => 'Last online';
@override
String get userOnlineText => 'Online';
@override
String userTypingText(Iterable<User> users) {
if (users.isEmpty) return '';
final first = users.first;
if (users.length == 1) {
return '${first.name} is typing';
}
return '${first.name} and ${users.length - 1} more are typing';
}
@override
String get threadReplyLabel => 'Thread Reply';
@override
String get onlyVisibleToYouText => 'Only visible to you';
@override
String threadReplyCountText(int count) => '$count Thread Replies';
@override
String attachmentsUploadProgressText({
required int remaining,
required int total,
}) =>
'Uploading $remaining/$total ...';
@override
String pinnedByUserText({
required User pinnedBy,
required User currentUser,
}) {
final pinnedByCurrentUser = currentUser.id == pinnedBy.id;
if (pinnedByCurrentUser) return 'Pinned by You';
return 'Pinned by ${pinnedBy.name}';
}
@override
String get emptyMessagesText => 'There are no messages currently';
@override
String get genericErrorText => 'Something went wrong';
@override
String get loadingMessagesError => 'Error loading messages';
@override
String resultCountText(int count) => '$count results';
@override
String get messageDeletedText => 'This message is deleted.';
@override
String get messageDeletedLabel => 'Message deleted';
@override
String get messageReactionsLabel => 'Message Reactions';
@override
String get emptyChatMessagesText => 'No chats here yet...';
@override
String threadSeparatorText(int replyCount) {
if (replyCount == 1) return '1 Reply';
return '$replyCount Replies';
}
@override
String get connectedLabel => 'Connected';
@override
String get disconnectedLabel => 'Disconnected';
@override
String get reconnectingLabel => 'Reconnecting...';
@override
String get alsoSendAsDirectMessageLabel => 'Also send as direct message';
@override
String get addACommentOrSendLabel => 'Add a comment or send';
@override
String get searchGifLabel => 'Search GIFs';
@override
String get writeAMessageLabel => 'Write a message';
@override
String get instantCommandsLabel => 'Instant Commands';
@override
String fileTooLargeAfterCompressionError(double limitInMB) =>
'The file is too large to upload. '
'The file size limit is $limitInMB MB. '
'We tried compressing it, but it was not enough.';
@override
String fileTooLargeError(double limitInMB) =>
'The file is too large to upload. The file size limit is $limitInMB MB.';
@override
String emojiMatchingQueryText(String query) => 'Emoji matching "$query"';
@override
String get addAFileLabel => 'Add a file';
@override
String get photoFromCameraLabel => 'Photo from camera';
@override
String get uploadAFileLabel => 'Upload a file';
@override
String get uploadAPhotoLabel => 'Upload a photo';
@override
String get uploadAVideoLabel => 'Upload a video';
@override
String get videoFromCameraLabel => 'Video from camera';
@override
String get okLabel => 'OK';
@override
String get somethingWentWrongError => 'Something went wrong';
@override
String get addMoreFilesLabel => 'Add more files';
@override
String get enablePhotoAndVideoAccessMessage =>
'Please enable access to your photos'
'\nand videos so you can share them with friends.';
@override
String get allowGalleryAccessMessage => 'Allow access to your gallery';
@override
String get flagMessageLabel => 'Flag Message';
@override
String get flagMessageQuestion =>
'Do you want to send a copy of this message to a'
'\nmoderator for further investigation?';
@override
String get flagLabel => 'FLAG';
@override
String get cancelLabel => 'CANCEL';
@override
String get flagMessageSuccessfulLabel => 'Message flagged';
@override
String get flagMessageSuccessfulText =>
'The message has been reported to a moderator.';
@override
String get deleteLabel => 'DELETE';
@override
String get deleteMessageLabel => 'Delete Message';
@override
String get deleteMessageQuestion =>
'Are you sure you want to permanently delete this\nmessage?';
@override
String get operationCouldNotBeCompletedText =>
'The operation couldn\'t be completed.';
@override
String get replyLabel => 'Reply';
@override
String togglePinUnpinText({required bool pinned}) {
if (pinned) return 'Unpin from Conversation';
return 'Pin to Conversation';
}
@override
String toggleDeleteRetryDeleteMessageText({required bool isDeleteFailed}) {
if (isDeleteFailed) return 'Retry Deleting Message';
return 'Delete Message';
}
@override
String get copyMessageLabel => 'Copy Message';
@override
String get editMessageLabel => 'Edit Message';
@override
String toggleResendOrResendEditedMessage({required bool isUpdateFailed}) {
if (isUpdateFailed) return 'Resend Edited Message';
return 'Resend';
}
@override
String get photosLabel => 'Photos';
String _getDay(DateTime dateTime) {
final now = DateTime.now();
final today = DateTime(now.year, now.month, now.day);
final yesterday = DateTime(now.year, now.month, now.day - 1);
final date = DateTime(dateTime.year, dateTime.month, dateTime.day);
if (date == today) {
return 'today';
} else if (date == yesterday) {
return 'yesterday';
} else {
return 'on ${Jiffy(date).MMMd}';
}
}
@override
String sentAtText({required DateTime date, required DateTime time}) =>
'Sent ${_getDay(date)} at ${Jiffy(time.toLocal()).format('HH:mm')}';
@override
String get todayLabel => 'Today';
@override
String get yesterdayLabel => 'Yesterday';
@override
String get channelIsMutedText => 'Channel is muted';
@override
String get noTitleText => 'No title';
@override
String get letsStartChattingLabel => 'Lets start chatting!';
@override
String get sendingFirstMessageLabel =>
'How about sending your first message to a friend?';
@override
String get startAChatLabel => 'Start a chat';
@override
String get loadingChannelsError => 'Error loading channels';
@override
String get deleteConversationLabel => 'Delete Conversation';
@override
String get deleteConversationQuestion =>
'Are you sure you want to delete this conversation?';
@override
String get streamChatLabel => 'Stream Chat';
@override
String get searchingForNetworkText => 'Searching for Network';
@override
String get offlineLabel => 'Offline...';
@override
String get tryAgainLabel => 'Try Again';
@override
String membersCountText(int count) {
if (count == 1) return '1 Member';
return '$count Members';
}
@override
String watchersCountText(int count) {
if (count == 1) return '1 Online';
return '$count Online';
}
@override
String get viewInfoLabel => 'View Info';
@override
String get leaveGroupLabel => 'Leave Group';
@override
String get leaveLabel => 'LEAVE';
@override
String get leaveConversationLabel => 'Leave conversation';
@override
String get leaveConversationQuestion =>
'Are you sure you want to leave this conversation?';
@override
String get showInChatLabel => 'Show in Chat';
@override
String get saveImageLabel => 'Save Image';
@override
String get saveVideoLabel => 'Save Video';
@override
String get uploadErrorLabel => 'UPLOAD ERROR';
@override
String get giphyLabel => 'Giphy';
@override
String get shuffleLabel => 'Shuffle';
@override
String get sendLabel => 'Send';
@override
String get withText => 'with';
@override
String get inText => 'in';
@override
String get youText => 'You';
@override
String get ofText => 'of';
@override
String get fileText => 'File';
@override
String get replyToMessageLabel => 'Reply to Message';
}
@@ -268,16 +268,14 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
final streamChatThemeData = StreamChatTheme.of(context);
final answer = await showConfirmationDialog(
context,
title: 'Flag Message',
title: context.translations.flagMessageLabel,
icon: StreamSvgIcon.flag(
color: streamChatThemeData.colorTheme.accentError,
size: 24,
),
question:
// ignore: lines_longer_than_80_chars
'Do you want to send a copy of this message to a\nmoderator for further investigation?',
okText: 'FLAG',
cancelText: 'CANCEL',
question: context.translations.flagMessageQuestion,
okText: context.translations.flagLabel,
cancelText: context.translations.cancelLabel,
);
final theme = streamChatThemeData;
@@ -290,9 +288,9 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
color: theme.colorTheme.accentError,
size: 24,
),
details: 'The message has been reported to a moderator.',
title: 'Message flagged',
okText: 'OK',
details: context.translations.flagMessageSuccessfulText,
title: context.translations.flagMessageSuccessfulLabel,
okText: context.translations.okLabel,
);
} catch (err) {
if (err is StreamChatNetworkError &&
@@ -303,9 +301,9 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
color: theme.colorTheme.accentError,
size: 24,
),
details: 'The message has been reported to a moderator.',
title: 'Message flagged',
okText: 'OK',
details: context.translations.flagMessageSuccessfulText,
title: context.translations.flagMessageSuccessfulLabel,
okText: context.translations.okLabel,
);
} else {
_showErrorAlert();
@@ -335,14 +333,14 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
});
final answer = await showConfirmationDialog(
context,
title: 'Delete message',
title: context.translations.deleteMessageLabel,
icon: StreamSvgIcon.flag(
color: StreamChatTheme.of(context).colorTheme.accentError,
size: 24,
),
question: 'Are you sure you want to permanently delete this\nmessage?',
okText: 'DELETE',
cancelText: 'CANCEL',
question: context.translations.deleteMessageQuestion,
okText: context.translations.deleteLabel,
cancelText: context.translations.cancelLabel,
);
if (answer == true) {
@@ -366,9 +364,9 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
color: StreamChatTheme.of(context).colorTheme.accentError,
size: 24,
),
details: 'The operation couldn\'t be completed.',
title: 'Something went wrong',
okText: 'OK',
details: context.translations.operationCouldNotBeCompletedText,
title: context.translations.somethingWentWrongError,
okText: context.translations.okLabel,
);
}
@@ -390,7 +388,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
),
const SizedBox(width: 16),
Text(
'Reply',
context.translations.replyLabel,
style: streamChatThemeData.textTheme.body,
),
],
@@ -412,7 +410,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
),
const SizedBox(width: 16),
Text(
'Flag Message',
context.translations.flagMessageLabel,
style: streamChatThemeData.textTheme.body,
),
],
@@ -435,7 +433,9 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
),
const SizedBox(width: 16),
Text(
'${widget.message.pinned ? 'Unpin from' : 'Pin to'} Conversation',
context.translations.togglePinUnpinText(
pinned: widget.message.pinned,
),
style: streamChatThemeData.textTheme.body,
),
],
@@ -458,7 +458,9 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
),
const SizedBox(width: 16),
Text(
isDeleteFailed ? 'Retry Deleting Message' : 'Delete Message',
context.translations.toggleDeleteRetryDeleteMessageText(
isDeleteFailed: isDeleteFailed,
),
style: StreamChatTheme.of(context)
.textTheme
.body
@@ -487,7 +489,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
),
const SizedBox(width: 16),
Text(
'Copy Message',
context.translations.copyMessageLabel,
style: streamChatThemeData.textTheme.body,
),
],
@@ -512,7 +514,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
),
const SizedBox(width: 16),
Text(
'Edit Message',
context.translations.editMessageLabel,
style: streamChatThemeData.textTheme.body,
),
],
@@ -544,7 +546,9 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
),
const SizedBox(width: 16),
Text(
isUpdateFailed ? 'Resend Edited Message' : 'Resend',
context.translations.toggleResendOrResendEditedMessage(
isUpdateFailed: isUpdateFailed,
),
style: streamChatThemeData.textTheme.body,
),
],
@@ -588,9 +592,9 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
color: streamChatThemeData.colorTheme.disabled,
),
),
const Text(
'Edit Message',
style: TextStyle(fontWeight: FontWeight.bold),
Text(
context.translations.editMessageLabel,
style: const TextStyle(fontWeight: FontWeight.bold),
),
IconButton(
visualDensity: VisualDensity.compact,
@@ -636,7 +640,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
),
const SizedBox(width: 16),
Text(
'Thread Reply',
context.translations.threadReplyLabel,
style: streamChatThemeData.textTheme.body,
),
],
@@ -357,9 +357,9 @@ class MessageInputState extends State<MessageInput> {
color: _streamChatTheme.colorTheme.disabled,
),
),
const Text(
'Reply to Message',
style: TextStyle(fontWeight: FontWeight.bold),
Text(
context.translations.replyToMessageLabel,
style: const TextStyle(fontWeight: FontWeight.bold),
),
IconButton(
visualDensity: VisualDensity.compact,
@@ -461,7 +461,7 @@ class MessageInputState extends State<MessageInput> {
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Text(
'Also send as direct message',
context.translations.alsoSendAsDirectMessageLabel,
style: _streamChatTheme.textTheme.footnote.copyWith(
color: _streamChatTheme.colorTheme.textHighEmphasis
.withOpacity(0.5),
@@ -586,7 +586,7 @@ class MessageInputState extends State<MessageInput> {
style: _streamChatTheme.messageInputTheme.inputTextStyle,
autofocus: widget.autofocus,
textAlignVertical: TextAlignVertical.center,
decoration: _getInputDecoration(),
decoration: _getInputDecoration(context),
textCapitalization: TextCapitalization.sentences,
),
)
@@ -598,11 +598,11 @@ class MessageInputState extends State<MessageInput> {
);
}
InputDecoration _getInputDecoration() {
InputDecoration _getInputDecoration(BuildContext context) {
final passedDecoration = _streamChatTheme.messageInputTheme.inputDecoration;
return InputDecoration(
isDense: true,
hintText: _getHint(),
hintText: _getHint(context),
hintStyle: _streamChatTheme.messageInputTheme.inputTextStyle!.copyWith(
color: _streamChatTheme.colorTheme.textLowEmphasis,
),
@@ -751,14 +751,14 @@ class MessageInputState extends State<MessageInput> {
);
}
String _getHint() {
String _getHint(BuildContext context) {
if (_commandEnabled && _chosenCommand!.name == 'giphy') {
return 'Search GIFs';
return context.translations.searchGifLabel;
}
if (_attachments.isNotEmpty) {
return 'Add a comment or send';
return context.translations.addACommentOrSendLabel;
}
return 'Write a message';
return context.translations.writeAMessageLabel;
}
void _checkEmoji(String s, BuildContext context) {
@@ -881,7 +881,7 @@ class MessageInputState extends State<MessageInput> {
),
),
Text(
'Instant Commands',
context.translations.instantCommandsLabel,
style: TextStyle(
color: _streamChatTheme.colorTheme.textHighEmphasis
.withOpacity(.5),
@@ -1144,8 +1144,9 @@ class MessageInputState extends State<MessageInput> {
if (mediaInfo.filesize! > widget.maxAttachmentSize) {
_showErrorAlert(
// ignore: lines_longer_than_80_chars
'The file is too large to upload. The file size limit is 20MB. We tried compressing it, but it was not enough.',
context.translations.fileTooLargeAfterCompressionError(
widget.maxAttachmentSize / (1024 * 1024),
),
);
return;
}
@@ -1156,9 +1157,9 @@ class MessageInputState extends State<MessageInput> {
path: mediaInfo.path,
);
} else {
_showErrorAlert(
'The file is too large to upload. The file size limit is 20MB.',
);
_showErrorAlert(context.translations.fileTooLargeError(
widget.maxAttachmentSize / (1024 * 1024),
));
return;
}
}
@@ -1426,7 +1427,9 @@ class MessageInputState extends State<MessageInput> {
),
Flexible(
child: Text(
'Emoji matching "$query"',
context.translations.emojiMatchingQueryText(
query,
),
style: TextStyle(
color: _streamChatTheme
.colorTheme.textHighEmphasis
@@ -1776,17 +1779,17 @@ class MessageInputState extends State<MessageInput> {
builder: (_) => Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const ListTile(
ListTile(
title: Text(
'Add a file',
style: TextStyle(
context.translations.addAFileLabel,
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
),
ListTile(
leading: const Icon(Icons.image),
title: const Text('Upload a photo'),
title: Text(context.translations.uploadAPhotoLabel),
onTap: () {
pickFile(DefaultAttachmentTypes.image);
Navigator.pop(context);
@@ -1794,7 +1797,7 @@ class MessageInputState extends State<MessageInput> {
),
ListTile(
leading: const Icon(Icons.video_library),
title: const Text('Upload a video'),
title: Text(context.translations.uploadAVideoLabel),
onTap: () {
pickFile(DefaultAttachmentTypes.video);
Navigator.pop(context);
@@ -1803,7 +1806,7 @@ class MessageInputState extends State<MessageInput> {
if (!kIsWeb)
ListTile(
leading: const Icon(Icons.camera_alt),
title: const Text('Photo from camera'),
title: Text(context.translations.photoFromCameraLabel),
onTap: () {
pickFile(DefaultAttachmentTypes.image, true);
Navigator.pop(context);
@@ -1812,7 +1815,7 @@ class MessageInputState extends State<MessageInput> {
if (!kIsWeb)
ListTile(
leading: const Icon(Icons.videocam),
title: const Text('Video from camera'),
title: Text(context.translations.videoFromCameraLabel),
onTap: () {
pickFile(DefaultAttachmentTypes.video, true);
Navigator.pop(context);
@@ -1820,7 +1823,7 @@ class MessageInputState extends State<MessageInput> {
),
ListTile(
leading: const Icon(Icons.insert_drive_file),
title: const Text('Upload a file'),
title: Text(context.translations.uploadAFileLabel),
onTap: () {
pickFile(DefaultAttachmentTypes.file);
Navigator.pop(context);
@@ -1923,8 +1926,9 @@ class MessageInputState extends State<MessageInput> {
if (mediaInfo.filesize! > widget.maxAttachmentSize) {
_showErrorAlert(
// ignore: lines_longer_than_80_chars
'The file is too large to upload. The file size limit is 20MB. We tried compressing it, but it was not enough.',
context.translations.fileTooLargeAfterCompressionError(
widget.maxAttachmentSize / (1024 * 1024),
),
);
return;
}
@@ -1935,9 +1939,9 @@ class MessageInputState extends State<MessageInput> {
path: mediaInfo.path,
);
} else {
_showErrorAlert(
'The file is too large to upload. The file size limit is 20MB.',
);
_showErrorAlert(context.translations.fileTooLargeError(
widget.maxAttachmentSize / (1024 * 1024),
));
return;
}
}
@@ -2117,7 +2121,7 @@ class MessageInputState extends State<MessageInput> {
height: 26,
),
Text(
'Something went wrong',
context.translations.somethingWentWrongError,
style: _streamChatTheme.textTheme.headlineBold,
),
const SizedBox(
@@ -2146,7 +2150,7 @@ class MessageInputState extends State<MessageInput> {
Navigator.of(context).pop();
},
child: Text(
'OK',
context.translations.okLabel,
style: _streamChatTheme.textTheme.bodyBold.copyWith(
color: _streamChatTheme.colorTheme.accentPrimary),
),
@@ -2257,10 +2261,10 @@ class _PickerWidget extends StatefulWidget {
final StreamChatThemeData streamChatTheme;
@override
__PickerWidgetState createState() => __PickerWidgetState();
_PickerWidgetState createState() => _PickerWidgetState();
}
class __PickerWidgetState extends State<_PickerWidget> {
class _PickerWidgetState extends State<_PickerWidget> {
Future<bool>? requestPermission;
@override
@@ -2292,7 +2296,7 @@ class __PickerWidgetState extends State<_PickerWidget> {
color: widget.streamChatTheme.colorTheme.inputBg,
alignment: Alignment.center,
child: Text(
'Add more files',
context.translations.addMoreFilesLabel,
style: TextStyle(
color: widget.streamChatTheme.colorTheme.accentPrimary,
fontWeight: FontWeight.bold,
@@ -2324,8 +2328,7 @@ class __PickerWidgetState extends State<_PickerWidget> {
color: widget.streamChatTheme.colorTheme.disabled,
),
Text(
// ignore: lines_longer_than_80_chars
'Please enable access to your photos \nand videos so you can share them with friends.',
context.translations.enablePhotoAndVideoAccessMessage,
style: widget.streamChatTheme.textTheme.body.copyWith(
color:
widget.streamChatTheme.colorTheme.textLowEmphasis),
@@ -2334,7 +2337,7 @@ class __PickerWidgetState extends State<_PickerWidget> {
const SizedBox(height: 6),
Center(
child: Text(
'Allow access to your gallery',
context.translations.allowGalleryAccessMessage,
style: widget.streamChatTheme.textTheme.bodyBold.copyWith(
color: widget.streamChatTheme.colorTheme.accentPrimary,
),
@@ -335,6 +335,7 @@ class _MessageListViewState extends State<MessageListView> {
bool _inBetweenList = false;
late final _defaultController = MessageListController();
MessageListController get _messageListController =>
widget.messageListController ?? _defaultController;
@@ -349,7 +350,7 @@ class _MessageListViewState extends State<MessageListView> {
emptyBuilder: widget.emptyBuilder ??
(context) => Center(
child: Text(
'No chats here yet...',
context.translations.emptyChatMessagesText,
style: _streamTheme.textTheme.footnote.copyWith(
color: _streamTheme.colorTheme.textHighEmphasis
.withOpacity(.5)),
@@ -362,7 +363,7 @@ class _MessageListViewState extends State<MessageListView> {
errorBuilder: widget.errorBuilder ??
(BuildContext context, Object error) => Center(
child: Text(
'Something went wrong',
context.translations.genericErrorText,
style: _streamTheme.textTheme.footnote.copyWith(
color: _streamTheme.colorTheme.textHighEmphasis
.withOpacity(.5)),
@@ -408,14 +409,14 @@ class _MessageListViewState extends State<MessageListView> {
var showStatus = true;
switch (status) {
case ConnectionStatus.connected:
statusString = 'Connected';
statusString = context.translations.connectedLabel;
showStatus = false;
break;
case ConnectionStatus.connecting:
statusString = 'Reconnecting...';
statusString = context.translations.reconnectingLabel;
break;
case ConnectionStatus.disconnected:
statusString = 'Disconnected';
statusString = context.translations.disconnectedLabel;
break;
}
@@ -617,7 +618,7 @@ class _MessageListViewState extends State<MessageListView> {
return widget.threadSeparatorBuilder!.call(context);
}
final replyCount = widget.parentMessage!.replyCount;
final replyCount = widget.parentMessage!.replyCount!;
return DecoratedBox(
decoration: BoxDecoration(
gradient: _streamTheme.colorTheme.bgGradient,
@@ -625,7 +626,7 @@ class _MessageListViewState extends State<MessageListView> {
child: Padding(
padding: const EdgeInsets.all(8),
child: Text(
'$replyCount ${replyCount == 1 ? 'Reply' : 'Replies'}',
context.translations.threadSeparatorText(replyCount),
textAlign: TextAlign.center,
style: _streamTheme.channelTheme.channelHeaderTheme.subtitle,
),
@@ -1249,8 +1250,8 @@ class _LoadingIndicator extends StatelessWidget {
initialData: false,
errorBuilder: (context, error) => Container(
color: streamTheme.colorTheme.accentError.withOpacity(.2),
child: const Center(
child: Text('Error loading messages'),
child: Center(
child: Text(context.translations.loadingMessagesError),
),
),
builder: (context, data) {
@@ -7,6 +7,7 @@ import 'package:stream_chat_flutter/src/stream_chat.dart';
import 'package:stream_chat_flutter/src/user_avatar.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Modal widget for displaying message reactions
class MessageReactionsModal extends StatelessWidget {
@@ -154,7 +155,7 @@ class MessageReactionsModal extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Message Reactions',
context.translations.messageReactionsLabel,
style: chatThemeData.textTheme.headlineBold,
),
const SizedBox(height: 16),
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:jiffy/jiffy.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// It shows the current [Message] preview.
///
@@ -50,13 +51,13 @@ class MessageSearchItem extends StatelessWidget {
children: [
Text(
user.id == StreamChat.of(context).currentUser?.id
? 'You'
? context.translations.youText
: user.name,
style: chatThemeData.channelPreviewTheme.title,
),
if (channelName != null) ...[
Text(
' in ',
' ${context.translations.inText} ',
style: chatThemeData.channelPreviewTheme.title?.copyWith(
fontWeight: FontWeight.normal,
),
@@ -100,7 +101,7 @@ class MessageSearchItem extends StatelessWidget {
Widget _buildSubtitle(BuildContext context, Message message) {
var text = message.text;
if (message.isDeleted) {
text = 'This message was deleted.';
text = context.translations.messageDeletedText;
} else if (message.attachments.isNotEmpty) {
final parts = <String>[
...message.attachments.map((e) {
@@ -3,6 +3,7 @@ import 'package:stream_chat_flutter/src/info_tile.dart';
import 'package:stream_chat_flutter/src/message_search_item.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Callback called when tapping on a user
typedef MessageSearchItemTapCallback = void Function(GetMessageResponse);
@@ -140,6 +141,7 @@ class MessageSearchListView extends StatefulWidget {
class _MessageSearchListViewState extends State<MessageSearchListView> {
late final _defaultController = MessageSearchListController();
MessageSearchListController get _messageSearchListController =>
widget.messageSearchListController ?? _defaultController;
@@ -161,8 +163,8 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
),
child: const Center(
child: Text('There are no messages currently'),
child: Center(
child: Text(context.translations.emptyMessagesText),
),
),
),
@@ -176,7 +178,7 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
showMessage: widget.showErrorTile,
tileAnchor: Alignment.topCenter,
childAnchor: Alignment.topCenter,
message: 'An error occurred.',
message: context.translations.genericErrorText,
child: Container(),
);
},
@@ -240,10 +242,10 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
.colorTheme
.accentError
.withOpacity(.2),
child: const Padding(
padding: EdgeInsets.symmetric(vertical: 16),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Center(
child: Text('Error loading messages'),
child: Text(context.translations.loadingMessagesError),
),
),
);
@@ -306,7 +308,7 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
horizontal: 8,
),
child: Text(
'${items.length} results',
context.translations.resultCountText(items.length),
style: TextStyle(
color: chatThemeData.colorTheme.textLowEmphasis,
),
@@ -51,15 +51,10 @@ class MessageText extends StatelessWidget {
final mentionedUser = message.mentionedUsers.firstWhereOrNull(
(u) => '@${u.name}' == link,
);
if (mentionedUser == null) {
return;
}
if (onMentionTap != null) {
onMentionTap!(mentionedUser);
} else {
print('tap on ${mentionedUser.name}');
}
if (mentionedUser == null) return;
onMentionTap?.call(mentionedUser);
} else {
if (onLinkTap != null) {
onLinkTap!(link);
@@ -860,9 +860,9 @@ class _MessageWidgetState extends State<MessageWidget>
final showThreadParticipants = threadParticipants?.isNotEmpty == true;
final replyCount = widget.message.replyCount;
var msg = 'Thread Reply';
var msg = context.translations.threadReplyLabel;
if (showThreadReplyIndicator && replyCount! > 1) {
msg = '$replyCount Thread Replies';
msg = context.translations.threadReplyCountText(replyCount);
}
// ignore: prefer_function_declarations_over_variables
@@ -1207,7 +1207,10 @@ class _MessageWidgetState extends State<MessageWidget>
);
}
return Text(
'Uploading $uploadRemaining/$totalAttachments ...',
context.translations.attachmentsUploadProgressText(
remaining: uploadRemaining,
total: totalAttachments,
),
style: style,
);
}
@@ -1281,8 +1284,8 @@ class _MessageWidgetState extends State<MessageWidget>
}
Widget _buildPinnedMessage(Message message) {
final pinnedBy = message.pinnedBy;
final pinnedByMe = _streamChat.currentUser!.id == pinnedBy!.id;
final pinnedBy = message.pinnedBy!;
final currentUser = _streamChat.currentUser!;
return Padding(
padding: const EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 8),
@@ -1296,7 +1299,10 @@ class _MessageWidgetState extends State<MessageWidget>
width: 4,
),
Text(
'Pinned by ${pinnedByMe ? 'You' : pinnedBy.name}',
context.translations.pinnedByUserText(
pinnedBy: pinnedBy,
currentUser: currentUser,
),
style: TextStyle(
color: _streamChatTheme.colorTheme.textLowEmphasis,
fontSize: 13,
@@ -146,15 +146,14 @@ class StreamChatState extends State<StreamChat> {
/// The current user as a stream
Stream<User?> get currentUserStream => widget.client.state.currentUserStream;
@override
void initState() {
super.initState();
}
@override
void didChangeDependencies() {
final locale = ui.window.locale;
Jiffy.locale(locale.languageCode);
final languageCode = locale.languageCode;
final availableLocales = Jiffy.getAllAvailableLocales();
if (availableLocales.contains(languageCode)) {
Jiffy.locale(languageCode);
}
super.didChangeDependencies();
}
}
@@ -13,8 +13,8 @@ class SystemMessage extends StatelessWidget {
/// This message
final Message message;
// ignore: lines_longer_than_80_chars
/// The function called when tapping on the message when the message is not failed
/// The function called when tapping on the message
/// when the message is not failed
final void Function(Message)? onMessageTap;
@override
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/thread_header.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/thread_header_paint.png)
@@ -111,7 +112,7 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget {
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'with ',
'${context.translations.withText} ',
style: chatThemeData.channelTheme.channelHeaderTheme.subtitle,
),
Flexible(
@@ -149,7 +150,7 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget {
children: [
title ??
Text(
'Thread Reply',
context.translations.threadReplyLabel,
style: chatThemeData.channelTheme.channelHeaderTheme.title,
),
const SizedBox(height: 2),
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Widget to show the current list of typing users
class TypingIndicator extends StatelessWidget {
@@ -63,8 +64,7 @@ class TypingIndicator extends StatelessWidget {
height: 4,
),
Text(
// ignore: lines_longer_than_80_chars
' ${data.elementAt(0).name}${data.length == 1 ? '' : ' and ${data.length - 1} more'} ${data.length == 1 ? 'is' : 'are'} typing',
context.translations.userTypingText(data),
maxLines: 1,
style: style,
),
@@ -4,6 +4,7 @@ import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import 'package:stream_chat_flutter/src/user_list_view.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
///
/// It shows the current [User] preview.
@@ -86,12 +87,13 @@ class UserItem extends StatelessWidget {
);
}
Widget _buildLastActive(context) {
Widget _buildLastActive(BuildContext context) {
final chatTheme = StreamChatTheme.of(context);
return Text(
user.online == true
? 'Online'
: 'Last online ${Jiffy(user.lastActive).fromNow()}',
? context.translations.userOnlineText
: '${context.translations.userLastOnlineText} '
'${Jiffy(user.lastActive).fromNow()}',
style: chatTheme.textTheme.footnote.copyWith(
color: chatTheme.colorTheme.textHighEmphasis.withOpacity(.5)),
);
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Callback called when tapping on a user
typedef UserTapCallback = void Function(User, Widget?);
@@ -155,6 +156,7 @@ class _UserListViewState extends State<UserListView>
bool get _isListView => widget.crossAxisCount == 1;
late final _defaultController = UserListController();
UserListController get _userListController =>
widget.userListController ?? _defaultController;
@@ -220,9 +222,9 @@ class _UserListViewState extends State<UserListView>
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text.rich(
const TextSpan(
TextSpan(
children: [
WidgetSpan(
const WidgetSpan(
child: Padding(
padding: EdgeInsets.only(
right: 2,
@@ -230,14 +232,14 @@ class _UserListViewState extends State<UserListView>
child: Icon(Icons.error_outline),
),
),
TextSpan(text: 'Error loading users'),
TextSpan(text: context.translations.loadingUsersError),
],
),
style: Theme.of(context).textTheme.headline6,
),
TextButton(
onPressed: () => _userListController.loadData!(),
child: const Text('Retry'),
child: Text(context.translations.retryLabel),
),
],
),
@@ -250,8 +252,8 @@ class _UserListViewState extends State<UserListView>
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
),
child: const Center(
child: Text('There are no users currently'),
child: Center(
child: Text(context.translations.noUsersLabel),
),
),
),
@@ -400,10 +402,10 @@ class _UserListViewState extends State<UserListView>
.colorTheme
.accentError
.withOpacity(.2),
child: const Padding(
padding: EdgeInsets.symmetric(vertical: 16),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Center(
child: Text('Error loading users'),
child: Text(context.translations.loadingUsersError),
),
),
);
@@ -4,17 +4,15 @@ import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Launch URL
Future<void> launchURL(BuildContext context, String? url) async {
if (url != null && await canLaunch(url)) {
await launch(url);
} else {
// ignore: deprecated_member_use
Scaffold.of(context).showSnackBar(
const SnackBar(
content: Text('Cannot launch the url'),
),
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.translations.launchUrlError)),
);
}
}
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Widget for displaying a footnote
class VisibleFootnote extends StatelessWidget {
@@ -19,7 +20,7 @@ class VisibleFootnote extends StatelessWidget {
),
const SizedBox(width: 8),
Text(
'Only visible to you',
context.translations.onlyVisibleToYouText,
style: chatThemeData.textTheme.footnote
.copyWith(color: chatThemeData.colorTheme.textLowEmphasis),
),
@@ -1,3 +1,4 @@
export 'package:jiffy/jiffy.dart';
export 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
export 'src/attachment/attachment.dart';
@@ -16,6 +17,8 @@ export 'src/gallery_footer.dart';
export 'src/gallery_header.dart';
export 'src/gradient_avatar.dart';
export 'src/info_tile.dart';
export 'src/localization/stream_chat_localizations.dart';
export 'src/localization/translations.dart' show DefaultTranslations;
export 'src/mention_tile.dart';
export 'src/message_action.dart';
export 'src/message_input.dart';
@@ -7,6 +7,7 @@ issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
environment:
sdk: '>=2.12.0 <3.0.0'
flutter: ">=1.17.0"
dependencies:
cached_network_image: ^3.0.0
@@ -0,0 +1,174 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
void main() {
test('Default translations should exist', () {
const translations = DefaultTranslations.instance;
expect(translations.launchUrlError, isNotNull);
expect(translations.loadingUsersError, isNotNull);
expect(translations.noUsersLabel, isNotNull);
expect(translations.retryLabel, isNotNull);
expect(translations.userLastOnlineText, isNotNull);
expect(translations.userOnlineText, isNotNull);
expect(translations.userOnlineText, isNotNull);
// no users
expect(translations.userTypingText([]), isNotNull);
// single user
expect(translations.userTypingText([User(id: 'test-id')]), isNotNull);
// multiple users
expect(
translations.userTypingText([
User(id: 'test-id-1'),
User(id: 'test-id-2'),
]),
isNotNull,
);
expect(translations.threadReplyLabel, isNotNull);
expect(translations.onlyVisibleToYouText, isNotNull);
expect(translations.threadReplyCountText(3), isNotNull);
expect(
translations.attachmentsUploadProgressText(remaining: 3, total: 10),
isNotNull,
);
expect(
translations.pinnedByUserText(
pinnedBy: User(id: 'pinned-by-user-id'),
currentUser: OwnUser(id: 'current-user-id'),
),
isNotNull,
);
expect(translations.emptyMessagesText, isNotNull);
expect(translations.genericErrorText, isNotNull);
expect(translations.loadingMessagesError, isNotNull);
expect(translations.resultCountText(3), isNotNull);
expect(translations.messageDeletedText, isNotNull);
expect(translations.messageDeletedLabel, isNotNull);
expect(translations.messageReactionsLabel, isNotNull);
expect(translations.emptyChatMessagesText, isNotNull);
expect(translations.threadSeparatorText(3), isNotNull);
expect(translations.connectedLabel, isNotNull);
expect(translations.disconnectedLabel, isNotNull);
expect(translations.reconnectingLabel, isNotNull);
expect(translations.alsoSendAsDirectMessageLabel, isNotNull);
expect(translations.addACommentOrSendLabel, isNotNull);
expect(translations.searchGifLabel, isNotNull);
expect(translations.writeAMessageLabel, isNotNull);
expect(translations.instantCommandsLabel, isNotNull);
expect(translations.fileTooLargeAfterCompressionError(33), isNotNull);
expect(translations.fileTooLargeError(33), isNotNull);
expect(translations.emojiMatchingQueryText('sahil'), isNotNull);
expect(translations.addAFileLabel, isNotNull);
expect(translations.photoFromCameraLabel, isNotNull);
expect(translations.uploadAFileLabel, isNotNull);
expect(translations.uploadAPhotoLabel, isNotNull);
expect(translations.uploadAVideoLabel, isNotNull);
expect(translations.videoFromCameraLabel, isNotNull);
expect(translations.okLabel, isNotNull);
expect(translations.somethingWentWrongError, isNotNull);
expect(translations.addMoreFilesLabel, isNotNull);
expect(translations.enablePhotoAndVideoAccessMessage, isNotNull);
expect(translations.allowGalleryAccessMessage, isNotNull);
expect(translations.flagMessageLabel, isNotNull);
expect(translations.flagMessageQuestion, isNotNull);
expect(translations.flagLabel, isNotNull);
expect(translations.cancelLabel, isNotNull);
expect(translations.flagMessageSuccessfulLabel, isNotNull);
expect(translations.flagMessageSuccessfulText, isNotNull);
expect(translations.deleteLabel, isNotNull);
expect(translations.deleteMessageLabel, isNotNull);
expect(translations.deleteMessageQuestion, isNotNull);
expect(translations.operationCouldNotBeCompletedText, isNotNull);
expect(translations.replyLabel, isNotNull);
// pinned
expect(translations.togglePinUnpinText(pinned: true), isNotNull);
// un-pinned
expect(translations.togglePinUnpinText(pinned: false), isNotNull);
// delete-failed
expect(
translations.toggleDeleteRetryDeleteMessageText(isDeleteFailed: true),
isNotNull,
);
// first-delete
expect(
translations.toggleDeleteRetryDeleteMessageText(isDeleteFailed: false),
isNotNull,
);
expect(translations.copyMessageLabel, isNotNull);
expect(translations.editMessageLabel, isNotNull);
// resend-failed
expect(
translations.toggleResendOrResendEditedMessage(isUpdateFailed: true),
isNotNull,
);
// first resend
expect(
translations.toggleResendOrResendEditedMessage(isUpdateFailed: false),
isNotNull,
);
expect(translations.photosLabel, isNotNull);
// today
expect(
translations.sentAtText(
date: DateTime.now(),
time: DateTime.now(),
),
isNotNull,
);
// yesterday
expect(
translations.sentAtText(
date: DateTime.now().subtract(const Duration(days: 1)),
time: DateTime.now(),
),
isNotNull,
);
// any other day
expect(
translations.sentAtText(
date: DateTime.now().subtract(const Duration(days: 3)),
time: DateTime.now(),
),
isNotNull,
);
expect(translations.todayLabel, isNotNull);
expect(translations.yesterdayLabel, isNotNull);
expect(translations.channelIsMutedText, isNotNull);
expect(translations.noTitleText, isNotNull);
expect(translations.letsStartChattingLabel, isNotNull);
expect(translations.sendingFirstMessageLabel, isNotNull);
expect(translations.startAChatLabel, isNotNull);
expect(translations.loadingChannelsError, isNotNull);
expect(translations.deleteConversationLabel, isNotNull);
expect(translations.deleteConversationQuestion, isNotNull);
expect(translations.streamChatLabel, isNotNull);
expect(translations.searchingForNetworkText, isNotNull);
expect(translations.offlineLabel, isNotNull);
expect(translations.tryAgainLabel, isNotNull);
// 1 member
expect(translations.membersCountText(1), isNotNull);
// 3 members
expect(translations.membersCountText(3), isNotNull);
// 1 member
expect(translations.watchersCountText(1), isNotNull);
// 3 members
expect(translations.watchersCountText(3), isNotNull);
expect(translations.viewInfoLabel, isNotNull);
expect(translations.leaveGroupLabel, isNotNull);
expect(translations.leaveLabel, isNotNull);
expect(translations.leaveConversationLabel, isNotNull);
expect(translations.leaveConversationQuestion, isNotNull);
expect(translations.showInChatLabel, isNotNull);
expect(translations.saveImageLabel, isNotNull);
expect(translations.saveVideoLabel, isNotNull);
expect(translations.uploadErrorLabel, isNotNull);
expect(translations.giphyLabel, isNotNull);
expect(translations.shuffleLabel, isNotNull);
expect(translations.sendLabel, isNotNull);
expect(translations.withText, isNotNull);
expect(translations.inText, isNotNull);
expect(translations.youText, isNotNull);
expect(translations.ofText, isNotNull);
expect(translations.fileText, isNotNull);
expect(translations.replyToMessageLabel, isNotNull);
});
}
@@ -717,7 +717,7 @@ void main() {
await tester.tap(find.text('Delete Message'));
await tester.pumpAndSettle();
expect(find.text('Delete message'), findsOneWidget);
expect(find.text('Delete Message'), findsOneWidget);
await tester.tap(find.text('DELETE'));
await tester.pumpAndSettle();
@@ -773,7 +773,7 @@ void main() {
await tester.tap(find.text('Delete Message'));
await tester.pumpAndSettle();
expect(find.text('Delete message'), findsOneWidget);
expect(find.text('Delete Message'), findsOneWidget);
await tester.tap(find.text('DELETE'));
await tester.pumpAndSettle();
@@ -0,0 +1,74 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
build/
# Android related
**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: d79295af24c3ed621c33713ecda14ad196fd9c31
channel: stable
project_type: package
@@ -0,0 +1,3 @@
## 1.0.0
* First release
+219
View File
@@ -0,0 +1,219 @@
SOURCE CODE LICENSE AGREEMENT
IMPORTANT - READ THIS CAREFULLY BEFORE DOWNLOADING, INSTALLING, USING OR
ELECTRONICALLY ACCESSING THIS PROPRIETARY PRODUCT.
THIS IS A LEGAL AGREEMENT BETWEEN STREAM.IO, INC. (“STREAM.IO”) AND THE
BUSINESS ENTITY OR PERSON FOR WHOM YOU (“YOU”) ARE ACTING (“CUSTOMER”) AS THE
LICENSEE OF THE PROPRIETARY SOFTWARE INTO WHICH THIS AGREEMENT HAS BEEN
INCLUDED (THE “AGREEMENT”). YOU AGREE THAT YOU ARE THE CUSTOMER, OR YOU ARE AN
EMPLOYEE OR AGENT OF CUSTOMER AND ARE ENTERING INTO THIS AGREEMENT FOR LICENSE
OF THE SOFTWARE BY CUSTOMER FOR CUSTOMERS BUSINESS PURPOSES AS DESCRIBED IN
AND IN ACCORDANCE WITH THIS AGREEMENT. YOU HEREBY AGREE THAT YOU ENTER INTO
THIS AGREEMENT ON BEHALF OF CUSTOMER AND THAT YOU HAVE THE AUTHORITY TO BIND
CUSTOMER TO THIS AGREEMENT.
STREAM.IO IS WILLING TO LICENSE THE SOFTWARE TO CUSTOMER ONLY ON THE FOLLOWING
CONDITIONS: (1) YOU ARE A CURRENT CUSTOMER OF STREAM.IO; (2) YOU ARE NOT A
COMPETITOR OF STREAM.IO; AND (3) THAT YOU ACCEPT ALL THE TERMS IN THIS
AGREEMENT. BY DOWNLOADING, INSTALLING, CONFIGURING, ACCESSING OR OTHERWISE
USING THE SOFTWARE, INCLUDING ANY UPDATES, UPGRADES, OR NEWER VERSIONS, YOU
REPRESENT, WARRANT AND ACKNOWLEDGE THAT (A) CUSTOMER IS A CURRENT CUSTOMER OF
STREAM.IO; (B) CUSTOMER IS NOT A COMPETITOR OF STREAM.IO; AND THAT (C) YOU HAVE
READ THIS AGREEMENT, UNDERSTAND THIS AGREEMENT, AND THAT CUSTOMER AGREES TO BE
BOUND BY ALL THE TERMS OF THIS AGREEMENT.
IF YOU DO NOT AGREE TO ALL THE TERMS AND CONDITIONS OF THIS AGREEMENT,
STREAM.IO IS UNWILLING TO LICENSE THE SOFTWARE TO CUSTOMER, AND THEREFORE, DO
NOT COMPLETE THE DOWNLOAD PROCESS, ACCESS OR OTHERWISE USE THE SOFTWARE, AND
CUSTOMER SHOULD IMMEDIATELY RETURN THE SOFTWARE AND CEASE ANY USE OF THE
SOFTWARE.
1. SOFTWARE. The Stream.io software accompanying this Agreement, may include
Source Code, Executable Object Code, associated media, printed materials and
documentation (collectively, the “Software”). The Software also includes any
updates or upgrades to or new versions of the original Software, if and when
made available to you by Stream.io. “Source Code” means computer programming
code in human readable form that is not suitable for machine execution without
the intervening steps of interpretation or compilation. “Executable Object
Code" means the computer programming code in any other form than Source Code
that is not readily perceivable by humans and suitable for machine execution
without the intervening steps of interpretation or compilation. “Site” means a
Customer location controlled by Customer. “Authorized User” means any employee
or contractor of Customer working at the Site, who has signed a written
confidentiality agreement with Customer or is otherwise bound in writing by
confidentiality and use obligations at least as restrictive as those imposed
under this Agreement.
2. LICENSE GRANT. Subject to the terms and conditions of this Agreement, in
consideration for the representations, warranties, and covenants made by
Customer in this Agreement, Stream.io grants to Customer, during the term of
this Agreement, a personal, non-exclusive, non-transferable, non-sublicensable
license to:
a. install and use Software Source Code on password protected computers at a Site,
restricted to Authorized Users;
b. create derivative works, improvements (whether or not patentable), extensions
and other modifications to the Software Source Code (“Modifications”) to build
unique scalable newsfeeds, activity streams, and in-app messaging via Streams
application program interface (“API”);
c. compile the Software Source Code to create Executable Object Code versions of
the Software Source Code and Modifications to build such newsfeeds, activity
streams, and in-app messaging via the API;
d. install, execute and use such Executable Object Code versions solely for
Customers internal business use (including development of websites through
which data generated by Stream services will be streamed (“Apps”));
e. use and distribute such Executable Object Code as part of Customers Apps; and
f. make electronic copies of the Software and Modifications as required for backup
or archival purposes.
3. RESTRICTIONS. Customer is responsible for all activities that occur in
connection with the Software. Customer will not, and will not attempt to: (a)
sublicense or transfer the Software or any Source Code related to the Software
or any of Customers rights under this Agreement, except as otherwise provided
in this Agreement, (b) use the Software Source Code for the benefit of a third
party or to operate a service; (c) allow any third party to access or use the
Software Source Code; (d) sublicense or distribute the Software Source Code or
any Modifications in Source Code or other derivative works based on any part of
the Software Source Code; (e) use the Software in any manner that competes with
Stream.io or its business; or (e) otherwise use the Software in any manner that
exceeds the scope of use permitted in this Agreement. Customer shall use the
Software in compliance with any accompanying documentation any laws applicable
to Customer.
4. OPEN SOURCE. Customer and its Authorized Users shall not use any software or
software components that are open source in conjunction with the Software
Source Code or any Modifications in Source Code or in any way that could
subject the Software to any open source licenses.
5. CONTRACTORS. Under the rights granted to Customer under this Agreement,
Customer may permit its employees, contractors, and agencies of Customer to
become Authorized Users to exercise the rights to the Software granted to
Customer in accordance with this Agreement solely on behalf of Customer to
provide services to Customer; provided that Customer shall be liable for the
acts and omissions of all Authorized Users to the extent any of such acts or
omissions, if performed by Customer, would constitute a breach of, or otherwise
give rise to liability to Customer under, this Agreement. Customer shall not
and shall not permit any Authorized User to use the Software except as
expressly permitted in this Agreement.
6. COMPETITIVE PRODUCT DEVELOPMENT. Customer shall not use the Software in any way
to engage in the development of products or services which could be reasonably
construed to provide a complete or partial functional or commercial alternative
to Stream.ios products or services (a “Competitive Product”). Customer shall
ensure that there is no direct or indirect use of, or sharing of, Software
source code, or other information based upon or derived from the Software to
develop such products or services. Without derogating from the generality of
the foregoing, development of Competitive Products shall include having direct
or indirect access to, supervising, consulting or assisting in the development
of, or producing any specifications, documentation, object code or source code
for, all or part of a Competitive Product.
7. LIMITATION ON MODIFICATIONS. Notwithstanding any provision in this Agreement,
Modifications may only be created and used by Customer as permitted by this
Agreement and Modification Source Code may not be distributed to third parties.
Customer will not assert against Stream.io, its affiliates, or their customers,
direct or indirect, agents and contractors, in any way, any patent rights that
Customer may obtain relating to any Modifications for Stream.io, its
affiliates, or their customers, direct or indirect, agents and contractors
manufacture, use, import, offer for sale or sale of any Stream.io products or
services.
8. DELIVERY AND ACCEPTANCE. The Software will be delivered electronically pursuant
to Stream.io standard download procedures. The Software is deemed accepted upon
delivery.
9. IMPLEMENTATION AND SUPPORT. Stream.io has no obligation under this Agreement to
provide any support or consultation concerning the Software.
10. TERM AND TERMINATION. The term of this Agreement begins when the Software is
downloaded or accessed and shall continue until terminated. Either party may
terminate this Agreement upon written notice. This Agreement shall
automatically terminate if Customer is or becomes a competitor of Stream.io or
makes or sells any Competitive Products. Upon termination of this Agreement for
any reason, (a) all rights granted to Customer in this Agreement immediately
cease to exist, (b) Customer must promptly discontinue all use of the Software
and return to Stream.io or destroy all copies of the Software in Customers
possession or control. Any continued use of the Software by Customer or attempt
by Customer to exercise any rights under this Agreement after this Agreement
has terminated shall be considered copyright infringement and subject Customer
to applicable remedies for copyright infringement. Sections 2, 5, 6, 8 and 9
shall survive expiration or termination of this Agreement for any reason.
11. OWNERSHIP. As between the parties, the Software and all worldwide intellectual
property rights and proprietary rights relating thereto or embodied therein,
are the exclusive property of Stream.io and its suppliers. Stream.io and its
suppliers reserve all rights in and to the Software not expressly granted to
Customer in this Agreement, and no other licenses or rights are granted by
implication, estoppel or otherwise.
12. WARRANTY DISCLAIMER. USE OF THIS SOFTWARE IS ENTIRELY AT YOURS AND CUSTOMERS
OWN RISK. THE SOFTWARE IS PROVIDED “AS IS” WITHOUT ANY WARRANTY OF ANY KIND
WHATSOEVER. STREAM.IO DOES NOT MAKE, AND HEREBY DISCLAIMS, ANY WARRANTY OF ANY
KIND, WHETHER EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING WITHOUT
LIMITATION, THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE, TITLE, NON-INFRINGEMENT OF THIRD-PARTY RIGHTS, RESULTS, EFFORTS,
QUALITY OR QUIET ENJOYMENT. STREAM.IO DOES NOT WARRANT THAT THE SOFTWARE IS
ERROR-FREE, WILL FUNCTION WITHOUT INTERRUPTION, WILL MEET ANY SPECIFIC NEED
THAT CUSTOMER HAS, THAT ALL DEFECTS WILL BE CORRECTED OR THAT IT IS
SUFFICIENTLY DOCUMENTED TO BE USABLE BY CUSTOMER. TO THE EXTENT THAT STREAM.IO
MAY NOT DISCLAIM ANY WARRANTY AS A MATTER OF APPLICABLE LAW, THE SCOPE AND
DURATION OF SUCH WARRANTY WILL BE THE MINIMUM PERMITTED UNDER SUCH LAW.
CUSTOMER ACKNOWLEDGES THAT IT HAS RELIED ON NO WARRANTIES OTHER THAN THE
EXPRESS WARRANTIES IN THIS AGREEMENT.
13. LIMITATION OF LIABILITY. TO THE FULLEST EXTENT PERMISSIBLE BY LAW, STREAM.IOS
TOTAL LIABILITY FOR ALL DAMAGES ARISING OUT OF OR RELATED TO THE SOFTWARE OR
THIS AGREEMENT, WHETHER IN CONTRACT, TORT (INCLUDING NEGLIGENCE) OR OTHERWISE,
SHALL NOT EXCEED $100. IN NO EVENT WILL STREAM.IO BE LIABLE FOR ANY INDIRECT,
CONSEQUENTIAL, EXEMPLARY, PUNITIVE, SPECIAL OR INCIDENTAL DAMAGES OF ANY KIND
WHATSOEVER, INCLUDING ANY LOST DATA AND LOST PROFITS, ARISING FROM OR RELATING
TO THE SOFTWARE EVEN IF STREAM.IO HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES. CUSTOMER ACKNOWLEDGES THAT THIS PROVISION REFLECTS THE AGREED UPON
ALLOCATION OF RISK FOR THIS AGREEMENT AND THAT STREAM.IO WOULD NOT ENTER INTO
THIS AGREEMENT WITHOUT THESE LIMITATIONS ON ITS LIABILITY.
14. General. Customer may not assign or transfer this Agreement, by operation of
law or otherwise, or any of its rights under this Agreement (including the
license rights granted to Customer) to any third party without Stream.ios
prior written consent, which consent will not be unreasonably withheld or
delayed. Stream.io may assign this Agreement, without consent, including, but
limited to, affiliate or any successor to all or substantially all its business
or assets to which this Agreement relates, whether by merger, sale of assets,
sale of stock, reorganization or otherwise. Any attempted assignment or
transfer in violation of the foregoing will be null and void. Stream.io shall
not be liable hereunder by reason of any failure or delay in the performance of
its obligations hereunder for any cause which is beyond the reasonable control.
All notices, consents, and approvals under this Agreement must be delivered in
writing by courier, by electronic mail, or by certified or registered mail,
(postage prepaid and return receipt requested) to the other party at the
address set forth in the customer agreement between Stream.io and Customer and
will be effective upon receipt or when delivery is refused. This Agreement will
be governed by and interpreted in accordance with the laws of the State of
Colorado, without reference to its choice of laws rules. The United Nations
Convention on Contracts for the International Sale of Goods does not apply to
this Agreement. Any action or proceeding arising from or relating to this
Agreement shall be brought in a federal or state court in Denver, Colorado, and
each party irrevocably submits to the jurisdiction and venue of any such court
in any such action or proceeding. All waivers must be in writing. Any waiver or
failure to enforce any provision of this Agreement on one occasion will not be
deemed a waiver of any other provision or of such provision on any other
occasion. If any provision of this Agreement is unenforceable, such provision
will be changed and interpreted to accomplish the objectives of such provision
to the greatest extent possible under applicable law and the remaining
provisions will continue in full force and effect. Customer shall not violate
any applicable law, rule or regulation, including those regarding the export of
technical data. The headings of Sections of this Agreement are for convenience
and are not to be used in interpreting this Agreement. As used in this
Agreement, the word “including” means “including but not limited to.” This
Agreement (including all exhibits and attachments) constitutes the entire
agreement between the parties regarding the subject hereof and supersedes all
prior or contemporaneous agreements, understandings and communication, whether
written or oral. This Agreement may be amended only by a written document
signed by both parties. The terms of any purchase order or similar document
submitted by Customer to Stream.io will have no effect.
@@ -0,0 +1,115 @@
# Official Localizations for [Stream Chat Flutter](https://getstream.io/chat/sdk/flutter/) library.
> The Official localizations for Stream Chat Flutter, a service for
> building chat applications.
[![Pub](https://img.shields.io/pub/v/stream_chat_localizations.svg)](https://pub.dartlang.org/packages/stream_chat_localizations)
![](https://img.shields.io/badge/platform-flutter%20%7C%20flutter%20web-ff69b4.svg?style=flat-square)
![CI](https://github.com/GetStream/stream-chat-flutter/workflows/stream_flutter_workflow/badge.svg?branch=master)
**Quick Links**
- [Register](https://getstream.io/chat/trial/) to get an API key for Stream Chat
- [Flutter Chat Tutorial](https://getstream.io/chat/flutter/tutorial/)
- [Chat UI Kit](https://getstream.io/chat/ui-kit/)
This package provides localized strings for the stream chat widgets for many languages.
### Changelog
Check out the [changelog on pub.dev](https://pub.dev/packages/stream_chat_localizations/changelog) to see the latest changes in the package.
## Supported languages
At the moment we support the following languages:
- [English](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localizations/StreamChatLocalizationsEn-class.html)
- [Hindi](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localizations/StreamChatLocalizationsHi-class.html)
- [Italian](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localizations/StreamChatLocalizationsIt-class.html)
- [French](https://pub.dev/documentation/stream_chat_localizations/latest/stream_chat_localizations/StreamChatLocalizationsFr-class.html)
More languages will be added in the future. Feel free to [contribute](https://github.com/GetStream/stream-chat-flutter/blob/master/CONTRIBUTING.md) to add more languages.
## Add dependency
Add this to your package's pubspec.yaml file, use the latest version [![Pub](https://img.shields.io/pub/v/stream_chat_localizations.svg)](https://pub.dartlang.org/packages/stream_chat_localizations)
```yaml
dependencies:
stream_chat_localizations: ^latest_version
```
You should then run `flutter packages get`
### Usage
```dart
import 'package:flutter/material.dart';
import 'package:stream_chat_localizations/stream_chat_localizations.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
// Add all the supported locales
supportedLocales: const [
Locale('en'),
Locale('hi'),
Locale('fr'),
Locale('it'),
],
// Add GlobalStreamChatLocalizations.delegates
localizationsDelegates: GlobalStreamChatLocalizations.delegates,
builder: (context, widget) => StreamChat(
client: client,
child: widget,
),
home: StreamChannel(
channel: channel,
child: const ChannelPage(),
),
);
}
}
```
### Adding a new language
To add a new language, you need to create a new class extending `GlobalStreamChatLocalizations` and create a delegate for it adding it to the `delegates` array.
Checkout [this example](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/example/lib/add_new_lang.dart) to see how to add a new language.
### Override exisiting languages
To override an existing language, you need to create a new class extending that particular language class and create a delegate for it adding it to the `delegates` array.
Checkout [this example](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/example/lib/override_lang.dart) to see how to override an existing language.
### ⚠️ Note on **iOS**
For translation to work on **iOS** you need to add supported locales to
`ios/Runner/Info.plist` as described [here](https://flutter.dev/docs/development/accessibility-and-localization/internationalization#specifying-supportedlocales).
Example:
```xml
<key>CFBundleLocalizations</key>
<array>
<string>en</string>
<string>nb</string>
<string>fr</string>
<string>it</string>
</array>
```
## Contributing
We welcome code changes that improve this library or fix a problem,
please make sure to follow all best practices and add tests if applicable before submitting a Pull Request on Github.
We are pleased to merge your code into the official repository.
Make sure to sign our [Contributor License Agreement (CLA)](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform) first.
See our license file for more details.
@@ -0,0 +1,41 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
# Web related
lib/generated_plugin_registrant.dart
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 78910062997c3a836feee883712c241a5fd22983
channel: stable
project_type: app
@@ -0,0 +1,2 @@
# Stream Chat Persistence Example
Please see `lib/` for example code.
@@ -0,0 +1,11 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
key.properties
@@ -0,0 +1,64 @@
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 30
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
lintOptions {
disable 'InvalidPackage'
checkReleaseBuilds false
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.example"
minSdkVersion 21
targetSdkVersion 30
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.example">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
@@ -0,0 +1,47 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.example">
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<application
android:name="io.flutter.app.FlutterApplication"
android:label="example"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<!-- Displays an Android View that continues showing the launch screen
Drawable until Flutter paints its first frame, then this splash
screen fades out. A splash screen is useful to avoid any visual
gap between the end of Android's launch screen and the painting of
Flutter's first frame. -->
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
@@ -0,0 +1,6 @@
package com.example.example
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">@android:color/white</item>
</style>
</resources>
@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.example">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
@@ -0,0 +1,31 @@
buildscript {
ext.kotlin_version = '1.5.20'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.2.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
jcenter()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}
@@ -0,0 +1,4 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
android.enableR8=true
@@ -0,0 +1,6 @@
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-all.zip
@@ -0,0 +1,11 @@
include ':app'
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
def properties = new Properties()
assert localPropertiesFile.exists()
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
@@ -0,0 +1,32 @@
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3
@@ -0,0 +1,563 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
0E9B23A7BA08E142FA5000CF /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D76F8024ABE1070895D659BA /* Pods_Runner.framework */; };
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
3F6A054EEDAF06BCF649C130 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
6FE1ECA061EBB001F6BCE8B7 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
D76F8024ABE1070895D659BA /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
EC2A45E9198C1011BED23834 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
0E9B23A7BA08E142FA5000CF /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
04AAB960E493BD92262BBF82 /* Frameworks */ = {
isa = PBXGroup;
children = (
D76F8024ABE1070895D659BA /* Pods_Runner.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
8559384DCD98ED6067CEF8CB /* Pods */ = {
isa = PBXGroup;
children = (
3F6A054EEDAF06BCF649C130 /* Pods-Runner.debug.xcconfig */,
EC2A45E9198C1011BED23834 /* Pods-Runner.release.xcconfig */,
6FE1ECA061EBB001F6BCE8B7 /* Pods-Runner.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
8559384DCD98ED6067CEF8CB /* Pods */,
04AAB960E493BD92262BBF82 /* Frameworks */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
C1EE41B94EADE099F7AF3A1C /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
BD38DACC9A0AD429D9ED9939 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1020;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
BD38DACC9A0AD429D9ED9939 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
C1EE41B94EADE099F7AF3A1C /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1020"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
@@ -0,0 +1,13 @@
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
@@ -0,0 +1,122 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "[email protected]",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

@@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "[email protected]",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Some files were not shown because too many files have changed in this diff Show More