Skip to content

Commit

Permalink
Merge pull request #46 from Metatavu/feature-45-improve-realtime-comm…
Browse files Browse the repository at this point in the history
…unication

Feature 45 improve realtime communication
  • Loading branch information
villejuutila committed Apr 26, 2024
2 parents 3bb437d + 1078e24 commit fbd7556
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 40 deletions.
26 changes: 26 additions & 0 deletions lib/event_bus/event_bus.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,38 @@
import "package:event_bus/event_bus.dart";
import "package:noheva_visitor_ui/widgets/noheva_widgets.dart";
import "package:noheva_visitor_ui/mqtt/listeners/pages_listener.dart";
import "package:noheva_visitor_ui/screens/page_screen.dart";

/// This module contains the event bus instance and, at the moment, all the events that are used in the app.
final EventBus eventBus = EventBus();

/// Event that is triggered when the user clicks on a play button.
/// [NohevaVideo] then starts playing the video.
class PlayVideoEvent {}

/// Event that is triggered when the [NohevaVideo] wants to toggle the play button.
class HidePlayButtonEvent {
final bool hide;
HidePlayButtonEvent(this.hide);
}

/// Event that is triggered when [PagesListener] needs to know what exhibition page is currenty being disabled.
class RequestAchiveExhibitionPageIdEvent {}

/// Event that is sent by [PageScreen] as a response to [RequestAchiveExhibitionPageIdEvent].
class ActiveExhibitionPageIdEvent {
final String? pageId;
ActiveExhibitionPageIdEvent(this.pageId);
}

/// Event that is triggered when the software wants to load a new exhibition page.
///
/// e.g. when the device boots and it it attached to an exhibition and has content to be dispayed
class LoadExhibitionPageByIdEvent {
final String? pageId;
LoadExhibitionPageByIdEvent(this.pageId);
}

/// Event that is triggered when the software needs to navigate to the management screen.
class OpenManagementScreenEvent {}
9 changes: 3 additions & 6 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import "package:flutter/material.dart";
import "package:flutter/services.dart";
import "package:flutter_dotenv/flutter_dotenv.dart";
import "package:noheva_api/noheva_api.dart";
import "package:noheva_visitor_ui/event_bus/event_bus.dart";
import "package:noheva_visitor_ui/mqtt/mqtt_client.dart";
import "package:noheva_visitor_ui/screens/startup_screen.dart";
import "package:noheva_visitor_ui/theme/font_helper.dart";
Expand All @@ -23,14 +24,10 @@ late final String environment;
final apiFactory = ApiFactory();
late bool isDeviceApproved;
String? deviceId;
final StreamController<String?> pageStreamController =
StreamController.broadcast(sync: true);
final StreamController<bool?> managementStreamController =
StreamController.broadcast(sync: true);
final managementButtonTickCounter = TimedTickCounter(
ticksRequired: 10,
timeout: const Duration(seconds: 5),
onTicksReached: () => managementStreamController.sink.add(true),
onTicksReached: () => eventBus.fire(OpenManagementScreenEvent()),
);

Future<void> _setAndroidImmersiveMode() async {
Expand Down Expand Up @@ -160,7 +157,7 @@ void _addManagementButtonOverlay(BuildContext context) {
return Overlay.of(context).insert(
OverlayEntry(
builder: (context) => Positioned(
left: MediaQuery.of(context).size.width / 2 - 100,
left: MediaQuery.of(context).size.width / 3,
top: 0,
width: 200,
height: 100,
Expand Down
5 changes: 3 additions & 2 deletions lib/mqtt/listeners/attach_listener.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import "package:noheva_visitor_ui/database/dao/device_exhibition_detail_dao.dart
import "package:noheva_visitor_ui/database/dao/layout_dao.dart";
import "package:noheva_visitor_ui/database/dao/page_dao.dart";
import "package:noheva_visitor_ui/database/database.dart";
import "package:noheva_visitor_ui/event_bus/event_bus.dart";
import "package:noheva_visitor_ui/main.dart";
import "package:noheva_visitor_ui/mqtt/listeners/abstract_listener.dart";
import "package:noheva_visitor_ui/mqtt/mqtt_client.dart";
Expand Down Expand Up @@ -58,7 +59,7 @@ class AttachListener {
SimpleLogger().info("Successfully loaded pages!");
final firstPage =
await pageDao.findPageByOrderNumber(attachedMessage.exhibitionId!, 0);
pageStreamController.sink.add(firstPage?.id);
eventBus.fire(LoadExhibitionPageByIdEvent(firstPage?.id));
}

/// Callback function for handling detach messages
Expand Down Expand Up @@ -86,6 +87,6 @@ class AttachListener {
SimpleLogger().info("Deleted layouts!");
await deviceExhitionDetailDao.deleteDeviceExhibitionDetails();
SimpleLogger().info("Deleted exhibitions!");
pageStreamController.sink.add(null);
eventBus.fire(LoadExhibitionPageByIdEvent(null));
}
}
93 changes: 87 additions & 6 deletions lib/mqtt/listeners/pages_listener.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import "dart:async";

import "package:noheva_api/noheva_api.dart";
import "package:noheva_visitor_ui/database/dao/page_dao.dart";
import "package:noheva_visitor_ui/event_bus/event_bus.dart";
import "package:noheva_visitor_ui/main.dart";
import "package:noheva_visitor_ui/mqtt/listeners/abstract_listener.dart";
import "package:noheva_visitor_ui/utils/layout_controller.dart";
import "package:noheva_visitor_ui/utils/page_controller.dart";
import "package:simple_logger/simple_logger.dart";

/// MQTT listener for Exhibition Pages
Expand All @@ -8,32 +15,106 @@ class PagesListener extends AbstractListener {
String baseTopic = "${AbstractListener.BASE_TOPIC}/pages";

@override
void handleCreate(String message) {
void handleCreate(String message) async {
SimpleLogger().info("Handling page create message...");
final createMessage =
AbstractListener.decodeMessage<MqttExhibitionPageCreate>(
message, MqttExhibitionPageCreate);
SimpleLogger().info(createMessage);
// TODO: Implement logic for handling page create messages
final createdPageId = createMessage.id;
final exhibitionId = createMessage.exhibitionId;
if (createdPageId == null) {
SimpleLogger()
.info("Page ID is null, cannot handle page create message!");
return;
}
if (exhibitionId == null) {
SimpleLogger()
.info("Exhibition ID is null, cannot handle page create message!");
return;
}
final existingPage = await pageDao.findPage(createdPageId);
if (existingPage != null) {
SimpleLogger().info("Page already exists, ignoring...");
return;
}

await _loadData(exhibitionId);
}

@override
void handleDelete(String message) {
void handleDelete(String message) async {
SimpleLogger().info("Handling page delete message...");
final deleteMessage =
AbstractListener.decodeMessage<MqttExhibitionPageDelete>(
message, MqttExhibitionPageDelete);
SimpleLogger().info(deleteMessage);
// TODO: Implement logic for handling page delete messages
final deletedPageId = deleteMessage.id;
if (deletedPageId == null) {
SimpleLogger()
.info("Page ID is null, cannot handle page delete message!");
return;
}
final existingPage = await pageDao.findPage(deletedPageId);
if (existingPage == null) {
SimpleLogger().info("Page does not exist, ignoring...");
return;
}
await pageDao.deletePage(deletedPageId);
SimpleLogger().info("Deleted page: $deletedPageId");
}

@override
void handleUpdate(String message) {
void handleUpdate(String message) async {
SimpleLogger().info("Handling page update message...");
final updateMessage =
AbstractListener.decodeMessage<MqttExhibitionPageUpdate>(
message, MqttExhibitionPageUpdate);
SimpleLogger().info(updateMessage);
// TODO: Implement logic for handling page update messages
final updatedPageId = updateMessage.id;
final exhibitionId = updateMessage.exhibitionId;
if (updatedPageId == null) {
SimpleLogger()
.info("Page ID is null, cannot handle page update message!");
return;
}
if (exhibitionId == null) {
SimpleLogger()
.info("Exhibition ID is null, cannot handle page update message!");
return;
}
final existingPage = await pageDao.findPage(updatedPageId);
if (existingPage == null) {
SimpleLogger().info("Page does not exist, ignoring...");
return;
}

await _loadData(exhibitionId);
eventBus.fire(RequestAchiveExhibitionPageIdEvent());
final activePageId = await eventBus
.on<ActiveExhibitionPageIdEvent>()
.first
.then((value) => value.pageId);
if (activePageId == updatedPageId) {
SimpleLogger().info("Page is active, updating screen...");
eventBus.fire(LoadExhibitionPageByIdEvent(activePageId));
} else {
SimpleLogger().info("Page is not active, not updating!");
}
}

/// Loads layouts and pages for the this device
Future<void> _loadData(String exhibitionId) async {
SimpleLogger().info("Loading layouts...");
await LayoutController.loadLayouts(deviceId!);
SimpleLogger().info("Successfully loaded layouts!");
SimpleLogger().info("Loading pages...");
final deviceDataApi = await apiFactory.getDeviceDataApi();
final pages = (await deviceDataApi.listDeviceDataPages(
deviceId: deviceId!,
))
.data!
.toList();
await PageController.comparePages(exhibitionId, pages);
}
}
17 changes: 10 additions & 7 deletions lib/screens/default_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import "package:flutter_gen/gen_l10n/app_localizations.dart";
import "package:noheva_visitor_ui/database/dao/device_exhibition_detail_dao.dart";
import "package:noheva_visitor_ui/database/dao/key_dao.dart";
import "package:noheva_visitor_ui/database/dao/page_dao.dart";
import "package:noheva_visitor_ui/event_bus/event_bus.dart";
import "package:noheva_visitor_ui/main.dart";
import "package:noheva_visitor_ui/screens/page_screen.dart";
import "package:noheva_visitor_ui/utils/layout_controller.dart";
Expand All @@ -21,7 +22,8 @@ class DefaultScreen extends StatefulWidget {
/// Default Screen State
class _DefaultScreenState extends State<DefaultScreen> {
bool _isDeviceApproved = false;
late StreamSubscription<String?> _pageStreamSubscription;
late StreamSubscription<LoadExhibitionPageByIdEvent>
_loadExhibitionPageByIdEventSubscription;
Timer? _deviceApprovalTimer;

/// Navigates to [PageScreen] with [pageId]
Expand All @@ -37,9 +39,10 @@ class _DefaultScreenState extends State<DefaultScreen> {
/// Handles stream events
///
/// If event is not null, navigates to [PageScreen] with [event] as [pageId]
void _handleStreamEvent(dynamic event) {
if (event != null) {
_navigateToPageScreen(event);
void _handleStreamEvent(LoadExhibitionPageByIdEvent event) {
final eventPageId = event.pageId;
if (eventPageId != null) {
_navigateToPageScreen(eventPageId);
}
}

Expand Down Expand Up @@ -107,14 +110,14 @@ class _DefaultScreenState extends State<DefaultScreen> {
@override
void initState() {
super.initState();
_pageStreamSubscription =
pageStreamController.stream.listen(_handleStreamEvent);
_loadExhibitionPageByIdEventSubscription =
eventBus.on<LoadExhibitionPageByIdEvent>().listen(_handleStreamEvent);
_checkDeviceApproval();
}

@override
void dispose() {
_pageStreamSubscription.cancel();
_loadExhibitionPageByIdEventSubscription.cancel();
_deviceApprovalTimer?.cancel();
super.dispose();
}
Expand Down
25 changes: 12 additions & 13 deletions lib/screens/noheva_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import "package:flutter/services.dart";
import "package:noheva_api/noheva_api.dart";
import 'package:noheva_visitor_ui/actions/key_event_listener.dart';
import "package:noheva_visitor_ui/actions/page_action_provider_factory.dart";
import "package:noheva_visitor_ui/main.dart";
import "package:noheva_visitor_ui/event_bus/event_bus.dart";
import "package:noheva_visitor_ui/screens/management_screen.dart";

/// Abstract Noheva Screen
Expand All @@ -20,30 +20,29 @@ abstract class NohevaScreen extends StatefulWidget {
/// All screens states should extend this class.
/// Provides a stream subscription for navigating to management screen when required.
abstract class NohevaScreenState<T extends NohevaScreen> extends State<T> {
late StreamSubscription<bool?> _managementButtonStreamSubscription;
late StreamSubscription<OpenManagementScreenEvent>
_managementButtonEventSubscription;
final List<KeyEventListener> _keyDownListeners = [];
final List<KeyEventListener> _keyUpListeners = [];

@override
void initState() {
super.initState();
_managementButtonStreamSubscription =
managementStreamController.stream.listen((event) {
if (event != null) {
Navigator.push(
context,
MaterialPageRoute<NohevaScreen>(
builder: (_) => const ManagementScreen(),
),
);
}
_managementButtonEventSubscription =
eventBus.on<OpenManagementScreenEvent>().listen((_) {
Navigator.push(
context,
MaterialPageRoute<NohevaScreen>(
builder: (_) => const ManagementScreen(),
),
);
});
ServicesBinding.instance.keyboard.addHandler(_keyEventHandler);
}

@override
void dispose() {
_managementButtonStreamSubscription.cancel();
_managementButtonEventSubscription.cancel();
ServicesBinding.instance.keyboard.removeHandler(_keyEventHandler);
super.dispose();
}
Expand Down
24 changes: 18 additions & 6 deletions lib/screens/page_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import "package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart
import "package:noheva_api/noheva_api.dart";
import "package:noheva_visitor_ui/database/dao/layout_dao.dart";
import "package:noheva_visitor_ui/database/dao/page_dao.dart";
import "package:noheva_visitor_ui/main.dart";
import "package:noheva_visitor_ui/event_bus/event_bus.dart";
import "package:noheva_visitor_ui/screens/default_screen.dart";
import "package:noheva_visitor_ui/screens/noheva_screen.dart";
import "package:noheva_visitor_ui/utils/custom_widget_factory.dart";
Expand All @@ -31,7 +31,10 @@ class PageScreenState extends NohevaScreenState<PageScreen> {
final List<ExhibitionPageEventTrigger> _eventTriggers = [];
final List<ExhibitionPageTransition> _enterTransitions = [];
final List<ExhibitionPageTransition> _exitTransitions = [];
late StreamSubscription<String?> _streamSubscription;
late StreamSubscription<LoadExhibitionPageByIdEvent>
_loadExhibitionPageByIdEventSubscription;
late StreamSubscription<RequestAchiveExhibitionPageIdEvent>
_activeExhibitionPageIdEventStreamSubscription;

/// Loads page and its content by [pageId]
Future<void> _loadPage(String pageId) async {
Expand Down Expand Up @@ -64,9 +67,11 @@ class PageScreenState extends NohevaScreenState<PageScreen> {
void initState() {
super.initState();
_loadPage(widget.pageId);
_streamSubscription = pageStreamController.stream.listen((event) {
if (event != null) {
_loadPage(event);
_loadExhibitionPageByIdEventSubscription =
eventBus.on<LoadExhibitionPageByIdEvent>().listen((event) {
final eventPageId = event.pageId;
if (eventPageId != null) {
_loadPage(eventPageId);
} else {
Navigator.pushReplacement(
context,
Expand All @@ -76,11 +81,18 @@ class PageScreenState extends NohevaScreenState<PageScreen> {
);
}
});
_activeExhibitionPageIdEventStreamSubscription =
eventBus.on<RequestAchiveExhibitionPageIdEvent>().listen(
(_) => eventBus.fire(
ActiveExhibitionPageIdEvent(widget.pageId),
),
);
}

@override
void dispose() {
_streamSubscription.cancel();
_loadExhibitionPageByIdEventSubscription.cancel();
_activeExhibitionPageIdEventStreamSubscription.cancel();
super.dispose();
}

Expand Down

0 comments on commit fbd7556

Please sign in to comment.