Skip to content

Commit

Permalink
Merge pull request #297 from Automattic/issue/273-save-frame-service-…
Browse files Browse the repository at this point in the history
…no-state-multi-errror

Progress and Error notifications: keep state of multiple concurrent Stories being saved
  • Loading branch information
mzorz authored Apr 15, 2020
2 parents ee352fe + 99b0de6 commit 78132a6
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 84 deletions.
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)
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)
}
}.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

0 comments on commit 78132a6

Please sign in to comment.