chore: add fake event to show when activity ended
This commit is contained in:
parent
f578119352
commit
414c01b616
5 changed files with 206 additions and 190 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:scroll_to_index/scroll_to_index.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
|
|
@ -10,7 +11,9 @@ import 'package:fluffychat/pages/chat/events/message.dart';
|
|||
import 'package:fluffychat/pages/chat/seen_by_row.dart';
|
||||
import 'package:fluffychat/pages/chat/typing_indicators.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_plan_message.dart';
|
||||
import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
|
||||
import 'package:fluffychat/pangea/events/extensions/pangea_event_extension.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/utils/account_config.dart';
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/filtered_timeline_extension.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
|
|
@ -40,6 +43,30 @@ class ChatEventList extends StatelessWidget {
|
|||
final horizontalPadding = FluffyThemes.isColumnMode(context) ? 8.0 : 0.0;
|
||||
|
||||
final events = timeline.events.filterByVisibleInGui();
|
||||
// #Pangea
|
||||
if (timeline.room.activityPlan?.endAt != null &&
|
||||
timeline.room.activityPlan!.endAt!.isBefore(DateTime.now())) {
|
||||
final eventIndex = events.indexWhere(
|
||||
(e) => e.originServerTs.isBefore(
|
||||
timeline.room.activityPlan!.endAt!,
|
||||
),
|
||||
);
|
||||
|
||||
if (eventIndex != -1) {
|
||||
events.insert(
|
||||
eventIndex,
|
||||
Event(
|
||||
type: PangeaEventTypes.activityPlanEnd,
|
||||
eventId: timeline.room.client.generateUniqueTransactionId(),
|
||||
senderId: timeline.room.client.userID!,
|
||||
originServerTs: timeline.room.activityPlan!.endAt!,
|
||||
room: timeline.room,
|
||||
content: {},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
// Pangea#
|
||||
final animateInEventIndex = controller.animateInEventIndex;
|
||||
|
||||
// create a map of eventId --> index to greatly improve performance of
|
||||
|
|
|
|||
|
|
@ -134,6 +134,10 @@ class Message extends StatelessWidget {
|
|||
? ActivityStateEvent(event: event)
|
||||
: const SizedBox();
|
||||
}
|
||||
|
||||
if (event.type == PangeaEventTypes.activityPlanEnd) {
|
||||
return const ActivityFinishedEvent();
|
||||
}
|
||||
// Pangea#
|
||||
return StateMessage(event);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,11 +57,6 @@ class ActivityStateEventState extends State<ActivityStateEvent> {
|
|||
}
|
||||
}
|
||||
|
||||
bool get _activityIsOver {
|
||||
return activityPlan?.endAt != null &&
|
||||
DateTime.now().isAfter(activityPlan!.endAt!);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (activityPlan == null) {
|
||||
|
|
@ -83,186 +78,151 @@ class ActivityStateEventState extends State<ActivityStateEvent> {
|
|||
spacing: 12.0,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(_activityIsOver ? 24.0 : 16.0),
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.primaryContainer,
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
),
|
||||
child: AnimatedSize(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
child: _activityIsOver
|
||||
? Column(
|
||||
spacing: 12.0,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
L10n.of(context).activityEnded,
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.onPrimaryContainer,
|
||||
fontSize: 16.0,
|
||||
),
|
||||
),
|
||||
CachedNetworkImage(
|
||||
width: 120.0,
|
||||
imageUrl:
|
||||
"${AppConfig.assetsBaseURL}/${ActivityConstants.activityFinishedAsset}",
|
||||
fit: BoxFit.cover,
|
||||
placeholder: (context, url) => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
errorWidget: (context, url, error) =>
|
||||
const SizedBox(),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Text(
|
||||
activityPlan!.markdown,
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.onPrimaryContainer,
|
||||
fontSize: AppConfig.fontSizeFactor *
|
||||
AppConfig.messageFontSize,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
activityPlan!.markdown,
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.onPrimaryContainer,
|
||||
fontSize:
|
||||
AppConfig.fontSizeFactor * AppConfig.messageFontSize,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
AnimatedSize(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
child: _activityIsOver
|
||||
? const SizedBox()
|
||||
: IntrinsicHeight(
|
||||
child: Row(
|
||||
spacing: 12.0,
|
||||
child: IntrinsicHeight(
|
||||
child: Row(
|
||||
spacing: 12.0,
|
||||
children: [
|
||||
Container(
|
||||
height: imageWidth,
|
||||
width: imageWidth,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: activityPlan!.imageURL != null
|
||||
? activityPlan!.imageURL!.startsWith("mxc")
|
||||
? MxcImage(
|
||||
uri: Uri.parse(
|
||||
activityPlan!.imageURL!,
|
||||
),
|
||||
width: imageWidth,
|
||||
height: imageWidth,
|
||||
cacheKey: activityPlan!.bookmarkId,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: CachedNetworkImage(
|
||||
imageUrl: activityPlan!.imageURL!,
|
||||
fit: BoxFit.cover,
|
||||
placeholder: (context, url) => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
errorWidget: (
|
||||
context,
|
||||
url,
|
||||
error,
|
||||
) =>
|
||||
const SizedBox(),
|
||||
)
|
||||
: const SizedBox(),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
spacing: 9.0,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Container(
|
||||
height: imageWidth,
|
||||
width: imageWidth,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: activityPlan!.imageURL != null
|
||||
? activityPlan!.imageURL!.startsWith("mxc")
|
||||
? MxcImage(
|
||||
uri: Uri.parse(
|
||||
activityPlan!.imageURL!,
|
||||
),
|
||||
width: imageWidth,
|
||||
height: imageWidth,
|
||||
cacheKey: activityPlan!.bookmarkId,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: CachedNetworkImage(
|
||||
imageUrl: activityPlan!.imageURL!,
|
||||
fit: BoxFit.cover,
|
||||
placeholder: (context, url) =>
|
||||
const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
errorWidget: (
|
||||
context,
|
||||
url,
|
||||
error,
|
||||
) =>
|
||||
const SizedBox(),
|
||||
)
|
||||
: const SizedBox(),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
spacing: 9.0,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Expanded(
|
||||
child: SizedBox.expand(
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(20),
|
||||
),
|
||||
backgroundColor:
|
||||
theme.colorScheme.primaryContainer,
|
||||
foregroundColor: theme
|
||||
.colorScheme.onPrimaryContainer,
|
||||
),
|
||||
onPressed: () async {
|
||||
final Duration? duration =
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return ActivityDurationPopup(
|
||||
initialValue:
|
||||
activityPlan?.duration ??
|
||||
const Duration(days: 1),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (duration == null) return;
|
||||
|
||||
showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => widget.event.room
|
||||
.sendActivityPlan(
|
||||
activityPlan!.copyWith(
|
||||
endAt:
|
||||
DateTime.now().add(duration),
|
||||
duration: duration,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: CountDown(
|
||||
deadline: activityPlan!.endAt,
|
||||
iconSize: 20.0,
|
||||
textSize: 16.0,
|
||||
),
|
||||
),
|
||||
child: SizedBox.expand(
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
), // Optional spacing between buttons
|
||||
Expanded(
|
||||
child: SizedBox.expand(
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(20),
|
||||
),
|
||||
backgroundColor:
|
||||
theme.colorScheme.error,
|
||||
foregroundColor:
|
||||
theme.colorScheme.onPrimary,
|
||||
),
|
||||
onPressed: () {
|
||||
showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => widget.event.room
|
||||
.sendActivityPlan(
|
||||
activityPlan!.copyWith(
|
||||
endAt: DateTime.now(),
|
||||
duration: Duration.zero,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
L10n.of(context).endNow,
|
||||
style: const TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
backgroundColor:
|
||||
theme.colorScheme.primaryContainer,
|
||||
foregroundColor:
|
||||
theme.colorScheme.onPrimaryContainer,
|
||||
),
|
||||
onPressed: () async {
|
||||
final Duration? duration = await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return ActivityDurationPopup(
|
||||
initialValue: activityPlan?.duration ??
|
||||
const Duration(days: 1),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (duration == null) return;
|
||||
|
||||
showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () =>
|
||||
widget.event.room.sendActivityPlan(
|
||||
activityPlan!.copyWith(
|
||||
endAt: DateTime.now().add(duration),
|
||||
duration: duration,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: CountDown(
|
||||
deadline: activityPlan!.endAt,
|
||||
iconSize: 20.0,
|
||||
textSize: 16.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
), // Optional spacing between buttons
|
||||
Expanded(
|
||||
child: SizedBox.expand(
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
backgroundColor: theme.colorScheme.error,
|
||||
foregroundColor: theme.colorScheme.onPrimary,
|
||||
),
|
||||
onPressed: () {
|
||||
showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () =>
|
||||
widget.event.room.sendActivityPlan(
|
||||
activityPlan!.copyWith(
|
||||
endAt: DateTime.now(),
|
||||
duration: Duration.zero,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
L10n.of(context).endNow,
|
||||
style: const TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -270,3 +230,50 @@ class ActivityStateEventState extends State<ActivityStateEvent> {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ActivityFinishedEvent extends StatelessWidget {
|
||||
const ActivityFinishedEvent({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return Center(
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 400.0,
|
||||
),
|
||||
margin: const EdgeInsets.all(18.0),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.primaryContainer,
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
),
|
||||
child: Column(
|
||||
spacing: 12.0,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
L10n.of(context).activityEnded,
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.onPrimaryContainer,
|
||||
fontSize: 16.0,
|
||||
),
|
||||
),
|
||||
CachedNetworkImage(
|
||||
width: 120.0,
|
||||
imageUrl:
|
||||
"${AppConfig.assetsBaseURL}/${ActivityConstants.activityFinishedAsset}",
|
||||
fit: BoxFit.cover,
|
||||
placeholder: (context, url) => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
errorWidget: (context, url, error) => const SizedBox(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ class PangeaEventTypes {
|
|||
static const capacity = "pangea.capacity";
|
||||
|
||||
static const activityPlan = "pangea.activity_plan";
|
||||
static const activityPlanEnd = "pangea.activity.end";
|
||||
|
||||
static const userAge = "pangea.user_age";
|
||||
|
||||
|
|
|
|||
|
|
@ -5,33 +5,9 @@ import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
|
|||
import '../../config/app_config.dart';
|
||||
|
||||
extension VisibleInGuiExtension on List<Event> {
|
||||
List<Event> filterByVisibleInGui({String? exceptionEventId}) {
|
||||
final visibleEvents =
|
||||
where((e) => e.isVisibleInGui || e.eventId == exceptionEventId)
|
||||
.toList();
|
||||
|
||||
// Hide creation state events:
|
||||
if (visibleEvents.isNotEmpty &&
|
||||
visibleEvents.last.type == EventTypes.RoomCreate) {
|
||||
var i = visibleEvents.length - 2;
|
||||
while (i > 0) {
|
||||
final event = visibleEvents[i];
|
||||
if (!event.isState) break;
|
||||
if (event.type == EventTypes.Encryption) {
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
if (event.type == EventTypes.RoomMember &&
|
||||
event.roomMemberChangeType == RoomMemberChangeType.acceptInvite) {
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
visibleEvents.removeAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
return visibleEvents;
|
||||
}
|
||||
List<Event> filterByVisibleInGui({String? exceptionEventId}) => where(
|
||||
(event) => event.isVisibleInGui || event.eventId == exceptionEventId,
|
||||
).toList();
|
||||
}
|
||||
|
||||
extension IsStateExtension on Event {
|
||||
|
|
@ -89,6 +65,7 @@ extension IsStateExtension on Event {
|
|||
EventTypes.RoomTombstone,
|
||||
EventTypes.CallInvite,
|
||||
PangeaEventTypes.activityPlan,
|
||||
PangeaEventTypes.activityPlanEnd,
|
||||
};
|
||||
// Pangea#
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue