diff --git a/AGENTS.md b/AGENTS.md index 126fcd80c..af39f90f7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,7 +5,7 @@ Read this file first, then navigate to the relevant platform directory. ## Repository Overview -This repository contains sample projects demonstrating Agora RTC Native SDK APIs across five platforms. Each platform is fully independent — do not share source files or dependencies across platforms. +This repository contains sample projects demonstrating Agora RTC Native SDK APIs across four independent platforms. Each platform is self-contained — do not share source files, build scripts, or dependencies across platforms. | Platform | Language(s) | Directory | SDK | |----------|-------------|-----------|-----| diff --git a/iOS/APIExample/.agent/skills/review-case/SKILL.md b/iOS/APIExample/.agent/skills/review-case/SKILL.md index 403a8c84e..70e70d6eb 100644 --- a/iOS/APIExample/.agent/skills/review-case/SKILL.md +++ b/iOS/APIExample/.agent/skills/review-case/SKILL.md @@ -102,7 +102,7 @@ AgoraAudioSession.sharedInstance().requestRecordPermission { [weak self] granted **Check:** - Entry class inherits `UIViewController`, Main class inherits `BaseViewController` -- Class names follow `Entry` / `Main` pattern +- Entry class usually follows `Entry`; the main controller may be `Main` or an existing project-specific `*ViewController` name as long as storyboard wiring is correct - `configs` dictionary used to pass data from Entry to Main (no direct property injection) - File placed under `Examples/Basic/` or `Examples/Advanced/` matching the MenuItem section - Storyboard ID of Main scene matches the `controller` field in `MenuItem` diff --git a/iOS/APIExample/.agent/skills/upsert-case/SKILL.md b/iOS/APIExample/.agent/skills/upsert-case/SKILL.md index eccfe1e6b..9e1c531d2 100644 --- a/iOS/APIExample/.agent/skills/upsert-case/SKILL.md +++ b/iOS/APIExample/.agent/skills/upsert-case/SKILL.md @@ -16,7 +16,7 @@ metadata: ## When to Use - **Add**: the feature has no existing case in `Examples/Basic/` or `Examples/Advanced/` -- **Modify**: the case already exists — skip Steps 1–3, go directly to Step 4+ +- **Modify**: the case already exists — update the existing `.swift` and storyboard first, then check registration and docs Before adding, search the Case Index in `ARCHITECTURE.md` to confirm the case does not already exist. @@ -24,11 +24,22 @@ Before adding, search the Case Index in `ARCHITECTURE.md` to confirm the case do | Scenario | Files | |----------|-------| -| Add new case | New folder + `.swift` file + `.storyboard`, `ViewController.swift` (MenuItem), `ARCHITECTURE.md` (Case Index) | -| Modify existing case | Existing `.swift` file(s), optionally `.storyboard`, `ARCHITECTURE.md` (Case Index) | +| Add new case | New folder + `.swift` file + `Base.lproj/.storyboard`, `ViewController.swift` (MenuItem), `ARCHITECTURE.md` (Case Index) | +| Modify existing case | Existing `.swift` file(s), optionally `Base.lproj/.storyboard`, `ViewController.swift` if registration/wiring changed, `ARCHITECTURE.md` (Case Index) | --- +## Modify Existing Case + +When repairing or rebuilding an existing case, use this order instead of the new-case flow: + +1. Locate the existing `.swift` implementation and update the actual runtime logic first +2. Update the existing storyboard if scene wiring, outlets, actions, or controller identifiers changed +3. Check `APIExample/ViewController.swift` and fix the `MenuItem` only if registration or `controller` / `storyboard` wiring is wrong +4. Update `ARCHITECTURE.md` last if the case path, APIs, or description changed + +Do not skip implementation edits just because the case folder already exists. + ## Step 1 — Create the Example Folder ``` @@ -98,7 +109,7 @@ extension Main: AgoraRtcEngineDelegate { ## Step 3 — Create the Storyboard -Create `APIExample/Base.lproj/.storyboard` with two scenes: +Create `APIExample/Examples/[Basic|Advanced]//Base.lproj/.storyboard` with two scenes: | Scene | Storyboard ID | Class | |-------|--------------|-------| @@ -107,6 +118,8 @@ Create `APIExample/Base.lproj/.storyboard` with two scenes: Connect a `Show` segue or use the manual push in `onJoinPressed`. +Keep the storyboard inside the example folder. Do not place new case storyboards in the shared `APIExample/Base.lproj/` directory. + ## Default Entry UI Convention Unless the user explicitly asks for a different flow, use this default Entry layout and interaction: diff --git a/iOS/APIExample/APIExample/Examples/Basic/JoinChannelVideo/SKILL.md b/iOS/APIExample/APIExample/Examples/Basic/JoinChannelVideo/SKILL.md deleted file mode 100644 index f3f7f3a51..000000000 --- a/iOS/APIExample/APIExample/Examples/Basic/JoinChannelVideo/SKILL.md +++ /dev/null @@ -1,194 +0,0 @@ ---- -name: join-channel-video-guide -description: Guide for implementing video call functionality in business scenarios, including SDK initialization, joining channels, video encoding configuration, and event handling -compatibility: [Cursor, Kiro, Windsurf, Claude, Copilot] -license: MIT -metadata: - example: JoinChannelVideo - category: Basic - apis: [AgoraRtcEngineKit, joinChannel, enableVideo, setupLocalVideo, setupRemoteVideo] ---- - -# Video Call Implementation Guide - -## Feature Description - -This example demonstrates how to use Agora RTC SDK to implement basic video call functionality, including: -- Initialize SDK engine -- Configure video encoding parameters (resolution, frame rate, orientation) -- Join channel -- Display local and remote video -- Handle user join/leave events - -## Core API Call Flow - -### 1. Initialize SDK - -```swift -let config = AgoraRtcEngineConfig() -config.appId = KeyCenter.AppId -config.areaCode = GlobalSettings.shared.area -config.channelProfile = .liveBroadcasting -agoraKit = AgoraRtcEngineKit.sharedEngine(with: config, delegate: self) -``` - -**Key Parameters:** -- `appId`: App ID obtained from Agora Console -- `areaCode`: Region code for specifying connection region -- `channelProfile`: Channel profile, `.liveBroadcasting` supports host and audience roles - -### 2. Set User Role and Enable Audio/Video - -```swift -agoraKit.setClientRole(.broadcaster) // or .audience -agoraKit.enableVideo() -agoraKit.enableAudio() -``` - -### 3. Configure Video Encoding Parameters - -```swift -agoraKit.setVideoEncoderConfiguration( - AgoraVideoEncoderConfiguration( - size: CGSize(width: 960, height: 540), - frameRate: .fps15, - bitrate: AgoraVideoBitrateStandard, - orientationMode: .adaptative, - mirrorMode: .auto - ) -) -``` - -**Configurable Parameters:** -- `size`: Video resolution (90x90 ~ 1280x720) -- `frameRate`: Frame rate (10/15/24/30/60 fps) -- `orientationMode`: Video orientation mode - - `.adaptative`: Adaptive - - `.fixedLandscape`: Fixed landscape - - `.fixedPortrait`: Fixed portrait - -### 4. Set Local Video Preview - -```swift -let videoCanvas = AgoraRtcVideoCanvas() -videoCanvas.uid = 0 // Local user uid is 0 -videoCanvas.view = localVideoView -videoCanvas.renderMode = .hidden -agoraKit.setupLocalVideo(videoCanvas) -agoraKit.startPreview() -``` - -### 5. Join Channel - -```swift -let option = AgoraRtcChannelMediaOptions() -option.publishCameraTrack = true -option.publishMicrophoneTrack = true -option.clientRoleType = .broadcaster - -agoraKit.joinChannel( - byToken: token, - channelId: channelName, - uid: 0, // 0 means SDK automatically assigns uid - mediaOptions: option -) -``` - -**Important Notes:** -- If App Certificate is enabled in console, Token must be used -- Token must be generated with the same channel name and uid -- Return value of 0 indicates success - -### 6. Set Remote Video - -Set in `didJoinedOfUid` callback: - -```swift -func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinedOfUid uid: UInt, elapsed: Int) { - let videoCanvas = AgoraRtcVideoCanvas() - videoCanvas.uid = uid - videoCanvas.view = remoteVideoView - videoCanvas.renderMode = .hidden - agoraKit.setupRemoteVideo(videoCanvas) -} -``` - -### 7. Leave Channel and Clean Up Resources - -```swift -agoraKit.disableAudio() -agoraKit.disableVideo() -agoraKit.stopPreview() -agoraKit.leaveChannel { stats in - print("left channel, duration: \(stats.duration)") -} -``` - -## Key Event Callbacks - -### didJoinChannel -```swift -func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinChannel channel: String, withUid uid: UInt, elapsed: Int) -``` -Triggered when local user successfully joins channel. - -### didJoinedOfUid -```swift -func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinedOfUid uid: UInt, elapsed: Int) -``` -Triggered when remote user joins channel (not triggered for audience role). - -### didOfflineOfUid -```swift -func rtcEngine(_ engine: AgoraRtcEngineKit, didOfflineOfUid uid: UInt, reason: AgoraUserOfflineReason) -``` -Triggered when remote user leaves channel. - -### didOccurError -```swift -func rtcEngine(_ engine: AgoraRtcEngineKit, didOccurError errorCode: AgoraErrorCode) -``` -Triggered when SDK encounters an error, recommend displaying error message to user. - -## Common Questions - -### Q: Can't see local video? -A: Check if `startPreview()` and `setupLocalVideo()` have been called - -### Q: Can't see remote video? -A: -1. Confirm remote user role is broadcaster -2. Check if `setupRemoteVideo()` is correctly set in `didJoinedOfUid` callback -3. Confirm `option.publishCameraTrack = true` - -### Q: joinChannel returns non-zero value? -A: -- Check if App ID is correct -- If App Certificate is enabled, check if Token is valid -- Check if channel name and uid match those used when generating Token - -### Q: How to switch camera? -A: Call `agoraKit.switchCamera()` - -### Q: How to mute/unmute? -A: -- Local mute: `agoraKit.muteLocalAudioStream(true/false)` -- Remote mute: `agoraKit.muteRemoteAudioStream(uid, mute: true/false)` - -### Q: How to disable/enable video? -A: -- Local video: `agoraKit.muteLocalVideoStream(true/false)` -- Remote video: `agoraKit.muteRemoteVideoStream(uid, mute: true/false)` - -## Reference Documentation - -- [iOS API Reference (English)](https://api-ref.agora.io/en/video-sdk/ios/4.x/documentation/agorartckit) -- [iOS API Reference (Chinese)](https://doc.shengwang.cn/api-ref/rtc/ios/API/toc_video_call) -- [Error Code Description](https://doc.shengwang.cn/api-ref/rtc/ios/error-code) - -## Related Examples - -- `JoinChannelAudio` - Audio-only call -- `JoinChannelVideoToken` - Join channel with Token -- `VideoProcess` - Video processing (filters, watermarks) -- `CustomVideoSourcePush` - Custom video source diff --git a/macOS/.agent/skills/review-case/SKILL.md b/macOS/.agent/skills/review-case/SKILL.md index b21c61176..92d813ec2 100644 --- a/macOS/.agent/skills/review-case/SKILL.md +++ b/macOS/.agent/skills/review-case/SKILL.md @@ -87,6 +87,7 @@ See `references/incorrect-thread-safety.swift` for common mistakes. - [ ] Microphone permission requested before `enableAudio()` - [ ] Camera permission requested before `enableVideo()` - [ ] Permissions checked before accessing devices +- [ ] Review guidance stays macOS-specific and does not suggest iOS-only APIs such as `AVAudioSession.sharedInstance().requestRecordPermission` **Correct Pattern:** ```swift @@ -98,7 +99,7 @@ func initializeAgoraEngine() { } } - AVAudioSession.sharedInstance().requestRecordPermission { granted in + AVCaptureDevice.requestAccess(for: .audio) { granted in if granted { self.agoraKit.enableAudio() } diff --git a/macOS/.agent/skills/upsert-case/SKILL.md b/macOS/.agent/skills/upsert-case/SKILL.md index c8827d048..4e7b61909 100644 --- a/macOS/.agent/skills/upsert-case/SKILL.md +++ b/macOS/.agent/skills/upsert-case/SKILL.md @@ -2,7 +2,7 @@ name: upsert-case description: > Add a new API example or modify an existing one. Covers both creation and modification scenarios, - including file structure, registration, and ARCHITECTURE.md updates. + including file structure, per-example storyboard creation, registration, and ARCHITECTURE.md updates. compatibility: [Cursor, Kiro, Windsurf, Claude, Copilot] license: MIT metadata: @@ -30,9 +30,10 @@ Use this skill when you need to: 1. Determine if the example belongs in `Basic/` or `Advanced/` 2. Create the example folder with PascalCase name 3. Create the Swift implementation file -4. Register the example in `ViewController.swift` -5. Update `ARCHITECTURE.md` Case Index -6. Verify compilation and functionality +4. Create the example storyboard +5. Register the example in `ViewController.swift` +6. Update `ARCHITECTURE.md` Case Index +7. Verify compilation and functionality ### Scenario 2: Modify an Existing Example @@ -41,8 +42,9 @@ Use this skill when you need to: **Steps:** 1. Locate the example in `APIExample/Examples/[Basic|Advanced]//` 2. Modify the Swift file -3. Update `ARCHITECTURE.md` Case Index if APIs changed -4. Verify compilation and functionality +3. Modify the storyboard if outlets, actions, or controller identifiers changed +4. Update `ARCHITECTURE.md` Case Index if APIs changed +5. Verify compilation and functionality --- @@ -53,6 +55,7 @@ Use this skill when you need to: | File | Action | Notes | |------|--------|-------| | `APIExample/Examples/[Basic\|Advanced]//.swift` | Create | Main implementation file | +| `APIExample/Examples/[Basic\|Advanced]//Base.lproj/.storyboard` | Create | Example UI and controller identifier | | `APIExample/ViewController.swift` | Modify | Register example in menu/list | | `ARCHITECTURE.md` | Modify | Add entry to Case Index | @@ -61,6 +64,7 @@ Use this skill when you need to: | File | Action | Notes | |------|--------|-------| | `APIExample/Examples/[Basic\|Advanced]//.swift` | Modify | Update implementation | +| `APIExample/Examples/[Basic\|Advanced]//Base.lproj/.storyboard` | Modify if needed | Keep outlets and controller identifiers aligned | | `ARCHITECTURE.md` | Modify | Update Case Index if APIs changed | --- @@ -86,19 +90,26 @@ Create `APIExample/Examples/[Basic|Advanced]//.swift` Use the template from `references/example-template.swift` as a starting point. Replace `` with your example name. -### Step 4: Register in ViewController +### Step 4: Create the Example Storyboard + +Create `APIExample/Examples/[Basic|Advanced]//Base.lproj/.storyboard`. + +The storyboard name must match the value passed to `NSStoryboard(name:bundle:)` from `ViewController.swift`, and the main controller identifier in the storyboard must match the `controller` field in `MenuItem`. + +### Step 5: Register in ViewController Edit `APIExample/ViewController.swift` and add the example to the menu/list: ```swift -// In the example list or menu setup -let examples = [ - // ... existing examples - ("ExampleName", Main.self), -] +MenuItem(name: "Example Name".localized, + identifier: "menuCell", + controller: "", + storyboard: "") ``` -### Step 5: Update ARCHITECTURE.md +Place it in the correct section (Basic / Advanced). + +### Step 6: Update ARCHITECTURE.md Add a new row to the Case Index table in `ARCHITECTURE.md`: @@ -108,13 +119,14 @@ Add a new row to the Case Index table in `ARCHITECTURE.md`: **Key APIs column:** List 2-5 core SDK methods used in this example. -### Step 6: Verify +### Step 7: Verify - [ ] Code compiles without errors - [ ] Example appears in the menu/list - [ ] Example can join channel and receive callbacks - [ ] `leaveChannel()` and `destroy()` are called on close - [ ] UI updates happen on main thread +- [ ] Storyboard loads with the expected controller identifier - [ ] ARCHITECTURE.md Case Index is updated --- @@ -139,6 +151,7 @@ See `references/` directory for code patterns: - Forget to implement `AgoraRtcEngineDelegate` for event handling - Leave the channel without calling `leaveChannel()` first - Modify examples outside the `APIExample/Examples/[Basic|Advanced]/` structure +- Register a new menu item without also creating the per-example storyboard under the same example folder - Forget to update `ARCHITECTURE.md` Case Index after adding/modifying an example --- @@ -150,6 +163,8 @@ After completing the upsert, verify: - [ ] Example folder is in correct location (`APIExample/Examples/[Basic|Advanced]//`) - [ ] Swift file is named `.swift` (PascalCase) - [ ] Class name is `Main` and extends `BaseViewController` +- [ ] Storyboard exists at `APIExample/Examples/[Basic|Advanced]//Base.lproj/.storyboard` +- [ ] Storyboard identifier matches the `controller` value registered in `ViewController.swift` - [ ] Example is registered in `ViewController.swift` - [ ] `initializeAgoraEngine()` creates engine with correct config - [ ] `joinChannel()` uses token from `KeyCenter` diff --git a/macOS/AGENTS.md b/macOS/AGENTS.md index 93c142e20..2116452eb 100644 --- a/macOS/AGENTS.md +++ b/macOS/AGENTS.md @@ -59,15 +59,6 @@ All work must conform to the rules defined in `ARCHITECTURE.md`: - State management uses instance variables and delegate callbacks — do not introduce Combine or async/await patterns unless they already exist in the file being modified - Match the code style, naming, and patterns of existing examples -### Use Example-Level SKILLs - -Each example may contain a `SKILL.md` file in its folder. When working on or referencing a specific example: -1. Check whether a `SKILL.md` exists in that example's directory -2. If it exists, read it before making changes — it describes the API usage, call flow, and known constraints -3. If it does not exist, one will be created in the future; proceed using the source code as the reference - -**SKILL.md location pattern:** `APIExample/Examples/[Basic|Advanced]//SKILL.md` - ### Use Project-Level SKILLs For broader tasks, use the skills in `.agent/skills/`: diff --git a/macOS/ARCHITECTURE.md b/macOS/ARCHITECTURE.md index 78299542f..61149da5b 100644 --- a/macOS/ARCHITECTURE.md +++ b/macOS/ARCHITECTURE.md @@ -47,7 +47,7 @@ macOS/ Each example lives in its own folder under `APIExample/Examples/Basic/` or `APIExample/Examples/Advanced/` and consists of: - A Swift file containing the example implementation -- Optional: A storyboard or XIB file for UI layout +- A per-example storyboard, typically at `Base.lproj/.storyboard` ### Example Pattern diff --git a/windows/.agent/skills/review-case/SKILL.md b/windows/.agent/skills/review-case/SKILL.md index 95095e3c3..ead1ccdfa 100644 --- a/windows/.agent/skills/review-case/SKILL.md +++ b/windows/.agent/skills/review-case/SKILL.md @@ -29,10 +29,10 @@ Use this skill when you need to: - [ ] Engine is created in `InitializeAgoraEngine()` or similar - [ ] Engine is initialized with `RtcEngineContext` - [ ] `leaveChannel()` is called before `release()` -- [ ] `release()` is called in `PostNcDestroy()` or cleanup method +- [ ] `release()` is called in the case's real cleanup path - [ ] No engine leaks (engine not recreated on every join) -**Correct Pattern:** +**Correct Pattern A — standalone dialog teardown:** ```cpp BOOL CExampleDlg::OnInitDialog() { CDialogEx::OnInitDialog(); @@ -61,6 +61,33 @@ void CExampleDlg::LeaveChannel() { } ``` +**Correct Pattern B — scene-switching dialog teardown:** +```cpp +bool CExampleDlg::InitAgora() { + m_rtcEngine = createAgoraRtcEngine(); + // initialize once when the scene becomes active + return m_rtcEngine != nullptr; +} + +void CExampleDlg::UnInitAgora() { + if (!m_rtcEngine) return; + if (m_joinChannel) { + m_rtcEngine->leaveChannel(); + } + m_rtcEngine->release(nullptr); + m_rtcEngine = nullptr; +} + +void CExampleDlg::OnShowWindow(BOOL bShow, UINT nStatus) { + CDialogEx::OnShowWindow(bShow, nStatus); + if (!bShow) { + UnInitAgora(); + } +} +``` + +Accept either pattern as long as the dialog follows one lifecycle consistently and does not leak the engine across scene switches. + **Incorrect Pattern:** See `references/incorrect-lifecycle.cpp` for common mistakes. diff --git a/windows/.agent/skills/upsert-case/SKILL.md b/windows/.agent/skills/upsert-case/SKILL.md index 0f53149ef..f809c4b4a 100644 --- a/windows/.agent/skills/upsert-case/SKILL.md +++ b/windows/.agent/skills/upsert-case/SKILL.md @@ -2,7 +2,8 @@ name: upsert-case description: > Add a new API example or modify an existing one. Covers both creation and modification scenarios, - including dialog class structure, message map registration, and ARCHITECTURE.md updates. + including dialog class structure, registration in APIExampleDlg, localization wiring, and + ARCHITECTURE.md updates. compatibility: [Cursor, Kiro, Windsurf, Claude, Copilot] license: MIT metadata: @@ -30,9 +31,10 @@ Use this skill when you need to: 1. Determine if the example belongs in `Basic/` or `Advanced/` 2. Create the example folder with PascalCase name 3. Create `.h` and `.cpp` files for the dialog class -4. Register the example in `CSceneDialog` -5. Update `ARCHITECTURE.md` Case Index -6. Verify compilation and functionality +4. Register the example in `APIExampleDlg.h` and `APIExampleDlg.cpp` +5. Add scene label wiring in `Language.h`, `stdafx.cpp`, and language `.ini` files +6. Update `ARCHITECTURE.md` Case Index +7. Verify compilation and functionality ### Scenario 2: Modify an Existing Example @@ -41,8 +43,9 @@ Use this skill when you need to: **Steps:** 1. Locate the example in `APIExample/APIExample/[Basic|Advanced]//` 2. Modify the `.h` and `.cpp` files -3. Update `ARCHITECTURE.md` Case Index if APIs changed -4. Verify compilation and functionality +3. Update `APIExampleDlg.cpp` if routing or lifecycle hooks changed +4. Update `ARCHITECTURE.md` Case Index if APIs changed +5. Verify compilation and functionality --- @@ -54,7 +57,14 @@ Use this skill when you need to: |------|--------|-------| | `APIExample/APIExample/[Basic\|Advanced]//CDlg.h` | Create | Dialog class header | | `APIExample/APIExample/[Basic\|Advanced]//CDlg.cpp` | Create | Dialog class implementation | -| `APIExample/APIExample/CSceneDialog.cpp` | Modify | Register example in scene list | +| `APIExample/APIExample/APIExampleDlg.h` | Modify | Add include and dialog member pointer | +| `APIExample/APIExample/APIExampleDlg.cpp` | Modify | Register, create, show, and release the dialog | +| `APIExample/APIExample/Language.h` | Modify | Declare the localized scene label | +| `APIExample/APIExample/stdafx.cpp` | Modify | Initialize the localized scene label in `InitKeyInfomation()` | +| `APIExample/APIExample/en.ini` | Modify | Add English display text | +| `APIExample/APIExample/zh-cn.ini` | Modify | Add Chinese display text | +| `APIExample/APIExample/APIExample.vcxproj` | Modify if needed | Add new source/header files when not using the Visual Studio UI | +| `APIExample/APIExample/APIExample.vcxproj.filters` | Modify if needed | Keep Solution Explorer grouping correct | | `ARCHITECTURE.md` | Modify | Add entry to Case Index | ### Modify Existing Example @@ -63,6 +73,7 @@ Use this skill when you need to: |------|--------|-------| | `APIExample/APIExample/[Basic\|Advanced]//CDlg.h` | Modify | Update dialog class | | `APIExample/APIExample/[Basic\|Advanced]//CDlg.cpp` | Modify | Update implementation | +| `APIExample/APIExample/APIExampleDlg.cpp` | Modify if routing changes | Update show/hide or scene selection behavior if needed | | `ARCHITECTURE.md` | Modify | Update Case Index if APIs changed | --- @@ -94,19 +105,31 @@ Create `APIExample/APIExample/[Basic|Advanced]//CDlg.c Use the template from `references/example-template.cpp` as a starting point. Replace `` with your example name. -### Step 5: Register in CSceneDialog +### Step 5: Register in APIExampleDlg -Edit `APIExample/APIExample/CSceneDialog.cpp` and add the example to the scene list: +Do not edit `CSceneDialog.cpp` for case registration. In this project, scene ownership lives in the main dialog: -```cpp -// In the scene list initialization -m_sceneList.push_back({ - _T("ExampleName"), - [](CWnd* pParent) { return new CDlg(pParent); } -}); -``` +- `APIExample/APIExample/APIExampleDlg.h` + Add the example header include and a member pointer such as `CDlg* m_pDlg = nullptr;` +- `APIExample/APIExample/APIExampleDlg.cpp` + Mirror an existing example across: + - `InitSceneDialog()` to push the localized label into `m_vecBasic` or `m_vecAdvanced`, create the dialog, and position it + - `CreateScene()` to call the dialog's init/show path when the tree item is selected + - `ReleaseScene()` to call the dialog's cleanup/hide path when the tree item is left + +`InitSceneList()` reads from `m_vecBasic` and `m_vecAdvanced`, so the tree updates automatically once the vectors are populated in `InitSceneDialog()`. + +### Step 6: Add localized scene labels + +Register the example name used by the tree view: + +- Add an `extern wchar_t ...[INFO_LEN];` declaration to `APIExample/APIExample/Language.h` +- Initialize it in `APIExample/APIExample/stdafx.cpp` inside `InitKeyInfomation()` +- Add matching keys to `APIExample/APIExample/en.ini` and `APIExample/APIExample/zh-cn.ini` + +Follow the existing naming pattern such as `Basic.JoinChannelVideoByToken` or `Advanced.ScreenCap`. -### Step 6: Update ARCHITECTURE.md +### Step 7: Update ARCHITECTURE.md Add a new row to the Case Index table in `ARCHITECTURE.md`: @@ -116,13 +139,14 @@ Add a new row to the Case Index table in `ARCHITECTURE.md`: **Key APIs column:** List 2-5 core SDK methods used in this example. -### Step 7: Verify +### Step 8: Verify - [ ] Code compiles without errors - [ ] Example appears in the scene list - [ ] Example can join channel and receive callbacks - [ ] `leaveChannel()` and `release()` are called on close - [ ] UI updates happen on main thread (via message map) +- [ ] Localized labels resolve correctly in both `en.ini` and `zh-cn.ini` - [ ] ARCHITECTURE.md Case Index is updated --- @@ -148,6 +172,7 @@ See `references/` directory for code patterns: - Hardcode App ID or token — use `CConfig` - Forget to implement `IRtcEngineEventHandler` for event handling - Leave the channel without calling `leaveChannel()` first +- Register a new case in `CSceneDialog.cpp` — registration lives in `APIExampleDlg.h` and `APIExampleDlg.cpp` - Modify examples outside the `APIExample/APIExample/[Basic|Advanced]/` structure - Forget to update `ARCHITECTURE.md` Case Index after adding/modifying an example - Use modern C++ patterns (lambdas, smart pointers) unless already in the file @@ -164,11 +189,14 @@ After completing the upsert, verify: - [ ] Dialog class inherits from `CDialogEx` or `CDialog` - [ ] Event handler implements `IRtcEngineEventHandler` - [ ] Message map properly defined with `BEGIN_MESSAGE_MAP` / `END_MESSAGE_MAP` -- [ ] Example is registered in `CSceneDialog` +- [ ] Example is registered in `APIExampleDlg.h` and `APIExampleDlg.cpp` +- [ ] Scene label is declared in `Language.h` and initialized in `stdafx.cpp` +- [ ] Scene label has entries in both `en.ini` and `zh-cn.ini` - [ ] `InitializeAgoraEngine()` creates engine with correct config - [ ] `JoinChannel()` uses token from `CConfig` - [ ] `LeaveChannel()` and `release()` are called in `PostNcDestroy()` - [ ] All engine events posted to main thread via `PostMessage()` +- [ ] `APIExample.vcxproj` and `.filters` include the new files when they were added outside the IDE - [ ] `ARCHITECTURE.md` Case Index includes new/updated example - [ ] Code compiles without warnings or errors - [ ] Example appears in the scene list diff --git a/windows/AGENTS.md b/windows/AGENTS.md index b8939152d..5e3601839 100644 --- a/windows/AGENTS.md +++ b/windows/AGENTS.md @@ -49,7 +49,7 @@ All work must conform to the rules defined in `ARCHITECTURE.md`: - Each example implements `IAgoraRtcEngineEventHandler` interface - Each example manages its own Agora engine lifecycle - Message handlers are defined via `BEGIN_MESSAGE_MAP` / `END_MESSAGE_MAP` -- All examples are registered in `CSceneDialog` +- All examples are registered in `APIExampleDlg.h` and `APIExampleDlg.cpp` - Configuration is managed centrally via `CConfig` class ### Follow the Existing Language and Framework @@ -60,15 +60,6 @@ All work must conform to the rules defined in `ARCHITECTURE.md`: - Use message map pattern for event handling — do not introduce modern C++ patterns unless they already exist in the file being modified - Match the code style, naming, and patterns of existing examples -### Use Example-Level SKILLs - -Each example may contain a `SKILL.md` file in its folder. When working on or referencing a specific example: -1. Check whether a `SKILL.md` exists in that example's directory -2. If it exists, read it before making changes — it describes the API usage, call flow, and known constraints -3. If it does not exist, one will be created in the future; proceed using the source code as the reference - -**SKILL.md location pattern:** `APIExample/APIExample/[Basic|Advanced]//SKILL.md` - ### Use Project-Level SKILLs For broader tasks, use the skills in `.agent/skills/`: diff --git a/windows/ARCHITECTURE.md b/windows/ARCHITECTURE.md index 1655a93c9..e92efe599 100644 --- a/windows/ARCHITECTURE.md +++ b/windows/ARCHITECTURE.md @@ -30,11 +30,11 @@ windows/ │ │ ├── res/ # Resources (icons, dialogs, strings) │ │ ├── APIExample.cpp │ │ ├── APIExample.h -│ │ ├── APIExampleDlg.cpp # Main dialog +│ │ ├── APIExampleDlg.cpp # Main dialog and example registration │ │ ├── APIExampleDlg.h │ │ ├── CConfig.cpp # Configuration management │ │ ├── CConfig.h -│ │ ├── CSceneDialog.cpp # Scene selection dialog +│ │ ├── CSceneDialog.cpp # Shared dialog helper, not the case registration source │ │ └── CSceneDialog.h │ ├── APIExample.sln # Visual Studio solution │ ├── cicd/ # CI/CD scripts @@ -73,7 +73,7 @@ Each example is a dialog class that: ### Menu Registration -All examples are registered in `CSceneDialog` via a scene list. The example name must match the folder name. +All examples are registered in `APIExampleDlg.h` and `APIExampleDlg.cpp`. The localized scene name is wired through `Language.h`, `stdafx.cpp`, `en.ini`, and `zh-cn.ini`, and the example name should still match the folder name. ### Configuration Management