Skip to content

Presentation Layer

The presentation layer contains the user interface components of the feature. The presentation layer is responsible for displaying the feature to the user and handling user interactions.

Getting started

Create a new folder named presentation inside the feature folder. Then create a new file named scratch_note_page.dart inside the presentation folder. This file will contain the UI code for the feature.

Write the UI code

We'll start by creating a simple UI for the scratch notes feature. An example of the UI code is shown below:

dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart';
import 'package:techshoi_app/configure_dependencies.dart';
import 'package:techshoi_app/features/core/extensions/string_extensions.dart';
import 'package:techshoi_app/features/scratch_notes/application/scratch_note_cubit.dart';
import 'package:techshoi_app/features/settings/application/settings_cubit.dart';
import 'package:techshoi_app/features/techshoi_ui/dialogs/techshoi_dialog.dart';
import 'package:techshoi_app/features/techshoi_ui/styles/colors.dart';
import 'package:techshoi_app/l10n/l10n.dart';

class ScratchNotesPage extends StatelessWidget {
  const ScratchNotesPage({super.key});

  static String route = '/scratch-notes';

  @override
  Widget build(BuildContext context) {
    return BlocProvider<ScratchNoteCubit>(
      create: (context) =>
          serviceLocator<ScratchNoteCubit>()..listenForChanges(),
      child: const ScratchNotesBody(),
    );
  }
}

class ScratchNotesBody extends HookWidget {
  const ScratchNotesBody({
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    final controller = useTextEditingController();

    return BlocBuilder<ScratchNoteCubit, ScratchNoteState>(
      builder: (context, state) {
        return Scaffold(
          appBar: AppBar(
            title: Text(context.l10n.appName),
          ),
          body: Padding(
            padding: const EdgeInsets.symmetric(horizontal: 16),
            child: state.isLoading
                ? const Center(
                    child: CircularProgressIndicator(),
                  )
                : !state.isLoading && state.notes.isEmpty
                    ? const Center(
                        child: Text(
                          'If you give people nothingness, they can ponder '
                          'what can be achieved from that nothingness.',
                          textAlign: TextAlign.center,
                        ),
                      )
                    : ListView.builder(
                        itemBuilder: (context, index) {
                          return Card(
                            color: Color(
                              context
                                      .read<SettingsCubit>()
                                      .state
                                      .settings
                                      .isDarkTheme
                                  ? AppColors.darkerInk
                                  : AppColors.lightestSky,
                            ),
                            child: ListTile(
                              title: Text(
                                state.notes[index].title,
                                style: Theme.of(context).textTheme.bodyLarge,
                              ),
                              subtitle: Text(
                                state.notes[index].createdAt
                                    .toIso8601String()
                                    .substring(0, 10),
                                style: Theme.of(context).textTheme.bodySmall,
                              ),
                              onTap: () {
                                controller.text = state.notes[index].title;

                                TechshoiDialog(
                                  context: context,
                                  title: 'Edit Note',
                                  content: TextFormField(
                                    controller: controller,
                                    decoration: const InputDecoration(
                                      hintText: 'Enter your note here',
                                    ),
                                  ),
                                  actions: [
                                    DialogAction(
                                      text: 'Update',
                                      onPressed: () {
                                        if (!controller.text.isNullOrEmpty()) {
                                          context
                                              .read<ScratchNoteCubit>()
                                              .updateScratchNote(
                                                state.notes[index].copyWith(
                                                  title: controller.text.trim(),
                                                ),
                                              );
                                          context.pop();
                                        }
                                      },
                                    ),
                                    DialogAction(
                                      text: 'Delete',
                                      onPressed: () {
                                        context
                                            .read<ScratchNoteCubit>()
                                            .deleteScratchNote(
                                              state.notes[index].id,
                                            );
                                        context.pop();
                                      },
                                    ),
                                  ],
                                ).show();
                              },
                            ),
                          );
                        },
                        itemCount: state.notes.length,
                      ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              controller.text = '';
              TechshoiDialog(
                context: context,
                title: 'Add Note',
                content: TextFormField(
                  controller: controller,
                  decoration: const InputDecoration(
                    hintText: 'Enter your note here',
                  ),
                ),
                actions: [
                  DialogAction(
                    text: 'Add',
                    onPressed: () {
                      if (!controller.text.isNullOrEmpty()) {
                        context.read<ScratchNoteCubit>().createScratchNote(
                              title: controller.text.trim(),
                            );
                        context.pop();
                      }
                    },
                  ),
                ],
              ).show();
            },
            tooltip: 'Add Note',
            child: const Icon(Icons.add),
          ),
        );
      },
    );
  }
}

Note that we have a static route property in the ScratchNotesPage class. This property is used to define the route for the page in the app's router.