81 lines
6.4 KiB
Typst
81 lines
6.4 KiB
Typst
#import "../style.typ"
|
||
#set heading(numbering: "1.1")
|
||
|
||
= Implementacja <implementacja>
|
||
== Projekt architektury systemu <projekt>
|
||
Przygotowana implementacja bazuje na architekturze Model-View. Jest to podejście, gdzie cała logika biznesowa jest zawarta w modelach, które bezpośrednio są przekazywane do warstwy prezentacji (View). Na tej warstwie jest wykonywane odpowiednie formatowanie danych, gdzie też brane pod uwagę są preferencje zapisu i językowe użytkownika. Model-View to uproszczony wariant popularnej w aplikacjach mobilnych architektury Model-View-ViewModel (MVVM), gdzie ViewModel jest warstwą zajmującą się przekształcaniem modelów biznesowych na gotowe do prezentacji obiekty. Warstwa View wtedy zajmuje się przede wszystkim definiowaniem struktury interfejsu użytkownika oraz logiką związaną z dostępnością (wsparcie dla funkcjonalności czytników ekranów, skalowaniem interfejsu). Przygotowana implementacja nie zawiera złożonej logiki prezentacji ani rozbudowanego graficznego interfejsu użytkownika, więc w celu uproszczenia kodu zdecydowałem się na porzucenie użycia warstwy ViewModelu.
|
||
|
||
Warstwa prezentacji aplikacji opisująca początkowy interfejs użytkownika jest zaimplementowana w `AllNotesScreen`. Ekran jest widoczny dla użytkownika, gdy ustawił swoją nazwę użytkownika w sieci peer-to-peer. Składa się z listy podzielonej na dwie sekcje - notatek których użytkownik jest autorem, oraz notatek do których użytkownik został zaproszony. Nad listą znajduje się przycisk stworzenia nowej notatki. Naciśnięcie na którąkolwiek z istniejących notatek prowadzi do otwarcia jej zawartości. W zależności od tego, czy użytkownik jest właścicielem danej notatki czy współtwórcą, interfejsem do edycji tej notatki są odpowiednio obiekty `NoteEditorScreen` oraz `SharedNoteEditor`. Z ekranu `NoteEditorScreen` możemy przejść do `ManageMembersScreen`, który jest listą użytkowników zaproszonych i możliwych do zaproszenia do edytowania aktualnej notatki.
|
||
|
||
== Model danych
|
||
Podstawowym obiektem reprezentującym notatkę w systemie plików jest `Note`. Składa się on z URL (Universal Resource Locator), czyli ścieżki prowadzącej do pełnej zawartości notatki użytkownika, która jest przechowywana w pliku tekstowym o rozszerzeniu `txt`. Dodatkowo przechowujemy w tym obiekcie jeszcze `name`, które pełni funkcję ułatwionego dostępu do przyjaznej nazwy, jednocześnie będąc nazwą pliku w lokalnym systemie plików oraz przyjazną nazwą prezentowaną w liście notatek do których użytkownik został zaproszony. Obiekt implementuje protokół `Identifiable`, który gwarantuje możliwość identyfikacji obiektu w zbiorze zawierającym wiele jego instancji, np. w tablicy. Ta cecha jest wymagana i wykorzystywana przez SwiftUI, by móc rozróżniać rodzaje zmian na komponentach interfejsów graficznych zawierających wiele kopii takiego obiektu, np. `List` lub `ForEach`. Umożliwia to dokonanie decyzji co do sposobu animacji zmian na ekranie użytkownika, ponieważ framework będzie mógł rozróżnić usunięcie i wstawienie nowego obiektu, od zmiany parametrów tej samej instancji.
|
||
|
||
```swift
|
||
struct Note: Identifiable {
|
||
var id: URL { path }
|
||
|
||
let name: String
|
||
let path: URL
|
||
}
|
||
```
|
||
|
||
`NoteMessage` to obiekt zawierający zawartość notatki, którą wysyłamy do użytkowników, którzy przyjęli zaproszenie do edycji notatki. Znajdziemy w niej pola `SenderID`, które jest identyfikatorem właściciela notatki oraz `content`, czyli właściwą zawartość notatki. Jest ona również używana do wysyłania każdej aktualizacji do wszystkich użytkowników. Obiekt implementuje protokół `Codable`, który jest uniwersalnym interfejsem kodowania danych. Daje to nam wbudowane wsparcie kodowowania do formatów JSON i XML. Mamy możliwość również pisania własnych koderów, które umożliwią zamianę obiektów implementujących `Codable` do wybranych przez nas, innych formatów danych.
|
||
|
||
```swift
|
||
struct NoteMessage: Codable {
|
||
let senderID: String
|
||
let content: String
|
||
}
|
||
```
|
||
|
||
Do reprezentacji zaproszeń stworzyłem obiekt `NoteInvitation`. Przechowuje on informacje potrzebne do obsługi całego procesu zaproszeń między użytkownikami systemu. Składa się z `invitatorID`, który jest wyspecjalizowanym obiektem frameworku `MultipeerConnectivity` i umożliwia identyfikację użytkownika w czasie komunikacji peer to peer. `note` to obiekt reprezentujący notatkę, którą otrzymujemy w zaproszeniu. Składa się on z tytułu i treści, które były aktualne w momencie zapraszania użytkownika. Ostatnim i jednocześnie prywatnym parametrem jest `invitationHandler`, który jest typem funkcji przyjmującej wartość boolowską (prawda/fałsz). Jest on wykorzystywany do wstrzyknięcia logiki akceptacji notatki, która wymaga kilku operacji w logice klasy obsługującej przyjmowanie zaproszeń. Dzięki takiemu podejściu tworzymy luźną zależność do skomplikowanego obiektu na dalszych etapach systemu. `NoteInvitation` zawiera również metody `accept()` oraz `decline()`, które odpowiadają za wykonanie akcji zaproszenia, które pod spodem odpowiednio używają parametru `invitationHandler`, który można wykonać tylko raz, ze względu na specyfikę `Multipeer Connectivity`, a następnie usuwamy go z pamięci.
|
||
|
||
```swift
|
||
struct NoteInvitation: Identifiable {
|
||
struct NoteContent: Codable {
|
||
let title: String
|
||
let noteSnapshot: String
|
||
}
|
||
|
||
var id: MCPeerID { invitatorID }
|
||
var noteName: String { note.title }
|
||
let invitatorID: MCPeerID
|
||
let note: NoteContent
|
||
private var invitationHandler: ((Bool) -> Void)?
|
||
|
||
init(
|
||
invitatorID: MCPeerID,
|
||
note: NoteContent,
|
||
invitationHandler: ((Bool) -> Void)? = nil
|
||
) {
|
||
self.invitatorID = invitatorID
|
||
self.note = note
|
||
self.invitationHandler = invitationHandler
|
||
}
|
||
|
||
mutating func accept() {
|
||
invitationHandler?(true)
|
||
invitationHandler = nil
|
||
}
|
||
|
||
mutating func decline() {
|
||
invitationHandler?(false)
|
||
invitationHandler = nil
|
||
}
|
||
}
|
||
```
|
||
|
||
Ostatnim obiektem jest struktura `Peer`, która jest opisem aktualnego stanu połączenia wykrytego innej instancji systemu w pobliżu użytkownika.
|
||
|
||
== Warstwa sieciowa i komunikacja P2P
|
||
|
||
|
||
== Odkrywanie innych urządzeń
|
||
== Transportowanie danych
|
||
== Algorytm rozwiązywania konfliktów
|
||
== Środowisko developerskie i stack technologiczny
|
||
== Implementacja logiki P2P
|
||
== Interfejs użytkownika
|
||
== Napotkane wyzwania implementacyjne i rozwiązania
|
||
== Ograniczenia środowisk iOS/macOS
|