KO-CALL
npm package
Installation
IOS
- Need to install these dependencies:
yarn add ko-call react-native-agora@4.1.1 && react-native-keep-awake@4.0.0 && npx react-native link react-native-keep-awake && cd ios && pod install
- In AppDelegate.m add following lines:
...
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
moduleName:@"edtalk"
initialProperties:nil];
//EKLENECEK OLAN BÖLÜM//
//PushRegistry register işlemi yapılıyor
self.voipQueue = dispatch_queue_create("com.edtalk.voipqueue", NULL);
//self.pushRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()];
self.pushRegistry = [[PKPushRegistry alloc] initWithQueue: self.voipQueue];
self.pushRegistry.delegate = self;
self.pushRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP];
//PushRegistry register işlemi yapılıyor
[RNCallKeep setup:@{
@"appName": @"ed-talk",
@"maximumCallGroups": @3,
@"maximumCallsPerCallGroup": @1,
@"supportsVideo": @NO,
}];
//EKLENECEK OLAN BÖLÜM//
...
/* arama ve notifications metotları */
/* Cihaz tokeni burada alınıyor ve React tarafında da ulaşabilmek için
UserDefaults a kaydediliyor */
- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(PKPushType)type {
// Register VoIP push token (a property of PKPushCredentials) with server
NSLog(@"my console log");
if([credentials.token length] == 0) {
NSLog(@"voip token NULL");
return;
}
const unsigned *tokenBytes = (const unsigned *)[credentials.token bytes];
NSString *tkn = [NSString stringWithFormat:@"%08x%08x%08x%08x%08x%08x%08x%08x",
ntohl(tokenBytes[0]), ntohl(tokenBytes[1]), ntohl(tokenBytes[2]),
ntohl(tokenBytes[3]), ntohl(tokenBytes[4]), ntohl(tokenBytes[5]),
ntohl(tokenBytes[6]), ntohl(tokenBytes[7])];
self.prefs = [NSUserDefaults standardUserDefaults];
[self.prefs setObject:tkn forKey:@"deviceToken"];
NSLog(@"TOKEN: %@", tkn);
}
//Bu metot olmak zorunda, o sebeple eklendi
- (void)pushRegistry:(PKPushRegistry *)registry didInvalidatePushTokenForType:(PKPushType)type
{
}
/* Cihaz tokeni burada alınıyor ve React tarafında da ulaşabilmek için
UserDefaults a kaydediliyor */
/* Gelen notification burada yakalanıyor ve işlemler burada yapılıyor */
- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion {
//Eğer gelen bildirim voip bildirimi ise işlemleri yap
if (type == PKPushTypeVoIP) {
//UserDefault ile ReactNative tarafındaki Settings iletişimi sağlanıyor
self.prefs = [NSUserDefaults standardUserDefaults];
//Gelen verileri alıyoruz
NSDictionary *payloadDict = payload.dictionaryPayload[@"data"];
self.channel = payloadDict[@"channel"];
self.token = payloadDict[@"token"];
self.phoneNum = payloadDict[@"number"];
self.callerName = payloadDict[@"teacher"];
self.appId = payloadDict[@"appId"];
//
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
self.remoteTimeStamp = [formatter numberFromString:payloadDict[@"callTime"]];
NSDate *now = [NSDate date];
self.localTimeStamp = @(round([now timeIntervalSince1970] * 1000));
double timeDiffInMillis = [self.localTimeStamp doubleValue] - [self.remoteTimeStamp doubleValue];
self.timeDiff = @(timeDiffInMillis / 1000.0);
NSNumber *callTime = @28;
NSLog(@"Time difference: %@", self.timeDiff);
//UUID oluşturuluyor
NSUUID * _Nonnull uuidTemp = [NSUUID UUID];
self.uuid = uuidTemp;
//react-native-callkeep paketi için uuid gönderilecek. String'e dönüştürülüyor
NSString * _Nonnull uuidString = [[uuidTemp UUIDString] lowercaseString];
[self.prefs setObject:uuidString forKey:@"uuid"];
//Agora engine initialize işlemi yapılıyor
[self initializeAgoraEngine];
//Arama için gerekli configuration ayarları oluşturuluyor
CXProviderConfiguration *providerConfiguration;
providerConfiguration = [[CXProviderConfiguration alloc] initWithLocalizedName:@"ed-talk"];
providerConfiguration.supportsVideo = false;
providerConfiguration.supportedHandleTypes = [[NSSet alloc] initWithObjects:[NSNumber numberWithInt:(int)CXHandleTypePhoneNumber], nil];
providerConfiguration.maximumCallsPerCallGroup = 1;
self.provider = [[CXProvider alloc] initWithConfiguration:providerConfiguration];
[self.provider setDelegate:self queue:dispatch_get_main_queue()];
//Arama için gerekli configuration ayarları oluşturuluyor
//Aramayı raporlarmak için ön hazırlıklar yapılıyor
CXCallUpdate * _Nonnull callUpdate = [[CXCallUpdate alloc] init];;
CXHandle *phoneNumber = [[CXHandle alloc] initWithType:CXHandleTypePhoneNumber value: self.phoneNum];
callUpdate.remoteHandle = phoneNumber;
callUpdate.localizedCallerName = self.callerName;
callUpdate.supportsDTMF = NO;
callUpdate.supportsGrouping = NO;
callUpdate.supportsHolding = NO;
//Aramayı raporlarmak için ön hazırlıklar yapılıyor
NSNumber *callDuration = @([callTime doubleValue] - [self.timeDiff doubleValue]);
NSLog(@"Call duration: %@", callDuration);
NSLog(@"Time difference is not greater than callTime");
if ([self.timeDiff doubleValue] < [callTime doubleValue]) {
// Do something if callTime is greater than time difference
NSLog(@"Time difference is greater than callTime");
//Arama yeni arama olarak CallKit'e raporlanıyor
[self.provider reportNewIncomingCallWithUUID:self.uuid update:callUpdate completion:^(NSError * _Nullable error) {
if (error != nil) {
NSLog(@"reportNewIncomingCall error: %@",error.localizedDescription);
}
NSLog(@"timer comes here"); //25 Saniye süre için timer çağırılıyor
[self startTimer:callDuration];
completion();
}];
//Arama yeni arama olarak CallKit'e raporlanıyor
} else {
// Do something else if callTime is not greater than time difference
NSLog(@"Time difference is not greater than callTime");
[self.provider reportNewIncomingCallWithUUID:self.uuid update:callUpdate completion:^(NSError * _Nullable error) {
if (error != nil) {
NSLog(@"reportNewIncomingCall error: %@",error.localizedDescription);
}
NSLog(@"timer comes here");
[self resetCallVariable];
completion();
}];
[self endCall];
//Missed call logic
}
completion();
} else {
NSLog(@"Gelen bildirim voip değillll");
}
}
/* Gelen notification burada yakalanıyor ve işlemler burada yapılıyor */
//Timer durduruluyor ve saniye sıfırlanıyor
- (void) resetCallVariable {
//Timerı durdur
[self.timer invalidate];
self.timer = nil;
//Saniyeyi sıfırla
self.second = 0;
}
- (void) startTimer:(NSNumber*)duration {
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSInteger durationSeconds = [duration integerValue];
self.second = self.second + 1;
NSLog(@"%ld",(long)self.second);
if(self.second >= durationSeconds) {
[self resetCallVariable];
[self endCall];
[timer invalidate];
self.timer = nil;
}
}];
}
//Agora initialize fonksiyonu
- (void)initializeAgoraEngine {
self.agoraKit = [AgoraRtcEngineKit sharedEngineWithAppId: self.appId delegate:self];
//Mikrofon ve aktif arama değişkenlerini false olarak işaretliyoruz
self.isMuted = false;
self.isActiveCall = false;
}
//Agora kanala giriş yapılıyor
- (void)joinChannel {
//Ses ayarlarını yapıyoruz
//Agora kanalına girmeden önce broadcaster olduğumuzu belirtiyoruz.
AgoraRtcChannelMediaOptions *option = [[AgoraRtcChannelMediaOptions alloc] init];
option.channelProfile = AgoraChannelProfileLiveBroadcasting;
option.clientRoleType = AgoraClientRoleBroadcaster;
//Kanala yukarıdaki ayarlar ile giriyoruzz
[self.agoraKit joinChannelByToken:self.token channelId:self.channel uid:0 mediaOptions:option joinSuccess:^(NSString * _Nonnull channel, NSUInteger uid, NSInteger elapsed) {
}];
NSInteger speakerResult = [self.agoraKit setDefaultAudioRouteToSpeakerphone:false];
[self.agoraKit enableAudio];
[self configureAudioSession];
//Kanala giriş yapıldığı için aktif arama olduğunu belirtiyoruz
//Eğer aktif arama varsa endCall metotunda agoradan da çıkılacak
self.isActiveCall = true;
}
//Arama biterse yapılacak işlemler
-(void) endCall {
[self.provider reportCallWithUUID: self.uuid endedAtDate:NSDate.date reason: CXCallEndedReasonRemoteEnded];
//Eğer aktif arama varsa agora kanalından ayrıl ve engine destroy et
if (self.isActiveCall) {
[self.agoraKit leaveChannel:nil];
self.isActiveCall = false;
}
[AgoraRtcEngineKit destroy];
}
//Aramaya yanıt verildiğinde yapılacak işlemler
- (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAction *)action {
//Bütün değerler sıfırlanıyor
[self resetCallVariable];
//Gelen token ve kanal bilgileri ile kanala giriliyor
[self joinChannel];
[action fulfill];
}
//Arama reddedildiğinde yapılan işlemler
- (void)provider:(CXProvider *)provider performEndCallAction:(CXEndCallAction *)action {
//Arama kapandığında uuid değişeknini sıfırla
self.prefs = [NSUserDefaults standardUserDefaults];
[self.prefs setObject:@"" forKey:@"uuid"];
//Bütün değerler sıfırlanıyor
[self resetCallVariable];
//Arama kapatılıyor
[self endCall];
[action fulfill];
}
//Mikrofon kapatıldığında yapılan işlemler
- (void)provider:(CXProvider *)provider performSetMutedCallAction:(nonnull CXSetMutedCallAction *)action {
[self.agoraKit muteLocalAudioStream:!self.isMuted];
self.isMuted = !self.isMuted;
[action fulfill];
}
//Cihaz kilitliyken arama gelirse mikrofonu etkinleştiriyoruz
- (void) configureAudioSession {
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord mode:AVAudioSessionModeDefault options: AVAudioSessionCategoryOptionDuckOthers | AVAudioSessionCategoryOptionAllowBluetooth | AVAudioSessionCategoryOptionAllowBluetoothA2DP error:nil];
[[AVAudioSession sharedInstance] setMode:AVAudioSessionModeVoiceChat error:nil];
}
//Eğer karşı taraf aramayı kapatırsa
- (void) rtcEngine:(AgoraRtcEngineKit *)engine didOfflineOfUid:(NSUInteger)uid reason:(AgoraUserOfflineReason)reason {
[self endCall];
}
- in AppDelegate.h file:
#import <React/RCTBridgeDelegate.h>
#import <UIKit/UIKit.h>
#import <AgoraRtcKit/AgoraRtcEngineKit.h>
#import <CallKit/CallKit.h>
#import <PushKit/PushKit.h>
#import <AVFAudio/AVAudioSession.h>
#import <RNCallkeep/RNCallKeep.h>
//PKPushRegistryDelegate, CXProviderDelegate, AgoraRtcEngineDelegate isimli delegeler ekleniyor
@interface AppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate, PKPushRegistryDelegate, CXProviderDelegate, AgoraRtcEngineDelegate>
@property (nonatomic, strong) UIWindow *window;
//UserDefaults için genel değişken tanımlanıyor
@property NSUserDefaults *prefs;
//Agora için gerekli olan veriler buraya tanımlanıyor
@property (strong, nonatomic) AgoraRtcEngineKit *agoraKit;
@property NSString *channel;
@property NSString *token;
@property NSString *phoneNum;
@property NSString *callerName;
@property NSString *appId;
@property NSNumber *remoteTimeStamp;
@property NSNumber *localTimeStamp;
@property NSNumber *timeDiff;
@property Boolean isActiveCall;
@property Boolean isMuted;
//Arama için gerekli değişkenler
@property CXProvider *provider;
@property PKPushRegistry *pushRegistry;
@property NSUUID *uuid;
@property NSObject<OS_dispatch_queue> *voipQueue;
//Aramanın 25 saniye sürüp kapanması için gerekli değişkenler
@property NSInteger second;
@property NSTimer *timer;
@end
- in info.plist:
<key>NSCameraUsageDescription</key>
<string>The app needs camera permission to attend the classes.</string>
<key>NSMicrophoneUsageDescription</key>
<string>The app needs microphone permission to attend the classes.</string>
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
<string>voip</string>
</array>
- in the podfile add this lines:
pod 'RNCallKeep', :path => '../node_modules/react-native-callkeep'
- Enable Push Notifications
- Enable Background Modes
ANDROID
- in AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.edtalk">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
<application
android:name=".MainApplication"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="false"
android:usesCleartextTraffic="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:resizeableActivity="true"
android:supportsPictureInPicture="true"
android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|smallestScreenSize|orientation"
android:windowSoftInputMode="adjustResize"
android:exported="true"
android:launchMode="singleTask"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<action android:name="android.intent.action.DOWNLOAD_COMPLETE"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="edtalk" />
</intent-filter>
</activity>
<receiver android:name=".ActionReceiver" />
</application>
</manifest>
- in android build.gradle:
buildscript {
ext {
buildToolsVersion = "30.0.2"
minSdkVersion = 26
compileSdkVersion = 31
targetSdkVersion = 31
ndkVersion = "21.4.7075529"
}
...
}
- in android/app/src/main/java/com/edtalk we need 3 files, first one is ActionReceiver.java:
package com.edtalk;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Vibrator;
public class ActionReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String[] actionArray=intent.getStringExtra("action").split(":");
String action = actionArray[0];
String notificationId = actionArray[1];
if (action.equals("Reject")) {
Vibrator rr = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
KoCall.ringToneStop();
rr.cancel();
KoCall.removeNotification(notificationId);
KoCall.resetCallVariables();
}
}
}
second one is KoCall.java:
package com.edtalk;
import static android.content.Context.NOTIFICATION_SERVICE;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.PictureInPictureParams;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.Build;
import android.os.SystemClock;
import android.os.Vibrator;
import android.provider.Settings;
import android.util.Log;
import android.util.Rational;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import java.util.Calendar;
import java.util.concurrent.TimeUnit;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.modules.storage.ReactDatabaseSupplier;
import com.reactnativecommunity.asyncstorage.AsyncLocalStorageUtil;
import com.zxcpoiu.incallmanager.InCallManagerModule;
public class KoCall extends ReactContextBaseJavaModule {
private static ReactApplicationContext reactContext;
public static final String LOG_TAG = "KO:KoCall";
//React native tarafında iletişide kullanılacak değişkenler
public static int notificationId;
private String teacher;
//25 saniyelik süreyi korumak için kullanılacak değişkenler
private long firstTime;
private long currentTime;
//Uygulamanın deeplink ismi bu alanda tutuluyor.
//Her uygulama için bu alan değişecek.
private final String deepLink = "edtalk";
//InCallManager react-native tarafında başladığında native tarafta durmuyor
//O sebeple başlatma ve durdurma işlemlerini native tarafta ara metot ile yapıyoruz.
private static InCallManagerModule inCallManager;
//AsyncStorage da bazı değerleri native tarafta güncellememiz gerekiyor.
//O sebeple bu değişkenleri tutuyoruz
private static ReactDatabaseSupplier reactDatabaseSupplier;
static final String TABLE_CATALYST = "catalystLocalStorage";
static final String KEY_COLUMN = "key";
static final String VALUE_COLUMN = "value";
//Picture-inPicture için değişkenleri tanımlıyoruz
private static final int ASPECT_WIDTH = 16;
private static final int ASPECT_HEIGHT = 9;
private static boolean isPipSupported = false;
private static boolean isCustomAspectRatioSupported = false;
private static Rational aspectRatio;
//Picture-inPicture için değişkenleri tanımlıyoruz
public KoCall(ReactApplicationContext context) {
super(context);
reactContext = context;
inCallManager = new InCallManagerModule(reactContext);
//Sürüme göre Pictur-inPicture desteklenme işlerini değişkenlere atıyoruz
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
isPipSupported = true;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
isCustomAspectRatioSupported = true;
aspectRatio = new Rational(ASPECT_WIDTH, ASPECT_HEIGHT);
}
//Sürüme göre Pictur-inPicture desteklenme işlerini değişkenlere atıyoruz
}
@Override
public String getName() {
return "KoCall";
}
//Uygulama arka plandayken arama gelirse notification çıkması için bu metot kullanılıyor
@SuppressLint("NotificationTrampoline")
@ReactMethod
private void showCallNotification(String teacher) {
this.notificationId = (int) SystemClock.uptimeMillis();
String channelId = "fcm_call_channel";
String channelName = "Incoming Call";
if (teacher != null && !teacher.isEmpty() && !teacher.equals("")) {
this.teacher = teacher;
} else {
this.teacher = "Teacher";
}
//Aktif arama başlangıçta true ya çekiliyor.
//Bu sayede uygulama açılınca kanala otomarik giriş sağlanacak
setAsyncStorageItem("activeCall", "true");
String notification_title = this.teacher + " is calling";
String packageName = reactContext.getPackageName();
Intent launchIntent = reactContext.getPackageManager().getLaunchIntentForPackage(packageName);
String className = launchIntent.getComponent().getClassName();
Class<?> activityClass;
try {
activityClass = Class.forName(className);
} catch (Exception e) {
return;
}
//NOTIFICATION BUTONLARI VE BUTONLARA BASILDIĞINDA YAPILACAK İŞLEMLER
//Open Call butonuna bastığımızda uygulamada arama ekranının açılması gerekiyor.
//Bu sebeple deeplink kullanıyoruz ve call ekranına notification Id gönderiyoruz
String openCallUri = (new StringBuilder()).append(deepLink).append("://home/").toString();
Intent openCallIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(openCallUri), reactContext, activityClass);
openCallIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
//Reject e basıldığında BroadcastRecevier ile bu durumu yakalayarak uygulamayı açmadan sesi kapatıyoruz
//AsyncStorage ile bazı verileri güncelliyoruz. Bize ActionReceiverClass bu konuda yardımcı oluyor
Intent rejectIntent = new Intent(reactContext, ActionReceiver.class);
rejectIntent.putExtra("action", "Reject:" + notificationId);
//NOTIFICATION BUTONLARI VE BUTONLARA BASILDIĞINDA YAPILACAK İŞLEMLER
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(reactContext, channelId)
.setContentTitle(notification_title)
.setContentText(channelName)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setCategory(NotificationCompat.CATEGORY_CALL)
.setAutoCancel(true)
.addAction(0, "Accept", PendingIntent.getActivity(reactContext, 0, openCallIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT))
.addAction(0, "Reject", PendingIntent.getBroadcast(reactContext, 1, rejectIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT))
.setDefaults(Notification.DEFAULT_VIBRATE)
.setSmallIcon(R.mipmap.ic_launcher);
NotificationManager notificationManager = (NotificationManager) reactContext.getSystemService(NOTIFICATION_SERVICE);
int importance = NotificationManager.IMPORTANCE_HIGH;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
NotificationChannel mChannel = new NotificationChannel(
channelId, channelName, importance);
mChannel.setDescription(channelName);
mChannel.enableLights(true);
mChannel.enableVibration(true);
notificationManager.createNotificationChannel(mChannel);
}
notificationBuilder.build().flags |= Notification.FLAG_AUTO_CANCEL;
notificationManager.notify(notificationId, notificationBuilder.build());
firstTime = Calendar.getInstance().getTimeInMillis();
}
//Eğer aramaya yanıt verilmezse missed call notification düşüyor
@ReactMethod
private void showMissedCallNotification(String teacher) {
int _notificationId = (int) SystemClock.uptimeMillis();
String channelId = "fcm_missed_call_channel";
String channelName = "Missed Call";
// 25 saniye sonra sesi durdur
// Programda kapanıyor ama herhangi bir aksilik olursa sesi ve titreşimi kapatmamız iyi olur
ringToneStop();
Vibrator rr = (Vibrator) reactContext.getSystemService(Context.VIBRATOR_SERVICE);
rr.cancel();
//Aktif arama başlangıçta true ya çekiliyor.
//Eğer arama karşılanmazsa bu alan boş olarak güncellenecek
setAsyncStorageItem("activeCall", "");
String _teacher = "";
if (teacher != null && !teacher.isEmpty() && !teacher.equals("")) {
_teacher = teacher;
} else {
_teacher = "Teacher";
}
String notification_title = "Missed call - " + _teacher;
//Missedcall notificationa basıldığında uygulamamızın açılması için bu bölümü oluşturuyoruz.
//setContentIntent ile Notification a burayı ekleyeceğiz
Intent intent = new Intent(reactContext, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(reactContext, 0, intent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_CANCEL_CURRENT);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(reactContext, channelId)
.setContentTitle(notification_title)
.setContentText(channelName)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setCategory(NotificationCompat.CATEGORY_CALL)
.setAutoCancel(true)
.setContentIntent(pendingIntent)
.setDefaults(Notification.DEFAULT_VIBRATE)
.setSmallIcon(R.mipmap.ic_launcher);
NotificationManager notificationManager = (NotificationManager) reactContext.getSystemService(NOTIFICATION_SERVICE);
int importance = NotificationManager.IMPORTANCE_HIGH;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
NotificationChannel mChannel = new NotificationChannel(
channelId, channelName, importance);
mChannel.setDescription(channelName);
mChannel.enableLights(true);
mChannel.enableVibration(true);
notificationManager.createNotificationChannel(mChannel);
}
notificationBuilder.build().flags |= Notification.FLAG_AUTO_CANCEL;
notificationManager.notify(_notificationId, notificationBuilder.build());
}
//Eğer uygulama arka planda iken arama gelirse counter bu metot aracılığı ile
//uygulamaya aktarılıyor. Buradaki amaç 25 saniye çalma süresini korumak
@ReactMethod
public void getCounter(Callback callback) {
currentTime = Calendar.getInstance().getTimeInMillis();
long counter = TimeUnit.MILLISECONDS.toSeconds(currentTime - firstTime);
callback.invoke(String.valueOf(counter));
}
//Aramaya ait notificationId bu bölümden gidiyor
@ReactMethod
public void getNotificationId(Callback callback) {
callback.invoke(notificationId);
}
//Arama notification silinmesi için bu metot kullanılyor.
@ReactMethod
public void removeCallNotification(String notificationId) {
this.notificationId = 0;
removeNotification(notificationId);
}
//Notification alma bölümünün aktif olup olmadığı kontrol ediliyor
//Eğer bu metot false dönerse react-native tarafında Linking.openSettings()
//ile uygulama ayarlarını açtıracağız
@ReactMethod
public void allowNotification(final Promise promise) {
try {
Boolean areEnabled = NotificationManagerCompat.from(reactContext).areNotificationsEnabled();
promise.resolve(areEnabled);
} catch (Exception e) {
promise.reject("Error", e.getMessage());
}
}
//IncallManager react-native tarafında başladığında native tarafta durmuyor
//Bu sebeple başlatma işlemini ara bir metot ile yapıyoruz
@ReactMethod
public void startRingTone(int seconds) {
inCallManager.startRingtone("_DEFAULT_", seconds);
}
//IncallManager react-native tarafında başladığında native tarafta durmuyor
//Bu sebeple durdurma işlemini ara bir metot ile yapıyoruz
@ReactMethod
public void stopRingTone() {
ringToneStop();
}
//Notification Id 0 lanıyor
@ReactMethod
public void resetNotificationId() {
notificationId = 0;
}
//Picture-in-Picture için kullanılan metotlar burada yer alıyor
@ReactMethod
public void enterPictureInPictureMode() {
enterPipMode();
}
//Picture-in-Picture için kullanılan metotlar burada yer alıyor
//Eğer battery optimizasyonu kısıtlanmış olarak işaretli ise arka plan bildirimleri gelmiyor
//Burada onun kontrolünü yapacağız
@ReactMethod
public void isBatteryOptEnabled(Promise promise) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
String packageName = reactContext.getPackageName();
ActivityManager activityManager = (ActivityManager) reactContext.getSystemService(Context.ACTIVITY_SERVICE);
Boolean isRestricted = activityManager.isBackgroundRestricted();
if (!isRestricted) {
promise.resolve(true);
return;
}
}
promise.resolve(false);
}
//AsyncStorage DB sinden veri okuyabilmemiz için bu metotu kullanacağız
public static String getAsyncStorageItem(String key) {
String result = "";
SQLiteDatabase db = reactDatabaseSupplier.getInstance(reactContext).getReadableDatabase();
if (db != null) {
result = AsyncLocalStorageUtil.getItemImpl(db, key);
}
return result;
}
//AsyncStorage DB sinde güncelleme yapabilmemiz için bu metotu kullanacağız
public static boolean setAsyncStorageItem(String key, String value) {
SQLiteDatabase db = reactDatabaseSupplier.getInstance(reactContext).getReadableDatabase();
ContentValues contentValues = new ContentValues();
contentValues.put(KEY_COLUMN, key);
contentValues.put(VALUE_COLUMN, value);
long inserted = db.insertWithOnConflict(
TABLE_CATALYST,
null,
contentValues,
SQLiteDatabase.CONFLICT_REPLACE);
db.close();
return (-1 != inserted);
}
//Pip mode diğer class içinde de kullanabilmek için static bir metota taşıyoruz
@RequiresApi(api = Build.VERSION_CODES.O)
public static void enterPipMode() {
if (isPipSupported) {
if (isCustomAspectRatioSupported) {
PictureInPictureParams params = new PictureInPictureParams.Builder().setAspectRatio(aspectRatio).build();
reactContext.getCurrentActivity().enterPictureInPictureMode(params);
} else
reactContext.getCurrentActivity().enterPictureInPictureMode();
}
}
//InCallManager durdurma işlemi
public static void ringToneStop() {
inCallManager.stopRingtone();
}
//InCallManager durdurma işlemi
//Notification silme işlemi. Birden fazla yerde kullanıldığı için tekrara düşmemek adına buraya alındı
public static void removeNotification(String notificationId) {
int _notificationId = Integer.parseInt(notificationId);
NotificationManager manager = (NotificationManager) reactContext.getSystemService(NOTIFICATION_SERVICE);
manager.cancel(_notificationId);
}
//Arama reddedildiğinde AsyncStorage da bazı alanları güncellememiz gerekiyor
public static void resetCallVariables() {
//Aramaya ait verileri temizliyoruz
setAsyncStorageItem("incomingCall", "");
setAsyncStorageItem("activeCall", "");
setAsyncStorageItem("appId", "");
setAsyncStorageItem("token", "");
setAsyncStorageItem("channelName", "");
setAsyncStorageItem("teacherName", "");
//setAsyncStorageItem("teacherImg", "");
setAsyncStorageItem("providerType", "");
//Arama reddedildiği için notification Id yi sıfırlıyor ve incomingCall u false a çekiyoruz
notificationId = 0;
}
}
and the finally last one is KoCallPackage.java:
package com.edtalk;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.bridge.JavaScriptModule;
public class KoCallPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Arrays.<NativeModule>asList(new KoCall(reactContext));
}
// Deprecated from RN 0.47
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}