Skip to content

Commit

Permalink
feat: add events for raiding (#370)
Browse files Browse the repository at this point in the history
  • Loading branch information
kevmo314 authored Apr 26, 2022
1 parent 47d7d6f commit 7375a33
Show file tree
Hide file tree
Showing 14 changed files with 322 additions and 10 deletions.
18 changes: 13 additions & 5 deletions lib/components/chat_history/decorated_event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,21 @@ class DecoratedEventWidget extends StatelessWidget {
final Widget child;
final ImageProvider? avatar;
final IconData? icon;
final BoxDecoration decoration;

const DecoratedEventWidget._(
{Key? key, required this.child, this.avatar, this.icon})
{Key? key,
required this.child,
this.avatar,
this.icon,
this.decoration = const BoxDecoration()})
: super(key: key);

@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Theme.of(context).highlightColor,
decoration: decoration.copyWith(
color: decoration.color ?? Theme.of(context).highlightColor,
border: Border(
left: BorderSide(
width: 4,
Expand Down Expand Up @@ -65,8 +70,11 @@ class DecoratedEventWidget extends StatelessWidget {
}

const DecoratedEventWidget.avatar(
{Key? key, required Widget child, required ImageProvider avatar})
: this._(key: key, child: child, avatar: avatar);
{Key? key,
required Widget child,
required ImageProvider avatar,
BoxDecoration decoration = const BoxDecoration()})
: this._(key: key, child: child, avatar: avatar, decoration: decoration);

const DecoratedEventWidget.icon(
{Key? key, required Widget child, required IconData icon})
Expand Down
8 changes: 8 additions & 0 deletions lib/components/chat_history/message.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'package:rtchat/components/chat_history/twitch/message.dart';
import 'package:rtchat/components/chat_history/twitch/poll_event.dart';
import 'package:rtchat/components/chat_history/twitch/prediction_event.dart';
import 'package:rtchat/components/chat_history/twitch/raid_event.dart';
import 'package:rtchat/components/chat_history/twitch/raiding_event.dart';
import 'package:rtchat/components/chat_history/twitch/subscription_event.dart';
import 'package:rtchat/models/adapters/actions.dart';
import 'package:rtchat/models/channels.dart';
Expand All @@ -23,6 +24,7 @@ import 'package:rtchat/models/messages/twitch/eventsub_configuration.dart';
import 'package:rtchat/models/messages/twitch/hype_train_event.dart';
import 'package:rtchat/models/messages/twitch/message.dart';
import 'package:rtchat/models/messages/twitch/prediction_event.dart';
import 'package:rtchat/models/messages/twitch/raiding_event.dart';
import 'package:rtchat/models/messages/twitch/subscription_event.dart';
import 'package:rtchat/models/messages/twitch/subscription_gift_event.dart';
import 'package:rtchat/models/messages/twitch/subscription_message_event.dart';
Expand Down Expand Up @@ -220,6 +222,12 @@ class ChatHistoryMessage extends StatelessWidget {
builder: (_, config, __) =>
config.showEvent ? TwitchHostEventWidget(m) : Container(),
);
} else if (m is TwitchRaidingEventModel) {
return Selector<EventSubConfigurationModel, RaidingEventConfig>(
selector: (_, model) => model.raidingEventConfig,
builder: (_, config, __) =>
config.showEvent ? TwitchRaidingEventWidget(m) : Container(),
);
} else {
throw AssertionError("invalid message type");
}
Expand Down
2 changes: 1 addition & 1 deletion lib/components/chat_history/twitch/prediction_event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class TwitchPredictionEventWidget extends StatelessWidget {

@override
Widget build(BuildContext context) {
return model.status != "cancelled"
return model.status != "canceled"
? DecoratedEventWidget(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
Expand Down
92 changes: 92 additions & 0 deletions lib/components/chat_history/twitch/raiding_event.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:rtchat/components/chat_history/decorated_event.dart';
import 'package:rtchat/models/channels.dart';
import 'package:rtchat/models/messages/twitch/raiding_event.dart';
import 'package:rtchat/models/user.dart';

class TwitchRaidingEventWidget extends StatelessWidget {
final TwitchRaidingEventModel model;

const TwitchRaidingEventWidget(this.model, {Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
if (!model.isComplete) {
return StreamBuilder<int>(
stream: Stream.periodic(const Duration(milliseconds: 500), (x) => x),
builder: (context, snapshot) {
final flash = snapshot.data == null || snapshot.data! % 2 == 0;
final expiration = model.timestamp.add(model.duration);
final remaining = expiration.difference(DateTime.now());
return Stack(children: [
Positioned.fill(
child: AnimatedContainer(
duration: const Duration(milliseconds: 500),
color: flash
? Theme.of(context).highlightColor
: Theme.of(context).colorScheme.secondary)),
DecoratedEventWidget.avatar(
decoration: const BoxDecoration(color: Colors.transparent),
avatar: NetworkImage(model.targetUser.profilePictureUrl),
child: Row(children: [
Text.rich(TextSpan(
children: [
const TextSpan(text: "Raiding "),
TextSpan(
text: model.targetUser.displayName,
style: Theme.of(context).textTheme.subtitle2),
const TextSpan(text: "."),
],
)),
const Spacer(),
Text.rich(TextSpan(
text: remaining.isNegative
? "0s"
: "${remaining.inSeconds}s",
style: Theme.of(context).textTheme.subtitle2))
])),
]);
});
} else if (model.isSuccessful) {
return GestureDetector(
child: DecoratedEventWidget.avatar(
avatar: NetworkImage(model.targetUser.profilePictureUrl),
child: Row(children: [
Text.rich(TextSpan(
children: [
const TextSpan(text: "Raided "),
TextSpan(
text: model.targetUser.displayName,
style: Theme.of(context).textTheme.subtitle2),
const TextSpan(text: "."),
],
)),
const Spacer(),
Text.rich(TextSpan(
text: "Join",
style: Theme.of(context).textTheme.subtitle2?.copyWith(
color: Theme.of(context)
.buttonTheme
.colorScheme
?.primary))),
])),
onTap: () {
final userModel = Provider.of<UserModel>(context, listen: false);
userModel.activeChannel = model.targetUser.asChannel;
});
} else {
return DecoratedEventWidget.avatar(
avatar: NetworkImage(model.targetUser.profilePictureUrl),
child: Text.rich(TextSpan(
children: [
const TextSpan(text: "Raid to "),
TextSpan(
text: model.targetUser.displayName,
style: Theme.of(context).textTheme.subtitle2),
const TextSpan(text: " canceled."),
],
)));
}
}
}
2 changes: 2 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import 'package:rtchat/screens/settings/events/hypetrain.dart';
import 'package:rtchat/screens/settings/events/poll.dart';
import 'package:rtchat/screens/settings/events/prediction.dart';
import 'package:rtchat/screens/settings/events/raid.dart';
import 'package:rtchat/screens/settings/events/raiding.dart';
import 'package:rtchat/screens/settings/events/subscription.dart';
import 'package:rtchat/screens/settings/quick_links.dart';
import 'package:rtchat/screens/settings/settings.dart';
Expand Down Expand Up @@ -333,6 +334,7 @@ class _AppState extends State<App> {
const HypetrainEventScreen(),
'/settings/events/prediction': (context) =>
const PredictionEventScreen(),
'/settings/events/raiding': (context) => const RaidingEventScreen(),
},
),
);
Expand Down
17 changes: 17 additions & 0 deletions lib/models/adapters/messages.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'package:rtchat/models/messages/twitch/emote.dart';
import 'package:rtchat/models/messages/twitch/event.dart';
import 'package:rtchat/models/messages/twitch/hype_train_event.dart';
import 'package:rtchat/models/messages/twitch/message.dart';
import 'package:rtchat/models/messages/twitch/raiding_event.dart';
import 'package:rtchat/models/messages/twitch/subscription_event.dart';
import 'package:rtchat/models/messages/twitch/subscription_gift_event.dart';
import 'package:rtchat/models/messages/twitch/subscription_message_event.dart';
Expand Down Expand Up @@ -221,6 +222,22 @@ DeltaEvent? _toDeltaEvent(
isOnline: data['type'] == "stream.online",
timestamp: data['timestamp'].toDate());
return AppendDeltaEvent(model);
case "raid_update_v2":
return AppendDeltaEvent(TwitchRaidingEventModel.fromDocumentData(data));
case "raid_cancel_v2":
return UpdateDeltaEvent("raiding.${data['raid']['id']}", (message) {
if (message is! TwitchRaidingEventModel) {
return message;
}
return message.withCancel();
});
case "raid_go_v2":
return UpdateDeltaEvent("raiding.${data['raid']['id']}", (message) {
if (message is! TwitchRaidingEventModel) {
return message;
}
return message.withSuccessful();
});
}
return null;
}
Expand Down
2 changes: 1 addition & 1 deletion lib/models/layout.dart
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class LayoutModel extends ChangeNotifier {
double get panelHeight => _panelHeight;

double get panelWidth => _panelWidth;

set panelWidth(double panelWidth) {
_panelWidth = panelWidth;
notifyListeners();
Expand Down
19 changes: 19 additions & 0 deletions lib/models/messages/twitch/eventsub_configuration.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:rtchat/models/messages/twitch/raiding_event.dart';

class FollowEventConfig {
bool showEvent;
Expand Down Expand Up @@ -174,6 +175,9 @@ class EventSubConfigurationModel extends ChangeNotifier {
HypetrainEventConfig(true, const Duration(seconds: 6));
PredictionEventConfig predictionEventConfig =
PredictionEventConfig(true, const Duration(seconds: 6));
RaidingEventConfig
raidingEventConfig = // 90 seconds for raid + 10 for join prompt.
RaidingEventConfig(true, const Duration(seconds: 100));
// other configs
// final HypeTrainEventConfig;

Expand Down Expand Up @@ -277,6 +281,16 @@ class EventSubConfigurationModel extends ChangeNotifier {
notifyListeners();
}

setRaidingEventDuration(Duration duration) {
raidingEventConfig.eventDuration = duration;
notifyListeners();
}

setRaidingEventShowable(bool value) {
raidingEventConfig.showEvent = value;
notifyListeners();
}

EventSubConfigurationModel.fromJson(Map<String, dynamic> json) {
if (json['followEventConfig'] != null) {
followEventConfig = FollowEventConfig.fromJson(json['followEventConfig']);
Expand Down Expand Up @@ -310,6 +324,10 @@ class EventSubConfigurationModel extends ChangeNotifier {
predictionEventConfig =
PredictionEventConfig.fromJson(json['predictionEventConfig']);
}
if (json['raidingEventConfig'] != null) {
raidingEventConfig =
RaidingEventConfig.fromJson(json['raidingEventConfig']);
}
}

Map<String, dynamic> toJson() => {
Expand All @@ -323,5 +341,6 @@ class EventSubConfigurationModel extends ChangeNotifier {
"hostEventConfig": hostEventConfig.toJson(),
"hypetrainEventConfig": hypetrainEventConfig.toJson(),
"predictionEventConfig": predictionEventConfig.toJson(),
"raidingEventConfig": raidingEventConfig.toJson(),
};
}
83 changes: 83 additions & 0 deletions lib/models/messages/twitch/raiding_event.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import 'package:flutter/cupertino.dart';
import 'package:rtchat/models/messages/message.dart';
import 'package:rtchat/models/messages/twitch/user.dart';

class RaidingEventConfig {
bool showEvent;
Duration eventDuration;

RaidingEventConfig(this.showEvent, this.eventDuration);

RaidingEventConfig.fromJson(Map<String, dynamic> json)
: showEvent = json['showEvent'],
eventDuration = Duration(seconds: json['eventDuration'].toInt());

Map<String, dynamic> toJson() => {
"showEvent": showEvent,
"eventDuration": eventDuration.inSeconds.toInt(),
};
}

class TwitchRaidingEventModel extends MessageModel {
// we don't populate viewer count because it's not accurate anyways.
final Duration duration;
final TwitchUserModel targetUser;
final bool isComplete;
final bool isSuccessful;

const TwitchRaidingEventModel(
{required DateTime timestamp,
required String messageId,
required this.duration,
required this.targetUser,
this.isComplete = false,
this.isSuccessful = false})
: super(messageId: messageId, timestamp: timestamp);

static TwitchRaidingEventModel fromDocumentData(Map<String, dynamic> data) {
return TwitchRaidingEventModel(
timestamp: data['timestamp'].toDate(),
messageId: "raiding.${data['raid']['id']}",
duration: Duration(seconds: data['raid']['force_raid_now_seconds']),
targetUser: TwitchUserModel(
userId: data['raid']['target_id'],
displayName: data['raid']['target_display_name'],
login: data['raid']['target_login'],
),
);
}

TwitchRaidingEventModel withSuccessful() {
return TwitchRaidingEventModel(
timestamp: timestamp,
messageId: messageId,
duration: duration,
targetUser: targetUser,
isComplete: true,
isSuccessful: true,
);
}

TwitchRaidingEventModel withCancel() {
return TwitchRaidingEventModel(
timestamp: timestamp,
messageId: messageId,
duration: duration,
targetUser: targetUser,
isComplete: true,
isSuccessful: false,
);
}

@override
bool operator ==(Object other) =>
other is TwitchRaidingEventModel &&
other.duration == duration &&
other.targetUser == targetUser &&
other.isComplete == isComplete &&
other.isSuccessful == isSuccessful;

@override
int get hashCode =>
hashValues(duration, targetUser, isComplete, isSuccessful);
}
3 changes: 3 additions & 0 deletions lib/models/messages/twitch/user.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:flutter/painting.dart';
import 'package:rtchat/models/channels.dart';

const colors = [
Color(0xFFFF0000),
Expand Down Expand Up @@ -72,6 +73,8 @@ class TwitchUserModel {
return "https://us-central1-rtchat-47692.cloudfunctions.net/getProfilePicture?provider=twitch&channelId=$userId";
}

Channel get asChannel => Channel("twitch", userId, displayName ?? login);

TwitchUserModel.fromJson(Map<String, dynamic> json)
: userId = json["userId"],
displayName = json["displayName"],
Expand Down
Loading

0 comments on commit 7375a33

Please sign in to comment.