From 30ac79164f82c8e05921ced5e3b6867044576c07 Mon Sep 17 00:00:00 2001 From: mym0404 Date: Sat, 27 Apr 2024 21:56:40 +0900 Subject: [PATCH] feat(social): getFriends feature --- docs/docs/social/get-friends.mdx | 59 +++++++++++++ docs/docs/social/get-talk-profile.mdx | 2 +- docs/docs/social/select-talk-friends.mdx | 2 +- example/app.json | 4 +- example/src/app/social.tsx | 9 ++ .../rnkakao/social/RNCKakaoSocialModule.kt | 54 ++++++++++++ .../android/src/oldarch/KakaoSocialSpec.kt | 5 ++ packages/social/ios/RNCKakaoSocial.mm | 22 +++-- .../social/ios/RNCKakaoSocialManager.swift | 85 +++++++++++-------- packages/social/src/index.tsx | 17 +++- packages/social/src/spec/NativeKakaoSocial.ts | 31 +++++++ script/arch-convert.sh | 8 -- 12 files changed, 244 insertions(+), 54 deletions(-) create mode 100644 docs/docs/social/get-friends.mdx diff --git a/docs/docs/social/get-friends.mdx b/docs/docs/social/get-friends.mdx new file mode 100644 index 0000000..eca109d --- /dev/null +++ b/docs/docs/social/get-friends.mdx @@ -0,0 +1,59 @@ +--- +sidebar_position: 4 +--- + +# 카카오톡 친구 가져오기 +## 카카오톡 친구 가져오기 + +[공식 문서](https://developers.kakao.com/docs/latest/ko/kakaotalk-social/android#get-friends) + +:::info +API를 사용하기 위해서 카카오 로그인 및 관련 동의 항목이 동의된 상태여야 합니다. + +피커와 친구 목록 가져오기 API는 사용 권한이 주어진 앱에서만 사용할 수 있습니다. +::: + +현재 로그인한 사용자의 카카오톡 친구 목록을 불러옵니다. +요청 시 친구 목록의 정렬 순서, 한 페이지당 친구 수를 선택적으로 지정할 수 있습니다. 파라미터 없이 요청 시 기본 설정대로 요청이 전송됩니다. + +## Usage + +`getFriends()`를 이용해 친구 목록을 가져올 수 있습니다. + +다음과 같이 정의되어 있습니다. + +```tsx +export function getFriends(params: { + options?: KakaoTalkGetFriendsOptions; +}): Promise +``` + +사용되는 타입들은 다음과 같습니다. + +```tsx +/** + * 카카오톡 친구 + * + * @property id 회원번호 + * @property uuid 메시지를 전송하기 위한 고유 아이디. 사용자의 계정 상태에 따라 이 정보는 바뀔 수 있으므로 앱내의 사용자 식별자로는 권장하지 않음. + * @property profileNickname 친구의 닉네임 + * @property profileThumbnailImage 썸네일 이미지 URL + * @property favorite 즐겨찾기 추가 여부 + * @property allowedMsg 메시지 수신이 허용되었는지 여부. 앱가입 친구의 경우는 feed msg 에 해당. 앱미가입친구는 invite msg 에 해당 + */ +export interface KakaoTalkFriend { + id?: number; + uuid: string; + profileNickname: string; + profileThumbnailImage?: string; + favorite?: boolean; + allowedMsg?: boolean; +} + +export interface KakaoTalkGetFriendsOptions { + offset?: number; + limit?: number; + order?: 'asc' | 'desc'; + friendOrder?: 'nickname' | 'age' | 'favorite'; +} +``` diff --git a/docs/docs/social/get-talk-profile.mdx b/docs/docs/social/get-talk-profile.mdx index f5e614a..1e32cf1 100644 --- a/docs/docs/social/get-talk-profile.mdx +++ b/docs/docs/social/get-talk-profile.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 5 +sidebar_position: 2 --- # 내 카카오톡 프로필 가져오기 diff --git a/docs/docs/social/select-talk-friends.mdx b/docs/docs/social/select-talk-friends.mdx index 4d0de17..2daa217 100644 --- a/docs/docs/social/select-talk-friends.mdx +++ b/docs/docs/social/select-talk-friends.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 5 +sidebar_position: 3 --- # 카카오톡 친구 선택하기 diff --git a/example/app.json b/example/app.json index 774c21b..34c8e3c 100644 --- a/example/app.json +++ b/example/app.json @@ -53,10 +53,10 @@ "https://devrepo.kakao.com/nexus/content/groups/public/" ], "minSdkVerson": 26, - "newArchEnabled": false + "newArchEnabled": true }, "ios": { - "newArchEnabled": false + "newArchEnabled": true } } ], diff --git a/example/src/app/social.tsx b/example/src/app/social.tsx index 21c3c4d..2e23063 100644 --- a/example/src/app/social.tsx +++ b/example/src/app/social.tsx @@ -2,6 +2,7 @@ import { useState } from 'react'; import { showMessage } from 'react-native-flash-message'; import { formatJson } from '@mj-studio/js-util'; import { + getFriends, getTalkProfile, type KakaoTalkProfile, selectMultipleFriends, @@ -54,6 +55,14 @@ export default function Page() { .catch((e) => showMessage({ type: 'warning', message: e.message })) } /> + + getFriends({ options: {} }) + .then((res) => showMessage({ message: formatJson(res) })) + .catch((e) => showMessage({ type: 'warning', message: e.message })) + } + /> ); } diff --git a/packages/social/android/src/main/java/net/mjstudio/rnkakao/social/RNCKakaoSocialModule.kt b/packages/social/android/src/main/java/net/mjstudio/rnkakao/social/RNCKakaoSocialModule.kt index 101c44b..27ee708 100644 --- a/packages/social/android/src/main/java/net/mjstudio/rnkakao/social/RNCKakaoSocialModule.kt +++ b/packages/social/android/src/main/java/net/mjstudio/rnkakao/social/RNCKakaoSocialModule.kt @@ -15,6 +15,8 @@ import com.kakao.sdk.friend.model.ViewAppearance.AUTO import com.kakao.sdk.friend.model.ViewAppearance.DARK import com.kakao.sdk.friend.model.ViewAppearance.LIGHT import com.kakao.sdk.talk.TalkApiClient +import com.kakao.sdk.talk.model.FriendOrder +import com.kakao.sdk.talk.model.Order import net.mjstudio.rnkakao.core.util.RNCKakaoResponseNotFoundException import net.mjstudio.rnkakao.core.util.argArr import net.mjstudio.rnkakao.core.util.argMap @@ -133,6 +135,58 @@ class RNCKakaoSocialModule internal constructor(context: ReactApplicationContext minPickableCount = options?.getIntElseNull("minPickableCount"), ) + @ReactMethod + override fun getFriends( + options: ReadableMap?, + promise: Promise, + ) = onMain { + TalkApiClient.instance.friends( + offset = options?.getIntElseNull("offset"), + limit = options?.getIntElseNull("limit"), + order = + when (options?.getString("order")) { + "asc" -> Order.ASC + "desc" -> Order.DESC + else -> null + }, + friendOrder = + when (options?.getString("friendOrder")) { + "nickname" -> FriendOrder.NICKNAME + "age" -> FriendOrder.AGE + "favorite" -> FriendOrder.FAVORITE + else -> null + }, + ) { friends, error -> + if (error != null) { + promise.rejectWith(error) + } else if (friends?.elements == null) { + promise.rejectWith(RNCKakaoResponseNotFoundException("friends")) + } else { + promise.resolve( + argMap().apply { + putInt("totalCount", friends.totalCount) + putIntIfNotNull("favoriteCount", friends.favoriteCount) + putArray( + "friends", + argArr().pushMapList( + friends.elements!!.map { + argMap().apply { + putIntIfNotNull("id", it.id?.toInt()) + putString("uuid", it.uuid) + putString("profileNickname", it.profileNickname) + putString("profileThumbnailImage", it.profileThumbnailImage) + putBooleanIfNotNull("favorite", it.favorite) + putBooleanIfNotNull("allowedMsg", it.allowedMsg) + } + }, + ), + ) + }, + ) + } + } + } + companion object { const val NAME = "RNCKakaoSocial" } diff --git a/packages/social/android/src/oldarch/KakaoSocialSpec.kt b/packages/social/android/src/oldarch/KakaoSocialSpec.kt index 28515ff..9096c14 100644 --- a/packages/social/android/src/oldarch/KakaoSocialSpec.kt +++ b/packages/social/android/src/oldarch/KakaoSocialSpec.kt @@ -15,4 +15,9 @@ abstract class KakaoSocialSpec internal constructor(context: ReactApplicationCon options: ReadableMap?, promise: Promise, ) + + abstract fun getFriends( + options: ReadableMap?, + promise: Promise, + ) } diff --git a/packages/social/ios/RNCKakaoSocial.mm b/packages/social/ios/RNCKakaoSocial.mm index 88918af..3444119 100644 --- a/packages/social/ios/RNCKakaoSocial.mm +++ b/packages/social/ios/RNCKakaoSocial.mm @@ -9,18 +9,28 @@ - (RNCKakaoSocialManager*)manager { RCT_EXPORT_MODULE() -- (void)getProfile:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { +RCT_EXPORT_METHOD(getProfile + : (RCTPromiseResolveBlock)resolve reject + : (RCTPromiseRejectBlock)reject) { [[self manager] getProfile:resolve reject:reject]; } -- (void)selectFriends:(BOOL)multiple - mode:(NSString*)mode - options:(NSDictionary*)options - resolve:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject { +RCT_EXPORT_METHOD(selectFriends + : (BOOL)multiple mode + : (NSString*)mode options + : (NSDictionary*)options resolve + : (RCTPromiseResolveBlock)resolve reject + : (RCTPromiseRejectBlock)reject) { [[self manager] selectFriends:multiple mode:mode options:options resolve:resolve reject:reject]; } +RCT_EXPORT_METHOD(getFriends + : (NSDictionary*)options resolve + : (RCTPromiseResolveBlock)resolve reject + : (RCTPromiseRejectBlock)reject) { + [[self manager] getFriendsWithOptions:options resolve:resolve reject:reject]; +} + // Don't compile this code when we build for the old architecture. #ifdef RCT_NEW_ARCH_ENABLED - (std::shared_ptr)getTurboModule: diff --git a/packages/social/ios/RNCKakaoSocialManager.swift b/packages/social/ios/RNCKakaoSocialManager.swift index da359ec..f2d7df5 100644 --- a/packages/social/ios/RNCKakaoSocialManager.swift +++ b/packages/social/ios/RNCKakaoSocialManager.swift @@ -32,41 +32,6 @@ import RNCKakaoCore } } - @objc public func getFriends( - options: [String: Any], - resolve: @escaping RCTPromiseResolveBlock, - reject: @escaping RCTPromiseRejectBlock - ) { - let order: Order? = switch options["order"] { - case let x as String where x == "asc": .Asc - case let x as String where x == "desc": .Desc - default: nil - } - let friendOrder: FriendOrder? = switch options["friendOrder"] { - case let x as String where x == "nickname": .Nickname - case let x as String where x == "age": .Age - case let x as String where x == "favorite": .Favorite - default: nil - } - - onMain { - TalkApi.shared - .friends( - offset: options["offset"] as? Int, - limit: options["limit"] as? Int, - order: order, - friendOrder: friendOrder - ) { friends, error in - if let error { - RNCKakaoUtil.reject(reject, error) - } else if let friends { - } else { - RNCKakaoUtil.reject(reject, RNCKakaoError.responseNotFound(name: "friends")) - } - } - } - } - @objc public func selectFriends( _ multiple: Bool, mode: String, @@ -138,6 +103,56 @@ import RNCKakaoCore } } } + + @objc public func getFriends( + options: [String: Any], + resolve: @escaping RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock + ) { + let order: Order? = switch options["order"] { + case let x as String where x == "asc": .Asc + case let x as String where x == "desc": .Desc + default: nil + } + let friendOrder: FriendOrder? = switch options["friendOrder"] { + case let x as String where x == "nickname": .Nickname + case let x as String where x == "age": .Age + case let x as String where x == "favorite": .Favorite + default: nil + } + + onMain { + TalkApi.shared + .friends( + offset: options["offset"] as? Int, + limit: options["limit"] as? Int, + order: order, + friendOrder: friendOrder + ) { friends, error in + if let error { + RNCKakaoUtil.reject(reject, error) + } else if let friends, let items = friends.elements { + resolve([ + "totalCount": friends.totalCount, + "favoriteCount": friends.favoriteCount as Any, + "friends": items + .map { + [ + "id": $0.id as Any, + "uuid": $0.uuid, + "profileNickname": $0.profileNickname as Any, + "profileThumbnailImage": $0.profileThumbnailImage as Any, + "favorite": $0.favorite as Any, + "allowedMsg": $0.allowedMsg as Any + ] + } + ]) + } else { + RNCKakaoUtil.reject(reject, RNCKakaoError.responseNotFound(name: "friends")) + } + } + } + } } /// /// 친구 피커의 이름 diff --git a/packages/social/src/index.tsx b/packages/social/src/index.tsx index 3e8879a..6e3af9d 100644 --- a/packages/social/src/index.tsx +++ b/packages/social/src/index.tsx @@ -1,8 +1,12 @@ import { NativeModules, Platform } from 'react-native'; import type { + KakaoTalkFriend, KakaoTalkFriendProfile, KakaoTalkFriendSelectOptions, + KakaoTalkFriendSelectResult, + KakaoTalkGetFriendsOptions, + KakaoTalkGetFriendsResult, KakaoTalkProfile, Spec, } from './spec/NativeKakaoSocial'; @@ -12,7 +16,10 @@ export type { KakaoTalkFriendProfile, KakaoTalkFriendSelectOptions, KakaoTalkFriendSelectResult, -} from './spec/NativeKakaoSocial'; + KakaoTalkGetFriendsOptions, + KakaoTalkFriend, + KakaoTalkGetFriendsResult, +}; const LINKING_ERROR = "The package '@react-native-kakao/social' doesn't seem to be linked. Make sure: \n\n" + @@ -63,3 +70,11 @@ export function selectMultipleFriends({ }) { return Native.selectFriends(true, mode, options); } + +export function getFriends({ + options = {}, +}: { + options?: KakaoTalkGetFriendsOptions; +}): Promise { + return Native.getFriends(options); +} diff --git a/packages/social/src/spec/NativeKakaoSocial.ts b/packages/social/src/spec/NativeKakaoSocial.ts index 503e97f..3361e21 100644 --- a/packages/social/src/spec/NativeKakaoSocial.ts +++ b/packages/social/src/spec/NativeKakaoSocial.ts @@ -75,6 +75,36 @@ export interface KakaoTalkFriendSelectOptions { minPickableCount?: number; } +export interface KakaoTalkGetFriendsResult { + totalCount: number; + favoriteCount?: number; + friends: KakaoTalkFriend[]; +} +/** + * 카카오톡 친구 + * + * @property id 회원번호 + * @property uuid 메시지를 전송하기 위한 고유 아이디. 사용자의 계정 상태에 따라 이 정보는 바뀔 수 있으므로 앱내의 사용자 식별자로는 권장하지 않음. + * @property profileNickname 친구의 닉네임 + * @property profileThumbnailImage 썸네일 이미지 URL + * @property favorite 즐겨찾기 추가 여부 + * @property allowedMsg 메시지 수신이 허용되었는지 여부. 앱가입 친구의 경우는 feed msg 에 해당. 앱미가입친구는 invite msg 에 해당 + */ +export interface KakaoTalkFriend { + id?: number; + uuid: string; + profileNickname: string; + profileThumbnailImage?: string; + favorite?: boolean; + allowedMsg?: boolean; +} +export interface KakaoTalkGetFriendsOptions { + offset?: number; + limit?: number; + order?: 'asc' | 'desc'; + friendOrder?: 'nickname' | 'age' | 'favorite'; +} + export interface Spec extends TurboModule { getProfile(): Promise; selectFriends( @@ -82,6 +112,7 @@ export interface Spec extends TurboModule { mode: string, options: UnsafeObject, ): Promise; + getFriends(options: UnsafeObject): Promise; } export default TurboModuleRegistry.getEnforcing('RNCKakaoSocial'); diff --git a/script/arch-convert.sh b/script/arch-convert.sh index ea88a74..bccb112 100755 --- a/script/arch-convert.sh +++ b/script/arch-convert.sh @@ -1,13 +1,5 @@ #!/usr/bin/env bash -if [ "$OS" == "Darwin" ]; then - echo "MacOS" -elif [ "$OS" == "Linux" ]; then - echo "Linux" -else - echo "Other" -fi - # New value from first command line argument NEW_VALUE="$1" POD="$2"