feat(ads): client banner rotation, fade UX & live toggle (PR2)
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 50s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 58s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 50s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 58s
Consume the server-driven banner block (PR1) in the UI and retire the gate. - banner.ts: createScheduler — a smooth weighted round-robin over campaigns (each appears its weight share per cycle, evenly interleaved) with round-robin over a campaign's messages; the rotator drives fade-in -> hold/scroll -> fade-out -> gap -> fade-in, a lone message stays put, reduce-motion swaps instantly without scroll. - model.ts/codec.ts: Profile.banner (Banner/BannerCampaign/BannerTimings) decoded from the fbs block. - Screen.svelte: drop the compile-time SHOW_AD_BANNER; render AdBanner from app.profile.banner (campaigns + timings + reduceMotion). - AdBanner.svelte: opacity-driven fades + scroll host; the rotator is recreated when the campaigns/timings change (a `banner` notify re-fetch swaps them in place). - app.svelte.ts: on the `notify` `banner` sub-kind, refreshProfile() so the banner shows/hides in place. - tests: scheduler distribution + round-robin, the fade sequence, single-message, reduce-motion, stop(); codec banner decode. UI_DESIGN.md + trackers updated.
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
||||
decodeLinkResult,
|
||||
decodeMatchResult,
|
||||
decodeOutgoingList,
|
||||
decodeProfile,
|
||||
decodeSession,
|
||||
decodeFeedbackState,
|
||||
decodeFeedbackUnread,
|
||||
@@ -449,6 +450,40 @@ describe('codec', () => {
|
||||
expect(r.game?.id).toBe('g-open');
|
||||
expect(r.game?.status).toBe('open');
|
||||
});
|
||||
|
||||
it('decodes the profile banner block (campaigns + timings)', () => {
|
||||
const b = new Builder(256);
|
||||
const uid = b.createString('u-1');
|
||||
const m0 = b.createString('promo-en');
|
||||
const msgs = fb.BannerCampaign.createMessagesVector(b, [m0]);
|
||||
const camp = fb.BannerCampaign.createBannerCampaign(b, 3, msgs);
|
||||
const camps = fb.BannerInfo.createCampaignsVector(b, [camp]);
|
||||
const banner = fb.BannerInfo.createBannerInfo(b, camps, 60000, 5000, 40, 1000, 250, 1000);
|
||||
fb.Profile.startProfile(b);
|
||||
fb.Profile.addUserId(b, uid);
|
||||
fb.Profile.addBanner(b, banner);
|
||||
b.finish(fb.Profile.endProfile(b));
|
||||
|
||||
const p = decodeProfile(b.asUint8Array());
|
||||
expect(p.banner?.campaigns).toEqual([{ weight: 3, messages: ['promo-en'] }]);
|
||||
expect(p.banner?.timings).toEqual({
|
||||
holdMs: 60000,
|
||||
edgePauseMs: 5000,
|
||||
scrollPxPerSec: 40,
|
||||
fadeOutMs: 1000,
|
||||
gapMs: 250,
|
||||
fadeInMs: 1000,
|
||||
});
|
||||
});
|
||||
|
||||
it('leaves the profile banner undefined when absent', () => {
|
||||
const b = new Builder(64);
|
||||
const uid = b.createString('u-1');
|
||||
fb.Profile.startProfile(b);
|
||||
fb.Profile.addUserId(b, uid);
|
||||
b.finish(fb.Profile.endProfile(b));
|
||||
expect(decodeProfile(b.asUint8Array()).banner).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
// The live play loop exchanges alphabet indices, mapped through the per-variant
|
||||
|
||||
Reference in New Issue
Block a user