Skip to content

feat: add trading over the same network - #339

Open
JunSungSoo wants to merge 2 commits into
chattymin:mainfrom
JunSungSoo:feature/network-trade
Open

JunSungSoo wants to merge 2 commits into
chattymin:mainfrom
JunSungSoo:feature/network-trade

Conversation

@JunSungSoo

Copy link
Copy Markdown

Summary

When several people run PokeTokenBar on the same network while working or
studying together, being able to trade the Pokémon they raised makes the whole
thing more fun. This adds a Trade tab that lets two users on the same
Wi-Fi/LAN swap either a graduated Dex entry or the Pokémon they are currently
raising.

  • Peers discover each other automatically, identified by a nickname (defaults
    to the computer name, editable in Settings) and a permanent 4-character trade
    code. A manual ip:port code is the fallback when discovery is blocked.
  • Each side picks an offer, reviews a proposal showing both sprites, names and
    rarities, and approves. The trade commits only when both sides approve.
  • Receiving a partner's in-progress Pokémon overwrites your own, so the
    approval screen warns by name and progress first (e.g. "Vulpix (82%) will be
    lost"). Every commit first snapshots the save to
    companion-state.pre-trade-<timestamp>.json, and the Trade tab shows where
    that file is and how to restore from it.

Implementation is behind a TradeTransport protocol with two implementations:
MultipeerTradeTransport (MultipeerConnectivity, encryptionPreference: .required, Bonjour _ptb-trade._tcp) and ManualTradeTransport (a direct
NWConnection with 4-byte length-prefixed frames and a size cap). Multipeer
allows many peers, so the 1:1 rule is enforced in that layer, with a
deterministic tiebreak for simultaneous cross-invitations. TradeSession is
the transport-agnostic state machine above it (hello → offer → both-accept →
commit), and CompanionStore.applyTradeCommit(sending:receiving:) performs the
backup, removal and insert. Incoming payloads are clamped in
TradeItem.sanitized() at the trust boundary, following the existing
SaveTransfer.sanitized precedent.

Bluetooth-only trading is out of scope here. The transport layer is
protocol-based so it can be added later; this PR is Wi-Fi/LAN only.

Type of change

  • Bug fix
  • New feature
  • Refactor / cleanup
  • Documentation
  • Other:

UI changes

Before After
The popover has four tabs (Home / Dex / Bag / Settings), and Settings has no trade-related fields. A fifth Trade tab is added, still fitting the 360pt popover width. It shows your own nickname and trade code, a discovered-peer list (nickname + code), a manual-code fallback, an offer picker rendering each candidate's sprite and name, and the proposal screen with both sides' sprite, name and rarity plus the overwrite warning and the backup-file location. Settings gains a trade identity group with an editable nickname field and the read-only trade code.

While a trade is connected the popover no longer closes on an outside click,
so a trade cannot be lost mid-approval.

Checklist

  • swift build and swift test pass locally
  • PR title and description are written in English
  • UI changes are described above (before/after — images optional)
  • No copyrighted assets, secrets, or private tooling references are committed (see CONTRIBUTING)
  • Tests were added or updated for this change

Tests added: item model, message codec, session state machine, TCP framing,
both transports, and the commit/backup path. One integration test drives the
real MultipeerConnectivity stack and skips rather than fails when no local
network is available. Also verified manually end to end with two app instances
on one Mac — a Dex entry and an in-progress Pokémon were swapped, the overwrite
warning appeared, and both sides produced a pre-trade backup file.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant