InfiniTime.git

commit cb9e8815d8bc6ce71fd8e97f3e3dae402658ce1f

Author: JF002 <JF002@users.noreply.github.com>

Merge pull request #108 from JF002/notification-ui

Improved notification UI

 src/CMakeLists.txt | 8 
 src/components/ble/AlertNotificationClient.cpp | 28 +-
 src/components/ble/AlertNotificationService.cpp | 35 +--
 src/components/ble/AlertNotificationService.h | 4 
 src/components/ble/ImmediateAlertService.cpp | 8 
 src/components/ble/NotificationManager.cpp | 85 +++++++-
 src/components/ble/NotificationManager.h | 20 +
 src/displayapp/Apps.h | 2 
 src/displayapp/DisplayApp.cpp | 14 +
 src/displayapp/screens/ApplicationList.cpp | 6 
 src/displayapp/screens/Clock.cpp | 20 +
 src/displayapp/screens/Clock.h | 12 
 src/displayapp/screens/NotificationIcon.cpp | 8 
 src/displayapp/screens/NotificationIcon.h | 12 +
 src/displayapp/screens/Notifications.cpp | 174 +++++++++++++++++++
 src/displayapp/screens/Notifications.h | 61 ++++++


diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index af0b110e53dff606b87b445e127e148d8b20a227..4d691ede93b31eb38f32a1cfff2f58366859ca6b 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -289,6 +289,8 @@         libs/lvgl/src/lv_objx/lv_slider.h
         libs/lvgl/src/lv_objx/lv_slider.c
         libs/lvgl/src/lv_objx/lv_ddlist.c
         libs/lvgl/src/lv_objx/lv_ddlist.h
+        libs/lvgl/src/lv_objx/lv_line.c
+        libs/lvgl/src/lv_objx/lv_line.h
         )
 
 list(APPEND IMAGE_FILES
@@ -335,6 +337,7 @@         displayapp/screens/DropDownDemo.cpp
         displayapp/screens/Modal.cpp
         displayapp/screens/BatteryIcon.cpp
         displayapp/screens/BleIcon.cpp
+        displayapp/screens/NotificationIcon.cpp
         displayapp/screens/Brightness.cpp
         displayapp/screens/SystemInfo.cpp
         displayapp/screens/Label.cpp
@@ -342,6 +345,7 @@         displayapp/screens/FirmwareUpdate.cpp
         displayapp/screens/Music.cpp
         displayapp/screens/FirmwareValidation.cpp
         displayapp/screens/ApplicationList.cpp
+        displayapp/screens/Notifications.cpp
         main.cpp
         drivers/St7789.cpp
         drivers/SpiNorFlash.cpp
@@ -412,7 +416,8 @@         displayapp/screens/InfiniPaint.h
         displayapp/screens/DropDownDemo.h
         displayapp/screens/Modal.h
         displayapp/screens/BatteryIcon.h
-        displayapp/screens/BleIcon.cpp
+        displayapp/screens/BleIcon.h
+        displayapp/screens/NotificationIcon.h
         displayapp/screens/Brightness.h
         displayapp/screens/SystemInfo.h
         displayapp/screens/ScreenList.h
@@ -421,6 +426,7 @@         displayapp/screens/FirmwareUpdate.h
         displayapp/screens/FirmwareValidation.h
         displayapp/screens/ApplicationList.h
         displayapp/Apps.h
+        displayapp/screens/Notifications.h
         drivers/St7789.h
         drivers/SpiNorFlash.h
         drivers/SpiMaster.h




diff --git a/src/components/ble/AlertNotificationClient.cpp b/src/components/ble/AlertNotificationClient.cpp
index ddc7296715876be6ccef974750d1c444264df739..40970e0b6daf45015ec661667069290cdfe152e8 100644
--- a/src/components/ble/AlertNotificationClient.cpp
+++ b/src/components/ble/AlertNotificationClient.cpp
@@ -105,25 +105,21 @@ }
 
 void AlertNotificationClient::OnNotification(ble_gap_event *event) {
   if(event->notify_rx.attr_handle == newAlertHandle) {
-    // TODO implement this with more memory safety (and constexpr)
-    static const size_t maxBufferSize{21};
-    static const size_t maxMessageSize{18};
-    size_t bufferSize = min(OS_MBUF_PKTLEN(event->notify_rx.om), maxBufferSize);
-
-    uint8_t data[bufferSize];
-    os_mbuf_copydata(event->notify_rx.om, 0, bufferSize, data);
+    constexpr size_t stringTerminatorSize = 1; // end of string '\0'
+    constexpr size_t headerSize = 3;
+    const auto maxMessageSize {NotificationManager::MaximumMessageSize()};
+    const auto maxBufferSize{maxMessageSize + headerSize};
 
-    char *s = (char *) &data[3];
-    auto messageSize = min(maxMessageSize, (bufferSize-3));
+    const auto dbgPacketLen = OS_MBUF_PKTLEN(event->notify_rx.om);
+    size_t bufferSize = min(dbgPacketLen + stringTerminatorSize, maxBufferSize);
+    auto messageSize = min(maxMessageSize, (bufferSize-headerSize));
 
-    for (uint i = 0; i < messageSize-1; i++) {
-      if (s[i] == 0x00) {
-        s[i] = 0x0A;
-      }
-    }
-    s[messageSize-1] = '\0';
+    NotificationManager::Notification notif;
+    os_mbuf_copydata(event->notify_rx.om, headerSize, messageSize-1, notif.message.data());
+    notif.message[messageSize-1] = '\0';
+    notif.category = Pinetime::Controllers::NotificationManager::Categories::SimpleAlert;
+    notificationManager.Push(std::move(notif));
 
-    notificationManager.Push(Pinetime::Controllers::NotificationManager::Categories::SimpleAlert, s, messageSize);
     systemTask.PushMessage(Pinetime::System::SystemTask::Messages::OnNewNotification);
   }
 }




diff --git a/src/components/ble/AlertNotificationService.cpp b/src/components/ble/AlertNotificationService.cpp
index 9a9b535df0ba6aeceffbd09fa9abe34a617c44ef..32711b92abe9b462418b56e67289ddf0d1c370c9 100644
--- a/src/components/ble/AlertNotificationService.cpp
+++ b/src/components/ble/AlertNotificationService.cpp
@@ -38,7 +38,7 @@                 {
                   0
                 }
         },
-        serviceDefinition{
+    serviceDefinition{
                 {
                         /* Device Information Service */
                         .type = BLE_GATT_SVC_TYPE_PRIMARY,
@@ -48,33 +48,28 @@                 },
                 {
                         0
                 },
-        }, m_systemTask{systemTask}, m_notificationManager{notificationManager} {
+        }, systemTask{systemTask}, notificationManager{notificationManager} {
 }
 
 int AlertNotificationService::OnAlert(uint16_t conn_handle, uint16_t attr_handle,
                                                     struct ble_gatt_access_ctxt *ctxt) {
-
   if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
-    // TODO implement this with more memory safety (and constexpr)
-    static const size_t maxBufferSize{21};
-    static const size_t maxMessageSize{18};
-    size_t bufferSize = min(OS_MBUF_PKTLEN(ctxt->om), maxBufferSize);
+    constexpr size_t stringTerminatorSize = 1; // end of string '\0'
+    constexpr size_t headerSize = 3;
+    const auto maxMessageSize {NotificationManager::MaximumMessageSize()};
+    const auto maxBufferSize{maxMessageSize + headerSize};
 
-    uint8_t data[bufferSize];
-    os_mbuf_copydata(ctxt->om, 0, bufferSize, data);
+    const auto dbgPacketLen = OS_MBUF_PKTLEN(ctxt->om);
+    size_t bufferSize = min(dbgPacketLen + stringTerminatorSize, maxBufferSize);
+    auto messageSize = min(maxMessageSize, (bufferSize-headerSize));
 
-    char *s = (char *) &data[3];
-    auto messageSize = min(maxMessageSize, (bufferSize-3));
+    NotificationManager::Notification notif;
+    os_mbuf_copydata(ctxt->om, headerSize, messageSize-1, notif.message.data());
+    notif.message[messageSize-1] = '\0';
+    notif.category = Pinetime::Controllers::NotificationManager::Categories::SimpleAlert;
+    notificationManager.Push(std::move(notif));
 
-    for (uint i = 0; i < messageSize-1; i++) {
-      if (s[i] == 0x00) {
-        s[i] = 0x0A;
-      }
-    }
-    s[messageSize-1] = '\0';
-
-    m_notificationManager.Push(Pinetime::Controllers::NotificationManager::Categories::SimpleAlert, s, messageSize);
-    m_systemTask.PushMessage(Pinetime::System::SystemTask::Messages::OnNewNotification);
+    systemTask.PushMessage(Pinetime::System::SystemTask::Messages::OnNewNotification);
   }
   return 0;
 }




diff --git a/src/components/ble/AlertNotificationService.h b/src/components/ble/AlertNotificationService.h
index 53cb44cc59fb7bf1444d9737831112b90a2a3102..1b8c4989622853262066299429ff2d3633fd4ead 100644
--- a/src/components/ble/AlertNotificationService.h
+++ b/src/components/ble/AlertNotificationService.h
@@ -32,8 +32,8 @@
         struct ble_gatt_chr_def characteristicDefinition[2];
         struct ble_gatt_svc_def serviceDefinition[2];
 
-        Pinetime::System::SystemTask &m_systemTask;
-        NotificationManager &m_notificationManager;
+        Pinetime::System::SystemTask &systemTask;
+        NotificationManager &notificationManager;
     };
   }
 }




diff --git a/src/components/ble/ImmediateAlertService.cpp b/src/components/ble/ImmediateAlertService.cpp
index 3b7f47bf59652e9b2ccfc6b1184b37923b8d2994..e2cee30865e5855d6ab22aaa3181d759cf00ca2e 100644
--- a/src/components/ble/ImmediateAlertService.cpp
+++ b/src/components/ble/ImmediateAlertService.cpp
@@ -1,4 +1,5 @@
 #include <systemtask/SystemTask.h>
+#include <cstring>
 #include "ImmediateAlertService.h"
 #include "AlertNotificationService.h"
 
@@ -67,7 +68,12 @@   if(attributeHandle == alertLevelHandle) {
     if(context->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
       auto alertLevel = static_cast<Levels>(context->om->om_data[0]);
       auto* alertString = ToString(alertLevel);
-      notificationManager.Push(Pinetime::Controllers::NotificationManager::Categories::SimpleAlert, alertString, strlen(alertString));
+
+      NotificationManager::Notification notif;
+      std::memcpy(notif.message.data(), alertString, strlen(alertString));
+      notif.category = Pinetime::Controllers::NotificationManager::Categories::SimpleAlert;
+      notificationManager.Push(std::move(notif));
+
       systemTask.PushMessage(Pinetime::System::SystemTask::Messages::OnNewNotification);
     }
   }




diff --git a/src/components/ble/NotificationManager.cpp b/src/components/ble/NotificationManager.cpp
index 0aea0697358482a966e6c158e525751c9c4bc156..6771172322c7fdc03b30b7d43ad65b9316b86cd5 100644
--- a/src/components/ble/NotificationManager.cpp
+++ b/src/components/ble/NotificationManager.cpp
@@ -1,30 +1,81 @@
 #include <cstring>
+#include <algorithm>
 #include "NotificationManager.h"
 
 using namespace Pinetime::Controllers;
 
-void NotificationManager::Push(Pinetime::Controllers::NotificationManager::Categories category,
-                                                      const char *message, uint8_t currentMessageSize) {
-  // TODO handle edge cases on read/write index
-  auto checkedSize = std::min(currentMessageSize, uint8_t{18});
-  auto& notif = notifications[writeIndex];
-  std::memcpy(notif.message.data(), message, checkedSize);
-  notif.message[checkedSize] = '\0';
-  notif.category = category;
+constexpr uint8_t NotificationManager::MessageSize;
+
 
+void NotificationManager::Push(NotificationManager::Notification &&notif) {
+  notif.id = GetNextId();
+  notif.valid = true;
+  notifications[writeIndex] = std::move(notif);
   writeIndex = (writeIndex + 1 < TotalNbNotifications) ? writeIndex + 1 : 0;
-  if(!empty && writeIndex == readIndex)
-    readIndex = writeIndex + 1;
+  if(!empty)
+    readIndex = (readIndex + 1 < TotalNbNotifications) ? readIndex + 1 : 0;
+  else empty = false;
+
+  newNotification = true;
 }
 
-NotificationManager::Notification Pinetime::Controllers::NotificationManager::Pop() {
-// TODO handle edge cases on read/write index
+NotificationManager::Notification NotificationManager::GetLastNotification() {
   NotificationManager::Notification notification = notifications[readIndex];
+  notification.index = 1;
+  return notification;
+}
 
-  if(readIndex != writeIndex) {
-    readIndex = (readIndex + 1 < TotalNbNotifications) ? readIndex + 1 : 0;
-  }
+NotificationManager::Notification::Id NotificationManager::GetNextId() {
+  return nextId++;
+}
+
+NotificationManager::Notification NotificationManager::GetNext(NotificationManager::Notification::Id id) {
+  auto currentIterator = std::find_if(notifications.begin(), notifications.end(), [id](const Notification& n){return n.valid && n.id == id;});
+  if(currentIterator == notifications.end() || currentIterator->id != id) return Notification{};
+
+  auto& lastNotification = notifications[readIndex];
+
+  NotificationManager::Notification result;
+
+  if(currentIterator == (notifications.end()-1))
+    result = *(notifications.begin());
+  else
+    result = *(currentIterator+1);
+
+  if(result.id <= id) return {};
 
-  // TODO Check move optimization on return
-  return notification;
+  result.index = (lastNotification.id - result.id)+1;
+  return result;
 }
+
+NotificationManager::Notification NotificationManager::GetPrevious(NotificationManager::Notification::Id id) {
+  auto currentIterator = std::find_if(notifications.begin(), notifications.end(), [id](const Notification& n){return n.valid && n.id == id;});
+  if(currentIterator == notifications.end() || currentIterator->id != id) return Notification{};
+
+  auto& lastNotification = notifications[readIndex];
+
+  NotificationManager::Notification result;
+
+  if(currentIterator == notifications.begin())
+    result = *(notifications.end()-1);
+  else
+    result = *(currentIterator-1);
+
+  if(result.id >= id) return {};
+
+  result.index = (lastNotification.id - result.id)+1;
+  return result;
+}
+
+bool NotificationManager::AreNewNotificationsAvailable() {
+  return newNotification;
+}
+
+bool NotificationManager::ClearNewNotificationFlag() {
+  return newNotification.exchange(false);
+}
+
+size_t NotificationManager::NbNotifications() const {
+  return std::count_if(notifications.begin(), notifications.end(), [](const Notification& n){ return n.valid;});
+}
+




diff --git a/src/components/ble/NotificationManager.h b/src/components/ble/NotificationManager.h
index daa1571b3d6feeafcc80287854d265b9ab9b5c4b..49fe830660bdeaf63cc449d1651487e593c5403d 100644
--- a/src/components/ble/NotificationManager.h
+++ b/src/components/ble/NotificationManager.h
@@ -1,29 +1,43 @@
 #pragma once
 
 #include <array>
+#include <atomic>
 
 namespace Pinetime {
   namespace Controllers {
     class NotificationManager {
       public:
         enum class Categories {Unknown, SimpleAlert, Email, News, IncomingCall, MissedCall, Sms, VoiceMail, Schedule, HighProriotyAlert, InstantMessage };
-        static constexpr uint8_t MessageSize{18};
+        static constexpr uint8_t MessageSize{100};
 
         struct Notification {
+          using Id = uint8_t;
+          Id id;
+          bool valid = false;
+          uint8_t index;
           std::array<char, MessageSize+1> message;
           Categories category = Categories::Unknown;
         };
+        Notification::Id nextId {0};
 
-      void Push(Categories category, const char* message, uint8_t messageSize);
-      Notification Pop();
+      void Push(Notification&& notif);
+      Notification GetLastNotification();
+      Notification GetNext(Notification::Id id);
+      Notification GetPrevious(Notification::Id id);
+      bool ClearNewNotificationFlag();
+      bool AreNewNotificationsAvailable();
 
+      static constexpr uint8_t MaximumMessageSize() { return MessageSize; };
+      size_t NbNotifications() const;
 
       private:
+        Notification::Id GetNextId();
         static constexpr uint8_t TotalNbNotifications = 5;
         std::array<Notification, TotalNbNotifications> notifications;
         uint8_t readIndex = 0;
         uint8_t writeIndex = 0;
         bool empty = true;
+        std::atomic<bool> newNotification{false};
     };
   }
 }
\ No newline at end of file




diff --git a/src/displayapp/Apps.h b/src/displayapp/Apps.h
index 3842e4e58778874797b469dad9b8218183e5d986..bfa799ba04a5556114388fe2f841218b741c9662 100644
--- a/src/displayapp/Apps.h
+++ b/src/displayapp/Apps.h
@@ -2,6 +2,6 @@ #pragma once
 
 namespace Pinetime {
   namespace Applications {
-    enum class Apps {None, Launcher, Clock, SysInfo, Meter, Gauge, Brightness, Music, FirmwareValidation, Paint};
+    enum class Apps {None, Launcher, Clock, SysInfo, Meter, Gauge, Brightness, Music, FirmwareValidation, Paint, Notifications};
   }
 }
\ No newline at end of file




diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp
index d65e4f92afae5e02a8718093ab9795cd4ffbf3ad..d4d41333968c55cc74b3c766a9c9298ca66e5e09 100644
--- a/src/displayapp/DisplayApp.cpp
+++ b/src/displayapp/DisplayApp.cpp
@@ -8,6 +8,7 @@ #include 
 #include <queue.h>
 #include "components/datetime/DateTimeController.h"
 #include <drivers/Cst816s.h>
+#include "displayapp/screens/Notifications.h"
 #include "displayapp/screens/Tile.h"
 #include "displayapp/screens/Meter.h"
 #include "displayapp/screens/Gauge.h"
@@ -35,7 +36,7 @@         bleController{bleController},
         dateTimeController{dateTimeController},
         watchdog{watchdog},
         touchPanel{touchPanel},
-        currentScreen{new Screens::Clock(this, dateTimeController, batteryController, bleController) },
+        currentScreen{new Screens::Clock(this, dateTimeController, batteryController, bleController, notificationManager) },
         systemTask{systemTask},
         notificationManager{notificationManager} {
   msgQueue = xQueueCreate(queueSize, itemSize);
@@ -114,8 +115,12 @@       case Messages::UpdateBatteryLevel:
 //        clockScreen.SetBatteryPercentRemaining(batteryController.PercentRemaining());
         break;
       case Messages::NewNotification: {
-        auto notification = notificationManager.Pop();
-        modal->Show(notification.message.data());
+        if(onClockApp) {
+          currentScreen.reset(nullptr);
+          lvgl.SetFullRefresh(Components::LittleVgl::FullRefreshDirections::Up);
+          onClockApp = false;
+          currentScreen.reset(new Screens::Notifications(this, notificationManager, Screens::Notifications::Modes::Preview));
+        }
       }
         break;
       case Messages::TouchEvent: {
@@ -191,7 +196,7 @@     switch(nextApp) {
       case Apps::None:
       case Apps::Launcher: currentScreen.reset(new Screens::ApplicationList(this)); break;
       case Apps::Clock:
-        currentScreen.reset(new Screens::Clock(this, dateTimeController, batteryController, bleController));
+        currentScreen.reset(new Screens::Clock(this, dateTimeController, batteryController, bleController, notificationManager));
         onClockApp = true;
         break;
 //      case Apps::Test: currentScreen.reset(new Screens::Message(this)); break;
@@ -202,6 +207,7 @@       case Apps::Paint: currentScreen.reset(new Screens::InfiniPaint(this, lvgl)); break;
       case Apps::Brightness : currentScreen.reset(new Screens::Brightness(this, brightnessController)); break;
       case Apps::Music : currentScreen.reset(new Screens::Music(this, systemTask.nimble().music())); break;
       case Apps::FirmwareValidation: currentScreen.reset(new Screens::FirmwareValidation(this, validator)); break;
+      case Apps::Notifications: currentScreen.reset(new Screens::Notifications(this, notificationManager, Screens::Notifications::Modes::Normal)); break;
     }
     nextApp = Apps::None;
   }




diff --git a/src/displayapp/screens/ApplicationList.cpp b/src/displayapp/screens/ApplicationList.cpp
index 71ba91c4fa9fb172dc509e20addcc63e0509a164..7eb97188526cc4abfcded4673b52a4ce0b1388d5 100644
--- a/src/displayapp/screens/ApplicationList.cpp
+++ b/src/displayapp/screens/ApplicationList.cpp
@@ -58,9 +58,9 @@   std::array applications {
           {{Symbols::tachometer, Apps::Gauge},
            {Symbols::asterisk, Apps::Meter},
            {Symbols::paintbrush, Apps::Paint},
-           {Symbols::none, Apps::None},
-           {Symbols::none, Apps::None},
-           {Symbols::none, Apps::None}
+                  {Symbols::info, Apps::Notifications},
+                  {Symbols::none, Apps::None},
+                  {Symbols::none, Apps::None}
           }
   };
 




diff --git a/src/displayapp/screens/Clock.cpp b/src/displayapp/screens/Clock.cpp
index bb14d520aa64a0d544b4275599d82d626508c9d7..977321c1f0bd07d06561692084246fa89e595521 100644
--- a/src/displayapp/screens/Clock.cpp
+++ b/src/displayapp/screens/Clock.cpp
@@ -8,6 +8,9 @@ #include "../DisplayApp.h"
 #include "BatteryIcon.h"
 #include "BleIcon.h"
 #include "Symbols.h"
+#include "components/ble/NotificationManager.h"
+#include "NotificationIcon.h"
+
 using namespace Pinetime::Applications::Screens;
 extern lv_font_t jetbrains_mono_extrabold_compressed;
 extern lv_font_t jetbrains_mono_bold_20;
@@ -21,8 +24,10 @@
 Clock::Clock(DisplayApp* app,
         Controllers::DateTime& dateTimeController,
         Controllers::Battery& batteryController,
-        Controllers::Ble& bleController) : Screen(app), currentDateTime{{}},
-                                           dateTimeController{dateTimeController}, batteryController{batteryController}, bleController{bleController} {
+        Controllers::Ble& bleController,
+        Controllers::NotificationManager& notificatioManager) : Screen(app), currentDateTime{{}},
+                                           dateTimeController{dateTimeController}, batteryController{batteryController},
+                                           bleController{bleController}, notificatioManager{notificatioManager} {
   displayedChar[0] = 0;
   displayedChar[1] = 0;
   displayedChar[2] = 0;
@@ -41,6 +46,9 @@   bleIcon = lv_label_create(lv_scr_act(), nullptr);
   lv_label_set_text(bleIcon, Symbols::bluetooth);
   lv_obj_align(bleIcon, batteryPlug, LV_ALIGN_OUT_LEFT_MID, -5, 0);
 
+  notificationIcon = lv_label_create(lv_scr_act(), NULL);
+  lv_label_set_text(notificationIcon, NotificationIcon::GetIcon(false));
+  lv_obj_align(notificationIcon, nullptr, LV_ALIGN_IN_TOP_LEFT, 10, 0);
 
   label_date = lv_label_create(lv_scr_act(), nullptr);
 
@@ -105,6 +113,14 @@   }
   lv_obj_align(batteryIcon, lv_scr_act(), LV_ALIGN_IN_TOP_RIGHT, -5, 5);
   lv_obj_align(batteryPlug, batteryIcon, LV_ALIGN_OUT_LEFT_MID, -5, 0);
   lv_obj_align(bleIcon, batteryPlug, LV_ALIGN_OUT_LEFT_MID, -5, 0);
+
+  notificationState = notificatioManager.AreNewNotificationsAvailable();
+  if(notificationState.IsUpdated()) {
+    if(notificationState.Get() == true)
+      lv_label_set_text(notificationIcon, NotificationIcon::GetIcon(true));
+    else
+      lv_label_set_text(notificationIcon, NotificationIcon::GetIcon(false));
+  }
 
   currentDateTime = dateTimeController.CurrentDateTime();
 




diff --git a/src/displayapp/screens/Clock.h b/src/displayapp/screens/Clock.h
index 5753f6a3213dc4d5b21ca2cddea441b7b806e9f1..58149a79fd8b182bc82327113f5b35e55ed3156d 100644
--- a/src/displayapp/screens/Clock.h
+++ b/src/displayapp/screens/Clock.h
@@ -7,6 +7,7 @@ #include "Screen.h"
 #include <bits/unique_ptr.h>
 #include <libs/lvgl/src/lv_core/lv_style.h>
 #include <libs/lvgl/src/lv_core/lv_obj.h>
+#include "components/ble/NotificationManager.h"
 #include "components/battery/BatteryController.h"
 #include "components/ble/BleController.h"
 
@@ -38,7 +39,8 @@         public:
           Clock(DisplayApp* app,
                   Controllers::DateTime& dateTimeController,
                   Controllers::Battery& batteryController,
-                  Controllers::Ble& bleController);
+                  Controllers::Ble& bleController,
+                  Controllers::NotificationManager& notificatioManager);
           ~Clock() override;
 
           bool Refresh() override;
@@ -63,23 +65,25 @@           DirtyValue bleState {false};
           DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime;
           DirtyValue<uint32_t> stepCount  {0};
           DirtyValue<uint8_t> heartbeat  {0};
-
+          DirtyValue<bool> notificationState {false};
 
           lv_obj_t* label_time;
           lv_obj_t* label_date;
           lv_obj_t* backgroundLabel;
-          lv_obj_t * batteryIcon;
-          lv_obj_t * bleIcon;
+          lv_obj_t* batteryIcon;
+          lv_obj_t* bleIcon;
           lv_obj_t* batteryPlug;
           lv_obj_t* heartbeatIcon;
           lv_obj_t* heartbeatValue;
           lv_obj_t* heartbeatBpm;
           lv_obj_t* stepIcon;
           lv_obj_t* stepValue;
+          lv_obj_t* notificationIcon;
 
           Controllers::DateTime& dateTimeController;
           Controllers::Battery& batteryController;
           Controllers::Ble& bleController;
+          Controllers::NotificationManager& notificatioManager;
 
           bool running = true;
 




diff --git a/src/displayapp/screens/NotificationIcon.cpp b/src/displayapp/screens/NotificationIcon.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..64898c2cfadd515b7ab07e118c1e005d37a97e37
--- /dev/null
+++ b/src/displayapp/screens/NotificationIcon.cpp
@@ -0,0 +1,8 @@
+#include "NotificationIcon.h"
+#include "Symbols.h"
+using namespace Pinetime::Applications::Screens;
+
+const char* NotificationIcon::GetIcon(bool newNotificationAvailable) {
+  if(newNotificationAvailable) return Symbols::info;
+  else return "";
+}
\ No newline at end of file




diff --git a/src/displayapp/screens/NotificationIcon.h b/src/displayapp/screens/NotificationIcon.h
new file mode 100644
index 0000000000000000000000000000000000000000..dc34c3f083f62731483206b779a3668336d40c00
--- /dev/null
+++ b/src/displayapp/screens/NotificationIcon.h
@@ -0,0 +1,12 @@
+#pragma once
+
+namespace Pinetime {
+  namespace Applications {
+    namespace Screens {
+      class NotificationIcon {
+      public:
+        static const char* GetIcon(bool newNotificationAvailable);
+      };
+    }
+  }
+}
\ No newline at end of file




diff --git a/src/displayapp/screens/Notifications.cpp b/src/displayapp/screens/Notifications.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..85848b2f8712c772e52e6b8c7ac9680416c8a7e6
--- /dev/null
+++ b/src/displayapp/screens/Notifications.cpp
@@ -0,0 +1,174 @@
+#include <libs/lvgl/lvgl.h>
+#include <displayapp/DisplayApp.h>
+#include <functional>
+#include "Notifications.h"
+
+using namespace Pinetime::Applications::Screens;
+
+Notifications::Notifications(DisplayApp *app, Pinetime::Controllers::NotificationManager &notificationManager, Modes mode) :
+        Screen(app), notificationManager{notificationManager}, mode{mode} {
+  notificationManager.ClearNewNotificationFlag();
+  auto notification = notificationManager.GetLastNotification();
+  if(notification.valid) {
+    currentId = notification.id;
+    currentItem.reset(new NotificationItem("\nNotification", notification.message.data(), notification.index, notificationManager.NbNotifications(), mode));
+    validDisplay = true;
+  } else {
+    currentItem.reset(new NotificationItem("\nNotification", "No notification to display", 0, notificationManager.NbNotifications(), Modes::Preview));
+  }
+
+  if(mode == Modes::Preview) {
+    static lv_style_t style_line;
+    lv_style_copy(&style_line, &lv_style_plain);
+    style_line.line.color = LV_COLOR_WHITE;
+    style_line.line.width = 3;
+    style_line.line.rounded = 0;
+
+
+    timeoutLine = lv_line_create(lv_scr_act(), nullptr);
+    lv_line_set_style(timeoutLine, LV_LINE_STYLE_MAIN, &style_line);
+    lv_line_set_points(timeoutLine, timeoutLinePoints, 2);
+    timeoutTickCountStart = xTaskGetTickCount();
+    timeoutTickCountEnd = timeoutTickCountStart + (5*1024);
+  }
+}
+
+Notifications::~Notifications() {
+  lv_obj_clean(lv_scr_act());
+}
+
+bool Notifications::Refresh() {
+  if (mode == Modes::Preview) {
+    auto tick = xTaskGetTickCount();
+    int32_t pos = 240 - ((tick - timeoutTickCountStart) / ((timeoutTickCountEnd - timeoutTickCountStart) / 240));
+    if (pos < 0)
+      running = false;
+
+    timeoutLinePoints[1].x = pos;
+    lv_line_set_points(timeoutLine, timeoutLinePoints, 2);
+
+    if (!running) {
+      // Start clock app when exiting this one
+      app->StartApp(Apps::Clock);
+    }
+  }
+
+  return running;
+}
+
+bool Notifications::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
+  switch (event) {
+    case Pinetime::Applications::TouchEvents::SwipeUp: {
+      Controllers::NotificationManager::Notification previousNotification;
+      if(validDisplay)
+        previousNotification = notificationManager.GetPrevious(currentId);
+      else
+        previousNotification = notificationManager.GetLastNotification();
+
+      if (!previousNotification.valid) return true;
+
+      validDisplay = true;
+      currentId = previousNotification.id;
+      currentItem.reset(nullptr);
+      app->SetFullRefresh(DisplayApp::FullRefreshDirections::Up);
+      currentItem.reset(new NotificationItem("\nNotification", previousNotification.message.data(),  previousNotification.index, notificationManager.NbNotifications(), mode));
+    }
+      return true;
+    case Pinetime::Applications::TouchEvents::SwipeDown: {
+      Controllers::NotificationManager::Notification nextNotification;
+      if(validDisplay)
+        nextNotification = notificationManager.GetNext(currentId);
+      else
+        nextNotification = notificationManager.GetLastNotification();
+
+      if (!nextNotification.valid) return true;
+
+      validDisplay = true;
+      currentId = nextNotification.id;
+      currentItem.reset(nullptr);
+      app->SetFullRefresh(DisplayApp::FullRefreshDirections::Down);
+      currentItem.reset(new NotificationItem("\nNotification", nextNotification.message.data(),  nextNotification.index, notificationManager.NbNotifications(), mode));
+    }
+      return true;
+    default:
+      return false;
+  }
+}
+
+
+bool Notifications::OnButtonPushed() {
+  running = false;
+  return true;
+}
+
+
+Notifications::NotificationItem::NotificationItem(const char *title, const char *msg, uint8_t notifNr, uint8_t notifNb, Modes mode)
+        : notifNr{notifNr}, notifNb{notifNb}, mode{mode} {
+  container1 = lv_cont_create(lv_scr_act(), nullptr);
+  static lv_style_t contStyle;
+  lv_style_copy(&contStyle, lv_cont_get_style(container1, LV_CONT_STYLE_MAIN));
+  contStyle.body.padding.inner = 20;
+  lv_cont_set_style(container1, LV_CONT_STYLE_MAIN, &contStyle);
+  lv_obj_set_width(container1, LV_HOR_RES);
+  lv_obj_set_height(container1, LV_VER_RES);
+  lv_obj_set_pos(container1, 0, 0);
+  lv_cont_set_layout(container1, LV_LAYOUT_OFF);
+  lv_cont_set_fit2(container1, LV_FIT_FLOOD, LV_FIT_FLOOD);
+
+  t1 = lv_label_create(container1, nullptr);
+  static lv_style_t titleStyle;
+  static lv_style_t textStyle;
+  static lv_style_t bottomStyle;
+  lv_style_copy(&titleStyle, lv_label_get_style(t1, LV_LABEL_STYLE_MAIN));
+  lv_style_copy(&textStyle, lv_label_get_style(t1, LV_LABEL_STYLE_MAIN));
+  lv_style_copy(&bottomStyle, lv_label_get_style(t1, LV_LABEL_STYLE_MAIN));
+  titleStyle.body.padding.inner = 5;
+  titleStyle.body.grad_color = LV_COLOR_GRAY;
+  titleStyle.body.main_color = LV_COLOR_GRAY;
+  titleStyle.body.radius = 20;
+  textStyle.body.border.part = LV_BORDER_NONE;
+  textStyle.body.padding.inner = 5;
+
+  bottomStyle.body.main_color = LV_COLOR_GREEN;
+  bottomStyle.body.grad_color = LV_COLOR_GREEN;
+  bottomStyle.body.border.part = LV_BORDER_TOP;
+  bottomStyle.body.border.color = LV_COLOR_RED;
+
+  lv_label_set_style(t1, LV_LABEL_STYLE_MAIN, &titleStyle);
+  lv_label_set_long_mode(t1, LV_LABEL_LONG_BREAK);
+  lv_label_set_body_draw(t1, true);
+  lv_obj_set_width(t1, LV_HOR_RES - (titleStyle.body.padding.left + titleStyle.body.padding.right));
+  lv_label_set_text(t1, title);
+  static constexpr int16_t offscreenOffset = -20 ;
+  lv_obj_set_pos(t1, titleStyle.body.padding.left, offscreenOffset);
+
+  auto titleHeight = lv_obj_get_height(t1);
+
+  l1 = lv_label_create(container1, nullptr);
+  lv_label_set_style(l1, LV_LABEL_STYLE_MAIN, &textStyle);
+  lv_obj_set_pos(l1, textStyle.body.padding.left,
+                 titleHeight + offscreenOffset + textStyle.body.padding.bottom +
+                 textStyle.body.padding.top);
+
+  lv_label_set_long_mode(l1, LV_LABEL_LONG_BREAK);
+  lv_label_set_body_draw(l1, true);
+  lv_obj_set_width(l1, LV_HOR_RES - (textStyle.body.padding.left + textStyle.body.padding.right));
+  lv_label_set_text(l1, msg);
+
+  if(mode == Modes::Normal) {
+    if(notifNr < notifNb) {
+      bottomPlaceholder = lv_label_create(container1, nullptr);
+      lv_label_set_style(bottomPlaceholder, LV_LABEL_STYLE_MAIN, &titleStyle);
+      lv_label_set_long_mode(bottomPlaceholder, LV_LABEL_LONG_BREAK);
+      lv_label_set_body_draw(bottomPlaceholder, true);
+      lv_obj_set_width(bottomPlaceholder, LV_HOR_RES - (titleStyle.body.padding.left + titleStyle.body.padding.right));
+      lv_label_set_text(bottomPlaceholder, " ");
+      lv_obj_set_pos(bottomPlaceholder, titleStyle.body.padding.left, LV_VER_RES - 5);
+    }
+  }
+}
+
+
+Notifications::NotificationItem::~NotificationItem() {
+  lv_obj_clean(lv_scr_act());
+}




diff --git a/src/displayapp/screens/Notifications.h b/src/displayapp/screens/Notifications.h
new file mode 100644
index 0000000000000000000000000000000000000000..fb4e1ef2f078c8bbda1c89abc819391e54edc4a3
--- /dev/null
+++ b/src/displayapp/screens/Notifications.h
@@ -0,0 +1,61 @@
+#pragma once
+
+#include <functional>
+#include <vector>
+
+#include "Screen.h"
+#include "ScreenList.h"
+
+
+namespace Pinetime {
+  namespace Applications {
+    namespace Screens {
+      class Notifications : public Screen {
+        public:
+          enum class Modes {Normal, Preview};
+          explicit Notifications(DisplayApp* app, Pinetime::Controllers::NotificationManager& notificationManager, Modes mode);
+          ~Notifications() override;
+
+          bool Refresh() override;
+          bool OnButtonPushed() override;
+          bool OnTouchEvent(Pinetime::Applications::TouchEvents event) override;
+
+        private:
+          bool running = true;
+
+          class NotificationItem {
+            public:
+              NotificationItem(const char* title, const char* msg, uint8_t notifNr, uint8_t notifNb, Modes mode);
+              ~NotificationItem();
+              bool Refresh() {return false;}
+
+            private:
+              uint8_t notifNr = 0;
+              uint8_t notifNb = 0;
+              char pageText[4];
+
+              lv_obj_t* container1;
+              lv_obj_t* t1;
+              lv_obj_t* l1;
+              lv_obj_t* bottomPlaceholder;
+              Modes mode;
+          };
+
+          struct NotificationData {
+            const char* title;
+            const char* text;
+          };
+          Pinetime::Controllers::NotificationManager& notificationManager;
+          Modes mode = Modes::Normal;
+          std::unique_ptr<NotificationItem> currentItem;
+          Controllers::NotificationManager::Notification::Id currentId;
+          bool validDisplay = false;
+
+          lv_point_t timeoutLinePoints[2]  { {0, 237}, {239, 237} };
+          lv_obj_t* timeoutLine;
+          uint32_t timeoutTickCountStart;
+          uint32_t timeoutTickCountEnd;
+      };
+    }
+  }
+}