Compare commits
182 Commits
stable
...
master-and
Author | SHA1 | Date | |
---|---|---|---|
|
9cde37c6f5 | ||
|
3d8d84f978 | ||
|
f4ae60756c | ||
|
eedc1b2860 | ||
|
24a35698dc | ||
|
7e37155938 | ||
|
c8b38183c9 | ||
|
90a866ca56 | ||
|
5da8aef794 | ||
|
9514acf6aa | ||
|
40d54f3f16 | ||
|
3249c5e463 | ||
|
e440fba582 | ||
|
43cedbce26 | ||
|
ebad009553 | ||
|
35cf113d98 | ||
|
e82d2cbea2 | ||
|
a0e056994e | ||
|
e4d8ea17fc | ||
|
e69db17804 | ||
|
9282cca396 | ||
|
ad2eaeb004 | ||
|
3f44c9af24 | ||
|
4296515473 | ||
|
88830a2e09 | ||
|
c8a269e391 | ||
|
a45897007c | ||
|
42ea1df342 | ||
|
47214e33ea | ||
|
ff239d81e5 | ||
|
0a6e47dd61 | ||
|
ee75219ed3 | ||
|
2e337a753f | ||
|
9918d91def | ||
|
82dd5751c1 | ||
|
de637fab50 | ||
|
cb49f6f01d | ||
|
6d0a83aa58 | ||
|
05f57e98ad | ||
|
6a66525927 | ||
|
9530a6055a | ||
|
f5ed8debcc | ||
|
355d2449c5 | ||
|
f7382cdd6f | ||
|
dc8d10d068 | ||
|
951245d33f | ||
|
521b901cc9 | ||
|
6311ba451b | ||
|
754c76d6fd | ||
|
aea7ff1c89 | ||
|
77ac972e09 | ||
|
256f85024f | ||
|
870f9e42dd | ||
|
0a4a3a24e1 | ||
|
8a66390a78 | ||
|
3d48eded3d | ||
|
6546426ec0 | ||
|
4a7ceb00fb | ||
|
53560378bb | ||
|
cdb3b6aafd | ||
|
9f3d3e8ba4 | ||
|
047aad592e | ||
|
087acd9180 | ||
|
0b822e4a5c | ||
|
f8a469488e | ||
|
3b5e806418 | ||
|
79e208193a | ||
|
ef5c13b1c1 | ||
|
38533213d2 | ||
|
5f1aa6fa9d | ||
|
b0002fe07d | ||
|
ef21fd1d26 | ||
|
4a311b9578 | ||
|
b8da5e225b | ||
|
f27de052cf | ||
|
5cc537f14c | ||
|
dc8ca4cf89 | ||
|
b62dd801f1 | ||
|
0c096e2c89 | ||
|
cc127e56fe | ||
|
1781495ee3 | ||
|
831231d8e6 | ||
|
45102442f4 | ||
|
f323c8e112 | ||
|
3bdc6b5e28 | ||
|
d8373262bc | ||
|
3597d34716 | ||
|
bd4259e89e | ||
|
55ead740cc | ||
|
5ef0eda2d7 | ||
|
49a9b0e7d6 | ||
|
45ada450a2 | ||
|
307a1b3c5e | ||
|
ed6b3bbead | ||
|
901610eec5 | ||
|
7d4127c51d | ||
|
13215d91d7 | ||
|
e1a8099474 | ||
|
daa8d9bb21 | ||
|
5fcbade1bc | ||
|
3937ffa9a6 | ||
|
80ddb50e1c | ||
|
f6e66f1c53 | ||
|
0c23ff9ae3 | ||
|
1570bc2b99 | ||
|
1e2104cabf | ||
|
f3014f258d | ||
|
f0991cc0ba | ||
|
74b78a8d7b | ||
|
82cd70a75c | ||
|
fe4eb7b5af | ||
|
c459e71d02 | ||
|
2516d5a393 | ||
|
477d98d75a | ||
|
4253cd7fb9 | ||
|
ca78958667 | ||
|
1f5b80d560 | ||
|
2de111e76c | ||
|
8343285d93 | ||
|
5dbe2b2745 | ||
|
fb9485190d | ||
|
6881600e06 | ||
|
9ed723bafa | ||
|
9ded1c9821 | ||
|
bb374c68b1 | ||
|
c3e82a6a4e | ||
|
7c12e82042 | ||
|
e7e66ff873 | ||
|
c4d7e5307c | ||
|
d6b9a45a39 | ||
|
7fd3b4d6ba | ||
|
4004aafbc5 | ||
|
95008eeeaf | ||
|
c7a8992043 | ||
|
ea2b5f2ccf | ||
|
ed9f277421 | ||
|
5c14c3b349 | ||
|
d8fb31f167 | ||
|
02db38ffd3 | ||
|
7692195bfa | ||
|
c435cbdc7b | ||
|
effc281271 | ||
|
41eb2e5689 | ||
|
67d74a0a27 | ||
|
f66405e79b | ||
|
74d186af16 | ||
|
187fef0c5a | ||
|
4782cab507 | ||
|
bcbee67709 | ||
|
2501cbe55d | ||
|
2bd049db87 | ||
|
6b8b9ab4fd | ||
|
30db24265e | ||
|
316d605899 | ||
|
b4257f7767 | ||
|
a3f2d5c919 | ||
|
cf46469cd5 | ||
|
0312fde818 | ||
|
9defa44f0c | ||
|
915b53054c | ||
|
f81557b4fd | ||
|
e273bd1239 | ||
|
a63caf4640 | ||
|
e7f0234134 | ||
|
340552321e | ||
|
98a3fc214d | ||
|
6a578cfe3c | ||
|
dacc075fe8 | ||
|
55418e2bc0 | ||
|
f2b5c0f3a8 | ||
|
5ebdf5dba9 | ||
|
8e045764df | ||
|
503d3d77e6 | ||
|
81bd7d97c5 | ||
|
8f57925067 | ||
|
9bf99db82e | ||
|
5615cdbf1a | ||
|
d802ae0058 | ||
|
8f2278198c | ||
|
10937a5a4e | ||
|
6aff6e9804 | ||
|
95477cae7e |
@ -164,6 +164,11 @@
|
|||||||
64466DC829FC2B3B00E3D48D /* CreateSimpleXAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64466DC729FC2B3B00E3D48D /* CreateSimpleXAddress.swift */; };
|
64466DC829FC2B3B00E3D48D /* CreateSimpleXAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64466DC729FC2B3B00E3D48D /* CreateSimpleXAddress.swift */; };
|
||||||
64466DCC29FFE3E800E3D48D /* MailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64466DCB29FFE3E800E3D48D /* MailView.swift */; };
|
64466DCC29FFE3E800E3D48D /* MailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64466DCB29FFE3E800E3D48D /* MailView.swift */; };
|
||||||
6448BBB628FA9D56000D2AB9 /* GroupLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6448BBB528FA9D56000D2AB9 /* GroupLinkView.swift */; };
|
6448BBB628FA9D56000D2AB9 /* GroupLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6448BBB528FA9D56000D2AB9 /* GroupLinkView.swift */; };
|
||||||
|
6449333A2AF8E51000AC506E /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644933352AF8E51000AC506E /* libgmpxx.a */; };
|
||||||
|
6449333B2AF8E51000AC506E /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644933362AF8E51000AC506E /* libgmp.a */; };
|
||||||
|
6449333C2AF8E51000AC506E /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644933372AF8E51000AC506E /* libffi.a */; };
|
||||||
|
6449333D2AF8E51000AC506E /* libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644933382AF8E51000AC506E /* libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8-ghc9.6.3.a */; };
|
||||||
|
6449333E2AF8E51000AC506E /* libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644933392AF8E51000AC506E /* libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8.a */; };
|
||||||
644EFFDE292BCD9D00525D5B /* ComposeVoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFDD292BCD9D00525D5B /* ComposeVoiceView.swift */; };
|
644EFFDE292BCD9D00525D5B /* ComposeVoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFDD292BCD9D00525D5B /* ComposeVoiceView.swift */; };
|
||||||
644EFFE0292CFD7F00525D5B /* CIVoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFDF292CFD7F00525D5B /* CIVoiceView.swift */; };
|
644EFFE0292CFD7F00525D5B /* CIVoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFDF292CFD7F00525D5B /* CIVoiceView.swift */; };
|
||||||
644EFFE2292D089800525D5B /* FramedCIVoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFE1292D089800525D5B /* FramedCIVoiceView.swift */; };
|
644EFFE2292D089800525D5B /* FramedCIVoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFE1292D089800525D5B /* FramedCIVoiceView.swift */; };
|
||||||
@ -450,6 +455,11 @@
|
|||||||
64466DC729FC2B3B00E3D48D /* CreateSimpleXAddress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateSimpleXAddress.swift; sourceTree = "<group>"; };
|
64466DC729FC2B3B00E3D48D /* CreateSimpleXAddress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateSimpleXAddress.swift; sourceTree = "<group>"; };
|
||||||
64466DCB29FFE3E800E3D48D /* MailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailView.swift; sourceTree = "<group>"; };
|
64466DCB29FFE3E800E3D48D /* MailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailView.swift; sourceTree = "<group>"; };
|
||||||
6448BBB528FA9D56000D2AB9 /* GroupLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupLinkView.swift; sourceTree = "<group>"; };
|
6448BBB528FA9D56000D2AB9 /* GroupLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupLinkView.swift; sourceTree = "<group>"; };
|
||||||
|
644933352AF8E51000AC506E /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||||
|
644933362AF8E51000AC506E /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||||
|
644933372AF8E51000AC506E /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||||
|
644933382AF8E51000AC506E /* libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||||
|
644933392AF8E51000AC506E /* libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.0.3-EnhmkSQK6HvJ11g1uZERg8.a"; sourceTree = "<group>"; };
|
||||||
644EFFDD292BCD9D00525D5B /* ComposeVoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeVoiceView.swift; sourceTree = "<group>"; };
|
644EFFDD292BCD9D00525D5B /* ComposeVoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeVoiceView.swift; sourceTree = "<group>"; };
|
||||||
644EFFDF292CFD7F00525D5B /* CIVoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIVoiceView.swift; sourceTree = "<group>"; };
|
644EFFDF292CFD7F00525D5B /* CIVoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIVoiceView.swift; sourceTree = "<group>"; };
|
||||||
644EFFE1292D089800525D5B /* FramedCIVoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FramedCIVoiceView.swift; sourceTree = "<group>"; };
|
644EFFE1292D089800525D5B /* FramedCIVoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FramedCIVoiceView.swift; sourceTree = "<group>"; };
|
||||||
|
@ -103,11 +103,14 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity-alias>
|
</activity-alias>
|
||||||
|
|
||||||
|
<activity android:name=".views.call.CallActivity"
|
||||||
<activity android:name=".views.call.IncomingCallActivity"
|
|
||||||
android:showOnLockScreen="true"
|
android:showOnLockScreen="true"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:launchMode="singleTask"/>
|
android:launchMode="singleInstance"
|
||||||
|
android:supportsPictureInPicture="true"
|
||||||
|
android:autoRemoveFromRecents="true"
|
||||||
|
android:screenOrientation="portrait"
|
||||||
|
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"/>
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
@ -133,6 +136,18 @@
|
|||||||
android:stopWithTask="false"></service>
|
android:stopWithTask="false"></service>
|
||||||
|
|
||||||
<!-- SimplexService restart on reboot -->
|
<!-- SimplexService restart on reboot -->
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".CallService"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false"
|
||||||
|
android:stopWithTask="false"/>
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name=".CallService$CallActionReceiver"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".SimplexService$StartReceiver"
|
android:name=".SimplexService$StartReceiver"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
|
@ -0,0 +1,176 @@
|
|||||||
|
package chat.simplex.app
|
||||||
|
|
||||||
|
import android.app.*
|
||||||
|
import android.content.*
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.os.*
|
||||||
|
import androidx.compose.ui.graphics.asAndroidBitmap
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import chat.simplex.app.model.NtfManager.EndCallAction
|
||||||
|
import chat.simplex.app.views.call.CallActivity
|
||||||
|
import chat.simplex.common.model.NotificationPreviewMode
|
||||||
|
import chat.simplex.common.platform.*
|
||||||
|
import chat.simplex.common.views.call.CallState
|
||||||
|
import chat.simplex.common.views.helpers.*
|
||||||
|
import chat.simplex.res.MR
|
||||||
|
import kotlinx.datetime.Instant
|
||||||
|
|
||||||
|
class CallService: Service() {
|
||||||
|
private var wakeLock: PowerManager.WakeLock? = null
|
||||||
|
private var notificationManager: NotificationManager? = null
|
||||||
|
private var serviceNotification: Notification? = null
|
||||||
|
|
||||||
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
Log.d(TAG, "onStartCommand startId: $startId")
|
||||||
|
if (intent != null) {
|
||||||
|
val action = intent.action
|
||||||
|
Log.d(TAG, "intent action $action")
|
||||||
|
when (action) {
|
||||||
|
Action.START.name -> startService()
|
||||||
|
else -> Log.e(TAG, "No action in the intent")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "null intent. Probably restarted by the system.")
|
||||||
|
}
|
||||||
|
startForeground(CALL_SERVICE_ID, serviceNotification)
|
||||||
|
return START_STICKY
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
Log.d(TAG, "Call service created")
|
||||||
|
notificationManager = createNotificationChannel()
|
||||||
|
updateNotification()
|
||||||
|
startForeground(CALL_SERVICE_ID, serviceNotification)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
Log.d(TAG, "Call service destroyed")
|
||||||
|
try {
|
||||||
|
wakeLock?.let {
|
||||||
|
while (it.isHeld) it.release() // release all, in case acquired more than once
|
||||||
|
}
|
||||||
|
wakeLock = null
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.d(TAG, "Exception while releasing wakelock: ${e.message}")
|
||||||
|
}
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startService() {
|
||||||
|
Log.d(TAG, "CallService startService")
|
||||||
|
if (wakeLock != null) return
|
||||||
|
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).run {
|
||||||
|
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKE_LOCK_TAG).apply {
|
||||||
|
acquire()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateNotification() {
|
||||||
|
val call = chatModel.activeCall.value
|
||||||
|
val previewMode = appPreferences.notificationPreviewMode.get()
|
||||||
|
val title = if (previewMode == NotificationPreviewMode.HIDDEN.name)
|
||||||
|
generalGetString(MR.strings.notification_preview_somebody)
|
||||||
|
else
|
||||||
|
call?.contact?.profile?.displayName ?: ""
|
||||||
|
val text = generalGetString(if (call?.supportsVideo() == true) MR.strings.call_service_notification_video_call else MR.strings.call_service_notification_audio_call)
|
||||||
|
val image = call?.contact?.image
|
||||||
|
val largeIcon = if (image == null || previewMode == NotificationPreviewMode.HIDDEN.name)
|
||||||
|
BitmapFactory.decodeResource(resources, R.drawable.icon)
|
||||||
|
else
|
||||||
|
base64ToBitmap(image).asAndroidBitmap()
|
||||||
|
|
||||||
|
serviceNotification = createNotification(title, text, largeIcon, call?.connectedAt)
|
||||||
|
startForeground(CALL_SERVICE_ID, serviceNotification)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createNotificationChannel(): NotificationManager? {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
val channel = NotificationChannel(CALL_NOTIFICATION_CHANNEL_ID, CALL_NOTIFICATION_CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT)
|
||||||
|
notificationManager.createNotificationChannel(channel)
|
||||||
|
return notificationManager
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createNotification(title: String, text: String, icon: Bitmap, connectedAt: Instant? = null): Notification {
|
||||||
|
val pendingIntent: PendingIntent = Intent(this, CallActivity::class.java).let { notificationIntent ->
|
||||||
|
PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE)
|
||||||
|
}
|
||||||
|
|
||||||
|
val endCallPendingIntent: PendingIntent = Intent(this, CallActionReceiver::class.java).let { notificationIntent ->
|
||||||
|
notificationIntent.setAction(EndCallAction)
|
||||||
|
PendingIntent.getBroadcast(this, 1, notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
||||||
|
}
|
||||||
|
|
||||||
|
val builder = NotificationCompat.Builder(this, CALL_NOTIFICATION_CHANNEL_ID)
|
||||||
|
.setSmallIcon(R.drawable.ntf_icon)
|
||||||
|
.setLargeIcon(icon.clipToCircle())
|
||||||
|
.setColor(0x88FFFF)
|
||||||
|
.setContentTitle(title)
|
||||||
|
.setContentText(text)
|
||||||
|
.setContentIntent(pendingIntent)
|
||||||
|
.setSilent(true)
|
||||||
|
.addAction(R.drawable.ntf_icon, generalGetString(MR.strings.call_service_notification_end_call), endCallPendingIntent)
|
||||||
|
if (connectedAt != null) {
|
||||||
|
builder.setUsesChronometer(true)
|
||||||
|
builder.setWhen(connectedAt.epochSeconds * 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBind(intent: Intent): IBinder {
|
||||||
|
return CallServiceBinder()
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class CallServiceBinder : Binder() {
|
||||||
|
fun getService() = this@CallService
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class Action {
|
||||||
|
START,
|
||||||
|
}
|
||||||
|
|
||||||
|
class CallActionReceiver: BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
|
when (intent?.action) {
|
||||||
|
EndCallAction -> {
|
||||||
|
val call = chatModel.activeCall.value
|
||||||
|
if (call != null) {
|
||||||
|
withBGApi {
|
||||||
|
chatModel.callManager.endCall(call)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
Log.e(TAG, "Unknown action. Make sure you provided an action")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = "CALL_SERVICE"
|
||||||
|
const val CALL_NOTIFICATION_CHANNEL_ID = "chat.simplex.app.CALL_SERVICE_NOTIFICATION"
|
||||||
|
const val CALL_NOTIFICATION_CHANNEL_NAME = "SimpleX Chat call service"
|
||||||
|
const val CALL_SERVICE_ID = 6788
|
||||||
|
const val WAKE_LOCK_TAG = "CallService::lock"
|
||||||
|
|
||||||
|
fun startService(): Intent {
|
||||||
|
Log.d(TAG, "CallService start")
|
||||||
|
return Intent(androidAppContext, CallService::class.java).also {
|
||||||
|
it.action = Action.START.name
|
||||||
|
ContextCompat.startForegroundService(androidAppContext, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stopService() {
|
||||||
|
androidAppContext.stopService(Intent(androidAppContext, CallService::class.java))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +1,15 @@
|
|||||||
package chat.simplex.app
|
package chat.simplex.app
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.*
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.compose.ui.platform.ClipboardManager
|
|
||||||
import chat.simplex.common.platform.Log
|
import chat.simplex.common.platform.Log
|
||||||
import android.app.UiModeManager
|
import android.content.Intent
|
||||||
import android.os.*
|
import android.os.*
|
||||||
import androidx.lifecycle.*
|
import androidx.lifecycle.*
|
||||||
import androidx.work.*
|
import androidx.work.*
|
||||||
import chat.simplex.app.model.NtfManager
|
import chat.simplex.app.model.NtfManager
|
||||||
|
import chat.simplex.app.model.NtfManager.AcceptCallAction
|
||||||
|
import chat.simplex.app.views.call.CallActivity
|
||||||
import chat.simplex.common.helpers.APPLICATION_ID
|
import chat.simplex.common.helpers.APPLICATION_ID
|
||||||
import chat.simplex.common.helpers.requiresIgnoringBattery
|
import chat.simplex.common.helpers.requiresIgnoringBattery
|
||||||
import chat.simplex.common.model.*
|
import chat.simplex.common.model.*
|
||||||
@ -18,6 +19,7 @@ import chat.simplex.common.platform.*
|
|||||||
import chat.simplex.common.ui.theme.CurrentColors
|
import chat.simplex.common.ui.theme.CurrentColors
|
||||||
import chat.simplex.common.ui.theme.DefaultTheme
|
import chat.simplex.common.ui.theme.DefaultTheme
|
||||||
import chat.simplex.common.views.call.RcvCallInvitation
|
import chat.simplex.common.views.call.RcvCallInvitation
|
||||||
|
import chat.simplex.common.views.call.activeCallDestroyWebView
|
||||||
import chat.simplex.common.views.helpers.*
|
import chat.simplex.common.views.helpers.*
|
||||||
import chat.simplex.common.views.onboarding.OnboardingStage
|
import chat.simplex.common.views.onboarding.OnboardingStage
|
||||||
import com.jakewharton.processphoenix.ProcessPhoenix
|
import com.jakewharton.processphoenix.ProcessPhoenix
|
||||||
@ -184,6 +186,10 @@ class SimplexApp: Application(), LifecycleEventObserver {
|
|||||||
SimplexService.safeStopService()
|
SimplexService.safeStopService()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun androidCallServiceSafeStop() {
|
||||||
|
CallService.stopService()
|
||||||
|
}
|
||||||
|
|
||||||
override fun androidNotificationsModeChanged(mode: NotificationsMode) {
|
override fun androidNotificationsModeChanged(mode: NotificationsMode) {
|
||||||
if (mode.requiresIgnoringBattery && !SimplexService.isBackgroundAllowed()) {
|
if (mode.requiresIgnoringBattery && !SimplexService.isBackgroundAllowed()) {
|
||||||
appPrefs.backgroundServiceNoticeShown.set(false)
|
appPrefs.backgroundServiceNoticeShown.set(false)
|
||||||
@ -254,6 +260,28 @@ class SimplexApp: Application(), LifecycleEventObserver {
|
|||||||
uiModeManager.setApplicationNightMode(mode)
|
uiModeManager.setApplicationNightMode(mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun androidStartCallActivity(acceptCall: Boolean, remoteHostId: Long?, chatId: ChatId?) {
|
||||||
|
val context = mainActivity.get() ?: return
|
||||||
|
val intent = Intent(context, CallActivity::class.java)
|
||||||
|
.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
|
||||||
|
if (acceptCall) {
|
||||||
|
intent.setAction(AcceptCallAction)
|
||||||
|
.putExtra("remoteHostId", remoteHostId)
|
||||||
|
.putExtra("chatId", chatId)
|
||||||
|
}
|
||||||
|
intent.flags += Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun androidPictureInPictureAllowed(): Boolean {
|
||||||
|
val appOps = androidAppContext.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
|
||||||
|
return appOps.checkOpNoThrow(AppOpsManager.OPSTR_PICTURE_IN_PICTURE, Process.myUid(), packageName) == AppOpsManager.MODE_ALLOWED
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun androidCallEnded() {
|
||||||
|
activeCallDestroyWebView()
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun androidAskToAllowBackgroundCalls(): Boolean {
|
override suspend fun androidAskToAllowBackgroundCalls(): Boolean {
|
||||||
if (SimplexService.isBackgroundRestricted()) {
|
if (SimplexService.isBackgroundRestricted()) {
|
||||||
val userChoice: CompletableDeferred<Boolean> = CompletableDeferred()
|
val userChoice: CompletableDeferred<Boolean> = CompletableDeferred()
|
||||||
|
@ -34,12 +34,13 @@ import kotlin.system.exitProcess
|
|||||||
|
|
||||||
class SimplexService: Service() {
|
class SimplexService: Service() {
|
||||||
private var wakeLock: PowerManager.WakeLock? = null
|
private var wakeLock: PowerManager.WakeLock? = null
|
||||||
private var isStartingService = false
|
private var isCheckingNewMessages = false
|
||||||
private var notificationManager: NotificationManager? = null
|
private var notificationManager: NotificationManager? = null
|
||||||
private var serviceNotification: Notification? = null
|
private var serviceNotification: Notification? = null
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
Log.d(TAG, "onStartCommand startId: $startId")
|
Log.d(TAG, "onStartCommand startId: $startId")
|
||||||
|
isServiceStarting = false
|
||||||
if (intent != null) {
|
if (intent != null) {
|
||||||
val action = intent.action
|
val action = intent.action
|
||||||
Log.d(TAG, "intent action $action")
|
Log.d(TAG, "intent action $action")
|
||||||
@ -71,6 +72,7 @@ class SimplexService: Service() {
|
|||||||
stopForeground(true)
|
stopForeground(true)
|
||||||
stopSelf()
|
stopSelf()
|
||||||
} else {
|
} else {
|
||||||
|
isServiceStarting = false
|
||||||
isServiceStarted = true
|
isServiceStarted = true
|
||||||
// In case of self-destruct is enabled the initialization process will not start in SimplexApp, Let's start it here
|
// In case of self-destruct is enabled the initialization process will not start in SimplexApp, Let's start it here
|
||||||
if (DatabaseUtils.ksSelfDestructPassword.get() != null && chatModel.chatDbStatus.value == null) {
|
if (DatabaseUtils.ksSelfDestructPassword.get() != null && chatModel.chatDbStatus.value == null) {
|
||||||
@ -89,6 +91,7 @@ class SimplexService: Service() {
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.d(TAG, "Exception while releasing wakelock: ${e.message}")
|
Log.d(TAG, "Exception while releasing wakelock: ${e.message}")
|
||||||
}
|
}
|
||||||
|
isServiceStarting = false
|
||||||
isServiceStarted = false
|
isServiceStarted = false
|
||||||
stopAfterStart = false
|
stopAfterStart = false
|
||||||
saveServiceState(this, ServiceState.STOPPED)
|
saveServiceState(this, ServiceState.STOPPED)
|
||||||
@ -101,9 +104,9 @@ class SimplexService: Service() {
|
|||||||
|
|
||||||
private fun startService() {
|
private fun startService() {
|
||||||
Log.d(TAG, "SimplexService startService")
|
Log.d(TAG, "SimplexService startService")
|
||||||
if (wakeLock != null || isStartingService) return
|
if (wakeLock != null || isCheckingNewMessages) return
|
||||||
val self = this
|
val self = this
|
||||||
isStartingService = true
|
isCheckingNewMessages = true
|
||||||
withLongRunningApi {
|
withLongRunningApi {
|
||||||
val chatController = ChatController
|
val chatController = ChatController
|
||||||
waitDbMigrationEnds(chatController)
|
waitDbMigrationEnds(chatController)
|
||||||
@ -123,7 +126,7 @@ class SimplexService: Service() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
isStartingService = false
|
isCheckingNewMessages = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -262,6 +265,7 @@ class SimplexService: Service() {
|
|||||||
private const val SHARED_PREFS_SERVICE_STATE = "SIMPLEX_SERVICE_STATE"
|
private const val SHARED_PREFS_SERVICE_STATE = "SIMPLEX_SERVICE_STATE"
|
||||||
private const val WORK_NAME_ONCE = "ServiceStartWorkerOnce"
|
private const val WORK_NAME_ONCE = "ServiceStartWorkerOnce"
|
||||||
|
|
||||||
|
var isServiceStarting = false
|
||||||
var isServiceStarted = false
|
var isServiceStarted = false
|
||||||
private var stopAfterStart = false
|
private var stopAfterStart = false
|
||||||
|
|
||||||
@ -281,7 +285,7 @@ class SimplexService: Service() {
|
|||||||
fun safeStopService() {
|
fun safeStopService() {
|
||||||
if (isServiceStarted) {
|
if (isServiceStarted) {
|
||||||
androidAppContext.stopService(Intent(androidAppContext, SimplexService::class.java))
|
androidAppContext.stopService(Intent(androidAppContext, SimplexService::class.java))
|
||||||
} else {
|
} else if (isServiceStarting) {
|
||||||
stopAfterStart = true
|
stopAfterStart = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -291,6 +295,7 @@ class SimplexService: Service() {
|
|||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
Intent(androidAppContext, SimplexService::class.java).also {
|
Intent(androidAppContext, SimplexService::class.java).also {
|
||||||
it.action = action.name
|
it.action = action.name
|
||||||
|
isServiceStarting = true
|
||||||
ContextCompat.startForegroundService(androidAppContext, it)
|
ContextCompat.startForegroundService(androidAppContext, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ import androidx.compose.ui.graphics.asAndroidBitmap
|
|||||||
import androidx.core.app.*
|
import androidx.core.app.*
|
||||||
import chat.simplex.app.*
|
import chat.simplex.app.*
|
||||||
import chat.simplex.app.TAG
|
import chat.simplex.app.TAG
|
||||||
import chat.simplex.app.views.call.IncomingCallActivity
|
import chat.simplex.app.views.call.CallActivity
|
||||||
import chat.simplex.app.views.call.getKeyguardManager
|
import chat.simplex.app.views.call.getKeyguardManager
|
||||||
import chat.simplex.common.views.helpers.*
|
import chat.simplex.common.views.helpers.*
|
||||||
import chat.simplex.common.model.*
|
import chat.simplex.common.model.*
|
||||||
@ -33,6 +33,7 @@ object NtfManager {
|
|||||||
const val CallChannel: String = "chat.simplex.app.CALL_NOTIFICATION_2"
|
const val CallChannel: String = "chat.simplex.app.CALL_NOTIFICATION_2"
|
||||||
const val AcceptCallAction: String = "chat.simplex.app.ACCEPT_CALL"
|
const val AcceptCallAction: String = "chat.simplex.app.ACCEPT_CALL"
|
||||||
const val RejectCallAction: String = "chat.simplex.app.REJECT_CALL"
|
const val RejectCallAction: String = "chat.simplex.app.REJECT_CALL"
|
||||||
|
const val EndCallAction: String = "chat.simplex.app.END_CALL"
|
||||||
const val CallNotificationId: Int = -1
|
const val CallNotificationId: Int = -1
|
||||||
private const val UserIdKey: String = "userId"
|
private const val UserIdKey: String = "userId"
|
||||||
private const val ChatIdKey: String = "chatId"
|
private const val ChatIdKey: String = "chatId"
|
||||||
@ -157,7 +158,7 @@ object NtfManager {
|
|||||||
val screenOff = displayManager.displays.all { it.state != Display.STATE_ON }
|
val screenOff = displayManager.displays.all { it.state != Display.STATE_ON }
|
||||||
var ntfBuilder =
|
var ntfBuilder =
|
||||||
if ((keyguardManager.isKeyguardLocked || screenOff) && appPreferences.callOnLockScreen.get() != CallOnLockScreen.DISABLE) {
|
if ((keyguardManager.isKeyguardLocked || screenOff) && appPreferences.callOnLockScreen.get() != CallOnLockScreen.DISABLE) {
|
||||||
val fullScreenIntent = Intent(context, IncomingCallActivity::class.java)
|
val fullScreenIntent = Intent(context, CallActivity::class.java)
|
||||||
val fullScreenPendingIntent = PendingIntent.getActivity(context, 0, fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
val fullScreenPendingIntent = PendingIntent.getActivity(context, 0, fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
||||||
NotificationCompat.Builder(context, CallChannel)
|
NotificationCompat.Builder(context, CallChannel)
|
||||||
.setFullScreenIntent(fullScreenPendingIntent, true)
|
.setFullScreenIntent(fullScreenPendingIntent, true)
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
package chat.simplex.app.views.call
|
package chat.simplex.app.views.call
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.*
|
||||||
import android.app.KeyguardManager
|
import android.content.*
|
||||||
import android.content.Context
|
import android.content.res.Configuration
|
||||||
import android.content.Intent
|
import android.graphics.Rect
|
||||||
import android.os.Build
|
import android.os.*
|
||||||
import android.os.Bundle
|
import android.util.Rational
|
||||||
import chat.simplex.common.platform.Log
|
import android.view.*
|
||||||
import android.view.WindowManager
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.trackPipAnimationHintView
|
||||||
import androidx.compose.desktop.ui.tooling.preview.Preview
|
import androidx.compose.desktop.ui.tooling.preview.Preview
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
@ -22,33 +23,115 @@ import androidx.compose.ui.draw.scale
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.painter.Painter
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
import chat.simplex.app.*
|
import chat.simplex.app.*
|
||||||
import chat.simplex.app.R
|
import chat.simplex.app.R
|
||||||
|
import chat.simplex.app.TAG
|
||||||
|
import chat.simplex.app.model.NtfManager
|
||||||
|
import chat.simplex.app.model.NtfManager.AcceptCallAction
|
||||||
import chat.simplex.common.model.*
|
import chat.simplex.common.model.*
|
||||||
import chat.simplex.app.model.NtfManager.OpenChatAction
|
import chat.simplex.common.platform.*
|
||||||
import chat.simplex.common.platform.ntfManager
|
|
||||||
import chat.simplex.common.ui.theme.*
|
import chat.simplex.common.ui.theme.*
|
||||||
import chat.simplex.common.views.call.*
|
import chat.simplex.common.views.call.*
|
||||||
import chat.simplex.common.views.helpers.*
|
import chat.simplex.common.views.helpers.*
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
import dev.icerock.moko.resources.compose.stringResource
|
import dev.icerock.moko.resources.compose.stringResource
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.datetime.Clock
|
import kotlinx.datetime.Clock
|
||||||
|
import java.lang.ref.WeakReference
|
||||||
|
import chat.simplex.common.platform.chatModel as m
|
||||||
|
|
||||||
class IncomingCallActivity: ComponentActivity() {
|
class CallActivity: ComponentActivity(), ServiceConnection {
|
||||||
|
|
||||||
|
var boundService: CallService? = null
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContent { IncomingCallActivityView(ChatModel) }
|
callActivity = WeakReference(this)
|
||||||
unlockForIncomingCall()
|
when (intent?.action) {
|
||||||
|
AcceptCallAction -> {
|
||||||
|
val remoteHostId = intent.getLongExtra("remoteHostId", -1).takeIf { it != -1L }
|
||||||
|
val chatId = intent.getStringExtra("chatId")
|
||||||
|
val invitation = (m.callInvitations.values + m.activeCallInvitation.value).lastOrNull {
|
||||||
|
it?.remoteHostId == remoteHostId && it?.contact?.id == chatId
|
||||||
|
}
|
||||||
|
if (invitation != null) {
|
||||||
|
m.callManager.acceptIncomingCall(invitation = invitation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setContent { CallActivityView() }
|
||||||
|
|
||||||
|
if (isOnLockScreenNow()) {
|
||||||
|
unlockForIncomingCall()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
lockAfterIncomingCall()
|
if (isOnLockScreenNow()) {
|
||||||
|
lockAfterIncomingCall()
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
unbindService(this)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.i(TAG, "Unable to unbind service: " + e.stackTraceToString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isOnLockScreenNow() = getKeyguardManager(this).isKeyguardLocked
|
||||||
|
|
||||||
|
fun setPipParams(video: Boolean, sourceRectHint: Rect? = null, viewRatio: Rational? = null) {
|
||||||
|
// By manually specifying source rect we exclude empty background while toggling PiP
|
||||||
|
val builder = PictureInPictureParams.Builder()
|
||||||
|
.setAspectRatio(viewRatio)
|
||||||
|
.setSourceRectHint(sourceRectHint)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
builder.setAutoEnterEnabled(video)
|
||||||
|
}
|
||||||
|
setPictureInPictureParams(builder.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration) {
|
||||||
|
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
|
||||||
|
m.activeCallViewIsCollapsed.value = isInPictureInPictureMode
|
||||||
|
val layoutType = if (!isInPictureInPictureMode) {
|
||||||
|
LayoutType.Default
|
||||||
|
} else {
|
||||||
|
LayoutType.RemoteVideo
|
||||||
|
}
|
||||||
|
m.callCommand.add(WCallCommand.Layout(layoutType))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBackPressed() {
|
||||||
|
if (isOnLockScreenNow()) {
|
||||||
|
super.onBackPressed()
|
||||||
|
} else {
|
||||||
|
m.activeCallViewIsCollapsed.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPictureInPictureRequested(): Boolean {
|
||||||
|
Log.d(TAG, "Requested picture-in-picture from the system")
|
||||||
|
return super.onPictureInPictureRequested()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUserLeaveHint() {
|
||||||
|
// On Android 12+ PiP is enabled automatically when a user hides the app
|
||||||
|
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R && callSupportsVideo() && platform.androidPictureInPictureAllowed()) {
|
||||||
|
enterPictureInPictureMode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
m.activeCallViewIsCollapsed.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun unlockForIncomingCall() {
|
private fun unlockForIncomingCall() {
|
||||||
@ -72,6 +155,23 @@ class IncomingCallActivity: ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun startServiceAndBind() {
|
||||||
|
/**
|
||||||
|
* On Android 12 there is a bug that prevents starting activity after pressing back button
|
||||||
|
* (the error says that it denies to start activity in background).
|
||||||
|
* Workaround is to bind to a service
|
||||||
|
* */
|
||||||
|
bindService(CallService.startService(), this, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||||
|
boundService = (service as CallService.CallServiceBinder).getService()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServiceDisconnected(name: ComponentName?) {
|
||||||
|
boundService = null
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val activityFlags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
|
const val activityFlags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
|
||||||
}
|
}
|
||||||
@ -80,38 +180,96 @@ class IncomingCallActivity: ComponentActivity() {
|
|||||||
fun getKeyguardManager(context: Context): KeyguardManager =
|
fun getKeyguardManager(context: Context): KeyguardManager =
|
||||||
context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
|
context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
|
||||||
|
|
||||||
|
private fun callSupportsVideo() = m.activeCall.value?.supportsVideo() == true || m.activeCallInvitation.value?.callType?.media == CallMediaType.Video
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun IncomingCallActivityView(m: ChatModel) {
|
fun CallActivityView() {
|
||||||
val switchingCall = m.switchingCall.value
|
val switchingCall = m.switchingCall.value
|
||||||
val invitation = m.activeCallInvitation.value
|
val invitation = m.activeCallInvitation.value
|
||||||
val call = m.activeCall.value
|
val call = remember { m.activeCall }.value
|
||||||
val showCallView = m.showCallView.value
|
val showCallView = m.showCallView.value
|
||||||
val activity = LocalContext.current as Activity
|
val activity = LocalContext.current as CallActivity
|
||||||
LaunchedEffect(invitation, call, switchingCall, showCallView) {
|
LaunchedEffect(Unit) {
|
||||||
if (!switchingCall && invitation == null && (!showCallView || call == null)) {
|
snapshotFlow { m.activeCallViewIsCollapsed.value }
|
||||||
Log.d(TAG, "IncomingCallActivityView: finishing activity")
|
.collect { collapsed ->
|
||||||
activity.finish()
|
when {
|
||||||
}
|
collapsed -> {
|
||||||
|
if (!platform.androidPictureInPictureAllowed() || !callSupportsVideo()) {
|
||||||
|
activity.moveTaskToBack(true)
|
||||||
|
activity.startActivity(Intent(activity, MainActivity::class.java))
|
||||||
|
} else if (!activity.isInPictureInPictureMode && activity.lifecycle.currentState == Lifecycle.State.RESUMED) {
|
||||||
|
// User pressed back button, show MainActivity
|
||||||
|
activity.startActivity(Intent(activity, MainActivity::class.java))
|
||||||
|
activity.enterPictureInPictureMode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
callSupportsVideo() && !platform.androidPictureInPictureAllowed() -> {
|
||||||
|
// PiP disabled by user
|
||||||
|
platform.androidStartCallActivity(false)
|
||||||
|
}
|
||||||
|
activity.isInPictureInPictureMode -> {
|
||||||
|
platform.androidStartCallActivity(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
SimpleXTheme {
|
SimpleXTheme {
|
||||||
Surface(
|
var prevCall by remember { mutableStateOf(call) }
|
||||||
Modifier
|
KeyChangeEffect(m.activeCall.value) {
|
||||||
.fillMaxSize(),
|
if (m.activeCall.value != null) {
|
||||||
color = MaterialTheme.colors.background,
|
prevCall = m.activeCall.value
|
||||||
contentColor = LocalContentColor.current
|
activity.boundService?.updateNotification()
|
||||||
) {
|
}
|
||||||
if (showCallView) {
|
}
|
||||||
Box {
|
Box(Modifier.background(Color.Black)) {
|
||||||
ActiveCallView()
|
if (call != null) {
|
||||||
if (invitation != null) IncomingCallAlertView(invitation, m)
|
val view = LocalView.current
|
||||||
|
ActiveCallView()
|
||||||
|
if (callSupportsVideo()) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
scope.launch {
|
||||||
|
activity.setPipParams(callSupportsVideo(), viewRatio = Rational(view.width, view.height))
|
||||||
|
activity.trackPipAnimationHintView(view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (prevCall != null) {
|
||||||
|
prevCall?.let { ActiveCallOverlayDisabled(it) }
|
||||||
|
}
|
||||||
|
if (invitation != null) {
|
||||||
|
if (call == null) {
|
||||||
|
Surface(
|
||||||
|
Modifier
|
||||||
|
.fillMaxSize(),
|
||||||
|
color = MaterialTheme.colors.background,
|
||||||
|
contentColor = LocalContentColor.current
|
||||||
|
) {
|
||||||
|
IncomingCallLockScreenAlert(invitation, m)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
IncomingCallAlertView(invitation, m)
|
||||||
}
|
}
|
||||||
} else if (invitation != null) {
|
|
||||||
IncomingCallLockScreenAlert(invitation, m)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
LaunchedEffect(call == null) {
|
||||||
|
if (call != null) {
|
||||||
|
activity.startServiceAndBind()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LaunchedEffect(invitation, call, switchingCall, showCallView) {
|
||||||
|
if (!switchingCall && invitation == null && (!showCallView || call == null)) {
|
||||||
|
Log.d(TAG, "CallActivityView: finishing activity")
|
||||||
|
activity.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Related to lockscreen
|
||||||
|
* */
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun IncomingCallLockScreenAlert(invitation: RcvCallInvitation, chatModel: ChatModel) {
|
fun IncomingCallLockScreenAlert(invitation: RcvCallInvitation, chatModel: ChatModel) {
|
||||||
val cm = chatModel.callManager
|
val cm = chatModel.callManager
|
||||||
@ -135,7 +293,7 @@ fun IncomingCallLockScreenAlert(invitation: RcvCallInvitation, chatModel: ChatMo
|
|||||||
acceptCall = { cm.acceptIncomingCall(invitation = invitation) },
|
acceptCall = { cm.acceptIncomingCall(invitation = invitation) },
|
||||||
openApp = {
|
openApp = {
|
||||||
val intent = Intent(context, MainActivity::class.java)
|
val intent = Intent(context, MainActivity::class.java)
|
||||||
.setAction(OpenChatAction)
|
.setAction(NtfManager.OpenChatAction)
|
||||||
.putExtra("userId", invitation.user.userId)
|
.putExtra("userId", invitation.user.userId)
|
||||||
.putExtra("chatId", invitation.contact.id)
|
.putExtra("chatId", invitation.contact.id)
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
@ -4,6 +4,7 @@ import android.annotation.SuppressLint
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.LocalServerSocket
|
import android.net.LocalServerSocket
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import chat.simplex.common.*
|
import chat.simplex.common.*
|
||||||
import chat.simplex.common.platform.*
|
import chat.simplex.common.platform.*
|
||||||
@ -25,7 +26,8 @@ val defaultLocale: Locale = Locale.getDefault()
|
|||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
lateinit var androidAppContext: Context
|
lateinit var androidAppContext: Context
|
||||||
lateinit var mainActivity: WeakReference<FragmentActivity>
|
var mainActivity: WeakReference<FragmentActivity> = WeakReference(null)
|
||||||
|
var callActivity: WeakReference<ComponentActivity> = WeakReference(null)
|
||||||
|
|
||||||
fun initHaskell() {
|
fun initHaskell() {
|
||||||
val socketName = "chat.simplex.app.local.socket.address.listen.native.cmd2" + Random.nextLong(100000)
|
val socketName = "chat.simplex.app.local.socket.address.listen.native.cmd2" + Random.nextLong(100000)
|
||||||
|
@ -61,6 +61,16 @@ actual fun cropToSquare(image: ImageBitmap): ImageBitmap {
|
|||||||
return Bitmap.createBitmap(image.asAndroidBitmap(), xOffset, yOffset, side, side).asImageBitmap()
|
return Bitmap.createBitmap(image.asAndroidBitmap(), xOffset, yOffset, side, side).asImageBitmap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Bitmap.clipToCircle(): Bitmap {
|
||||||
|
val circle = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||||
|
val path = android.graphics.Path()
|
||||||
|
path.addCircle(width / 2f, height / 2f, min(width, height) / 2f, android.graphics.Path.Direction.CCW)
|
||||||
|
val canvas = android.graphics.Canvas(circle)
|
||||||
|
canvas.clipPath(path)
|
||||||
|
canvas.drawBitmap(this, 0f, 0f, null)
|
||||||
|
return circle
|
||||||
|
}
|
||||||
|
|
||||||
actual fun compressImageStr(bitmap: ImageBitmap): String {
|
actual fun compressImageStr(bitmap: ImageBitmap): String {
|
||||||
val usePng = bitmap.hasAlpha()
|
val usePng = bitmap.hasAlpha()
|
||||||
val ext = if (usePng) "png" else "jpg"
|
val ext = if (usePng) "png" else "jpg"
|
||||||
|
@ -28,6 +28,7 @@ import androidx.compose.ui.platform.LocalLifecycleOwner
|
|||||||
import dev.icerock.moko.resources.compose.painterResource
|
import dev.icerock.moko.resources.compose.painterResource
|
||||||
import dev.icerock.moko.resources.compose.stringResource
|
import dev.icerock.moko.resources.compose.stringResource
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
@ -50,20 +51,30 @@ import kotlinx.datetime.Clock
|
|||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
|
|
||||||
|
// Should be destroy()'ed and set as null when call is ended. Otherwise, it will be a leak
|
||||||
|
@SuppressLint("StaticFieldLeak")
|
||||||
|
private var staticWebView: WebView? = null
|
||||||
|
|
||||||
|
// WebView methods must be called on Main thread
|
||||||
|
fun activeCallDestroyWebView() = withApi {
|
||||||
|
// Stop it when call ended
|
||||||
|
platform.androidCallServiceSafeStop()
|
||||||
|
staticWebView?.destroy()
|
||||||
|
staticWebView = null
|
||||||
|
Log.d(TAG, "CallView: webview was destroyed")
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("SourceLockedOrientationActivity")
|
@SuppressLint("SourceLockedOrientationActivity")
|
||||||
@Composable
|
@Composable
|
||||||
actual fun ActiveCallView() {
|
actual fun ActiveCallView() {
|
||||||
val chatModel = ChatModel
|
|
||||||
BackHandler(onBack = {
|
|
||||||
val call = chatModel.activeCall.value
|
|
||||||
if (call != null) withBGApi { chatModel.callManager.endCall(call) }
|
|
||||||
})
|
|
||||||
val audioViaBluetooth = rememberSaveable { mutableStateOf(false) }
|
val audioViaBluetooth = rememberSaveable { mutableStateOf(false) }
|
||||||
val ntfModeService = remember { chatModel.controller.appPrefs.notificationsMode.get() == NotificationsMode.SERVICE }
|
val proximityLock = remember {
|
||||||
LaunchedEffect(Unit) {
|
val pm = (androidAppContext.getSystemService(Context.POWER_SERVICE) as PowerManager)
|
||||||
// Start service when call happening since it's not already started.
|
if (pm.isWakeLockLevelSupported(PROXIMITY_SCREEN_OFF_WAKE_LOCK)) {
|
||||||
// It's needed to prevent Android from shutting down a microphone after a minute or so when screen is off
|
pm.newWakeLock(PROXIMITY_SCREEN_OFF_WAKE_LOCK, androidAppContext.packageName + ":proximityLock")
|
||||||
if (!ntfModeService) platform.androidServiceStart()
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
DisposableEffect(Unit) {
|
DisposableEffect(Unit) {
|
||||||
val am = androidAppContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
val am = androidAppContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||||
@ -93,22 +104,24 @@ actual fun ActiveCallView() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
am.registerAudioDeviceCallback(audioCallback, null)
|
am.registerAudioDeviceCallback(audioCallback, null)
|
||||||
val pm = (androidAppContext.getSystemService(Context.POWER_SERVICE) as PowerManager)
|
|
||||||
val proximityLock = if (pm.isWakeLockLevelSupported(PROXIMITY_SCREEN_OFF_WAKE_LOCK)) {
|
|
||||||
pm.newWakeLock(PROXIMITY_SCREEN_OFF_WAKE_LOCK, androidAppContext.packageName + ":proximityLock")
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
proximityLock?.acquire()
|
|
||||||
onDispose {
|
onDispose {
|
||||||
// Stop it when call ended
|
|
||||||
if (!ntfModeService) platform.androidServiceSafeStop()
|
|
||||||
dropAudioManagerOverrides()
|
dropAudioManagerOverrides()
|
||||||
am.unregisterAudioDeviceCallback(audioCallback)
|
am.unregisterAudioDeviceCallback(audioCallback)
|
||||||
proximityLock?.release()
|
if (proximityLock?.isHeld == true) {
|
||||||
|
proximityLock.release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LaunchedEffect(chatModel.activeCallViewIsCollapsed.value) {
|
||||||
|
if (chatModel.activeCallViewIsCollapsed.value) {
|
||||||
|
if (proximityLock?.isHeld == true) proximityLock.release()
|
||||||
|
} else {
|
||||||
|
delay(1000)
|
||||||
|
if (proximityLock?.isHeld == false) proximityLock.acquire()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
val call = chatModel.activeCall.value
|
||||||
Box(Modifier.fillMaxSize()) {
|
Box(Modifier.fillMaxSize()) {
|
||||||
WebRTCView(chatModel.callCommand) { apiMsg ->
|
WebRTCView(chatModel.callCommand) { apiMsg ->
|
||||||
Log.d(TAG, "received from WebRTCView: $apiMsg")
|
Log.d(TAG, "received from WebRTCView: $apiMsg")
|
||||||
@ -120,15 +133,15 @@ actual fun ActiveCallView() {
|
|||||||
is WCallResponse.Capabilities -> withBGApi {
|
is WCallResponse.Capabilities -> withBGApi {
|
||||||
val callType = CallType(call.localMedia, r.capabilities)
|
val callType = CallType(call.localMedia, r.capabilities)
|
||||||
chatModel.controller.apiSendCallInvitation(callRh, call.contact, callType)
|
chatModel.controller.apiSendCallInvitation(callRh, call.contact, callType)
|
||||||
chatModel.activeCall.value = call.copy(callState = CallState.InvitationSent, localCapabilities = r.capabilities)
|
updateActiveCall(call) { it.copy(callState = CallState.InvitationSent, localCapabilities = r.capabilities) }
|
||||||
}
|
}
|
||||||
is WCallResponse.Offer -> withBGApi {
|
is WCallResponse.Offer -> withBGApi {
|
||||||
chatModel.controller.apiSendCallOffer(callRh, call.contact, r.offer, r.iceCandidates, call.localMedia, r.capabilities)
|
chatModel.controller.apiSendCallOffer(callRh, call.contact, r.offer, r.iceCandidates, call.localMedia, r.capabilities)
|
||||||
chatModel.activeCall.value = call.copy(callState = CallState.OfferSent, localCapabilities = r.capabilities)
|
updateActiveCall(call) { it.copy(callState = CallState.OfferSent, localCapabilities = r.capabilities) }
|
||||||
}
|
}
|
||||||
is WCallResponse.Answer -> withBGApi {
|
is WCallResponse.Answer -> withBGApi {
|
||||||
chatModel.controller.apiSendCallAnswer(callRh, call.contact, r.answer, r.iceCandidates)
|
chatModel.controller.apiSendCallAnswer(callRh, call.contact, r.answer, r.iceCandidates)
|
||||||
chatModel.activeCall.value = call.copy(callState = CallState.Negotiated)
|
updateActiveCall(call) { it.copy(callState = CallState.Negotiated) }
|
||||||
}
|
}
|
||||||
is WCallResponse.Ice -> withBGApi {
|
is WCallResponse.Ice -> withBGApi {
|
||||||
chatModel.controller.apiSendCallExtraInfo(callRh, call.contact, r.iceCandidates)
|
chatModel.controller.apiSendCallExtraInfo(callRh, call.contact, r.iceCandidates)
|
||||||
@ -137,7 +150,7 @@ actual fun ActiveCallView() {
|
|||||||
try {
|
try {
|
||||||
val callStatus = json.decodeFromString<WebRTCCallStatus>("\"${r.state.connectionState}\"")
|
val callStatus = json.decodeFromString<WebRTCCallStatus>("\"${r.state.connectionState}\"")
|
||||||
if (callStatus == WebRTCCallStatus.Connected) {
|
if (callStatus == WebRTCCallStatus.Connected) {
|
||||||
chatModel.activeCall.value = call.copy(callState = CallState.Connected, connectedAt = Clock.System.now())
|
updateActiveCall(call) { it.copy(callState = CallState.Connected, connectedAt = Clock.System.now()) }
|
||||||
setCallSound(call.soundSpeaker, audioViaBluetooth)
|
setCallSound(call.soundSpeaker, audioViaBluetooth)
|
||||||
}
|
}
|
||||||
withBGApi { chatModel.controller.apiCallStatus(callRh, call.contact, callStatus) }
|
withBGApi { chatModel.controller.apiCallStatus(callRh, call.contact, callStatus) }
|
||||||
@ -145,7 +158,7 @@ actual fun ActiveCallView() {
|
|||||||
Log.d(TAG,"call status ${r.state.connectionState} not used")
|
Log.d(TAG,"call status ${r.state.connectionState} not used")
|
||||||
}
|
}
|
||||||
is WCallResponse.Connected -> {
|
is WCallResponse.Connected -> {
|
||||||
chatModel.activeCall.value = call.copy(callState = CallState.Connected, connectionInfo = r.connectionInfo)
|
updateActiveCall(call) { it.copy(callState = CallState.Connected, connectionInfo = r.connectionInfo) }
|
||||||
scope.launch {
|
scope.launch {
|
||||||
setCallSound(call.soundSpeaker, audioViaBluetooth)
|
setCallSound(call.soundSpeaker, audioViaBluetooth)
|
||||||
}
|
}
|
||||||
@ -154,27 +167,29 @@ actual fun ActiveCallView() {
|
|||||||
withBGApi { chatModel.callManager.endCall(call) }
|
withBGApi { chatModel.callManager.endCall(call) }
|
||||||
}
|
}
|
||||||
is WCallResponse.Ended -> {
|
is WCallResponse.Ended -> {
|
||||||
chatModel.activeCall.value = call.copy(callState = CallState.Ended)
|
updateActiveCall(call) { it.copy(callState = CallState.Ended) }
|
||||||
withBGApi { chatModel.callManager.endCall(call) }
|
withBGApi { chatModel.callManager.endCall(call) }
|
||||||
chatModel.showCallView.value = false
|
|
||||||
}
|
}
|
||||||
is WCallResponse.Ok -> when (val cmd = apiMsg.command) {
|
is WCallResponse.Ok -> when (val cmd = apiMsg.command) {
|
||||||
is WCallCommand.Answer ->
|
is WCallCommand.Answer ->
|
||||||
chatModel.activeCall.value = call.copy(callState = CallState.Negotiated)
|
updateActiveCall(call) { it.copy(callState = CallState.Negotiated) }
|
||||||
is WCallCommand.Media -> {
|
is WCallCommand.Media -> {
|
||||||
when (cmd.media) {
|
updateActiveCall(call) {
|
||||||
CallMediaType.Video -> chatModel.activeCall.value = call.copy(videoEnabled = cmd.enable)
|
when (cmd.media) {
|
||||||
CallMediaType.Audio -> chatModel.activeCall.value = call.copy(audioEnabled = cmd.enable)
|
CallMediaType.Video -> it.copy(videoEnabled = cmd.enable)
|
||||||
|
CallMediaType.Audio -> it.copy(audioEnabled = cmd.enable)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is WCallCommand.Camera -> {
|
is WCallCommand.Camera -> {
|
||||||
chatModel.activeCall.value = call.copy(localCamera = cmd.camera)
|
updateActiveCall(call) { it.copy(localCamera = cmd.camera) }
|
||||||
if (!call.audioEnabled) {
|
if (!call.audioEnabled) {
|
||||||
chatModel.callCommand.add(WCallCommand.Media(CallMediaType.Audio, enable = false))
|
chatModel.callCommand.add(WCallCommand.Media(CallMediaType.Audio, enable = false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is WCallCommand.End ->
|
is WCallCommand.End -> {
|
||||||
chatModel.showCallView.value = false
|
withBGApi { chatModel.callManager.endCall(call) }
|
||||||
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
is WCallResponse.Error -> {
|
is WCallResponse.Error -> {
|
||||||
@ -183,8 +198,16 @@ actual fun ActiveCallView() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val call = chatModel.activeCall.value
|
val showOverlay = when {
|
||||||
if (call != null) ActiveCallOverlay(call, chatModel, audioViaBluetooth)
|
call == null -> false
|
||||||
|
!platform.androidPictureInPictureAllowed() -> true
|
||||||
|
!call.supportsVideo() -> true
|
||||||
|
!chatModel.activeCallViewIsCollapsed.value -> true
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
if (call != null && showOverlay) {
|
||||||
|
ActiveCallOverlay(call, chatModel, audioViaBluetooth)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
@ -229,6 +252,20 @@ private fun ActiveCallOverlay(call: Call, chatModel: ChatModel, audioViaBluetoot
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ActiveCallOverlayDisabled(call: Call) {
|
||||||
|
ActiveCallOverlayLayout(
|
||||||
|
call = call,
|
||||||
|
speakerCanBeEnabled = false,
|
||||||
|
enabled = false,
|
||||||
|
dismiss = {},
|
||||||
|
toggleAudio = {},
|
||||||
|
toggleVideo = {},
|
||||||
|
toggleSound = {},
|
||||||
|
flipCamera = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun setCallSound(speaker: Boolean, audioViaBluetooth: MutableState<Boolean>) {
|
private fun setCallSound(speaker: Boolean, audioViaBluetooth: MutableState<Boolean>) {
|
||||||
val am = androidAppContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
val am = androidAppContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||||
Log.d(TAG, "setCallSound: set audio mode, speaker enabled: $speaker")
|
Log.d(TAG, "setCallSound: set audio mode, speaker enabled: $speaker")
|
||||||
@ -271,59 +308,69 @@ private fun dropAudioManagerOverrides() {
|
|||||||
private fun ActiveCallOverlayLayout(
|
private fun ActiveCallOverlayLayout(
|
||||||
call: Call,
|
call: Call,
|
||||||
speakerCanBeEnabled: Boolean,
|
speakerCanBeEnabled: Boolean,
|
||||||
|
enabled: Boolean = true,
|
||||||
dismiss: () -> Unit,
|
dismiss: () -> Unit,
|
||||||
toggleAudio: () -> Unit,
|
toggleAudio: () -> Unit,
|
||||||
toggleVideo: () -> Unit,
|
toggleVideo: () -> Unit,
|
||||||
toggleSound: () -> Unit,
|
toggleSound: () -> Unit,
|
||||||
flipCamera: () -> Unit
|
flipCamera: () -> Unit
|
||||||
) {
|
) {
|
||||||
Column(Modifier.padding(DEFAULT_PADDING)) {
|
Column {
|
||||||
when (call.peerMedia ?: call.localMedia) {
|
val media = call.peerMedia ?: call.localMedia
|
||||||
CallMediaType.Video -> {
|
CloseSheetBar({ chatModel.activeCallViewIsCollapsed.value = true }, true, tintColor = Color(0xFFFFFFD8)) {
|
||||||
CallInfoView(call, alignment = Alignment.Start)
|
if (media == CallMediaType.Video) {
|
||||||
Box(Modifier.fillMaxWidth().fillMaxHeight().weight(1f), contentAlignment = Alignment.BottomCenter) {
|
Text(call.contact.chatViewName, Modifier.fillMaxWidth().padding(end = DEFAULT_PADDING), color = Color(0xFFFFFFD8), style = MaterialTheme.typography.h2, overflow = TextOverflow.Ellipsis, maxLines = 1)
|
||||||
DisabledBackgroundCallsButton()
|
|
||||||
}
|
|
||||||
Row(Modifier.fillMaxWidth().padding(horizontal = 6.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) {
|
|
||||||
ToggleAudioButton(call, toggleAudio)
|
|
||||||
Spacer(Modifier.size(40.dp))
|
|
||||||
IconButton(onClick = dismiss) {
|
|
||||||
Icon(painterResource(MR.images.ic_call_end_filled), stringResource(MR.strings.icon_descr_hang_up), tint = Color.Red, modifier = Modifier.size(64.dp))
|
|
||||||
}
|
|
||||||
if (call.videoEnabled) {
|
|
||||||
ControlButton(call, painterResource(MR.images.ic_flip_camera_android_filled), MR.strings.icon_descr_flip_camera, flipCamera)
|
|
||||||
ControlButton(call, painterResource(MR.images.ic_videocam_filled), MR.strings.icon_descr_video_off, toggleVideo)
|
|
||||||
} else {
|
|
||||||
Spacer(Modifier.size(48.dp))
|
|
||||||
ControlButton(call, painterResource(MR.images.ic_videocam_off), MR.strings.icon_descr_video_on, toggleVideo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
CallMediaType.Audio -> {
|
}
|
||||||
Spacer(Modifier.fillMaxHeight().weight(1f))
|
Column(Modifier.padding(horizontal = DEFAULT_PADDING)) {
|
||||||
Column(
|
when (media) {
|
||||||
Modifier.fillMaxWidth(),
|
CallMediaType.Video -> {
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
VideoCallInfoView(call)
|
||||||
verticalArrangement = Arrangement.Center
|
Box(Modifier.fillMaxWidth().fillMaxHeight().weight(1f), contentAlignment = Alignment.BottomCenter) {
|
||||||
) {
|
DisabledBackgroundCallsButton()
|
||||||
ProfileImage(size = 192.dp, image = call.contact.profile.image)
|
}
|
||||||
CallInfoView(call, alignment = Alignment.CenterHorizontally)
|
Row(Modifier.fillMaxWidth().padding(horizontal = 6.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) {
|
||||||
}
|
ToggleAudioButton(call, enabled, toggleAudio)
|
||||||
Box(Modifier.fillMaxWidth().fillMaxHeight().weight(1f), contentAlignment = Alignment.BottomCenter) {
|
Spacer(Modifier.size(40.dp))
|
||||||
DisabledBackgroundCallsButton()
|
IconButton(onClick = dismiss, enabled = enabled) {
|
||||||
}
|
Icon(painterResource(MR.images.ic_call_end_filled), stringResource(MR.strings.icon_descr_hang_up), tint = if (enabled) Color.Red else MaterialTheme.colors.secondary, modifier = Modifier.size(64.dp))
|
||||||
Box(Modifier.fillMaxWidth().padding(bottom = DEFAULT_BOTTOM_PADDING), contentAlignment = Alignment.CenterStart) {
|
}
|
||||||
Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
|
if (call.videoEnabled) {
|
||||||
IconButton(onClick = dismiss) {
|
ControlButton(call, painterResource(MR.images.ic_flip_camera_android_filled), MR.strings.icon_descr_flip_camera, enabled, flipCamera)
|
||||||
Icon(painterResource(MR.images.ic_call_end_filled), stringResource(MR.strings.icon_descr_hang_up), tint = Color.Red, modifier = Modifier.size(64.dp))
|
ControlButton(call, painterResource(MR.images.ic_videocam_filled), MR.strings.icon_descr_video_off, enabled, toggleVideo)
|
||||||
|
} else {
|
||||||
|
Spacer(Modifier.size(48.dp))
|
||||||
|
ControlButton(call, painterResource(MR.images.ic_videocam_off), MR.strings.icon_descr_video_on, enabled, toggleVideo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Box(Modifier.padding(start = 32.dp)) {
|
}
|
||||||
ToggleAudioButton(call, toggleAudio)
|
|
||||||
|
CallMediaType.Audio -> {
|
||||||
|
Spacer(Modifier.fillMaxHeight().weight(1f))
|
||||||
|
Column(
|
||||||
|
Modifier.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
ProfileImage(size = 192.dp, image = call.contact.profile.image)
|
||||||
|
AudioCallInfoView(call)
|
||||||
}
|
}
|
||||||
Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.CenterEnd) {
|
Box(Modifier.fillMaxWidth().fillMaxHeight().weight(1f), contentAlignment = Alignment.BottomCenter) {
|
||||||
Box(Modifier.padding(end = 32.dp)) {
|
DisabledBackgroundCallsButton()
|
||||||
ToggleSoundButton(call, speakerCanBeEnabled, toggleSound)
|
}
|
||||||
|
Box(Modifier.fillMaxWidth().padding(bottom = DEFAULT_BOTTOM_PADDING), contentAlignment = Alignment.CenterStart) {
|
||||||
|
Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
|
||||||
|
IconButton(onClick = dismiss, enabled = enabled) {
|
||||||
|
Icon(painterResource(MR.images.ic_call_end_filled), stringResource(MR.strings.icon_descr_hang_up), tint = if (enabled) Color.Red else MaterialTheme.colors.secondary, modifier = Modifier.size(64.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Box(Modifier.padding(start = 32.dp)) {
|
||||||
|
ToggleAudioButton(call, enabled, toggleAudio)
|
||||||
|
}
|
||||||
|
Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.CenterEnd) {
|
||||||
|
Box(Modifier.padding(end = 32.dp)) {
|
||||||
|
ToggleSoundButton(call, speakerCanBeEnabled && enabled, toggleSound)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -333,7 +380,7 @@ private fun ActiveCallOverlayLayout(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ControlButton(call: Call, icon: Painter, iconText: StringResource, action: () -> Unit, enabled: Boolean = true) {
|
private fun ControlButton(call: Call, icon: Painter, iconText: StringResource, enabled: Boolean = true, action: () -> Unit) {
|
||||||
if (call.hasMedia) {
|
if (call.hasMedia) {
|
||||||
IconButton(onClick = action, enabled = enabled) {
|
IconButton(onClick = action, enabled = enabled) {
|
||||||
Icon(icon, stringResource(iconText), tint = if (enabled) Color(0xFFFFFFD8) else MaterialTheme.colors.secondary, modifier = Modifier.size(40.dp))
|
Icon(icon, stringResource(iconText), tint = if (enabled) Color(0xFFFFFFD8) else MaterialTheme.colors.secondary, modifier = Modifier.size(40.dp))
|
||||||
@ -344,28 +391,26 @@ private fun ControlButton(call: Call, icon: Painter, iconText: StringResource, a
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ToggleAudioButton(call: Call, toggleAudio: () -> Unit) {
|
private fun ToggleAudioButton(call: Call, enabled: Boolean = true, toggleAudio: () -> Unit) {
|
||||||
if (call.audioEnabled) {
|
if (call.audioEnabled) {
|
||||||
ControlButton(call, painterResource(MR.images.ic_mic), MR.strings.icon_descr_audio_off, toggleAudio)
|
ControlButton(call, painterResource(MR.images.ic_mic), MR.strings.icon_descr_audio_off, enabled, toggleAudio)
|
||||||
} else {
|
} else {
|
||||||
ControlButton(call, painterResource(MR.images.ic_mic_off), MR.strings.icon_descr_audio_on, toggleAudio)
|
ControlButton(call, painterResource(MR.images.ic_mic_off), MR.strings.icon_descr_audio_on, enabled, toggleAudio)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ToggleSoundButton(call: Call, enabled: Boolean, toggleSound: () -> Unit) {
|
private fun ToggleSoundButton(call: Call, enabled: Boolean, toggleSound: () -> Unit) {
|
||||||
if (call.soundSpeaker) {
|
if (call.soundSpeaker) {
|
||||||
ControlButton(call, painterResource(MR.images.ic_volume_up), MR.strings.icon_descr_speaker_off, toggleSound, enabled)
|
ControlButton(call, painterResource(MR.images.ic_volume_up), MR.strings.icon_descr_speaker_off, enabled, toggleSound)
|
||||||
} else {
|
} else {
|
||||||
ControlButton(call, painterResource(MR.images.ic_volume_down), MR.strings.icon_descr_speaker_on, toggleSound, enabled)
|
ControlButton(call, painterResource(MR.images.ic_volume_down), MR.strings.icon_descr_speaker_on, enabled, toggleSound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CallInfoView(call: Call, alignment: Alignment.Horizontal) {
|
fun AudioCallInfoView(call: Call) {
|
||||||
@Composable fun InfoText(text: String, style: TextStyle = MaterialTheme.typography.body2) =
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
Text(text, color = Color(0xFFFFFFD8), style = style)
|
|
||||||
Column(horizontalAlignment = alignment) {
|
|
||||||
InfoText(call.contact.chatViewName, style = MaterialTheme.typography.h2)
|
InfoText(call.contact.chatViewName, style = MaterialTheme.typography.h2)
|
||||||
InfoText(call.callState.text)
|
InfoText(call.callState.text)
|
||||||
|
|
||||||
@ -375,6 +420,21 @@ fun CallInfoView(call: Call, alignment: Alignment.Horizontal) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun VideoCallInfoView(call: Call) {
|
||||||
|
Column(horizontalAlignment = Alignment.Start) {
|
||||||
|
InfoText(call.callState.text)
|
||||||
|
|
||||||
|
val connInfo = call.connectionInfo
|
||||||
|
val connInfoText = if (connInfo == null) "" else " (${connInfo.text})"
|
||||||
|
InfoText(call.encryptionStatus + connInfoText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun InfoText(text: String, modifier: Modifier = Modifier, style: TextStyle = MaterialTheme.typography.body2) =
|
||||||
|
Text(text, modifier, color = Color(0xFFFFFFD8), style = style)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun DisabledBackgroundCallsButton() {
|
private fun DisabledBackgroundCallsButton() {
|
||||||
var show by remember { mutableStateOf(!platform.androidIsBackgroundCallAllowed()) }
|
var show by remember { mutableStateOf(!platform.androidIsBackgroundCallAllowed()) }
|
||||||
@ -452,7 +512,6 @@ private fun DisabledBackgroundCallsButton() {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun WebRTCView(callCommand: SnapshotStateList<WCallCommand>, onResponse: (WVAPIMessage) -> Unit) {
|
fun WebRTCView(callCommand: SnapshotStateList<WCallCommand>, onResponse: (WVAPIMessage) -> Unit) {
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
val webView = remember { mutableStateOf<WebView?>(null) }
|
val webView = remember { mutableStateOf<WebView?>(null) }
|
||||||
val permissionsState = rememberMultiplePermissionsState(
|
val permissionsState = rememberMultiplePermissionsState(
|
||||||
permissions = listOf(
|
permissions = listOf(
|
||||||
@ -475,10 +534,10 @@ fun WebRTCView(callCommand: SnapshotStateList<WCallCommand>, onResponse: (WVAPIM
|
|||||||
}
|
}
|
||||||
lifecycleOwner.lifecycle.addObserver(observer)
|
lifecycleOwner.lifecycle.addObserver(observer)
|
||||||
onDispose {
|
onDispose {
|
||||||
val wv = webView.value
|
|
||||||
if (wv != null) processCommand(wv, WCallCommand.End)
|
|
||||||
lifecycleOwner.lifecycle.removeObserver(observer)
|
lifecycleOwner.lifecycle.removeObserver(observer)
|
||||||
webView.value?.destroy()
|
// val wv = webView.value
|
||||||
|
// if (wv != null) processCommand(wv, WCallCommand.End)
|
||||||
|
// webView.value?.destroy()
|
||||||
webView.value = null
|
webView.value = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -505,7 +564,7 @@ fun WebRTCView(callCommand: SnapshotStateList<WCallCommand>, onResponse: (WVAPIM
|
|||||||
Box(Modifier.fillMaxSize()) {
|
Box(Modifier.fillMaxSize()) {
|
||||||
AndroidView(
|
AndroidView(
|
||||||
factory = { AndroidViewContext ->
|
factory = { AndroidViewContext ->
|
||||||
WebView(AndroidViewContext).apply {
|
(staticWebView ?: WebView(androidAppContext)).apply {
|
||||||
layoutParams = ViewGroup.LayoutParams(
|
layoutParams = ViewGroup.LayoutParams(
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
@ -530,7 +589,11 @@ fun WebRTCView(callCommand: SnapshotStateList<WCallCommand>, onResponse: (WVAPIM
|
|||||||
webViewSettings.javaScriptEnabled = true
|
webViewSettings.javaScriptEnabled = true
|
||||||
webViewSettings.mediaPlaybackRequiresUserGesture = false
|
webViewSettings.mediaPlaybackRequiresUserGesture = false
|
||||||
webViewSettings.cacheMode = WebSettings.LOAD_NO_CACHE
|
webViewSettings.cacheMode = WebSettings.LOAD_NO_CACHE
|
||||||
this.loadUrl("file:android_asset/www/android/call.html")
|
if (staticWebView == null) {
|
||||||
|
this.loadUrl("file:android_asset/www/android/call.html")
|
||||||
|
} else {
|
||||||
|
webView.value = this
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) { /* WebView */ }
|
) { /* WebView */ }
|
||||||
@ -554,6 +617,15 @@ class WebRTCInterface(private val onResponse: (WVAPIMessage) -> Unit) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateActiveCall(initial: Call, transform: (Call) -> Call) {
|
||||||
|
val activeCall = chatModel.activeCall.value
|
||||||
|
if (activeCall != null && activeCall.contact.apiId == initial.contact.apiId) {
|
||||||
|
chatModel.activeCall.value = transform(activeCall)
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "withActiveCall: ignoring, not in call with the contact ${activeCall?.contact?.id}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class LocalContentWebViewClient(val webView: MutableState<WebView?>, private val assetLoader: WebViewAssetLoader) : WebViewClientCompat() {
|
private class LocalContentWebViewClient(val webView: MutableState<WebView?>, private val assetLoader: WebViewAssetLoader) : WebViewClientCompat() {
|
||||||
override fun shouldInterceptRequest(
|
override fun shouldInterceptRequest(
|
||||||
view: WebView,
|
view: WebView,
|
||||||
@ -566,6 +638,7 @@ private class LocalContentWebViewClient(val webView: MutableState<WebView?>, pri
|
|||||||
super.onPageFinished(view, url)
|
super.onPageFinished(view, url)
|
||||||
view.evaluateJavascript("sendMessageToNative = (msg) => WebRTCInterface.postMessage(JSON.stringify(msg))", null)
|
view.evaluateJavascript("sendMessageToNative = (msg) => WebRTCInterface.postMessage(JSON.stringify(msg))", null)
|
||||||
webView.value = view
|
webView.value = view
|
||||||
|
staticWebView = view
|
||||||
Log.d(TAG, "WebRTCView: webview ready")
|
Log.d(TAG, "WebRTCView: webview ready")
|
||||||
// for debugging
|
// for debugging
|
||||||
// view.evaluateJavascript("sendMessageToNative = ({resp}) => WebRTCInterface.postMessage(JSON.stringify({command: resp}))", null)
|
// view.evaluateJavascript("sendMessageToNative = ({resp}) => WebRTCInterface.postMessage(JSON.stringify({command: resp}))", null)
|
||||||
@ -579,6 +652,7 @@ fun PreviewActiveCallOverlayVideo() {
|
|||||||
ActiveCallOverlayLayout(
|
ActiveCallOverlayLayout(
|
||||||
call = Call(
|
call = Call(
|
||||||
remoteHostId = null,
|
remoteHostId = null,
|
||||||
|
userProfile = Profile.sampleData,
|
||||||
contact = Contact.sampleData,
|
contact = Contact.sampleData,
|
||||||
callState = CallState.Negotiated,
|
callState = CallState.Negotiated,
|
||||||
localMedia = CallMediaType.Video,
|
localMedia = CallMediaType.Video,
|
||||||
@ -605,6 +679,7 @@ fun PreviewActiveCallOverlayAudio() {
|
|||||||
ActiveCallOverlayLayout(
|
ActiveCallOverlayLayout(
|
||||||
call = Call(
|
call = Call(
|
||||||
remoteHostId = null,
|
remoteHostId = null,
|
||||||
|
userProfile = Profile.sampleData,
|
||||||
contact = Contact.sampleData,
|
contact = Contact.sampleData,
|
||||||
callState = CallState.Negotiated,
|
callState = CallState.Negotiated,
|
||||||
localMedia = CallMediaType.Audio,
|
localMedia = CallMediaType.Audio,
|
||||||
|
@ -1,8 +1,112 @@
|
|||||||
package chat.simplex.common.views.chatlist
|
package chat.simplex.common.views.chatlist
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import androidx.compose.foundation.*
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material.*
|
||||||
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.toArgb
|
||||||
|
import androidx.compose.ui.platform.*
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import chat.simplex.common.ANDROID_CALL_TOP_PADDING
|
||||||
|
import chat.simplex.common.model.durationText
|
||||||
|
import chat.simplex.common.platform.*
|
||||||
|
import chat.simplex.common.ui.theme.*
|
||||||
|
import chat.simplex.common.views.call.*
|
||||||
import chat.simplex.common.views.helpers.*
|
import chat.simplex.common.views.helpers.*
|
||||||
|
import chat.simplex.res.MR
|
||||||
|
import dev.icerock.moko.resources.compose.painterResource
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.datetime.Clock
|
||||||
|
|
||||||
|
private val CALL_INTERACTIVE_AREA_HEIGHT = 74.dp
|
||||||
|
private val CALL_TOP_OFFSET = (-10).dp
|
||||||
|
private val CALL_TOP_GREEN_LINE_HEIGHT = ANDROID_CALL_TOP_PADDING - CALL_TOP_OFFSET
|
||||||
|
private val CALL_BOTTOM_ICON_OFFSET = (-15).dp
|
||||||
|
private val CALL_BOTTOM_ICON_HEIGHT = CALL_INTERACTIVE_AREA_HEIGHT + CALL_BOTTOM_ICON_OFFSET
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
actual fun DesktopActiveCallOverlayLayout(newChatSheetState: MutableStateFlow<AnimatedViewState>) {}
|
actual fun ActiveCallInteractiveArea(call: Call, newChatSheetState: MutableStateFlow<AnimatedViewState>) {
|
||||||
|
val onClick = { platform.androidStartCallActivity(false) }
|
||||||
|
Box(Modifier.offset(y = CALL_TOP_OFFSET).height(CALL_INTERACTIVE_AREA_HEIGHT)) {
|
||||||
|
val source = remember { MutableInteractionSource() }
|
||||||
|
val indication = rememberRipple(bounded = true, 3000.dp)
|
||||||
|
Box(Modifier.height(CALL_TOP_GREEN_LINE_HEIGHT).clickable(onClick = onClick, indication = indication, interactionSource = source)) {
|
||||||
|
GreenLine(call)
|
||||||
|
}
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.offset(y = CALL_BOTTOM_ICON_OFFSET)
|
||||||
|
.size(CALL_BOTTOM_ICON_HEIGHT)
|
||||||
|
.background(SimplexGreen, CircleShape)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.clickable(onClick = onClick, indication = indication, interactionSource = source)
|
||||||
|
.align(Alignment.BottomCenter),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
val media = call.peerMedia ?: call.localMedia
|
||||||
|
if (media == CallMediaType.Video) {
|
||||||
|
Icon(painterResource(MR.images.ic_videocam_filled), null, Modifier.size(27.dp).offset(x = 2.5.dp, y = 2.dp), tint = Color.White)
|
||||||
|
} else {
|
||||||
|
Icon(painterResource(MR.images.ic_call_filled), null, Modifier.size(27.dp).offset(x = -0.5.dp, y = 2.dp), tint = Color.White)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun GreenLine(call: Call) {
|
||||||
|
Row(
|
||||||
|
Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(SimplexGreen)
|
||||||
|
.padding(top = -CALL_TOP_OFFSET)
|
||||||
|
.padding(horizontal = DEFAULT_PADDING),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
ContactName(call.contact.displayName)
|
||||||
|
Spacer(Modifier.weight(1f))
|
||||||
|
CallDuration(call)
|
||||||
|
}
|
||||||
|
val window = (LocalContext.current as Activity).window
|
||||||
|
DisposableEffect(Unit) {
|
||||||
|
window.statusBarColor = SimplexGreen.toArgb()
|
||||||
|
onDispose {
|
||||||
|
window.statusBarColor = Color.Black.toArgb()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ContactName(name: String) {
|
||||||
|
Text(name, Modifier.width(windowWidth() * 0.35f), color = Color.White, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun CallDuration(call: Call) {
|
||||||
|
val connectedAt = call.connectedAt
|
||||||
|
if (connectedAt != null) {
|
||||||
|
val time = remember { mutableStateOf(durationText(0)) }
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
while (true) {
|
||||||
|
time.value = durationText((Clock.System.now() - connectedAt).inWholeSeconds.toInt())
|
||||||
|
delay(250)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val text = time.value
|
||||||
|
val sp40Or50 = with(LocalDensity.current) { if (text.length >= 6) 60.sp.toDp() else 42.sp.toDp() }
|
||||||
|
val offset = with(LocalDensity.current) { 7.sp.toDp() }
|
||||||
|
Text(text, Modifier.offset(x = offset).widthIn(min = sp40Or50), color = Color.White)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -71,7 +71,7 @@ if(NOT APPLE)
|
|||||||
else()
|
else()
|
||||||
# Without direct linking it can't find hs_init in linking step
|
# Without direct linking it can't find hs_init in linking step
|
||||||
add_library( rts SHARED IMPORTED )
|
add_library( rts SHARED IMPORTED )
|
||||||
FILE(GLOB RTSLIB ${CMAKE_SOURCE_DIR}/libs/${OS_LIB_PATH}-${OS_LIB_ARCH}/libHSrts*_thr-*.${OS_LIB_EXT})
|
FILE(GLOB RTSLIB ${CMAKE_SOURCE_DIR}/libs/${OS_LIB_PATH}-${OS_LIB_ARCH}/deps/libHSrts*_thr-*.${OS_LIB_EXT})
|
||||||
set_target_properties( rts PROPERTIES IMPORTED_LOCATION ${RTSLIB})
|
set_target_properties( rts PROPERTIES IMPORTED_LOCATION ${RTSLIB})
|
||||||
|
|
||||||
target_link_libraries(app-lib rts simplex)
|
target_link_libraries(app-lib rts simplex)
|
||||||
|
@ -1,16 +1,19 @@
|
|||||||
package chat.simplex.common
|
package chat.simplex.common
|
||||||
|
|
||||||
import androidx.compose.animation.core.Animatable
|
import androidx.compose.animation.core.Animatable
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.*
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.draw.clipToBounds
|
import androidx.compose.ui.draw.clipToBounds
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import chat.simplex.common.views.usersettings.SetDeliveryReceiptsView
|
import chat.simplex.common.views.usersettings.SetDeliveryReceiptsView
|
||||||
@ -20,8 +23,7 @@ import chat.simplex.common.ui.theme.*
|
|||||||
import chat.simplex.common.views.CreateFirstProfile
|
import chat.simplex.common.views.CreateFirstProfile
|
||||||
import chat.simplex.common.views.helpers.SimpleButton
|
import chat.simplex.common.views.helpers.SimpleButton
|
||||||
import chat.simplex.common.views.SplashView
|
import chat.simplex.common.views.SplashView
|
||||||
import chat.simplex.common.views.call.ActiveCallView
|
import chat.simplex.common.views.call.*
|
||||||
import chat.simplex.common.views.call.IncomingCallAlertView
|
|
||||||
import chat.simplex.common.views.chat.ChatView
|
import chat.simplex.common.views.chat.ChatView
|
||||||
import chat.simplex.common.views.chatlist.*
|
import chat.simplex.common.views.chatlist.*
|
||||||
import chat.simplex.common.views.database.DatabaseErrorView
|
import chat.simplex.common.views.database.DatabaseErrorView
|
||||||
@ -169,7 +171,17 @@ fun MainScreen() {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (chatModel.showCallView.value) {
|
if (chatModel.showCallView.value) {
|
||||||
ActiveCallView()
|
if (appPlatform.isAndroid) {
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
// This if prevents running the activity in the following condition:
|
||||||
|
// - the activity already started before and was destroyed by collapsing active call (start audio call, press back button, go to a launcher)
|
||||||
|
if (!chatModel.activeCallViewIsCollapsed.value) {
|
||||||
|
platform.androidStartCallActivity(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ActiveCallView()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// It's needed for privacy settings toggle, so it can be shown even if the app is passcode unlocked
|
// It's needed for privacy settings toggle, so it can be shown even if the app is passcode unlocked
|
||||||
ModalManager.fullscreen.showPasscodeInView()
|
ModalManager.fullscreen.showPasscodeInView()
|
||||||
@ -206,9 +218,13 @@ fun MainScreen() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val ANDROID_CALL_TOP_PADDING = 40.dp
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AndroidScreen(settingsState: SettingsViewState) {
|
fun AndroidScreen(settingsState: SettingsViewState) {
|
||||||
BoxWithConstraints {
|
BoxWithConstraints {
|
||||||
|
val call = remember { chatModel.activeCall} .value
|
||||||
|
val showCallArea = call != null && call.callState != CallState.WaitCapabilities && call.callState != CallState.InvitationAccepted
|
||||||
var currentChatId by rememberSaveable { mutableStateOf(chatModel.chatId.value) }
|
var currentChatId by rememberSaveable { mutableStateOf(chatModel.chatId.value) }
|
||||||
val offset = remember { Animatable(if (chatModel.chatId.value == null) 0f else maxWidth.value) }
|
val offset = remember { Animatable(if (chatModel.chatId.value == null) 0f else maxWidth.value) }
|
||||||
Box(
|
Box(
|
||||||
@ -216,6 +232,7 @@ fun AndroidScreen(settingsState: SettingsViewState) {
|
|||||||
.graphicsLayer {
|
.graphicsLayer {
|
||||||
translationX = -offset.value.dp.toPx()
|
translationX = -offset.value.dp.toPx()
|
||||||
}
|
}
|
||||||
|
.padding(top = if (showCallArea) ANDROID_CALL_TOP_PADDING else 0.dp)
|
||||||
) {
|
) {
|
||||||
StartPartOfScreen(settingsState)
|
StartPartOfScreen(settingsState)
|
||||||
}
|
}
|
||||||
@ -242,11 +259,17 @@ fun AndroidScreen(settingsState: SettingsViewState) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Box(Modifier.graphicsLayer { translationX = maxWidth.toPx() - offset.value.dp.toPx() }) Box2@{
|
Box(Modifier
|
||||||
|
.graphicsLayer { translationX = maxWidth.toPx() - offset.value.dp.toPx() }
|
||||||
|
.padding(top = if (showCallArea) ANDROID_CALL_TOP_PADDING else 0.dp)
|
||||||
|
) Box2@{
|
||||||
currentChatId?.let {
|
currentChatId?.let {
|
||||||
ChatView(it, chatModel, onComposed)
|
ChatView(it, chatModel, onComposed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (call != null && showCallArea) {
|
||||||
|
ActiveCallInteractiveArea(call, remember { MutableStateFlow(AnimatedViewState.GONE) })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,6 +96,7 @@ object ChatModel {
|
|||||||
val activeCallInvitation = mutableStateOf<RcvCallInvitation?>(null)
|
val activeCallInvitation = mutableStateOf<RcvCallInvitation?>(null)
|
||||||
val activeCall = mutableStateOf<Call?>(null)
|
val activeCall = mutableStateOf<Call?>(null)
|
||||||
val activeCallViewIsVisible = mutableStateOf<Boolean>(false)
|
val activeCallViewIsVisible = mutableStateOf<Boolean>(false)
|
||||||
|
val activeCallViewIsCollapsed = mutableStateOf<Boolean>(false)
|
||||||
val callCommand = mutableStateListOf<WCallCommand>()
|
val callCommand = mutableStateListOf<WCallCommand>()
|
||||||
val showCallView = mutableStateOf(false)
|
val showCallView = mutableStateOf(false)
|
||||||
val switchingCall = mutableStateOf(false)
|
val switchingCall = mutableStateOf(false)
|
||||||
|
@ -1900,10 +1900,8 @@ object ChatController {
|
|||||||
if (invitation != null) {
|
if (invitation != null) {
|
||||||
chatModel.callManager.reportCallRemoteEnded(invitation = invitation)
|
chatModel.callManager.reportCallRemoteEnded(invitation = invitation)
|
||||||
}
|
}
|
||||||
withCall(r, r.contact) { _ ->
|
withCall(r, r.contact) { call ->
|
||||||
chatModel.callCommand.add(WCallCommand.End)
|
withBGApi { chatModel.callManager.endCall(call) }
|
||||||
chatModel.activeCall.value = null
|
|
||||||
chatModel.showCallView.value = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is CR.ContactSwitch ->
|
is CR.ContactSwitch ->
|
||||||
|
@ -1,16 +1,21 @@
|
|||||||
package chat.simplex.common.platform
|
package chat.simplex.common.platform
|
||||||
|
|
||||||
|
import chat.simplex.common.model.ChatId
|
||||||
import chat.simplex.common.model.NotificationsMode
|
import chat.simplex.common.model.NotificationsMode
|
||||||
|
|
||||||
interface PlatformInterface {
|
interface PlatformInterface {
|
||||||
suspend fun androidServiceStart() {}
|
suspend fun androidServiceStart() {}
|
||||||
fun androidServiceSafeStop() {}
|
fun androidServiceSafeStop() {}
|
||||||
|
fun androidCallServiceSafeStop() {}
|
||||||
fun androidNotificationsModeChanged(mode: NotificationsMode) {}
|
fun androidNotificationsModeChanged(mode: NotificationsMode) {}
|
||||||
fun androidChatStartedAfterBeingOff() {}
|
fun androidChatStartedAfterBeingOff() {}
|
||||||
fun androidChatStopped() {}
|
fun androidChatStopped() {}
|
||||||
fun androidChatInitializedAndStarted() {}
|
fun androidChatInitializedAndStarted() {}
|
||||||
fun androidIsBackgroundCallAllowed(): Boolean = true
|
fun androidIsBackgroundCallAllowed(): Boolean = true
|
||||||
fun androidSetNightModeIfSupported() {}
|
fun androidSetNightModeIfSupported() {}
|
||||||
|
fun androidStartCallActivity(acceptCall: Boolean, remoteHostId: Long? = null, chatId: ChatId? = null) {}
|
||||||
|
fun androidPictureInPictureAllowed(): Boolean = true
|
||||||
|
fun androidCallEnded() {}
|
||||||
suspend fun androidAskToAllowBackgroundCalls(): Boolean = true
|
suspend fun androidAskToAllowBackgroundCalls(): Boolean = true
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package chat.simplex.common.views.call
|
package chat.simplex.common.views.call
|
||||||
|
|
||||||
import chat.simplex.common.model.ChatModel
|
import chat.simplex.common.model.*
|
||||||
import chat.simplex.common.platform.*
|
import chat.simplex.common.platform.*
|
||||||
import chat.simplex.common.views.helpers.withBGApi
|
import chat.simplex.common.views.helpers.withBGApi
|
||||||
import kotlinx.datetime.Clock
|
import kotlinx.datetime.Clock
|
||||||
@ -23,27 +23,29 @@ class CallManager(val chatModel: ChatModel) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun acceptIncomingCall(invitation: RcvCallInvitation) {
|
fun acceptIncomingCall(invitation: RcvCallInvitation) = withBGApi {
|
||||||
val call = chatModel.activeCall.value
|
val call = chatModel.activeCall.value
|
||||||
if (call == null) {
|
val contactInfo = chatModel.controller.apiContactInfo(invitation.remoteHostId, invitation.contact.contactId)
|
||||||
justAcceptIncomingCall(invitation = invitation)
|
val profile = contactInfo?.second ?: invitation.user.profile.toProfile()
|
||||||
|
// In case the same contact calling while previous call didn't end yet (abnormal ending of call from the other side)
|
||||||
|
if (call == null || (call.remoteHostId == invitation.remoteHostId && call.contact.id == invitation.contact.id)) {
|
||||||
|
justAcceptIncomingCall(invitation = invitation, profile)
|
||||||
} else {
|
} else {
|
||||||
withBGApi {
|
chatModel.switchingCall.value = true
|
||||||
chatModel.switchingCall.value = true
|
try {
|
||||||
try {
|
endCall(call = call)
|
||||||
endCall(call = call)
|
justAcceptIncomingCall(invitation = invitation, profile)
|
||||||
justAcceptIncomingCall(invitation = invitation)
|
} finally {
|
||||||
} finally {
|
chatModel.switchingCall.value = false
|
||||||
chatModel.switchingCall.value = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun justAcceptIncomingCall(invitation: RcvCallInvitation) {
|
private fun justAcceptIncomingCall(invitation: RcvCallInvitation, userProfile: Profile) {
|
||||||
with (chatModel) {
|
with (chatModel) {
|
||||||
activeCall.value = Call(
|
activeCall.value = Call(
|
||||||
remoteHostId = invitation.remoteHostId,
|
remoteHostId = invitation.remoteHostId,
|
||||||
|
userProfile = userProfile,
|
||||||
contact = invitation.contact,
|
contact = invitation.contact,
|
||||||
callState = CallState.InvitationAccepted,
|
callState = CallState.InvitationAccepted,
|
||||||
localMedia = invitation.callType.media,
|
localMedia = invitation.callType.media,
|
||||||
@ -68,17 +70,23 @@ class CallManager(val chatModel: ChatModel) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun endCall(call: Call) {
|
suspend fun endCall(call: Call) {
|
||||||
with (chatModel) {
|
with(chatModel) {
|
||||||
|
// If there is active call currently and it's with other contact, don't interrupt it
|
||||||
|
if (activeCall.value != null && !(activeCall.value?.remoteHostId == call.remoteHostId && activeCall.value?.contact?.id == call.contact.id)) return
|
||||||
|
|
||||||
|
// Don't destroy WebView if you plan to accept next call right after this one
|
||||||
|
if (!switchingCall.value) {
|
||||||
|
showCallView.value = false
|
||||||
|
activeCall.value = null
|
||||||
|
activeCallViewIsCollapsed.value = false
|
||||||
|
platform.androidCallEnded()
|
||||||
|
}
|
||||||
if (call.callState == CallState.Ended) {
|
if (call.callState == CallState.Ended) {
|
||||||
Log.d(TAG, "CallManager.endCall: call ended")
|
Log.d(TAG, "CallManager.endCall: call ended")
|
||||||
activeCall.value = null
|
|
||||||
showCallView.value = false
|
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "CallManager.endCall: ending call...")
|
Log.d(TAG, "CallManager.endCall: ending call...")
|
||||||
callCommand.add(WCallCommand.End)
|
//callCommand.add(WCallCommand.End)
|
||||||
showCallView.value = false
|
|
||||||
controller.apiEndCall(call.remoteHostId, call.contact)
|
controller.apiEndCall(call.remoteHostId, call.contact)
|
||||||
activeCall.value = null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,11 +7,11 @@ import kotlinx.datetime.Instant
|
|||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.util.*
|
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
data class Call(
|
data class Call(
|
||||||
val remoteHostId: Long?,
|
val remoteHostId: Long?,
|
||||||
|
val userProfile: Profile,
|
||||||
val contact: Contact,
|
val contact: Contact,
|
||||||
val callState: CallState,
|
val callState: CallState,
|
||||||
val localMedia: CallMediaType,
|
val localMedia: CallMediaType,
|
||||||
@ -23,7 +23,7 @@ data class Call(
|
|||||||
val soundSpeaker: Boolean = localMedia == CallMediaType.Video,
|
val soundSpeaker: Boolean = localMedia == CallMediaType.Video,
|
||||||
var localCamera: VideoCamera = VideoCamera.User,
|
var localCamera: VideoCamera = VideoCamera.User,
|
||||||
val connectionInfo: ConnectionInfo? = null,
|
val connectionInfo: ConnectionInfo? = null,
|
||||||
var connectedAt: Instant? = null
|
var connectedAt: Instant? = null,
|
||||||
) {
|
) {
|
||||||
val encrypted: Boolean get() = localEncrypted && sharedKey != null
|
val encrypted: Boolean get() = localEncrypted && sharedKey != null
|
||||||
val localEncrypted: Boolean get() = localCapabilities?.encryption ?: false
|
val localEncrypted: Boolean get() = localCapabilities?.encryption ?: false
|
||||||
@ -36,6 +36,9 @@ data class Call(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val hasMedia: Boolean get() = callState == CallState.OfferSent || callState == CallState.Negotiated || callState == CallState.Connected
|
val hasMedia: Boolean get() = callState == CallState.OfferSent || callState == CallState.Negotiated || callState == CallState.Connected
|
||||||
|
|
||||||
|
fun supportsVideo(): Boolean = peerMedia == CallMediaType.Video || localMedia == CallMediaType.Video
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class CallState {
|
enum class CallState {
|
||||||
@ -75,6 +78,7 @@ sealed class WCallCommand {
|
|||||||
@Serializable @SerialName("media") data class Media(val media: CallMediaType, val enable: Boolean): WCallCommand()
|
@Serializable @SerialName("media") data class Media(val media: CallMediaType, val enable: Boolean): WCallCommand()
|
||||||
@Serializable @SerialName("camera") data class Camera(val camera: VideoCamera): WCallCommand()
|
@Serializable @SerialName("camera") data class Camera(val camera: VideoCamera): WCallCommand()
|
||||||
@Serializable @SerialName("description") data class Description(val state: String, val description: String): WCallCommand()
|
@Serializable @SerialName("description") data class Description(val state: String, val description: String): WCallCommand()
|
||||||
|
@Serializable @SerialName("layout") data class Layout(val layout: LayoutType): WCallCommand()
|
||||||
@Serializable @SerialName("end") object End: WCallCommand()
|
@Serializable @SerialName("end") object End: WCallCommand()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,6 +171,13 @@ enum class VideoCamera {
|
|||||||
val flipped: VideoCamera get() = if (this == User) Environment else User
|
val flipped: VideoCamera get() = if (this == User) Environment else User
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
enum class LayoutType {
|
||||||
|
@SerialName("default") Default,
|
||||||
|
@SerialName("localVideo") LocalVideo,
|
||||||
|
@SerialName("remoteVideo") RemoteVideo
|
||||||
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ConnectionState(
|
data class ConnectionState(
|
||||||
val connectionState: String,
|
val connectionState: String,
|
||||||
|
@ -301,7 +301,9 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
|
|||||||
withBGApi {
|
withBGApi {
|
||||||
val cInfo = chat.chatInfo
|
val cInfo = chat.chatInfo
|
||||||
if (cInfo is ChatInfo.Direct) {
|
if (cInfo is ChatInfo.Direct) {
|
||||||
chatModel.activeCall.value = Call(remoteHostId = chatRh, contact = cInfo.contact, callState = CallState.WaitCapabilities, localMedia = media)
|
val contactInfo = chatModel.controller.apiContactInfo(chat.remoteHostId, cInfo.contact.contactId)
|
||||||
|
val profile = contactInfo?.second ?: chatModel.currentUser.value?.profile?.toProfile() ?: return@withBGApi
|
||||||
|
chatModel.activeCall.value = Call(remoteHostId = chatRh, contact = cInfo.contact, callState = CallState.WaitCapabilities, localMedia = media, userProfile = profile)
|
||||||
chatModel.showCallView.value = true
|
chatModel.showCallView.value = true
|
||||||
chatModel.callCommand.add(WCallCommand.Capabilities(media))
|
chatModel.callCommand.add(WCallCommand.Capabilities(media))
|
||||||
}
|
}
|
||||||
@ -673,7 +675,7 @@ fun ChatInfoToolbar(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (activeCall?.contact?.id == chat.id) {
|
} else if (activeCall?.contact?.id == chat.id && appPlatform.isDesktop) {
|
||||||
barButtons.add {
|
barButtons.add {
|
||||||
val call = remember { chatModel.activeCall }.value
|
val call = remember { chatModel.activeCall }.value
|
||||||
val connectedAt = call?.connectedAt
|
val connectedAt = call?.connectedAt
|
||||||
|
@ -29,6 +29,7 @@ import chat.simplex.common.views.onboarding.WhatsNewView
|
|||||||
import chat.simplex.common.views.onboarding.shouldShowWhatsNew
|
import chat.simplex.common.views.onboarding.shouldShowWhatsNew
|
||||||
import chat.simplex.common.views.usersettings.SettingsView
|
import chat.simplex.common.views.usersettings.SettingsView
|
||||||
import chat.simplex.common.platform.*
|
import chat.simplex.common.platform.*
|
||||||
|
import chat.simplex.common.views.call.Call
|
||||||
import chat.simplex.common.views.newchat.*
|
import chat.simplex.common.views.newchat.*
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
@ -121,7 +122,12 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (searchText.value.text.isEmpty()) {
|
if (searchText.value.text.isEmpty()) {
|
||||||
DesktopActiveCallOverlayLayout(newChatSheetState)
|
if (appPlatform.isDesktop) {
|
||||||
|
val call = remember { chatModel.activeCall }.value
|
||||||
|
if (call != null) {
|
||||||
|
ActiveCallInteractiveArea(call, newChatSheetState)
|
||||||
|
}
|
||||||
|
}
|
||||||
// TODO disable this button and sheet for the duration of the switch
|
// TODO disable this button and sheet for the duration of the switch
|
||||||
tryOrShowError("NewChatSheet", error = {}) {
|
tryOrShowError("NewChatSheet", error = {}) {
|
||||||
NewChatSheet(chatModel, newChatSheetState, stopped, hideNewChatSheet)
|
NewChatSheet(chatModel, newChatSheetState, stopped, hideNewChatSheet)
|
||||||
@ -314,7 +320,7 @@ private fun ToggleFilterDisabledButton() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
expect fun DesktopActiveCallOverlayLayout(newChatSheetState: MutableStateFlow<AnimatedViewState>)
|
expect fun ActiveCallInteractiveArea(call: Call, newChatSheetState: MutableStateFlow<AnimatedViewState>)
|
||||||
|
|
||||||
fun connectIfOpenedViaUri(rhId: Long?, uri: URI, chatModel: ChatModel) {
|
fun connectIfOpenedViaUri(rhId: Long?, uri: URI, chatModel: ChatModel) {
|
||||||
Log.d(TAG, "connectIfOpenedViaUri: opened via link")
|
Log.d(TAG, "connectIfOpenedViaUri: opened via link")
|
||||||
|
@ -85,7 +85,7 @@ private fun ShareListToolbar(chatModel: ChatModel, userPickerState: MutableState
|
|||||||
userPickerState.value = AnimatedViewState.VISIBLE
|
userPickerState.value = AnimatedViewState.VISIBLE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> NavigationButtonBack { chatModel.sharedContent.value = null }
|
else -> NavigationButtonBack(onButtonClicked = { chatModel.sharedContent.value = null })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (chatModel.chats.size >= 8) {
|
if (chatModel.chats.size >= 8) {
|
||||||
@ -143,7 +143,7 @@ private fun ShareList(chatModel: ChatModel, search: String) {
|
|||||||
}
|
}
|
||||||
val chats by remember(search) {
|
val chats by remember(search) {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
if (search.isEmpty()) chatModel.chats.filter { it.chatInfo.ready } else chatModel.chats.filter { it.chatInfo.ready }.filter(filter)
|
if (search.isEmpty()) chatModel.chats.toList().filter { it.chatInfo.ready } else chatModel.chats.toList().filter { it.chatInfo.ready }.filter(filter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
|
@ -18,7 +18,7 @@ import chat.simplex.res.MR
|
|||||||
import dev.icerock.moko.resources.compose.painterResource
|
import dev.icerock.moko.resources.compose.painterResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CloseSheetBar(close: (() -> Unit)?, showClose: Boolean = true, endButtons: @Composable RowScope.() -> Unit = {}) {
|
fun CloseSheetBar(close: (() -> Unit)?, showClose: Boolean = true, tintColor: Color = if (close != null) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, endButtons: @Composable RowScope.() -> Unit = {}) {
|
||||||
Column(
|
Column(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@ -35,7 +35,7 @@ fun CloseSheetBar(close: (() -> Unit)?, showClose: Boolean = true, endButtons: @
|
|||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
if (showClose) {
|
if (showClose) {
|
||||||
NavigationButtonBack(onButtonClicked = close)
|
NavigationButtonBack(tintColor = tintColor, onButtonClicked = close)
|
||||||
} else {
|
} else {
|
||||||
Spacer(Modifier)
|
Spacer(Modifier)
|
||||||
}
|
}
|
||||||
|
@ -44,10 +44,10 @@ fun DefaultTopAppBar(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NavigationButtonBack(onButtonClicked: (() -> Unit)?) {
|
fun NavigationButtonBack(onButtonClicked: (() -> Unit)?, tintColor: Color = if (onButtonClicked != null) MaterialTheme.colors.primary else MaterialTheme.colors.secondary) {
|
||||||
IconButton(onButtonClicked ?: {}, enabled = onButtonClicked != null) {
|
IconButton(onButtonClicked ?: {}, enabled = onButtonClicked != null) {
|
||||||
Icon(
|
Icon(
|
||||||
painterResource(MR.images.ic_arrow_back_ios_new), stringResource(MR.strings.back), tint = if (onButtonClicked != null) MaterialTheme.colors.primary else MaterialTheme.colors.secondary
|
painterResource(MR.images.ic_arrow_back_ios_new), stringResource(MR.strings.back), tint = tintColor
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ fun ModalView(
|
|||||||
}
|
}
|
||||||
Surface(Modifier.fillMaxSize(), contentColor = LocalContentColor.current) {
|
Surface(Modifier.fillMaxSize(), contentColor = LocalContentColor.current) {
|
||||||
Column(if (background != MaterialTheme.colors.background) Modifier.background(background) else Modifier.themedBackground()) {
|
Column(if (background != MaterialTheme.colors.background) Modifier.background(background) else Modifier.themedBackground()) {
|
||||||
CloseSheetBar(close, showClose, endButtons)
|
CloseSheetBar(close, showClose, endButtons = endButtons)
|
||||||
Box(modifier) { content() }
|
Box(modifier) { content() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -419,7 +419,7 @@ expect fun ByteArray.toBase64StringForPassphrase(): String
|
|||||||
|
|
||||||
// Android's default implementation that was used before multiplatform, adds non-needed characters at the end of string
|
// Android's default implementation that was used before multiplatform, adds non-needed characters at the end of string
|
||||||
// which can be bypassed by:
|
// which can be bypassed by:
|
||||||
// fun String.toByteArrayFromBase64(): ByteArray = Base64.getDecoder().decode(this.trimEnd { it == '\n' || it == ' ' })
|
// fun String.toByteArrayFromBase64(): ByteArray = Base64.getMimeDecoder().decode(this.trimEnd { it == '\n' || it == ' ' })
|
||||||
expect fun String.toByteArrayFromBase64ForPassphrase(): ByteArray
|
expect fun String.toByteArrayFromBase64ForPassphrase(): ByteArray
|
||||||
|
|
||||||
val LongRange.Companion.saver
|
val LongRange.Companion.saver
|
||||||
|
@ -179,6 +179,9 @@
|
|||||||
<!-- SimpleX Chat foreground Service -->
|
<!-- SimpleX Chat foreground Service -->
|
||||||
<string name="simplex_service_notification_title">SimpleX Chat service</string>
|
<string name="simplex_service_notification_title">SimpleX Chat service</string>
|
||||||
<string name="simplex_service_notification_text">Receiving messages…</string>
|
<string name="simplex_service_notification_text">Receiving messages…</string>
|
||||||
|
<string name="call_service_notification_audio_call">Audio call</string>
|
||||||
|
<string name="call_service_notification_video_call">Video call</string>
|
||||||
|
<string name="call_service_notification_end_call">End call</string>
|
||||||
<string name="hide_notification">Hide</string>
|
<string name="hide_notification">Hide</string>
|
||||||
|
|
||||||
<!-- Notification channels -->
|
<!-- Notification channels -->
|
||||||
@ -803,6 +806,10 @@
|
|||||||
<string name="callstate_connected">connected</string>
|
<string name="callstate_connected">connected</string>
|
||||||
<string name="callstate_ended">ended</string>
|
<string name="callstate_ended">ended</string>
|
||||||
|
|
||||||
|
<!-- CallView -->
|
||||||
|
<string name="unable_to_open_browser_title">Error opening browser</string>
|
||||||
|
<string name="unable_to_open_browser_desc">The default web browser is required for calls. Please configure the default browser in the system, and share more information with the developers.</string>
|
||||||
|
|
||||||
<!-- SimpleXInfo -->
|
<!-- SimpleXInfo -->
|
||||||
<string name="next_generation_of_private_messaging">The next generation of private messaging</string>
|
<string name="next_generation_of_private_messaging">The next generation of private messaging</string>
|
||||||
<string name="privacy_redefined">Privacy redefined</string>
|
<string name="privacy_redefined">Privacy redefined</string>
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<video
|
<video
|
||||||
id="remote-video-stream"
|
id="remote-video-stream"
|
||||||
|
class="inline"
|
||||||
autoplay
|
autoplay
|
||||||
playsinline
|
playsinline
|
||||||
poster="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAQAAAAnOwc2AAAAEUlEQVR42mNk+M+AARiHsiAAcCIKAYwFoQ8AAAAASUVORK5CYII="
|
poster="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAQAAAAnOwc2AAAAEUlEQVR42mNk+M+AARiHsiAAcCIKAYwFoQ8AAAAASUVORK5CYII="
|
||||||
@ -15,6 +16,7 @@
|
|||||||
></video>
|
></video>
|
||||||
<video
|
<video
|
||||||
id="local-video-stream"
|
id="local-video-stream"
|
||||||
|
class="inline"
|
||||||
muted
|
muted
|
||||||
autoplay
|
autoplay
|
||||||
playsinline
|
playsinline
|
||||||
|
@ -5,14 +5,14 @@ body {
|
|||||||
background-color: black;
|
background-color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
#remote-video-stream {
|
#remote-video-stream.inline {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
#local-video-stream {
|
#local-video-stream.inline {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 30%;
|
width: 30%;
|
||||||
max-width: 30%;
|
max-width: 30%;
|
||||||
@ -23,6 +23,20 @@ body {
|
|||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#remote-video-stream.fullscreen {
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
#local-video-stream.fullscreen {
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
*::-webkit-media-controls {
|
*::-webkit-media-controls {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
-webkit-appearance: none !important;
|
-webkit-appearance: none !important;
|
||||||
|
@ -11,6 +11,12 @@ var VideoCamera;
|
|||||||
VideoCamera["User"] = "user";
|
VideoCamera["User"] = "user";
|
||||||
VideoCamera["Environment"] = "environment";
|
VideoCamera["Environment"] = "environment";
|
||||||
})(VideoCamera || (VideoCamera = {}));
|
})(VideoCamera || (VideoCamera = {}));
|
||||||
|
var LayoutType;
|
||||||
|
(function (LayoutType) {
|
||||||
|
LayoutType["Default"] = "default";
|
||||||
|
LayoutType["LocalVideo"] = "localVideo";
|
||||||
|
LayoutType["RemoteVideo"] = "remoteVideo";
|
||||||
|
})(LayoutType || (LayoutType = {}));
|
||||||
// for debugging
|
// for debugging
|
||||||
// var sendMessageToNative = ({resp}: WVApiMessage) => console.log(JSON.stringify({command: resp}))
|
// var sendMessageToNative = ({resp}: WVApiMessage) => console.log(JSON.stringify({command: resp}))
|
||||||
var sendMessageToNative = (msg) => console.log(JSON.stringify(msg));
|
var sendMessageToNative = (msg) => console.log(JSON.stringify(msg));
|
||||||
@ -319,6 +325,10 @@ const processCommand = (function () {
|
|||||||
localizedDescription = command.description;
|
localizedDescription = command.description;
|
||||||
resp = { type: "ok" };
|
resp = { type: "ok" };
|
||||||
break;
|
break;
|
||||||
|
case "layout":
|
||||||
|
changeLayout(command.layout);
|
||||||
|
resp = { type: "ok" };
|
||||||
|
break;
|
||||||
case "end":
|
case "end":
|
||||||
endCall();
|
endCall();
|
||||||
resp = { type: "ok" };
|
resp = { type: "ok" };
|
||||||
@ -607,6 +617,28 @@ function toggleMedia(s, media) {
|
|||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
function changeLayout(layout) {
|
||||||
|
const local = document.getElementById("local-video-stream");
|
||||||
|
const remote = document.getElementById("remote-video-stream");
|
||||||
|
switch (layout) {
|
||||||
|
case LayoutType.Default:
|
||||||
|
local.className = "inline";
|
||||||
|
remote.className = "inline";
|
||||||
|
local.style.visibility = "visible";
|
||||||
|
remote.style.visibility = "visible";
|
||||||
|
break;
|
||||||
|
case LayoutType.LocalVideo:
|
||||||
|
local.className = "fullscreen";
|
||||||
|
local.style.visibility = "visible";
|
||||||
|
remote.style.visibility = "hidden";
|
||||||
|
break;
|
||||||
|
case LayoutType.RemoteVideo:
|
||||||
|
remote.className = "fullscreen";
|
||||||
|
local.style.visibility = "hidden";
|
||||||
|
remote.style.visibility = "visible";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
// Cryptography function - it is loaded both in the main window and in worker context (if the worker is used)
|
// Cryptography function - it is loaded both in the main window and in worker context (if the worker is used)
|
||||||
function callCryptoFunction() {
|
function callCryptoFunction() {
|
||||||
const initialPlainTextRequired = {
|
const initialPlainTextRequired = {
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<video
|
<video
|
||||||
id="remote-video-stream"
|
id="remote-video-stream"
|
||||||
|
class="inline"
|
||||||
autoplay
|
autoplay
|
||||||
playsinline
|
playsinline
|
||||||
poster="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAQAAAAnOwc2AAAAEUlEQVR42mNk+M+AARiHsiAAcCIKAYwFoQ8AAAAASUVORK5CYII="
|
poster="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAQAAAAnOwc2AAAAEUlEQVR42mNk+M+AARiHsiAAcCIKAYwFoQ8AAAAASUVORK5CYII="
|
||||||
@ -16,6 +17,7 @@
|
|||||||
></video>
|
></video>
|
||||||
<video
|
<video
|
||||||
id="local-video-stream"
|
id="local-video-stream"
|
||||||
|
class="inline"
|
||||||
muted
|
muted
|
||||||
autoplay
|
autoplay
|
||||||
playsinline
|
playsinline
|
||||||
|
@ -5,14 +5,14 @@ body {
|
|||||||
background-color: black;
|
background-color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
#remote-video-stream {
|
#remote-video-stream.inline {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
#local-video-stream {
|
#local-video-stream.inline {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 20%;
|
width: 20%;
|
||||||
max-width: 20%;
|
max-width: 20%;
|
||||||
@ -23,6 +23,20 @@ body {
|
|||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#remote-video-stream.fullscreen {
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
#local-video-stream.fullscreen {
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
*::-webkit-media-controls {
|
*::-webkit-media-controls {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
-webkit-appearance: none !important;
|
-webkit-appearance: none !important;
|
||||||
|
@ -17,14 +17,14 @@ import javax.imageio.stream.MemoryCacheImageOutputStream
|
|||||||
import kotlin.math.sqrt
|
import kotlin.math.sqrt
|
||||||
|
|
||||||
private fun errorBitmap(): ImageBitmap =
|
private fun errorBitmap(): ImageBitmap =
|
||||||
ImageIO.read(ByteArrayInputStream(Base64.getDecoder().decode("iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAKVJREFUeF7t1kENACEUQ0FQhnVQ9lfGO+xggITQdvbMzArPey+8fa3tAfwAEdABZQspQStgBssEcgAIkSAJkiAJljtEgiRIgmUCSZAESZAESZAEyx0iQRIkwTKBJEiCv5fgvTd1wDmn7QAP4AeIgA4oW0gJWgEzWCZwbQ7gAA7ggLKFOIADOKBMIAeAEAmSIAmSYLlDJEiCJFgmkARJkARJ8N8S/ADTZUewBvnTOQAAAABJRU5ErkJggg=="))).toComposeImageBitmap()
|
ImageIO.read(ByteArrayInputStream(Base64.getMimeDecoder().decode("iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAKVJREFUeF7t1kENACEUQ0FQhnVQ9lfGO+xggITQdvbMzArPey+8fa3tAfwAEdABZQspQStgBssEcgAIkSAJkiAJljtEgiRIgmUCSZAESZAESZAEyx0iQRIkwTKBJEiCv5fgvTd1wDmn7QAP4AeIgA4oW0gJWgEzWCZwbQ7gAA7ggLKFOIADOKBMIAeAEAmSIAmSYLlDJEiCJFgmkARJkARJ8N8S/ADTZUewBvnTOQAAAABJRU5ErkJggg=="))).toComposeImageBitmap()
|
||||||
|
|
||||||
actual fun base64ToBitmap(base64ImageString: String): ImageBitmap {
|
actual fun base64ToBitmap(base64ImageString: String): ImageBitmap {
|
||||||
val imageString = base64ImageString
|
val imageString = base64ImageString
|
||||||
.removePrefix("data:image/png;base64,")
|
.removePrefix("data:image/png;base64,")
|
||||||
.removePrefix("data:image/jpg;base64,")
|
.removePrefix("data:image/jpg;base64,")
|
||||||
return try {
|
return try {
|
||||||
ImageIO.read(ByteArrayInputStream(Base64.getDecoder().decode(imageString))).toComposeImageBitmap()
|
ImageIO.read(ByteArrayInputStream(Base64.getMimeDecoder().decode(imageString))).toComposeImageBitmap()
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
Log.e(TAG, "base64ToBitmap error: $e")
|
Log.e(TAG, "base64ToBitmap error: $e")
|
||||||
errorBitmap()
|
errorBitmap()
|
||||||
@ -77,7 +77,7 @@ actual fun compressImageStr(bitmap: ImageBitmap): String {
|
|||||||
return try {
|
return try {
|
||||||
val encoded = Base64.getEncoder().encodeToString(compressImageData(bitmap, usePng).toByteArray())
|
val encoded = Base64.getEncoder().encodeToString(compressImageData(bitmap, usePng).toByteArray())
|
||||||
"data:image/$ext;base64,$encoded"
|
"data:image/$ext;base64,$encoded"
|
||||||
} catch (e: IOException) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "resizeImageToStrSize error: $e")
|
Log.e(TAG, "resizeImageToStrSize error: $e")
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
|
@ -146,8 +146,21 @@ private fun SendStateUpdates() {
|
|||||||
@Composable
|
@Composable
|
||||||
fun WebRTCController(callCommand: SnapshotStateList<WCallCommand>, onResponse: (WVAPIMessage) -> Unit) {
|
fun WebRTCController(callCommand: SnapshotStateList<WCallCommand>, onResponse: (WVAPIMessage) -> Unit) {
|
||||||
val uriHandler = LocalUriHandler.current
|
val uriHandler = LocalUriHandler.current
|
||||||
|
val endCall = {
|
||||||
|
val call = chatModel.activeCall.value
|
||||||
|
if (call != null) withBGApi { chatModel.callManager.endCall(call) }
|
||||||
|
}
|
||||||
val server = remember {
|
val server = remember {
|
||||||
uriHandler.openUri("http://${SERVER_HOST}:$SERVER_PORT/simplex/call/")
|
try {
|
||||||
|
uriHandler.openUri("http://${SERVER_HOST}:$SERVER_PORT/simplex/call/")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Unable to open browser: ${e.stackTraceToString()}")
|
||||||
|
AlertManager.shared.showAlertMsg(
|
||||||
|
title = generalGetString(MR.strings.unable_to_open_browser_title),
|
||||||
|
text = generalGetString(MR.strings.unable_to_open_browser_desc)
|
||||||
|
)
|
||||||
|
endCall()
|
||||||
|
}
|
||||||
startServer(onResponse)
|
startServer(onResponse)
|
||||||
}
|
}
|
||||||
fun processCommand(cmd: WCallCommand) {
|
fun processCommand(cmd: WCallCommand) {
|
||||||
|
@ -3,7 +3,6 @@ package chat.simplex.common.views.chatlist
|
|||||||
import androidx.compose.foundation.*
|
import androidx.compose.foundation.*
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material.Icon
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
@ -13,6 +12,7 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import chat.simplex.common.platform.*
|
import chat.simplex.common.platform.*
|
||||||
import chat.simplex.common.ui.theme.*
|
import chat.simplex.common.ui.theme.*
|
||||||
|
import chat.simplex.common.views.call.Call
|
||||||
import chat.simplex.common.views.call.CallMediaType
|
import chat.simplex.common.views.call.CallMediaType
|
||||||
import chat.simplex.common.views.chat.item.ItemAction
|
import chat.simplex.common.views.chat.item.ItemAction
|
||||||
import chat.simplex.common.views.helpers.*
|
import chat.simplex.common.views.helpers.*
|
||||||
@ -22,10 +22,9 @@ import dev.icerock.moko.resources.compose.stringResource
|
|||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
actual fun DesktopActiveCallOverlayLayout(newChatSheetState: MutableStateFlow<AnimatedViewState>) {
|
actual fun ActiveCallInteractiveArea(call: Call, newChatSheetState: MutableStateFlow<AnimatedViewState>) {
|
||||||
val call = remember { chatModel.activeCall}.value
|
// if (call.callState == CallState.Connected && !newChatSheetState.collectAsState().value.isVisible()) {
|
||||||
// if (call?.callState == CallState.Connected && !newChatSheetState.collectAsState().value.isVisible()) {
|
if (!newChatSheetState.collectAsState().value.isVisible()) {
|
||||||
if (call != null && !newChatSheetState.collectAsState().value.isVisible()) {
|
|
||||||
val showMenu = remember { mutableStateOf(false) }
|
val showMenu = remember { mutableStateOf(false) }
|
||||||
val media = call.peerMedia ?: call.localMedia
|
val media = call.peerMedia ?: call.localMedia
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
|
697
flake.lock
697
flake.lock
@ -16,6 +16,21 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"blank": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1625557891,
|
||||||
|
"narHash": "sha256-O8/MWsPBGhhyPoPLHZAuoZiiHo9q6FLlEeIDEXuj6T4=",
|
||||||
|
"owner": "divnix",
|
||||||
|
"repo": "blank",
|
||||||
|
"rev": "5a5d2684073d9f563072ed07c871d577a6c614a8",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "divnix",
|
||||||
|
"repo": "blank",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"cabal-32": {
|
"cabal-32": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
@ -83,6 +98,64 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"devshell": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": [
|
||||||
|
"haskellNix",
|
||||||
|
"tullia",
|
||||||
|
"std",
|
||||||
|
"flake-utils"
|
||||||
|
],
|
||||||
|
"nixpkgs": [
|
||||||
|
"haskellNix",
|
||||||
|
"tullia",
|
||||||
|
"std",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1663445644,
|
||||||
|
"narHash": "sha256-+xVlcK60x7VY1vRJbNUEAHi17ZuoQxAIH4S4iUFUGBA=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "devshell",
|
||||||
|
"rev": "e3dc3e21594fe07bdb24bdf1c8657acaa4cb8f66",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "devshell",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dmerge": {
|
||||||
|
"inputs": {
|
||||||
|
"nixlib": [
|
||||||
|
"haskellNix",
|
||||||
|
"tullia",
|
||||||
|
"std",
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"yants": [
|
||||||
|
"haskellNix",
|
||||||
|
"tullia",
|
||||||
|
"std",
|
||||||
|
"yants"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1659548052,
|
||||||
|
"narHash": "sha256-fzI2gp1skGA8mQo/FBFrUAtY0GQkAIAaV/V127TJPyY=",
|
||||||
|
"owner": "divnix",
|
||||||
|
"repo": "data-merge",
|
||||||
|
"rev": "d160d18ce7b1a45b88344aa3f13ed1163954b497",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "divnix",
|
||||||
|
"repo": "data-merge",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"flake-compat": {
|
"flake-compat": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
@ -100,34 +173,74 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"flake-parts": {
|
"flake-compat_2": {
|
||||||
"inputs": {
|
"flake": false,
|
||||||
"nixpkgs-lib": "nixpkgs-lib"
|
|
||||||
},
|
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1698579227,
|
"lastModified": 1650374568,
|
||||||
"narHash": "sha256-KVWjFZky+gRuWennKsbo6cWyo7c/z/VgCte5pR9pEKg=",
|
"narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=",
|
||||||
"owner": "hercules-ci",
|
"owner": "edolstra",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-compat",
|
||||||
"rev": "f76e870d64779109e41370848074ac4eaa1606ec",
|
"rev": "b4a34015c698c7793d592d66adbab377907a2be8",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "hercules-ci",
|
"owner": "edolstra",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-compat",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"flake-utils": {
|
"flake-utils": {
|
||||||
"inputs": {
|
|
||||||
"systems": "systems"
|
|
||||||
},
|
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1701680307,
|
"lastModified": 1676283394,
|
||||||
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
|
"narHash": "sha256-XX2f9c3iySLCw54rJ/CZs+ZK6IQy7GXNY4nSOyu2QG4=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
|
"rev": "3db36a8b464d0c4532ba1c7dda728f4576d6d073",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1667395993,
|
||||||
|
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils_3": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1653893745,
|
||||||
|
"narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils_4": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1659877975,
|
||||||
|
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -153,51 +266,33 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ghc98X": {
|
"gomod2nix": {
|
||||||
"flake": false,
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs_2",
|
||||||
|
"utils": "utils"
|
||||||
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1696643148,
|
"lastModified": 1655245309,
|
||||||
"narHash": "sha256-E02DfgISH7EvvNAu0BHiPvl1E5FGMDi0pWdNZtIBC9I=",
|
"narHash": "sha256-d/YPoQ/vFn1+GTmSdvbSBSTOai61FONxB4+Lt6w/IVI=",
|
||||||
"ref": "ghc-9.8",
|
"owner": "tweag",
|
||||||
"rev": "443e870d977b1ab6fc05f47a9a17bc49296adbd6",
|
"repo": "gomod2nix",
|
||||||
"revCount": 61642,
|
"rev": "40d32f82fc60d66402eb0972e6e368aeab3faf58",
|
||||||
"submodules": true,
|
"type": "github"
|
||||||
"type": "git",
|
|
||||||
"url": "https://gitlab.haskell.org/ghc/ghc"
|
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"ref": "ghc-9.8",
|
"owner": "tweag",
|
||||||
"submodules": true,
|
"repo": "gomod2nix",
|
||||||
"type": "git",
|
"type": "github"
|
||||||
"url": "https://gitlab.haskell.org/ghc/ghc"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ghc99": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1697054644,
|
|
||||||
"narHash": "sha256-kKarOuXUaAH3QWv7ASx+gGFMHaHKe0pK5Zu37ky2AL4=",
|
|
||||||
"ref": "refs/heads/master",
|
|
||||||
"rev": "f383a242c76f90bcca8a4d7ee001dcb49c172a9a",
|
|
||||||
"revCount": 62040,
|
|
||||||
"submodules": true,
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://gitlab.haskell.org/ghc/ghc"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"submodules": true,
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://gitlab.haskell.org/ghc/ghc"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"hackage": {
|
"hackage": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1702513363,
|
"lastModified": 1702340598,
|
||||||
"narHash": "sha256-kloro9uEe8aYhPMoMjVNq2rfrXNgMOZhOPwVH5DH2K0=",
|
"narHash": "sha256-CC0HI+6iKPtH+8r/ZfcpW5v/OYvL7zMwpr0xfkXV1zU=",
|
||||||
"owner": "input-output-hk",
|
"owner": "input-output-hk",
|
||||||
"repo": "hackage.nix",
|
"repo": "hackage.nix",
|
||||||
"rev": "a9d931d0398da67846fa257922a924829233cb91",
|
"rev": "24617c569995e38bf3b83b48eec6628a50fdb4fb",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -214,40 +309,33 @@
|
|||||||
"cabal-36": "cabal-36",
|
"cabal-36": "cabal-36",
|
||||||
"cardano-shell": "cardano-shell",
|
"cardano-shell": "cardano-shell",
|
||||||
"flake-compat": "flake-compat",
|
"flake-compat": "flake-compat",
|
||||||
|
"flake-utils": "flake-utils_2",
|
||||||
"ghc-8.6.5-iohk": "ghc-8.6.5-iohk",
|
"ghc-8.6.5-iohk": "ghc-8.6.5-iohk",
|
||||||
"ghc98X": "ghc98X",
|
|
||||||
"ghc99": "ghc99",
|
|
||||||
"hackage": [
|
"hackage": [
|
||||||
"hackage"
|
"hackage"
|
||||||
],
|
],
|
||||||
"hls-1.10": "hls-1.10",
|
|
||||||
"hls-2.0": "hls-2.0",
|
|
||||||
"hls-2.2": "hls-2.2",
|
|
||||||
"hls-2.3": "hls-2.3",
|
|
||||||
"hls-2.4": "hls-2.4",
|
|
||||||
"hpc-coveralls": "hpc-coveralls",
|
"hpc-coveralls": "hpc-coveralls",
|
||||||
"hydra": "hydra",
|
"hydra": "hydra",
|
||||||
"iserv-proxy": "iserv-proxy",
|
"iserv-proxy": "iserv-proxy",
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"haskellNix",
|
"nixpkgs"
|
||||||
"nixpkgs-unstable"
|
|
||||||
],
|
],
|
||||||
"nixpkgs-2003": "nixpkgs-2003",
|
"nixpkgs-2003": "nixpkgs-2003",
|
||||||
"nixpkgs-2105": "nixpkgs-2105",
|
"nixpkgs-2105": "nixpkgs-2105",
|
||||||
"nixpkgs-2111": "nixpkgs-2111",
|
"nixpkgs-2111": "nixpkgs-2111",
|
||||||
"nixpkgs-2205": "nixpkgs-2205",
|
"nixpkgs-2205": "nixpkgs-2205",
|
||||||
"nixpkgs-2211": "nixpkgs-2211",
|
"nixpkgs-2211": "nixpkgs-2211",
|
||||||
"nixpkgs-2305": "nixpkgs-2305",
|
|
||||||
"nixpkgs-unstable": "nixpkgs-unstable",
|
"nixpkgs-unstable": "nixpkgs-unstable",
|
||||||
"old-ghc-nix": "old-ghc-nix",
|
"old-ghc-nix": "old-ghc-nix",
|
||||||
"stackage": "stackage"
|
"stackage": "stackage",
|
||||||
|
"tullia": "tullia"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1701163700,
|
"lastModified": 1677975916,
|
||||||
"narHash": "sha256-sOrewUS3LnzV09nGr7+3R6Q6zsgU4smJc61QsHq+4DE=",
|
"narHash": "sha256-dbe8lEEPyfzjdRwpePClv7J9p9lQg7BwbBqAMCw4RLw=",
|
||||||
"owner": "input-output-hk",
|
"owner": "input-output-hk",
|
||||||
"repo": "haskell.nix",
|
"repo": "haskell.nix",
|
||||||
"rev": "2808bfe3e62e9eb4ee8974cd623a00e1611f302b",
|
"rev": "ab5efd87ce3fd8ade38a01d97693d29a4f1ae7e4",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -257,91 +345,6 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"hls-1.10": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1680000865,
|
|
||||||
"narHash": "sha256-rc7iiUAcrHxwRM/s0ErEsSPxOR3u8t7DvFeWlMycWgo=",
|
|
||||||
"owner": "haskell",
|
|
||||||
"repo": "haskell-language-server",
|
|
||||||
"rev": "b08691db779f7a35ff322b71e72a12f6e3376fd9",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "haskell",
|
|
||||||
"ref": "1.10.0.0",
|
|
||||||
"repo": "haskell-language-server",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"hls-2.0": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1687698105,
|
|
||||||
"narHash": "sha256-OHXlgRzs/kuJH8q7Sxh507H+0Rb8b7VOiPAjcY9sM1k=",
|
|
||||||
"owner": "haskell",
|
|
||||||
"repo": "haskell-language-server",
|
|
||||||
"rev": "783905f211ac63edf982dd1889c671653327e441",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "haskell",
|
|
||||||
"ref": "2.0.0.1",
|
|
||||||
"repo": "haskell-language-server",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"hls-2.2": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1693064058,
|
|
||||||
"narHash": "sha256-8DGIyz5GjuCFmohY6Fa79hHA/p1iIqubfJUTGQElbNk=",
|
|
||||||
"owner": "haskell",
|
|
||||||
"repo": "haskell-language-server",
|
|
||||||
"rev": "b30f4b6cf5822f3112c35d14a0cba51f3fe23b85",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "haskell",
|
|
||||||
"ref": "2.2.0.0",
|
|
||||||
"repo": "haskell-language-server",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"hls-2.3": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1695910642,
|
|
||||||
"narHash": "sha256-tR58doOs3DncFehHwCLczJgntyG/zlsSd7DgDgMPOkI=",
|
|
||||||
"owner": "haskell",
|
|
||||||
"repo": "haskell-language-server",
|
|
||||||
"rev": "458ccdb55c9ea22cd5d13ec3051aaefb295321be",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "haskell",
|
|
||||||
"ref": "2.3.0.0",
|
|
||||||
"repo": "haskell-language-server",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"hls-2.4": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1696939266,
|
|
||||||
"narHash": "sha256-VOMf5+kyOeOmfXTHlv4LNFJuDGa7G3pDnOxtzYR40IU=",
|
|
||||||
"owner": "haskell",
|
|
||||||
"repo": "haskell-language-server",
|
|
||||||
"rev": "362fdd1293efb4b82410b676ab1273479f6d17ee",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "haskell",
|
|
||||||
"ref": "2.4.0.0",
|
|
||||||
"repo": "haskell-language-server",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"hpc-coveralls": {
|
"hpc-coveralls": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
@ -381,14 +384,37 @@
|
|||||||
"type": "indirect"
|
"type": "indirect"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"incl": {
|
||||||
|
"inputs": {
|
||||||
|
"nixlib": [
|
||||||
|
"haskellNix",
|
||||||
|
"tullia",
|
||||||
|
"std",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1669263024,
|
||||||
|
"narHash": "sha256-E/+23NKtxAqYG/0ydYgxlgarKnxmDbg6rCMWnOBqn9Q=",
|
||||||
|
"owner": "divnix",
|
||||||
|
"repo": "incl",
|
||||||
|
"rev": "ce7bebaee048e4cd7ebdb4cee7885e00c4e2abca",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "divnix",
|
||||||
|
"repo": "incl",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"iserv-proxy": {
|
"iserv-proxy": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1691634696,
|
"lastModified": 1670983692,
|
||||||
"narHash": "sha256-MZH2NznKC/gbgBu8NgIibtSUZeJ00HTLJ0PlWKCBHb0=",
|
"narHash": "sha256-avLo34JnI9HNyOuauK5R69usJm+GfW3MlyGlYxZhTgY=",
|
||||||
"ref": "hkm/remote-iserv",
|
"ref": "hkm/remote-iserv",
|
||||||
"rev": "43a979272d9addc29fbffc2e8542c5d96e993d73",
|
"rev": "50d0abb3317ac439a4e7495b185a64af9b7b9300",
|
||||||
"revCount": 14,
|
"revCount": 10,
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://gitlab.haskell.org/hamishmack/iserv-proxy.git"
|
"url": "https://gitlab.haskell.org/hamishmack/iserv-proxy.git"
|
||||||
},
|
},
|
||||||
@ -414,22 +440,32 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mac2ios": {
|
"n2c": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-parts": "flake-parts",
|
"flake-utils": [
|
||||||
"nixpkgs": "nixpkgs_2"
|
"haskellNix",
|
||||||
|
"tullia",
|
||||||
|
"std",
|
||||||
|
"flake-utils"
|
||||||
|
],
|
||||||
|
"nixpkgs": [
|
||||||
|
"haskellNix",
|
||||||
|
"tullia",
|
||||||
|
"std",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1699767871,
|
"lastModified": 1665039323,
|
||||||
"narHash": "sha256-kxeCUfwC/Vgh2FvVMlBUq0eVx1JvfHyN+5MPKUik9mE=",
|
"narHash": "sha256-SAh3ZjFGsaCI8FRzXQyp56qcGdAqgKEfJWPCQ0Sr7tQ=",
|
||||||
"owner": "zw3rk",
|
"owner": "nlewo",
|
||||||
"repo": "mobile-core-tools",
|
"repo": "nix2container",
|
||||||
"rev": "4dcb77d5ea896d749381806dfab5358851b08951",
|
"rev": "b008fe329ffb59b67bf9e7b08ede6ee792f2741a",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "zw3rk",
|
"owner": "nlewo",
|
||||||
"repo": "mobile-core-tools",
|
"repo": "nix2container",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -454,6 +490,95 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"nix-nomad": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-compat": "flake-compat_2",
|
||||||
|
"flake-utils": [
|
||||||
|
"haskellNix",
|
||||||
|
"tullia",
|
||||||
|
"nix2container",
|
||||||
|
"flake-utils"
|
||||||
|
],
|
||||||
|
"gomod2nix": "gomod2nix",
|
||||||
|
"nixpkgs": [
|
||||||
|
"haskellNix",
|
||||||
|
"tullia",
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"nixpkgs-lib": [
|
||||||
|
"haskellNix",
|
||||||
|
"tullia",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1658277770,
|
||||||
|
"narHash": "sha256-T/PgG3wUn8Z2rnzfxf2VqlR1CBjInPE0l1yVzXxPnt0=",
|
||||||
|
"owner": "tristanpemble",
|
||||||
|
"repo": "nix-nomad",
|
||||||
|
"rev": "054adcbdd0a836ae1c20951b67ed549131fd2d70",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "tristanpemble",
|
||||||
|
"repo": "nix-nomad",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nix2container": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils_3",
|
||||||
|
"nixpkgs": "nixpkgs_3"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1658567952,
|
||||||
|
"narHash": "sha256-XZ4ETYAMU7XcpEeAFP3NOl9yDXNuZAen/aIJ84G+VgA=",
|
||||||
|
"owner": "nlewo",
|
||||||
|
"repo": "nix2container",
|
||||||
|
"rev": "60bb43d405991c1378baf15a40b5811a53e32ffa",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nlewo",
|
||||||
|
"repo": "nix2container",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixago": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": [
|
||||||
|
"haskellNix",
|
||||||
|
"tullia",
|
||||||
|
"std",
|
||||||
|
"flake-utils"
|
||||||
|
],
|
||||||
|
"nixago-exts": [
|
||||||
|
"haskellNix",
|
||||||
|
"tullia",
|
||||||
|
"std",
|
||||||
|
"blank"
|
||||||
|
],
|
||||||
|
"nixpkgs": [
|
||||||
|
"haskellNix",
|
||||||
|
"tullia",
|
||||||
|
"std",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1661824785,
|
||||||
|
"narHash": "sha256-/PnwdWoO/JugJZHtDUioQp3uRiWeXHUdgvoyNbXesz8=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "nixago",
|
||||||
|
"rev": "8c1f9e5f1578d4b2ea989f618588d62a335083c3",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "nixago",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1657693803,
|
"lastModified": 1657693803,
|
||||||
@ -520,11 +645,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs-2205": {
|
"nixpkgs-2205": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1685573264,
|
"lastModified": 1672580127,
|
||||||
"narHash": "sha256-Zffu01pONhs/pqH07cjlF10NnMDLok8ix5Uk4rhOnZQ=",
|
"narHash": "sha256-3lW3xZslREhJogoOkjeZtlBtvFMyxHku7I/9IVehhT8=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "380be19fbd2d9079f677978361792cb25e8a3635",
|
"rev": "0874168639713f547c05947c76124f78441ea46c",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -536,11 +661,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs-2211": {
|
"nixpkgs-2211": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1688392541,
|
"lastModified": 1675730325,
|
||||||
"narHash": "sha256-lHrKvEkCPTUO+7tPfjIcb7Trk6k31rz18vkyqmkeJfY=",
|
"narHash": "sha256-uNvD7fzO5hNlltNQUAFBPlcEjNG5Gkbhl/ROiX+GZU4=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "ea4c80b39be4c09702b0cb3b42eab59e2ba4f24b",
|
"rev": "b7ce17b1ebf600a72178f6302c77b6382d09323f",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -550,40 +675,6 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs-2305": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1695416179,
|
|
||||||
"narHash": "sha256-610o1+pwbSu+QuF3GE0NU5xQdTHM3t9wyYhB9l94Cd8=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "715d72e967ec1dd5ecc71290ee072bcaf5181ed6",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixpkgs-23.05-darwin",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs-lib": {
|
|
||||||
"locked": {
|
|
||||||
"dir": "lib",
|
|
||||||
"lastModified": 1696019113,
|
|
||||||
"narHash": "sha256-X3+DKYWJm93DRSdC5M6K5hLqzSya9BjibtBsuARoPco=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "f5892ddac112a1e9b3612c39af1b72987ee5783a",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"dir": "lib",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixos-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs-regression": {
|
"nixpkgs-regression": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1643052045,
|
"lastModified": 1643052045,
|
||||||
@ -602,11 +693,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs-unstable": {
|
"nixpkgs-unstable": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1695318763,
|
"lastModified": 1675758091,
|
||||||
"narHash": "sha256-FHVPDRP2AfvsxAdc+AsgFJevMz5VBmnZglFUMlxBkcY=",
|
"narHash": "sha256-7gFSQbSVAFUHtGCNHPF7mPc5CcqDk9M2+inlVPZSneg=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "e12483116b3b51a185a33a272bf351e357ba9a99",
|
"rev": "747927516efcb5e31ba03b7ff32f61f6d47e7d87",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -618,20 +709,82 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs_2": {
|
"nixpkgs_2": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1698434055,
|
"lastModified": 1653581809,
|
||||||
"narHash": "sha256-Phxi5mUKSoL7A0IYUiYtkI9e8NcGaaV5PJEaJApU1Ko=",
|
"narHash": "sha256-Uvka0V5MTGbeOfWte25+tfRL3moECDh1VwokWSZUdoY=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "1a3c95e3b23b3cdb26750621c08cc2f1560cb883",
|
"rev": "83658b28fe638a170a19b8933aa008b30640fbd1",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"ref": "nixos-23.05",
|
"ref": "nixos-unstable",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"nixpkgs_3": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1654807842,
|
||||||
|
"narHash": "sha256-ADymZpr6LuTEBXcy6RtFHcUZdjKTBRTMYwu19WOx17E=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "fc909087cc3386955f21b4665731dbdaceefb1d8",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs_4": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1665087388,
|
||||||
|
"narHash": "sha256-FZFPuW9NWHJteATOf79rZfwfRn5fE0wi9kRzvGfDHPA=",
|
||||||
|
"owner": "nixos",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "95fda953f6db2e9496d2682c4fc7b82f959878f7",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nixos",
|
||||||
|
"ref": "nixpkgs-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs_5": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1676726892,
|
||||||
|
"narHash": "sha256-M7OYVR6dKmzmlebIjybFf3l18S2uur8lMyWWnHQooLY=",
|
||||||
|
"owner": "angerman",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "729469087592bdea58b360de59dadf6d58714c42",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "angerman",
|
||||||
|
"ref": "release-22.11",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nosys": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1667881534,
|
||||||
|
"narHash": "sha256-FhwJ15uPLRsvaxtt/bNuqE/ykMpNAPF0upozFKhTtXM=",
|
||||||
|
"owner": "divnix",
|
||||||
|
"repo": "nosys",
|
||||||
|
"rev": "2d0d5207f6a230e9d0f660903f8db9807b54814f",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "divnix",
|
||||||
|
"repo": "nosys",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"old-ghc-nix": {
|
"old-ghc-nix": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
@ -654,21 +807,17 @@
|
|||||||
"flake-utils": "flake-utils",
|
"flake-utils": "flake-utils",
|
||||||
"hackage": "hackage",
|
"hackage": "hackage",
|
||||||
"haskellNix": "haskellNix",
|
"haskellNix": "haskellNix",
|
||||||
"mac2ios": "mac2ios",
|
"nixpkgs": "nixpkgs_5"
|
||||||
"nixpkgs": [
|
|
||||||
"haskellNix",
|
|
||||||
"nixpkgs-2305"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"stackage": {
|
"stackage": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1699834215,
|
"lastModified": 1677888571,
|
||||||
"narHash": "sha256-g/JKy0BCvJaxPuYDl3QVc4OY8cFEomgG+hW/eEV470M=",
|
"narHash": "sha256-YkhRNOaN6QVagZo1cfykYV8KqkI8/q6r2F5+jypOma4=",
|
||||||
"owner": "input-output-hk",
|
"owner": "input-output-hk",
|
||||||
"repo": "stackage.nix",
|
"repo": "stackage.nix",
|
||||||
"rev": "47aacd04abcce6bad57f43cbbbd133538380248e",
|
"rev": "cb50e6fabdfb2d7e655059039012ad0623f06a27",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -677,18 +826,110 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"systems": {
|
"std": {
|
||||||
|
"inputs": {
|
||||||
|
"arion": [
|
||||||
|
"haskellNix",
|
||||||
|
"tullia",
|
||||||
|
"std",
|
||||||
|
"blank"
|
||||||
|
],
|
||||||
|
"blank": "blank",
|
||||||
|
"devshell": "devshell",
|
||||||
|
"dmerge": "dmerge",
|
||||||
|
"flake-utils": "flake-utils_4",
|
||||||
|
"incl": "incl",
|
||||||
|
"makes": [
|
||||||
|
"haskellNix",
|
||||||
|
"tullia",
|
||||||
|
"std",
|
||||||
|
"blank"
|
||||||
|
],
|
||||||
|
"microvm": [
|
||||||
|
"haskellNix",
|
||||||
|
"tullia",
|
||||||
|
"std",
|
||||||
|
"blank"
|
||||||
|
],
|
||||||
|
"n2c": "n2c",
|
||||||
|
"nixago": "nixago",
|
||||||
|
"nixpkgs": "nixpkgs_4",
|
||||||
|
"nosys": "nosys",
|
||||||
|
"yants": "yants"
|
||||||
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1681028828,
|
"lastModified": 1674526466,
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
"narHash": "sha256-tMTaS0bqLx6VJ+K+ZT6xqsXNpzvSXJTmogkraBGzymg=",
|
||||||
"owner": "nix-systems",
|
"owner": "divnix",
|
||||||
"repo": "default",
|
"repo": "std",
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
"rev": "516387e3d8d059b50e742a2ff1909ed3c8f82826",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "nix-systems",
|
"owner": "divnix",
|
||||||
"repo": "default",
|
"repo": "std",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tullia": {
|
||||||
|
"inputs": {
|
||||||
|
"nix-nomad": "nix-nomad",
|
||||||
|
"nix2container": "nix2container",
|
||||||
|
"nixpkgs": [
|
||||||
|
"haskellNix",
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"std": "std"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1675695930,
|
||||||
|
"narHash": "sha256-B7rEZ/DBUMlK1AcJ9ajnAPPxqXY6zW2SBX+51bZV0Ac=",
|
||||||
|
"owner": "input-output-hk",
|
||||||
|
"repo": "tullia",
|
||||||
|
"rev": "621365f2c725608f381b3ad5b57afef389fd4c31",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "input-output-hk",
|
||||||
|
"repo": "tullia",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"utils": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1653893745,
|
||||||
|
"narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"yants": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"haskellNix",
|
||||||
|
"tullia",
|
||||||
|
"std",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1667096281,
|
||||||
|
"narHash": "sha256-wRRec6ze0gJHmGn6m57/zhz/Kdvp9HS4Nl5fkQ+uIuA=",
|
||||||
|
"owner": "divnix",
|
||||||
|
"repo": "yants",
|
||||||
|
"rev": "d18f356ec25cb94dc9c275870c3a7927a10f8c3c",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "divnix",
|
||||||
|
"repo": "yants",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
301
flake.nix
301
flake.nix
@ -1,15 +1,15 @@
|
|||||||
{
|
{
|
||||||
description = "nix flake for simplex-chat";
|
description = "nix flake for simplex-chat";
|
||||||
|
inputs.nixpkgs.url = "github:angerman/nixpkgs/release-22.11";
|
||||||
inputs.haskellNix.url = "github:input-output-hk/haskell.nix/armv7a";
|
inputs.haskellNix.url = "github:input-output-hk/haskell.nix/armv7a";
|
||||||
inputs.nixpkgs.follows = "haskellNix/nixpkgs-2305";
|
inputs.haskellNix.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
inputs.mac2ios.url = "github:zw3rk/mobile-core-tools";
|
|
||||||
inputs.hackage = {
|
inputs.hackage = {
|
||||||
url = "github:input-output-hk/hackage.nix";
|
url = "github:input-output-hk/hackage.nix";
|
||||||
flake = false;
|
flake = false;
|
||||||
};
|
};
|
||||||
inputs.haskellNix.inputs.hackage.follows = "hackage";
|
inputs.haskellNix.inputs.hackage.follows = "hackage";
|
||||||
inputs.flake-utils.url = "github:numtide/flake-utils";
|
inputs.flake-utils.url = "github:numtide/flake-utils";
|
||||||
outputs = { self, haskellNix, nixpkgs, flake-utils, mac2ios, ... }:
|
outputs = { self, haskellNix, nixpkgs, flake-utils, ... }:
|
||||||
let systems = [ "x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin" ]; in
|
let systems = [ "x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin" ]; in
|
||||||
flake-utils.lib.eachSystem systems (system:
|
flake-utils.lib.eachSystem systems (system:
|
||||||
# this android26 overlay makes the pkgsCross.{aarch64-android,armv7a-android-prebuilt} to set stdVer to 26 (Android 8).
|
# this android26 overlay makes the pkgsCross.{aarch64-android,armv7a-android-prebuilt} to set stdVer to 26 (Android 8).
|
||||||
@ -30,7 +30,7 @@
|
|||||||
# `appendOverlays` with a singleton is identical to `extend`.
|
# `appendOverlays` with a singleton is identical to `extend`.
|
||||||
let pkgs = haskellNix.legacyPackages.${system}.appendOverlays [android26]; in
|
let pkgs = haskellNix.legacyPackages.${system}.appendOverlays [android26]; in
|
||||||
let drv' = { extra-modules, pkgs', ... }: pkgs'.haskell-nix.project {
|
let drv' = { extra-modules, pkgs', ... }: pkgs'.haskell-nix.project {
|
||||||
compiler-nix-name = "ghc963";
|
compiler-nix-name = "ghc8107";
|
||||||
index-state = "2023-12-12T00:00:00Z";
|
index-state = "2023-12-12T00:00:00Z";
|
||||||
# We need this, to specify we want the cabal project.
|
# We need this, to specify we want the cabal project.
|
||||||
# If the stack.yaml was dropped, this would not be necessary.
|
# If the stack.yaml was dropped, this would not be necessary.
|
||||||
@ -40,12 +40,9 @@
|
|||||||
src = ./.;
|
src = ./.;
|
||||||
};
|
};
|
||||||
sha256map = import ./scripts/nix/sha256map.nix;
|
sha256map = import ./scripts/nix/sha256map.nix;
|
||||||
modules = [
|
modules = [{
|
||||||
({ pkgs, lib, ...}: lib.mkIf (!pkgs.stdenv.hostPlatform.isWindows) {
|
|
||||||
# This patch adds `dl` as an extra-library to direct-sqlciper, which is needed
|
|
||||||
# on pretty much all unix platforms, but then blows up on windows m(
|
|
||||||
packages.direct-sqlcipher.patches = [ ./scripts/nix/direct-sqlcipher-2.3.27.patch ];
|
packages.direct-sqlcipher.patches = [ ./scripts/nix/direct-sqlcipher-2.3.27.patch ];
|
||||||
})
|
}
|
||||||
({ pkgs,lib, ... }: lib.mkIf (pkgs.stdenv.hostPlatform.isAndroid) {
|
({ pkgs,lib, ... }: lib.mkIf (pkgs.stdenv.hostPlatform.isAndroid) {
|
||||||
packages.simplex-chat.components.library.ghcOptions = [ "-pie" ];
|
packages.simplex-chat.components.library.ghcOptions = [ "-pie" ];
|
||||||
})] ++ extra-modules;
|
})] ++ extra-modules;
|
||||||
@ -67,9 +64,6 @@
|
|||||||
}); in
|
}); in
|
||||||
let iosPostInstall = bundleName: ''
|
let iosPostInstall = bundleName: ''
|
||||||
${pkgs.tree}/bin/tree $out
|
${pkgs.tree}/bin/tree $out
|
||||||
mkdir tmp
|
|
||||||
find ./dist -name "libHS*-ghc*.a" -exec cp {} tmp \;
|
|
||||||
(cd tmp; ${pkgs.tree}/bin/tree .; ar x libHS*.a; for o in *.o; do if /usr/bin/otool -xv $o|grep ldadd ; then echo $o; fi; done; cd ..; rm -fR tmp)
|
|
||||||
mkdir -p $out/_pkg
|
mkdir -p $out/_pkg
|
||||||
# copy over includes, we might want those, but maybe not.
|
# copy over includes, we might want those, but maybe not.
|
||||||
# cp -r $out/lib/*/*/include $out/_pkg/
|
# cp -r $out/lib/*/*/include $out/_pkg/
|
||||||
@ -80,18 +74,6 @@
|
|||||||
find ${pkgs.gmp6.override { withStatic = true; }}/lib -name "*.a" -exec cp {} $out/_pkg \;
|
find ${pkgs.gmp6.override { withStatic = true; }}/lib -name "*.a" -exec cp {} $out/_pkg \;
|
||||||
# There is no static libc
|
# There is no static libc
|
||||||
${pkgs.tree}/bin/tree $out/_pkg
|
${pkgs.tree}/bin/tree $out/_pkg
|
||||||
for pkg in $out/_pkg/*.a; do
|
|
||||||
chmod +w $pkg
|
|
||||||
${mac2ios.packages.${system}.mac2ios}/bin/mac2ios $pkg
|
|
||||||
chmod -w $pkg
|
|
||||||
done
|
|
||||||
|
|
||||||
mkdir tmp
|
|
||||||
find $out/_pkg -name "libHS*-ghc*.a" -exec cp {} tmp \;
|
|
||||||
(cd tmp; ${pkgs.tree}/bin/tree .; ar x libHS*.a; for o in *.o; do if /usr/bin/otool -xv $o|grep ldadd ; then echo $o; fi; done; cd ..; rm -fR tmp)
|
|
||||||
|
|
||||||
sha256sum $out/_pkg/*.a
|
|
||||||
|
|
||||||
(cd $out/_pkg; ${pkgs.zip}/bin/zip -r -9 $out/${bundleName}.zip *)
|
(cd $out/_pkg; ${pkgs.zip}/bin/zip -r -9 $out/${bundleName}.zip *)
|
||||||
rm -fR $out/_pkg
|
rm -fR $out/_pkg
|
||||||
mkdir -p $out/nix-support
|
mkdir -p $out/nix-support
|
||||||
@ -137,149 +119,13 @@
|
|||||||
hardeningDisable = [ "fortify" ];
|
hardeningDisable = [ "fortify" ];
|
||||||
}
|
}
|
||||||
);in {
|
);in {
|
||||||
# STATIC x86_64-linux
|
|
||||||
"${pkgs.pkgsCross.musl64.hostPlatform.system}-static:exe:simplex-chat" = (drv pkgs.pkgsCross.musl64).simplex-chat.components.exes.simplex-chat;
|
"${pkgs.pkgsCross.musl64.hostPlatform.system}-static:exe:simplex-chat" = (drv pkgs.pkgsCross.musl64).simplex-chat.components.exes.simplex-chat;
|
||||||
# STATIC i686-linux
|
"${pkgs.pkgsCross.musl32.hostPlatform.system}-static:exe:simplex-chat" = (drv pkgs.pkgsCross.musl32).simplex-chat.components.exes.simplex-chat;
|
||||||
"${pkgs.pkgsCross.musl32.hostPlatform.system}-static:exe:simplex-chat" = (drv' {
|
|
||||||
pkgs' = pkgs.pkgsCross.musl32;
|
|
||||||
extra-modules = [{
|
|
||||||
# 32 bit patches
|
|
||||||
packages.basement.patches = [
|
|
||||||
./scripts/nix/basement-pr-573.patch
|
|
||||||
];
|
|
||||||
packages.memory.patches = [
|
|
||||||
./scripts/nix/memory-pr-99.patch
|
|
||||||
];
|
|
||||||
}];
|
|
||||||
}).simplex-chat.components.exes.simplex-chat;
|
|
||||||
# WINDOWS x86_64-mingwW64
|
|
||||||
"${pkgs.pkgsCross.mingwW64.hostPlatform.system}:exe:simplex-chat" = (drv' {
|
|
||||||
pkgs' = pkgs.pkgsCross.mingwW64;
|
|
||||||
extra-modules = [{
|
|
||||||
packages.direct-sqlcipher.flags.openssl = true;
|
|
||||||
packages.bitvec.flags.simd = false;
|
|
||||||
packages.direct-sqlcipher.patches = [
|
|
||||||
./scripts/nix/direct-sqlcipher-2.3.27-win.patch
|
|
||||||
];
|
|
||||||
packages.direct-sqlcipher.components.library.libs = pkgs.lib.mkForce [
|
|
||||||
(pkgs.pkgsCross.mingwW64.openssl) #.override) # { static = true; enableKTLS = false; })
|
|
||||||
];
|
|
||||||
packages.simplexmq.components.library.libs = pkgs.lib.mkForce [
|
|
||||||
(pkgs.pkgsCross.mingwW64.openssl) #.override) # { static = true; enableKTLS = false; })
|
|
||||||
];
|
|
||||||
packages.unix-time.postPatch = ''
|
|
||||||
sed -i 's/mingwex//g' unix-time.cabal
|
|
||||||
'';
|
|
||||||
}];
|
|
||||||
}).simplex-chat.components.exes.simplex-chat.override {
|
|
||||||
postInstall = ''
|
|
||||||
set -x
|
|
||||||
${pkgs.tree}/bin/tree $out
|
|
||||||
mkdir -p $out/_pkg
|
|
||||||
cp $out/bin/* $out/_pkg
|
|
||||||
${pkgs.tree}/bin/tree $out/_pkg
|
|
||||||
(cd $out/_pkg; ${pkgs.zip}/bin/zip -r -9 $out/${pkgs.pkgsCross.mingwW64.hostPlatform.system}-simplex-chat.zip *)
|
|
||||||
rm -fR $out/_pkg
|
|
||||||
mkdir -p $out/nix-support
|
|
||||||
echo "file binary-dist \"$(echo $out/*.zip)\"" \
|
|
||||||
> $out/nix-support/hydra-build-products
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
"${pkgs.pkgsCross.mingwW64.hostPlatform.system}:lib:simplex-chat" = (drv' rec {
|
|
||||||
pkgs' = pkgs.pkgsCross.mingwW64;
|
|
||||||
extra-modules = [{
|
|
||||||
packages.direct-sqlcipher.flags.openssl = true;
|
|
||||||
# simd will try to read __cpu_model, which we don't expose
|
|
||||||
# from the rts (yet!).
|
|
||||||
packages.bitvec.flags.simd = false;
|
|
||||||
packages.direct-sqlcipher.patches = [
|
|
||||||
./scripts/nix/direct-sqlcipher-2.3.27-win.patch
|
|
||||||
];
|
|
||||||
packages.direct-sqlcipher.components.library.libs = pkgs.lib.mkForce [
|
|
||||||
pkgs.pkgsCross.mingwW64.openssl
|
|
||||||
];
|
|
||||||
packages.simplexmq.components.library.libs = pkgs.lib.mkForce [
|
|
||||||
pkgs.pkgsCross.mingwW64.openssl
|
|
||||||
];
|
|
||||||
packages.unix-time.postPatch = ''
|
|
||||||
sed -i 's/mingwex//g' unix-time.cabal
|
|
||||||
'';
|
|
||||||
}];
|
|
||||||
}).simplex-chat.components.library
|
|
||||||
.override (p: {
|
|
||||||
# enableShared = false;
|
|
||||||
setupBuildFlags = p.component.setupBuildFlags ++ map (x: "--ghc-option=${x}") [
|
|
||||||
"-shared"
|
|
||||||
"-threaded"
|
|
||||||
"-o" "libsimplex.dll"
|
|
||||||
# "-optl-lHSrts_thr"
|
|
||||||
"-optl-lffi"
|
|
||||||
# "-optl-static-libgcc"
|
|
||||||
# We can't do -optl-static-libstdc++ with gcc. g++ might
|
|
||||||
# but then we are chaning the compiler altogether.
|
|
||||||
"${./libsimplex.dll.def}"
|
|
||||||
];
|
|
||||||
postInstall = ''
|
|
||||||
set -x
|
|
||||||
function deps() {
|
|
||||||
${pkgs.binutils}/bin/strings "$1" | grep '.\.dll'|grep -v -E 'Winsock|ADVAPI32|dbghelp|KERNEL32|msvcrt|ntdll|ole32|RPCRT4|SHELL32|USER32|WINMM|WS2_32|kernel32|GDI32'|grep -v "$1"
|
|
||||||
}
|
|
||||||
${pkgs.tree}/bin/tree $out
|
|
||||||
mkdir -p $out/_pkg
|
|
||||||
cp libsimplex.dll $out/_pkg
|
|
||||||
cp libsimplex.dll.a $out/_pkg
|
|
||||||
mkdir $out/libs
|
|
||||||
find ${pkgs.lib.getBin pkgs.pkgsCross.mingwW64.openssl} -name "*.dll" -exec cp {} $out/libs \;
|
|
||||||
find ${pkgs.lib.getBin pkgs.pkgsCross.mingwW64.libffi} -name "*.dll" -exec cp {} $out/libs \;
|
|
||||||
find ${pkgs.lib.getBin pkgs.pkgsCross.mingwW64.gmp} -name "*.dll" -exec cp {} $out/libs \;
|
|
||||||
find ${pkgs.lib.getBin pkgs.pkgsCross.mingwW64.stdenv.cc.cc} -name "*.dll" -exec cp {} $out/libs \;
|
|
||||||
find ${pkgs.lib.getBin pkgs.pkgsCross.mingwW64.windows.mcfgthreads} -name "*.dll" -exec cp {} $out/libs \;
|
|
||||||
|
|
||||||
pushd $out/_pkg
|
|
||||||
function copyDeps() {
|
|
||||||
for dep in $(deps "$1"); do
|
|
||||||
if [ ! -f "$dep" ]; then
|
|
||||||
if [ ! -f ../libs/"$dep" ]; then
|
|
||||||
echo "WARN: $1 -> $dep not found!"
|
|
||||||
else
|
|
||||||
cp ../libs/"$dep" .
|
|
||||||
copyDeps "$dep"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
}
|
|
||||||
copyDeps libsimplex.dll
|
|
||||||
popd
|
|
||||||
${pkgs.tree}/bin/tree $out/_pkg
|
|
||||||
(cd $out/_pkg; ${pkgs.zip}/bin/zip -r -9 $out/pkg-${pkgs.pkgsCross.mingwW64.hostPlatform.system}-libsimplex.zip *)
|
|
||||||
rm -fR $out/_pkg
|
|
||||||
mkdir -p $out/nix-support
|
|
||||||
echo "file binary-dist \"$(echo $out/*.zip)\"" \
|
|
||||||
> $out/nix-support/hydra-build-products
|
|
||||||
'';
|
|
||||||
});
|
|
||||||
# "${pkgs.pkgsCross.muslpi.hostPlatform.system}-static:exe:simplex-chat" = (drv pkgs.pkgsCross.muslpi).simplex-chat.components.exes.simplex-chat;
|
# "${pkgs.pkgsCross.muslpi.hostPlatform.system}-static:exe:simplex-chat" = (drv pkgs.pkgsCross.muslpi).simplex-chat.components.exes.simplex-chat;
|
||||||
|
|
||||||
# STATIC aarch64-linux
|
|
||||||
"${pkgs.pkgsCross.aarch64-multiplatform-musl.hostPlatform.system}-static:exe:simplex-chat" = (drv pkgs.pkgsCross.aarch64-multiplatform-musl).simplex-chat.components.exes.simplex-chat;
|
"${pkgs.pkgsCross.aarch64-multiplatform-musl.hostPlatform.system}-static:exe:simplex-chat" = (drv pkgs.pkgsCross.aarch64-multiplatform-musl).simplex-chat.components.exes.simplex-chat;
|
||||||
"armv7a-android:lib:support" = (drv android32Pkgs).android-support.components.library.override (p: {
|
"armv7a-android:lib:support" = (drv android32Pkgs).android-support.components.library.override {
|
||||||
smallAddressSpace = true;
|
smallAddressSpace = true; enableShared = false;
|
||||||
# we won't want -dyamic (see aarch64-android:lib:simplex-chat)
|
setupBuildFlags = map (x: "--ghc-option=${x}") [ "-shared" "-o" "libsupport.so" ];
|
||||||
enableShared = false;
|
|
||||||
# we also do not want to have any dependencies listed (especially no rts!)
|
|
||||||
enableStatic = false;
|
|
||||||
|
|
||||||
# This used to work with 8.10.7...
|
|
||||||
# setupBuildFlags = p.component.setupBuildFlags ++ map (x: "--ghc-option=${x}") [ "-shared" "-o" "libsupport.so" ];
|
|
||||||
# ... but now with 9.6+
|
|
||||||
# we have to do the -shared thing by hand.
|
|
||||||
postBuild = ''
|
|
||||||
armv7a-unknown-linux-androideabi-ghc -shared -o libsupport.so \
|
|
||||||
-optl-Wl,-u,setLineBuffering \
|
|
||||||
-optl-Wl,-u,pipe_std_to_socket \
|
|
||||||
dist/build/*.a
|
|
||||||
'';
|
|
||||||
|
|
||||||
postInstall = ''
|
postInstall = ''
|
||||||
|
|
||||||
mkdir -p $out/_pkg
|
mkdir -p $out/_pkg
|
||||||
@ -292,29 +138,14 @@
|
|||||||
echo "file binary-dist \"$(echo $out/*.zip)\"" \
|
echo "file binary-dist \"$(echo $out/*.zip)\"" \
|
||||||
> $out/nix-support/hydra-build-products
|
> $out/nix-support/hydra-build-products
|
||||||
'';
|
'';
|
||||||
});
|
};
|
||||||
# The android-support package is at
|
"aarch64-android:lib:support" = (drv androidPkgs).android-support.components.library.override {
|
||||||
# https://github.com/simplex-chat/android-support
|
smallAddressSpace = true; enableShared = false;
|
||||||
"aarch64-android:lib:support" = (drv androidPkgs).android-support.components.library.override (p: {
|
setupBuildFlags = map (x: "--ghc-option=${x}") [ "-shared" "-o" "libsupport.so" ];
|
||||||
smallAddressSpace = true;
|
|
||||||
# no -dynamic
|
|
||||||
enableShared = false;
|
|
||||||
# but also no -staticlib
|
|
||||||
enableStatic = false;
|
|
||||||
|
|
||||||
# we have to do the -shared thing by hand.
|
|
||||||
postBuild = ''
|
|
||||||
aarch64-unknown-linux-android-ghc -shared -o libsupport.so \
|
|
||||||
-optl-Wl,-u,setLineBuffering \
|
|
||||||
-optl-Wl,-u,pipe_std_to_socket \
|
|
||||||
dist/build/*.a
|
|
||||||
'';
|
|
||||||
|
|
||||||
postInstall = ''
|
postInstall = ''
|
||||||
|
|
||||||
mkdir -p $out/_pkg
|
mkdir -p $out/_pkg
|
||||||
cp libsupport.so $out/_pkg
|
cp libsupport.so $out/_pkg
|
||||||
ls -lah $out/_pkg/*
|
|
||||||
${pkgs.patchelf}/bin/patchelf --remove-needed libunwind.so.1 $out/_pkg/libsupport.so
|
${pkgs.patchelf}/bin/patchelf --remove-needed libunwind.so.1 $out/_pkg/libsupport.so
|
||||||
(cd $out/_pkg; ${pkgs.zip}/bin/zip -r -9 $out/pkg-aarch64-android-libsupport.zip *)
|
(cd $out/_pkg; ${pkgs.zip}/bin/zip -r -9 $out/pkg-aarch64-android-libsupport.zip *)
|
||||||
rm -fR $out/_pkg
|
rm -fR $out/_pkg
|
||||||
@ -323,11 +154,10 @@
|
|||||||
echo "file binary-dist \"$(echo $out/*.zip)\"" \
|
echo "file binary-dist \"$(echo $out/*.zip)\"" \
|
||||||
> $out/nix-support/hydra-build-products
|
> $out/nix-support/hydra-build-products
|
||||||
'';
|
'';
|
||||||
});
|
};
|
||||||
"armv7a-android:lib:simplex-chat" = (drv' {
|
"armv7a-android:lib:simplex-chat" = (drv' {
|
||||||
pkgs' = android32Pkgs;
|
pkgs' = android32Pkgs;
|
||||||
extra-modules = [{
|
extra-modules = [{
|
||||||
packages.text.flags.simdutf = false;
|
|
||||||
packages.direct-sqlcipher.flags.openssl = true;
|
packages.direct-sqlcipher.flags.openssl = true;
|
||||||
packages.direct-sqlcipher.components.library.libs = pkgs.lib.mkForce [
|
packages.direct-sqlcipher.components.library.libs = pkgs.lib.mkForce [
|
||||||
(android32Pkgs.openssl.override { static = true; enableKTLS = false; })
|
(android32Pkgs.openssl.override { static = true; enableKTLS = false; })
|
||||||
@ -338,56 +168,13 @@
|
|||||||
packages.simplexmq.components.library.libs = pkgs.lib.mkForce [
|
packages.simplexmq.components.library.libs = pkgs.lib.mkForce [
|
||||||
(android32Pkgs.openssl.override { static = true; enableKTLS = false; })
|
(android32Pkgs.openssl.override { static = true; enableKTLS = false; })
|
||||||
];
|
];
|
||||||
# 32 bit patches
|
|
||||||
packages.basement.patches = [
|
|
||||||
./scripts/nix/basement-pr-573.patch
|
|
||||||
];
|
|
||||||
packages.memory.patches = [
|
|
||||||
./scripts/nix/memory-pr-99.patch
|
|
||||||
];
|
|
||||||
}];
|
}];
|
||||||
}).simplex-chat.components.library.override (p: {
|
}).simplex-chat.components.library.override {
|
||||||
smallAddressSpace = true;
|
smallAddressSpace = true; enableShared = false;
|
||||||
# we want -shared, but not -dyanmic, hence `enableShared = false`.
|
|
||||||
enableShared = false;
|
|
||||||
# we _do_ want rts, and other libs. Hence `enableStatic = true`.
|
|
||||||
enableStatic = true;
|
|
||||||
# for android we build a shared library, passing these arguments is a bit tricky, as
|
# for android we build a shared library, passing these arguments is a bit tricky, as
|
||||||
# we want only the threaded rts (HSrts_thr) and ffi to be linked, but not fed into iserv for
|
# we want only the threaded rts (HSrts_thr) and ffi to be linked, but not fed into iserv for
|
||||||
# template haskell cross compilation. Thus we just pass them as linker options (-optl).
|
# template haskell cross compilation. Thus we just pass them as linker options (-optl).
|
||||||
setupBuildFlags = p.component.setupBuildFlags
|
setupBuildFlags = map (x: "--ghc-option=${x}") [ "-shared" "-o" "libsimplex.so" "-optl-lHSrts_thr" "-optl-lffi"];
|
||||||
# flags to tell GHC we want to produce a -shared object, and we want to also link
|
|
||||||
# - the ffi library (ffi)
|
|
||||||
++ map (x: "--ghc-option=${x}") [
|
|
||||||
"-shared" "-o" "libsimplex.so"
|
|
||||||
"-threaded"
|
|
||||||
# "-debug"
|
|
||||||
"-optl-lffi"
|
|
||||||
]
|
|
||||||
# This is fairly idiotic. LLD will strip out foreign exported
|
|
||||||
# symbols (a GHC bug? Codegen bug?). So we need to pass `-u <sym>`
|
|
||||||
# to ensure they stay in the produced library. Having them
|
|
||||||
# _undefined_ and _lazy_ (lld will tell with -y <sym> that the
|
|
||||||
# symbol is lazy), makes them _defined_. m(
|
|
||||||
++ map (sym: "--ghc-option=-optl-Wl,-u,${sym}") [
|
|
||||||
"chat_close_store"
|
|
||||||
"chat_decrypt_file"
|
|
||||||
"chat_decrypt_media"
|
|
||||||
"chat_encrypt_file"
|
|
||||||
"chat_encrypt_media"
|
|
||||||
"chat_migrate_init"
|
|
||||||
"chat_parse_markdown"
|
|
||||||
"chat_parse_server"
|
|
||||||
"chat_password_hash"
|
|
||||||
"chat_read_file"
|
|
||||||
"chat_recv_msg"
|
|
||||||
"chat_recv_msg_wait"
|
|
||||||
"chat_send_cmd"
|
|
||||||
"chat_send_remote_cmd"
|
|
||||||
"chat_valid_name"
|
|
||||||
"chat_json_length"
|
|
||||||
"chat_write_file"
|
|
||||||
];
|
|
||||||
postInstall = ''
|
postInstall = ''
|
||||||
set -x
|
set -x
|
||||||
${pkgs.tree}/bin/tree $out
|
${pkgs.tree}/bin/tree $out
|
||||||
@ -431,11 +218,10 @@
|
|||||||
echo "file binary-dist \"$(echo $out/*.zip)\"" \
|
echo "file binary-dist \"$(echo $out/*.zip)\"" \
|
||||||
> $out/nix-support/hydra-build-products
|
> $out/nix-support/hydra-build-products
|
||||||
'';
|
'';
|
||||||
});
|
};
|
||||||
"aarch64-android:lib:simplex-chat" = (drv' {
|
"aarch64-android:lib:simplex-chat" = (drv' {
|
||||||
pkgs' = androidPkgs;
|
pkgs' = androidPkgs;
|
||||||
extra-modules = [{
|
extra-modules = [{
|
||||||
packages.text.flags.simdutf = false;
|
|
||||||
packages.direct-sqlcipher.flags.openssl = true;
|
packages.direct-sqlcipher.flags.openssl = true;
|
||||||
packages.direct-sqlcipher.components.library.libs = pkgs.lib.mkForce [
|
packages.direct-sqlcipher.components.library.libs = pkgs.lib.mkForce [
|
||||||
(androidPkgs.openssl.override { static = true; })
|
(androidPkgs.openssl.override { static = true; })
|
||||||
@ -447,50 +233,12 @@
|
|||||||
(androidPkgs.openssl.override { static = true; })
|
(androidPkgs.openssl.override { static = true; })
|
||||||
];
|
];
|
||||||
}];
|
}];
|
||||||
}).simplex-chat.components.library.override (p: {
|
}).simplex-chat.components.library.override {
|
||||||
smallAddressSpace = true;
|
smallAddressSpace = true; enableShared = false;
|
||||||
# we do not want a dynamically linked object, even though we _do_
|
|
||||||
# want to produce a _shared_ object. But `shared` implied -dyanmic
|
|
||||||
# with cabal, so we disable and pass `-shared` explicitly.
|
|
||||||
enableShared = false;
|
|
||||||
# we do want static (e.g. pass all dependencies in, so we get -staticlib)
|
|
||||||
enableStatic = true;
|
|
||||||
# for android we build a shared library, passing these arguments is a bit tricky, as
|
# for android we build a shared library, passing these arguments is a bit tricky, as
|
||||||
# we want only the threaded rts (HSrts_thr) and ffi to be linked, but not fed into iserv for
|
# we want only the threaded rts (HSrts_thr) and ffi to be linked, but not fed into iserv for
|
||||||
# template haskell cross compilation. Thus we just pass them as linker options (-optl).
|
# template haskell cross compilation. Thus we just pass them as linker options (-optl).
|
||||||
setupBuildFlags = p.component.setupBuildFlags
|
setupBuildFlags = map (x: "--ghc-option=${x}") [ "-shared" "-o" "libsimplex.so" "-optl-lHSrts_thr" "-optl-lffi"];
|
||||||
# flags to tell GHC we want to produce a -shared object, and we want to also link
|
|
||||||
# - the ffi library (ffi)
|
|
||||||
++ map (x: "--ghc-option=${x}") [
|
|
||||||
"-shared" "-o" "libsimplex.so"
|
|
||||||
"-threaded"
|
|
||||||
# "-debug"
|
|
||||||
"-optl-lffi"
|
|
||||||
]
|
|
||||||
# This is fairly idiotic. LLD will strip out foreign exported
|
|
||||||
# symbols (a GHC bug? Codegen bug?). So we need to pass `-u <sym>`
|
|
||||||
# to ensure they stay in the produced library. Having them
|
|
||||||
# _undefined_ and _lazy_ (lld will tell with -y <sym> that the
|
|
||||||
# symbol is lazy), makes them _defined_. m(
|
|
||||||
++ map (sym: "--ghc-option=-optl-Wl,-u,${sym}") [
|
|
||||||
"chat_close_store"
|
|
||||||
"chat_decrypt_file"
|
|
||||||
"chat_decrypt_media"
|
|
||||||
"chat_encrypt_file"
|
|
||||||
"chat_encrypt_media"
|
|
||||||
"chat_migrate_init"
|
|
||||||
"chat_parse_markdown"
|
|
||||||
"chat_parse_server"
|
|
||||||
"chat_password_hash"
|
|
||||||
"chat_read_file"
|
|
||||||
"chat_recv_msg"
|
|
||||||
"chat_recv_msg_wait"
|
|
||||||
"chat_send_cmd"
|
|
||||||
"chat_send_remote_cmd"
|
|
||||||
"chat_valid_name"
|
|
||||||
"chat_json_length"
|
|
||||||
"chat_write_file"
|
|
||||||
];
|
|
||||||
postInstall = ''
|
postInstall = ''
|
||||||
set -x
|
set -x
|
||||||
${pkgs.tree}/bin/tree $out
|
${pkgs.tree}/bin/tree $out
|
||||||
@ -534,7 +282,7 @@
|
|||||||
echo "file binary-dist \"$(echo $out/*.zip)\"" \
|
echo "file binary-dist \"$(echo $out/*.zip)\"" \
|
||||||
> $out/nix-support/hydra-build-products
|
> $out/nix-support/hydra-build-products
|
||||||
'';
|
'';
|
||||||
});
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
# builds for iOS and iOS simulator
|
# builds for iOS and iOS simulator
|
||||||
@ -548,8 +296,7 @@
|
|||||||
packages.direct-sqlcipher.flags.commoncrypto = true;
|
packages.direct-sqlcipher.flags.commoncrypto = true;
|
||||||
packages.entropy.flags.DoNotGetEntropy = true;
|
packages.entropy.flags.DoNotGetEntropy = true;
|
||||||
packages.simplexmq.components.library.libs = pkgs.lib.mkForce [
|
packages.simplexmq.components.library.libs = pkgs.lib.mkForce [
|
||||||
# TODO: have a cross override for iOS, that sets this.
|
(pkgs.openssl.override { static = true; })
|
||||||
((pkgs.openssl.override { static = true; }).overrideDerivation (old: { CFLAGS = "-mcpu=apple-a7 -march=armv8-a+norcpc" ;}))
|
|
||||||
];
|
];
|
||||||
}];
|
}];
|
||||||
}).simplex-chat.components.library.override (
|
}).simplex-chat.components.library.override (
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<video
|
<video
|
||||||
id="remote-video-stream"
|
id="remote-video-stream"
|
||||||
|
class="inline"
|
||||||
autoplay
|
autoplay
|
||||||
playsinline
|
playsinline
|
||||||
poster="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAQAAAAnOwc2AAAAEUlEQVR42mNk+M+AARiHsiAAcCIKAYwFoQ8AAAAASUVORK5CYII="
|
poster="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAQAAAAnOwc2AAAAEUlEQVR42mNk+M+AARiHsiAAcCIKAYwFoQ8AAAAASUVORK5CYII="
|
||||||
@ -15,6 +16,7 @@
|
|||||||
></video>
|
></video>
|
||||||
<video
|
<video
|
||||||
id="local-video-stream"
|
id="local-video-stream"
|
||||||
|
class="inline"
|
||||||
muted
|
muted
|
||||||
autoplay
|
autoplay
|
||||||
playsinline
|
playsinline
|
||||||
|
@ -5,14 +5,14 @@ body {
|
|||||||
background-color: black;
|
background-color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
#remote-video-stream {
|
#remote-video-stream.inline {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
#local-video-stream {
|
#local-video-stream.inline {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 30%;
|
width: 30%;
|
||||||
max-width: 30%;
|
max-width: 30%;
|
||||||
@ -23,6 +23,20 @@ body {
|
|||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#remote-video-stream.fullscreen {
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
#local-video-stream.fullscreen {
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
*::-webkit-media-controls {
|
*::-webkit-media-controls {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
-webkit-appearance: none !important;
|
-webkit-appearance: none !important;
|
||||||
|
@ -16,6 +16,7 @@ type WCallCommand =
|
|||||||
| WCEnableMedia
|
| WCEnableMedia
|
||||||
| WCToggleCamera
|
| WCToggleCamera
|
||||||
| WCDescription
|
| WCDescription
|
||||||
|
| WCLayout
|
||||||
| WCEndCall
|
| WCEndCall
|
||||||
|
|
||||||
type WCallResponse =
|
type WCallResponse =
|
||||||
@ -31,7 +32,7 @@ type WCallResponse =
|
|||||||
| WRError
|
| WRError
|
||||||
| WCAcceptOffer
|
| WCAcceptOffer
|
||||||
|
|
||||||
type WCallCommandTag = "capabilities" | "start" | "offer" | "answer" | "ice" | "media" | "camera" | "description" | "end"
|
type WCallCommandTag = "capabilities" | "start" | "offer" | "answer" | "ice" | "media" | "camera" | "description" | "layout" | "end"
|
||||||
|
|
||||||
type WCallResponseTag = "capabilities" | "offer" | "answer" | "ice" | "connection" | "connected" | "end" | "ended" | "ok" | "error"
|
type WCallResponseTag = "capabilities" | "offer" | "answer" | "ice" | "connection" | "connected" | "end" | "ended" | "ok" | "error"
|
||||||
|
|
||||||
@ -45,6 +46,12 @@ enum VideoCamera {
|
|||||||
Environment = "environment",
|
Environment = "environment",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum LayoutType {
|
||||||
|
Default = "default",
|
||||||
|
LocalVideo = "localVideo",
|
||||||
|
RemoteVideo = "remoteVideo",
|
||||||
|
}
|
||||||
|
|
||||||
interface IWCallCommand {
|
interface IWCallCommand {
|
||||||
type: WCallCommandTag
|
type: WCallCommandTag
|
||||||
}
|
}
|
||||||
@ -115,6 +122,11 @@ interface WCDescription extends IWCallCommand {
|
|||||||
description: string
|
description: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface WCLayout extends IWCallCommand {
|
||||||
|
type: "layout"
|
||||||
|
layout: LayoutType
|
||||||
|
}
|
||||||
|
|
||||||
interface WRCapabilities extends IWCallResponse {
|
interface WRCapabilities extends IWCallResponse {
|
||||||
type: "capabilities"
|
type: "capabilities"
|
||||||
capabilities: CallCapabilities
|
capabilities: CallCapabilities
|
||||||
@ -515,6 +527,10 @@ const processCommand = (function () {
|
|||||||
localizedDescription = command.description
|
localizedDescription = command.description
|
||||||
resp = {type: "ok"}
|
resp = {type: "ok"}
|
||||||
break
|
break
|
||||||
|
case "layout":
|
||||||
|
changeLayout(command.layout)
|
||||||
|
resp = {type: "ok"}
|
||||||
|
break
|
||||||
case "end":
|
case "end":
|
||||||
endCall()
|
endCall()
|
||||||
resp = {type: "ok"}
|
resp = {type: "ok"}
|
||||||
@ -824,6 +840,29 @@ function toggleMedia(s: MediaStream, media: CallMediaType): boolean {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function changeLayout(layout: LayoutType) {
|
||||||
|
const local = document.getElementById("local-video-stream")!
|
||||||
|
const remote = document.getElementById("remote-video-stream")!
|
||||||
|
switch (layout) {
|
||||||
|
case LayoutType.Default:
|
||||||
|
local.className = "inline"
|
||||||
|
remote.className = "inline"
|
||||||
|
local.style.visibility = "visible"
|
||||||
|
remote.style.visibility = "visible"
|
||||||
|
break
|
||||||
|
case LayoutType.LocalVideo:
|
||||||
|
local.className = "fullscreen"
|
||||||
|
local.style.visibility = "visible"
|
||||||
|
remote.style.visibility = "hidden"
|
||||||
|
break
|
||||||
|
case LayoutType.RemoteVideo:
|
||||||
|
remote.className = "fullscreen"
|
||||||
|
local.style.visibility = "hidden"
|
||||||
|
remote.style.visibility = "visible"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type TransformFrameFunc = (key: CryptoKey) => (frame: RTCEncodedVideoFrame, controller: TransformStreamDefaultController) => Promise<void>
|
type TransformFrameFunc = (key: CryptoKey) => (frame: RTCEncodedVideoFrame, controller: TransformStreamDefaultController) => Promise<void>
|
||||||
|
|
||||||
interface CallCrypto {
|
interface CallCrypto {
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<video
|
<video
|
||||||
id="remote-video-stream"
|
id="remote-video-stream"
|
||||||
|
class="inline"
|
||||||
autoplay
|
autoplay
|
||||||
playsinline
|
playsinline
|
||||||
poster="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAQAAAAnOwc2AAAAEUlEQVR42mNk+M+AARiHsiAAcCIKAYwFoQ8AAAAASUVORK5CYII="
|
poster="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAQAAAAnOwc2AAAAEUlEQVR42mNk+M+AARiHsiAAcCIKAYwFoQ8AAAAASUVORK5CYII="
|
||||||
@ -16,6 +17,7 @@
|
|||||||
></video>
|
></video>
|
||||||
<video
|
<video
|
||||||
id="local-video-stream"
|
id="local-video-stream"
|
||||||
|
class="inline"
|
||||||
muted
|
muted
|
||||||
autoplay
|
autoplay
|
||||||
playsinline
|
playsinline
|
||||||
|
@ -5,14 +5,14 @@ body {
|
|||||||
background-color: black;
|
background-color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
#remote-video-stream {
|
#remote-video-stream.inline {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
#local-video-stream {
|
#local-video-stream.inline {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 20%;
|
width: 20%;
|
||||||
max-width: 20%;
|
max-width: 20%;
|
||||||
@ -23,6 +23,20 @@ body {
|
|||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#remote-video-stream.fullscreen {
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
#local-video-stream.fullscreen {
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
*::-webkit-media-controls {
|
*::-webkit-media-controls {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
-webkit-appearance: none !important;
|
-webkit-appearance: none !important;
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
security create-keychain -p "" simplex.keychain
|
|
||||||
security set-keychain-settings -u simplex.keychain
|
|
||||||
security add-certificates -k simplex.keychain "Developer ID Application: SimpleX Chat Ltd (5NN7GUYB6T).cer"
|
|
||||||
security add-certificates -k simplex.keychain "Developer ID Certification Authority.cer"
|
|
||||||
# Private key with access from any app
|
|
||||||
security import "SimpleX Chat.p12" -P "" -k simplex.keychain -A
|
|
||||||
# Public key
|
|
||||||
security import "SimpleX Chat.pem" -k simplex.keychain
|
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
trap "rm apps/multiplatform/local.properties 2> /dev/null || true; rm local.properties 2> /dev/null || true; rm /tmp/simplex.keychain" EXIT
|
trap "rm apps/multiplatform/local.properties || true; rm local.properties || true; rm /tmp/simplex.keychain || true" EXIT
|
||||||
echo "desktop.mac.signing.identity=Developer ID Application: SimpleX Chat Ltd (5NN7GUYB6T)" >> apps/multiplatform/local.properties
|
echo "desktop.mac.signing.identity=Developer ID Application: SimpleX Chat Ltd (5NN7GUYB6T)" >> apps/multiplatform/local.properties
|
||||||
echo "desktop.mac.signing.keychain=/tmp/simplex.keychain" >> apps/multiplatform/local.properties
|
echo "desktop.mac.signing.keychain=/tmp/simplex.keychain" >> apps/multiplatform/local.properties
|
||||||
echo "desktop.mac.notarization.apple_id=$APPLE_SIMPLEX_NOTARIZATION_APPLE_ID" >> apps/multiplatform/local.properties
|
echo "desktop.mac.notarization.apple_id=$APPLE_SIMPLEX_NOTARIZATION_APPLE_ID" >> apps/multiplatform/local.properties
|
||||||
@ -10,10 +10,6 @@ echo "desktop.mac.notarization.password=$APPLE_SIMPLEX_NOTARIZATION_PASSWORD" >>
|
|||||||
echo "desktop.mac.notarization.team_id=5NN7GUYB6T" >> apps/multiplatform/local.properties
|
echo "desktop.mac.notarization.team_id=5NN7GUYB6T" >> apps/multiplatform/local.properties
|
||||||
echo "$APPLE_SIMPLEX_SIGNING_KEYCHAIN" | base64 --decode - > /tmp/simplex.keychain
|
echo "$APPLE_SIMPLEX_SIGNING_KEYCHAIN" | base64 --decode - > /tmp/simplex.keychain
|
||||||
|
|
||||||
security unlock-keychain -p "" /tmp/simplex.keychain
|
|
||||||
# Adding keychain to the list of keychains.
|
|
||||||
# Otherwise, it can find cert but exits while signing with "error: The specified item could not be found in the keychain."
|
|
||||||
security list-keychains -s `security list-keychains | xargs` /tmp/simplex.keychain
|
|
||||||
scripts/desktop/build-lib-mac.sh
|
scripts/desktop/build-lib-mac.sh
|
||||||
cd apps/multiplatform
|
cd apps/multiplatform
|
||||||
./gradlew packageDmg
|
./gradlew packageDmg
|
@ -8,7 +8,7 @@ function readlink() {
|
|||||||
|
|
||||||
OS=linux
|
OS=linux
|
||||||
ARCH=${1:-`uname -a | rev | cut -d' ' -f2 | rev`}
|
ARCH=${1:-`uname -a | rev | cut -d' ' -f2 | rev`}
|
||||||
GHC_VERSION=9.6.3
|
GHC_VERSION=8.10.7
|
||||||
|
|
||||||
if [ "$ARCH" == "aarch64" ]; then
|
if [ "$ARCH" == "aarch64" ]; then
|
||||||
COMPOSE_ARCH=arm64
|
COMPOSE_ARCH=arm64
|
||||||
@ -25,7 +25,7 @@ for elem in "${exports[@]}"; do count=$(grep -R "$elem$" libsimplex.dll.def | wc
|
|||||||
for elem in "${exports[@]}"; do count=$(grep -R "\"$elem\"" flake.nix | wc -l); if [ $count -ne 2 ]; then echo Wrong exports in flake.nix. Add \"$elem\" in two places of the file; exit 1; fi ; done
|
for elem in "${exports[@]}"; do count=$(grep -R "\"$elem\"" flake.nix | wc -l); if [ $count -ne 2 ]; then echo Wrong exports in flake.nix. Add \"$elem\" in two places of the file; exit 1; fi ; done
|
||||||
|
|
||||||
rm -rf $BUILD_DIR
|
rm -rf $BUILD_DIR
|
||||||
cabal build lib:simplex-chat --ghc-options='-optl-Wl,-rpath,$ORIGIN -flink-rts -threaded'
|
cabal build lib:simplex-chat --ghc-options='-optl-Wl,-rpath,$ORIGIN' --ghc-options="-optl-L$(ghc --print-libdir)/rts -optl-Wl,--as-needed,-lHSrts_thr-ghc$GHC_VERSION"
|
||||||
cd $BUILD_DIR/build
|
cd $BUILD_DIR/build
|
||||||
#patchelf --add-needed libHSrts_thr-ghc${GHC_VERSION}.so libHSsimplex-chat-*-inplace-ghc${GHC_VERSION}.so
|
#patchelf --add-needed libHSrts_thr-ghc${GHC_VERSION}.so libHSsimplex-chat-*-inplace-ghc${GHC_VERSION}.so
|
||||||
#patchelf --add-rpath '$ORIGIN' libHSsimplex-chat-*-inplace-ghc${GHC_VERSION}.so
|
#patchelf --add-rpath '$ORIGIN' libHSsimplex-chat-*-inplace-ghc${GHC_VERSION}.so
|
||||||
|
@ -5,14 +5,13 @@ set -e
|
|||||||
OS=mac
|
OS=mac
|
||||||
ARCH="${1:-`uname -a | rev | cut -d' ' -f1 | rev`}"
|
ARCH="${1:-`uname -a | rev | cut -d' ' -f1 | rev`}"
|
||||||
COMPOSE_ARCH=$ARCH
|
COMPOSE_ARCH=$ARCH
|
||||||
GHC_VERSION=9.6.3
|
GHC_VERSION=8.10.7
|
||||||
|
|
||||||
if [ "$ARCH" == "arm64" ]; then
|
if [ "$ARCH" == "arm64" ]; then
|
||||||
ARCH=aarch64
|
ARCH=aarch64
|
||||||
else
|
else
|
||||||
COMPOSE_ARCH=x64
|
COMPOSE_ARCH=x64
|
||||||
fi
|
fi
|
||||||
|
|
||||||
LIB_EXT=dylib
|
LIB_EXT=dylib
|
||||||
LIB=libHSsimplex-chat-*-inplace-ghc*.$LIB_EXT
|
LIB=libHSsimplex-chat-*-inplace-ghc*.$LIB_EXT
|
||||||
GHC_LIBS_DIR=$(ghc --print-libdir)
|
GHC_LIBS_DIR=$(ghc --print-libdir)
|
||||||
@ -24,26 +23,13 @@ for elem in "${exports[@]}"; do count=$(grep -R "$elem$" libsimplex.dll.def | wc
|
|||||||
for elem in "${exports[@]}"; do count=$(grep -R "\"$elem\"" flake.nix | wc -l); if [ $count -ne 2 ]; then echo Wrong exports in flake.nix. Add \"$elem\" in two places of the file; exit 1; fi ; done
|
for elem in "${exports[@]}"; do count=$(grep -R "\"$elem\"" flake.nix | wc -l); if [ $count -ne 2 ]; then echo Wrong exports in flake.nix. Add \"$elem\" in two places of the file; exit 1; fi ; done
|
||||||
|
|
||||||
rm -rf $BUILD_DIR
|
rm -rf $BUILD_DIR
|
||||||
cabal build lib:simplex-chat lib:simplex-chat --ghc-options="-optl-Wl,-rpath,@loader_path -optl-Wl,-L$GHC_LIBS_DIR/$ARCH-osx-ghc-$GHC_VERSION -optl-lHSrts_thr-ghc$GHC_VERSION -optl-lffi"
|
cabal build lib:simplex-chat lib:simplex-chat --ghc-options="-optl-Wl,-rpath,@loader_path -optl-Wl,-L$GHC_LIBS_DIR/rts -optl-lHSrts_thr-ghc8.10.7 -optl-lffi"
|
||||||
|
|
||||||
cd $BUILD_DIR/build
|
cd $BUILD_DIR/build
|
||||||
mkdir deps 2> /dev/null || true
|
mkdir deps 2> /dev/null || true
|
||||||
|
|
||||||
# It's not included by default for some reason. Compiled lib tries to find system one but it's not always available
|
# It's not included by default for some reason. Compiled lib tries to find system one but it's not always available
|
||||||
#cp $GHC_LIBS_DIR/libffi.dylib ./deps
|
cp $GHC_LIBS_DIR/rts/libffi.dylib ./deps
|
||||||
(
|
|
||||||
BUILD=$PWD
|
|
||||||
cp /tmp/libffi-3.4.4/*-apple-darwin*/.libs/libffi.dylib $BUILD/deps || \
|
|
||||||
( \
|
|
||||||
cd /tmp && \
|
|
||||||
curl --tlsv1.2 "https://gitlab.haskell.org/ghc/libffi-tarballs/-/raw/libffi-3.4.4/libffi-3.4.4.tar.gz?inline=false" -o libffi.tar.gz && \
|
|
||||||
tar -xzvf libffi.tar.gz && \
|
|
||||||
cd "libffi-3.4.4" && \
|
|
||||||
./configure && \
|
|
||||||
make && \
|
|
||||||
cp *-apple-darwin*/.libs/libffi.dylib $BUILD/deps \
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
DYLIBS=`otool -L $LIB | grep @rpath | tail -n +2 | cut -d' ' -f 1 | cut -d'/' -f2`
|
DYLIBS=`otool -L $LIB | grep @rpath | tail -n +2 | cut -d' ' -f 1 | cut -d'/' -f2`
|
||||||
RPATHS=`otool -l $LIB | grep "path "| cut -d' ' -f11`
|
RPATHS=`otool -l $LIB | grep "path "| cut -d' ' -f11`
|
||||||
@ -84,8 +70,6 @@ function copy_deps() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
copy_deps $LIB
|
copy_deps $LIB
|
||||||
# Special case
|
|
||||||
cp $(ghc --print-libdir)/$ARCH-osx-ghc-$GHC_VERSION/libHSghc-boot-th-$GHC_VERSION-ghc$GHC_VERSION.dylib deps
|
|
||||||
rm deps/`basename $LIB`
|
rm deps/`basename $LIB`
|
||||||
|
|
||||||
cd -
|
cd -
|
||||||
|
@ -1,242 +0,0 @@
|
|||||||
From 38be2c93acb6f459d24ed6c626981c35ccf44095 Mon Sep 17 00:00:00 2001
|
|
||||||
From: Sylvain Henry <sylvain@haskus.fr>
|
|
||||||
Date: Thu, 16 Feb 2023 15:40:45 +0100
|
|
||||||
Subject: [PATCH] Fix build on 32-bit architectures
|
|
||||||
|
|
||||||
---
|
|
||||||
Basement/Bits.hs | 4 ++++
|
|
||||||
Basement/From.hs | 24 -----------------------
|
|
||||||
Basement/Numerical/Additive.hs | 4 ++++
|
|
||||||
Basement/Numerical/Conversion.hs | 20 +++++++++++++++++++
|
|
||||||
Basement/PrimType.hs | 6 +++++-
|
|
||||||
Basement/Types/OffsetSize.hs | 22 +++++++++++++++++++--
|
|
||||||
6 files changed, 53 insertions(+), 27 deletions(-)
|
|
||||||
|
|
||||||
diff --git a/Basement/Bits.hs b/Basement/Bits.hs
|
|
||||||
index 7eeea0f5..24520ed7 100644
|
|
||||||
--- a/Basement/Bits.hs
|
|
||||||
+++ b/Basement/Bits.hs
|
|
||||||
@@ -54,8 +54,12 @@ import GHC.Int
|
|
||||||
import Basement.Compat.Primitive
|
|
||||||
|
|
||||||
#if WORD_SIZE_IN_BITS < 64
|
|
||||||
+#if __GLASGOW_HASKELL__ >= 904
|
|
||||||
+import GHC.Exts
|
|
||||||
+#else
|
|
||||||
import GHC.IntWord64
|
|
||||||
#endif
|
|
||||||
+#endif
|
|
||||||
|
|
||||||
-- | operation over finite bits
|
|
||||||
class FiniteBitsOps bits where
|
|
||||||
diff --git a/Basement/From.hs b/Basement/From.hs
|
|
||||||
index 7bbe141c..80014b3e 100644
|
|
||||||
--- a/Basement/From.hs
|
|
||||||
+++ b/Basement/From.hs
|
|
||||||
@@ -272,23 +272,11 @@ instance (NatWithinBound (CountOf ty) n, KnownNat n, PrimType ty)
|
|
||||||
tryFrom = BlockN.toBlockN . UArray.toBlock . BoxArray.mapToUnboxed id
|
|
||||||
|
|
||||||
instance (KnownNat n, NatWithinBound Word8 n) => From (Zn64 n) Word8 where
|
|
||||||
-#if __GLASGOW_HASKELL__ >= 904
|
|
||||||
- from = narrow . unZn64 where narrow (W64# w) = W8# (wordToWord8# (word64ToWord# (GHC.Prim.word64ToWord# w)))
|
|
||||||
-#else
|
|
||||||
from = narrow . unZn64 where narrow (W64# w) = W8# (wordToWord8# (word64ToWord# w))
|
|
||||||
-#endif
|
|
||||||
instance (KnownNat n, NatWithinBound Word16 n) => From (Zn64 n) Word16 where
|
|
||||||
-#if __GLASGOW_HASKELL__ >= 904
|
|
||||||
- from = narrow . unZn64 where narrow (W64# w) = W16# (wordToWord16# (word64ToWord# (GHC.Prim.word64ToWord# w)))
|
|
||||||
-#else
|
|
||||||
from = narrow . unZn64 where narrow (W64# w) = W16# (wordToWord16# (word64ToWord# w))
|
|
||||||
-#endif
|
|
||||||
instance (KnownNat n, NatWithinBound Word32 n) => From (Zn64 n) Word32 where
|
|
||||||
-#if __GLASGOW_HASKELL__ >= 904
|
|
||||||
- from = narrow . unZn64 where narrow (W64# w) = W32# (wordToWord32# (word64ToWord# (GHC.Prim.word64ToWord# w)))
|
|
||||||
-#else
|
|
||||||
from = narrow . unZn64 where narrow (W64# w) = W32# (wordToWord32# (word64ToWord# w))
|
|
||||||
-#endif
|
|
||||||
instance From (Zn64 n) Word64 where
|
|
||||||
from = unZn64
|
|
||||||
instance From (Zn64 n) Word128 where
|
|
||||||
@@ -297,23 +285,11 @@ instance From (Zn64 n) Word256 where
|
|
||||||
from = from . unZn64
|
|
||||||
|
|
||||||
instance (KnownNat n, NatWithinBound Word8 n) => From (Zn n) Word8 where
|
|
||||||
-#if __GLASGOW_HASKELL__ >= 904
|
|
||||||
- from = narrow . naturalToWord64 . unZn where narrow (W64# w) = W8# (wordToWord8# (word64ToWord# (GHC.Prim.word64ToWord# w)))
|
|
||||||
-#else
|
|
||||||
from = narrow . naturalToWord64 . unZn where narrow (W64# w) = W8# (wordToWord8# (word64ToWord# w))
|
|
||||||
-#endif
|
|
||||||
instance (KnownNat n, NatWithinBound Word16 n) => From (Zn n) Word16 where
|
|
||||||
-#if __GLASGOW_HASKELL__ >= 904
|
|
||||||
- from = narrow . naturalToWord64 . unZn where narrow (W64# w) = W16# (wordToWord16# (word64ToWord# (GHC.Prim.word64ToWord# w)))
|
|
||||||
-#else
|
|
||||||
from = narrow . naturalToWord64 . unZn where narrow (W64# w) = W16# (wordToWord16# (word64ToWord# w))
|
|
||||||
-#endif
|
|
||||||
instance (KnownNat n, NatWithinBound Word32 n) => From (Zn n) Word32 where
|
|
||||||
-#if __GLASGOW_HASKELL__ >= 904
|
|
||||||
- from = narrow . naturalToWord64 . unZn where narrow (W64# w) = W32# (wordToWord32# (word64ToWord# (GHC.Prim.word64ToWord# w)))
|
|
||||||
-#else
|
|
||||||
from = narrow . naturalToWord64 . unZn where narrow (W64# w) = W32# (wordToWord32# (word64ToWord# w))
|
|
||||||
-#endif
|
|
||||||
instance (KnownNat n, NatWithinBound Word64 n) => From (Zn n) Word64 where
|
|
||||||
from = naturalToWord64 . unZn
|
|
||||||
instance (KnownNat n, NatWithinBound Word128 n) => From (Zn n) Word128 where
|
|
||||||
diff --git a/Basement/Numerical/Additive.hs b/Basement/Numerical/Additive.hs
|
|
||||||
index d0dfb973..8ab65aa0 100644
|
|
||||||
--- a/Basement/Numerical/Additive.hs
|
|
||||||
+++ b/Basement/Numerical/Additive.hs
|
|
||||||
@@ -30,8 +30,12 @@ import qualified Basement.Types.Word128 as Word128
|
|
||||||
import qualified Basement.Types.Word256 as Word256
|
|
||||||
|
|
||||||
#if WORD_SIZE_IN_BITS < 64
|
|
||||||
+#if __GLASGOW_HASKELL__ >= 904
|
|
||||||
+import GHC.Exts
|
|
||||||
+#else
|
|
||||||
import GHC.IntWord64
|
|
||||||
#endif
|
|
||||||
+#endif
|
|
||||||
|
|
||||||
-- | Represent class of things that can be added together,
|
|
||||||
-- contains a neutral element and is commutative.
|
|
||||||
diff --git a/Basement/Numerical/Conversion.hs b/Basement/Numerical/Conversion.hs
|
|
||||||
index db502c07..fddc8232 100644
|
|
||||||
--- a/Basement/Numerical/Conversion.hs
|
|
||||||
+++ b/Basement/Numerical/Conversion.hs
|
|
||||||
@@ -26,8 +26,12 @@ import GHC.Word
|
|
||||||
import Basement.Compat.Primitive
|
|
||||||
|
|
||||||
#if WORD_SIZE_IN_BITS < 64
|
|
||||||
+#if __GLASGOW_HASKELL__ >= 904
|
|
||||||
+import GHC.Exts
|
|
||||||
+#else
|
|
||||||
import GHC.IntWord64
|
|
||||||
#endif
|
|
||||||
+#endif
|
|
||||||
|
|
||||||
intToInt64 :: Int -> Int64
|
|
||||||
#if WORD_SIZE_IN_BITS == 64
|
|
||||||
@@ -96,11 +100,22 @@ int64ToWord64 (I64# i) = W64# (int64ToWord64# i)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if WORD_SIZE_IN_BITS == 64
|
|
||||||
+#if __GLASGOW_HASKELL__ >= 904
|
|
||||||
+word64ToWord# :: Word64# -> Word#
|
|
||||||
+word64ToWord# i = word64ToWord# i
|
|
||||||
+#else
|
|
||||||
word64ToWord# :: Word# -> Word#
|
|
||||||
word64ToWord# i = i
|
|
||||||
+#endif
|
|
||||||
{-# INLINE word64ToWord# #-}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
+#if WORD_SIZE_IN_BITS < 64
|
|
||||||
+word64ToWord32# :: Word64# -> Word32#
|
|
||||||
+word64ToWord32# i = wordToWord32# (word64ToWord# i)
|
|
||||||
+{-# INLINE word64ToWord32# #-}
|
|
||||||
+#endif
|
|
||||||
+
|
|
||||||
-- | 2 Word32s
|
|
||||||
data Word32x2 = Word32x2 {-# UNPACK #-} !Word32
|
|
||||||
{-# UNPACK #-} !Word32
|
|
||||||
@@ -113,9 +128,14 @@ word64ToWord32s (W64# w64) = Word32x2 (W32# (wordToWord32# (uncheckedShiftRL# (G
|
|
||||||
word64ToWord32s (W64# w64) = Word32x2 (W32# (wordToWord32# (uncheckedShiftRL# w64 32#))) (W32# (wordToWord32# w64))
|
|
||||||
#endif
|
|
||||||
#else
|
|
||||||
+#if __GLASGOW_HASKELL__ >= 904
|
|
||||||
+word64ToWord32s :: Word64 -> Word32x2
|
|
||||||
+word64ToWord32s (W64# w64) = Word32x2 (W32# (word64ToWord32# (uncheckedShiftRL64# w64 32#))) (W32# (word64ToWord32# w64))
|
|
||||||
+#else
|
|
||||||
word64ToWord32s :: Word64 -> Word32x2
|
|
||||||
word64ToWord32s (W64# w64) = Word32x2 (W32# (word64ToWord# (uncheckedShiftRL64# w64 32#))) (W32# (word64ToWord# w64))
|
|
||||||
#endif
|
|
||||||
+#endif
|
|
||||||
|
|
||||||
wordToChar :: Word -> Char
|
|
||||||
wordToChar (W# word) = C# (chr# (word2Int# word))
|
|
||||||
diff --git a/Basement/PrimType.hs b/Basement/PrimType.hs
|
|
||||||
index f8ca2926..a888ec91 100644
|
|
||||||
--- a/Basement/PrimType.hs
|
|
||||||
+++ b/Basement/PrimType.hs
|
|
||||||
@@ -54,7 +54,11 @@ import Basement.Nat
|
|
||||||
import qualified Prelude (quot)
|
|
||||||
|
|
||||||
#if WORD_SIZE_IN_BITS < 64
|
|
||||||
-import GHC.IntWord64
|
|
||||||
+#if __GLASGOW_HASKELL__ >= 904
|
|
||||||
+import GHC.Exts
|
|
||||||
+#else
|
|
||||||
+import GHC.IntWord64
|
|
||||||
+#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef FOUNDATION_BOUNDS_CHECK
|
|
||||||
diff --git a/Basement/Types/OffsetSize.hs b/Basement/Types/OffsetSize.hs
|
|
||||||
index cd944927..1ea80dad 100644
|
|
||||||
--- a/Basement/Types/OffsetSize.hs
|
|
||||||
+++ b/Basement/Types/OffsetSize.hs
|
|
||||||
@@ -70,8 +70,12 @@ import Data.List (foldl')
|
|
||||||
import qualified Prelude
|
|
||||||
|
|
||||||
#if WORD_SIZE_IN_BITS < 64
|
|
||||||
+#if __GLASGOW_HASKELL__ >= 904
|
|
||||||
+import GHC.Exts
|
|
||||||
+#else
|
|
||||||
import GHC.IntWord64
|
|
||||||
#endif
|
|
||||||
+#endif
|
|
||||||
|
|
||||||
-- | File size in bytes
|
|
||||||
newtype FileSize = FileSize Word64
|
|
||||||
@@ -225,20 +229,26 @@ countOfRoundUp alignment (CountOf n) = CountOf ((n + (alignment-1)) .&. compleme
|
|
||||||
|
|
||||||
csizeOfSize :: CountOf Word8 -> CSize
|
|
||||||
#if WORD_SIZE_IN_BITS < 64
|
|
||||||
+#if __GLASGOW_HASKELL__ >= 904
|
|
||||||
+csizeOfSize (CountOf (I# sz)) = CSize (W32# (wordToWord32# (int2Word# sz)))
|
|
||||||
+#else
|
|
||||||
csizeOfSize (CountOf (I# sz)) = CSize (W32# (int2Word# sz))
|
|
||||||
+#endif
|
|
||||||
#else
|
|
||||||
#if __GLASGOW_HASKELL__ >= 904
|
|
||||||
csizeOfSize (CountOf (I# sz)) = CSize (W64# (wordToWord64# (int2Word# sz)))
|
|
||||||
-
|
|
||||||
#else
|
|
||||||
csizeOfSize (CountOf (I# sz)) = CSize (W64# (int2Word# sz))
|
|
||||||
-
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
csizeOfOffset :: Offset8 -> CSize
|
|
||||||
#if WORD_SIZE_IN_BITS < 64
|
|
||||||
+#if __GLASGOW_HASKELL__ >= 904
|
|
||||||
+csizeOfOffset (Offset (I# sz)) = CSize (W32# (wordToWord32# (int2Word# sz)))
|
|
||||||
+#else
|
|
||||||
csizeOfOffset (Offset (I# sz)) = CSize (W32# (int2Word# sz))
|
|
||||||
+#endif
|
|
||||||
#else
|
|
||||||
#if __GLASGOW_HASKELL__ >= 904
|
|
||||||
csizeOfOffset (Offset (I# sz)) = CSize (W64# (wordToWord64# (int2Word# sz)))
|
|
||||||
@@ -250,7 +260,11 @@ csizeOfOffset (Offset (I# sz)) = CSize (W64# (int2Word# sz))
|
|
||||||
sizeOfCSSize :: CSsize -> CountOf Word8
|
|
||||||
sizeOfCSSize (CSsize (-1)) = error "invalid size: CSSize is -1"
|
|
||||||
#if WORD_SIZE_IN_BITS < 64
|
|
||||||
+#if __GLASGOW_HASKELL__ >= 904
|
|
||||||
+sizeOfCSSize (CSsize (I32# sz)) = CountOf (I# (int32ToInt# sz))
|
|
||||||
+#else
|
|
||||||
sizeOfCSSize (CSsize (I32# sz)) = CountOf (I# sz)
|
|
||||||
+#endif
|
|
||||||
#else
|
|
||||||
#if __GLASGOW_HASKELL__ >= 904
|
|
||||||
sizeOfCSSize (CSsize (I64# sz)) = CountOf (I# (int64ToInt# sz))
|
|
||||||
@@ -261,7 +275,11 @@ sizeOfCSSize (CSsize (I64# sz)) = CountOf (I# sz)
|
|
||||||
|
|
||||||
sizeOfCSize :: CSize -> CountOf Word8
|
|
||||||
#if WORD_SIZE_IN_BITS < 64
|
|
||||||
+#if __GLASGOW_HASKELL__ >= 904
|
|
||||||
+sizeOfCSize (CSize (W32# sz)) = CountOf (I# (word2Int# (word32ToWord# sz)))
|
|
||||||
+#else
|
|
||||||
sizeOfCSize (CSize (W32# sz)) = CountOf (I# (word2Int# sz))
|
|
||||||
+#endif
|
|
||||||
#else
|
|
||||||
#if __GLASGOW_HASKELL__ >= 904
|
|
||||||
sizeOfCSize (CSize (W64# sz)) = CountOf (I# (word2Int# (word64ToWord# sz)))
|
|
@ -1,12 +0,0 @@
|
|||||||
diff --git a/direct-sqlcipher.cabal b/direct-sqlcipher.cabal
|
|
||||||
index 728ba3e..c63745e 100644
|
|
||||||
--- a/direct-sqlcipher.cabal
|
|
||||||
+++ b/direct-sqlcipher.cabal
|
|
||||||
@@ -84,6 +84,8 @@ library
|
|
||||||
cc-options: -DSQLITE_TEMP_STORE=2
|
|
||||||
-DSQLITE_HAS_CODEC
|
|
||||||
|
|
||||||
+ extra-libraries: ws2_32
|
|
||||||
+
|
|
||||||
if !os(windows) && !os(android)
|
|
||||||
extra-libraries: pthread
|
|
@ -1,36 +0,0 @@
|
|||||||
From 2738929ce15b4c8704bbbac24a08539b5d4bf30e Mon Sep 17 00:00:00 2001
|
|
||||||
From: sternenseemann <sternenseemann@systemli.org>
|
|
||||||
Date: Mon, 14 Aug 2023 10:51:30 +0200
|
|
||||||
Subject: [PATCH] Data.Memory.Internal.CompatPrim64: fix 32 bit with GHC >= 9.4
|
|
||||||
|
|
||||||
Since 9.4, GHC.Prim exports Word64# operations like timesWord64# even on
|
|
||||||
i686 whereas GHC.IntWord64 no longer exists. Therefore, we can just use
|
|
||||||
the ready made solution.
|
|
||||||
|
|
||||||
Closes #98, as it should be the better solution.
|
|
||||||
---
|
|
||||||
Data/Memory/Internal/CompatPrim64.hs | 4 ++++
|
|
||||||
1 file changed, 4 insertions(+)
|
|
||||||
|
|
||||||
diff --git a/Data/Memory/Internal/CompatPrim64.hs b/Data/Memory/Internal/CompatPrim64.hs
|
|
||||||
index b9eef8a..a134c88 100644
|
|
||||||
--- a/Data/Memory/Internal/CompatPrim64.hs
|
|
||||||
+++ b/Data/Memory/Internal/CompatPrim64.hs
|
|
||||||
@@ -150,6 +150,7 @@ w64# :: Word# -> Word# -> Word# -> Word64#
|
|
||||||
w64# w _ _ = w
|
|
||||||
|
|
||||||
#elif WORD_SIZE_IN_BITS == 32
|
|
||||||
+#if __GLASGOW_HASKELL__ < 904
|
|
||||||
import GHC.IntWord64
|
|
||||||
import GHC.Prim (Word#)
|
|
||||||
|
|
||||||
@@ -158,6 +159,9 @@ timesWord64# a b =
|
|
||||||
let !ai = word64ToInt64# a
|
|
||||||
!bi = word64ToInt64# b
|
|
||||||
in int64ToWord64# (timesInt64# ai bi)
|
|
||||||
+#else
|
|
||||||
+import GHC.Prim
|
|
||||||
+#endif
|
|
||||||
|
|
||||||
w64# :: Word# -> Word# -> Word# -> Word64#
|
|
||||||
w64# _ hw lw =
|
|
Loading…
Reference in New Issue
Block a user