mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
fix: only fetch visible recent view (#5090)
* fix: only fetch visible recent view * fix: flutter analyze
This commit is contained in:
parent
72049d28d5
commit
8042be6575
@ -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: '');
|
||||||
|
}
|
@ -117,49 +117,21 @@ class _RecentViews extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Row(
|
SizedBox(
|
||||||
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
height: 148,
|
||||||
// children: [
|
child: ListView.separated(
|
||||||
// Padding(
|
key: const PageStorageKey('recent_views_page_storage_key'),
|
||||||
// padding: const EdgeInsets.symmetric(horizontal: 24),
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8),
|
||||||
// child: FlowyText.semibold(
|
scrollDirection: Axis.horizontal,
|
||||||
// LocaleKeys.sideBar_recent.tr(),
|
itemBuilder: (context, index) {
|
||||||
// fontSize: 20.0,
|
final view = recentViews[index];
|
||||||
// ),
|
return SizedBox.square(
|
||||||
// ),
|
dimension: 148,
|
||||||
// if (kDebugMode)
|
child: MobileRecentView(view: view),
|
||||||
// Padding(
|
);
|
||||||
// padding: const EdgeInsets.only(right: 16.0),
|
},
|
||||||
// child: FlowyButton(
|
separatorBuilder: (context, index) => const HSpace(8),
|
||||||
// useIntrinsicWidth: true,
|
itemCount: recentViews.length,
|
||||||
// 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'),
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8),
|
|
||||||
child: IntrinsicHeight(
|
|
||||||
child: SeparatedRow(
|
|
||||||
separatorBuilder: () => const HSpace(8),
|
|
||||||
children: recentViews
|
|
||||||
.map(
|
|
||||||
(view) => SizedBox.square(
|
|
||||||
dimension: 148,
|
|
||||||
child: MobileRecentView(view: view),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -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,181 +23,136 @@ 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 GestureDetector(
|
return BlocProvider<RecentViewBloc>(
|
||||||
onTap: () => context.pushView(view),
|
create: (context) => RecentViewBloc(view: view)
|
||||||
child: Stack(
|
..add(
|
||||||
children: [
|
const RecentViewEvent.initial(),
|
||||||
DecoratedBox(
|
),
|
||||||
decoration: BoxDecoration(
|
child: BlocBuilder<RecentViewBloc, RecentViewState>(
|
||||||
borderRadius: BorderRadius.circular(8),
|
builder: (context, state) {
|
||||||
border: Border.all(color: theme.colorScheme.outline),
|
return GestureDetector(
|
||||||
),
|
onTap: () => context.pushView(view),
|
||||||
child: Column(
|
child: Stack(
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
DecoratedBox(
|
||||||
child: ClipRRect(
|
decoration: BoxDecoration(
|
||||||
borderRadius: const BorderRadius.only(
|
borderRadius: BorderRadius.circular(8),
|
||||||
topLeft: Radius.circular(8),
|
border: Border.all(color: theme.colorScheme.outline),
|
||||||
topRight: Radius.circular(8),
|
),
|
||||||
),
|
child: Column(
|
||||||
child: _buildCoverWidget(),
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(8),
|
||||||
|
topRight: Radius.circular(8),
|
||||||
|
),
|
||||||
|
child: _RecentCover(
|
||||||
|
coverType: state.coverType,
|
||||||
|
value: state.coverValue,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(8, 18, 8, 2),
|
||||||
|
// hack: minLines currently not supported in Text widget.
|
||||||
|
// https://github.com/flutter/flutter/issues/31134
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
FlowyText.medium(
|
||||||
|
view.name,
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
const FlowyText(
|
||||||
|
"\n\n",
|
||||||
|
maxLines: 2,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(8, 18, 8, 2),
|
padding: const EdgeInsets.only(left: 8.0),
|
||||||
// hack: minLines currently not supported in Text widget.
|
child: state.icon.isNotEmpty
|
||||||
// https://github.com/flutter/flutter/issues/31134
|
? EmojiText(
|
||||||
child: Stack(
|
emoji: state.icon,
|
||||||
children: [
|
fontSize: 30.0,
|
||||||
FlowyText.medium(
|
)
|
||||||
view.name,
|
: SizedBox.square(
|
||||||
maxLines: 2,
|
dimension: 32.0,
|
||||||
overflow: TextOverflow.ellipsis,
|
child: view.defaultIcon(),
|
||||||
),
|
),
|
||||||
const FlowyText(
|
|
||||||
"\n\n",
|
|
||||||
maxLines: 2,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
Align(
|
},
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 8.0),
|
|
||||||
child: icon.isNotEmpty
|
|
||||||
? EmojiText(
|
|
||||||
emoji: icon,
|
|
||||||
fontSize: 30.0,
|
|
||||||
)
|
|
||||||
: SizedBox.square(
|
|
||||||
dimension: 32.0,
|
|
||||||
child: view.defaultIcon(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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 placeholder = Container(
|
|
||||||
// random color, update it once we have a better placeholder
|
final CoverType coverType;
|
||||||
color:
|
final String? value;
|
||||||
Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.2),
|
|
||||||
);
|
@override
|
||||||
if (node == null) {
|
Widget build(BuildContext context) {
|
||||||
return placeholder;
|
final placeholder = Container(
|
||||||
}
|
// random color, update it once we have a better placeholder
|
||||||
final type = CoverType.fromString(
|
color: Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.2),
|
||||||
node.attributes[DocumentHeaderBlockKeys.coverType],
|
|
||||||
);
|
|
||||||
final cover =
|
|
||||||
node.attributes[DocumentHeaderBlockKeys.coverDetails] as String?;
|
|
||||||
if (cover == null) {
|
|
||||||
return placeholder;
|
|
||||||
}
|
|
||||||
switch (type) {
|
|
||||||
case CoverType.file:
|
|
||||||
if (isURL(cover)) {
|
|
||||||
final userProfilePB = Provider.of<UserProfilePB?>(context);
|
|
||||||
return FlowyNetworkImage(
|
|
||||||
url: cover,
|
|
||||||
userProfilePB: userProfilePB,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
final imageFile = File(cover);
|
|
||||||
if (!imageFile.existsSync()) {
|
|
||||||
return placeholder;
|
|
||||||
}
|
|
||||||
return Image.file(
|
|
||||||
imageFile,
|
|
||||||
);
|
|
||||||
case CoverType.asset:
|
|
||||||
return Image.asset(
|
|
||||||
cover,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
);
|
|
||||||
case CoverType.color:
|
|
||||||
final color = cover.tryToColor() ?? Colors.white;
|
|
||||||
return Container(
|
|
||||||
color: color,
|
|
||||||
);
|
|
||||||
case CoverType.none:
|
|
||||||
return placeholder;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
final value = this.value;
|
||||||
|
if (value == null) {
|
||||||
Future<Node?> _getPageNode() async {
|
return placeholder;
|
||||||
final data = await DocumentEventGetDocumentData(
|
}
|
||||||
OpenDocumentPayloadPB(documentId: view.id),
|
switch (coverType) {
|
||||||
).send();
|
case CoverType.file:
|
||||||
final document = data.fold((l) => l.toDocument(), (r) => null);
|
if (isURL(value)) {
|
||||||
if (document != null) {
|
final userProfilePB = Provider.of<UserProfilePB?>(context);
|
||||||
return document.root;
|
return FlowyNetworkImage(
|
||||||
|
url: value,
|
||||||
|
userProfilePB: userProfilePB,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final imageFile = File(value);
|
||||||
|
if (!imageFile.existsSync()) {
|
||||||
|
return placeholder;
|
||||||
|
}
|
||||||
|
return Image.file(
|
||||||
|
imageFile,
|
||||||
|
);
|
||||||
|
case CoverType.asset:
|
||||||
|
return Image.asset(
|
||||||
|
value,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
);
|
||||||
|
case CoverType.color:
|
||||||
|
final color = value.tryToColor() ?? Colors.white;
|
||||||
|
return Container(
|
||||||
|
color: color,
|
||||||
|
);
|
||||||
|
case CoverType.none:
|
||||||
|
return placeholder;
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user