Bot animations (#1262)
* onPopinvok * Randomly reset bot animation * Use user ID to identify bot * Keeps timer from acting on nonexistent widget * fix: remove setState call in bot face SVG build function --------- Co-authored-by: ggurdin <ggurdin@gmail.com>
This commit is contained in:
parent
6e7dc594f2
commit
d6d6875882
20 changed files with 161 additions and 71 deletions
|
|
@ -1,14 +1,12 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/utils/date_time_extension.dart';
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
import 'package:fluffychat/widgets/presence_builder.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class ChatAppBarTitle extends StatelessWidget {
|
||||
final ChatController controller;
|
||||
|
|
@ -38,6 +36,9 @@ class ChatAppBarTitle extends StatelessWidget {
|
|||
name: room.getLocalizedDisplayname(
|
||||
MatrixLocals(L10n.of(context)),
|
||||
),
|
||||
// #Pangea
|
||||
presenceUserId: room.directChatMatrixID,
|
||||
// Pangea#
|
||||
size: 32,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -458,6 +458,9 @@ class _ChatAccountPicker extends StatelessWidget {
|
|||
mxContent: snapshot.data?.avatarUrl,
|
||||
name: snapshot.data?.displayName ??
|
||||
client.userID!.localpart,
|
||||
// #Pangea
|
||||
presenceUserId: client.userID!,
|
||||
// Pangea#
|
||||
size: 20,
|
||||
),
|
||||
title: Text(snapshot.data?.displayName ?? client.userID!),
|
||||
|
|
@ -471,6 +474,9 @@ class _ChatAccountPicker extends StatelessWidget {
|
|||
mxContent: snapshot.data?.avatarUrl,
|
||||
name: snapshot.data?.displayName ??
|
||||
Matrix.of(context).client.userID!.localpart,
|
||||
// #Pangea
|
||||
presenceUserId: Matrix.of(context).client.userID!,
|
||||
// Pangea#
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/utils/date_time_extension.dart';
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
class RoomCreationStateEvent extends StatelessWidget {
|
||||
final Event event;
|
||||
|
|
@ -32,6 +30,9 @@ class RoomCreationStateEvent extends StatelessWidget {
|
|||
Avatar(
|
||||
mxContent: event.room.avatar,
|
||||
name: roomName,
|
||||
// #Pangea
|
||||
presenceUserId: event.room.directChatMatrixID,
|
||||
// Pangea#
|
||||
size: Avatar.defaultSize * 2,
|
||||
),
|
||||
Text(
|
||||
|
|
|
|||
|
|
@ -42,6 +42,9 @@ class SeenByRow extends StatelessWidget {
|
|||
(user) => Avatar(
|
||||
mxContent: user.avatarUrl,
|
||||
name: user.calcDisplayname(),
|
||||
// #Pangea
|
||||
presenceUserId: user.id,
|
||||
// Pangea#
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TypingIndicators extends StatelessWidget {
|
||||
final ChatController controller;
|
||||
|
|
@ -61,6 +60,9 @@ class TypingIndicators extends StatelessWidget {
|
|||
Avatar(
|
||||
size: avatarSize,
|
||||
mxContent: typingUsers.first.avatarUrl,
|
||||
// #Pangea
|
||||
presenceUserId: typingUsers.first.id,
|
||||
// Pangea#
|
||||
name: typingUsers.first.calcDisplayname(),
|
||||
),
|
||||
if (typingUsers.length == 2)
|
||||
|
|
@ -71,6 +73,11 @@ class TypingIndicators extends StatelessWidget {
|
|||
mxContent: typingUsers.length == 2
|
||||
? typingUsers.last.avatarUrl
|
||||
: null,
|
||||
// #Pangea
|
||||
presenceUserId: typingUsers.length == 2
|
||||
? typingUsers.last.id
|
||||
: null,
|
||||
// Pangea#
|
||||
name: typingUsers.length == 2
|
||||
? typingUsers.last.calcDisplayname()
|
||||
: '+${typingUsers.length - 1}',
|
||||
|
|
|
|||
|
|
@ -95,6 +95,9 @@ class ChatDetailsView extends StatelessWidget {
|
|||
child: Avatar(
|
||||
mxContent: room.avatar,
|
||||
name: displayname,
|
||||
// #Pangea
|
||||
presenceUserId: room.directChatMatrixID,
|
||||
// Pangea#
|
||||
size: Avatar.defaultSize * 2.5,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -732,6 +732,9 @@ class ChatListController extends State<ChatList>
|
|||
mxContent: room.avatar,
|
||||
size: Avatar.defaultSize / 2,
|
||||
name: displayname,
|
||||
// #Pangea
|
||||
presenceUserId: room.directChatMatrixID,
|
||||
// Pangea#
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
|
|
@ -753,6 +756,9 @@ class ChatListController extends State<ChatList>
|
|||
mxContent: space.avatar,
|
||||
size: Avatar.defaultSize / 2,
|
||||
name: space.getLocalizedDisplayname(),
|
||||
// #Pangea
|
||||
presenceUserId: space.directChatMatrixID,
|
||||
// Pangea#
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
|
|
|
|||
|
|
@ -150,6 +150,9 @@ class ChatListItem extends StatelessWidget {
|
|||
mxContent: space.avatar,
|
||||
size: Avatar.defaultSize * 0.75,
|
||||
name: space.getLocalizedDisplayname(),
|
||||
// #Pangea
|
||||
presenceUserId: space.directChatMatrixID,
|
||||
// Pangea#
|
||||
onTap: () => onLongPress?.call(context),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -112,6 +112,9 @@ class ChatListView extends StatelessWidget {
|
|||
icon: Avatar(
|
||||
mxContent: rootSpaces[i].avatar,
|
||||
name: displayname,
|
||||
// #Pangea
|
||||
presenceUserId: space.directChatMatrixID,
|
||||
// Pangea#
|
||||
size: 32,
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppConfig.borderRadius / 4,
|
||||
|
|
|
|||
|
|
@ -231,6 +231,7 @@ class ClientChooserButton extends StatelessWidget {
|
|||
name: snapshot.data?.displayName ??
|
||||
matrix.client.userID!.localpart,
|
||||
// #Pangea
|
||||
presenceUserId: matrix.client.userID!,
|
||||
// size: 32,
|
||||
size: 60,
|
||||
// Pangea#
|
||||
|
|
|
|||
|
|
@ -546,6 +546,9 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
leading: Avatar(
|
||||
mxContent: room?.avatar,
|
||||
name: displayname,
|
||||
// #Pangea
|
||||
presenceUserId: room?.directChatMatrixID,
|
||||
// Pangea#
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2),
|
||||
),
|
||||
title: Text(
|
||||
|
|
@ -713,6 +716,10 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
Avatar(
|
||||
mxContent: joinedParents[i].avatar,
|
||||
name: displayname,
|
||||
// #Pangea
|
||||
presenceUserId:
|
||||
joinedParents[i].directChatMatrixID,
|
||||
// Pangea#
|
||||
size: Avatar.defaultSize / 2,
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppConfig.borderRadius / 4,
|
||||
|
|
@ -809,6 +816,12 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
leading: Avatar(
|
||||
mxContent: item.avatarUrl,
|
||||
name: displayname,
|
||||
// #Pangea
|
||||
presenceUserId: Matrix.of(context)
|
||||
.client
|
||||
.getRoomById(item.roomId)
|
||||
?.directChatMatrixID,
|
||||
// Pangea#
|
||||
borderRadius: item.roomType == 'm.space'
|
||||
? BorderRadius.circular(
|
||||
AppConfig.borderRadius / 2,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pages/user_bottom_sheet/user_bottom_sheet.dart';
|
||||
|
|
@ -10,6 +6,8 @@ import 'package:fluffychat/utils/stream_extension.dart';
|
|||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
import 'package:fluffychat/widgets/hover_builder.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
class StatusMessageList extends StatelessWidget {
|
||||
final void Function() onStatusEdit;
|
||||
|
|
@ -155,6 +153,9 @@ class PresenceAvatar extends StatelessWidget {
|
|||
),
|
||||
child: Avatar(
|
||||
name: displayName,
|
||||
// #Pangea
|
||||
presenceUserId: profile?.userId,
|
||||
// Pangea#
|
||||
mxContent: profile?.avatarUrl,
|
||||
size: avatarSize - 6,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/utils/date_time_extension.dart';
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||
import 'package:fluffychat/utils/url_launcher.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
class ChatSearchMessageTab extends StatelessWidget {
|
||||
final String searchQuery;
|
||||
|
|
@ -137,6 +135,9 @@ class _MessageSearchResultListTile extends StatelessWidget {
|
|||
Avatar(
|
||||
mxContent: sender.avatarUrl,
|
||||
name: displayname,
|
||||
// #Pangea
|
||||
presenceUserId: sender.id,
|
||||
// Pangea#
|
||||
size: 16,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
|
|
|
|||
|
|
@ -58,6 +58,9 @@ class SettingsView extends StatelessWidget {
|
|||
Avatar(
|
||||
mxContent: profile?.avatarUrl,
|
||||
name: displayname,
|
||||
// #Pangea
|
||||
presenceUserId: profile?.userId,
|
||||
// Pangea#
|
||||
size: Avatar.defaultSize * 2.5,
|
||||
),
|
||||
if (profile != null)
|
||||
|
|
|
|||
|
|
@ -81,6 +81,9 @@ class SettingsIgnoreListView extends StatelessWidget {
|
|||
leading: Avatar(
|
||||
mxContent: s.data?.avatarUrl ?? Uri.parse(''),
|
||||
name: s.data?.displayName ?? client.ignoredUsers[i],
|
||||
// #Pangea
|
||||
presenceUserId: s.data?.userId,
|
||||
// Pangea#
|
||||
),
|
||||
title: Text(
|
||||
s.data?.displayName ?? client.ignoredUsers[i],
|
||||
|
|
|
|||
|
|
@ -118,6 +118,9 @@ class UserBottomSheetView extends StatelessWidget {
|
|||
Matrix.of(controller.widget.outerContext).client,
|
||||
mxContent: avatarUrl,
|
||||
name: displayname,
|
||||
// #Pangea
|
||||
presenceUserId: user?.id,
|
||||
// Pangea#
|
||||
size: Avatar.defaultSize * 2.5,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -121,6 +121,9 @@ class PangeaChatDetailsView extends StatelessWidget {
|
|||
child: Avatar(
|
||||
mxContent: room.avatar,
|
||||
name: displayname,
|
||||
// #Pangea
|
||||
presenceUserId: room.directChatMatrixID,
|
||||
// Pangea#
|
||||
size: Avatar.defaultSize * 2.5,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rive/rive.dart';
|
||||
|
||||
|
|
@ -22,11 +25,41 @@ class BotFace extends StatefulWidget {
|
|||
class BotFaceState extends State<BotFace> {
|
||||
Artboard? _artboard;
|
||||
StateMachineController? _controller;
|
||||
final Random _random = Random();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadRiveFile();
|
||||
_loadRiveFile().then((_) => _scheduleNextRun());
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(BotFace oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.expression != widget.expression) {
|
||||
_controller!.setInputValue(
|
||||
_controller!.stateMachine.inputs[0].id,
|
||||
mapExpressionToInput(widget.expression),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _scheduleNextRun() {
|
||||
final int nextInterval =
|
||||
_random.nextInt(21) + 20; // Random interval between 20-40 seconds
|
||||
|
||||
Future.delayed(Duration(seconds: nextInterval), () {
|
||||
if (mounted) {
|
||||
_loadRiveFile();
|
||||
_scheduleNextRun();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
double mapExpressionToInput(BotExpression expression) {
|
||||
|
|
@ -45,11 +78,12 @@ class BotFaceState extends State<BotFace> {
|
|||
}
|
||||
|
||||
Future<void> _loadRiveFile() async {
|
||||
final riveFile = await RiveFile.asset('assets/pangea/bot_faces/pangea_bot.riv');
|
||||
final riveFile =
|
||||
await RiveFile.asset('assets/pangea/bot_faces/pangea_bot.riv');
|
||||
|
||||
final artboard = riveFile.mainArtboard;
|
||||
_controller = StateMachineController
|
||||
.fromArtboard(artboard, 'BotIconStateMachine');
|
||||
_controller =
|
||||
StateMachineController.fromArtboard(artboard, 'BotIconStateMachine');
|
||||
|
||||
if (_controller != null) {
|
||||
artboard.addController(_controller!);
|
||||
|
|
@ -59,40 +93,24 @@ class BotFaceState extends State<BotFace> {
|
|||
);
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_artboard = artboard;
|
||||
});
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_artboard = artboard;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
return SizedBox(
|
||||
width: widget.width,
|
||||
height: widget.width,
|
||||
child: _artboard != null
|
||||
? Rive(
|
||||
artboard: _artboard!,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: Container(),
|
||||
? Rive(
|
||||
artboard: _artboard!,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: Container(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(BotFace oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.expression != widget.expression) {
|
||||
_controller!.setInputValue(
|
||||
_controller!.stateMachine.inputs[0].id,
|
||||
mapExpressionToInput(widget.expression),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// extension ParseToString on BotExpressions {
|
||||
// String toShortString() {
|
||||
// return toString().split('.').last;
|
||||
// }
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import 'package:fluffychat/pangea/utils/bot_name.dart';
|
||||
import 'package:fluffychat/pangea/widgets/common/bot_face_svg.dart';
|
||||
import 'package:fluffychat/utils/string_color.dart';
|
||||
import 'package:fluffychat/widgets/mxc_image.dart';
|
||||
import 'package:fluffychat/widgets/presence_builder.dart';
|
||||
|
|
@ -76,24 +78,30 @@ class Avatar extends StatelessWidget {
|
|||
side: border ?? BorderSide.none,
|
||||
),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: noPic
|
||||
? textWidget
|
||||
: MxcImage(
|
||||
client: client,
|
||||
key: ValueKey(mxContent.toString()),
|
||||
cacheKey: '${mxContent}_$size',
|
||||
uri: mxContent,
|
||||
fit: BoxFit.cover,
|
||||
width: size,
|
||||
height: size,
|
||||
placeholder: (_) => Center(
|
||||
child: Icon(
|
||||
Icons.person_2,
|
||||
color: theme.colorScheme.tertiary,
|
||||
size: size / 1.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
child:
|
||||
// #Pangea
|
||||
presenceUserId == BotName.byEnvironment
|
||||
? BotFace(width: size, expression: BotExpression.idle)
|
||||
:
|
||||
// Pangea#
|
||||
noPic
|
||||
? textWidget
|
||||
: MxcImage(
|
||||
client: client,
|
||||
key: ValueKey(mxContent.toString()),
|
||||
cacheKey: '${mxContent}_$size',
|
||||
uri: mxContent,
|
||||
fit: BoxFit.cover,
|
||||
width: size,
|
||||
height: size,
|
||||
placeholder: (_) => Center(
|
||||
child: Icon(
|
||||
Icons.person_2,
|
||||
color: theme.colorScheme.tertiary,
|
||||
size: size / 1.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (presenceUserId != null)
|
||||
|
|
|
|||
|
|
@ -89,6 +89,9 @@ class ProfileBottomSheet extends StatelessWidget {
|
|||
child: Avatar(
|
||||
mxContent: profile?.avatarUrl,
|
||||
name: profile?.displayName ?? userId,
|
||||
// #Pangea
|
||||
presenceUserId: userId,
|
||||
// Pangea#
|
||||
size: Avatar.defaultSize * 3,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue