Skip to content
All work

03 · Campus social app

Campify

A campus social app on web, iOS and Android. Students import their timetable from a screenshot, see when friends are free, and find people in their classes.

Campify's friend schedule screen
iOS · ANDROID · WEB
StackFlutterReactFirebaseNode.js
students onboarded
300+
students onboarded
across multiple universities
platforms shipped
3
platforms shipped
iOS · Android · web
profile completion
+17 pts
profile completion
54% → 71% after redesign
to import a timetable
1 photo
to import a timetable
on-device OCR
Smart scheduling: see when friends are free
Campus feed screen
Course ratings screen
Profile and campus activity screen

Why

Campus life runs on schedules, but nobody shares them.

Meeting up between classes usually means a group chat full of timetable screenshots. Campify puts everyone's timetable in one place. You can see when friends are free, find people in your courses, and follow what's happening on campus.

I built and launched it on iOS, Android and the web with Flutter, React, Firebase and Node.js. It grew to 300+ students across multiple universities, which meant real data and real users finding edge cases I hadn't thought of.

Timetable import

Every school's timetable looks different. The parser can't assume a layout, so it works from geometry and patterns.

From a messy screenshot to a structured schedule.

  1. 01

    On-device OCR

    ML Kit text blocks, lines and boxes

  2. 02

    Find day columns

    header labels, or x-clustering if none

  3. 03

    Extract class cards

    course codes, times, rooms via regex

  4. 04

    De-duplicate

    merge boxes with heavy IoU overlap

  5. 05

    User review

    low-confidence rows get confirmed

  6. 06

    Write schedule

    Firestore + optional .ics

The hard part was defensive parsing. Course codes come as MAT223H5 or PHYSICS 1D03, times as 9:30 PM, 9PM or 21:30, and OCR breaks words in odd places. So the parser looks at neighbouring lines in a block, normalizes everything to 24-hour time, and falls back to clustering by x-position when no day headers are found. Nothing is written until the student has checked the rows.

schedule_image_import.dart
// course codes: MAT223H5 / MAT 223H5, then looser fallbacks
final _reCourseTight = RegExp(r'\b([A-Z]{3}\s?\d{3}[A-Z]\d?)\b');
final _reCourseLoose = RegExp(r'\b([A-Z]{2,}\s?\d{3}[A-Z]?\d?)\b');
// 9:30 PM – 11 PM  →  21:30–23:00
final _reTimeAMPM = RegExp(r'(\d{1,2}(:\d{2})?\s?(AM|PM))\s*[-–]\s*…');

Recommendations

The social graph and the schedule data feed one ranking, so a suggestion reflects both who you know and where you are.

Friend suggestions that make sense.

01

Mutual connections

People your friends already know.

02

Schedule overlap

Shared courses and free time, from imported timetables.

03

Social proximity

How close you already are in the network.

04

Interaction signals

Who you actually engage with in the app.

Onboarding

I used the data to fix the funnel.

Many new students never finished their profile, and an empty profile makes a social app feel empty. I looked at where new users dropped off during activation and redesigned onboarding around those points.

Profile completion+17 percentage points

Before
54%
After
71%