Skip to content

Commit

Permalink
[Presentation] Performance improvements on the banner history and cha…
Browse files Browse the repository at this point in the history
…rts page
  • Loading branch information
Wolfteam committed May 30, 2022
1 parent 72a94a5 commit af0b850
Show file tree
Hide file tree
Showing 16 changed files with 409 additions and 271 deletions.
3 changes: 3 additions & 0 deletions lib/application/banner_history/banner_history_bloc.dart
@@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:math';

import 'package:bloc/bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
Expand All @@ -16,6 +17,7 @@ const _initialState = BannerHistoryState.initial(
sortType: BannerHistorySortType.versionAsc,
banners: [],
versions: [],
maxNumberOfItems: 0,
);

class BannerHistoryBloc extends Bloc<BannerHistoryEvent, BannerHistoryState> {
Expand Down Expand Up @@ -56,6 +58,7 @@ class BannerHistoryBloc extends Bloc<BannerHistoryEvent, BannerHistoryState> {
sortType: _initialState.sortType,
banners: banners,
versions: versions,
maxNumberOfItems: max(_characterBanners.length, _weaponBanners.length),
);
}

Expand Down
1 change: 1 addition & 0 deletions lib/application/banner_history/banner_history_state.dart
Expand Up @@ -7,6 +7,7 @@ class BannerHistoryState with _$BannerHistoryState {
required BannerHistorySortType sortType,
required List<BannerHistoryItemModel> banners,
required List<double> versions,
required int maxNumberOfItems,
@Default(<double>[]) List<double> selectedVersions,
@Default(<String>[]) List<String> selectedItemKeys,
}) = _InitialState;
Expand Down
131 changes: 67 additions & 64 deletions lib/presentation/banner_history/banner_history_page.dart
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:linked_scroll_controller/linked_scroll_controller.dart';
import 'package:responsive_builder/responsive_builder.dart';
import 'package:shiori/application/bloc.dart';
import 'package:shiori/domain/enums/enums.dart';
Expand All @@ -14,7 +15,7 @@ import 'package:shiori/presentation/shared/app_fab.dart';
import 'package:shiori/presentation/shared/extensions/i18n_extensions.dart';
import 'package:shiori/presentation/shared/item_popupmenu_filter.dart';
import 'package:shiori/presentation/shared/mixins/app_fab_mixin.dart';
import 'package:shiori/presentation/shared/sync_controller.dart';
import 'package:shiori/presentation/shared/nothing_found_column.dart';

const double _tabletFirstCellWidth = 150;
const double _mobileFirstCellWidth = 120;
Expand All @@ -31,15 +32,23 @@ class BannerHistoryPage extends StatefulWidget {
}

class _BannerHistoryPageState extends State<BannerHistoryPage> with SingleTickerProviderStateMixin, AppFabMixin {
late final LinkedScrollControllerGroup _verticalControllers;
late final LinkedScrollControllerGroup _horizontalControllers;
late final ScrollController _fixedHeaderScrollController;
late final ScrollController _fixedLeftColumnScrollController;
late final SyncScrollController _syncScrollController;
late final ScrollController _fabController;

@override
void initState() {
_fixedHeaderScrollController = ScrollController();
_fixedLeftColumnScrollController = ScrollController();
_syncScrollController = SyncScrollController([_fixedHeaderScrollController, _fixedLeftColumnScrollController]);
_verticalControllers = LinkedScrollControllerGroup();
_horizontalControllers = LinkedScrollControllerGroup();

_fixedHeaderScrollController = _horizontalControllers.addAndGet();
_fixedLeftColumnScrollController = _verticalControllers.addAndGet();
_fabController = _verticalControllers.addAndGet();

//another hack here, for some reason I had to invert the fab scroll listener
setFabScrollListener(_fabController, inverted: true);
super.initState();
}

Expand All @@ -56,80 +65,74 @@ class _BannerHistoryPageState extends State<BannerHistoryPage> with SingleTicker
create: (_) => Injection.bannerHistoryBloc..add(const BannerHistoryEvent.init()),
child: Scaffold(
appBar: const _AppBar(),
floatingActionButton: AppFab(
hideFabAnimController: hideFabAnimController,
scrollController: _fabController,
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
NotificationListener<ScrollNotification>(
onNotification: (scrollInfo) {
_syncScrollController.processNotification(scrollInfo, _fixedHeaderScrollController);
return true;
},
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
BlocBuilder<BannerHistoryBloc, BannerHistoryState>(
builder: (ctx, state) => FixedHeaderRow(
type: state.type,
versions: state.versions,
selectedVersions: state.selectedVersions,
margin: margin,
firstCellWidth: firstCellWidth,
firstCellHeight: _firstCellHeight,
cellWidth: cellWidth,
cellHeight: 60,
controller: _fixedHeaderScrollController,
child: BlocBuilder<BannerHistoryBloc, BannerHistoryState>(
builder: (ctx, state) => FixedHeaderRow(
type: state.type,
versions: state.versions,
selectedVersions: state.selectedVersions,
margin: margin,
firstCellWidth: firstCellWidth,
firstCellHeight: _firstCellHeight,
cellWidth: cellWidth,
cellHeight: 60,
),
),
),
),
Flexible(
child: SingleChildScrollView(
controller: scrollController,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
BlocBuilder<BannerHistoryBloc, BannerHistoryState>(
builder: (ctx, state) => FixedLeftColumn(
margin: margin,
cellWidth: firstCellWidth,
cellHeight: _cellHeight,
items: state.banners,
),
Expanded(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
BlocBuilder<BannerHistoryBloc, BannerHistoryState>(
builder: (ctx, state) => FixedLeftColumn(
margin: margin,
cellWidth: firstCellWidth,
cellHeight: _cellHeight,
items: state.banners,
controller: _fabController,
),
Flexible(
child: NotificationListener<ScrollNotification>(
onNotification: (scrollInfo) {
_syncScrollController.processNotification(scrollInfo, _fixedLeftColumnScrollController);
return true;
},
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
controller: _fixedLeftColumnScrollController,
child: BlocBuilder<BannerHistoryBloc, BannerHistoryState>(
builder: (ctx, state) => Content(
banners: state.banners,
versions: state.versions,
margin: margin,
cellWidth: cellWidth,
cellHeight: _cellHeight,
),
Expanded(
child: BlocBuilder<BannerHistoryBloc, BannerHistoryState>(
builder: (ctx, state) => state.banners.isEmpty
? const NothingFoundColumn()
: Container(
//this margin is a kinda hack xd
margin: const EdgeInsets.only(left: 8),
child: Content(
banners: state.banners,
versions: state.versions,
margin: margin,
cellWidth: cellWidth,
cellHeight: _cellHeight,
verticalController: _fixedLeftColumnScrollController,
horizontalControllerGroup: _horizontalControllers,
maxNumberOfItems: state.maxNumberOfItems,
),
),
),
),
),
)
],
),
),
),
],
),
),
],
),
floatingActionButton: AppFab(
hideFabAnimController: hideFabAnimController,
scrollController: scrollController,
mini: false,
),
),
);
}

@override
void dispose() {
_fixedHeaderScrollController.dispose();
_fixedLeftColumnScrollController.dispose();
super.dispose();
}
}

class _AppBar extends StatelessWidget implements PreferredSizeWidget {
Expand Down
119 changes: 76 additions & 43 deletions lib/presentation/banner_history/widgets/content.dart
@@ -1,13 +1,17 @@
import 'package:flutter/material.dart';
import 'package:linked_scroll_controller/linked_scroll_controller.dart';
import 'package:shiori/domain/models/models.dart';
import 'package:shiori/presentation/banner_history/widgets/item_release_history_dialog.dart';
import 'package:shiori/presentation/shared/dialogs/item_release_history_dialog.dart';

class Content extends StatelessWidget {
class Content extends StatefulWidget {
final List<BannerHistoryItemModel> banners;
final List<double> versions;
final EdgeInsets margin;
final double cellWidth;
final double cellHeight;
final ScrollController verticalController;
final LinkedScrollControllerGroup horizontalControllerGroup;
final int maxNumberOfItems;

const Content({
Key? key,
Expand All @@ -16,34 +20,65 @@ class Content extends StatelessWidget {
required this.margin,
required this.cellWidth,
required this.cellHeight,
required this.verticalController,
required this.horizontalControllerGroup,
required this.maxNumberOfItems,
}) : super(key: key);

@override
State<Content> createState() => _ContentState();
}

class _ContentState extends State<Content> {
final List<ScrollController> horizontalControllers = [];

@override
void initState() {
for (int i = 0; i < widget.maxNumberOfItems; i++) {
final controller = widget.horizontalControllerGroup.addAndGet();
horizontalControllers.add(controller);
}
super.initState();
}

@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: List.generate(
banners.length,
(i) => Row(
children: List.generate(
versions.length,
(j) {
final version = versions[j];
final banner = banners[i];
return ListView.builder(
controller: widget.verticalController,
itemCount: widget.banners.length,
itemBuilder: (context, i) {
final banner = widget.banners[i];
final horizontalController = horizontalControllers[i];
return SizedBox(
height: widget.cellHeight + widget.margin.vertical,
child: ListView.builder(
controller: horizontalController,
itemCount: banner.versions.length,
scrollDirection: Axis.horizontal,
itemBuilder: (context, j) {
final version = widget.versions[j];
return _ContentCard(
banner: banner,
number: banner.versions.firstWhere((el) => el.version == version).number,
margin: margin,
cellHeight: cellHeight,
cellWidth: cellWidth,
margin: widget.margin,
cellHeight: widget.cellHeight,
cellWidth: widget.cellWidth,
version: version,
);
},
),
),
),
);
},
);
}

@override
void dispose() {
for (final controller in horizontalControllers) {
controller.dispose();
}
super.dispose();
}
}

class _ContentCard extends StatelessWidget {
Expand Down Expand Up @@ -72,34 +107,32 @@ class _ContentCard extends StatelessWidget {
return SizedBox.fromSize(size: Size(cellWidth + margin.horizontal, cellHeight + margin.vertical));
}
final theme = Theme.of(context);
return SizedBox(
child: InkWell(
onTap: number != null
? null
: () => showDialog(
context: context,
builder: (_) => ItemReleaseHistoryDialog(
itemKey: banner.key,
itemName: banner.name,
selectedVersion: version,
),
return InkWell(
onTap: number != null
? null
: () => showDialog(
context: context,
builder: (_) => ItemReleaseHistoryDialog(
itemKey: banner.key,
itemName: banner.name,
selectedVersion: version,
),
),
child: Container(
width: cellWidth,
height: cellHeight,
margin: margin,
child: Container(
width: cellWidth,
height: cellHeight,
margin: margin,
child: Container(
alignment: Alignment.center,
margin: const EdgeInsets.only(top: 15, bottom: 15, left: 10, right: 10),
color: theme.brightness == Brightness.dark ? theme.colorScheme.background.withOpacity(0.2) : theme.dividerColor,
child: number != null
? Text(
'$number',
overflow: TextOverflow.ellipsis,
style: theme.textTheme.titleLarge!.copyWith(fontWeight: FontWeight.bold),
)
: Icon(Icons.check_circle, size: iconSize, color: Colors.green),
),
alignment: Alignment.center,
margin: const EdgeInsets.only(top: 25, bottom: 25, left: 10, right: 10),
color: theme.brightness == Brightness.dark ? theme.colorScheme.background.withOpacity(0.2) : theme.dividerColor,
child: number != null
? Text(
'$number',
overflow: TextOverflow.ellipsis,
style: theme.textTheme.titleLarge!.copyWith(fontWeight: FontWeight.bold),
)
: Icon(Icons.check_circle, size: iconSize, color: Colors.green),
),
),
);
Expand Down

0 comments on commit af0b850

Please sign in to comment.