توسعه اپلیکیشن چت چند پلتفرمی iOS و macOS با Stream — راهنمای کاربردی
اپل اخیراً Mac Catalyst را معرفی کرده است که به اپلیکیشنهای iOS و macOS امکان میدهد که کدبیس نیتیو خود را به میزان زیادی به اشتراک بگذارند. با این که در گذشته کدبیس چند پلتفرمی دسکتاپ و موبایل عموماً به معنی وباپلیکیشنهای بستهبندیشده در یک قالب پرتابل بود، اما اینک میتوان با استفاده از کد نیتیو همین نتیجه را به سرعت به دست آورد. این بدان معنی است که نیاز به صرف زمان و پول کمتری برای توسعه کد وجود دارد و تجربه کاربری منسجم و عمیقتری حاصل میشود. در این راهنما با روش طراحی یک اپلیکیشن چت چند پلتفرمی آشنا خواهیم شد.
اکنون ساخت اپلیکیشنهای نیتیو Mac از روی اپلیکیشن کنونی iPad بسیار ساده شده است. با استفاده از Mac Catalyst اپلیکیشنهای شما پروژه و سورس کد یکسانی را به اشتراک میگذارند و میتوانند به طرز مؤثری قابلیتهای بدون دسکتاپ اپلیکیشنهای iPad را برای Mac تبدیل کنند. به این ترتیب میتوانید اپلیکیشن جدید Mac را به مخاطبان انگیزهمند بیش از 100 میلیون کاربر فعال سیستمهای مک ارائه کنید.
SDK سوئیفت برای اپلیکیشن چت Stream کاملاً با Mac Catalyst سازگار است، یعنی تجربه چت که برای iOS ساخته میشود، میتواند به سهولت به macOS انتقال یابد. در ادامه این مقاله این تبدیلپذیری را بررسی میکنیم.
کامپوننتهای UI
Stream Chat SDK به همراه همه کامپوننتهای UI برای فراهم ساختن امکان ساخت اپلیکیشن در طی چند دقیقه ارائه میشود.
این کامپوننتها به طرز یکپارچهای بین iOS و macOS ترجمه میشوند و تفاوتهای جزئی در رفتار نشان میدهند که برای به دست آوردن تجربه کاربری مورد انتظار روی هر پلتفرم ضروری است.
با استفاده از UISplitViewController میتوانید کانالها و صفحههای چت را در پنجره واحدی جای بدهید و از مزیت صفحه بزرگ بهرهمند شوید. این کاربرد در اپلیکیشن نمونه که در ریپازیتوری stream-chat-swift (+) ارائه شده، موجود است.
منوی Context
با فشردن طولانی روی یک پیام در سیستم عامل iOS، پسزمینه تار میشود و یک منوی Context با مجموعهای از اکشنها برای اجرا ظاهر میشود. روی سیستمهای macOS این رفتار به کنترل-کلیک ترجمه میشود که یک منوی Context معمول با ظاهر macOS را با مجموعهای از گزینهها نمایش میدهد.
هر پلتفرم دارای مجموعه متفاوتی از راهنماها است و برخی مواد مقدماتی ممکن است در خصوص شیوه دسترسی و ارائه تفاوت زیادی داشته باشند. برای نمونه در صفحه راهنمای اینترفیس انسانی اپل در ارتباط با منوی Context برای iOS (+) و macOS (+) میتوانید این تفاوت را ببینید.
آپلود ضمیمهها
یکی دیگر از کارکردهای چت استریم که از iOS به macOS ترجمه شده است، امکان آپلود کردن ضمیمههای پیام است. روی هر دو سیستم عامل iOS و macOS میتوانید تصاویر را از گالری سیستم آپلود کنید، هر چند UI کمی متفاوت است.
نسخه macOS یک تصویر را میگیرد یا ویدئو را ضبط میکند و یک UI مشابه Photo Booth عرضه میکند، و مرور فایل به کل مجموعه فایلهای روی مک دسترسی میدهد که جایگزین درایو iCloud در iOS شده است. همه این موارد کدهای iOS هستند که اجرا میشوند و macOS مسئولیت تبدیل این عناصر به عناصر طبیعی macOS را بر عده میگیرد.
پوش نوتیفیکیشن
اعلانهای پوش نیز با همان پیکربندی iOS به طرز یکپارچهای ارائه میشوند.
Xcode این گزینه را در اختیار شما قرار میدهد که از Bundle ID یکسانی برای اپلیکیشنهای iOS و macOS استفاده کنید. اگر اپلیکیشن از قبل از نوتیفیکشنها پشتیبانی کند، هیچ تنظیم دیگری برای نسخه macOS لازم نخواهد بود و روش ثبت دستگاهها برای پوش نوتیفیکیشن با Stream Chat SDK نیز همچنین است.
کلاینت سطح پایین
مبنای Stream Chat SDK کلاینت سطح پایین است. این کلاینت با API مربوط به Stream Chat ارتباط میگیرد و دادههایی که باید روی کامپوننتهای UI نمایش یابند را عرضه میکند. این کلاینت به طور کامل بین iOS و macOS سازگار است و میتوانید در صورت نیاز به داشتن کنترل بیشتر به طور مستقیم از آن استفاده کنید.
نکات دیگر
از لحاظ نظری با استفاده از Stream Chat SDK هر کدی را که برای iOS نوشته شده باشد، میتوان به ترتیبی به macOS ترجمه کرد. در صورتی که هر نوع شکافی مشاهده کردید میتوانید به سرعت کدی برای رفعِ مشکل بنویسید. Mac Catalyst یک فریمورک است که به سرعت در حال تکامل است و همین موضوع را در مورد Stream Chat نیز میتوان گفت. تیم سازنده آن این اطمینان را میدهند که کد کاملاً سازگار خواهد ماند و میتوانید تجربیات چت عالی برای هر دو سیستم iOS و macOS بسازید.
کدنویسی
برای شروع به ساخت اپلیکیشن چت چندپلتفرمی iOS و macOS بهترین روش استفاده از iOS Swift Chat SDK است. به این منظور یک پروژه جدید سوئیفت در Xcode11 با قالب Single View App و نام ChatDemo ایجاد کنید. از بین گزینههای User Interface مورد Storyboard را انتخاب کنید.
CocoaPods
در این پروژه ما از CocoaPods به عنوان ابزار مدیریت وابستگی استفاده میکنیم. شما میتوانید SDK را با استفاده از Cartage و یا Swift Package Manager نیز نصب کنید. اگر CocoaPods را هماینک نصب ندارید، میتوانید با دستور زیر آن را نصب کنید.
sudo gem install cocoapods
توجه کنید که اگر CocoaPods از قبل روی سیستم شما نصب است، باید مطمئن شوید که جدیدترین نسخه را دارید، چون در غیر این صورت در ادامه و در مراحل توسعه با خطا مواجه میشوید.
sudo gem update cocoapods
اکنون که در دایرکتوری پروژه هستید و مطمئن شدهاید که CocoaPods نصب شده است، نوبت آن رسیده که Swift Chat SDK را نصب کنیم. به این منظور CocoaPods را مقداردهی کرده و یک Podfile ایجاد کنید:
pod init
Podfile ایجاد شده را انتخاب کرده و محتوای فایل را با قطعه کد زیر ویرایش کنید:
1platform :ios, '11.0'
2
3target 'ChatDemo' do
4 use_frameworks!
5 pod 'StreamChat', '~> 2.0'
6end
اکنون که Podfile را ویرایش کردیم، باید به نصب وابستگیهای پروژه از طریق ترمینال با یک دستور ساده بپردازیم:
pod install --repo-update
دستور فوق به صورت خودکار فایل ChatDemo.xcworkspace را ایجاد میکند. اکنون که فضای کاری پروژه Pod ما با وابستگیها و همچنین پروژه اصلی تجهیز شده است، نوبت به انتقال به Xcode و تکمیل فرایند کار رسیده است.
افزون چت استریم به اپلیکیشن iOS
فایل ChatDemo.xcworkspace را در Xcode باز کنید:
open ChatDemo.xcworkspace
برای تنظیم چت استریم باید چند چیز را به فایل AppDelegate.swift اضافه کنیم. بدین ترتیب فایل AppDelegate.swift به صورت زیر درمیآید:
1import UIKit
2import StreamChatClient
3
4@UIApplicationMain
5class AppDelegate: UIResponder, UIApplicationDelegate {
6
7 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
8 Client.config = .init(apiKey: "b67pax5b2wdq", logOptions: .info)
9 let userExtraData = UserExtraData(name: "Delicate flower")
10 Client.shared.set(user: User(id: "delicate-flower-9", extraData: userExtraData),
11 token: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiZGVsaWNhdGUtZmxvd2VyLTkifQ.W6yuOncwfq7cdNwVVKfSjf1J9P-yqLzy4IeZV2ajtF8")
12 return true
13 }
14}
اکنون یک خطای کامپایلر به صورت زیر دریافت میکنید:
No such module ‘StreamChat’
چنین خطایی پیش از بیلد کردن طبیعی است. پروژه را با استفاده از کلیدهای Commnd+B بیلد کنید یا این که فعلاً آن را نادیده بگیرید تا در ادامه پروژه را بیلد کنیم. اپلیکیشن شما اکنون با اطلاعات احراز هویت API مربوط به اپلیکیشن دمو پیکربندی شده است. از آنجا که در مرحله توسعه هستیم کلاینت را طوری پیکربندی میکنیم که توضیحات طولانیتر از حد معمول داشته باشد. در یک اپلیکیشن واقعی احتمالاً تنها هشدارها یا پیامهای خطا را لاگ میکنیم.
توجه کنید که فایل Client.shared یک سینگلتون است که در Client.shared اپلیکیشن تعریف شده و از آن در سراسر اپلیکیشن استفاده مجدد میکنیم. کلاینت مشترک نشست جاری کاربر (توکن) و اطلاعات احراز هویت API اپلیکیشن استریم را مدیریت میکند. از آنجا که این یک راهنمای مقدماتی است، توکن کاربر از پیشتولید شده را در آن گنجاندهایم. در اپلیکیشنهای واقعی، بکاند احراز هویت باید چنین توکنی را در زمان لاگین/ثبت نام تولید کرده و به اپلیکیشن موبایل ارسال کند. برای کسب اطلاعات بیشتر در این مورد به مستندات (+) مراجعه کنید.
ضمیمه پیامها
برای الصاق عکس یا تصاویر دوربین به پیامها باید اپلیکیشن چت iOS از کاربر درخواست دسترسی کند. زمانی که این کار انجام یافت، میتوانید فایلها تصاویر را از روی سیستم انتخاب کرده و به پیام ضمیمه کنید. به این منظور باید چند تغییر در فایل Info.plist ایجاد کنید.
- NSPhotoLibraryUsageDescription – به کاربر دسترسی کتابخانه عکسها را میدهد.
- NSCameraUsageDescription – به کاربر دسترسی به دوربین برای گرفتن عکس میدهد.
- NSMicrophoneUsageDescription – به کاربر دسترسی به میکروفن برای گرفتن ویدئو از دوربین میدهد.
1...
2<key>NSCameraUsageDescription</key>
3<string>$(PRODUCT_NAME) would like to access your camera</string>
4<key>NSMicrophoneUsageDescription</key>
5<string>$(PRODUCT_NAME) would like to access your microphone</string>
6<key>NSPhotoLibraryUsageDescription</key>
7<string>$(PRODUCT_NAME) would like to access your photo</string>
8...
گفتگوهای چندگانه
اغلب اپلیکیشنهای چت بیش از یک گفتگو را مدیریت میکنند. Facebook Messenger ،Whatsapp و تلگرام نهمگی امکان داشتن چند گفتگوی یک به یک و یا گروهی را فراهم میسازند. در ادامه با روش تغییر اپلیکیشن iOS به طرزی آشنا میشویم که لیستی از کانالهای که کاربران عضو هستند را نمایش دهد. ما میخواهیم همه کانالهایی که کاربر جاری عضو آنها است را نمایش دهیم. ChannelsViewController از چند فیلتر پشتیبانی میکند. در این مورد باید از فیلتر زیر استفاده کنیم:
.in("members", ["delicate-flower-9"])
برای کسب اطلاعت بیشتر در این خصوص به مستندات (+) مراجعه کنید. اینک فایل SceneDelegate.swift باید به صورت زیر درآمده باشد:
1import UIKit
2import StreamChatClient
3import StreamChatCore
4import StreamChat
5
6class SceneDelegate: UIResponder, UIWindowSceneDelegate {
7
8 var window: UIWindow?
9
10
11 func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
12 // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
13 // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
14 // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
15 guard let _ = (scene as? UIWindowScene) else { return }
16
17 if let navigationController = window?.rootViewController as? UINavigationController,
18 let channelsViewController = navigationController.viewControllers.first as? ChannelsViewController {
19 channelsViewController.presenter = ChannelsPresenter(filter: .in("members", ["delicate-flower-9"]))
20 }
21 }
22}
در نرمافزار Xocde به منوی Editor > Embed In > Navigation Controller بروید. به این منظور باید مطمئن شوید که ویوکنترلر در استوریبورد انتخاب شده است.
اکنون باید ویوکنترلر ایجاد شده از سوی Xcode را طوری بهروزرسانی کنیم که از ChannelsViewController ارثبری کند و لیستی از کانالها را نمایش دهد. به این منظور فایل ViewController.swift را باز کرده و به صورت زیر تغییر دهید:
1import UIKit
2import StreamChatClient
3import StreamChatCore
4import StreamChat
5
6/// Use ChannelsViewController as the parent view controller.
7class ViewController: ChannelsViewController {
8}
اینک اگر اپلیکیشن iOS را اجرا کنید، فهرستی از کانالهایی که کاربر به آنها دسترسی دارد را نمایش میدهد. این فهرست کانالها به صورت خودکار از صفحهبندی پشتیبانی میکند و میتوانید آنها را به صورت خودکار همگامسازی کنید. همه اپلیکیشنهای iOS منطق یکسانی در زمینه لیست کردن کانالها ندارند. هم API و هم SDK به شما امکان میدهند که پارامترهای فیلترینگ و مرتبسازی خاص خود را داشته باشید.
قابلیتهای چت
ما میتوانیم در این اپلیکیشن پیامهایی مانند /giphy awesome ارسال کنیم. اپلیکیشن چت استریم دارای قابلیتهای از پیش آمادهای به شرح زیر است:
- دستورها: روی composer تمرکز دارند و با وارد کردن / میتوان از دستورهایی مانند /giphy استفاده کرد.
- واکنشها: روی یک پیام بزنید تا واکنشی نشان دهید و یا با فشردن طولانی یک تصویر ارسال کنید.
- پیشنمایش لینک: این پیشنمایشها به صورت خودکار در زمان ارسال لینک ایجاد میشوند.
- الصاق تصویر: با استفاده از دکمه ⨁ در composer میتوانید یک تصویر یا فایل را ضمیمه پیام خود بکنید.
- ویرایش پیامها: با فشردن طولانی روی پیامهای خودتان میتوانید منویی برای ویرایش پیام باز کنید.
- رویدادهای تایپ: در زمانی که فردی شروع به نوشتن پیام میکند، رویدادهای تایپ را در بخش تحتانی میبینید.
- رشتهها: برای ایجاد رشتهای برای هر پیام در کانال میتوانید روی یک پیام بزنید و روی Replay کلیک کنید تا وارد نمای رشته پیام شوید.
دیدن برخی از قابلیتهای چت زمانی که تنها یک فرد آنلاین باشد، کار دشواری است. اگر مایل به دیدن این موارد هستید یک کانال را روی وب باز کرده و تلاش کنید تعاملی داشته باشید تا رویدادهای تایپ، واکنشها و رشته پیامها را بررسی کنید.
سفارشیسازی پیشنمایش کانال
تا به اینجا با روش استفاده از کامپوننتهای پیشفرض آشنا شدیم. این کتابخانه برای این منظور طراحی شده است که کاملاً قابل بسط باشد و به شما امکان بدهد که هر نوع چت را بسازید. ما در مثال خود پنج مقصود اصلی را در نظر داریم:
- چت اجتماعی/ پیامرسانی
- چت با استایل اسلک / کاری
- چت پشتیبانی کاربر
- چت پخش زنده
- گیم
در ادامه بررسی میکنیم که چطور با ایجاد برخی تغییرها در کامپوننتهای UI مربوط به SDK میتوانیم مقاصد فوق را برآورده سازیم. کار خود را با تغییر شیوه نمایش پیشنمایشهای کانال در لیست کانال و گنجاندن تعداد پیامهای ناخوانده برای هر کدام آغاز میکنیم.
ChannelsViewController پیشنمایش هر کانال را به صورت یک سلول جدول رندر میکند. تنها کاری که باید بکنیم این است که از آن کلاس فرعی بگیریم و اقدام به override گزینه زیر بکنیم:
channelCell(at indexPath: IndexPath, channelPresenter: ChannelPresenter)
همچنین باید شمار پیامهای ناخوانده را نیز بگنجانیم و به این منظور فایل ViewController.swift را باز کرده و به صورت زیر تغییر دهید:
1import UIKit
2import StreamChatClient
3import StreamChatCore
4import StreamChat
5
6/// Use ChannelsViewController as the parent view controller.
7class ViewController: ChannelsViewController {
8 /// We override the inherited method for a channel cell.
9 /// Here we can create absolutely new table view cell for the channel.
10 override func channelCell(at indexPath: IndexPath, channelPresenter: ChannelPresenter) -> UITableViewCell {
11 // For now, get the default channel cell.
12 let cell = super.channelCell(at: indexPath, channelPresenter: channelPresenter)
13
14 // We need to check if the returned cell is ChannelTableViewCell.
15 guard let channelCell = cell as? ChannelTableViewCell else {
16 return cell
17 }
18
19 let unreadCount = channelPresenter.channel.unreadCount.messages
20
21 // Check the number of unread messages.
22 if unreadCount > 0 {
23 // Add the info about unread messages to the cell.
24 channelCell.update(info: "\(unreadCount) unread", isUnread: true)
25 }
26
27 return channelCell
28 }
29}
اکنون میتوانیم اپلیکیشن را اجرا کرده و حالتهای سلول کانال را ببینیم. یک پیام به خودتان ارسال کنید تا تغییر تعداد پیامهای ناخوانده را مشاهده کنید:
پیام سفارشی
اینک به صفحه چت میرویم و UI سفارشی برای پیامها ایجاد میکنیم. یک ویوکنترلر دیگر برای این منظور میسازیم. پوشه ChatDemo را در بخش File inspector انتخاب کنید تا یک ویوکنترلر جدید بسازید. به منوی ...File menu > New > File بروید و قالب Cocoa Touch Class را انتخاب کنید. نام کلاس را به صورت DemoChatViewController وارد کنید و نام کلاس فرعی را ChatViewController بگذارید. روی Next کلیک کنید تا فایل ایجاد شود. کد فایل DemoChatViewController.swift را به صورت زیر تغییر دهید:
1import UIKit
2import StreamChatClient
3import StreamChatCore
4import StreamChat
5
6class DemoChatViewController: ChatViewController {
7
8 /// Override the default implementation of UI messages
9 /// with default UIKit table view cell.
10 override func messageCell(at indexPath: IndexPath, message: Message, readUsers: [User]) -> UITableViewCell {
11 let cell = tableView.dequeueReusableCell(withIdentifier: "message")
12 ?? UITableViewCell(style: .value2, reuseIdentifier: "message")
13
14 cell.textLabel?.text = message.user.name
15 cell.textLabel?.font = .systemFont(ofSize: 12, weight: .bold)
16 cell.detailTextLabel?.text = message.deleted == nil ? message.text : "This message was deleted"
17 cell.detailTextLabel?.font = message.deleted == nil ? .systemFont(ofSize: 12) : .systemFont(ofSize: 12, weight: .light)
18 cell.detailTextLabel?.numberOfLines = 0
19
20 return cell
21 }
22}
در کد فوق متد messageCell را override کردیم تا بازنمایی خودمان را برای پیامها پیادهسازی کنیم. مقدار cell.textLabel برای بهروزرسانی نام یک کاربر تغییر یافته است. cell.detailTextLabel برای دریافت پیام بهروزرسانی شده است. اینک که ویوکنترلر کانال سفارشی ما آماده است باید یک کنترلر لیست کانال داشته باشیم تا بتوانیم از آن استفاده کنیم. به این منظور باید متد createChatViewController را override کنیم. فایل ViewController.swift را باز کنید و متد زیر را اضافه نمایید:
1 override func createChatViewController(with channelPresenter: ChannelPresenter) -> ChatViewController {
2 return DemoChatViewController()
3 }
اینک اپلیکیشن را اجرا کنید و برخی پیامها را وارد نمایید تا یک نمای ساده با سلول نمای جدولی UIKit پیشفرض بینیم. به علاوه میتوانید یک سفارشیسازی برای سلولهای بارگذاری و حالت ببینید.
نکته: برای سفارشیسازیهای پیچیده بهترین رویکرد این است که کامپوننت پیام را به صورت یک ChannelTableViewCell پیادهسازی کنید.
Theme سفارشی
ChatViewController برخی مشخصههای استایل را عرضه میکند که با استفاده از آنها میتوان ظاهر اپلیکیشن را برای تطبیق با مقاصد مختلف تغییر داد. به این منظور کلاس DemoChatViewController را بهروزرسانی کرده و برخی تغییرهای استایل در کد ایجاد میکنیم:
1 required init?(coder aDecoder: NSCoder) {
2 super.init(coder: aDecoder)
3 setupStyles()
4 }
5
6 init() {
7 super.init(nibName: nil, bundle: nil)
8 setupStyles()
9 }
10
11 func setupStyles() {
12 style.incomingMessage.chatBackgroundColor = UIColor(hue: 0.2, saturation: 0.3, brightness: 1, alpha: 1)
13 style.incomingMessage.backgroundColor = UIColor(hue: 0.6, saturation: 0.5, brightness: 1, alpha: 1)
14 style.incomingMessage.borderWidth = 0
15 style.outgoingMessage.chatBackgroundColor = style.incomingMessage.chatBackgroundColor
16 style.outgoingMessage.backgroundColor = style.incomingMessage.chatBackgroundColor
17 style.outgoingMessage.font = .systemFont(ofSize: 15, weight: .bold)
18 style.outgoingMessage.cornerRadius = 0
19 style.outgoingMessage.avatarViewStyle = nil
20 }
در این مورد برخی تغییرهای استایل در استایل داخلی ViewController ایجاد کردهایم.
سخن پایانی
در این راهنما با روش ساخت یک اپلیکیشن چت iOS با استفاده از زبان سوئیفت آشنا شدیم. همچنین دیدیم که سفارشیسازی رفتار و ساخت انواع مختلفی از تجربههای پیام چت چه قدر آسان است. API پشت این اپلیکیشن چت بر مبنای Go ،RocksDB و Raft است و موجب میشود که تجربه چت بسیار سریع باشد و زمان پاسخگویی در اغلب موارد زیر 10 میلیثانیه است stream امکان نمایش فید فعالیت و چت بیش از 500 میلیون کاربر نهایی را دارد و از این رو در مورد API آن کاملاً مطمئن باشید.
پس از آن که اپلیکیشن خود را کدنویسی کردید و همه کدها و وابستگیها با Mac Catalyst سازگار بودند، تنها چیزی که لازم دارید انجام دهید، این است که Mac را به عنوان دستگاه در بخش Deployment Info در هدف اصلی انتخاب کنید.
اگر میخواهید از امکان آپلود فایل نیز پشتیبانی کنید، باید مطمئن شوید که یک مدخل BOOL برای com.apple.security.files.user-selected.read-only با مقدار YES به فایل .entitlements اضافه کردهاید چون در غیر این صورت اپلیکیشن در زمان باز کردن فایل اکسپلورر کرش میکند.
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای برنامهنویسی
- آموزش برنامهنویسی Swift (سوئیفت) برای برنامه نویسی iOS
- مجموعه آموزشهای دروس علوم و مهندسی کامپیوتر
- آموزش سوئیفت (Swift) — مجموعه مقالات مجله فرادرس
- پنج کتابخانه iOS برای بهبود اپلیکیشنها — راهنمای کاربردی
==