In c5bdf6c094, we started to show errors
when a subscription failed to renew because of a charge failure.
Now, we show additional text depending on what the charge failure was.
For example, if you had an invalid card number, we show a special string
for that.
This adds the first screen for badge gifting. It lets you see the gift
badge, pick the currency, and advance to the next screen.
It also adds a skeleton for the next screen, so there's somewhere to
advance to, but that screen is unfinished.
All of this is behind disabled flags, so this should have no user
impact.
Currently, when your subscription expires due a charge failure, we
incorrectly tell you that it's due to inactivity. This fixes that, by
telling you about the charge failure.
This is a bit difficult to test on its own, so I:
- Faked out the smallest pieces I could in an effort to test states
manually
- Created `BadgeErrorSheetState` which is pretty thoroughly tested
MediaGallery models a list of GalleryDate-based sections (in practice, months), each of which has a certain number of items. Sections are loaded on demand (that is, there may be newer and older sections that are not in the model), and always know their number of items. Items are also loaded on demand, potentially non-contiguously. This model is designed around the needs of UICollectionView (a thread's All Media view), but it also supports flat views of media (swiping between items in the media detail view, which can cross album boundaries).
This model is a result of refactoring I did last year to improve the performance and UX of the All Media view, but the implementation in MediaGallery.swift is entangled with the actual attachments and messages in the database, making it hard to test. This commit pulls the state management part out into its own struct, MediaGallerySections, which wraps the OrderedDictionary of items-by-section. Our MediaGalleryItem model references TSAttachmentStream directly and our queries go straight to the database, so to avoid having to set up real attachments in testing, MediaGallerySections is generic over a loader (delegate? data source?) of items with "gallery dates" and unique IDs. Now all the basic section- and item-loading APIs can be unit-tested.
This is implemented by seeing if a data-detected item is at the very
end of a truncated string, in which case it isn't trusted to be
complete. This applies not only to URLs, but to other data-detected
items as well like emails and addresses. It's also specific to
truncated text since a user could very well type an ellipsis in their
actual message, and *that* shouldn't prevent linkification.
It's possible that an item at the very end of a truncated string *is*
complete (i.e. the truncation point was just *after* the URL), but we
can't be sure without comparing to the full text or storing additional
information, and that's trickier for a number of reasons. The user can
still expand the message and get the linkification in this case.
If the load result is contiguous with the already loaded items, we append
rather than replace.
background:
We have 4 load flavors:
- load older (scrolling up)
- load more recent (scrolling down after unload)
- load most recent (initial page load or scroll down button)
- load around (arbitrary jump from search results or tapping quoted reply)
Previously only "load earlier" and "load later" would append their results to
the existing loaded items, trimming if necessary.
"load most recent" and "load around" would not append, instead they'd replace
the existing loaded items, since the loaded items might not be contiguous with
the existing loaded items.
An optimization we have is that when given a load request, we compare to the
already-loaded items to determine which "unfetched" items from the request
actually need to be fetched.
The error: we consulted the already-loaded items for "load most recent" and
"load around" modes, which discard the previously-loaded items to avoid
introducing a discontinuity in case the fetched items are far away in message
history.
Pseudo code example:
// given these already loaded items
alreadyLoaded = [1, 2, 3, 4, 5]
// if we have a search result for message 5, we'll request to "load around" 5
requestLoadedAround(5) ->
// we'll generate a request set around 5
requestSet = (3..<8)
// The problem is here, when we remove already-loaded items
unfetched = requestSet - alreadyLoaded // == [6, 7, 8]
// Since "load around" replaced the loaded set, rather than appending to
// it, we inadverently lost some important items (3, 4, 5) from our
// request set.
alreadyLoaded = unfetched
A simple solution would be to not consider alreadyLoaded when replacing rather than appending -
for "load around" or "load most recent" but not for "load older" and "load more recent".
alreadyLoaded = [1, 2, 3, 4, 5]
requestLoadedAround(5) ->
requestSet = (3..<8)
// fetch everything rather than worry about removing the
// already-fetched-items
unfetched = requestSet
alreadyLoaded = unfetched
When we're making large discontiguous jumps in the conversation history this
behavior of replacing rather than appending is unavoidable, but when doing
short hops it's wasteful to lose the already loaded items.
So now, whether the load be via "load older", "load more recent", "load most
recent", or "load around" - whenever the fetched items are contiguous with the
already loaded items, we append rather than replace.