diff --git a/go/protocol/keybase1/user.go b/go/protocol/keybase1/user.go index 52597868bfe4..76cdfbcd727f 100644 --- a/go/protocol/keybase1/user.go +++ b/go/protocol/keybase1/user.go @@ -672,6 +672,10 @@ type ReportUserArg struct { ConvID *string `codec:"convID,omitempty" json:"convID,omitempty"` } +type DismissBlockButtonsArg struct { + TlfID TLFID `codec:"tlfID" json:"tlfID"` +} + type BlockUserArg struct { Username string `codec:"username" json:"username"` } @@ -730,6 +734,7 @@ type UserInterface interface { SetUserBlocks(context.Context, SetUserBlocksArg) error GetUserBlocks(context.Context, GetUserBlocksArg) ([]UserBlock, error) ReportUser(context.Context, ReportUserArg) error + DismissBlockButtons(context.Context, TLFID) error BlockUser(context.Context, string) error UnblockUser(context.Context, string) error } @@ -1143,6 +1148,21 @@ func UserProtocol(i UserInterface) rpc.Protocol { return }, }, + "dismissBlockButtons": { + MakeArg: func() interface{} { + var ret [1]DismissBlockButtonsArg + return &ret + }, + Handler: func(ctx context.Context, args interface{}) (ret interface{}, err error) { + typedArgs, ok := args.(*[1]DismissBlockButtonsArg) + if !ok { + err = rpc.NewTypeError((*[1]DismissBlockButtonsArg)(nil), args) + return + } + err = i.DismissBlockButtons(ctx, typedArgs[0].TlfID) + return + }, + }, "blockUser": { MakeArg: func() interface{} { var ret [1]BlockUserArg @@ -1346,6 +1366,12 @@ func (c UserClient) ReportUser(ctx context.Context, __arg ReportUserArg) (err er return } +func (c UserClient) DismissBlockButtons(ctx context.Context, tlfID TLFID) (err error) { + __arg := DismissBlockButtonsArg{TlfID: tlfID} + err = c.Cli.Call(ctx, "keybase.1.user.dismissBlockButtons", []interface{}{__arg}, nil, 0*time.Millisecond) + return +} + func (c UserClient) BlockUser(ctx context.Context, username string) (err error) { __arg := BlockUserArg{Username: username} err = c.Cli.Call(ctx, "keybase.1.user.blockUser", []interface{}{__arg}, nil, 0*time.Millisecond) diff --git a/go/service/user.go b/go/service/user.go index 1d73d15723cc..0290c4938bb4 100644 --- a/go/service/user.go +++ b/go/service/user.go @@ -6,6 +6,7 @@ package service import ( "errors" "fmt" + "github.com/keybase/client/go/protocol/gregor1" "sort" "strings" "time" @@ -662,6 +663,17 @@ func (h *UserHandler) SetUserBlocks(ctx context.Context, arg keybase1.SetUserBlo } +const blockButtonsGregorPrefix = "blockButtons." + +func (h *UserHandler) DismissBlockButtons(ctx context.Context, tlfID keybase1.TLFID) (err error) { + mctx := libkb.NewMetaContext(ctx, h.G()) + defer mctx.TraceTimed( + fmt.Sprintf("UserHandler#DismissBlockButtons(TLF=%s)", tlfID), + func() error { return err })() + + return h.service.gregor.DismissCategory(ctx, gregor1.Category(fmt.Sprintf("%s%s", blockButtonsGregorPrefix, tlfID.String()))) +} + func (h *UserHandler) GetUserBlocks(ctx context.Context, arg keybase1.GetUserBlocksArg) (res []keybase1.UserBlock, err error) { mctx := libkb.NewMetaContext(ctx, h.G()) diff --git a/protocol/avdl/keybase1/user.avdl b/protocol/avdl/keybase1/user.avdl index 2f5b4e9161de..9a41f7e063bc 100644 --- a/protocol/avdl/keybase1/user.avdl +++ b/protocol/avdl/keybase1/user.avdl @@ -256,6 +256,8 @@ protocol user { void reportUser(int sessionID, string username, string reason, string comment, boolean includeTranscript, union { null, string } convID); + void dismissBlockButtons(TLFID tlfID); + // Legacy user blocking: void blockUser(string username); void unblockUser(string username); diff --git a/protocol/bin/enabled-calls.json b/protocol/bin/enabled-calls.json index 862c9da1c1df..1b898dba1068 100644 --- a/protocol/bin/enabled-calls.json +++ b/protocol/bin/enabled-calls.json @@ -278,6 +278,7 @@ "keybase.1.user.unblockUser": {"promise": true}, "keybase.1.user.setUserBlocks": {"promise": true}, "keybase.1.user.getUserBlocks": {"promise": true}, + "keybase.1.user.dismissBlockButtons": {"promise": true}, "keybase.1.user.reportUser": {"promise": true}, "keybase.1.user.userCard": {"promise": true}, "keybase.1.userSearch.getNonUserDetails": {"promise": true}, diff --git a/protocol/json/keybase1/user.json b/protocol/json/keybase1/user.json index 04bc0274a3b6..297f1317d8d7 100644 --- a/protocol/json/keybase1/user.json +++ b/protocol/json/keybase1/user.json @@ -934,6 +934,15 @@ ], "response": null }, + "dismissBlockButtons": { + "request": [ + { + "name": "tlfID", + "type": "TLFID" + } + ], + "response": null + }, "blockUser": { "request": [ { diff --git a/shared/actions/chat2-gen.tsx b/shared/actions/chat2-gen.tsx index d452b93b0392..11c3b359abb4 100644 --- a/shared/actions/chat2-gen.tsx +++ b/shared/actions/chat2-gen.tsx @@ -38,6 +38,7 @@ export const conversationErrored = 'chat2:conversationErrored' export const createConversation = 'chat2:createConversation' export const deselectConversation = 'chat2:deselectConversation' export const desktopNotification = 'chat2:desktopNotification' +export const dismissBlockButtons = 'chat2:dismissBlockButtons' export const dismissBottomBanner = 'chat2:dismissBottomBanner' export const enableAudioRecording = 'chat2:enableAudioRecording' export const giphyGotSearchResult = 'chat2:giphyGotSearchResult' @@ -239,6 +240,7 @@ type _DesktopNotificationPayload = { readonly author: string readonly body: string } +type _DismissBlockButtonsPayload = {readonly teamID: RPCTypes.TeamID} type _DismissBottomBannerPayload = {readonly conversationIDKey: Types.ConversationIDKey} type _EnableAudioRecordingPayload = { readonly conversationIDKey: Types.ConversationIDKey @@ -675,7 +677,11 @@ type _UnsentTextChangedPayload = { readonly conversationIDKey: Types.ConversationIDKey readonly text: HiddenString } -type _UpdateBlockButtonsPayload = {readonly teamID: RPCTypes.TeamID; readonly adder: string} +type _UpdateBlockButtonsPayload = { + readonly teamID: RPCTypes.TeamID + readonly adder?: string + readonly show: boolean +} type _UpdateCoinFlipStatusPayload = {readonly statuses: Array} type _UpdateConvExplodingModesPayload = { readonly modes: Array<{conversationIDKey: Types.ConversationIDKey; seconds: number}> @@ -1255,6 +1261,9 @@ export const createDeselectConversation = ( export const createDesktopNotification = ( payload: _DesktopNotificationPayload ): DesktopNotificationPayload => ({payload, type: desktopNotification}) +export const createDismissBlockButtons = ( + payload: _DismissBlockButtonsPayload +): DismissBlockButtonsPayload => ({payload, type: dismissBlockButtons}) export const createEnableAudioRecording = ( payload: _EnableAudioRecordingPayload ): EnableAudioRecordingPayload => ({payload, type: enableAudioRecording}) @@ -1567,6 +1576,10 @@ export type DesktopNotificationPayload = { readonly payload: _DesktopNotificationPayload readonly type: typeof desktopNotification } +export type DismissBlockButtonsPayload = { + readonly payload: _DismissBlockButtonsPayload + readonly type: typeof dismissBlockButtons +} export type DismissBottomBannerPayload = { readonly payload: _DismissBottomBannerPayload readonly type: typeof dismissBottomBanner @@ -2044,6 +2057,7 @@ export type Actions = | CreateConversationPayload | DeselectConversationPayload | DesktopNotificationPayload + | DismissBlockButtonsPayload | DismissBottomBannerPayload | EnableAudioRecordingPayload | GiphyGotSearchResultPayload diff --git a/shared/actions/chat2/index.tsx b/shared/actions/chat2/index.tsx index 97631a4c6d27..4ee16e0f4708 100644 --- a/shared/actions/chat2/index.tsx +++ b/shared/actions/chat2/index.tsx @@ -1767,6 +1767,11 @@ function* messageSend(state: TypedState, action: Chat2Gen.MessageSendPayload, lo logger.info('error') } + // If there are block buttons on this conversation, clear them. + if (state.chat2.blockButtonsMap.has(meta.teamID)) { + yield Saga.put(Chat2Gen.createDismissBlockButtons({teamID: meta.teamID})) + } + // Do some logging to track down the root cause of a bug causing // messages to not send. Do this after creating the objects above to // narrow down the places where the action can possibly stop. @@ -1801,9 +1806,15 @@ const messageSendByUsernames = async ( }, action.payload.waitingKey ) + + // If there are block buttons on this conversation, clear them. + if (state.chat2.blockButtonsMap.has(result.conv.info.triple.tlfid.toString('hex'))) { + return Chat2Gen.createDismissBlockButtons({teamID: result.conv.info.triple.tlfid.toString('hex')}) + } } catch (e) { logger.warn('Could not send in messageSendByUsernames', e) } + return [] } type StellarConfirmWindowResponse = {result: (b: boolean) => void} @@ -3189,9 +3200,12 @@ const gregorPushState = (state: TypedState, action: GregorGen.PushStatePayload, const isSearchNew = !items.some(i => i.item.category === Constants.inboxSearchNewKey) actions.push(Chat2Gen.createSetInboxShowIsNew({isNew: isSearchNew})) - // TODO: clear the block buttons in case you've followed someone or chatted them? const blockButtons = items.some(i => i.item.category.startsWith(Constants.blockButtonsGregorPrefix)) - if (blockButtons) { + if (blockButtons || state.chat2.blockButtonsMap.size > 0) { + const shouldKeepExistingBlockButtons = new Map() + state.chat2.blockButtonsMap.forEach((_, teamID: string) => + shouldKeepExistingBlockButtons.set(teamID, false) + ) items .filter(i => i.item.category.startsWith(Constants.blockButtonsGregorPrefix)) .forEach(i => { @@ -3199,9 +3213,16 @@ const gregorPushState = (state: TypedState, action: GregorGen.PushStatePayload, if (!state.chat2.blockButtonsMap.get(teamID)) { const body: {adder: string} = JSON.parse(i.item.body.toString()) const adder = body.adder - actions.push(Chat2Gen.createUpdateBlockButtons({adder, teamID})) + actions.push(Chat2Gen.createUpdateBlockButtons({adder, show: true, teamID})) + } else { + shouldKeepExistingBlockButtons.set(teamID, true) } }) + shouldKeepExistingBlockButtons.forEach((keep, teamID) => { + if (!keep) { + actions.push(Chat2Gen.createUpdateBlockButtons({show: false, teamID})) + } + }) } return actions } @@ -3274,6 +3295,14 @@ const onMarkInboxSearchOld = (state: TypedState) => state.chat2.inboxShowNew && GregorGen.createUpdateCategory({body: 'true', category: Constants.inboxSearchNewKey}) +const dismissBlockButtons = async (_: TypedState, action: Chat2Gen.DismissBlockButtonsPayload) => { + try { + await RPCTypes.userDismissBlockButtonsRpcPromise({tlfID: action.payload.teamID}) + } catch (err) { + logger.error(`Couldn't dismiss block buttons: ${err.message}`) + } +} + const createConversationFromTeamBuilder = ( state: TypedState, {payload: {namespace}}: TeamBuildingGen.FinishedTeamBuildingPayload @@ -3692,6 +3721,8 @@ function* chat2Saga() { yield* Saga.chainAction2(Chat2Gen.setInboxNumSmallRows, setInboxNumSmallRows) yield* Saga.chainAction2(ConfigGen.bootstrapStatusLoaded, getInboxNumSmallRows) + yield* Saga.chainAction2(Chat2Gen.dismissBlockButtons, dismissBlockButtons) + yield* chatTeamBuildingSaga() yield* Saga.chainAction2(EngineGen.chat1NotifyChatChatConvUpdate, onChatConvUpdate, 'onChatConvUpdate') } diff --git a/shared/actions/json/chat2.json b/shared/actions/json/chat2.json index 8e808319be79..c2720c98bbb7 100644 --- a/shared/actions/json/chat2.json +++ b/shared/actions/json/chat2.json @@ -778,7 +778,11 @@ "updateBlockButtons": { "_description": "Show or hide invitation to block for a given team ID", "teamID": "RPCTypes.TeamID", - "adder": "string" + "adder?": "string", + "show": "boolean" + }, + "dismissBlockButtons": { + "teamID": "RPCTypes.TeamID" }, "setInboxNumSmallRows": { "ignoreWrite?": "boolean", diff --git a/shared/actions/typed-actions-gen.tsx b/shared/actions/typed-actions-gen.tsx index b7400c96342d..877b9b66c287 100644 --- a/shared/actions/typed-actions-gen.tsx +++ b/shared/actions/typed-actions-gen.tsx @@ -216,6 +216,7 @@ export type TypedActionsMap = { 'chat2:toggleGiphyPrefill': chat2.ToggleGiphyPrefillPayload 'chat2:setChannelSearchText': chat2.SetChannelSearchTextPayload 'chat2:updateBlockButtons': chat2.UpdateBlockButtonsPayload + 'chat2:dismissBlockButtons': chat2.DismissBlockButtonsPayload 'chat2:setInboxNumSmallRows': chat2.SetInboxNumSmallRowsPayload 'chat2:enableAudioRecording': chat2.EnableAudioRecordingPayload 'chat2:attemptAudioRecording': chat2.AttemptAudioRecordingPayload diff --git a/shared/chat/blocking/invitation-to-block.tsx b/shared/chat/blocking/invitation-to-block.tsx index 257ab7643a11..853011eba091 100644 --- a/shared/chat/blocking/invitation-to-block.tsx +++ b/shared/chat/blocking/invitation-to-block.tsx @@ -33,7 +33,7 @@ const BlockButtons = (props: Props) => { {team ? `${adder} added you to this team.` : `You don't seem to know ${adder}.`} {!team && } - {!team && !others && ( + {!team && others.length === 0 && ( | null} outParam: Array | null @@ -3482,6 +3486,7 @@ export const trackTrackWithTokenRpcPromise = (params: MessageTypes['keybase.1.tr export const trackUntrackRpcPromise = (params: MessageTypes['keybase.1.track.untrack']['inParam'], waitingKey?: WaitingKey) => new Promise((resolve, reject) => engine()._rpcOutgoing({method: 'keybase.1.track.untrack', params, callback: (error, result) => (error ? reject(error) : resolve(result)), waitingKey})) export const userBlockUserRpcPromise = (params: MessageTypes['keybase.1.user.blockUser']['inParam'], waitingKey?: WaitingKey) => new Promise((resolve, reject) => engine()._rpcOutgoing({method: 'keybase.1.user.blockUser', params, callback: (error, result) => (error ? reject(error) : resolve(result)), waitingKey})) export const userCanLogoutRpcPromise = (params: MessageTypes['keybase.1.user.canLogout']['inParam'], waitingKey?: WaitingKey) => new Promise((resolve, reject) => engine()._rpcOutgoing({method: 'keybase.1.user.canLogout', params, callback: (error, result) => (error ? reject(error) : resolve(result)), waitingKey})) +export const userDismissBlockButtonsRpcPromise = (params: MessageTypes['keybase.1.user.dismissBlockButtons']['inParam'], waitingKey?: WaitingKey) => new Promise((resolve, reject) => engine()._rpcOutgoing({method: 'keybase.1.user.dismissBlockButtons', params, callback: (error, result) => (error ? reject(error) : resolve(result)), waitingKey})) export const userGetUserBlocksRpcPromise = (params: MessageTypes['keybase.1.user.getUserBlocks']['inParam'], waitingKey?: WaitingKey) => new Promise((resolve, reject) => engine()._rpcOutgoing({method: 'keybase.1.user.getUserBlocks', params, callback: (error, result) => (error ? reject(error) : resolve(result)), waitingKey})) export const userInterestingPeopleRpcPromise = (params: MessageTypes['keybase.1.user.interestingPeople']['inParam'], waitingKey?: WaitingKey) => new Promise((resolve, reject) => engine()._rpcOutgoing({method: 'keybase.1.user.interestingPeople', params, callback: (error, result) => (error ? reject(error) : resolve(result)), waitingKey})) export const userListTrackers2RpcPromise = (params: MessageTypes['keybase.1.user.listTrackers2']['inParam'], waitingKey?: WaitingKey) => new Promise((resolve, reject) => engine()._rpcOutgoing({method: 'keybase.1.user.listTrackers2', params, callback: (error, result) => (error ? reject(error) : resolve(result)), waitingKey})) diff --git a/shared/reducers/chat2.tsx b/shared/reducers/chat2.tsx index e71b5ab9f6cc..a30456d7bc4b 100644 --- a/shared/reducers/chat2.tsx +++ b/shared/reducers/chat2.tsx @@ -1029,7 +1029,11 @@ const reducer = Container.makeReducer(initialState, { } }, [Chat2Gen.updateBlockButtons]: (draftState, action) => { - draftState.blockButtonsMap.set(action.payload.teamID, {adder: action.payload.adder}) + if (action.payload.show) { + draftState.blockButtonsMap.set(action.payload.teamID, {adder: action.payload.adder || ''}) + } else { + draftState.blockButtonsMap.delete(action.payload.teamID) + } }, [Chat2Gen.updateReactions]: (draftState, action) => { const {conversationIDKey, updates} = action.payload diff --git a/shared/stories/__tests__/__snapshots__/Storyshots.test.js.snap b/shared/stories/__tests__/__snapshots__/Storyshots.test.js.snap index 912c358d7f02..8be93d090787 100644 --- a/shared/stories/__tests__/__snapshots__/Storyshots.test.js.snap +++ b/shared/stories/__tests__/__snapshots__/Storyshots.test.js.snap @@ -879,6 +879,74 @@ Array [
+
+
+
+
+ + View Profile + +
+
+
+