Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ShowCaseView on Interactive Book #170

Merged
merged 14 commits into from
Dec 22, 2021
Merged
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
47 changes: 47 additions & 0 deletions lib/models/ib/ib_showcase.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import 'dart:convert';

import 'package:flutter/material.dart';

class IBShowCase {
bool nextButton, prevButton, tocButton, drawerButton;

IBShowCase({
@required this.nextButton,
@required this.prevButton,
@required this.tocButton,
@required this.drawerButton,
});

factory IBShowCase.fromJson(Map<String, dynamic> json) {
return IBShowCase(
nextButton: json['next'] ?? false,
prevButton: json['prev'] ?? false,
tocButton: json['toc'] ?? false,
drawerButton: json['drawer'] ?? false,
);
}

IBShowCase copyWith({
bool nextButton,
bool prevButton,
bool tocButton,
bool drawerButton,
}) {
return IBShowCase(
nextButton: nextButton ?? this.nextButton,
prevButton: prevButton ?? this.prevButton,
tocButton: tocButton ?? this.tocButton,
drawerButton: drawerButton ?? this.drawerButton,
);
}

@override
String toString() {
return json.encode({
'next': nextButton,
'prev': prevButton,
'toc': tocButton,
'drawer': drawerButton,
});
}
}
13 changes: 13 additions & 0 deletions lib/services/local_storage_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class LocalStorageService {
static const String IS_LOGGED_IN = 'is_logged_in';
static const String IS_FIRST_TIME_LOGIN = 'is_first_time_login';
static const String AUTH_TYPE = 'auth_type';
static const String IB_SHOWCASE_STATE = 'ib_showcase_state';

static Future<LocalStorageService> getInstance() async {
_preferences ??= await SharedPreferences.getInstance();
Expand Down Expand Up @@ -86,4 +87,16 @@ class LocalStorageService {
set authType(AuthType authType) {
_saveToDisk(AUTH_TYPE, authTypeValues.reverse[authType]);
}

Map<String, dynamic> get getShowcaseState {
final Map<String, dynamic> result =
Map.castFrom<dynamic, dynamic, String, dynamic>(
json.decode(_getFromDisk(IB_SHOWCASE_STATE) ?? '{}')
as Map<dynamic, dynamic>);
return result;
}

set setShowcaseState(String state) {
_saveToDisk(IB_SHOWCASE_STATE, state);
}
}
129 changes: 92 additions & 37 deletions lib/ui/views/ib/ib_landing_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:mobile_app/ui/components/cv_drawer_tile.dart';
import 'package:mobile_app/ui/views/base_view.dart';
import 'package:mobile_app/ui/views/ib/ib_page_view.dart';
import 'package:mobile_app/viewmodels/ib/ib_landing_viewmodel.dart';
import 'package:showcaseview/showcaseview.dart';
import 'package:theme_provider/theme_provider.dart';

class IbLandingView extends StatefulWidget {
Expand All @@ -26,6 +27,9 @@ class _IbLandingViewState extends State<IbLandingView> {
);
IbChapter _selectedChapter;
ValueNotifier<Function> _tocNotifier;
IbLandingViewModel _model;

final GlobalKey<ScaffoldState> _key = GlobalKey();

@override
void initState() {
Expand All @@ -49,6 +53,25 @@ class _IbLandingViewState extends State<IbLandingView> {

Widget _buildAppBar() {
return AppBar(
leading: IconButton(
onPressed: () {
if (!_model.showCaseState.drawerButton) {
_model.onShowCased('drawer');
}
_key.currentState.openDrawer();
},
icon: Showcase(
key: _model.drawer,
description: 'Navigate to different chapters',
overlayPadding: const EdgeInsets.all(12.0),
onTargetClick: () {
_model.onShowCased('drawer');
_key.currentState.openDrawer();
},
disposeOnTap: true,
child: const Icon(Icons.menu),
),
),
title: Text(
_selectedChapter.id == _homeChapter.id
? 'CircuitVerse'
Expand All @@ -59,10 +82,21 @@ class _IbLandingViewState extends State<IbLandingView> {
valueListenable: _tocNotifier,
builder: (context, value, child) {
return value != null
? IconButton(
icon: const Icon(Icons.menu_book_rounded),
tooltip: 'Show Table of Contents',
onPressed: value,
? Showcase(
key: _model.toc,
description: 'Show Table of Contents',
child: IconButton(
icon: const Icon(Icons.menu_book_rounded),
onPressed: value,
),
onTargetClick: () {
_model.onShowCased('toc');
if (_key.currentState.isDrawerOpen) Get.back();
Future.delayed(const Duration(milliseconds: 200), () {
value();
});
},
disposeOnTap: true,
)
: Container();
},
Expand Down Expand Up @@ -135,7 +169,7 @@ class _IbLandingViewState extends State<IbLandingView> {
return Column(children: _chapters);
}

Widget _buildDrawer(IbLandingViewModel _model) {
Widget _buildDrawer() {
return Drawer(
child: Stack(
children: [
Expand Down Expand Up @@ -194,7 +228,10 @@ class _IbLandingViewState extends State<IbLandingView> {
@override
Widget build(BuildContext context) {
return BaseView<IbLandingViewModel>(
onModelReady: (model) => model.fetchChapters(),
onModelReady: (model) {
_model = model;
model.init();
},
builder: (context, model, child) {
// Set next page for home page
if (model.isSuccess(model.IB_FETCH_CHAPTERS) &&
Expand All @@ -209,41 +246,59 @@ class _IbLandingViewState extends State<IbLandingView> {
setState(() => _selectedChapter = _homeChapter);
return Future.value(false);
}
_model.saveShowcaseState();
return Future.value(true);
},
child: Theme(
data: IbTheme.getThemeData(context),
child: Scaffold(
key: const Key('IbLandingScaffold'),
appBar: _buildAppBar(),
drawer: _buildDrawer(model),
body: PageTransitionSwitcher(
transitionBuilder: (
Widget child,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
return FadeThroughTransition(
animation: animation,
secondaryAnimation: secondaryAnimation,
child: child,
);
},
child: IbPageView(
key: Key(_selectedChapter.toString()),
tocCallback: (val) {
Future.delayed(Duration.zero, () async {
if (mounted) {
_tocNotifier.value = val;
}
});
},
setPage: (chapter) {
setState(() => _selectedChapter = chapter);
},
chapter: _selectedChapter,
),
),
child: ShowCaseWidget(
onComplete: (index, globalKey) {
final String key = globalKey
.toString()
.substring(1, globalKey.toString().length - 1)
.split(" ")
.last;
model.onShowCased(key);
},
builder: Builder(builder: (context) {
return Scaffold(
key: _key,
appBar: _buildAppBar(),
drawer: _buildDrawer(),
body: PageTransitionSwitcher(
transitionBuilder: (
Widget child,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
return FadeThroughTransition(
animation: animation,
secondaryAnimation: secondaryAnimation,
child: child,
);
},
child: IbPageView(
key: Key(_selectedChapter.toString()),
tocCallback: (val) {
Future.delayed(Duration.zero, () async {
if (mounted) {
_tocNotifier.value = val;
}
});
},
setPage: (chapter) {
setState(() => _selectedChapter = chapter);
},
chapter: _selectedChapter,
setShowCase: (updatedState) {
model.showCaseState = updatedState;
},
showCase: model.showCaseState,
globalKeysMap: model.keyMap,
),
),
);
}),
),
),
);
Expand Down
56 changes: 50 additions & 6 deletions lib/ui/views/ib/ib_page_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:mobile_app/ib_theme.dart';
import 'package:mobile_app/models/ib/ib_chapter.dart';
import 'package:mobile_app/models/ib/ib_content.dart';
import 'package:mobile_app/models/ib/ib_page_data.dart';
import 'package:mobile_app/models/ib/ib_showcase.dart';
import 'package:mobile_app/services/ib_engine_service.dart';
import 'package:mobile_app/ui/views/base_view.dart';
import 'package:mobile_app/ui/views/ib/builders/ib_chapter_contents_builder.dart';
Expand All @@ -27,23 +28,31 @@ import 'package:mobile_app/ui/views/ib/syntaxes/ib_md_tag_syntax.dart';
import 'package:mobile_app/utils/url_launcher.dart';
import 'package:mobile_app/viewmodels/ib/ib_page_viewmodel.dart';
import 'package:scroll_to_index/scroll_to_index.dart';
import 'package:showcaseview/showcaseview.dart';
import 'package:url_launcher/url_launcher.dart';

typedef TocCallback = void Function(Function);
typedef SetPageCallback = void Function(IbChapter);
typedef SetShowCaseStateCallback = void Function(IBShowCase);

class IbPageView extends StatefulWidget {
const IbPageView({
@required Key key,
@required this.tocCallback,
@required this.chapter,
@required this.setPage,
@required this.showCase,
@required this.setShowCase,
@required this.globalKeysMap,
}) : super(key: key);

static const String id = 'ib_page_view';
final TocCallback tocCallback;
final SetPageCallback setPage;
final IbChapter chapter;
final IBShowCase showCase;
final SetShowCaseStateCallback setShowCase;
final Map<String, dynamic> globalKeysMap;

@override
_IbPageViewState createState() => _IbPageViewState();
Expand All @@ -53,13 +62,15 @@ class _IbPageViewState extends State<IbPageView> {
IbPageViewModel _model;
AutoScrollController _hideButtonController;
bool _isFabsVisible = true;
ShowCaseWidgetState _showCaseWidgetState;

/// To track index through slug for scroll_to_index
final Map<String, int> _slugMap = {};

@override
void initState() {
super.initState();
_showCaseWidgetState = ShowCaseWidget.of(context);
_isFabsVisible = true;
_hideButtonController = AutoScrollController(axis: Axis.vertical);
_hideButtonController.addListener(() {
Expand All @@ -74,6 +85,12 @@ class _IbPageViewState extends State<IbPageView> {
});
}

@override
void didChangeDependencies() {
_showCaseWidgetState = ShowCaseWidget.of(context);
super.didChangeDependencies();
}

Widget _buildDivider() {
return const Padding(
padding: EdgeInsets.symmetric(vertical: 10),
Expand Down Expand Up @@ -367,9 +384,20 @@ class _IbPageViewState extends State<IbPageView> {
}
widget.setPage(widget.chapter.prev);
},
child: const Icon(
Icons.arrow_back_rounded,
color: IbTheme.primaryColor,
child: Showcase(
key: _model.prevPage,
description: 'Tap to navigate to previous page',
overlayPadding: const EdgeInsets.all(12.0),
shapeBorder: const CircleBorder(),
onTargetClick: () {
widget.setShowCase(widget.showCase.copyWith(prevButton: true));
widget.setPage(widget.chapter.prev);
},
disposeOnTap: true,
child: const Icon(
Icons.arrow_back_rounded,
color: IbTheme.primaryColor,
),
),
),
),
Expand All @@ -396,9 +424,20 @@ class _IbPageViewState extends State<IbPageView> {
}
widget.setPage(widget.chapter.next);
},
child: const Icon(
Icons.arrow_forward_rounded,
color: IbTheme.primaryColor,
child: Showcase(
key: _model.nextPage,
description: 'Tap to navigate to next page',
overlayPadding: const EdgeInsets.all(12.0),
shapeBorder: const CircleBorder(),
onTargetClick: () {
widget.setShowCase(widget.showCase.copyWith(nextButton: true));
widget.setPage(widget.chapter.next);
},
disposeOnTap: false,
child: const Icon(
Icons.arrow_forward_rounded,
color: IbTheme.primaryColor,
),
),
),
),
Expand Down Expand Up @@ -454,6 +493,11 @@ class _IbPageViewState extends State<IbPageView> {
onModelReady: (model) {
_model = model;
model.fetchPageData(id: widget.chapter.id);
model.showCase(
_showCaseWidgetState,
widget.showCase,
widget.globalKeysMap,
);
},
builder: (context, model, child) {
// Set the callback to show bottom sheet for Table of Contents
Expand Down
4 changes: 3 additions & 1 deletion lib/utils/router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,9 @@ class CVRouter {
),
);
case IbLandingView.id:
return MaterialPageRoute(builder: (_) => const IbLandingView());
return MaterialPageRoute(
builder: (_) => const IbLandingView(),
);
case ProjectPreviewFullScreen.id:
var _project = settings.arguments as Project;
return MaterialPageRoute(
Expand Down
Loading