Fix keyboard avoidance on touchscreens for Ask AI tab (#7670)

* Fix keyboard avoidance on touchscreens for Ask AI tab
* Add placementId for in-app analytics

Don't show in changelog
This commit is contained in:
Florian Rival
2025-06-24 14:43:08 +01:00
committed by GitHub
parent f26e56c3bf
commit a4c2778b8d
27 changed files with 117 additions and 12 deletions

View File

@@ -30,6 +30,21 @@
justify-content: center;
animation: new-chat-appear 0.5s;
margin-bottom: var(--safe-area-inset-bottom);
}
.aiRequestChatContainer {
display: flex;
margin-left: 8px;
margin-right: 8px;
flex-direction: column;
align-items: stretch;
justify-content: stretch;
flex: 1 1 0%;
min-height: 0px;
margin-bottom: var(--safe-area-inset-bottom);
}
.thinkingText {

View File

@@ -37,6 +37,7 @@ import Hammer from '../../UI/CustomSvgIcons/Hammer';
import { ChatMessages } from './ChatMessages';
import Send from '../../UI/CustomSvgIcons/Send';
import { FeedbackBanner } from './FeedbackBanner';
import classNames from 'classnames';
const TOO_MANY_USER_MESSAGES_WARNING_COUNT = 5;
const TOO_MANY_USER_MESSAGES_ERROR_COUNT = 10;
@@ -330,6 +331,7 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
const subscriptionBanner =
quota && quota.limitReached && increaseQuotaOffering !== 'none' ? (
<GetSubscriptionCard
placementId="ai-requests"
subscriptionDialogOpeningReason={
increaseQuotaOffering === 'subscribe'
? 'AI requests (subscribe)'
@@ -386,7 +388,13 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
if (!aiRequest) {
return (
<div className={classes.newChatContainer}>
<div
className={classNames({
[classes.newChatContainer]: true,
// Move the entire screen up when the soft keyboard is open:
'avoid-soft-keyboard': true,
})}
>
<ColumnStackLayout justifyContent="center" expand>
<Line noMargin justifyContent="center">
<RobotIcon rotating size={40} />
@@ -620,11 +628,10 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
) : null;
return (
<Column
expand
alignItems="stretch"
justifyContent="stretch"
useFullHeight
<div
className={classNames({
[classes.aiRequestChatContainer]: true,
})}
>
<ScrollView ref={scrollViewRef} style={styles.chatScrollView}>
<ChatMessages
@@ -663,6 +670,10 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
userMessage: userRequestTextPerAiRequestId[aiRequestId] || '',
});
}}
className={classNames({
// Move the form up when the soft keyboard is open:
'avoid-soft-keyboard': true,
})}
>
<ColumnStackLayout
justifyContent="stretch"
@@ -798,7 +809,7 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
</Column>
</ColumnStackLayout>
</form>
</Column>
</div>
);
}
);

View File

@@ -656,6 +656,7 @@ const PrivateAssetPackInformationPage = ({
analyticsMetadata: {
reason: 'Claim asset pack',
recommendedPlanId: 'gdevelop_gold',
placementId: 'claim-asset-pack',
},
filter: 'individual',
})

View File

@@ -174,6 +174,7 @@ const LockedCourseChapterPreview = React.forwardRef<Props, HTMLDivElement>(
analyticsMetadata: {
reason: 'Unlock course chapter',
recommendedPlanId: 'gdevelop_silver',
placementId: 'unlock-course-chapter',
},
})
}

View File

@@ -371,6 +371,7 @@ export default class LocalPreviewLauncher extends React.Component<
}
id="Preview over wifi"
title={<Trans>Preview over wifi</Trans>}
placementId="preview-wifi"
mode="try"
isNotShownDuringInAppTutorial
/>
@@ -382,6 +383,7 @@ export default class LocalPreviewLauncher extends React.Component<
title={
<Trans>Live preview (apply changes to the running preview)</Trans>
}
placementId="hot-reloading"
mode="try"
isNotShownDuringInAppTutorial
/>

View File

@@ -326,6 +326,7 @@ const InviteHome = ({ cloudProjectId }: Props) => {
<GetSubscriptionCard
subscriptionDialogOpeningReason="Add collaborators on project"
recommendedPlanIdIfNoSubscription="gdevelop_startup"
placementId="invite-collaborators"
>
<Text>
<Trans>

View File

@@ -350,6 +350,7 @@ function LeaderboardAppearanceDialog({
<GetSubscriptionCard
subscriptionDialogOpeningReason="Leaderboard customization"
recommendedPlanIdIfNoSubscription="gdevelop_silver"
placementId="leaderboards-customization"
>
<Line>
<Column noMargin>
@@ -406,6 +407,7 @@ function LeaderboardAppearanceDialog({
<GetSubscriptionCard
subscriptionDialogOpeningReason="Leaderboard customization"
recommendedPlanIdIfNoSubscription="gdevelop_startup"
placementId="leaderboards-customization"
>
<Line>
<Column noMargin>

View File

@@ -256,6 +256,7 @@ function LeaderboardOptionsDialog({
<GetSubscriptionCard
subscriptionDialogOpeningReason="Leaderboard customization"
recommendedPlanIdIfNoSubscription="gdevelop_startup"
placementId="leaderboards-customization"
>
<Line>
<Column noMargin>

View File

@@ -47,6 +47,7 @@ const MaxLeaderboardCountAlertMessage = () => {
<Column expand>
<GetSubscriptionCard
subscriptionDialogOpeningReason="Leaderboard count per game limit reached"
placementId="leaderboards"
label={
!hasValidSubscription ? (
<Trans>Upgrade to GDevelop Premium</Trans>

View File

@@ -98,6 +98,7 @@ const ServicesWidget = ({
analyticsMetadata: {
reason: 'Leaderboard count per game limit reached',
recommendedPlanId: 'gdevelop_silver',
placementId: 'leaderboards',
},
})
}

View File

@@ -105,6 +105,7 @@ export class DebuggerEditorContainer extends React.Component<
}
id="Debugger"
title={<Trans>Debugger</Trans>}
placementId="debugger"
mode="try"
/>
</React.Fragment>

View File

@@ -55,6 +55,7 @@ export const MaxProjectCountAlertMessage = ({ margin }: Props) => {
}
hideButton={!canMaximumCountBeIncreased}
recommendedPlanIdIfNoSubscription="gdevelop_silver"
placementId="max-projects-reached"
>
<Line>
<Column noMargin expand>

View File

@@ -241,6 +241,7 @@ const EducationMarketingSection = ({
analyticsMetadata: {
reason: 'Callout in Classroom tab',
recommendedPlanId: 'gdevelop_education',
placementId: 'education',
},
});
},

View File

@@ -638,6 +638,7 @@ const ManageEducationAccountDialog = ({ onClose }: Props) => {
analyticsMetadata: {
reason: 'Manage subscription as teacher',
recommendedPlanId: 'gdevelop_education',
placementId: 'education',
},
filter: 'education',
})

View File

@@ -173,6 +173,7 @@ const CurrentUsageDisplayer = ({
}
hideButton={cannotUpgradeSubscription}
recommendedPlanIdIfNoSubscription="gdevelop_silver"
placementId="builds"
>
<Line>
{!isFeatureLocked ? (
@@ -219,6 +220,7 @@ const CurrentUsageDisplayer = ({
}
}
recommendedPlanIdIfNoSubscription="gdevelop_silver"
placementId="builds"
>
<Line>
{!isFeatureLocked ? (

View File

@@ -3,7 +3,10 @@ import * as React from 'react';
import { Trans } from '@lingui/macro';
import { Column, Line } from '../../UI/Grid';
import { ResponsiveLineStackLayout } from '../../UI/Layout';
import { type SubscriptionDialogDisplayReason } from '../../Utils/Analytics/EventSender';
import {
type SubscriptionDialogDisplayReason,
type SubscriptionPlacementId,
} from '../../Utils/Analytics/EventSender';
import { SubscriptionSuggestionContext } from './SubscriptionSuggestionContext';
import RaisedButton from '../../UI/RaisedButton';
import FlatButton from '../../UI/FlatButton';
@@ -53,6 +56,7 @@ type Props = {|
| 'gdevelop_startup'
| 'gdevelop_education',
canHide?: boolean,
placementId: SubscriptionPlacementId,
|};
const GetSubscriptionCard = ({
@@ -66,6 +70,7 @@ const GetSubscriptionCard = ({
filter,
recommendedPlanIdIfNoSubscription,
canHide,
placementId,
}: Props) => {
const [isHidden, setIsHidden] = React.useState(false);
const { subscription } = React.useContext(AuthenticatedUserContext);
@@ -116,6 +121,7 @@ const GetSubscriptionCard = ({
analyticsMetadata: {
reason: subscriptionDialogOpeningReason,
recommendedPlanId: actualPlanIdToRecommend,
placementId,
},
filter,
});

View File

@@ -16,6 +16,7 @@ import { isNativeMobileApp } from '../../Utils/Platform';
import InAppTutorialContext from '../../InAppTutorial/InAppTutorialContext';
import GetSubscriptionCard from './GetSubscriptionCard';
import { ColumnStackLayout } from '../../UI/Layout';
import { type SubscriptionPlacementId } from '../../Utils/Analytics/EventSender';
export type SubscriptionCheckerInterface = {|
checkUserHasSubscription: () => boolean,
@@ -29,6 +30,7 @@ type Props = {|
| 'Debugger'
| 'Hot reloading'
| 'Preview over wifi',
placementId: SubscriptionPlacementId,
onChangeSubscription?: () => Promise<void> | void,
mode: 'try' | 'mandatory',
isNotShownDuringInAppTutorial?: boolean,
@@ -39,7 +41,14 @@ const SubscriptionChecker = React.forwardRef<
SubscriptionCheckerInterface
>(
(
{ mode, id, title, onChangeSubscription, isNotShownDuringInAppTutorial },
{
mode,
id,
title,
onChangeSubscription,
placementId,
isNotShownDuringInAppTutorial,
},
ref
) => {
const authenticatedUser = React.useContext(AuthenticatedUserContext);
@@ -122,6 +131,7 @@ const SubscriptionChecker = React.forwardRef<
setDialogOpen(false);
}}
recommendedPlanIdIfNoSubscription="gdevelop_silver"
placementId={placementId}
>
<Column noMargin expand>
<Text>

View File

@@ -341,7 +341,10 @@ const SubscriptionDetails = ({
primary
onClick={() => {
openSubscriptionDialog({
analyticsMetadata: { reason: 'Consult profile' },
analyticsMetadata: {
reason: 'Consult profile',
placementId: 'profile',
},
});
}}
disabled={isManageSubscriptionLoading}
@@ -390,6 +393,7 @@ const SubscriptionDetails = ({
<GetSubscriptionCard
label={<Trans>Choose a subscription</Trans>}
subscriptionDialogOpeningReason="Consult profile"
placementId="profile"
>
<Text noMargin>
<Trans>
@@ -440,7 +444,10 @@ const SubscriptionDetails = ({
color={buttonColor}
onClick={() =>
openSubscriptionDialog({
analyticsMetadata: { reason: 'Consult profile' },
analyticsMetadata: {
reason: 'Consult profile',
placementId: 'profile',
},
filter: key,
})
}
@@ -458,6 +465,7 @@ const SubscriptionDetails = ({
label={<Trans>Choose a subscription</Trans>}
subscriptionDialogOpeningReason="Consult profile"
recommendedPlanIdIfNoSubscription="gdevelop_silver"
placementId="profile"
>
<Text noMargin>
<Trans>

View File

@@ -5,6 +5,7 @@ import SubscriptionDialog from './SubscriptionDialog';
import {
sendSubscriptionDialogShown,
type SubscriptionDialogDisplayReason,
type SubscriptionPlacementId,
} from '../../Utils/Analytics/EventSender';
import { isNativeMobileApp } from '../../Utils/Platform';
import {
@@ -26,6 +27,7 @@ export type SubscriptionType = 'individual' | 'team' | 'education';
export type SubscriptionAnalyticsMetadata = {|
reason: SubscriptionDialogDisplayReason,
recommendedPlanId?: string,
placementId: SubscriptionPlacementId,
preStep?: 'subscriptionChecker',
|};

View File

@@ -238,6 +238,7 @@ export const LoadingScreenEditor = ({
<GetSubscriptionCard
subscriptionDialogOpeningReason="Disable GDevelop splash at startup"
recommendedPlanIdIfNoSubscription="gdevelop_silver"
placementId="gdevelop-branding"
>
<Text>
<Trans>
@@ -555,6 +556,7 @@ export const LoadingScreenEditor = ({
mode="mandatory"
id="Disable GDevelop splash at startup"
title={<Trans>Disable GDevelop splash at startup</Trans>}
placementId="gdevelop-branding"
/>
</ColumnStackLayout>
)}

View File

@@ -32,6 +32,7 @@ const GetPremiumButton = () => {
analyticsMetadata: {
reason: 'Account get premium',
recommendedPlanId: 'gdevelop_silver',
placementId: 'account-get-premium',
},
});
}}

View File

@@ -390,6 +390,26 @@ export type SubscriptionDialogDisplayReason =
| 'AI requests (subscribe)'
| 'AI requests (upgrade)';
export type SubscriptionPlacementId =
| 'builds'
| 'debugger'
| 'gdevelop-branding'
| 'generate-from-prompt'
| 'hot-reloading'
| 'leaderboards-customization'
| 'leaderboards'
| 'max-projects-reached'
| 'opening-from-link'
| 'preview-wifi'
| 'profile'
| 'invite-collaborators'
| 'version-history'
| 'claim-asset-pack'
| 'unlock-course-chapter'
| 'account-get-premium'
| 'education'
| 'ai-requests';
export const sendSubscriptionDialogShown = (
metadata: SubscriptionAnalyticsMetadata
) => {

View File

@@ -43,6 +43,7 @@ const useOpenInitialDialog = ({
analyticsMetadata: {
reason: 'Landing dialog at opening',
recommendedPlanId,
placementId: 'opening-from-link',
},
});
removeRouteArguments(['initial-dialog', 'recommended-plan-id']);

View File

@@ -455,6 +455,7 @@ const useVersionHistory = ({
forceColumnLayout
filter="team"
recommendedPlanIdIfNoSubscription="gdevelop_startup"
placementId="version-history"
>
<Text>
<Trans>

View File

@@ -17,7 +17,10 @@ export default {
export const Default = () => (
<AuthenticatedUserContext.Provider value={fakeNotAuthenticatedUser}>
<GetSubscriptionCard subscriptionDialogOpeningReason="Build limit reached">
<GetSubscriptionCard
placementId="builds"
subscriptionDialogOpeningReason="Build limit reached"
>
<Line>
<Column noMargin>
<Text noMargin>
@@ -32,6 +35,7 @@ export const Default = () => (
export const CustomLabel = () => (
<AuthenticatedUserContext.Provider value={fakeNotAuthenticatedUser}>
<GetSubscriptionCard
placementId="builds"
subscriptionDialogOpeningReason="Build limit reached"
label="Upgrade your subscription"
>
@@ -49,6 +53,7 @@ export const CustomLabel = () => (
export const ButtonHidden = () => (
<AuthenticatedUserContext.Provider value={fakeNotAuthenticatedUser}>
<GetSubscriptionCard
placementId="builds"
subscriptionDialogOpeningReason="Build limit reached"
hideButton
>
@@ -66,6 +71,7 @@ export const ButtonHidden = () => (
export const PayWithCreditsOptions = () => (
<AuthenticatedUserContext.Provider value={fakeNotAuthenticatedUser}>
<GetSubscriptionCard
placementId="builds"
subscriptionDialogOpeningReason="Build limit reached"
payWithCreditsOptions={{
label: 'Purchase with 100 credits',
@@ -86,6 +92,7 @@ export const PayWithCreditsOptions = () => (
export const ForceColumnLayout = () => (
<AuthenticatedUserContext.Provider value={fakeNotAuthenticatedUser}>
<GetSubscriptionCard
placementId="builds"
subscriptionDialogOpeningReason="Build limit reached"
forceColumnLayout
>

View File

@@ -38,6 +38,7 @@ export const NotAuthenticatedTryMode = () => {
id="Preview over wifi"
onChangeSubscription={action('change subscription')}
mode="try"
placementId="gdevelop-branding"
/>
</AuthenticatedUserContext.Provider>
);
@@ -59,6 +60,7 @@ export const NotAuthenticatedMandatoryMode = () => {
id="Preview over wifi"
onChangeSubscription={action('change subscription')}
mode="mandatory"
placementId="gdevelop-branding"
/>
</AuthenticatedUserContext.Provider>
);
@@ -82,6 +84,7 @@ export const UserWithNoSubscription = () => {
id="Preview over wifi"
onChangeSubscription={action('change subscription')}
mode="mandatory"
placementId="gdevelop-branding"
/>
</AuthenticatedUserContext.Provider>
);
@@ -103,6 +106,7 @@ export const UserWithGoldSubscription = () => {
id="Preview over wifi"
onChangeSubscription={action('change subscription')}
mode="mandatory"
placementId="gdevelop-branding"
/>
</AuthenticatedUserContext.Provider>
);

View File

@@ -32,6 +32,7 @@ const SubscriptionDialogTestOpener = ({ label }: {| label: string |}) => {
analyticsMetadata: {
reason: 'Cloud Project limit reached',
recommendedPlanId: 'gdevelop_silver',
placementId: 'max-projects-reached',
},
});
},