Skip to content

Commit 8328d79

Browse files
Add Android Examples (#263)
Co-authored-by: Youngteac Hong <susukang98@gmail.com>
1 parent df62d2b commit 8328d79

33 files changed

+634
-60
lines changed

.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
NEXT_PUBLIC_YORKIE_VERSION='0.6.39'
33
NEXT_PUBLIC_YORKIE_JS_VERSION='0.6.39'
44
NEXT_PUBLIC_YORKIE_IOS_VERSION='0.6.25'
5-
NEXT_PUBLIC_YORKIE_ANDROID_VERSION='0.6.25'
5+
NEXT_PUBLIC_YORKIE_ANDROID_VERSION='0.6.26'
66
NEXT_PUBLIC_DASHBOARD_PATH='/dashboard'
77
NEXT_PUBLIC_JS_SDK_URL='https://cdn.jsdelivr.net/npm/@yorkie-js/sdk@0.6.39/dist/yorkie-js-sdk.js'
88
NEXT_PUBLIC_STATUS_URL='https://stats.uptimerobot.com/otOOggryMR'

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,6 @@ robots.txt
8787
# Built example apps
8888
/public/apps
8989
/temp
90+
91+
# idea
92+
.idea

components/Icons/Icon.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import FileSVG from '@/public/assets/icons/icon_file.svg';
4444
import TextInputSVG from '@/public/assets/icons/icon_text_input.svg';
4545
import TransformSVG from '@/public/assets/icons/icon_transform.svg';
4646
import ReactLogoSVG from '@/public/assets/icons/icon_react_logo.svg';
47+
import AndroidLogoSVG from '@/public/assets/icons/icon_android_logo.svg';
4748

4849
const svgMap = {
4950
star: <StarSVG />,
@@ -90,6 +91,7 @@ const svgMap = {
9091
textInput: <TextInputSVG />,
9192
transform: <TransformSVG />,
9293
reactLogo: <ReactLogoSVG />,
94+
androidLogo: <AndroidLogoSVG />,
9395
};
9496
type SVGType = keyof typeof svgMap;
9597

components/Layout/Footer.tsx

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,12 @@ export function Footer({ shortFooter }: { shortFooter?: boolean }): ReactElement
4242
<ul className="site_list">
4343
<li className="site_item">
4444
<Link href="/products#document-and-presence" className="link">
45-
Document and Presence
45+
Document
46+
</Link>
47+
</li>
48+
<li className="site_item">
49+
<Link href="/products#channel" className="link">
50+
Channel
4651
</Link>
4752
</li>
4853
<li className="site_item">
@@ -79,20 +84,16 @@ export function Footer({ shortFooter }: { shortFooter?: boolean }): ReactElement
7984
JS SDK
8085
</Link>
8186
</li>
82-
{process.env.NODE_ENV === 'development' && (
83-
<>
84-
<li className="site_item">
85-
<Link href="/docs/ios-sdk" className="link">
86-
iOS SDK
87-
</Link>
88-
</li>
89-
<li className="site_item">
90-
<Link href="/docs/android-sdk" className="link">
91-
Android SDK
92-
</Link>
93-
</li>
94-
</>
95-
)}
87+
<li className="site_item">
88+
<Link href="/docs/ios-sdk" className="link">
89+
iOS SDK
90+
</Link>
91+
</li>
92+
<li className="site_item">
93+
<Link href="/docs/android-sdk" className="link">
94+
Android SDK
95+
</Link>
96+
</li>
9697
</ul>
9798
</div>
9899
<div className="site">

components/Layout/MobileGnbDropdown.tsx

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -87,30 +87,26 @@ export function MobileGnbDropdown({ isLoggedIn }: { isLoggedIn: boolean }) {
8787
JS SDK
8888
</Link>
8989
</li>
90-
{process.env.NODE_ENV === 'development' && (
91-
<>
92-
<li className="navigator_group">
93-
<Link
94-
href="/docs/ios-sdk"
95-
className={classNames('navigator_item', 'add_icon', {
96-
is_active: asPath.startsWith(`/docs/ios-sdk`),
97-
})}
98-
>
99-
iOS SDK
100-
</Link>
101-
</li>
102-
<li className="navigator_group">
103-
<Link
104-
href="/docs/android-sdk"
105-
className={classNames('navigator_item', 'add_icon', {
106-
is_active: asPath.startsWith(`/docs/android-sdk`),
107-
})}
108-
>
109-
Android SDK
110-
</Link>
111-
</li>
112-
</>
113-
)}
90+
<li className="navigator_group">
91+
<Link
92+
href="/docs/ios-sdk"
93+
className={classNames('navigator_item', 'add_icon', {
94+
is_active: asPath.startsWith(`/docs/ios-sdk`),
95+
})}
96+
>
97+
iOS SDK
98+
</Link>
99+
</li>
100+
<li className="navigator_group">
101+
<Link
102+
href="/docs/android-sdk"
103+
className={classNames('navigator_item', 'add_icon', {
104+
is_active: asPath.startsWith(`/docs/android-sdk`),
105+
})}
106+
>
107+
Android SDK
108+
</Link>
109+
</li>
114110
<li className="navigator_group">
115111
<Link
116112
href="/docs/devtools"

components/exampleView/BasicView/ProjectCodes.tsx

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,20 @@ export function ProjectCodes({
2828
const [activeFileInfo, setActiveFileInfo] = useState<FileInfo | null>(
2929
activeFile ? getFileInfo(files, activeFile) : null,
3030
);
31-
const [isFolderOpen, setIsFolderOpen] = useState<boolean>(true);
31+
// Track open state for each folder by path
32+
const [openFolders, setOpenFolders] = useState<Set<string>>(new Set());
33+
34+
const toggleFolder = useCallback((folderPath: string) => {
35+
setOpenFolders((prev) => {
36+
const next = new Set(prev);
37+
if (next.has(folderPath)) {
38+
next.delete(folderPath);
39+
} else {
40+
next.add(folderPath);
41+
}
42+
return next;
43+
});
44+
}, []);
3245

3346
const onClickFile = useCallback(
3447
(targetFile: string) => {
@@ -53,6 +66,21 @@ export function ProjectCodes({
5366
initialFileInfo = directoryInfoResult;
5467
setActiveFileInfo(openedFile);
5568
setFileInfo(initialFileInfo);
69+
70+
// Initialize all folders as open
71+
const allFolderPaths = new Set<string>();
72+
const collectFolderPaths = (info: DirectoryInfo) => {
73+
if (!info.isFile) {
74+
allFolderPaths.add(info.path);
75+
info.children.forEach((child) => {
76+
if (!child.isFile) {
77+
collectFolderPaths(child);
78+
}
79+
});
80+
}
81+
};
82+
collectFolderPaths(initialFileInfo);
83+
setOpenFolders(allFolderPaths);
5684
}, [files, activeFile, ignoreFiles]);
5785

5886
return (
@@ -62,29 +90,37 @@ export function ProjectCodes({
6290
if (isEmptyDirectory(child)) {
6391
return null;
6492
}
93+
const isOpen = openFolders.has(child.path);
6594
return (
66-
<li key={child.name} className={classNames('folder_item', { is_active: child.isFile && child.isOpen })}>
95+
<li key={child.path} className={classNames('folder_item', { is_active: child.isFile && child.isOpen })}>
6796
<button
6897
type="button"
6998
className="btn_folder"
7099
onClick={() => {
71100
if (child.isFile) {
72101
onClickFile(child.path);
73102
} else {
74-
setIsFolderOpen(!isFolderOpen);
103+
toggleFolder(child.path);
75104
}
76105
}}
77106
>
78107
{child.isFile ? (
79108
<Icon type="file" />
80-
) : isFolderOpen ? (
109+
) : isOpen ? (
81110
<Icon type="folderOpen" />
82111
) : (
83112
<Icon type="folderClose" />
84113
)}
85114
<span className="name"> {child.name}</span>
86115
</button>
87-
{!child.isFile && isFolderOpen && <SubFolderCodes fileList={child.children} onClickFile={onClickFile} />}
116+
{!child.isFile && isOpen && (
117+
<SubFolderCodes
118+
fileList={child.children}
119+
onClickFile={onClickFile}
120+
openFolders={openFolders}
121+
toggleFolder={toggleFolder}
122+
/>
123+
)}
88124
</li>
89125
);
90126
})}
@@ -106,40 +142,51 @@ export function ProjectCodes({
106142
function SubFolderCodes({
107143
fileList,
108144
onClickFile,
145+
openFolders,
146+
toggleFolder,
109147
}: {
110148
fileList: Array<CodeInfo>;
111149
onClickFile: (targetFile: string) => void;
150+
openFolders: Set<string>;
151+
toggleFolder: (folderPath: string) => void;
112152
}) {
113-
const [isFolderOpen, setIsFolderOpen] = useState<boolean>(true);
114153
return (
115154
<ul className="folder_sub_list">
116155
{fileList.map((child) => {
117156
if (isEmptyDirectory(child)) {
118157
return null;
119158
}
159+
const isOpen = openFolders.has(child.path);
120160
return (
121-
<li className={classNames('folder_item', { is_active: child.isFile && child.isOpen })} key={child.name}>
161+
<li className={classNames('folder_item', { is_active: child.isFile && child.isOpen })} key={child.path}>
122162
<button
123163
type="button"
124164
className="btn_folder"
125165
onClick={() => {
126166
if (child.isFile) {
127167
onClickFile(child.path);
128168
} else {
129-
setIsFolderOpen(!isFolderOpen);
169+
toggleFolder(child.path);
130170
}
131171
}}
132172
>
133173
{child.isFile ? (
134174
<Icon type="file" />
135-
) : isFolderOpen ? (
175+
) : isOpen ? (
136176
<Icon type="folderOpen" />
137177
) : (
138178
<Icon type="folderClose" />
139179
)}
140180
<span className="name">{child.name}</span>
141181
</button>
142-
{!child.isFile && isFolderOpen && <SubFolderCodes fileList={child.children} onClickFile={onClickFile} />}
182+
{!child.isFile && isOpen && (
183+
<SubFolderCodes
184+
fileList={child.children}
185+
onClickFile={onClickFile}
186+
openFolders={openFolders}
187+
toggleFolder={toggleFolder}
188+
/>
189+
)}
143190
</li>
144191
);
145192
})}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Video Player Component
2+
export function VideoPlayer({videoSrc}: { videoSrc: string }) {
3+
return (
4+
<div className="content"
5+
style={{display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '24px'}}>
6+
<video
7+
controls
8+
autoPlay
9+
loop
10+
muted
11+
style={{
12+
maxWidth: '100%',
13+
maxHeight: 'calc(100vh - 112px)',
14+
borderRadius: '8px',
15+
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)'
16+
}}
17+
>
18+
<source src={videoSrc} type="video/mp4"/>
19+
Your browser does not support the video tag.
20+
</video>
21+
</div>
22+
);
23+
}
24+

components/exampleView/const.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
export const EXAMPLE_CODE_URL = 'https://github.com/yorkie-team/yorkie-js-sdk/tree/main/examples/';
22
export const EXAMPLE_PREVIEW_URL = '/apps/';
3+
4+
export const EXAMPLE_ANDROID_CODE_URL = 'https://github.com/yorkie-team/yorkie-android-sdk/tree/main/examples/';

docs/glossary.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ These words are used in the Yorkie project and the community. Understanding thes
1313
| Client | An regular client that communicates with the Yorkie server, synchronizing document changes for real-time collaboration. |
1414
| Server | The central component that manages documents and channels, stores changes, and facilitates communication between clients. |
1515
| Project | A logical grouping of documents, channels, and clients, allowing multiple independent services within a single Yorkie installation. |
16-
| Document | The primary data structure in Yorkie for persistent, collaborative data. It contains a presence and a root, and is stored in the database. Documents support offline editing and conflict-free synchronization using CRDTs. For more details, see [Document Concept](/docs/concepts/document). |
17-
| Channel | A lightweight, memory-only communication layer for real-time features like presence tracking and message broadcasting. Unlike Documents, Channels do not persist data to the database and are designed for ephemeral data. For more details, see [Channel Concept](/docs/concepts/channel). |
16+
| Document | The primary data structure in Yorkie for persistent, collaborative data. It contains a presence and a root, and is stored in the database. Documents support offline editing and conflict-free synchronization using CRDTs. |
17+
| Channel | A lightweight, memory-only communication layer for real-time features like presence tracking and message broadcasting. Unlike Documents, Channels do not persist data to the database and are designed for ephemeral data. |
1818
| Presence | A data structure representing a user's current state within a document (e.g., cursor position, selection). |
1919
| Root | The main JSON-like data structure(CRDT) within a document that can be shared and edited by multiple users. |
2020
| Change | A representation of modifications made to a document, created by calling `Document.Update()`. |

examples/android-rich-text-editor/fileInfo.ts

Lines changed: 2 additions & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)