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

[PART-2] TW-1690: Show external channel after search #1775

Closed
Closed
Show file tree
Hide file tree
Changes from 13 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
4 changes: 3 additions & 1 deletion assets/l10n/intl_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -3059,5 +3059,7 @@
"placeholders": {
"user": {}
}
}
},
"viewRoom": "View chat",
"joinRoomFailed": "Failed to join the room"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import 'package:fluffychat/pages/search/public_room/search_public_room_view_style.dart';
import 'package:fluffychat/widgets/avatar/avatar.dart';
import 'package:fluffychat/widgets/twake_components/twake_text_button.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';

class EmptySearchPublicRoomWidget extends StatelessWidget {
final String genericSearchTerm;
final VoidCallback? onTapJoin;

const EmptySearchPublicRoomWidget({
super.key,
required this.genericSearchTerm,
this.onTapJoin,
});

@override
Widget build(BuildContext context) {
return Padding(
padding: SearchPublicRoomViewStyle.paddingListItem,
Te-Z marked this conversation as resolved.
Show resolved Hide resolved
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: SearchPublicRoomViewStyle.paddingAvatar,
child: Avatar(
name: genericSearchTerm,
),
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
genericSearchTerm,
overflow: TextOverflow.ellipsis,
maxLines: 1,
softWrap: false,
style: SearchPublicRoomViewStyle.roomNameTextStyle,
),
const SizedBox(
height: SearchPublicRoomViewStyle.nameToButtonSpace,
),
TwakeTextButton(
message: L10n.of(context)!.joinRoom,
styleMessage:
SearchPublicRoomViewStyle.joinButtonLabelStyle(context),
paddingAll: SearchPublicRoomViewStyle.paddingButton,
onTap: onTapJoin,
buttonDecoration:
SearchPublicRoomViewStyle.actionButtonDecoration(
context,
),
),
],
),
),
],
),
);
}
}
26 changes: 26 additions & 0 deletions lib/pages/search/public_room/public_room_actions.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import 'package:fluffychat/pages/search/public_room/search_public_room_view_style.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:flutter/material.dart';

enum PublicRoomActions {
join,
view;

String getLabel(BuildContext context) {
switch (this) {
case PublicRoomActions.join:
return L10n.of(context)!.joinRoom;
case PublicRoomActions.view:
return L10n.of(context)!.viewRoom;
}
}

TextStyle? getLabelStyle(BuildContext context) {
switch (this) {
case PublicRoomActions.join:
return SearchPublicRoomViewStyle.joinButtonLabelStyle(context);
case PublicRoomActions.view:
return SearchPublicRoomViewStyle.viewButtonLabelStyle(context);
}
}
}
207 changes: 207 additions & 0 deletions lib/pages/search/public_room/search_public_room_controller.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
import 'package:dartz/dartz.dart';
import 'package:fluffychat/app_state/failure.dart';
import 'package:fluffychat/app_state/success.dart';
import 'package:fluffychat/di/global/get_it_initializer.dart';
import 'package:fluffychat/domain/app_state/search/public_room_state.dart';
import 'package:fluffychat/domain/usecase/search/public_room_interactor.dart';
import 'package:fluffychat/pages/search/public_room/public_room_actions.dart';
import 'package:fluffychat/pages/search/search_debouncer_mixin.dart';
import 'package:fluffychat/presentation/model/search/public_room/presentation_search_public_room.dart';
import 'package:fluffychat/presentation/model/search/public_room/presentation_search_public_room_empty.dart';
import 'package:fluffychat/presentation/model/search/public_room/presentation_search_public_room_state.dart';
import 'package:fluffychat/utils/dialog/twake_dialog.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/utils/string_extension.dart';
import 'package:fluffychat/utils/twake_snackbar.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:matrix/matrix.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';

class SearchPublicRoomController with SearchDebouncerMixin {
SearchPublicRoomController();

final PublicRoomInteractor _publicRoomInteractor =
getIt.get<PublicRoomInteractor>();

final searchResultsNotifier =
ValueNotifier<PresentationSearchPublicRoomUIState>(
PresentationSearchPublicRoomInitial(),
);

static const int _limitPublicRoomSearchFilter = 20;

PublicRoomQueryFilter? _filter;

bool get searchTermIsNotEmpty =>
_filter?.genericSearchTerm?.isNotEmpty == true;

String? get genericSearchTerm => _filter?.genericSearchTerm;

void init() {
initializeDebouncer((keyword) {
_updateFilter(keyword);

if (keyword.isEmpty) return;

if (keyword.isRoomAlias()) {
_resetSearchResults();
_searchPublicRoom();
} else if (keyword.isRoomId()) {
_handleKeywordIsRoomId();
}
});
}

void _updateFilter(String keyword) {
_filter = PublicRoomQueryFilter(
genericSearchTerm: keyword,
);
}

void _resetSearchResults() {
searchResultsNotifier.value = PresentationSearchPublicRoom(
searchResults: [],
);
}

void _searchPublicRoom() {
_publicRoomInteractor
.execute(
filter: _filter,
limit: _limitPublicRoomSearchFilter,
server: getServerName(_filter?.genericSearchTerm),
)
.listen(
(searchResult) => _handleListenSearchPublicRoom(searchResult),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

create a streamSubscription, then dispose it when done

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

);
}

void _handleListenSearchPublicRoom(Either<Failure, Success> searchResult) {
searchResult.fold(
(failure) {
if (searchResultsNotifier.value.props.isNotEmpty) {
return;
} else {
_resetSearchResults();
searchResultsNotifier.value = PresentationSearchPublicRoomEmpty();
}
},
(success) {
if (!searchTermIsNotEmpty) {
return;
}

if (success is PublicRoomSuccess) {
if (success.publicRoomsChunk == null ||
success.publicRoomsChunk!.isEmpty) {
searchResultsNotifier.value = PresentationSearchPublicRoomEmpty();
} else {
searchResultsNotifier.value = PresentationSearchPublicRoom(
searchResults: success.publicRoomsChunk!,
);
}
}
},
);
}

void _handleKeywordIsRoomId() {
searchResultsNotifier.value = PresentationSearchPublicRoomEmpty();
}

void _viewRoom(BuildContext context, String roomId) {
context.go('/rooms/$roomId');
if (!PlatformInfos.isMobile) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why need to pop for platforms other than mobile?

Navigator.of(context, rootNavigator: false).pop();
}
}

String? getServerName(String? roomIdOrAlias) {
if (roomIdOrAlias != null) {
return roomIdOrAlias.getServerNameFromRoomIdOrAlias();
}
return null;
}

void joinRoom(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO: should use Future<void>

BuildContext context,
String roomIdOrAlias,
String? server,
) async {
final client = Matrix.of(context).client;
final result = await TwakeDialog.showFutureLoadingDialogFullScreen<String>(
future: () => client.joinRoom(
roomIdOrAlias,
serverName: server != null ? [server] : null,
),
);
if (result.error == null) {
if (client.getRoomById(result.result!) == null) {
await TwakeDialog.showFutureLoadingDialogFullScreen<SyncUpdate>(
future: () => client.onSync.stream.firstWhere(
(sync) => sync.rooms?.join?.containsKey(result.result) ?? false,
),
);
}
if (!client.getRoomById(result.result!)!.isSpace) {
context.go('/rooms/${result.result!}');
}
} else {
TwakeSnackBar.show(context, L10n.of(context)!.joinRoomFailed);
}
if (!PlatformInfos.isMobile) {
Navigator.of(context, rootNavigator: false).pop();
}
Te-Z marked this conversation as resolved.
Show resolved Hide resolved
return;
}

void handlePublicRoomActions(
BuildContext context,
PublicRoomsChunk room,
PublicRoomActions action,
) {
switch (action) {
case PublicRoomActions.join:
joinRoom(
context,
room.roomId,
getServerName(room.roomId),
);
break;
case PublicRoomActions.view:
_viewRoom(context, room.roomId);
break;
}
}

PublicRoomActions? getAction(
BuildContext context,
PublicRoomsChunk room,
) {
final client = Matrix.of(context).client;
if (client.getRoomById(room.roomId) != null) {
return PublicRoomActions.view;
} else if (room.joinRule == 'public') {
sherlockvn marked this conversation as resolved.
Show resolved Hide resolved
return PublicRoomActions.join;
}
return null;
Te-Z marked this conversation as resolved.
Show resolved Hide resolved
}

void onSearchBarChanged(String keyword) {
if (keyword.isRoomAlias() || keyword.isRoomId()) {
setDebouncerValue(keyword);
} else {
setDebouncerValue('');
_resetSearchResults();
}
}

void dispose() {
super.disposeDebouncer();
searchResultsNotifier.dispose();
_resetSearchResults();
_filter = null;
}
}
Loading