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(
|
||||
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
// children: [
|
||||
// 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'),
|
||||
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(),
|
||||
),
|
||||
SizedBox(
|
||||
height: 148,
|
||||
child: ListView.separated(
|
||||
key: const PageStorageKey('recent_views_page_storage_key'),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8),
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemBuilder: (context, index) {
|
||||
final view = recentViews[index];
|
||||
return SizedBox.square(
|
||||
dimension: 148,
|
||||
child: MobileRecentView(view: view),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (context, index) => const HSpace(8),
|
||||
itemCount: recentViews.length,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -1,24 +1,21 @@
|
||||
import 'dart:io';
|
||||
|
||||
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/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/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_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-user/protobuf.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:string_validator/string_validator.dart';
|
||||
|
||||
class MobileRecentView extends StatefulWidget {
|
||||
class MobileRecentView extends StatelessWidget {
|
||||
const MobileRecentView({
|
||||
super.key,
|
||||
required this.view,
|
||||
@ -26,181 +23,136 @@ class MobileRecentView extends StatefulWidget {
|
||||
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
final icon = view.icon.value;
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => context.pushView(view),
|
||||
child: Stack(
|
||||
children: [
|
||||
DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: theme.colorScheme.outline),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
return BlocProvider<RecentViewBloc>(
|
||||
create: (context) => RecentViewBloc(view: view)
|
||||
..add(
|
||||
const RecentViewEvent.initial(),
|
||||
),
|
||||
child: BlocBuilder<RecentViewBloc, RecentViewState>(
|
||||
builder: (context, state) {
|
||||
return GestureDetector(
|
||||
onTap: () => context.pushView(view),
|
||||
child: Stack(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(8),
|
||||
topRight: Radius.circular(8),
|
||||
),
|
||||
child: _buildCoverWidget(),
|
||||
DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: theme.colorScheme.outline),
|
||||
),
|
||||
child: Column(
|
||||
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(
|
||||
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,
|
||||
),
|
||||
],
|
||||
),
|
||||
padding: const EdgeInsets.only(left: 8.0),
|
||||
child: state.icon.isNotEmpty
|
||||
? EmojiText(
|
||||
emoji: state.icon,
|
||||
fontSize: 30.0,
|
||||
)
|
||||
: SizedBox.square(
|
||||
dimension: 32.0,
|
||||
child: view.defaultIcon(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
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() {
|
||||
return FutureBuilder<Node?>(
|
||||
future: _getPageNode(),
|
||||
builder: (context, snapshot) {
|
||||
final node = snapshot.data;
|
||||
final placeholder = Container(
|
||||
// random color, update it once we have a better placeholder
|
||||
color:
|
||||
Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.2),
|
||||
);
|
||||
if (node == null) {
|
||||
return placeholder;
|
||||
}
|
||||
final type = CoverType.fromString(
|
||||
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;
|
||||
}
|
||||
},
|
||||
class _RecentCover extends StatelessWidget {
|
||||
const _RecentCover({
|
||||
required this.coverType,
|
||||
this.value,
|
||||
});
|
||||
|
||||
final CoverType coverType;
|
||||
final String? value;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final placeholder = Container(
|
||||
// random color, update it once we have a better placeholder
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.2),
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
final value = this.value;
|
||||
if (value == null) {
|
||||
return placeholder;
|
||||
}
|
||||
switch (coverType) {
|
||||
case CoverType.file:
|
||||
if (isURL(value)) {
|
||||
final userProfilePB = Provider.of<UserProfilePB?>(context);
|
||||
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