Merge pull request #2957 from pangeachat/2562-onboarding-todos-and-tutorials
feat: getting started page
This commit is contained in:
commit
30013f6d70
15 changed files with 596 additions and 240 deletions
|
|
@ -4998,5 +4998,15 @@
|
|||
"canBeFoundViaKnock": "\u2022 request to join and admin approval",
|
||||
"anyoneCanJoin": "Anyone can join! However, admin can kick and ban whoever misbehaves. Those who are banned may not return!",
|
||||
"createYourSpace": "Create your space",
|
||||
"sendActivities": "Send activities"
|
||||
"sendActivities": "Send activities",
|
||||
"getStarted": "Get Started",
|
||||
"getStartedBotChatDesc": "Chatting with AI is a great place to start and Pangea reading, writing, listening and speaking tools make it easy!",
|
||||
"getStartedCommunitiesDesc": "Learning with a community is where Pangea Chat shines!\nYou can join your class, find a school, or even make your own!",
|
||||
"getStartedFriendsDesc": "Do you have a friend that wants to learn with you?",
|
||||
"getStartedBotChatComplete": "Well-done! You're chatting with the bot!",
|
||||
"getStartedCommunitiesComplete": "Great, you have joined a space!",
|
||||
"getStartedComplete": "You've completed this section!\nKeep exploring our amazing features by chatting with friends!",
|
||||
"getStartedFriendsComplete": "Woohoo! You've got friends! 😉",
|
||||
"getStartedBotChatButton": "Start chatting!",
|
||||
"getStartedFriendsButton": "Invite a friend"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ import 'package:fluffychat/pangea/login/pages/login_or_signup_view.dart';
|
|||
import 'package:fluffychat/pangea/login/pages/signup.dart';
|
||||
import 'package:fluffychat/pangea/login/pages/space_code_onboarding.dart';
|
||||
import 'package:fluffychat/pangea/login/pages/user_settings.dart';
|
||||
import 'package:fluffychat/pangea/onboarding/onboarding.dart';
|
||||
import 'package:fluffychat/pangea/spaces/constants/space_constants.dart';
|
||||
import 'package:fluffychat/pangea/spaces/utils/join_with_alias.dart';
|
||||
import 'package:fluffychat/pangea/spaces/utils/join_with_link.dart';
|
||||
|
|
@ -238,7 +239,7 @@ abstract class AppRoutes {
|
|||
FluffyThemes.isColumnMode(context)
|
||||
// #Pangea
|
||||
// ? const EmptyPage()
|
||||
? const SuggestionsPage()
|
||||
? const Onboarding()
|
||||
// Pangea#
|
||||
: ChatList(
|
||||
activeChat: state.pathParameters['roomid'],
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import 'package:fluffychat/pages/chat_list/dummy_chat_list_item.dart';
|
|||
import 'package:fluffychat/pages/chat_list/search_title.dart';
|
||||
import 'package:fluffychat/pages/chat_list/space_view.dart';
|
||||
import 'package:fluffychat/pangea/chat_list/widgets/pangea_chat_list_header.dart';
|
||||
import 'package:fluffychat/pangea/onboarding/onboarding.dart';
|
||||
import 'package:fluffychat/pangea/public_spaces/public_room_bottom_sheet.dart';
|
||||
import 'package:fluffychat/utils/stream_extension.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/user_dialog.dart';
|
||||
|
|
@ -287,6 +288,16 @@ class ChatListViewBody extends StatelessWidget {
|
|||
);
|
||||
},
|
||||
),
|
||||
// #Pangea
|
||||
const SliverPadding(padding: EdgeInsets.all(12.0)),
|
||||
if (!FluffyThemes.isColumnMode(context))
|
||||
SliverList.builder(
|
||||
itemCount: 1,
|
||||
itemBuilder: (context, _) {
|
||||
return const Onboarding();
|
||||
},
|
||||
),
|
||||
// Pangea#
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import 'package:fluffychat/config/app_config.dart';
|
|||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pages/chat_list/chat_list.dart';
|
||||
import 'package:fluffychat/pangea/chat_list/widgets/chat_list_view_body_wrapper.dart';
|
||||
import 'package:fluffychat/pangea/onboarding/onboarding.dart';
|
||||
import 'package:fluffychat/pangea/onboarding/onboarding_steps_enum.dart';
|
||||
import 'package:fluffychat/widgets/navigation_rail.dart';
|
||||
|
||||
class ChatListView extends StatelessWidget {
|
||||
|
|
@ -59,9 +61,14 @@ class ChatListView extends StatelessWidget {
|
|||
// #Pangea
|
||||
// body: ChatListViewBody(controller),
|
||||
body: ChatListViewBodyWrapper(controller: controller),
|
||||
// Pangea#
|
||||
// floatingActionButton: !controller.isSearchMode &&
|
||||
// controller.activeSpaceId == null
|
||||
floatingActionButton: !controller.isSearchMode &&
|
||||
controller.activeSpaceId == null
|
||||
controller.activeSpaceId == null &&
|
||||
OnboardingController.complete(
|
||||
OnboardingStepsEnum.chatWithBot,
|
||||
)
|
||||
// Pangea#
|
||||
? FloatingActionButton.extended(
|
||||
onPressed: () => context.go('/rooms/newprivatechat'),
|
||||
icon: const Icon(Icons.add_outlined),
|
||||
|
|
|
|||
|
|
@ -116,8 +116,12 @@ class NaviRailItem extends StatelessWidget {
|
|||
: UnreadRoomsBadge(
|
||||
filter: unreadBadgeFilter,
|
||||
badgePosition: BadgePosition.topEnd(
|
||||
top: -12,
|
||||
end: -8,
|
||||
// #Pangea
|
||||
// top: -12,
|
||||
// end: -8,
|
||||
top: -20,
|
||||
end: -16,
|
||||
// Pangea#
|
||||
),
|
||||
child: icon,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import 'package:fluffychat/pages/chat_list/search_title.dart';
|
|||
import 'package:fluffychat/pangea/chat_settings/constants/pangea_room_types.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/onboarding/onboarding.dart';
|
||||
import 'package:fluffychat/pangea/public_spaces/public_room_bottom_sheet.dart';
|
||||
import 'package:fluffychat/pangea/spaces/constants/space_constants.dart';
|
||||
import 'package:fluffychat/pangea/spaces/widgets/knocking_users_indicator.dart';
|
||||
|
|
@ -952,6 +953,16 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
);
|
||||
},
|
||||
),
|
||||
// #Pangea
|
||||
const SliverPadding(padding: EdgeInsets.all(12.0)),
|
||||
if (!FluffyThemes.isColumnMode(context))
|
||||
SliverList.builder(
|
||||
itemCount: 1,
|
||||
itemBuilder: (context, _) {
|
||||
return const Onboarding();
|
||||
},
|
||||
),
|
||||
// Pangea#
|
||||
const SliverPadding(padding: EdgeInsets.only(top: 32)),
|
||||
],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,8 +2,11 @@ import 'package:flutter/material.dart';
|
|||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pages/chat_list/chat_list.dart';
|
||||
import 'package:fluffychat/pangea/analytics_summary/learning_progress_indicators.dart';
|
||||
import 'package:fluffychat/pangea/onboarding/onboarding.dart';
|
||||
import 'package:fluffychat/pangea/onboarding/onboarding_steps_enum.dart';
|
||||
|
||||
class PangeaChatListHeader extends StatelessWidget
|
||||
implements PreferredSizeWidget {
|
||||
|
|
@ -33,43 +36,51 @@ class PangeaChatListHeader extends StatelessWidget
|
|||
child: Column(
|
||||
children: [
|
||||
const LearningProgressIndicators(),
|
||||
TextField(
|
||||
controller: controller.searchController,
|
||||
focusNode: controller.searchFocusNode,
|
||||
textInputAction: TextInputAction.search,
|
||||
onChanged: (text) => controller.onSearchEnter(
|
||||
text,
|
||||
globalSearch: globalSearch,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: theme.colorScheme.secondaryContainer,
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
borderRadius: BorderRadius.circular(99),
|
||||
),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
hintText: L10n.of(context).search,
|
||||
hintStyle: TextStyle(
|
||||
color: theme.colorScheme.onPrimaryContainer,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||
prefixIcon: controller.isSearchMode
|
||||
? IconButton(
|
||||
tooltip: L10n.of(context).cancel,
|
||||
icon: const Icon(Icons.close_outlined),
|
||||
onPressed: controller.cancelSearch,
|
||||
color: theme.colorScheme.onPrimaryContainer,
|
||||
)
|
||||
: IconButton(
|
||||
onPressed: controller.startSearch,
|
||||
icon: Icon(
|
||||
Icons.search_outlined,
|
||||
color: theme.colorScheme.onPrimaryContainer,
|
||||
),
|
||||
AnimatedSize(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
child: OnboardingController.complete(
|
||||
OnboardingStepsEnum.joinSpace,
|
||||
)
|
||||
? TextField(
|
||||
controller: controller.searchController,
|
||||
focusNode: controller.searchFocusNode,
|
||||
textInputAction: TextInputAction.search,
|
||||
onChanged: (text) => controller.onSearchEnter(
|
||||
text,
|
||||
globalSearch: globalSearch,
|
||||
),
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: theme.colorScheme.secondaryContainer,
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
borderRadius: BorderRadius.circular(99),
|
||||
),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
hintText: L10n.of(context).search,
|
||||
hintStyle: TextStyle(
|
||||
color: theme.colorScheme.onPrimaryContainer,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||
prefixIcon: controller.isSearchMode
|
||||
? IconButton(
|
||||
tooltip: L10n.of(context).cancel,
|
||||
icon: const Icon(Icons.close_outlined),
|
||||
onPressed: controller.cancelSearch,
|
||||
color: theme.colorScheme.onPrimaryContainer,
|
||||
)
|
||||
: IconButton(
|
||||
onPressed: controller.startSearch,
|
||||
icon: Icon(
|
||||
Icons.search_outlined,
|
||||
color:
|
||||
theme.colorScheme.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
|
@ -10,13 +9,8 @@ import 'package:sentry_flutter/sentry_flutter.dart';
|
|||
|
||||
import 'package:fluffychat/pangea/analytics_misc/get_analytics_controller.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/put_analytics_controller.dart';
|
||||
import 'package:fluffychat/pangea/bot/utils/bot_name.dart';
|
||||
import 'package:fluffychat/pangea/chat/constants/default_power_level.dart';
|
||||
import 'package:fluffychat/pangea/chat_settings/constants/bot_mode.dart';
|
||||
import 'package:fluffychat/pangea/chat_settings/models/bot_options_model.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/controllers/contextual_definition_controller.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/controllers/word_net_controller.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
|
||||
import 'package:fluffychat/pangea/events/controllers/message_data_controller.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
|
|
@ -81,10 +75,7 @@ class PangeaController {
|
|||
putAnalytics.initialize();
|
||||
getAnalytics.initialize();
|
||||
subscriptionController.initialize();
|
||||
|
||||
startChatWithBotIfNotPresent();
|
||||
setPangeaPushRules();
|
||||
// joinSupportSpace();
|
||||
}
|
||||
|
||||
/// Initialize controllers
|
||||
|
|
@ -203,158 +194,6 @@ class PangeaController {
|
|||
await getAnalytics.initialize();
|
||||
}
|
||||
|
||||
void startChatWithBotIfNotPresent() {
|
||||
Future.delayed(const Duration(milliseconds: 10000), () async {
|
||||
// check if user is logged in
|
||||
if (!matrixState.client.isLogged() ||
|
||||
matrixState.client.userID == null ||
|
||||
matrixState.client.userID == BotName.byEnvironment) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<Room> botDMs = [];
|
||||
for (final room in matrixState.client.rooms) {
|
||||
if (await room.isBotDM) {
|
||||
botDMs.add(room);
|
||||
}
|
||||
}
|
||||
|
||||
if (botDMs.isEmpty) {
|
||||
try {
|
||||
// Copied from client.dart.startDirectChat
|
||||
final directChatRoomId =
|
||||
matrixState.client.getDirectChatFromUserId(BotName.byEnvironment);
|
||||
if (directChatRoomId != null) {
|
||||
final room = matrixState.client.getRoomById(directChatRoomId);
|
||||
if (room != null) {
|
||||
if (room.membership == Membership.join) {
|
||||
return null;
|
||||
} else if (room.membership == Membership.invite) {
|
||||
// we might already have an invite into a DM room. If that is the case, we should try to join. If the room is
|
||||
// unjoinable, that will automatically leave the room, so in that case we need to continue creating a new
|
||||
// room. (This implicitly also prevents the room from being returned as a DM room by getDirectChatFromUserId,
|
||||
// because it only returns joined or invited rooms atm.)
|
||||
await room.join();
|
||||
if (room.membership != Membership.leave) {
|
||||
if (room.membership != Membership.join) {
|
||||
// Wait for room actually appears in sync with the right membership
|
||||
await matrixState.client
|
||||
.waitForRoomInSync(directChatRoomId, join: true);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// enableEncryption ??=
|
||||
// encryptionEnabled && await userOwnsEncryptionKeys(mxid);
|
||||
// if (enableEncryption) {
|
||||
// initialState ??= [];
|
||||
// if (!initialState.any((s) => s.type == EventTypes.Encryption)) {
|
||||
// initialState.add(
|
||||
// StateEvent(
|
||||
// content: {
|
||||
// 'algorithm': supportedGroupEncryptionAlgorithms.first,
|
||||
// },
|
||||
// type: EventTypes.Encryption,
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
// Start a new direct chat
|
||||
final roomId = await matrixState.client.createRoom(
|
||||
invite: [], // intentionally not invite bot yet
|
||||
isDirect: true,
|
||||
preset: CreateRoomPreset.trustedPrivateChat,
|
||||
initialState: [
|
||||
BotOptionsModel(mode: BotMode.directChat).toStateEvent,
|
||||
RoomDefaults.defaultPowerLevels(
|
||||
matrixState.client.userID!,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
Room? room = matrixState.client.getRoomById(roomId);
|
||||
if (room == null || room.membership != Membership.join) {
|
||||
// Wait for room actually appears in sync
|
||||
await matrixState.client.waitForRoomInSync(roomId, join: true);
|
||||
room = matrixState.client.getRoomById(roomId);
|
||||
if (room == null) {
|
||||
ErrorHandler.logError(
|
||||
e: "Bot chat null after waiting for room in sync",
|
||||
data: {
|
||||
"roomId": roomId,
|
||||
},
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
final botOptions = room.getState(PangeaEventTypes.botOptions);
|
||||
if (botOptions == null) {
|
||||
await matrixState.client.setRoomStateWithKey(
|
||||
roomId,
|
||||
PangeaEventTypes.botOptions,
|
||||
"",
|
||||
BotOptionsModel(mode: BotMode.directChat).toJson(),
|
||||
);
|
||||
await matrixState.client
|
||||
.getRoomStateWithKey(roomId, PangeaEventTypes.botOptions, "");
|
||||
}
|
||||
|
||||
// invite bot to direct chat
|
||||
await matrixState.client.setRoomStateWithKey(
|
||||
roomId, EventTypes.RoomMember, BotName.byEnvironment, {
|
||||
"membership": Membership.invite.name,
|
||||
"is_direct": true,
|
||||
});
|
||||
await room.addToDirectChat(BotName.byEnvironment);
|
||||
|
||||
return null;
|
||||
} catch (err, stack) {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(
|
||||
e: err,
|
||||
s: stack,
|
||||
data: {
|
||||
"directChatRoomId": matrixState.client
|
||||
.getDirectChatFromUserId(BotName.byEnvironment),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final Room botDMWithLatestActivity = botDMs.reduce((a, b) {
|
||||
if (a.timeline == null ||
|
||||
b.timeline == null ||
|
||||
a.timeline!.events.isEmpty ||
|
||||
b.timeline!.events.isEmpty) {
|
||||
return a;
|
||||
}
|
||||
final aLastEvent = a.timeline!.events.last;
|
||||
final bLastEvent = b.timeline!.events.last;
|
||||
return aLastEvent.originServerTs.isAfter(bLastEvent.originServerTs)
|
||||
? a
|
||||
: b;
|
||||
});
|
||||
|
||||
for (final room in botDMs) {
|
||||
if (room.id != botDMWithLatestActivity.id) {
|
||||
await room.leave();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
final participants = await botDMWithLatestActivity.requestParticipants();
|
||||
final joinedParticipants =
|
||||
participants.where((e) => e.membership == Membership.join).toList();
|
||||
if (joinedParticipants.length < 2) {
|
||||
await botDMWithLatestActivity.invite(BotName.byEnvironment);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _subscribeToStreams() {
|
||||
matrixState.client.onLoginStateChanged.stream
|
||||
.listen(_handleLoginStateChange);
|
||||
|
|
|
|||
109
lib/pangea/onboarding/onboarding.dart
Normal file
109
lib/pangea/onboarding/onboarding.dart
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:get_storage/get_storage.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/bot/utils/bot_name.dart';
|
||||
import 'package:fluffychat/pangea/chat/constants/default_power_level.dart';
|
||||
import 'package:fluffychat/pangea/chat_settings/constants/bot_mode.dart';
|
||||
import 'package:fluffychat/pangea/chat_settings/models/bot_options_model.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/onboarding/onboarding_steps_enum.dart';
|
||||
import 'package:fluffychat/pangea/onboarding/onboarding_view.dart';
|
||||
import 'package:fluffychat/utils/fluffy_share.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class Onboarding extends StatefulWidget {
|
||||
const Onboarding({super.key});
|
||||
|
||||
@override
|
||||
OnboardingController createState() => OnboardingController();
|
||||
}
|
||||
|
||||
class OnboardingController extends State<Onboarding> {
|
||||
static final GetStorage _onboardingStorage = GetStorage('onboarding_storage');
|
||||
|
||||
static bool get isClosed => _onboardingStorage.read('closed') ?? false;
|
||||
|
||||
static bool get isComplete => OnboardingStepsEnum.values.every(
|
||||
(step) => complete(step),
|
||||
);
|
||||
|
||||
static bool complete(OnboardingStepsEnum step) {
|
||||
switch (step) {
|
||||
case OnboardingStepsEnum.chatWithBot:
|
||||
return hasBotDM;
|
||||
case OnboardingStepsEnum.joinSpace:
|
||||
return MatrixState.pangeaController.matrixState.client.rooms.any(
|
||||
(r) => r.isSpace,
|
||||
);
|
||||
case OnboardingStepsEnum.inviteFriends:
|
||||
return hasInvitedFriends;
|
||||
}
|
||||
}
|
||||
|
||||
static bool get hasInvitedFriends =>
|
||||
_onboardingStorage.read('invite_friends') ?? false;
|
||||
|
||||
static bool get hasBotDM =>
|
||||
MatrixState.pangeaController.matrixState.client.rooms.any((room) {
|
||||
if (room.isDirectChat &&
|
||||
room.directChatMatrixID == BotName.byEnvironment) {
|
||||
return true;
|
||||
}
|
||||
if (room.botOptions?.mode == BotMode.directChat) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
Future<void> closeCompletedMessage() async {
|
||||
await _onboardingStorage.write('closed', true);
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
Future<void> inviteFriends() async {
|
||||
FluffyShare.shareInviteLink(context);
|
||||
await _onboardingStorage.write('invite_friends', true);
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
Future<void> startChatWithBot() async {
|
||||
final resp = await showFutureLoadingDialog<String>(
|
||||
context: context,
|
||||
future: () => Matrix.of(context).client.createRoom(
|
||||
invite: [BotName.byEnvironment],
|
||||
isDirect: true,
|
||||
preset: CreateRoomPreset.trustedPrivateChat,
|
||||
initialState: [
|
||||
BotOptionsModel(mode: BotMode.directChat).toStateEvent,
|
||||
RoomDefaults.defaultPowerLevels(
|
||||
Matrix.of(context).client.userID!,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (resp.isError) return;
|
||||
context.go("/rooms/${resp.result}");
|
||||
}
|
||||
|
||||
void joinCommunities() {
|
||||
context.go('/rooms/communities');
|
||||
}
|
||||
|
||||
Future<void> onPressed(OnboardingStepsEnum step) async {
|
||||
switch (step) {
|
||||
case OnboardingStepsEnum.chatWithBot:
|
||||
return startChatWithBot();
|
||||
case OnboardingStepsEnum.joinSpace:
|
||||
return joinCommunities();
|
||||
case OnboardingStepsEnum.inviteFriends:
|
||||
return inviteFriends();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => OnboardingView(controller: this);
|
||||
}
|
||||
71
lib/pangea/onboarding/onboarding_complete.dart
Normal file
71
lib/pangea/onboarding/onboarding_complete.dart
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pangea/onboarding/onboarding.dart';
|
||||
import 'package:fluffychat/pangea/onboarding/onboarding_constants.dart';
|
||||
|
||||
class OnboardingComplete extends StatelessWidget {
|
||||
final OnboardingController controller;
|
||||
const OnboardingComplete({super.key, required this.controller});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FluffyThemes.isColumnMode(context)
|
||||
? Text(
|
||||
L10n.of(context).getStartedComplete,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontSize: 32.0,
|
||||
),
|
||||
)
|
||||
: Stack(
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.onSurface.withAlpha(20),
|
||||
borderRadius: BorderRadius.circular(
|
||||
10.0,
|
||||
),
|
||||
),
|
||||
margin: const EdgeInsets.all(12.0),
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
48.0,
|
||||
8.0,
|
||||
48.0,
|
||||
0.0,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 24.0,
|
||||
children: [
|
||||
Text(
|
||||
L10n.of(context).getStartedComplete,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontSize: 14.0,
|
||||
),
|
||||
),
|
||||
CachedNetworkImage(
|
||||
imageUrl:
|
||||
"${AppConfig.assetsBaseURL}/${OnboardingConstants.onboardingImageFileName}",
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
right: 16.0,
|
||||
top: 16.0,
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: controller.closeCompletedMessage,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
3
lib/pangea/onboarding/onboarding_constants.dart
Normal file
3
lib/pangea/onboarding/onboarding_constants.dart
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
class OnboardingConstants {
|
||||
static String onboardingImageFileName = "Getting+Started.png";
|
||||
}
|
||||
104
lib/pangea/onboarding/onboarding_step.dart
Normal file
104
lib/pangea/onboarding/onboarding_step.dart
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pangea/onboarding/onboarding_steps_enum.dart';
|
||||
|
||||
class OnboardingStep extends StatelessWidget {
|
||||
final OnboardingStepsEnum step;
|
||||
|
||||
final bool isComplete;
|
||||
final VoidCallback onPressed;
|
||||
|
||||
const OnboardingStep({
|
||||
super.key,
|
||||
required this.step,
|
||||
this.isComplete = false,
|
||||
required this.onPressed,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isColumnMode = FluffyThemes.isColumnMode(context);
|
||||
|
||||
return Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: isColumnMode ? 20.0 : 8.0,
|
||||
vertical: isColumnMode ? 24.0 : 8.0,
|
||||
),
|
||||
margin: isColumnMode
|
||||
? const EdgeInsets.only(
|
||||
bottom: 10.0,
|
||||
)
|
||||
: const EdgeInsets.all(0.0),
|
||||
decoration: isColumnMode && isComplete
|
||||
? ShapeDecoration(
|
||||
shape: RoundedRectangleBorder(
|
||||
side: const BorderSide(
|
||||
width: 1,
|
||||
color: AppConfig.success,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(
|
||||
24,
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
child: Row(
|
||||
spacing: isColumnMode ? 24.0 : 12.0,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.task_alt,
|
||||
size: isColumnMode ? 30.0 : 18.0,
|
||||
color: isComplete
|
||||
? AppConfig.success
|
||||
: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: isColumnMode ? 16.0 : 8.0,
|
||||
children: [
|
||||
Text(
|
||||
isComplete
|
||||
? step.completeMessage(
|
||||
L10n.of(context),
|
||||
)
|
||||
: step.description(
|
||||
L10n.of(context),
|
||||
),
|
||||
style: TextStyle(
|
||||
fontSize: isColumnMode ? 20.0 : 12.0,
|
||||
),
|
||||
),
|
||||
if (!isComplete)
|
||||
ElevatedButton(
|
||||
onPressed: onPressed,
|
||||
child: Row(
|
||||
spacing: 8.0,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
step.icon(18.0),
|
||||
Text(
|
||||
step.buttonText(
|
||||
L10n.of(
|
||||
context,
|
||||
),
|
||||
),
|
||||
style: const TextStyle(
|
||||
fontSize: 14.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
55
lib/pangea/onboarding/onboarding_steps_enum.dart
Normal file
55
lib/pangea/onboarding/onboarding_steps_enum.dart
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/bot/widgets/bot_face_svg.dart';
|
||||
|
||||
enum OnboardingStepsEnum {
|
||||
chatWithBot,
|
||||
joinSpace,
|
||||
inviteFriends;
|
||||
|
||||
String description(L10n l10n) {
|
||||
switch (this) {
|
||||
case OnboardingStepsEnum.chatWithBot:
|
||||
return l10n.getStartedBotChatDesc;
|
||||
case OnboardingStepsEnum.joinSpace:
|
||||
return l10n.getStartedCommunitiesDesc;
|
||||
case OnboardingStepsEnum.inviteFriends:
|
||||
return l10n.getStartedFriendsDesc;
|
||||
}
|
||||
}
|
||||
|
||||
String completeMessage(L10n l10n) {
|
||||
switch (this) {
|
||||
case OnboardingStepsEnum.chatWithBot:
|
||||
return l10n.getStartedBotChatComplete;
|
||||
case OnboardingStepsEnum.joinSpace:
|
||||
return l10n.getStartedCommunitiesComplete;
|
||||
case OnboardingStepsEnum.inviteFriends:
|
||||
return l10n.getStartedFriendsComplete;
|
||||
}
|
||||
}
|
||||
|
||||
Widget icon(double size) {
|
||||
switch (this) {
|
||||
case OnboardingStepsEnum.chatWithBot:
|
||||
return BotFace(expression: BotExpression.gold, width: size);
|
||||
case OnboardingStepsEnum.joinSpace:
|
||||
return Icon(Icons.groups_outlined, size: size);
|
||||
case OnboardingStepsEnum.inviteFriends:
|
||||
return Icon(Icons.share, size: size);
|
||||
}
|
||||
}
|
||||
|
||||
String buttonText(L10n l10n) {
|
||||
switch (this) {
|
||||
case OnboardingStepsEnum.chatWithBot:
|
||||
return l10n.getStartedBotChatButton;
|
||||
case OnboardingStepsEnum.joinSpace:
|
||||
return l10n.findYourPeople;
|
||||
case OnboardingStepsEnum.inviteFriends:
|
||||
return l10n.getStartedFriendsButton;
|
||||
}
|
||||
}
|
||||
}
|
||||
130
lib/pangea/onboarding/onboarding_view.dart
Normal file
130
lib/pangea/onboarding/onboarding_view.dart
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pangea/onboarding/onboarding.dart';
|
||||
import 'package:fluffychat/pangea/onboarding/onboarding_complete.dart';
|
||||
import 'package:fluffychat/pangea/onboarding/onboarding_constants.dart';
|
||||
import 'package:fluffychat/pangea/onboarding/onboarding_step.dart';
|
||||
import 'package:fluffychat/pangea/onboarding/onboarding_steps_enum.dart';
|
||||
import 'package:fluffychat/utils/stream_extension.dart';
|
||||
import 'package:fluffychat/widgets/layouts/max_width_body.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class OnboardingView extends StatelessWidget {
|
||||
final OnboardingController controller;
|
||||
|
||||
const OnboardingView({
|
||||
super.key,
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final client = Matrix.of(context).client;
|
||||
final isColumnMode = FluffyThemes.isColumnMode(context);
|
||||
|
||||
final screenheight = MediaQuery.of(context).size.height;
|
||||
|
||||
return Material(
|
||||
child: StreamBuilder(
|
||||
key: ValueKey(
|
||||
client.userID.toString(),
|
||||
),
|
||||
stream: client.onSync.stream
|
||||
.where((s) => s.hasRoomUpdate)
|
||||
.rateLimit(const Duration(seconds: 1)),
|
||||
builder: (context, _) {
|
||||
return Stack(
|
||||
alignment: Alignment.topCenter,
|
||||
children: [
|
||||
if (isColumnMode && !OnboardingController.isClosed)
|
||||
Positioned(
|
||||
bottom: 0.0,
|
||||
child: AnimatedOpacity(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
opacity: OnboardingController.isComplete ? 1.0 : 0.3,
|
||||
child: CachedNetworkImage(
|
||||
imageUrl:
|
||||
"${AppConfig.assetsBaseURL}/${OnboardingConstants.onboardingImageFileName}",
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
AnimatedContainer(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
height: OnboardingController.isClosed ? 0 : screenheight,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: 12.0,
|
||||
horizontal: isColumnMode ? 20.0 : 8.0,
|
||||
),
|
||||
child: MaxWidthBody(
|
||||
showBorder: false,
|
||||
maxWidth: 850.0,
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
L10n.of(context).getStarted,
|
||||
style: TextStyle(
|
||||
fontSize: isColumnMode ? 32.0 : 16.0,
|
||||
height: isColumnMode ? 1.2 : 1.5,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.all(
|
||||
isColumnMode ? 40.0 : 12.0,
|
||||
),
|
||||
child: Row(
|
||||
spacing: 8.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: OnboardingStepsEnum.values.map((step) {
|
||||
final complete =
|
||||
OnboardingController.complete(step);
|
||||
return CircleAvatar(
|
||||
radius: 6.0,
|
||||
backgroundColor: complete
|
||||
? AppConfig.success
|
||||
: Theme.of(context).colorScheme.primary,
|
||||
child: CircleAvatar(
|
||||
radius: 3.0,
|
||||
backgroundColor:
|
||||
Theme.of(context).colorScheme.surface,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
OnboardingController.isComplete
|
||||
? OnboardingComplete(
|
||||
controller: controller,
|
||||
)
|
||||
: Column(
|
||||
spacing: 12.0,
|
||||
children: [
|
||||
for (final step in OnboardingStepsEnum.values)
|
||||
OnboardingStep(
|
||||
step: step,
|
||||
isComplete:
|
||||
OnboardingController.complete(step),
|
||||
onPressed: () =>
|
||||
controller.onPressed(step),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -88,18 +88,10 @@ class SpacesNavigationRail extends StatelessWidget {
|
|||
// #Pangea
|
||||
if (i == 0) {
|
||||
return NaviRailItem(
|
||||
isSelected: isColumnMode
|
||||
? activeSpaceId == null &&
|
||||
!isSettings &&
|
||||
!isCommunities
|
||||
: isHomepage,
|
||||
isSelected: isHomepage,
|
||||
onTap: () {
|
||||
if (isColumnMode) {
|
||||
onGoToChats();
|
||||
} else {
|
||||
clearActiveSpace?.call();
|
||||
context.go("/rooms/homepage");
|
||||
}
|
||||
clearActiveSpace?.call();
|
||||
context.go("/rooms/homepage");
|
||||
},
|
||||
backgroundColor: Colors.transparent,
|
||||
icon: FutureBuilder<Profile>(
|
||||
|
|
@ -127,32 +119,30 @@ class SpacesNavigationRail extends StatelessWidget {
|
|||
i--;
|
||||
// Pangea#
|
||||
if (i == 0) {
|
||||
return isColumnMode
|
||||
? const SizedBox()
|
||||
: NaviRailItem(
|
||||
// #Pangea
|
||||
// isSelected: activeSpaceId == null && !isSettings,
|
||||
isSelected: activeSpaceId == null &&
|
||||
!isSettings &&
|
||||
!isHomepage &&
|
||||
!isCommunities,
|
||||
// Pangea#
|
||||
onTap: onGoToChats,
|
||||
// #Pangea
|
||||
// icon: const Padding(
|
||||
// padding: EdgeInsets.all(10.0),
|
||||
// child: Icon(Icons.forum_outlined),
|
||||
// ),
|
||||
// selectedIcon: const Padding(
|
||||
// padding: EdgeInsets.all(10.0),
|
||||
// child: Icon(Icons.forum),
|
||||
// ),
|
||||
icon: const Icon(Icons.forum_outlined),
|
||||
selectedIcon: const Icon(Icons.forum),
|
||||
// Pangea#
|
||||
toolTip: L10n.of(context).chats,
|
||||
unreadBadgeFilter: (room) => true,
|
||||
);
|
||||
return NaviRailItem(
|
||||
// #Pangea
|
||||
// isSelected: activeSpaceId == null && !isSettings,
|
||||
isSelected: activeSpaceId == null &&
|
||||
!isSettings &&
|
||||
!isHomepage &&
|
||||
!isCommunities,
|
||||
// Pangea#
|
||||
onTap: onGoToChats,
|
||||
// #Pangea
|
||||
// icon: const Padding(
|
||||
// padding: EdgeInsets.all(10.0),
|
||||
// child: Icon(Icons.forum_outlined),
|
||||
// ),
|
||||
// selectedIcon: const Padding(
|
||||
// padding: EdgeInsets.all(10.0),
|
||||
// child: Icon(Icons.forum),
|
||||
// ),
|
||||
icon: const Icon(Icons.forum_outlined),
|
||||
selectedIcon: const Icon(Icons.forum),
|
||||
// Pangea#
|
||||
toolTip: L10n.of(context).chats,
|
||||
unreadBadgeFilter: (room) => true,
|
||||
);
|
||||
}
|
||||
i--;
|
||||
if (i == rootSpaces.length) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue