Getting Started

Introduction

AkbayMed is a Flutter-based Android application designed to facilitate medication donation and distribution in Philippine healthcare centers. The app connects donors with patients in need, ensuring safe and transparent medicine redistribution.

Medication Donation

Easy and secure way to donate medications

Patient Support

Connect patients with needed medications

Safe Distribution

Verified and secure medication handling

Installation

Follow these steps to set up the development environment:

# Clone the repository
git clone https://github.com/lucifron28/AkbayMed_User.git
cd AkbayMed_User

# Install dependencies
flutter pub get

# Create .env file
cp .env.example .env

Environment Configuration

Create a `.env` file in the root directory with the following variables:

# Supabase Configuration
SUPABASE_URL=your_supabase_project_url
SUPABASE_ANON_KEY=your_supabase_anon_key

# Optional: Development Configuration
DEBUG=true
LOG_LEVEL=debug

Getting Supabase Credentials:

  1. Go to your Supabase project dashboard
  2. Click on the "Settings" icon in the sidebar
  3. Select "API" from the settings menu
  4. Copy the "Project URL" and paste it as your SUPABASE_URL
  5. Copy the "anon public" key and paste it as your SUPABASE_ANON_KEY

Prerequisites

Flutter SDK 3.x
Android Studio / VS Code
Android SDK (API 26+)
Git

Features

Authentication

Secure and user-friendly authentication system for both donors and patients.

  • Email and password-based authentication
  • User registration with role selection
  • Secure session management
  • Profile management with avatar upload
Login Screen

Login Screen

Registration Screen

Registration Screen

Medication Donation

Streamlined process for donors to contribute medications to those in need.

  • Donation submission with medication details
  • Integration with openFDA API for medication verification
  • Expiration date tracking
  • Donation history and status tracking
Donation Screen

Donation Screen

Medication Search

Medication Search

Medication Requests

Easy-to-use interface for patients to request needed medications.

  • Browse available medications
  • Submit medication requests
  • Track request status
  • View request history
Request Screen

Request Screen

User Profile

Personalized user experience with comprehensive profile management.

  • View and edit personal information
  • Track donation/request history
  • Update profile picture
  • Manage account settings
Profile Screen

Profile Screen

Update Profile

Update Profile

Home Dashboard

Centralized hub for all user activities and quick access to features.

  • Personalized welcome screen
  • Activity tracking
  • Quick access to main features
  • Recent transactions overview
Home Screen

Tech Stack

Frontend

Flutter 3.29.3

Cross-platform UI framework

Dart 3.7.2

Programming language

Material 3

UI Components

Backend

Supabase Auth

Authentication service

Supabase PostgreSQL

Database service

Supabase Storage

File storage service

Dependencies

dependencies:
  flutter:
    sdk: flutter
  supabase_flutter: ^2.9.0
  flutter_dotenv: ^5.2.1
  logger: ^2.5.0
  image_picker: ^1.0.4
  path: ^1.8.3
  permission_handler: ^11.0.0
  http: ^1.4.0
  intl: ^0.19.0

API Integration

openFDA API

Integration with the openFDA API for medication verification and information.

Future> searchMedication(String brandName) async {
  final response = await http.get(
    Uri.parse('https://api.fda.gov/drug/label.json?search=brand_name:$brandName&limit=1'),
  );

  if (response.statusCode == 200) {
    return json.decode(response.body);
  } else {
    throw Exception('Failed to load medication data');
  }
}

Supabase Integration

Authentication, database, and storage integration with Supabase.

// Authentication
final supabase = Supabase.instance.client;
final response = await supabase.auth.signInWithPassword(
  email: email,
  password: password,
);

// Database Operations
final data = await supabase
  .from('donations')
  .select()
  .order('created_at', ascending: false);

Database

Tables

-- Users Table
CREATE TABLE users (
    id UUID PRIMARY KEY REFERENCES auth.users(id),
    name TEXT NOT NULL,
    email TEXT NOT NULL,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    avatar_url TEXT,
    is_verified BOOLEAN NOT NULL DEFAULT TRUE,
    donation_count BIGINT NOT NULL DEFAULT 0
);

-- Medications Table
CREATE TABLE medications (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    name TEXT NOT NULL,
    description TEXT,
    category TEXT NOT NULL,
    created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW()
);

-- Donations Table
CREATE TABLE donations (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    donor_id UUID NOT NULL REFERENCES users(id),
    medication_id UUID NOT NULL REFERENCES medications(id),
    quantity INTEGER NOT NULL,
    expiration_date DATE NOT NULL,
    -- Note: Default 'approved' status is for prototype demonstration purposes only
    -- In production, this should be 'pending' with proper approval workflow
    status VARCHAR NOT NULL DEFAULT 'approved',
    created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW()
);

-- Requests Table
CREATE TABLE requests (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    patient_id UUID NOT NULL REFERENCES users(id),
    medication_id UUID NOT NULL REFERENCES medications(id),
    quantity INTEGER NOT NULL,
    -- Note: Default 'approved' status is for prototype demonstration purposes only
    -- In production, this should be 'pending' with proper approval workflow
    status VARCHAR NOT NULL DEFAULT 'approved',
    created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW()
);

-- Inventory Table
CREATE TABLE inventory (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    medication_id UUID REFERENCES medications(id),
    quantity INTEGER NOT NULL DEFAULT 0,
    expiration_date DATE,
    created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW()
);

-- Appointments Table
CREATE TABLE appointments (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    user_id UUID REFERENCES users(id),
    donation_id UUID REFERENCES donations(id),
    request_id UUID REFERENCES requests(id),
    appointment_date TIMESTAMP WITHOUT TIME ZONE NOT NULL,
    type VARCHAR NOT NULL,
    created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW()
);

Table Relationships

  • Users (1) → (N) Donations: One user can make multiple donations
  • Users (1) → (N) Requests: One user can make multiple requests
  • Users (1) → (N) Appointments: One user can have multiple appointments
  • Medications (1) → (N) Donations: One medication can be donated multiple times
  • Medications (1) → (N) Requests: One medication can be requested multiple times
  • Medications (1) → (1) Inventory: One medication has one inventory record
  • Donations (1) → (N) Appointments: One donation can have multiple appointments
  • Requests (1) → (N) Appointments: One request can have multiple appointments

Key Features

  • UUID primary keys for all tables
  • Automatic timestamp management for created_at and updated_at
  • Foreign key constraints for referential integrity
  • Default values for status fields
  • Proper indexing on frequently queried columns
  • Timestamp with time zone for user-related timestamps
  • Timestamp without time zone for business logic timestamps

Database Functions

Donation Count Management

Automatically tracks and updates the donation count for users when a donation is approved.

CREATE OR REPLACE FUNCTION increment_donation_count()
RETURNS TRIGGER AS $$
BEGIN
    IF (TG_OP = 'INSERT' AND NEW.status = 'approved') OR
       (TG_OP = 'UPDATE' AND NEW.status = 'approved' AND OLD.status != 'approved') THEN
        UPDATE public.users
        SET donation_count = donation_count + 1
        WHERE id = NEW.donor_id;
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER donation_count_trigger
AFTER INSERT OR UPDATE ON donations
FOR EACH ROW
EXECUTE FUNCTION increment_donation_count();

Donation Appointment Management

Automatically creates dropoff appointments for approved donations, scheduled 3 days after approval.

CREATE OR REPLACE FUNCTION create_donation_appointment()
RETURNS TRIGGER AS $$
BEGIN
    IF (TG_OP = 'INSERT' AND NEW.status = 'approved') OR
       (TG_OP = 'UPDATE' AND NEW.status = 'approved' AND OLD.status != 'approved') THEN
        IF NOT EXISTS (
            SELECT 1 FROM appointments
            WHERE donation_id = NEW.id
        ) THEN
            INSERT INTO appointments (
                user_id,
                donation_id,
                appointment_date,
                type,
                created_at
            )
            VALUES (
                NEW.donor_id,
                NEW.id,
                CURRENT_TIMESTAMP + INTERVAL '3 days',
                'dropoff',
                NOW()
            );
        END IF;
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER donation_appointment_trigger
AFTER INSERT OR UPDATE ON donations
FOR EACH ROW
EXECUTE FUNCTION create_donation_appointment();

Request Appointment Management

Automatically creates pickup appointments for approved requests, scheduled 3 days after approval.

CREATE OR REPLACE FUNCTION create_request_appointment()
RETURNS TRIGGER AS $$
BEGIN
    IF (TG_OP = 'INSERT' AND NEW.status = 'approved') OR
       (TG_OP = 'UPDATE' AND NEW.status = 'approved' AND OLD.status != 'approved') THEN
        IF NOT EXISTS (
            SELECT 1 FROM appointments
            WHERE request_id = NEW.id
        ) THEN
            INSERT INTO appointments (
                user_id,
                request_id,
                appointment_date,
                type,
                created_at
            )
            VALUES (
                NEW.patient_id,
                NEW.id,
                CURRENT_TIMESTAMP + INTERVAL '3 days',
                'pickup',
                NOW()
            );
        END IF;
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER request_appointment_trigger
AFTER INSERT OR UPDATE ON requests
FOR EACH ROW
EXECUTE FUNCTION create_request_appointment();

Inventory Management

Manages inventory levels for both donations and requests, including FIFO allocation and expiration date tracking.

Request Inventory Update
CREATE OR REPLACE FUNCTION update_inventory_from_request()
RETURNS TRIGGER AS $$
DECLARE
    total_available INTEGER;
    remaining_request INTEGER := NEW.quantity;
    rec RECORD;
BEGIN
    IF (TG_OP = 'INSERT' AND NEW.status = 'approved' AND NEW.medication_id IS NOT NULL) OR
       (TG_OP = 'UPDATE' AND NEW.status = 'approved' AND OLD.status != 'approved' AND NEW.medication_id IS NOT NULL) THEN
        SELECT COALESCE(SUM(quantity), 0) INTO total_available
        FROM inventory
        WHERE medication_id = NEW.medication_id
        AND (expiration_date IS NULL OR expiration_date >= CURRENT_DATE);

        IF total_available < NEW.quantity THEN
            RAISE EXCEPTION 'Insufficient inventory for medication_id %: requested %, available %',
                NEW.medication_id, NEW.quantity, total_available;
        END IF;

        FOR rec IN (
            SELECT id, quantity
            FROM inventory
            WHERE medication_id = NEW.medication_id
            AND (expiration_date IS NULL OR expiration_date >= CURRENT_DATE)
            AND quantity > 0
            ORDER BY COALESCE(expiration_date, '9999-12-31'::DATE) ASC
        ) LOOP
            IF remaining_request <= 0 THEN
                EXIT;
            END IF;
            DECLARE
                deduct_amount INTEGER := LEAST(remaining_request, rec.quantity);
            BEGIN
                UPDATE inventory
                SET quantity = quantity - deduct_amount,
                    updated_at = NOW()
                WHERE id = rec.id;
                remaining_request := remaining_request - deduct_amount;
            END;
        END LOOP;
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER request_inventory_trigger
AFTER INSERT OR UPDATE ON requests
FOR EACH ROW
EXECUTE FUNCTION update_inventory_from_request();
Donation Inventory Update
CREATE OR REPLACE FUNCTION update_inventory_from_donation()
RETURNS TRIGGER AS $$
DECLARE
    remaining_quantity INTEGER := NEW.quantity;
    rec RECORD;
BEGIN
    IF (TG_OP = 'INSERT' AND NEW.status = 'approved' AND NEW.medication_id IS NOT NULL) OR
       (TG_OP = 'UPDATE' AND NEW.status = 'approved' AND OLD.status != 'approved' AND NEW.medication_id IS NOT NULL) THEN
        INSERT INTO inventory (
            medication_id,
            quantity,
            expiration_date,
            source,
            donation_id,
            created_at,
            updated_at
        )
        VALUES (
            NEW.medication_id,
            NEW.quantity,
            NEW.expiration_date,
            'donation',
            NEW.id,
            NOW(),
            NOW()
        );
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER donation_inventory_trigger
AFTER INSERT OR UPDATE ON donations
FOR EACH ROW
EXECUTE FUNCTION update_inventory_from_donation();

Row Level Security

-- Enable RLS
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
ALTER TABLE medications ENABLE ROW LEVEL SECURITY;
ALTER TABLE donations ENABLE ROW LEVEL SECURITY;
ALTER TABLE requests ENABLE ROW LEVEL SECURITY;
ALTER TABLE inventory ENABLE ROW LEVEL SECURITY;
ALTER TABLE appointments ENABLE ROW LEVEL SECURITY;

-- Users table policies
CREATE POLICY "Users can view their own profile"
    ON users FOR SELECT
    USING (auth.uid() = id);

CREATE POLICY "Users can update their own profile"
    ON users FOR UPDATE
    USING (auth.uid() = id);

-- Medications table policies
CREATE POLICY "Anyone can view medications"
    ON medications FOR SELECT
    USING (true);

CREATE POLICY "Only admins can modify medications"
    ON medications FOR ALL
    USING (auth.uid() IN (SELECT id FROM users WHERE role = 'admin'));

-- Donations table policies
CREATE POLICY "Users can view their own donations"
    ON donations FOR SELECT
    USING (auth.uid() = donor_id);

CREATE POLICY "Users can create donations"
    ON donations FOR INSERT
    WITH CHECK (auth.uid() = donor_id);

CREATE POLICY "Users can update their own donations"
    ON donations FOR UPDATE
    USING (auth.uid() = donor_id);

-- Requests table policies
CREATE POLICY "Users can view their own requests"
    ON requests FOR SELECT
    USING (auth.uid() = patient_id);

CREATE POLICY "Users can create requests"
    ON requests FOR INSERT
    WITH CHECK (auth.uid() = patient_id);

CREATE POLICY "Users can update their own requests"
    ON requests FOR UPDATE
    USING (auth.uid() = patient_id);

-- Inventory table policies
CREATE POLICY "Anyone can view inventory"
    ON inventory FOR SELECT
    USING (true);

CREATE POLICY "Only admins can modify inventory"
    ON inventory FOR ALL
    USING (auth.uid() IN (SELECT id FROM users WHERE role = 'admin'));

-- Appointments table policies
CREATE POLICY "Users can view their own appointments"
    ON appointments FOR SELECT
    USING (auth.uid() = user_id);

CREATE POLICY "Users can create appointments"
    ON appointments FOR INSERT
    WITH CHECK (auth.uid() = user_id);

CREATE POLICY "Users can update their own appointments"
    ON appointments FOR UPDATE
    USING (auth.uid() = user_id);

Storage Configuration

-- Create storage bucket for avatars
INSERT INTO storage.buckets (id, name, public) VALUES ('avatars', 'avatars', true);

-- Set up storage policies
CREATE POLICY "Avatar images are publicly accessible"
ON storage.objects FOR SELECT
USING (bucket_id = 'avatars');

CREATE POLICY "Users can upload their own avatar"
ON storage.objects FOR INSERT
WITH CHECK (bucket_id = 'avatars' AND auth.uid() = owner);

CREATE POLICY "Users can update their own avatar"
ON storage.objects FOR UPDATE
USING (bucket_id = 'avatars' AND auth.uid() = owner);

CREATE POLICY "Users can delete their own avatar"
ON storage.objects FOR DELETE
USING (bucket_id = 'avatars' AND auth.uid() = owner);

Error Handling

  • Inventory validation before updates
  • Exception handling for insufficient stock
  • Duplicate appointment prevention
  • Transaction management for data consistency

UI/UX Design

Design System

AkbayMed follows Material Design 3 guidelines for a consistent and modern user experience.

Components

  • Material 3 widgets
  • Custom themed components
  • Responsive layouts

Layout

  • Grid-based system
  • Consistent spacing
  • Responsive breakpoints

Color Scheme

Primary

Teal 700 (#00796B)

Secondary

Teal 900 (#004D40)

Accent

Teal 100 (#B2DFDB)

Typography

Headings

Heading 1

Heading 2

Heading 3

Body Text

Body Large

Body Medium

Body Small