Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# 314 - Improve Tutorial UX

- Replaced the blocking showcase tutorial with dismissible contextual tips on the home screen; users can interact with the app while tips are visible
- Removed the dedicated tutorial sandbox page, `showcaseview` dependency, and linear `ProductTourStep` flow; drawer "Show app tips" replays tips in place
- Added `AppTip` model, `IAppTipsController`, legacy tour migration, and updated welcome modal copy (en/nl)

# Localization, App Icon, DevOps

- Completed slang string migration (#388, child of #372): localized remaining presentation strings across changelog, drawer, home, search, tutorial, data transfer, delete modal, and tooltips; added `localizeCoreMessage()` to map core validation/auth/feedback errors to en/nl at the presentation boundary; Dutch feedback strings aligned with English; forgot-password errors localized; dismiss tooltip required on `DismissibleBannerBar`
Expand Down
1 change: 0 additions & 1 deletion apps/multichoice/lib/app/engine/app_router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ class AppRouter extends RootStackRouter {
AutoRoute(page: DataTransferScreenRoute.page),
AutoRoute(page: EditTabPageRoute.page),
AutoRoute(page: EditEntryPageRoute.page),
AutoRoute(page: TutorialPageRoute.page),
AutoRoute(page: FeedbackPageRoute.page),
AutoRoute(page: ProfilePageRoute.page),
AutoRoute(page: AccountDeletionPageRoute.page),
Expand Down
1 change: 0 additions & 1 deletion apps/multichoice/lib/app/engine/static_keys.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import 'package:flutter/material.dart';

final scaffoldKey = GlobalKey<ScaffoldState>();
final scaffoldKeyTutorial = GlobalKey<ScaffoldState>();
22 changes: 19 additions & 3 deletions apps/multichoice/lib/i18n/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@
"appearance": "Appearance",
"data": "Data",
"more": "More",
"restartTutorial": "Restart Tutorial",
"restartTutorialDesc": "Temporarily switches to demo data to show app features, then restores your original data",
"restartTutorial": "Show app tips",
"restartTutorialDesc": "Replay helpful tips about using Multichoice on the home screen",
"sendFeedback": "Send Feedback",
"changelog": "Changelog",
"about": "About",
Expand Down Expand Up @@ -96,7 +96,9 @@
"finishTour": "Finish Tour",
"continueTour": "Continue Tour",
"welcomeToMultichoice": "Welcome to Multichoice",
"welcomeToMultichoiceBody": "Multichoice helps you organize your thoughts and ideas into customizable collections. Would you like to follow a quick tutorial to learn how to use the app?",
"welcomeToMultichoiceBody": "Multichoice helps you organize your thoughts and ideas into customizable collections. We can show a few quick tips as you explore — you can dismiss them anytime and keep using the app.",
"skipTips": "Skip tips",
"showTips": "Show tips",
"signedInSuccessfully": "Signed in successfully!",
"storageDataClearedSuccessfully": "Storage data cleared successfully",
"deletionRequestSubmitted": "Deletion request submitted. You have been signed out.",
Expand Down Expand Up @@ -266,6 +268,20 @@
"stay": "Stay",
"exit": "Exit"
},
"tips": {
"collectionsTitle": "Your collections",
"collectionsBody": "Collections group related items together. Swipe horizontally to browse them.",
"addCollectionTitle": "Add a collection",
"addCollectionBody": "Tap here anytime to create a new collection for a topic or category.",
"addEntryTitle": "Add an item",
"addEntryBody": "Use the plus button inside a collection to add a new entry.",
"entryActionsTitle": "Item shortcuts",
"entryActionsBody": "Tap to view details, double-tap to edit, and long-press for more options.",
"editAndSearchTitle": "Edit and search",
"editAndSearchBody": "Use edit mode to reorder collections and entries, or search across everything.",
"drawerTitle": "Settings and more",
"drawerBody": "Open the menu for appearance, data tools, feedback, and more. Tips appear as you explore."
},
"feedback": {
"sendFeedback": "Send Feedback",
"thankYouMessage": "Thank you for your feedback!",
Expand Down
22 changes: 19 additions & 3 deletions apps/multichoice/lib/i18n/nl.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@
"appearance": "Weergave",
"data": "Gegevens",
"more": "Meer",
"restartTutorial": "Herstart Tutorial",
"restartTutorialDesc": "Schakelt tijdelijk over naar demogegevens om app-functies te tonen, en herstelt daarna uw originele gegevens",
"restartTutorial": "Toon app-tips",
"restartTutorialDesc": "Speel nuttige tips over het gebruik van Multichoice op het startscherm opnieuw af",
"sendFeedback": "Stuur Feedback",
"changelog": "Wijzigingslogboek",
"about": "Over",
Expand Down Expand Up @@ -97,7 +97,9 @@
"finishTour": "Voltooi tour",
"continueTour": "Doorgaan met tour",
"welcomeToMultichoice": "Welkom bij Multichoice",
"welcomeToMultichoiceBody": "Multichoice helpt je je gedachten en ideeën te organiseren in aanpasbare collecties. Wil je een korte tutorial volgen om te leren hoe je de app gebruikt?",
"welcomeToMultichoiceBody": "Multichoice helpt je je gedachten en ideeën te organiseren in aanpasbare collecties. We kunnen een paar korte tips tonen terwijl je de app verkent — je kunt ze altijd sluiten en gewoon verder gaan.",
"skipTips": "Tips overslaan",
"showTips": "Toon tips",
"signedInSuccessfully": "Succesvol ingelogd!",
"storageDataClearedSuccessfully": "Opslaggegevens succesvol gewist",
"deletionRequestSubmitted": "Verzoek tot verwijdering ingediend. Je bent uitgelogd.",
Expand Down Expand Up @@ -267,6 +269,20 @@
"stay": "Blijven",
"exit": "Afsluiten"
},
"tips": {
"collectionsTitle": "Je collecties",
"collectionsBody": "Collecties groeperen gerelateerde items. Veeg horizontaal om ze te bekijken.",
"addCollectionTitle": "Collectie toevoegen",
"addCollectionBody": "Tik hier om een nieuwe collectie voor een onderwerp of categorie te maken.",
"addEntryTitle": "Item toevoegen",
"addEntryBody": "Gebruik de plusknop in een collectie om een nieuw item toe te voegen.",
"entryActionsTitle": "Item-snelkoppelingen",
"entryActionsBody": "Tik om details te bekijken, dubbeltik om te bewerken en houd ingedrukt voor meer opties.",
"editAndSearchTitle": "Bewerken en zoeken",
"editAndSearchBody": "Gebruik de bewerkmodus om collecties en items te herschikken, of zoek in alles.",
"drawerTitle": "Instellingen en meer",
"drawerBody": "Open het menu voor weergave, gegevenstools, feedback en meer. Tips verschijnen terwijl je de app verkent."
},
"feedback": {
"sendFeedback": "Stuur feedback",
"thankYouMessage": "Bedankt voor je feedback!",
Expand Down
2 changes: 2 additions & 0 deletions apps/multichoice/lib/layouts/home_layout/home_layout.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import 'dart:async';
import 'package:core/core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:models/models.dart';
import 'package:multichoice/app/export.dart';
import 'package:multichoice/i18n/localize_core_message.dart';
import 'package:multichoice/i18n/strings.g.dart';
import 'package:multichoice/presentation/home/home_page.dart';
import 'package:multichoice/utils/app_tips/app_tip_showcase.dart';
import 'package:ui_kit/ui_kit.dart';

part 'widgets/home/horizontal_home.dart';
Expand Down
1 change: 1 addition & 0 deletions apps/multichoice/lib/layouts/home_layout/tab_layout.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:models/models.dart';
import 'package:multichoice/app/export.dart';
import 'package:multichoice/i18n/strings.g.dart';
import 'package:multichoice/presentation/home/home_page.dart';
import 'package:multichoice/utils/app_tips/app_tip_showcase.dart';
import 'package:reorderable_grid/reorderable_grid.dart';
import 'package:ui_kit/ui_kit.dart';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,13 @@ class _HorizontalHomeState extends State<_HorizontalHome> {

return Padding(
padding: vertical6,
child: CollectionTab(
tab: tab,
isEditMode: isEditMode,
child: AppTipShowcase(
tip: AppTip.collections,
enabled: index == 0,
child: CollectionTab(
tab: tab,
isEditMode: isEditMode,
),
),
);
},
Expand All @@ -132,7 +136,10 @@ class _HorizontalHomeState extends State<_HorizontalHome> {
SliverPadding(
padding: horizontal12 + bottom24,
sliver: const SliverToBoxAdapter(
child: NewTab(),
child: AppTipShowcase(
tip: AppTip.addCollection,
child: NewTab(),
),
),
),
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,17 +132,24 @@ class _VerticalHomeState extends State<_VerticalHome> {

return Padding(
padding: horizontal6,
child: CollectionTab(
tab: tab,
isEditMode: isEditMode,
child: AppTipShowcase(
tip: AppTip.collections,
enabled: index == 0,
child: CollectionTab(
tab: tab,
isEditMode: isEditMode,
),
),
);
},
),
const SliverPadding(
padding: horizontal6,
sliver: SliverToBoxAdapter(
child: NewTab(),
child: AppTipShowcase(
tip: AppTip.addCollection,
child: NewTab(),
),
),
),
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ class EntriesGrid extends StatelessWidget {

@override
Widget build(BuildContext context) {
final isFirstTab = context.select<HomeBloc, bool>(
(bloc) => bloc.state.tabs?.firstOrNull?.id == tabId,
);

return SliverPadding(
padding: vertical4,
sliver: SliverGrid.builder(
Expand All @@ -25,25 +29,33 @@ class EntriesGrid extends StatelessWidget {
itemCount: entries.length + 1,
itemBuilder: (context, index) {
if (index == entries.length) {
return NewEntry(
tabId: tabId,
return AppTipShowcase(
tip: AppTip.addEntry,
enabled: isFirstTab,
child: NewEntry(
tabId: tabId,
),
);
}

final entry = entries[index];

return EntryCard(
entry: entry,
isLayoutVertical: isLayoutVertical,
isEditMode: isEditMode,
onDoubleTap: () async {
context.read<HomeBloc>().add(
HomeEvent.onUpdateEntry(entry.id),
);
await context.router.push(
EditEntryPageRoute(ctx: context),
);
},
return AppTipShowcase(
tip: AppTip.entryActions,
enabled: isFirstTab && index == 0,
child: EntryCard(
entry: entry,
isLayoutVertical: isLayoutVertical,
isEditMode: isEditMode,
onDoubleTap: () async {
context.read<HomeBloc>().add(
HomeEvent.onUpdateEntry(entry.id),
);
await context.router.push(
EditEntryPageRoute(ctx: context),
);
},
),
);
},
),
Expand Down
5 changes: 4 additions & 1 deletion apps/multichoice/lib/presentation/drawer/home_drawer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:multichoice/app/view/debug/remote_config_debug_notifier.dart';
import 'package:multichoice/i18n/strings.g.dart';
import 'package:multichoice/presentation/drawer/widgets/export.dart';
import 'package:multichoice/presentation/registration/login_modal.dart';
import 'package:multichoice/utils/app_tips/app_tip_drawer_showcase.dart';
import 'package:multichoice/utils/user_accounts_feature.dart';
import 'package:provider/provider.dart';
import 'package:ui_kit/ui_kit.dart';
Expand Down Expand Up @@ -75,7 +76,9 @@ class HomeDrawer extends StatelessWidget {
physics: const BouncingScrollPhysics(),
padding: EdgeInsets.zero,
children: [
const AppearanceSection(),
const AppTipDrawerShowcase(
child: AppearanceSection(),
),
const Divider(height: 32),
const DataSection(),
if (isLoggedIn && userAccountsEnabled) ...[
Expand Down
24 changes: 3 additions & 21 deletions apps/multichoice/lib/presentation/drawer/widgets/more_section.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,27 +33,9 @@ class MoreSection extends StatelessWidget {
),
trailing: IconButton(
onPressed: () async {
final appLayout = context.read<AppLayout>();
final originalLayout = appLayout.isLayoutVertical;
await appLayout.setLayoutVertical(isVertical: false);

await Future.value(
coreSl<IProductTourController>().resetTour(),
).whenComplete(() async {
if (context.mounted) {
Navigator.of(context).pop();

await context.router.push(
TutorialPageRoute(
onCallback: () async {
await appLayout.setLayoutVertical(
isVertical: originalLayout,
);
},
),
);
}
});
Navigator.of(context).pop();
if (!context.mounted) return;
context.read<ProductBloc>().add(const ProductEvent.resetTips());
},
icon: const Icon(
Icons.refresh_outlined,
Expand Down
39 changes: 16 additions & 23 deletions apps/multichoice/lib/presentation/home/home_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ import 'package:multichoice/presentation/home/widgets/welcome_modal_handler.dart
import 'package:multichoice/presentation/shared/widgets/add_widgets/_base.dart';
import 'package:multichoice/presentation/shared/widgets/forms/reusable_form.dart';
import 'package:multichoice/presentation/shared/widgets/modals/delete_modal.dart';
import 'package:multichoice/utils/app_tips/app_tip_coordinator.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:reorderable_grid/reorderable_grid.dart';
import 'package:showcaseview/showcaseview.dart';
import 'package:ui_kit/ui_kit.dart';

part 'utils/_check_and_request_permissions.dart';
Expand All @@ -36,17 +36,11 @@ class HomePage extends StatelessWidget {
return UpdateModalHandler(
builder: (_) => WelcomeModalHandler(
builder: (_) => const _HomePage(),
onSkipTour: () async {
context.read<ProductBloc>().add(const ProductEvent.skipTour());
onSkipTips: () async {
context.read<ProductBloc>().add(const ProductEvent.skipAllTips());
},
onFollowTutorial: () async {
await context.router.push(
TutorialPageRoute(
onCallback: () {
context.read<HomeBloc>().add(const HomeEvent.onGetTabs());
},
),
);
onStartTips: () async {
context.read<ProductBloc>().add(const ProductEvent.init());
},
),
);
Expand All @@ -61,26 +55,26 @@ class _HomePage extends StatefulWidget {
}

class _HomePageState extends State<_HomePage> {
bool _isDrawerOpen = false;
final GlobalKey<AppTipCoordinatorState> _tipCoordinatorKey =
GlobalKey<AppTipCoordinatorState>();

@override
Widget build(BuildContext context) {
return AnalyticsPageTracker(
page: AnalyticsPage.home,
// this ShowCaseWidget is here to fix an issue where it complains
// about ShowCaseView context not being available
// ignore: deprecated_member_use
child: ShowCaseWidget(
builder: (context) => BlocBuilder<HomeBloc, HomeState>(
child: AppTipCoordinator(
key: _tipCoordinatorKey,
child: BlocBuilder<HomeBloc, HomeState>(
buildWhen: (previous, current) =>
previous.isEditMode != current.isEditMode,
builder: (context, state) {
return PopScope(
canPop: !state.isEditMode && !_isDrawerOpen,
canPop: !state.isEditMode,
onPopInvokedWithResult: (didPop, _) {
if (didPop) return;

if (_isDrawerOpen) {
final isDrawerOpen = scaffoldKey.currentState?.isDrawerOpen;
if (isDrawerOpen ?? false) {
Navigator.of(context).pop();
return;
}
Expand All @@ -94,10 +88,9 @@ class _HomePageState extends State<_HomePage> {
child: Scaffold(
key: scaffoldKey,
onDrawerChanged: (isOpened) {
if (_isDrawerOpen == isOpened) return;
setState(() {
_isDrawerOpen = isOpened;
});
_tipCoordinatorKey.currentState?.handleDrawerChanged(
isOpened: isOpened,
);
},
appBar: const HomeAppBar(),
drawer: const HomeDrawer(),
Expand Down
Loading
Loading