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

Progress and Error notifications: keep state of multiple concurrent Stories being saved #297

Original file line number Diff line number Diff line change
Expand Up @@ -304,40 +304,7 @@ class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelectorTapped
// also, update the UI
updateFlashModeSelectionIcon()

photoEditorView.postDelayed({
if (intent.hasExtra(KEY_STORY_SAVE_RESULT)) {
val storySaveResult = intent.getParcelableExtra(KEY_STORY_SAVE_RESULT) as StorySaveResult?
if (storySaveResult != null &&
StoryRepository.getStoryAtIndex(storySaveResult.storyIndex).frames.size > 0) {
// dismiss the error notification
// TODO use NativeNotificationUtils.dismissNotification() when migrating to WPAndroid
intent.action?.let {
val notificationManager = NotificationManagerCompat.from(this)
notificationManager.cancel(it.toInt())
}

// if the StoryRepository contains a story, load it right away to continue editing
// TODO check pages in this Story and mark them errored according to the StorySaveResult
// see https://github.com/Automattic/portkey-android/issues/285 for details
Log.d("PORTKEY", "Being passed a SaveResult, render the Story")
storyViewModel.loadStory(storySaveResult.storyIndex)
onStoryFrameSelected(0, 0)
} else {
// TODO couldn't find the story frames? Show some Error Dialog - we can't recover here
}
} else {
launchCameraPreview()
storyViewModel.uiState.observe(this, Observer {
// if no frames in Story, launch the capture mode
if (storyViewModel.getCurrentStorySize() == 0) {
photoEditor.clearAllViews()
launchCameraPreview()
// finally, delete the captured media
deleteCapturedMedia()
}
})
}
}, SURFACE_MANAGER_READY_LAUNCH_DELAY)
onLoadFromIntent(intent)
} else {
currentOriginalCapturedFile =
savedInstanceState.getSerializable(STATE_KEY_CURRENT_ORIGINAL_CAPTURED_FILE) as File?
Expand All @@ -359,6 +326,48 @@ class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelectorTapped
}
}

override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
jd-alexander marked this conversation as resolved.
Show resolved Hide resolved
onLoadFromIntent(intent)
}

private fun onLoadFromIntent(intent: Intent) {
photoEditorView.postDelayed({
if (intent.hasExtra(KEY_STORY_SAVE_RESULT)) {
val storySaveResult = intent.getSerializableExtra(KEY_STORY_SAVE_RESULT) as StorySaveResult?
if (storySaveResult != null &&
StoryRepository.getStoryAtIndex(storySaveResult.storyIndex).frames.isNotEmpty()) {
// dismiss the error notification
// TODO use NativeNotificationUtils.dismissNotification() when migrating to WPAndroid
intent.action?.let {
val notificationManager = NotificationManagerCompat.from(this)
notificationManager.cancel(it.toInt())
}

// if the StoryRepository contains a story, load it right away to continue editing
// TODO check pages in this Story and mark them errored according to the StorySaveResult
// see https://github.com/Automattic/portkey-android/issues/285 for details
Log.d("PORTKEY", "Being passed a SaveResult, render the Story")
storyViewModel.loadStory(storySaveResult.storyIndex)
onStoryFrameSelected(0, 0)
} else {
// TODO couldn't find the story frames? Show some Error Dialog - we can't recover here
}
} else {
launchCameraPreview()
storyViewModel.uiState.observe(this, Observer {
// if no frames in Story, launch the capture mode
if (storyViewModel.getCurrentStorySize() == 0) {
photoEditor.clearAllViews()
launchCameraPreview()
// finally, delete the captured media
deleteCapturedMedia()
}
})
}
}, SURFACE_MANAGER_READY_LAUNCH_DELAY)
}

override fun onDestroy() {
doUnbindService()
EventBus.getDefault().unregister(this)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,12 @@ class FrameSaveManager(private val photoEditor: PhotoEditor) : CoroutineScope {
return frames.mapIndexed { index, frame ->
async {
yield()
saveLoopFrame(context, frame, index)
saveStoryFrame(context, frame, index)
jd-alexander marked this conversation as resolved.
Show resolved Hide resolved
}
}.awaitAll().filterNotNull()
}

private suspend fun saveLoopFrame(
private suspend fun saveStoryFrame(
context: Context,
frame: StoryFrameItem,
frameIndex: FrameIndex
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import com.automattic.portkey.R
import com.automattic.portkey.compose.ComposeLoopFrameActivity
import com.automattic.portkey.compose.frame.FrameSaveService.SaveResultReason.SaveSuccess
import com.automattic.portkey.compose.frame.FrameSaveService.StorySaveResult
import com.automattic.portkey.compose.story.StoryIndex
import com.automattic.portkey.util.KEY_STORY_SAVE_RESULT
import java.util.Random

Expand All @@ -26,6 +27,8 @@ class FrameSaveNotifier(private val context: Context, private val service: Frame
internal var notificationId: Int = 0
internal var totalMediaItems: Int = 0
internal var currentMediaItem: Int = 0
internal var currentStoriesToQtyUploadingMap = HashMap<StoryIndex, Int>() // keep amount of items being uploaded
// for multiple concurrent stories
internal val mediaItemToProgressMap = HashMap<String, Float>()
}

Expand Down Expand Up @@ -55,28 +58,31 @@ class FrameSaveNotifier(private val context: Context, private val service: Frame
}
}

private fun updateForegroundNotification(title: String? = null) {
private fun updateForegroundNotification(title: String) {
updateNotificationBuilder(title)
updateNotificationProgress()
}

private fun updateNotificationBuilder(title: String?) {
private fun updateNotificationBuilder(title: String) {
// set the Notification's title and prepare the Notifications message text,
// i.e. "Saving story... 3 frames remaining"
if (notificationData.totalMediaItems > 0) {
// only media items are being uploaded
// check if special case for ONE media item
if (title != null) {
notificationBuilder.setContentTitle(buildNotificationTitleForFrameSaveProcess(title))
} else {
notificationBuilder.setContentTitle(buildNotificationTitleForFrameSaveProcess(
context.getString(R.string.story_saving_untitled))
)
}
updateNotificationTitle(title)
notificationBuilder.setContentText(buildNotificationSubtitleForFrameSaveProcess())
}
}

private fun updateNotificationTitle(title: String) {
// if there are frames of more than 1 concurrent Story being saved, show plural title
if (notificationData.currentStoriesToQtyUploadingMap.size > 1) {
notificationBuilder.setContentTitle(buildNotificationTitleForFrameSaveProcess(
context.getString(R.string.story_saving_title_several))
)
} else {
notificationBuilder.setContentTitle(buildNotificationTitleForFrameSaveProcess(title))
}
}

private fun getCurrentMediaItem(): Int {
return if (notificationData.currentMediaItem >= notificationData.totalMediaItems)
notificationData.totalMediaItems - 1
Expand All @@ -101,14 +107,20 @@ class FrameSaveNotifier(private val context: Context, private val service: Frame
}
}

@Synchronized fun incrementUploadedMediaCountFromProgressNotification(id: String, success: Boolean = false) {
@Synchronized fun incrementUploadedMediaCountFromProgressNotification(
storyIndex: StoryIndex,
title: String,
id: String,
success: Boolean = false
) {
decrementCurrentUploadingItemQtyForStory(storyIndex)
notificationData.currentMediaItem++
if (success) {
setProgressForMediaItem(id, 1f)
}
if (!removeNotificationAndStopForegroundServiceIfNoItemsInQueue()) {
// update Notification now
updateForegroundNotification()
updateForegroundNotification(title)
}
}

Expand Down Expand Up @@ -140,7 +152,7 @@ class FrameSaveNotifier(private val context: Context, private val service: Frame
Math.ceil((getCurrentOverallProgress() * 100).toDouble()).toInt(),
false
)
doNotify(notificationData.notificationId.toLong(), notificationBuilder.build())
doNotify(notificationData.notificationId, notificationBuilder.build())
}

@Synchronized private fun setProgressForMediaItem(id: String, progress: Float) {
Expand Down Expand Up @@ -170,12 +182,12 @@ class FrameSaveNotifier(private val context: Context, private val service: Frame

// TODO: uncomment these lines when migrating code to WPAndroid
@Synchronized private fun doNotify(
id: Long,
id: Int,
notification: Notification
// notificationType: NotificationType?
) {
try {
notificationManager.notify(id.toInt(), notification)
notificationManager.notify(id, notification)
// TODO track notification when integrating in WPAndroid
// note: commented out code left on purpose
// if (notificationType != null) {
Expand All @@ -199,23 +211,13 @@ class FrameSaveNotifier(private val context: Context, private val service: Frame
notificationData.totalMediaItems = totalMediaItems
}

@Synchronized fun removeMediaInfoFromForegroundNotification(idList: List<String>) {
if (notificationData.totalMediaItems >= idList.size) {
notificationData.totalMediaItems -= idList.size
// update Notification now
updateForegroundNotification(null)
}
}

@Synchronized fun removeOneMediaItemInfoFromForegroundNotification() {
if (notificationData.totalMediaItems >= 1) {
notificationData.totalMediaItems--
// update Notification now
updateForegroundNotification(null)
}
}

@Synchronized fun addStoryPageInfoToForegroundNotification(idList: List<String>, title: String) {
@Synchronized fun addStoryPageInfoToForegroundNotification(
storyIndex: StoryIndex,
idList: List<String>,
title: String
) {
// keep our story current uploads quantity map updated
incrementCurrentUploadingItemQtyForStory(storyIndex)
notificationData.totalMediaItems += idList.size
// setup progresses for each media item
for (id in idList) {
Expand All @@ -224,15 +226,44 @@ class FrameSaveNotifier(private val context: Context, private val service: Frame
startOrUpdateForegroundNotification(title)
}

@Synchronized fun addStoryPageInfoToForegroundNotification(id: String, title: String) {
@Synchronized fun addStoryPageInfoToForegroundNotification(storyIndex: StoryIndex, id: String, title: String) {
// keep our story current uploads quantity map updated
incrementCurrentUploadingItemQtyForStory(storyIndex)
notificationData.totalMediaItems++
// setup progress for media item
setProgressForMediaItem(id, 0.0f)
startOrUpdateForegroundNotification(title)
}

// TODO change signature to receive a CPT (Post) as parameter instead of a plain String
@Synchronized private fun startOrUpdateForegroundNotification(title: String?) {
@Synchronized fun incrementCurrentUploadingItemQtyForStory(storyIndex: StoryIndex) {
val currentAmount = getCurrentUploadingItemQtyForStory(storyIndex)
notificationData.currentStoriesToQtyUploadingMap.put(storyIndex, currentAmount + 1)
}

@Synchronized fun decrementCurrentUploadingItemQtyForStory(storyIndex: StoryIndex) {
var currentAmount = getCurrentUploadingItemQtyForStory(storyIndex)
if (currentAmount > 0) {
currentAmount--
}
if (currentAmount == 0) {
// remove the entry altogether
notificationData.currentStoriesToQtyUploadingMap.remove(storyIndex)
} else {
// otherwise update the value
notificationData.currentStoriesToQtyUploadingMap.put(storyIndex, currentAmount)
}
}

@Synchronized fun getCurrentUploadingItemQtyForStory(storyIndex: StoryIndex): Int {
val currentUploadingQtyForStory = notificationData.currentStoriesToQtyUploadingMap.get(storyIndex)
if (currentUploadingQtyForStory != null) {
return currentUploadingQtyForStory
}
return 0
}

// TODO WPANDROID: change signature to receive a CPT (Post) as parameter instead of a plain String
@Synchronized private fun startOrUpdateForegroundNotification(title: String) {
updateNotificationBuilder(title)
if (notificationData.notificationId == 0) {
notificationData.notificationId = Random().nextInt()
Expand All @@ -242,10 +273,15 @@ class FrameSaveNotifier(private val context: Context, private val service: Frame
)
} else {
// service was already started, let's just modify the notification
doNotify(notificationData.notificationId.toLong(), notificationBuilder.build())
doNotify(notificationData.notificationId, notificationBuilder.build())
}
}

// TODO WPANDROID: we'll need the SiteModel and we can base the notification error IDs on the site ID plus
// PostModel id, the same we do for failed to upload Posts in WPAndroid's UploadService
// For now we will use the StoryIndex that comes with the StorySaveResult, as we can rest assured
// the storyIndex will remain accurate for as long as the notification lives (i.e. we'll cancel it on
// open or when the Composer activity is loaded with the corresponding Story)
fun updateNotificationErrorForStoryFramesSave(
// mediaList: List<MediaModel>,
// site: SiteModel,
Expand All @@ -260,7 +296,7 @@ class FrameSaveNotifier(private val context: Context, private val service: Frame
)

// val notificationId = getNotificationIdForMedia(site)
val notificationId = getNotificationIdForError()
val notificationId = getNotificationIdForError(storySaveResult.storyIndex)
// Tap notification intent (open the media browser)
val notificationIntent = Intent(context, ComposeLoopFrameActivity::class.java)
notificationIntent.putExtra(KEY_STORY_SAVE_RESULT, storySaveResult)
Expand All @@ -275,8 +311,9 @@ class FrameSaveNotifier(private val context: Context, private val service: Frame

val pendingIntent = PendingIntent.getActivity(
context,
notificationId.toInt(),
notificationIntent, PendingIntent.FLAG_ONE_SHOT
notificationId,
notificationIntent,
PendingIntent.FLAG_ONE_SHOT
)

notificationBuilder.setSmallIcon(android.R.drawable.stat_notify_error)
Expand Down Expand Up @@ -315,8 +352,11 @@ class FrameSaveNotifier(private val context: Context, private val service: Frame
doNotify(notificationId, notificationBuilder.build()) // , notificationType)
}

fun getNotificationIdForError(): Long {
return BASE_MEDIA_ERROR_NOTIFICATION_ID.toLong()
fun getNotificationIdForError(storyIndex: StoryIndex): Int {
// TODO WPANDROID we keep the base number because we'll use SiteId and PostModel id's to identify the error
// notification as well, and as such we are using a different base number to avoid collision of notification
// ids.
return BASE_MEDIA_ERROR_NOTIFICATION_ID + storyIndex
}

companion object {
Expand Down
Loading