Fliin
On this page

Screens / dashboard

Finance plan

Shows the total saved on a band in the theme color, savings goals in a carousel with their pace, monthly budgets as rings with overruns flagged, and adds money to a goal from a sheet.

16 of 16 variants pass
Finance plan, ready state
Platform
Brightness
Text scale
Direction

Import

Finance plan is part of Fliin Pro. With a license key, sign in once with fliin login, then add it in a Flutter app where fliin init has run:

fliin login
fliin add finance-plan

Also installs: app-bar, button, chart, chip, core, empty-state, icons, link-button, menu, page, progress, sheet, skeleton, surface, text-field

Examples

Ready

The savings plan of a finance app: the total saved and the plan's actions on the accent, then the goals in a carousel and the month's budgets as rings, under the app's floating tab bar. "Add money" opens a sheet that moves money from the account to the goal.

Finance plan, ready state
import 'package:your_app/ui/fliin/components/app_bar/fl_app_bar.dart';
import 'package:your_app/ui/fliin/components/tab_bar/fl_tab_bar.dart';
import 'package:your_app/ui/fliin/core/fliin.dart';
import 'package:your_app/ui/fliin/icons/fl_icons.dart';
import 'package:your_app/ui/fliin/screens/finance_plan/finance_plan_controller.dart';
import 'package:your_app/ui/fliin/screens/finance_plan/finance_plan_data.dart';
import 'package:your_app/ui/fliin/screens/finance_plan/finance_plan_screen.dart';
import 'package:flutter/widgets.dart';

class FinancePlanReadyExample extends StatefulWidget {
  const FinancePlanReadyExample({super.key});

  @override
  State<FinancePlanReadyExample> createState() => _FinancePlanReadyExampleState();
}

class _FinancePlanReadyExampleState extends State<FinancePlanReadyExample> {
  int _tab = 2;

  @override
  Widget build(BuildContext context) {
    return FinancePlanScreen.standalone(
      service: const SamplePlanService(latency: Duration(milliseconds: 900)),
      initial: PlanLoaded(samplePlanSnapshot()),
      onMenu: () {},
      actions: [FlAppBarAction(icon: FlIcon(FlIcons.bell), label: 'Notifications', onPressed: () {})],
      onAddBudget: () {},
      onSeeAllGoals: () {},
      onDeleteGoal: (_) {},
      now: samplePlanNow,
      tabBar: FlBottomTabBar(
        floating: true,
        translucent: true,
        labels: FlBottomTabLabels.beside,
        indicator: FlBottomTabIndicator.capsule,
        index: _tab,
        onSelected: (index) => setState(() => _tab = index),
        tabs: [
          FlBottomTab(icon: FlIcon(FlIcons.house), label: 'Home'),
          FlBottomTab(icon: FlIcon(FlIcons.chartColumn), label: 'Report'),
          FlBottomTab(icon: FlIcon(FlIcons.chartDonut), label: 'Plan'),
          FlBottomTab(icon: FlIcon(FlIcons.gear), label: 'Settings'),
        ],
      ),
    );
  }
}

Loading

While the plan loads, skeletons keep the place of the total, a goal card and the budgets.

Finance plan, loading state
import 'package:your_app/ui/fliin/screens/finance_plan/finance_plan_controller.dart';
import 'package:your_app/ui/fliin/screens/finance_plan/finance_plan_data.dart';
import 'package:your_app/ui/fliin/screens/finance_plan/finance_plan_screen.dart';
import 'package:flutter/widgets.dart';

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

  @override
  Widget build(BuildContext context) {
    return FinancePlanScreen.standalone(
      service: const SamplePlanService(latency: Duration(milliseconds: 900)),
      initial: const PlanLoading(),
      now: samplePlanNow,
    );
  }
}

Empty

No goal and no budget yet: the plan invites to set a first goal, or a budget.

Finance plan, empty state
import 'package:your_app/ui/fliin/screens/finance_plan/finance_plan_controller.dart';
import 'package:your_app/ui/fliin/screens/finance_plan/finance_plan_data.dart';
import 'package:your_app/ui/fliin/screens/finance_plan/finance_plan_screen.dart';
import 'package:flutter/widgets.dart';

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

  @override
  Widget build(BuildContext context) {
    return FinancePlanScreen.standalone(
      service: const SamplePlanService(latency: Duration(milliseconds: 900)),
      initial: const PlanEmpty(),
      now: samplePlanNow,
    );
  }
}

Error

The plan could not load: the screen says so and offers to try again.

Finance plan, error state
import 'package:your_app/ui/fliin/screens/finance_plan/finance_plan_controller.dart';
import 'package:your_app/ui/fliin/screens/finance_plan/finance_plan_data.dart';
import 'package:your_app/ui/fliin/screens/finance_plan/finance_plan_screen.dart';
import 'package:flutter/widgets.dart';

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

  @override
  Widget build(BuildContext context) {
    return FinancePlanScreen.standalone(
      service: const SamplePlanService(latency: Duration(milliseconds: 900)),
      initial: const PlanFailed(),
      now: samplePlanNow,
    );
  }
}

Offline

Offline with the plan already loaded: the goals and budgets stay, a notice on the band dates them, and money cannot be added.

Finance plan, offline state
import 'package:your_app/ui/fliin/screens/finance_plan/finance_plan_controller.dart';
import 'package:your_app/ui/fliin/screens/finance_plan/finance_plan_data.dart';
import 'package:your_app/ui/fliin/screens/finance_plan/finance_plan_screen.dart';
import 'package:flutter/widgets.dart';

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

  @override
  Widget build(BuildContext context) {
    return FinancePlanScreen.standalone(
      service: const SamplePlanService(latency: Duration(milliseconds: 900)),
      initial: PlanLoaded(samplePlanSnapshot(), notice: PlanNotice.offline),
      now: samplePlanNow,
    );
  }
}

Offline empty

Offline with nothing loaded yet: the screen asks to reconnect.

Finance plan, offline-empty state
import 'package:your_app/ui/fliin/screens/finance_plan/finance_plan_controller.dart';
import 'package:your_app/ui/fliin/screens/finance_plan/finance_plan_data.dart';
import 'package:your_app/ui/fliin/screens/finance_plan/finance_plan_screen.dart';
import 'package:flutter/widgets.dart';

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

  @override
  Widget build(BuildContext context) {
    return FinancePlanScreen.standalone(
      service: const SamplePlanService(latency: Duration(milliseconds: 900)),
      initial: const PlanOffline(),
      now: samplePlanNow,
    );
  }
}

Routing

fliin add finance-plan prints the route for the router it finds in your pubspec, a file next to the page that wires its navigation. Each of them:

go_router

// lib/finance_plan_route.dart, next to finance_plan_page.dart.
import 'package:go_router/go_router.dart';

import 'finance_plan_page.dart';

/// Add financePlanRoute to the routes of your GoRouter.
final financePlanRoute = GoRoute(
  path: '/plan',
  builder: (context, state) => FinancePlanPage(
    // Alone in the stack (deep link, go): your app's home.
    onBack: () => context.canPop() ? context.pop() : context.go('/'),
  ),
);

navigator

// lib/finance_plan_route.dart, next to finance_plan_page.dart.
import 'package:flutter/material.dart';

import 'finance_plan_page.dart';

/// Opens “Finance plan”: `Navigator.of(context).push(financePlanRoute())`.
Route<void> financePlanRoute() => MaterialPageRoute<void>(
  builder: (context) => FinancePlanPage(
    onBack: () => Navigator.of(context).maybePop(),
  ),
);

State

The controller is a ChangeNotifier from widgets.dart, with no package. fliin add finance-plan prints the page that holds it with the state management it finds, its imports written for your package; below they read your_app. Your backend implements the Service interface of the screen's _data.dart file:

none

// lib/finance_plan_page.dart
// Your backend: implement PlanService (finance_plan_data.dart) and pass it instead of the sample service.
import 'package:your_app/ui/fliin/screens/finance_plan/finance_plan_data.dart';
import 'package:your_app/ui/fliin/screens/finance_plan/finance_plan_screen.dart';
import 'package:flutter/widgets.dart';

class FinancePlanPage extends StatelessWidget {
  const FinancePlanPage({
    super.key,
    required this.onBack,
  });

  final VoidCallback onBack;

  @override
  Widget build(BuildContext context) => FinancePlanScreen.standalone(
    service: const SamplePlanService(),
    onBack: onBack,
  );
}

provider

// lib/finance_plan_page.dart
// Your backend: implement PlanService (finance_plan_data.dart) and pass it instead of the sample service.
import 'package:your_app/ui/fliin/screens/finance_plan/finance_plan_controller.dart';
import 'package:your_app/ui/fliin/screens/finance_plan/finance_plan_data.dart';
import 'package:your_app/ui/fliin/screens/finance_plan/finance_plan_screen.dart';
import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart';

class FinancePlanPage extends StatelessWidget {
  const FinancePlanPage({
    super.key,
    required this.onBack,
  });

  final VoidCallback onBack;

  @override
  Widget build(BuildContext context) => ChangeNotifierProvider(
    create: (_) => FinancePlanController(service: const SamplePlanService())..load(),
    child: Builder(
      builder: (context) => FinancePlanScreen(
        controller: context.read<FinancePlanController>(),
        onBack: onBack,
      ),
    ),
  );
}

riverpod

// lib/finance_plan_page.dart
// Your backend: implement PlanService (finance_plan_data.dart) and pass it instead of the sample service.
import 'package:your_app/ui/fliin/screens/finance_plan/finance_plan_controller.dart';
import 'package:your_app/ui/fliin/screens/finance_plan/finance_plan_data.dart';
import 'package:your_app/ui/fliin/screens/finance_plan/finance_plan_screen.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

final financePlanProvider = Provider.autoDispose<FinancePlanController>((ref) {
  final controller = FinancePlanController(service: const SamplePlanService())..load();
  ref.onDispose(controller.dispose);
  return controller;
});

class FinancePlanPage extends ConsumerWidget {
  const FinancePlanPage({
    super.key,
    required this.onBack,
  });

  final VoidCallback onBack;

  @override
  Widget build(BuildContext context, WidgetRef ref) => FinancePlanScreen(
    controller: ref.watch(financePlanProvider),
    onBack: onBack,
  );
}

bloc

// lib/finance_plan_page.dart
// Your backend: implement PlanService (finance_plan_data.dart) and pass it instead of the sample service.
import 'package:your_app/ui/fliin/screens/finance_plan/finance_plan_controller.dart';
import 'package:your_app/ui/fliin/screens/finance_plan/finance_plan_data.dart';
import 'package:your_app/ui/fliin/screens/finance_plan/finance_plan_screen.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

/// The screen's controller as a bloc, for your BlocListener and BlocBuilder:
/// one emission per controller notification. `version` tells them apart
/// when `state` does not change (mode, filters, text…, read them on
/// `controller`). The screen listens to the controller itself.
class FinancePlanCubit extends Cubit<({PlanState state, int version})> {
  FinancePlanCubit(this.controller) : super((state: controller.state, version: 0)) {
    controller.addListener(_changed);
  }

  final FinancePlanController controller;

  void _changed() => emit((state: controller.state, version: state.version + 1));

  @override
  Future<void> close() {
    controller
      ..removeListener(_changed)
      ..dispose();
    return super.close();
  }
}

class FinancePlanPage extends StatelessWidget {
  const FinancePlanPage({
    super.key,
    required this.onBack,
  });

  final VoidCallback onBack;

  @override
  Widget build(BuildContext context) => BlocProvider(
    create: (_) => FinancePlanCubit(FinancePlanController(service: const SamplePlanService())..load()),
    child: Builder(
      builder: (context) => FinancePlanScreen(
        controller: context.read<FinancePlanCubit>().controller,
        onBack: onBack,
      ),
    ),
  );
}

API

FinancePlanScreen: class extends StatefulWidget
FinancePlanScreen.new: const new({super.key, required FinancePlanController controller, VoidCallback? onBack, VoidCallback? onMenu, List<FlAppBarAction> actions, ValueChanged<PlanGoal>? onEditGoal, ValueChanged<PlanGoal>? onDeleteGoal, VoidCallback? onAddGoal, VoidCallback? onAddBudget, VoidCallback? onSeeAllGoals, Widget? tabBar, PlanFormat? format, DateTime? now})
FinancePlanScreen.standalone: const new({super.key, required PlanService service, PlanState? initial, VoidCallback? onBack, VoidCallback? onMenu, List<FlAppBarAction> actions, ValueChanged<PlanGoal>? onEditGoal, ValueChanged<PlanGoal>? onDeleteGoal, VoidCallback? onAddGoal, VoidCallback? onAddBudget, VoidCallback? onSeeAllGoals, Widget? tabBar, PlanFormat? format, DateTime? now})
FinancePlanScreen.controller: get FinancePlanController?
FinancePlanScreen.service: get PlanService?
FinancePlanScreen.initial: get PlanState?
FinancePlanScreen.onBack: get VoidCallback?
FinancePlanScreen.onMenu: get VoidCallback?
FinancePlanScreen.actions: get List<FlAppBarAction>
FinancePlanScreen.onEditGoal: get ValueChanged<PlanGoal>?
FinancePlanScreen.onDeleteGoal: get ValueChanged<PlanGoal>?
FinancePlanScreen.onAddGoal: get VoidCallback?
FinancePlanScreen.onAddBudget: get VoidCallback?
FinancePlanScreen.onSeeAllGoals: get VoidCallback?
FinancePlanScreen.tabBar: get Widget?
FinancePlanScreen.format: get PlanFormat?
FinancePlanScreen.now: get DateTime?
FinancePlanScreen.createState: State<FinancePlanScreen>()
PlanState: sealed class permits {PlanEmpty, PlanFailed, PlanLoaded, PlanLoading, PlanOffline}
PlanState.new: const new()
PlanLoading: final class extends PlanState
PlanLoading.new: const new()
PlanNotice: enum {refreshFailed, offline}
PlanNotice.refreshFailed: static final PlanNotice
PlanNotice.offline: static final PlanNotice
PlanLoaded: final class extends PlanState
PlanLoaded.new: const new(PlanSnapshot snapshot, {PlanNotice? notice})
PlanLoaded.snapshot: get PlanSnapshot
PlanLoaded.notice: get PlanNotice?
PlanEmpty: final class extends PlanState
PlanEmpty.new: const new({PlanNotice? notice})
PlanEmpty.notice: get PlanNotice?
PlanFailed: final class extends PlanState
PlanFailed.new: const new()
PlanOffline: final class extends PlanState
PlanOffline.new: const new()
PlanAddResult: enum {added, offline, failed}
PlanAddResult.added: static final PlanAddResult
PlanAddResult.offline: static final PlanAddResult
PlanAddResult.failed: static final PlanAddResult
PlanSaveResult: enum {saved, offline, failed}
PlanSaveResult.saved: static final PlanSaveResult
PlanSaveResult.offline: static final PlanSaveResult
PlanSaveResult.failed: static final PlanSaveResult
FinancePlanController: class extends ChangeNotifier
FinancePlanController.new: new({required PlanService service, PlanState initial})
FinancePlanController.service: get PlanService
FinancePlanController.state: get PlanState
FinancePlanController.load: Future<void>()
FinancePlanController.refresh: Future<bool>()
FinancePlanController.addMoney: Future<PlanAddResult>(String goalId, int cents)
FinancePlanController.saveGoal: Future<PlanSaveResult>(PlanGoalDraft draft)
FinancePlanController.dispose: void()
PlanGoal: final class
PlanGoal.new: const new({required String id, required String name, required int saved, required int target, required DateTime due, required double expected, FlIconData? icon})
PlanGoal.id: get String
PlanGoal.name: get String
PlanGoal.saved: get int
PlanGoal.target: get int
PlanGoal.due: get DateTime
PlanGoal.expected: get double
PlanGoal.icon: get FlIconData?
PlanGoal.progress: get double
PlanGoal.left: get int
PlanGoal.behind: get double
PlanGoal.adding: PlanGoal(int cents)
PlanGoalDraft: final class
PlanGoalDraft.new: const new({String? id, required String name, required int target, required DateTime due, FlIconData? icon})
PlanGoalDraft.id: get String?
PlanGoalDraft.name: get String
PlanGoalDraft.target: get int
PlanGoalDraft.due: get DateTime
PlanGoalDraft.icon: get FlIconData?
PlanBudget: final class
PlanBudget.new: const new({required String id, required String name, required int spent, required int limit, FlIconData? icon})
PlanBudget.id: get String
PlanBudget.name: get String
PlanBudget.spent: get int
PlanBudget.limit: get int
PlanBudget.icon: get FlIconData?
PlanBudget.share: get double
PlanBudget.over: get bool
PlanAccount: final class
PlanAccount.new: const new({required String name, required int available})
PlanAccount.name: get String
PlanAccount.available: get int
PlanSnapshot: final class
PlanSnapshot.new: const new({required List<PlanGoal> goals, required List<PlanBudget> budgets, required DateTime month, required int daysLeft, required DateTime updatedAt, PlanAccount? account, int? addedThisMonth})
PlanSnapshot.goals: get List<PlanGoal>
PlanSnapshot.budgets: get List<PlanBudget>
PlanSnapshot.month: get DateTime
PlanSnapshot.daysLeft: get int
PlanSnapshot.updatedAt: get DateTime
PlanSnapshot.account: get PlanAccount?
PlanSnapshot.addedThisMonth: get int?
PlanSnapshot.isEmpty: get bool
PlanSnapshot.saved: get int
PlanSnapshot.adding: PlanSnapshot(String goalId, int cents)
PlanSnapshot.saving: PlanSnapshot(PlanGoal goal)
PlanNoNetwork: final class implements Exception
PlanNoNetwork.new: const new({PlanSnapshot? lastKnown})
PlanNoNetwork.lastKnown: get PlanSnapshot?
PlanService: abstract interface class
PlanService.new: new()
PlanService.load: abstract Future<PlanSnapshot>()
PlanService.addMoney: abstract Future<void>(String goalId, int cents)
PlanService.saveGoal: abstract Future<PlanGoal>(PlanGoalDraft draft)
PlanFormat: abstract interface class
PlanFormat.new: new()
PlanFormat.amount: abstract String(int cents)
PlanFormat.whole: abstract String(int cents)
PlanFormat.currency: abstract get String
PlanFormat.currencyFirst: abstract get bool
SamplePlanFormat: final class implements PlanFormat
SamplePlanFormat.new: const new(String language)
SamplePlanFormat.language: get String
SamplePlanFormat.amount: String(int cents)
SamplePlanFormat.whole: String(int cents)
SamplePlanFormat.currency: get String
SamplePlanFormat.currencyFirst: get bool
samplePlanNow: get dynamic
samplePlanGoals: get dynamic
samplePlanBudgets: get dynamic
samplePlanSnapshot: PlanSnapshot()
sampleEmptyPlan: PlanSnapshot()
SamplePlanService: final class implements PlanService
SamplePlanService.new: const new({PlanSnapshot Function()? snapshot, Duration latency})
SamplePlanService.snapshot: get PlanSnapshot Function()?
SamplePlanService.latency: get Duration
SamplePlanService.load: Future<PlanSnapshot>()
SamplePlanService.addMoney: Future<void>(String goalId, int cents)
SamplePlanService.saveGoal: Future<PlanGoal>(PlanGoalDraft draft)
financePlanL10n: const Map<String, Map<String, String>>
FinancePlanStrings: final class
FinancePlanStrings.new: const new(String language)
FinancePlanStrings.of: factory new(BuildContext context)
FinancePlanStrings.language: get String
FinancePlanStrings.title: get String
FinancePlanStrings.back: get String
FinancePlanStrings.refresh: get String
FinancePlanStrings.loading: get String
FinancePlanStrings.menu: get String
FinancePlanStrings.savedAcross: String(int goals)
FinancePlanStrings.totalSaved: get String
FinancePlanStrings.thisMonth: String(String amount)
FinancePlanStrings.actions: get String
FinancePlanStrings.addBudget: get String
FinancePlanStrings.seeAll: get String
FinancePlanStrings.goals: get String
FinancePlanStrings.goalsList: get String
FinancePlanStrings.month: String(DateTime at)
FinancePlanStrings.date: String(DateTime at)
FinancePlanStrings.due: String(DateTime at)
FinancePlanStrings.ofTarget: String(String target)
FinancePlanStrings.toGo: String(String? amount)
FinancePlanStrings.expected: String(String percent)
FinancePlanStrings.status: String(double behind)
FinancePlanStrings.goalReading: String(String name, String saved, String target, String percent, String expected, String status)
FinancePlanStrings.goalOptions: String(String name)
FinancePlanStrings.addMoney: get String
FinancePlanStrings.editGoal: get String
FinancePlanStrings.deleteGoal: get String
FinancePlanStrings.budgetsLeft: String(int daysLeft)
FinancePlanStrings.spent: String(String spent, String limit)
FinancePlanStrings.over: String(String amount)
FinancePlanStrings.budgetReading: String(String name, String spent, String limit, String percent, {required bool over})
FinancePlanStrings.addGoal: get String
FinancePlanStrings.newGoal: get String
FinancePlanStrings.goalName: get String
FinancePlanStrings.goalNameHint: get String
FinancePlanStrings.goalTarget: get String
FinancePlanStrings.goalDue: get String
FinancePlanStrings.monthYear: String(DateTime at)
FinancePlanStrings.earlier: get String
FinancePlanStrings.later: get String
FinancePlanStrings.goalIcon: get String
FinancePlanStrings.goalIconName: String(int index)
FinancePlanStrings.createGoal: get String
FinancePlanStrings.saveGoal: get String
FinancePlanStrings.saveFailed: get String
FinancePlanStrings.saveOffline: get String
FinancePlanStrings.to: String(String goal)
FinancePlanStrings.amount: get String
FinancePlanStrings.plus: String(String amount)
FinancePlanStrings.from: String(String account, String available)
FinancePlanStrings.tooMuch: String(String available)
FinancePlanStrings.addAmount: String(String amount)
FinancePlanStrings.addFailed: get String
FinancePlanStrings.addOffline: get String
FinancePlanStrings.close: get String
FinancePlanStrings.percent: String(double share)
FinancePlanStrings.emptyTitle: get String
FinancePlanStrings.emptyMessage: get String
FinancePlanStrings.errorTitle: get String
FinancePlanStrings.errorMessage: get String
FinancePlanStrings.offlineTitle: get String
FinancePlanStrings.offlineMessage: get String
FinancePlanStrings.retry: get String
FinancePlanStrings.refreshFailed: get String
FinancePlanStrings.time: String(DateTime at)
FinancePlanStrings.offlineSince: String(DateTime at, DateTime now)

Strings

Every label in English, French and Arabic. The screen follows the closest Localizations; the same table is in the manifest, ready for your ARB files.

Keyenfrar
financePlanTitlePlanPlanالخطة
financePlanBackBackRetourرجوع
financePlanMenuMenuMenuالقائمة
financePlanRefreshRefreshActualiserتحديث
financePlanLoadingLoading your planChargement de votre planجارٍ تحميل خطتك
financePlanSavedAcrossSaved across {count} goalsÉpargné sur {count} objectifsالمدّخر في الأهداف: {count}
financePlanSavedOneSaved in 1 goalÉpargné sur 1 objectifالمدّخر في هدف واحد
financePlanTotalSavedTotal savedTotal épargnéإجمالي المدّخر
financePlanThisMonth{amount} this month{amount} ce mois-ci{amount} هذا الشهر
financePlanActionsPlan actionsActions du planإجراءات الخطة
financePlanAddBudgetAdd budgetAjouter un budgetإضافة ميزانية
financePlanGoalsGoalsObjectifsالأهداف
financePlanSeeAllSee allTout voirعرض الكل
financePlanGoalsListSavings goalsObjectifs d’épargneأهداف الادخار
financePlanMonthsJanuary,February,March,April,May,June,July,August,September,October,November,Decemberjanvier,février,mars,avril,mai,juin,juillet,août,septembre,octobre,novembre,décembreيناير,فبراير,مارس,أبريل,مايو,يونيو,يوليو,أغسطس,سبتمبر,أكتوبر,نوفمبر,ديسمبر
financePlanDate{day} {month} {year}{day} {month} {year}{day} {month} {year}
financePlanDueDue {date}Échéance le {date}الموعد {date}
financePlanOfTargetof {target}sur {target}من {target}
financePlanToGo{amount} to goEncore {amount}يتبقى {amount}
financePlanReachedTarget reachedObjectif atteintتحقق الهدف
financePlanExpectedExpected {percent}Prévu {percent}المتوقع {percent}
financePlanOnTrackOn trackDans les tempsفي الموعد
financePlanBehind{percent} behind schedule{percent} de retardمتأخر بنسبة {percent}
financePlanGoalReading{name}, {saved} of {target}, {percent}. Expected {expected} by now. {status}.{name}, {saved} sur {target}, {percent}. {expected} prévus à ce jour. {status}.{name}، {saved} من {target}، {percent}. المتوقع حتى الآن {expected}. {status}.
financePlanGoalOptionsOptions for {name}Options de {name}خيارات {name}
financePlanAddMoneyAdd moneyAjouter de l’argentإضافة مال
financePlanEditGoalEdit goalModifier l’objectifتعديل الهدف
financePlanDeleteGoalDelete goalSupprimer l’objectifحذف الهدف
financePlanBudgetsLeftBudgets · {left}Budgets · {left}الميزانيات · {left}
financePlanDaysLeft{count} days left{count} jours restantsتبقّى {count} يومًا
financePlanDayLeft1 day left1 jour restantتبقّى يوم واحد
financePlanLastDayLast dayDernier jourاليوم الأخير
financePlanSpent{spent} of {limit}{spent} sur {limit}{spent} من {limit}
financePlanOver{amount} over{amount} de tropتجاوز {amount}
financePlanBudgetReading{name} budget, {spent} of {limit}, {percent}Budget {name}, {spent} sur {limit}, {percent}ميزانية {name}، {spent} من {limit}، {percent}
financePlanBudgetReadingOver{name} budget, {spent} of {limit}, {percent}, over budgetBudget {name}, {spent} sur {limit}, {percent}, dépasséميزانية {name}، {spent} من {limit}، {percent}، تم تجاوزها
financePlanAddGoalAdd goalAjouter un objectifإضافة هدف
financePlanNewGoalNew goalNouvel objectifهدف جديد
financePlanGoalNameNameNomالاسم
financePlanGoalNameHintTrip to JapanVoyage au Japonرحلة إلى اليابان
financePlanGoalTargetTargetMontant viséالمبلغ المستهدف
financePlanGoalDueReach it byÀ atteindre enالموعد
financePlanMonthYear{month} {year}{month} {year}{month} {year}
financePlanEarlierOne month earlierUn mois plus tôtقبل شهر
financePlanLaterOne month laterUn mois plus tardبعد شهر
financePlanGoalIconIconIcôneالأيقونة
financePlanGoalIconsSafety,Travel,Tech,Home,Car,Health,Reward,SavingsSécurité,Voyage,Tech,Maison,Voiture,Santé,Récompense,Épargneالأمان,السفر,التقنية,المنزل,السيارة,الصحة,المكافأة,الادخار
financePlanCreateGoalCreate goalCréer l’objectifإنشاء الهدف
financePlanSaveGoalSaveEnregistrerحفظ
financePlanSaveFailedCouldn’t save the goal. Try again.Impossible d’enregistrer l’objectif. Réessayez.تعذّر حفظ الهدف. أعد المحاولة.
financePlanSaveOfflineYou’re offline. Nothing was saved.Vous êtes hors ligne. Rien n’a été enregistré.أنت غير متصل. لم يُحفظ شيء.
financePlanToTo {goal}Vers {goal}إلى {goal}
financePlanAmountAmountMontantالمبلغ
financePlanPlus+ {amount}+ {amount}+ {amount}
financePlanFromFrom {account} · {available} availableDepuis {account} · {available} disponiblesمن {account} · {available} متاح
financePlanTooMuchMore than the {available} availablePlus que les {available} disponiblesأكثر من {available} المتاح
financePlanAddAmountAdd {amount}Ajouter {amount}إضافة {amount}
financePlanAddFailedCouldn’t add the money. Try again.Impossible d’ajouter l’argent. Réessayez.تعذّرت إضافة المال. أعد المحاولة.
financePlanAddOfflineYou’re offline. Nothing was moved.Vous êtes hors ligne. Rien n’a été déplacé.أنت غير متصل. لم يُنقل أي مبلغ.
financePlanCloseCloseFermerإغلاق
financePlanEmptyTitleStart with a goalCommencez par un objectifابدأ بهدف
financePlanEmptyMessageSet money aside for what matters, and keep your monthly budgets here.Mettez de l’argent de côté pour ce qui compte, et gardez ici vos budgets du mois.ادّخر لما يهمّك، واحتفظ بميزانياتك الشهرية هنا.
financePlanErrorTitleWe couldn’t load your planImpossible de charger votre planتعذّر تحميل خطتك
financePlanErrorMessageOur server didn’t answer. Your money is safe.Notre serveur n’a pas répondu. Votre argent ne risque rien.لم يستجب خادمنا. أموالك في أمان.
financePlanOfflineTitleYou are offlineVous êtes hors ligneأنت غير متصل
financePlanOfflineMessageCheck your connection, then try again.Vérifiez votre connexion, puis réessayez.تحقق من اتصالك ثم أعد المحاولة.
financePlanRetryTry againRéessayerأعد المحاولة
financePlanRefreshFailedCouldn’t refresh. Amounts may be out of date.Actualisation impossible. Les montants ne sont peut-être plus à jour.تعذّر التحديث. قد لا تكون المبالغ محدّثة.
financePlanOfflineSinceYou’re offline. Updated {date} at {time}.Vous êtes hors ligne. Mis à jour le {date} à {time}.أنت غير متصل. آخر تحديث {date} الساعة {time}.
financePlanOfflineTodayYou’re offline. Updated today at {time}.Vous êtes hors ligne. Mis à jour aujourd’hui à {time}.أنت غير متصل. آخر تحديث اليوم الساعة {time}.

Reference renders

The 16 images published with version 0.2.0, rendered by the shared harness on macOS with Flutter 3.47.7. Each shows every state side by side, at the size of an iPhone (390 × 844) or of an Android phone (412 × 915). fliin verify finance-plan compares your copy to them.

iOS · Light
Finance plan, iOS, Light, text 1.0×, Left to right
Text 1.0× · Left to right
Finance plan, iOS, Light, text 1.0×, Right to left
Text 1.0× · Right to left
Finance plan, iOS, Light, text 2.0×, Left to right
Text 2.0× · Left to right
Finance plan, iOS, Light, text 2.0×, Right to left
Text 2.0× · Right to left
iOS · Dark
Finance plan, iOS, Dark, text 1.0×, Left to right
Text 1.0× · Left to right
Finance plan, iOS, Dark, text 1.0×, Right to left
Text 1.0× · Right to left
Finance plan, iOS, Dark, text 2.0×, Left to right
Text 2.0× · Left to right
Finance plan, iOS, Dark, text 2.0×, Right to left
Text 2.0× · Right to left
Android · Light
Finance plan, Android, Light, text 1.0×, Left to right
Text 1.0× · Left to right
Finance plan, Android, Light, text 1.0×, Right to left
Text 1.0× · Right to left
Finance plan, Android, Light, text 2.0×, Left to right
Text 2.0× · Left to right
Finance plan, Android, Light, text 2.0×, Right to left
Text 2.0× · Right to left
Android · Dark
Finance plan, Android, Dark, text 1.0×, Left to right
Text 1.0× · Left to right
Finance plan, Android, Dark, text 1.0×, Right to left
Text 1.0× · Right to left
Finance plan, Android, Dark, text 2.0×, Left to right
Text 2.0× · Left to right
Finance plan, Android, Dark, text 2.0×, Right to left
Text 2.0× · Right to left

Use it to

  • show savings goals and whether they are on schedule
  • add money to a goal from a bank account
  • show monthly budgets by category and which ones are over
  • create a savings goal with its target, due month and icon
  • offer to set a first savings goal when there is none
  • keep the last known plan offline, with its date
Not for
  • the balance and the latest transactions: use the finance dashboard (finance-dashboard)
  • a single progress bar: use a progress indicator (progress)
States
  • Ready
  • Loading
  • Empty
  • Error
  • Offline
  • Offline empty

Arrows to move, Enter to open, Escape to close.