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:
- Go to your Supabase project dashboard
- Click on the "Settings" icon in the sidebar
- Select "API" from the settings menu
- Copy the "Project URL" and paste it as your SUPABASE_URL
- Copy the "anon public" key and paste it as your SUPABASE_ANON_KEY
Prerequisites
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

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

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
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

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

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
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