fix: only fetch visible recent view (#5090)

* fix: only fetch visible recent view

* fix: flutter analyze
This commit is contained in:
Lucas.Xu 2024-04-09 20:01:29 +08:00 committed by GitHub
parent 72049d28d5
commit 8042be6575
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 256 additions and 207 deletions

View File

@ -0,0 +1,125 @@
import 'package:appflowy/plugins/document/application/document_data_pb_extension.dart';
import 'package:appflowy/plugins/document/application/document_listener.dart';
import 'package:appflowy/plugins/document/application/document_service.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy/workspace/application/view/prelude.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'recent_view_bloc.freezed.dart';
class RecentViewBloc extends Bloc<RecentViewEvent, RecentViewState> {
RecentViewBloc({
required this.view,
}) : _documentListener = DocumentListener(id: view.id),
_viewListener = ViewListener(viewId: view.id),
super(RecentViewState.initial()) {
on<RecentViewEvent>(
(event, emit) async {
await event.when(
initial: () async {
_documentListener.start(
onDocEventUpdate: (docEvent) async {
final (coverType, coverValue) = await getCover();
add(
RecentViewEvent.updateCover(
coverType,
coverValue,
),
);
},
);
_viewListener.start(
onViewUpdated: (view) {
add(
RecentViewEvent.updateNameOrIcon(
view.name,
view.icon.value,
),
);
},
);
final (coverType, coverValue) = await getCover();
emit(
state.copyWith(
name: view.name,
icon: view.icon.value,
coverType: coverType,
coverValue: coverValue,
),
);
},
updateNameOrIcon: (name, icon) {
emit(
state.copyWith(
name: name,
icon: icon,
),
);
},
updateCover: (coverType, coverValue) {
emit(
state.copyWith(
coverType: coverType,
coverValue: coverValue,
),
);
},
);
},
);
}
final _service = DocumentService();
final ViewPB view;
final DocumentListener _documentListener;
final ViewListener _viewListener;
Future<(CoverType, String?)> getCover() async {
final result = await _service.getDocument(viewId: view.id);
final document = result.fold((s) => s.toDocument(), (f) => null);
if (document != null) {
final coverType = CoverType.fromString(
document.root.attributes[DocumentHeaderBlockKeys.coverType],
);
final coverValue = document
.root.attributes[DocumentHeaderBlockKeys.coverDetails] as String?;
return (coverType, coverValue);
}
return (CoverType.none, null);
}
@override
Future<void> close() async {
await _documentListener.stop();
await _viewListener.stop();
return super.close();
}
}
@freezed
class RecentViewEvent with _$RecentViewEvent {
const factory RecentViewEvent.initial() = Initial;
const factory RecentViewEvent.updateCover(
CoverType coverType,
String? coverValue,
) = UpdateCover;
const factory RecentViewEvent.updateNameOrIcon(
String name,
String icon,
) = UpdateNameOrIcon;
}
@freezed
class RecentViewState with _$RecentViewState {
const factory RecentViewState({
required String name,
required String icon,
@Default(CoverType.none) CoverType coverType,
@Default(null) String? coverValue,
}) = _RecentViewState;
factory RecentViewState.initial() =>
const RecentViewState(name: '', icon: '');
}

View File

@ -117,49 +117,21 @@ class _RecentViews extends StatelessWidget {
}, },
), ),
), ),
// Row( SizedBox(
// mainAxisAlignment: MainAxisAlignment.spaceBetween, height: 148,
// children: [ child: ListView.separated(
// Padding(
// padding: const EdgeInsets.symmetric(horizontal: 24),
// child: FlowyText.semibold(
// LocaleKeys.sideBar_recent.tr(),
// fontSize: 20.0,
// ),
// ),
// if (kDebugMode)
// Padding(
// padding: const EdgeInsets.only(right: 16.0),
// child: FlowyButton(
// useIntrinsicWidth: true,
// text: FlowyText(LocaleKeys.button_clear.tr()),
// onTap: () {
// context.read<RecentViewsBloc>().add(
// RecentViewsEvent.removeRecentViews(
// recentViews.map((e) => e.id).toList(),
// ),
// );
// },
// ),
// ),
// ],
// ),
SingleChildScrollView(
key: const PageStorageKey('recent_views_page_storage_key'), key: const PageStorageKey('recent_views_page_storage_key'),
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8),
child: IntrinsicHeight( scrollDirection: Axis.horizontal,
child: SeparatedRow( itemBuilder: (context, index) {
separatorBuilder: () => const HSpace(8), final view = recentViews[index];
children: recentViews return SizedBox.square(
.map(
(view) => SizedBox.square(
dimension: 148, dimension: 148,
child: MobileRecentView(view: view), child: MobileRecentView(view: view),
), );
) },
.toList(), separatorBuilder: (context, index) => const HSpace(8),
), itemCount: recentViews.length,
), ),
), ),
], ],

View File

@ -1,24 +1,21 @@
import 'dart:io'; import 'dart:io';
import 'package:appflowy/mobile/application/mobile_router.dart'; import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/mobile/application/recent/recent_view_bloc.dart';
import 'package:appflowy/plugins/base/emoji/emoji_text.dart'; import 'package:appflowy/plugins/base/emoji/emoji_text.dart';
import 'package:appflowy/plugins/document/application/document_data_pb_extension.dart';
import 'package:appflowy/plugins/document/application/document_listener.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy/shared/appflowy_network_image.dart'; import 'package:appflowy/shared/appflowy_network_image.dart';
import 'package:appflowy/workspace/application/view/prelude.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-document/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:string_validator/string_validator.dart'; import 'package:string_validator/string_validator.dart';
class MobileRecentView extends StatefulWidget { class MobileRecentView extends StatelessWidget {
const MobileRecentView({ const MobileRecentView({
super.key, super.key,
required this.view, required this.view,
@ -26,54 +23,17 @@ class MobileRecentView extends StatefulWidget {
final ViewPB view; final ViewPB view;
@override
State<MobileRecentView> createState() => _MobileRecentViewState();
}
class _MobileRecentViewState extends State<MobileRecentView> {
late final ViewListener viewListener;
late ViewPB view;
late final DocumentListener documentListener;
@override
void initState() {
super.initState();
view = widget.view;
viewListener = ViewListener(
viewId: view.id,
)..start(
onViewUpdated: (view) {
setState(() {
this.view = view;
});
},
);
documentListener = DocumentListener(id: view.id)
..start(
onDocEventUpdate: (document) {
setState(() {
view = view;
});
},
);
}
@override
void dispose() {
viewListener.stop();
documentListener.stop();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final icon = view.icon.value;
final theme = Theme.of(context); final theme = Theme.of(context);
return BlocProvider<RecentViewBloc>(
create: (context) => RecentViewBloc(view: view)
..add(
const RecentViewEvent.initial(),
),
child: BlocBuilder<RecentViewBloc, RecentViewState>(
builder: (context, state) {
return GestureDetector( return GestureDetector(
onTap: () => context.pushView(view), onTap: () => context.pushView(view),
child: Stack( child: Stack(
@ -93,7 +53,10 @@ class _MobileRecentViewState extends State<MobileRecentView> {
topLeft: Radius.circular(8), topLeft: Radius.circular(8),
topRight: Radius.circular(8), topRight: Radius.circular(8),
), ),
child: _buildCoverWidget(), child: _RecentCover(
coverType: state.coverType,
value: state.coverValue,
),
), ),
), ),
Expanded( Expanded(
@ -123,9 +86,9 @@ class _MobileRecentViewState extends State<MobileRecentView> {
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Padding( child: Padding(
padding: const EdgeInsets.only(left: 8.0), padding: const EdgeInsets.only(left: 8.0),
child: icon.isNotEmpty child: state.icon.isNotEmpty
? EmojiText( ? EmojiText(
emoji: icon, emoji: state.icon,
fontSize: 30.0, fontSize: 30.0,
) )
: SizedBox.square( : SizedBox.square(
@ -137,39 +100,41 @@ class _MobileRecentViewState extends State<MobileRecentView> {
], ],
), ),
); );
},
),
);
} }
}
Widget _buildCoverWidget() { class _RecentCover extends StatelessWidget {
return FutureBuilder<Node?>( const _RecentCover({
future: _getPageNode(), required this.coverType,
builder: (context, snapshot) { this.value,
final node = snapshot.data; });
final CoverType coverType;
final String? value;
@override
Widget build(BuildContext context) {
final placeholder = Container( final placeholder = Container(
// random color, update it once we have a better placeholder // random color, update it once we have a better placeholder
color: color: Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.2),
Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.2),
); );
if (node == null) { final value = this.value;
if (value == null) {
return placeholder; return placeholder;
} }
final type = CoverType.fromString( switch (coverType) {
node.attributes[DocumentHeaderBlockKeys.coverType],
);
final cover =
node.attributes[DocumentHeaderBlockKeys.coverDetails] as String?;
if (cover == null) {
return placeholder;
}
switch (type) {
case CoverType.file: case CoverType.file:
if (isURL(cover)) { if (isURL(value)) {
final userProfilePB = Provider.of<UserProfilePB?>(context); final userProfilePB = Provider.of<UserProfilePB?>(context);
return FlowyNetworkImage( return FlowyNetworkImage(
url: cover, url: value,
userProfilePB: userProfilePB, userProfilePB: userProfilePB,
); );
} }
final imageFile = File(cover); final imageFile = File(value);
if (!imageFile.existsSync()) { if (!imageFile.existsSync()) {
return placeholder; return placeholder;
} }
@ -178,29 +143,16 @@ class _MobileRecentViewState extends State<MobileRecentView> {
); );
case CoverType.asset: case CoverType.asset:
return Image.asset( return Image.asset(
cover, value,
fit: BoxFit.cover, fit: BoxFit.cover,
); );
case CoverType.color: case CoverType.color:
final color = cover.tryToColor() ?? Colors.white; final color = value.tryToColor() ?? Colors.white;
return Container( return Container(
color: color, color: color,
); );
case CoverType.none: case CoverType.none:
return placeholder; return placeholder;
} }
},
);
}
Future<Node?> _getPageNode() async {
final data = await DocumentEventGetDocumentData(
OpenDocumentPayloadPB(documentId: view.id),
).send();
final document = data.fold((l) => l.toDocument(), (r) => null);
if (document != null) {
return document.root;
}
return null;
} }
} }