-
Notifications
You must be signed in to change notification settings - Fork 2
Design Rationale
Low-literate NT2 people in The Netherlands have a hard time dealing with formal letters they don't but want to understand. It's very difficult for them to assess if the letters require immediate action. They usually have to ask help from community centers, neighbors or family, which may take more time than necessary. Another factor is that more than enough people inside this demographic are ashamed of being low-literate, which makes opening up about it and asking for help difficult.
So the solution to this problem should be fast and anonymous, so that users don't feel uncomfortable asking for help. This is how we formulate the design challenge from the problem statement:
How can we develop a mobile application where low-literate NT2 citizens get explanations for letters they don't understand?
Our client for this project is Tessa De Goede, alumnus of Communication & Multimediadesign. For her graduation project Tessa came up with the idea to create a application for functional illiterate people. The current problem for this target audience is that they don't speak and write the dutch language well and have problems with understanding letters they receive from organisations and bodies. Currently these people are dependent on their neighbours, family or community centers to explain the letters to them. The steps a functinal illiterate person has to take to get the letters explained to them are currently cumbersome and time consuming.
The Wat Zegt Deze Brief application wants to resolve this problem by providing a platform where functional illiterate people can submit the letters they receive from organisaties and bodies. Volunteers will be active on the platform and translate/explain these letters to these people so they know which steps they have to take next. Thus eliminating the time consuming process of finding a person that can help them explain the letter.
The research and design of the application has already been delivered by our client. Our job is to create a web-application for this concept. For this project we will be working with the Scrum methodology, this is an agile development methodology where we, the developers, will working in an iterative process. We will be working in sprints of 1 week to develop the functionalities of the application and present the prototype to our client at the end of the week. This helps us generate value for our client by working in an effective way with clear communication between the client and us (the developers).
For the client it is important that we develop a working application that has the following core functionalities:
- Users need to be able to chat with each other in voice memos through a chat functionality
- Users need to be able to take pictures and submit them
- Users need to be able to provide a spoken explanation with the use of the dictaphone functionality
For the client it is important that she has a clear idea of our progress during this project. To follow the progress of this project it is important to the client that we submit a prototype at the end of every week and discuss the current progress.
This project has been made available to us by the the course Communication & Multimediadesign of the Hogeschool van Amsterdam. For this project we are assigned to a coach (Vasilis van Gemert) that will coach us during the process of this project. Every week we will be meeting with our coach to discuss the current progress of the project and receive feedback and guidance. To be graded at the end of the project we will have to provide the following deliverables:
- Design Rationale
- Product Biography
- Individual reflection on the project
- A happy customers
Based on these 4 deliverables we will be graded.
The target audience consists of two groups. The functional illiterate people and the volunteers that will help them explaining the letters. It is important to the target audience that an application is provided that fullfills their needs. For the functional illiterate people it is important that the application has the following aspects:
- Interface of the application doesn't have to much text
- The application should persist the language level A2
- Text in the application should be provided with an image to make it more clear
- The application should give a spoken explanation of the submitted letters
- The application should take away the shame they experience when they ask for help
- The application should be able to provide explanation in the mother tongue of the user
For the volunteers it is important that the application provides the following aspects:
- Volunteers need to be able to provide a spoken explanation for the letters
- Volunteers should be ale to read the submitted letters of the users
- Volunteers should be be able to choose for which submitted letters they want to provide an explanation
For us, the developers, it is important that we deliver an working application that fulfills the client's and the users needs. To achieve this it is important that we will be making a schedule that we will follow to provide a prototype every week for the client to see and discuss our current progress.
- OCR and redaction for scanned documents
- Dictaphone on the web
- Notifications in web apps
- Kickoff
- Write debriefing
- Think up User Scenarios/Job Stories
- Common reusable atomic components
- Onboarding
- User accounts
- Letter
- Photo
- Sender
- Upload date
- Amount of pages
- Page editing
- Urgency (based on date, maybe on amount of reminders)
- Resolved
- Present iteration 1
- Retrospective and planning (Product biography)
- Process feedback
- Volunteer features
- Review pages
- Respond to letters
- Filters and sorting
- Chat feature
- Automated text responses
- Voice messages
- Present iteration 2
- Retrospective and planning (Product biography)
- Process feedback
- Finish all features
- Present iteration 3
- Retrospective and planning (Product biography)
- Process feedback
- Enhance features
- Present iteration 4
- Retrospective and planning (Product biography)
- Process feedback
- Enhance features
- Present final iteration
- Expo
- Project management & setup
- Documentation
- Front-end
- Creative
- Interaction
- Technical
- Support
- Back-end
- Data flow
- Authentication
- Real-time
- Support
- Project management & setup
- Front-end: Interaction & Creative
- Back-end: Authentication
- Front-end: Technical & Interaction
- Back-end: Data flow & Support
- Documentation
- Front-end: Technical & Creative
- Back-end: Real-time & Support
A working prototype which started from the concept and designs from the client, but has been iterated on to create a unique product which will be of great value to the customers who use it.
A rationale, by the entire team, consisting the following:
- Debriefing
- Problem definition
- The final solution
- Code explanations
A product biography, by all individuals part of the team, consisting the following:
- A weekly log of the work that's been done and the process
- Sketches, tests, code examples and inspiration
A personal reflection, from your perspective and level of expertise, containing the following:
- Which subjects came in handy, and why, by looking back at their rubrics.
- Possible points of improvement on technique, interaction and/or other aspects of the design process.
Our solution to this problem is a fully interactive prototype that uses actual data. This prototype is housed in a web application using SvelteKit as its framework. The following core functionalities have been integrated into the prototype:
- User authentication through e-mail and password.
- User roles (regular user or volunteer).
- Submitting letters.
- Uploading pages.
- Chat through voice and text messages.
- Volunteers can explain letters to users.
- Users can mark letters as resolved when the explanation is sufficient.
These features are implemented in two flows: a "regular user" and "volunteer" flow. These flows are explained in this pdf document
- The user goes through the onboarding flow, optionally selects the languages they speak and registers an account.
- The user submits a new letter.
- The user takes photos of the pages of the letter.
- They add extra metadata about the letter.
- The user waits for an explanation.
- When a volunteer has explained the letter, the user can chat with them to ask extra questions.
- When the explanation is sufficient, the user can tell that automatically to the volunteer, resolving the letter.
- The volunteer goes through the onboarding flow and registers an account.
- The volunteer sees all letters that need an explanation and chooses one.
- The volunteer records an explanation.
- The volunteer chats with the user afterwards in case further explanation is needed.
- When the user is satisfied with the explanation, the volunteer gets a thank you note and moves on to the next letter.
We decided to use SvelteKit as our front-end framework. It is very simple to use, as the syntax is similar to plain HTML. The creator of SvelteKit, Rich Harris, has a big preference for building progressively enhanced apps, which is very important to us as well. During this minor we have learned that it is very important to include as many people in your target audience as possible, which progressive enhancement helps with. Considering we don't know where people using WZDB come from, and how technologically advanced they are, this is something we had to keep in the back of our mind constantly.
SvelteKit also has support for back-end functions, which makes creating a separate back-end app unnecessary. This keeps us focussed on building out nice features, without being concerned about authentication throughout both the front-end and back-end.
These back-end functions, as well as part of the front-end, interact with our database solution which is Supabase. Supabase is a database, authentication and cloud file storage solution in one. It has a JavaScript client which packs all these features in a simple-to-use package for us to use in our application.
All these choices have been made to make it as easy for us to create new features, and iterate as fast as possible.
Other tools we used:
We used the main SvelteKit project setup as our baseline, and added Atomic Design principles onto it. We have built up our components using these principles, so they can be reused as often as necessary throughout our app. We used custom paths to put different parts of our application into their own folder, and import them easier where we need them:
<!-- Example homepage Svelte Component -->
<script lang="typescript">
/**
* '$templates' points to the index.ts file inside the templates folder
* where all templates are exported.
*/
import { NavBar } from '$templates'
/**
* All TypeScript types live inside the custom '$types' path, which makes
* it very easy reuse types.
*/
import type { Letter } from '$types'
export let letters: Letter[]
</script>
<header>
<h1>Home page</h1>
</header>
<main>
<ul>
{#each letters as letter (letter.id)}
<li>
<LetterCard letter={letter} />
</li>
{/each}
</ul>
</main>
<NavBar links={['/home', '/chat']} />
We also use custom paths to preconfigure certain parts of our app, so we can reuse them everywhere else without constantly having to set them up:
// src/lib/config/supabase.ts
/**
* Environment variables from src/lib/config/env.ts
*/
import { SUPABASE_KEY, SUPABASE_URL } from "$config/env";
import { createClient } from "@supabase/supabase-js";
/**
* This client we can use everywhere in the app by using `import { client } from '$config/supabase'`
*/
export const client = createClient(SUPABASE_URL, SUPABASE_KEY, {
persistSession: true,
autoRefreshToken: true,
});
We set this up in our tsconfig.json
, which we then import into our svelte.config.js
to make sure SvelteKit knows of these custom paths.
// tsconfig.json
{
"paths": {
"$atoms": ["src/lib/components/atoms"],
"$molecules": ["src/lib/components/molecules"],
"$organisms": ["src/lib/components/organisms"],
"$templates": ["src/lib/components/templates"],
"$icons": ["src/lib/icons"],
"$actions": ["src/lib/actions"],
"$utils": ["src/lib/utils"],
"$types": ["src/lib/types"],
"$stores": ["src/lib/stores"],
"$config/*": ["src/lib/config/*"],
"$db/*": ["src/lib/db/*"]
}
}
// svelte.config.js
import { resolve } from "path";
import { createRequire } from "module";
const require = createRequire(import.meta.url);
const tsConfig = require("./tsconfig.json");
const config = {
//...
kit: {
vite: () => ({
resolve: {
// All paths from the tsconfig.json file are processed to remove "/*" from them if necessary.
alias: Object.entries(tsConfig.compilerOptions.paths).reduce(
(acc, [alias, path]) => ({
...acc,
[alias.replace("/*", "")]: resolve(
import.meta.url,
`/${path[0].replace("/*", "")}`
),
}),
{}
),
},
}),
},
};
All data we store is user-created or otherwise created by us. We have created several data structures to hold all necessary data. Below are all data structures with some sample data.
Supabase has a standard "auth" database it uses to store user authentication data. We wanted to store extra information per user, so we created our own database table for users containing the following information:
{
"id": "uuid", // Points to the same UUID in the auth database
"name": "John", // Optional
"role": "user", // "user" or "volunteer"
"languages": ["nl", "en", "fr"] // Optional
}
We have a few onboardings inside the app, and we wanted to provide a way for the users to skip them after first seeing them. This table holds data per user for this exact feature.
{
"id": 1,
"user_id": "uuid", // Points to user table
"app_onboarding": true, // The onboarding you go through before registering an account
"letter_onboarding": false // The onboarding you go through when submitting a letter
}
Letters contain a lot of data, some of which is: the user that created it, the volunteer who explains it, all the messages that have been sent and the pages it contains in the order the user put them. The data has a lot of relations to other parts of the database, which was very tough to wrap our heads around. We succeeded in the end though.
{
"id": "uuid",
"sender": "HvA", // Optional
"volunteer_id": "uuid", // Points to user in user table
"user_id": "uuid", // Points to user in user table
"status": "draft", // "draft", "published" or "resolved"
"messages": ["uuid1", "uuid2"], // IDs of messages in the message table
"page_order": ["uuid1", "uuid2", "uuid3"], // Names of the page images in the order the user put them in
"deadline": "18-06-2021" // Optional
}
A message can be of two types: "audio" or "text". When the type is "audio", the content of the message is the name of the audio file in our file storage (more on that later). When it is "text", the content is the literal text content of the message.
{
"id": "uuid",
"sender_id": "uuid", // Points to user in user table,
"type": "text", // "text" or "audio"
"content": "Dank u wel!", // Either text or uuid if the message type is "audio",
"date": "2021-06-17T16:09:20" // Timestamp on which the message was sent.
}
Message statuses contain extra metadata about the message, in this case if it has been read or not.
{
"id": 1,
"message_id": "uuid", // Points to message in message table
"user_id": "uuid", // Points to user who receives the message
"read": false
}
As you can see, there are a lot of different data objects to deal with. With TypeScript however, we are able to easily add typing for this data, so we don't have to guess what we have access to. We get basic type information from Supabase itself through their dynamic OpenAPI implementation, although this only covers data directly accessible in the table. We have created some of our own types to cater to our use cases more, and to add relational data.
interface Letter {
id: string;
image: string;
sender?: string;
createdAt: string;
user: {
id: string;
name?: string;
languages?: string[];
};
volunteer?: {
id: string;
name?: string;
};
messages: ChatMessage[];
page_order: string[];
status: "draft" | "published" | "resolved";
deadline?: string;
}
interface Message {
id: string;
sender: {
id: string;
name?: string;
};
content: string;
type: "audio" | "text";
date: string;
read: boolean;
}
interface AudioMessage extends Message {
type: "audio";
file: Blob;
}
interface TextMessage extends Message {
type: "text";
file: never;
}
type ChatMessage = AudioMessage | TextMessage; // ChatMessage is either AudioMessage _or_ TextMessage
With Supabase, we also store files. Specifically pages of letters and audio messages. Supabase makes it super easy to upload files to what are called "storage buckets", you just pass in a file to its client, give it a name and say in which bucket it belongs. Supabase does the rest!
import { client } from "$config/supabase";
import { v4 as uuid } from "uuid";
async function uploadMessage(file: File, letterId: string) {
const userId = client.auth.session().user.id;
const messageId = uuid();
// Upload file with the unique id generated above to a unique path.
await client.storage
.from("messages")
.upload(`${letterId}/${userId}/${messageId}`, file);
// Insert the message into the messages table.
const { body } = await client
.from("messages")
.insert({
sender_id: userId,
content: messageId,
type: "audio",
})
.single();
// Upload the list of messages on the letter.
await client.rpc("update_letter_messages", {
letter_id: letterId,
message_id: body.id,
});
}
Supabase also supports realtime database updates. With the chat feature, we listen to the the letter the chat is for and update the interface whenever a new message is sent.
// Part of the chat page Svelte Component. Abstracted a lot of fluff away for clarity.
let letter: Letter;
let messages: ChatMessage[];
client
.from<definitions["letters"]>(`letters:id=eq.${letter.id}`) // Subscribe to changes on the letter that we're currently chatting about.
.on("UPDATE", async ({ new: newLetter }) => {
// The status may always change, so we update it just to be sure.
letter = {
...letter,
status: newLetter.status,
};
// If there are no new messages, don't execute the rest of the function's code.
if (messages.length === newLetter.messages.length) return;
const messageID = newLetter.messages[newLetter.messages.length - 1];
// Fetch the message with the newly added message.
const message = await fetchMessage(messageID);
// If the type of message is not audio, just add it to the array of messages and return
if (message.type !== "audio") {
messages.push(message);
messages = messages; // Necessary to make Svelte know that this variable has been updated.
return;
}
// Download the audio file for the message
const file = await downloadAudioMessage(
newLetter.id,
message.sender.id,
message.content
);
// Add the message, with the file, to the array.
messages.push({
...message,
file,
});
messages = messages; // Same reason as above.
})
.subscribe();
With this feature, it's stupid easy to get a feature to work in realtime. It is too bad though that you can't subscribe to only certain columns of the table, every change to that database entry you get served by subscribing to it. Nevertheless it is a very nice feature, certainly helping with eliminating the need for a separate back-end solution.
We are very happy with how far we have gotten with this project. It was a very insightful and educational experience. A product is never finished however, and we would've loved to put some more attention in certain aspects of the application if we had the time to do so.
Right now, the application doesn't have any push notifications. E-mail notifications are also not a thing. We would've loved to add this functionality, as it highly improves the chatting experience by giving real time updates t ousers. This is very important when you have a letter that might be urgent, so knowing that you have a new message about is crucial in that regard.
On almost every page for regular users, there is a button that gives people who want extra help with a page an explanation of what needs to helping with both visual and auditory aid. We have this position on every spot on which it is necessary, but they do nothing at the moment. In the design, most of the screens already had the help button worked out, but becasue we also changed some of the pages and in some cases even the general flow of the application, it was unfeasable to recreate the feature with the time we had. It's a definite must though. The feature aligns very well with the philosophy of the application.
Like, come on. Who wouldn't want a slick waveform go across their screen while they record their voice messages? It needed to be in the application yesterday. But in all seriousness, a general layer of polish on the visuals would've been a great way to close out the project.
All in all though, we are again very happy with how far we got in the short amount of time we had for the project, and are curious how it finally turns out.