This is how I'm notifying the system to read out my elements:
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, cell)
The problem is that when I send multiple notifications, the one that is already reading gets interrupted! I want to be able to queue it...
I also found in another question here that I should use attributed string:
attributedString.addAttribute(NSAttributedStringKey(
rawValue: UIAccessibilitySpeechAttributeQueueAnnouncement),
value: true,
range: range!.nsRange)
What am I missing here?
What am I missing here?
I have made many tests to try and understand this behaviour that drove me crazy.
My conclusion is that if you send a notification while VoiceOver is speaking a {label / hint / value}, your notification won't be taken into account : there may be a kind of preemption when the system needs to vocalize an attribute of a focused element.
That's only at the end of the vocalization that you can post as many notifications as you want to be well analyzed and interpreted as you wish.
The UIAccessibilitySpeechAttributeQueueAnnouncement key is only useful with your own notifications when the system doesn't need to take over.
If you send many notifications and that the user flicks to focus a new element for instance, the notifications that weren't vocalized will be removed as soon as the system vocalizes the element's attributes.
In that case, if you catch the UIAccessibilityAnnouncementDidFinish event, you will have a false value with the UIAccessibilityAnnouncementKeyWasSuccessful key for the last vocalized notification (UIAccessibilityAnnouncementKeyStringValue)... all the following ones will be ignored with no info given by the observer.
Conclusion : no personal notification is taken into account by VoiceOver when a new focused element or screen/layout changed notification occurs.
How can I queue multiple accessibility notifications for VoiceOver?
According to what's exposed above, I suggest to set up a kind of retry mechanism that would still send your notifications (x times) while they're not perfectly received after y seconds for instance.
That could be a tricky way to be more certain that the notifications are flawlessly received.
With Swift 5 this is working for me:
if (UIAccessibility.isVoiceOverRunning) {
let message: NSAttributedString = NSAttributedString(string: "read me", attributes: [.accessibilitySpeechQueueAnnouncement: true])
UIAccessibility.post(notification: .announcement, argument: message)
}
The key is to set .accessibilitySpeechQueueAnnouncement true.
UIAccessibilityAnnouncementNotification is supposed to be used when the app needs to make an ad-hoc announcement to the user. It's probably not designed to be called repeatedly.
If you did want to queue, you might need to post the first notification, then wait for UIAccessibilityAnnouncementDidFinishNotification to be posted by the system, then you could post the next message in your queue.
(But I'd offer caution: is this really what a visually-impaired user will be expecting?)
You could try using https://github.com/spanage/AccessibilityAnnouncer for your own notifications but keep in mind that if the user taps and interacts with the screen these notifications will be ignored.
Related
I have a scenario where I want to make an announcement before I move the focus to some different elements. When I try to do it-
UIAccessibility.post(notification: .announcement, argument: "a very very very long text")
UIAccessibility.post(notification: .layoutChanged, argument: headerResult)
For the above code, my focus moves to the headerResult without completing the announcement.
I want to keep both notifications independent of each other.
Thanks
You can listen to the announcementDidFinish notification (see documentation) and check the user dictionary to compare the value for the announcementStringValueUserInfoKey key with your "a very very ..." String so you know which announcement finished. But note that it's very confusing for visually impaired persons if focus moves without their interaction (especially if it happens after a long text announcement). You should check the value for announcementWasSuccessfulUserInfoKey to only move focus if the announcement was read successfully until the end.
Suppose I have a collection called 'books' and a page called "all books" in my app. Now let's say I want a little message to pop up in the top right that says "This list is outdated" every time there is an addition, deletion, or change in my 'books' collection. Is there any way to achieve this without having the listener send back all the documents in the 'books' collection each time there is a change? I only want to be notified when the data is outdated and nothing else.
If you know how to achieve this please let me know. I'm good with any language but a solution written in Swift will be preferred.
Thanks in advance!
Just add a field updatedon to your books document and set it to the current time when doing any write (on delete also then, you will have to find a way to hide deleted books).
And set a listener like this:
colref.where("updatedon", ">", new Date())
.orderBy("updatedon","desc")
.limit(1)
This way you are only billed 1 read every time.
It's not possible. A listener on a document or collection always receives the entire document that changed.
If you want a "lite" notification, you could try to use FCM to send a message to the client when a document of interest has changed, but then you'll have to keep a record on the backend of every interesting document for every user, check that list with every change using a Cloud Functions trigger, then notify each client of each interesting change. This is not at all the same as a listener, and would be a lot of work.
I want to be able to design a storeCard that works as a punch card. Every time someone buys a bread one more punch will need to appear on his pass.
Should looks something similar to the design below.
The image withe the circles is strip#2x.png. What is the best way to update that? I am thinking to change the image with a new one every time I sent push notification and the pass gets an update. But from the documentation I am not sure if I can do that... from the documentation:
https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/PassKit_PG/Updating.html#//apple_ref/doc/uid/TP40012195-CH5-SW1
To send the list of serial numbers, do the following:
Look at the registrations table, and determine which passes the device is registered for.
Look at the passes table, and determine which passes have changed since the given tag. Don’t include serial numbers of passes that the device didn’t register for. If no update tag is provided, return all the passes that the device is registered for. For example, you return all registered passes the very first time a device communicates with your server.
Compare the update tags for each pass that has changed and determine which one is the latest. Return the latest update tag to the device.
Respond with this list of serial numbers and the latest update tag in a JSON payload. For example:
{
"serialNumbers" : ["001", "020", "3019"],
"lastUpdated" : "1351981923"
}
How can I approach this? Do I try to update the whole pass every time when a new push message comes? Is that how the update is done or just some bits of the json file (pass.json) are changed? Is there more clever way to achieve this?
I'm trying to figure out the mechanism to post an ephemeral message to a user and then remove it and replace it with a message visible to all. Similar behavior to giphy in which the Slash Command shows an interactive ephemeral message and creates a channel message once the user decides which gif to send. I'm also curious about updating the ephemeral message. I assume this can be done by the response_url if we use an interactive ephemeral message.
I initially figured I'd just create a ephemeral message using chat.postEphemeral and then call chat.delete on it, but it seems chat.delete and chat.update can't be called on a message created using chat.postEphemeral.
The Slack message guidelines seems to suggest that a multi-step interactive flow should always be handled in an ephemeral way so that other channel user don't see all intermediate messages before the result but I'm having bad luck figuring out how to get rid of the ephemeral when done. Probably just being bad at reading but any help appreciated.
Edit with more details:
The documentation around using response_url and postEphemeral states
As you replace messages using chat.update or the replace_original
option, you cannot change a message's type from ephemeral to
in_channel. Once a message has been issued, it will retain its
visibility quality for life.
The message guidelines suggest:
If a user has initiated an action that has multiple steps, those steps
should be shown as ephemeral messages visible only to that user until
the entire action is complete to avoid cluttering the channel for
everyone.
Presumably, I should be able to create an interaction in which I first send an in_channel interactive message.
When a user initiates an action, I should be able to send them a series of ephemeral messages using the response_url and passing response_type: 'ephemeral' and replace_original: false?
A new ephemeral interactive message created this way will have its own response_url for making edits, right?
Once I am done with the interactive flow via ephemeral messages, I can modify the original interactive message using its original response_url?
Lastly, how do I get rid of the last ephemeral edit? Or do I just change it to something like "Workflow completed" and hope for the best? I'm asking because Slash commands obviously seem to have a way to essentially replace the ephemeral message for an in_channel message and I'm trying to figure this kind of workflow out.
I searched high and low on how to do this and finally came across the answer.
Your ephemeral message must trigger an action, i.e. button click.
Your response to the action must use the following body
{
'response_type': 'ephemeral',
'text': '',
'replace_original': true,
'delete_original': true
}
'delete_original': true is the key here, which as far as I can tell is not mentioned in any of the API guides, however it is present in the API field guide under Top-level message fields
If you wish to change the response_type of your message instead of deleting it, you must do so by first deleting the ephemeral message and then posting the same message with 'response_type': 'in_channel'.
In my use case I wanted to take an ephemeral message and repost it with the exact same message body as an in-channel message. I have not found a way to retrieve the content of your ephemeral message, so the best method I've found is to pass whatever necessary data spawned your ephemeral message in the button's value so that your action handler can read this data and dynamically recreate the message body.
In my case, this was the user input being used to perform a query. On the off chance that data in the database changes between the time the original ephemeral message is posted and the in-channel version is posted they will be different. You may be able to send a JSON string directly through this value field and avoid making additional database calls and running the risk of messages changing when posted to the channel. The character limit of value is 2000 so JSON passing is extremely limited.
Assuming you use the same code to generate this body when initially creating the ephemeral message and also when recreating it in-channel, you should receive the same body and essentially are able to change an ephemeral message to in-channel message.
Some ephemeral messages can be "soft" deleted/replaced but only when posted as part of a message with interactive features like buttons or menus. When a button is clicked or a menu selection made, you have a chance to instruct Slack to either "delete" the original message, or replace it with a new one. These docs detail using responses and response_url to accomplish that.
A message created with chat.postEphemeral that itself has no interactive features can never be explicitly deleted. Once it's delivered, it's like a ghost and will disappear following a restart or refresh.
Answering your bulleted questions in order:
Correct, you essentially start a new chain of interactivity with net new ephemeral message you post to that user
Each interactive message interaction will have its own response URL. The new ephemeral message won't have a response_url you can use until the end user presses a button, selects a menu item, etc.
response_url will eventually expire ("using the response_url, your app can continue interacting with users up to 5 times within 30 minutes of the action invocation.") If the original message is non-ephemeral, using chat.update is a better strategy for longer timelines. With ephemeral messages, it's more of a "do your best" strategy. They'll eventually get cleaned up for the user after a refresh.
I think you have a good handle on what's best. Personally, I think it's easier to kick off a new "in_channel" message by using chat.postMessage instead of as a chain effect directly from a slash command or interaction.
The Kotlin/Java version for this solution using the Bolt API as shown below
import com.slack.api.bolt.handler.builtin.BlockActionHandler
import com.slack.api.bolt.request.builtin.BlockActionRequest
import com.slack.api.app_backend.interactive_components.response.ActionResponse
import com.slack.api.bolt.response.Response
import com.slack.api.bolt.context.builtin.ActionContext
object Handler : BlockActionHandler {
override fun apply(req: BlockActionRequest,
context: ActionContext): Response {
val response = ActionResponse
.builder()
.deleteOriginal(true)
.replaceOriginal(true)
.responseType("ephemeral")
.blocks(listOf())
.text("")
.build()
context.respond(response)
return context.ack()
}
}
If you are using Python and Flask the following code should work when you respond to a button click in the ephemeral message:
from flask import jsonify
response = jsonify({
'response_type': 'ephemeral',
'text': '',
'replace_original': 'true',
'delete_original':'true'
})
return make_response(response, 200)
I currently creating an app where the users can add a posting without logging into the app or using any credentials.
Other users of the app can open the app and directly comment on the posts(the comments are an array of the post object).
I read the parse docs and I believe that this will use advance targeting. I saw PFInstallation.currentInstallation() for advanced targeting but I believe that is based on the users class and I am not using the Parse.com users class
What I would like to do is to send a push notification to the original poster when a comment is added to their post... So, I was wondering how I would go completing that?
Thanks!
It couldn't be easier,
Installation has a "user" column. Just make a query that matches the "user" of interest. So, your code will look something like this....
if ( .. comment made, need to send a push .. )
{
console.log(">>> 'comment' was added....");
var query = new Parse.Query(Parse.Installation);
query.equalTo('user', .. whoWroteThePost .. );
alert = "Wow! You have a new comment on a post you wrote!!!";
Parse.Push.send(
{
where:query,
data:{ alert: alert, badge: "Increment" }
});
return;
}
Note that you said ...
"What I would like to do is to send a push notification to the original poster when a comment is added to their post... "
In that sentence you speak of the "original poster". So, that's going to be a variable like originalPoster. So this line of code
query.equalTo('user', .. whoWroteThePost .. );
will be
query.equalTo('user', originalPoster );
Note that this is extremely common, and you can find endless examples on the web! Here's just one: example
Note that:
Parse's term "advanced targeting" is very confusing.
To phrase the same thought another way,
Parse's 'channels' are just silly, ignore them.
That is to say, simply ignore the "channels" nonsense, and just search on users. It's easier and less confusing than the channels business, which is just an extra field you have to fill-out al the time.
It's just one of those weird things about Parse.
I've never used the "non-advanced targeting" - it's stupid and pointless. And the "advanced" targeting is trivial to use: assuming you can write cloud code at all you can do "advanced" targeting. If you can write a query in cloud code, you can do "advanced" targeting.
Essentially,
query.equalTo('user', .. whoWroteThePost .. );
Note that, of course, you may have to first look up who wrote the post, and then from there you can make the query for the Push.
Note, in this process it makes:
no difference at all if the user is anonymous.
You can and should go ahead and send pushes, in the same way.
Advanced targeting is not done against users. It's just that is the easiest way to show an example.
You need to store the installation against the objects you want to push to. So in this case store the installation against the post. Then when the comment comes in you can send a notification to the installation connected to the post it relates to.
I think you are looking something called anonymous users. There is almost impossible to send notification without user's data. But, Parse.com provides something called anonymous users so that app users are not necessary to sign up in order to fully function something user related operations.
Then, you will need to store some information in order to find the target.
Parse.com Anonymous Users