Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NO NEED TO REVIEW NOW] Feature/entry widget and secure conversations v2 #1133

Draft
wants to merge 106 commits into
base: development
Choose a base branch
from
Draft
Changes from 1 commit
Commits
Show all changes
106 commits
Select commit Hold shift + click to select a range
b67ed27
Refactor configurations
DavDo Sep 16, 2024
a4d0aa1
Separate Intent-related functionality
DavDo Sep 18, 2024
92afbc7
Implement Entry Widget (embedded view)
AndriiHorishniiMOC Sep 12, 2024
59276cd
Implement Entry Widget (bottom sheet)
AndriiHorishniiMOC Sep 12, 2024
784f646
Create new internal layer responsible for opening screens
DavDo Sep 18, 2024
c223631
Unified UI customization for Entry Widget and SCv2
AndriiHorishniiMOC Sep 19, 2024
e2d5c7f
Devs want to create public interfaces for EngagementLauncher
DavDo Sep 23, 2024
e69e2f2
Rename all the Unified UI related classes to new SecureMessaging... way
DavDo Sep 25, 2024
1062bb3
Add support of glia custom attributes to Entry Widgets
gugalo Oct 2, 2024
f5a473c
Change colors in layout xml's to use custom attr
gugalo Oct 2, 2024
d865315
Implement EngagementLauncher business logic
DavDo Sep 26, 2024
4db7619
Devs want to implement the logic for showing available engagement types
andrews-moc Sep 27, 2024
5b752a1
Update Entry Widget texts
gugalo Oct 3, 2024
f3f9d55
Remove deprecated functions which are older than 1 year
gugalo Sep 23, 2024
d562cfc
Add gliaBrandPrimaryColor tint to entry widget icon tint.
DavDo Oct 3, 2024
b6feeaa
Send error event if Glia is not initialized before getting an Entry W…
andrews-moc Oct 4, 2024
8af0602
Enable scrolling for the Entry Widget bottom sheet.
AndriiHorishniiMOC Oct 8, 2024
2d5f04f
Cache site queues after first request
DavDo Oct 7, 2024
36dc4d3
Change 'Try again' button style for Entry Widget error state accordin…
andrews-moc Oct 4, 2024
e393e26
Devs want to use cached Queues to check Secure Messaging availability
DavDo Oct 8, 2024
19742dc
Added snapshot tests for the Entry Widget
AndriiHorishniiMOC Oct 9, 2024
45e1cbd
Pass null instead of an empty list if "Default Queues" toggle is enab…
andrews-moc Oct 8, 2024
ad37045
Hide 'Secure Messaging' engagement type from Entry Widget if visitor …
andrews-moc Oct 9, 2024
3f87f1a
Make queueIds list non-nullable, so only empty list means that defaul…
andrews-moc Oct 11, 2024
7bcab02
Devs want to make UiTheme internal
DavDo Oct 11, 2024
0d8fed0
Added loading tint color customization
AndriiHorishniiMOC Oct 14, 2024
77de6f0
Update Kotlin version
gugalo Oct 14, 2024
b844485
Devs want to always fallback to default queues
DavDo Oct 14, 2024
862a788
Devs want to use integrator/default queues from QueueRepository
DavDo Oct 14, 2024
f01f3fd
Update existing secure conversation string
gugalo Oct 16, 2024
0e7362f
Update snapshots for the Entry Widget Bottom Sheet
AndriiHorishniiMOC Oct 14, 2024
05aa855
Refactor queue management to avoid race conditions
DavDo Oct 16, 2024
c7ee354
Implement Entry Widget item click navigation
andrews-moc Oct 18, 2024
08b2863
Implement the 'Try again' button click, add unit tests for EntryWidge…
andrews-moc Oct 22, 2024
f5eb81a
Add global colors support for SCv2
gugalo Oct 23, 2024
4fd16d4
Implement the hide() interface
andrews-moc Oct 17, 2024
bf573f2
Add accessibility labels to the Entry Widget
AndriiHorishniiMOC Oct 23, 2024
b9803b6
Upgrade all dependencies.
DavDo Oct 18, 2024
6c58c23
Entry Widget should show an empty state if only 'messaging' engagemen…
andrews-moc Oct 25, 2024
e9302df
Update existing secure conversation welcome screen
gugalo Oct 22, 2024
eb9e8ca
Implement bottom banner for secure conversation
gugalo Oct 23, 2024
fdf539d
Update snapshot tests to support SCv2 bottom banner
gugalo Oct 24, 2024
09d53c6
Add ability to show embedded Entry Widget view in the example app
AndriiHorishniiMOC Oct 29, 2024
6c8ce23
Apply default attributes from XML theme
AndriiHorishniiMOC Oct 25, 2024
8d33e60
Fix rebase conflicts
DavDo Nov 1, 2024
38a8886
Add SC business logic into Entry Widget public functions
DavDo Oct 31, 2024
44a9f6a
Make SC APIs work seamlessly when sdk is not initialized
DavDo Nov 4, 2024
45a5393
Add secure messaging unavailable label
gugalo Oct 30, 2024
e6d9375
Add ability to pass visitor context id through Engagement Launcher
andrews-moc Nov 1, 2024
0a7c2dc
Improve `chat_view.xml` layout preview
gugalo Oct 23, 2024
b9cd1db
Fix paparazzi having problems with locales
gugalo Nov 7, 2024
8c9fec4
Introduce Intention Enum for opening Chat screen
DavDo Nov 1, 2024
3f78143
Add business logic into EngagementLauncher.startSecureMessaging
DavDo Nov 4, 2024
cb886a6
Add Secure Conversation business logic into EngagementLauncher
DavDo Nov 5, 2024
eacc7e1
Prepare Leave Dialog business logic SC flow
DavDo Nov 6, 2024
c04dfcc
Apply SC business logic to the Chat screen
DavDo Nov 7, 2024
a4314e5
Fix tests and add new ones if needed
DavDo Nov 8, 2024
4b99480
Update glia colors according to new design requirements
gugalo Nov 7, 2024
ad530ba
Update glia color names in internal code to match designs
gugalo Nov 7, 2024
00156f3
Remove half-transparent glia normal color
gugalo Nov 7, 2024
d3c64ad
Improve color matching
gugalo Nov 8, 2024
11c6746
Update white-label color
gugalo Nov 8, 2024
79d88d7
Align remote theme internal color names with design mocks
gugalo Nov 8, 2024
305eec3
Update snapshots with according to new color scheme
gugalo Nov 8, 2024
555f441
Add logs and telemetry for Entry Widget
andrews-moc Oct 30, 2024
f859a51
Add use case for SC top banner visibility
gugalo Nov 11, 2024
faae679
Show the Entry Widget items in order according to the design
AndriiHorishniiMOC Nov 12, 2024
c0805ed
Show unread messages counter for 'Messaging' on embedded view
andrews-moc Nov 6, 2024
2787418
Show unread messages counter for 'Messaging' on embedded view
andrews-moc Nov 7, 2024
c19fa44
Show unread messages counter for 'Messaging' on embedded view
andrews-moc Nov 7, 2024
3af46b2
Show unread messages counter for 'Messaging' on embedded view
andrews-moc Nov 7, 2024
a769c48
Show unread messages counter for 'Messaging' on embedded view
andrews-moc Nov 12, 2024
c5b2602
Update EntryWidgetsView to support MESSAGING_LIVE_SUPPORT flag
gugalo Nov 12, 2024
afe9f00
Rename 'Live Chat' to 'Chat'
andrews-moc Nov 13, 2024
5df2673
Replace Activity with Context in EngagementLauncher
andrews-moc Nov 14, 2024
2e488d2
Resolve merge conflicts
DavDo Nov 18, 2024
098e8ff
Fix Chat screen is opened incorrectly from Call screen
DavDo Nov 19, 2024
8670d11
Refresh Entry Widget on Glia init
andrews-moc Nov 18, 2024
4345339
Adjust Entry Widget UI based on designer review
andrews-moc Nov 20, 2024
9552f4d
Use subscriptions for unread messages and pending secure conversations
DavDo Nov 20, 2024
2c96f74
Update Core SDK staging repo url
gugalo Nov 26, 2024
8ce307b
Add 'Need Live Support?' banner to SC chat
gugalo Nov 18, 2024
ffe9885
Devs want to implement GVA(Live engagement) to SC transfer
DavDo Nov 21, 2024
35c246c
Update snapshot tests after rebase
andrews-moc Nov 27, 2024
7083628
Update Core SDK staging repo URL
andrews-moc Nov 27, 2024
1034e8f
Fix RX timeout issue
gugalo Nov 27, 2024
8fe6c4d
Prevent recursive dismiss() call
andrews-moc Nov 27, 2024
e19999d
Consider the ongoing sc when checking for secure messaging availability
DavDo Nov 28, 2024
f776415
Fix Entry Widget watermark visibility is not configurable
DavDo Nov 29, 2024
2d6ebd2
Remove the timeout from SC properties
DavDo Dec 3, 2024
acdb98f
Devs want to handle SC chat top banner item click
DavDo Dec 4, 2024
45c9ea8
Refactor SC logic to make switching between Live and SC seamless
DavDo Dec 4, 2024
9f0c19b
Add snapshot tests for Entry Widget for the cases when 'whiteLabel ==…
andrews-moc Dec 4, 2024
67c75b9
Fix send and attachment buttons overlap
gugalo Dec 2, 2024
93ed0a8
Add snapshot tests for SC top banner
gugalo Nov 29, 2024
193bd13
Update staging build link
gugalo Dec 12, 2024
bbf1c40
Implement Leave Current Conversation dialog
gugalo Dec 11, 2024
71de68b
Add disabled color state for chat input
gugalo Dec 9, 2024
c55758d
Fix jumpy animation of chat input on keyboard show/hide
gugalo Dec 13, 2024
057d576
Refactor file attachment repositories
AndriiHorishniiMOC Dec 9, 2024
bd938d5
Fix grapish color for disabled input
gugalo Dec 13, 2024
ed696c8
Update staging build link
andrews-moc Dec 18, 2024
360bb87
Merge mark messages as read logic with leave SC
AndriiHorishniiMOC Dec 17, 2024
bd03f9b
Implement Entry Widget for ongoing engagement cases
andrews-moc Dec 13, 2024
badeb67
Add snapshot tests for 'ongoing engagement' items
andrews-moc Dec 19, 2024
ae3c8f4
Update staging build link
andrews-moc Dec 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Refactor file attachment repositories
Remove SecureFileAttachmentRepository and some SC use cases that duplicated the logic.
Refactor FileAttachmentRepository and the file upload use cases. FileAttachmentRepository is used for both SC and Live Engagement.

MOB-3791
  • Loading branch information
AndriiHorishniiMOC authored and gugalo committed Dec 16, 2024
commit 057d5763e69bd6f8c6d26d14914dd61dcaa9930b
Original file line number Diff line number Diff line change
@@ -47,7 +47,6 @@ import com.glia.widgets.core.engagement.domain.UpdateOperatorDefaultImageUrlUseC
import com.glia.widgets.core.fileupload.domain.AddFileAttachmentsObserverUseCase
import com.glia.widgets.core.fileupload.domain.AddFileToAttachmentAndUploadUseCase
import com.glia.widgets.core.fileupload.domain.GetFileAttachmentsUseCase
import com.glia.widgets.core.fileupload.domain.RemoveFileAttachmentObserverUseCase
import com.glia.widgets.core.fileupload.domain.RemoveFileAttachmentUseCase
import com.glia.widgets.core.fileupload.domain.SupportedFileCountCheckUseCase
import com.glia.widgets.core.fileupload.model.LocalAttachment
@@ -89,8 +88,8 @@ import com.glia.widgets.view.MinimizeHandler
import com.glia.widgets.webbrowser.domain.GetUrlFromLinkUseCase
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.functions.Consumer
import io.reactivex.rxjava3.schedulers.Schedulers
import java.util.Observer

internal class ChatController(
private val callTimer: TimeCounter,
@@ -104,7 +103,6 @@ internal class ChatController(
private val endEngagementUseCase: EndEngagementUseCase,
private val addFileToAttachmentAndUploadUseCase: AddFileToAttachmentAndUploadUseCase,
private val addFileAttachmentsObserverUseCase: AddFileAttachmentsObserverUseCase,
private val removeFileAttachmentObserverUseCase: RemoveFileAttachmentObserverUseCase,
private val getFileAttachmentsUseCase: GetFileAttachmentsUseCase,
private val removeFileAttachmentUseCase: RemoveFileAttachmentUseCase,
private val supportedFileCountCheckUseCase: SupportedFileCountCheckUseCase,
@@ -183,10 +181,10 @@ internal class ChatController(
@Volatile
private var isChatViewPaused = false

private val fileAttachmentObserver = Observer { _, _ ->
private val fileAttachmentCallback = Consumer<List<LocalAttachment>> { attachments ->
view?.apply {
emitUploadAttachments(getFileAttachmentsUseCase())
emitViewState {
emitUploadAttachments(attachments)
chatState.setShowSendButton(isShowSendButtonUseCase(chatState.lastTypedText))
.setIsAttachmentButtonEnabled(supportedFileCountCheckUseCase())
}
@@ -313,7 +311,9 @@ internal class ChatController(
}

private fun prepareChatComponents() {
addFileAttachmentsObserverUseCase.execute(fileAttachmentObserver)
disposable.add(
addFileAttachmentsObserverUseCase().subscribe(fileAttachmentCallback)
)
minimizeHandler.addListener { minimizeView() }
timerStatusListener?.also { callTimer.removeFormattedValueListener(it) }
val newTimerListener = createNewTimerCallback()
@@ -376,7 +376,6 @@ internal class ChatController(
timerStatusListener = null
callTimer.clear()
minimizeHandler.clear()
removeFileAttachmentObserverUseCase(fileAttachmentObserver)
chatManager.reset()
}
}
@@ -855,7 +854,7 @@ internal class ChatController(
}

private fun onAttachmentReceived(file: LocalAttachment) {
addFileToAttachmentAndUploadUseCase.execute(file, object : AddFileToAttachmentAndUploadUseCase.Listener {
addFileToAttachmentAndUploadUseCase(file, object : AddFileToAttachmentAndUploadUseCase.Listener {
override fun onFinished() {
Logger.d(TAG, "fileUploadFinished")
//We need this file locally, so clearing only file uri reference
Original file line number Diff line number Diff line change
@@ -44,7 +44,7 @@ internal class GliaSendMessageUseCase(

fun execute(message: String, listener: Listener) {
val localAttachments: List<LocalAttachment>? = fileAttachmentRepository
.readyToSendLocalAttachments
.getReadyToSendFileAttachments()
.filter { it.engagementFile != null }
.takeIf { it.isNotEmpty() }

Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@ internal class IsShowSendButtonUseCase(
}

private fun hadReadyToSendUnsentAttachments(): Boolean {
return fileAttachmentRepository.readyToSendLocalAttachments.isNotEmpty()
return fileAttachmentRepository.getReadyToSendFileAttachments().isNotEmpty()
}

private fun hasEngagementOngoingAndReadyToSendUnsentAttachments(): Boolean {
Original file line number Diff line number Diff line change
@@ -9,46 +9,75 @@ import com.glia.widgets.core.engagement.exception.EngagementMissingException
import com.glia.widgets.core.fileupload.domain.AddFileToAttachmentAndUploadUseCase
import com.glia.widgets.core.fileupload.model.LocalAttachment
import com.glia.widgets.di.GliaCore
import java.util.Observable
import java.util.Observer
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.subjects.BehaviorSubject
import kotlin.jvm.optionals.getOrNull

internal class FileAttachmentRepository(private val gliaCore: GliaCore) {
internal interface FileAttachmentRepository {
val observable: Observable<List<LocalAttachment>>

fun getFileAttachments(): List<LocalAttachment>
fun getReadyToSendFileAttachments(): List<LocalAttachment>
fun getAttachedFilesCount(): Int
fun isFileAttached(uri: Uri): Boolean
fun attachFile(file: LocalAttachment)
fun uploadFile(shouldUseSecureMessagingEndpoints: Boolean, file: LocalAttachment, listener: AddFileToAttachmentAndUploadUseCase.Listener)
fun detachFile(attachment: LocalAttachment)
fun detachFiles(attachments: List<LocalAttachment>)
fun detachAllFiles()
fun setFileAttachmentTooLarge(uri: Uri)
fun setSupportedFileAttachmentCountExceeded(uri: Uri)
fun setFileAttachmentEngagementMissing(uri: Uri)
}

internal class FileAttachmentRepositoryImpl(
private val gliaCore: GliaCore
) : FileAttachmentRepository {
private val secureConversations: SecureConversations by lazy {
gliaCore.secureConversations
}

private val observable = ObservableFileAttachmentList()
private val _observable = BehaviorSubject.createDefault(emptyList<LocalAttachment>())

override val observable: Observable<List<LocalAttachment>> = _observable

val localAttachments: List<LocalAttachment>
get() = observable.localAttachments
override fun getFileAttachments(): List<LocalAttachment> {
return _observable.value ?: emptyList()
}

val readyToSendLocalAttachments: List<LocalAttachment>
get() = observable.localAttachments
override fun getReadyToSendFileAttachments(): List<LocalAttachment> {
return getFileAttachments()
.filter { obj: LocalAttachment -> obj.isReadyToSend }
}

val attachedFilesCount: Long
get() = observable.localAttachments.size.toLong()
override fun getAttachedFilesCount(): Int {
return getFileAttachments().size
}

fun isFileAttached(uri: Uri): Boolean {
return observable.localAttachments
override fun isFileAttached(uri: Uri): Boolean {
return getFileAttachments()
.any { it.uri == uri }
}

fun attachFile(file: LocalAttachment) {
observable.notifyUpdate(
observable.localAttachments + file
)
override fun attachFile(file: LocalAttachment) {
_observable.onNext(getFileAttachments() + file)
}

fun uploadFile(shouldUseSecureMessagingEndpoints: Boolean, file: LocalAttachment, listener: AddFileToAttachmentAndUploadUseCase.Listener) {
val engagement = gliaCore.currentEngagement.getOrNull()
when {
override fun uploadFile(shouldUseSecureMessagingEndpoints: Boolean, file: LocalAttachment, listener: AddFileToAttachmentAndUploadUseCase.Listener) {
if (shouldUseSecureMessagingEndpoints) {
uploadFileForSecureConversation(file, listener)
} else {
uploadFileForLiveEngagement(file, listener)
}
}

shouldUseSecureMessagingEndpoints -> {
secureConversations.uploadFile(file.uri, handleFileUpload(file, listener))
}
private fun uploadFileForSecureConversation(file: LocalAttachment, listener: AddFileToAttachmentAndUploadUseCase.Listener) {
secureConversations.uploadFile(file.uri, handleFileUpload(file, listener))
}

private fun uploadFileForLiveEngagement(file: LocalAttachment, listener: AddFileToAttachmentAndUploadUseCase.Listener) {
val engagement = gliaCore.currentEngagement.getOrNull()
when {
engagement != null -> {
engagement.uploadFile(file.uri, handleFileUpload(file, listener))
}
@@ -76,51 +105,26 @@ internal class FileAttachmentRepository(private val gliaCore: GliaCore) {
}
}

fun setFileAttachmentTooLarge(uri: Uri) {
setFileAttachmentStatus(uri, LocalAttachment.Status.ERROR_FILE_TOO_LARGE)
}

fun setSupportedFileAttachmentCountExceeded(uri: Uri) {
setFileAttachmentStatus(
uri,
LocalAttachment.Status.ERROR_SUPPORTED_FILE_ATTACHMENT_COUNT_EXCEEDED
)
}

fun setFileAttachmentEngagementMissing(uri: Uri) {
setFileAttachmentStatus(uri, LocalAttachment.Status.ERROR_ENGAGEMENT_MISSING)
}

fun detachFile(attachment: LocalAttachment?) {
observable.notifyUpdate(
observable.localAttachments
.filter { it.uri !== attachment?.uri }
override fun detachFile(attachment: LocalAttachment) {
_observable.onNext(
getFileAttachments()
.filter { it.uri !== attachment.uri }
)
}

fun detachFiles(attachments: List<LocalAttachment?>) {
observable.notifyUpdate(
observable.localAttachments
override fun detachFiles(attachments: List<LocalAttachment>) {
_observable.onNext(
getFileAttachments()
.filter { attachment: LocalAttachment? ->
!attachments.contains(attachment)
!attachments.contains(
attachment
)
}
)
}

fun detachAllFiles() {
observable.notifyUpdate(ArrayList())
}

fun addObserver(observer: Observer?) {
observable.addObserver(observer)
}

fun removeObserver(observer: Observer?) {
observable.deleteObserver(observer)
}

fun clearObservers() {
observable.deleteObservers()
override fun detachAllFiles() {
_observable.onNext(emptyList())
}

private fun onUploadFileSecurityScanRequired(
@@ -130,7 +134,7 @@ internal class FileAttachmentRepository(private val gliaCore: GliaCore) {
) {
setFileAttachmentStatus(uri, LocalAttachment.Status.SECURITY_SCAN)
listener.onSecurityCheckStarted()
engagementFile.on(EngagementFile.Events.SCAN_RESULT) { scanResult: EngagementFile.ScanResult ->
engagementFile.on(EngagementFile.Events.SCAN_RESULT) { scanResult: EngagementFile.ScanResult? ->
engagementFile.off(EngagementFile.Events.SCAN_RESULT)
listener.onSecurityCheckFinished(scanResult)
onUploadFileSecurityScanReceived(uri, engagementFile, scanResult, listener)
@@ -140,7 +144,7 @@ internal class FileAttachmentRepository(private val gliaCore: GliaCore) {
private fun onUploadFileSecurityScanReceived(
uri: Uri,
engagementFile: EngagementFile?,
scanResult: EngagementFile.ScanResult,
scanResult: EngagementFile.ScanResult?,
listener: AddFileToAttachmentAndUploadUseCase.Listener
) {
if (scanResult == EngagementFile.ScanResult.CLEAN && engagementFile != null) {
@@ -161,8 +165,8 @@ internal class FileAttachmentRepository(private val gliaCore: GliaCore) {
}

private fun setFileAttachmentStatus(uri: Uri, status: LocalAttachment.Status) {
observable.notifyUpdate(
observable.localAttachments
_observable.onNext(
getFileAttachments()
.map { localAttachment: LocalAttachment ->
if (localAttachment.uri === uri) {
localAttachment.copy(attachmentStatus = status)
@@ -174,18 +178,30 @@ internal class FileAttachmentRepository(private val gliaCore: GliaCore) {
}

private fun onEngagementFileReceived(uri: Uri, engagementFile: EngagementFile) {
observable.notifyUpdate(
observable.localAttachments
_observable.onNext(
getFileAttachments()
.map { attachment: LocalAttachment ->
if (attachment.uri == uri) {
attachment.copy(attachmentStatus = LocalAttachment.Status.READY_TO_SEND, engagementFile = engagementFile)
attachment.copy(engagementFile = engagementFile, attachmentStatus = LocalAttachment.Status.READY_TO_SEND)
} else {
attachment
}
}
)
}

override fun setFileAttachmentTooLarge(uri: Uri) {
setFileAttachmentStatus(uri, LocalAttachment.Status.ERROR_FILE_TOO_LARGE)
}

override fun setSupportedFileAttachmentCountExceeded(uri: Uri) {
setFileAttachmentStatus(uri, LocalAttachment.Status.ERROR_SUPPORTED_FILE_ATTACHMENT_COUNT_EXCEEDED)
}

override fun setFileAttachmentEngagementMissing(uri: Uri) {
setFileAttachmentStatus(uri, LocalAttachment.Status.ERROR_ENGAGEMENT_MISSING)
}

private fun getAttachmentStatus(exception: GliaException): LocalAttachment.Status {
return when (exception.cause) {
GliaException.Cause.FILE_UPLOAD_FORBIDDEN -> LocalAttachment.Status.ERROR_FILE_UPLOAD_FORBIDDEN
@@ -198,14 +214,4 @@ internal class FileAttachmentRepository(private val gliaCore: GliaCore) {
else -> LocalAttachment.Status.ERROR_UNKNOWN
}
}

class ObservableFileAttachmentList : Observable() {
var localAttachments: List<LocalAttachment> = ArrayList()

fun notifyUpdate(newObject: List<LocalAttachment>) {
localAttachments = newObject
setChanged()
notifyObservers()
}
}
}
Loading