Skip to content

Commit

Permalink
Add ability to send notification for contract updates
Browse files Browse the repository at this point in the history
  • Loading branch information
ErikKalkoken committed Jan 3, 2025
1 parent 610726e commit be0ec2e
Show file tree
Hide file tree
Showing 8 changed files with 99 additions and 48 deletions.
20 changes: 12 additions & 8 deletions internal/app/character/charactercontract.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,6 @@ import (
esioptional "github.com/antihax/goesi/optional"
)

func (s *CharacterService) ListCharacterContracts(ctx context.Context, characterID int32) ([]*app.CharacterContract, error) {
return s.st.ListCharacterContracts(ctx, characterID)
}

func (s *CharacterService) ListCharacterContractItems(ctx context.Context, contractID int64) ([]*app.CharacterContractItem, error) {
return s.st.ListCharacterContractItems(ctx, contractID)
}

func (s *CharacterService) CountCharacterContractBids(ctx context.Context, contractID int64) (int, error) {
x, err := s.st.ListCharacterContractBidIDs(ctx, contractID)
if err != nil {
Expand All @@ -46,6 +38,18 @@ func (s *CharacterService) GetCharacterContractTopBid(ctx context.Context, contr
return top, nil
}

func (s *CharacterService) ListCharacterContracts(ctx context.Context, characterID int32) ([]*app.CharacterContract, error) {
return s.st.ListCharacterContracts(ctx, characterID)
}

func (s *CharacterService) ListCharacterContractItems(ctx context.Context, contractID int64) ([]*app.CharacterContractItem, error) {
return s.st.ListCharacterContractItems(ctx, contractID)
}

func (s *CharacterService) UpdateCharacterContractNotified(ctx context.Context, id int64, status app.ContractStatus) error {
return s.st.UpdateCharacterContractNotified(ctx, id, status)
}

// updateCharacterContractsESI updates the wallet journal from ESI and reports wether it has changed.
func (s *CharacterService) updateCharacterContractsESI(ctx context.Context, arg UpdateSectionParams) (bool, error) {
if arg.Section != app.SectionContracts {
Expand Down
17 changes: 5 additions & 12 deletions internal/app/storage/charactercontract.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,26 +211,19 @@ func (st *Storage) UpdateCharacterContract(ctx context.Context, arg UpdateCharac
return nil
}

type UpdateCharacterContractNotifiedParams struct {
CharacterID int32
ContractID int32
StatusNotified app.ContractStatus
}

func (st *Storage) UpdateCharacterContractNotified(ctx context.Context, arg UpdateCharacterContractNotifiedParams) error {
if arg.CharacterID == 0 || arg.ContractID == 0 {
return fmt.Errorf("update character contract notified. IDs not set: %v", arg)
func (st *Storage) UpdateCharacterContractNotified(ctx context.Context, id int64, status app.ContractStatus) error {
if id == 0 {
return fmt.Errorf("update character contract notified. IDs not set: %d", id)
}
var statusNotified string
for k, v := range contractStatusToEnum {
if v == arg.StatusNotified {
if v == status {
statusNotified = k
break
}
}
arg2 := queries.UpdateCharacterContractNotifiedParams{
CharacterID: int64(arg.CharacterID),
ContractID: int64(arg.ContractID),
ID: id,
StatusNotified: statusNotified,
}
err := st.q.UpdateCharacterContractNotified(ctx, arg2)
Expand Down
12 changes: 3 additions & 9 deletions internal/app/storage/charactercontract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,18 +127,12 @@ func TestCharacterContract(t *testing.T) {
t.Run("can update notified", func(t *testing.T) {
// given
testutil.TruncateTables(db)
c := factory.CreateCharacter()
o := factory.CreateCharacterContract(storage.CreateCharacterContractParams{CharacterID: c.ID})
arg2 := storage.UpdateCharacterContractNotifiedParams{
CharacterID: o.CharacterID,
ContractID: o.ContractID,
StatusNotified: app.ContractStatusInProgress,
}
c := factory.CreateCharacterContract()
// when
err := r.UpdateCharacterContractNotified(ctx, arg2)
err := r.UpdateCharacterContractNotified(ctx, c.ID, app.ContractStatusInProgress)
// then
if assert.NoError(t, err) {
o, err := r.GetCharacterContract(ctx, o.CharacterID, o.ContractID)
o, err := r.GetCharacterContract(ctx, c.CharacterID, c.ContractID)
if assert.NoError(t, err) {
assert.Equal(t, app.ContractStatusInProgress, o.StatusNotified)
}
Expand Down
3 changes: 1 addition & 2 deletions internal/app/storage/queries/character_contracts.sql
Original file line number Diff line number Diff line change
Expand Up @@ -163,5 +163,4 @@ SET
status_notified = ?,
updated_at = CURRENT_TIMESTAMP
WHERE
character_id = ?
AND contract_id = ?;
id = ?;
8 changes: 3 additions & 5 deletions internal/app/storage/queries/character_contracts.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion internal/app/ui/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const (
settingNotifyCommunicationsEnabledDefault = false
settingNotifyContractsEnabled = "settingNotifyContractsEnabled"
settingNotifyContractsEnabledDefault = false
settingNotifyContractsMaxAgeDefault = 12 // hours
settingNotifyContractsEarliest = "settingNotifyContractsEarliest"
settingNotifyMailsEnabled = "settingNotifyMailsEnabled"
settingNotifyMailsEnabledDefault = false
settingNotifyPIEarliest = "settingNotifyPIEarliest"
Expand Down
3 changes: 3 additions & 0 deletions internal/app/ui/settingswindow.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,9 @@ func (w *settingsWindow) makeNotificationPage() fyne.CanvasObject {
// Contracts toogle
contractsEnabledCheck := kxwidget.NewSwitch(func(on bool) {
w.u.fyneApp.Preferences().SetBool(settingNotifyContractsEnabled, on)
if on {
w.u.fyneApp.Preferences().SetString(settingNotifyContractsEarliest, time.Now().Format(time.RFC3339))
}
})
contractsEnabledCheck.SetState(w.u.fyneApp.Preferences().BoolWithFallback(
settingNotifyContractsEnabled,
Expand Down
82 changes: 71 additions & 11 deletions internal/app/ui/updateticker.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ func (u *UI) updateCharacterSectionAndRefreshIfNeeded(ctx context.Context, chara
if isShown && needsRefresh {
u.contractsArea.refresh()
}
if u.fyneApp.Preferences().BoolWithFallback(settingNotifyContractsEnabled, settingNotifyCommunicationsEnabledDefault) {
go u.notifyContractUpdates(ctx, characterID)
}
case app.SectionImplants:
if isShown && needsRefresh {
u.implantsArea.refresh()
Expand Down Expand Up @@ -175,14 +178,14 @@ func (u *UI) updateCharacterSectionAndRefreshIfNeeded(ctx context.Context, chara
u.overviewArea.refresh()
}
if u.fyneApp.Preferences().BoolWithFallback(settingNotifyMailsEnabled, settingNotifyMailsEnabledDefault) {
go u.processMails(ctx, characterID)
go u.notifyMails(ctx, characterID)
}
case app.SectionNotifications:
if isShown && needsRefresh {
u.notificationsArea.refresh()
}
if u.fyneApp.Preferences().BoolWithFallback(settingNotifyCommunicationsEnabled, settingNotifyCommunicationsEnabledDefault) {
go u.processNotifications(ctx, characterID)
go u.notifyCommunications(ctx, characterID)
}
case app.SectionSkills:
if isShown && needsRefresh {
Expand Down Expand Up @@ -224,7 +227,7 @@ func (u *UI) updateCharacterSectionAndRefreshIfNeeded(ctx context.Context, chara
}
}

func (u *UI) processNotifications(ctx context.Context, characterID int32) {
func (u *UI) notifyCommunications(ctx context.Context, characterID int32) {
maxAge := u.fyneApp.Preferences().IntWithFallback(settingMaxAge, settingMaxAgeDefault)
nn, err := u.CharacterService.ListCharacterNotificationsUnprocessed(ctx, characterID)
if err != nil {
Expand All @@ -239,16 +242,18 @@ func (u *UI) processNotifications(ctx context.Context, characterID int32) {
continue
}
title := fmt.Sprintf("%s: New Communication from %s", characterName, n.Sender.Name)
x := fyne.NewNotification(title, n.Title.ValueOrZero())
u.fyneApp.SendNotification(x)
content := n.Title.ValueOrZero()
nf := fyne.NewNotification(title, content)
u.fyneApp.SendNotification(nf)
slog.Info("communications notification sent", "notif", nf)
if err := u.CharacterService.UpdateCharacterNotificationSetProcessed(ctx, n); err != nil {
slog.Error("Failed to set notification as processed", "characterID", characterID, "id", n.ID, "error", err)
return
}
}
}

func (u *UI) processMails(ctx context.Context, characterID int32) {
func (u *UI) notifyMails(ctx context.Context, characterID int32) {
maxAge := u.fyneApp.Preferences().IntWithFallback(settingMaxAge, settingMaxAgeDefault)
mm, err := u.CharacterService.ListCharacterMailHeadersForUnprocessed(ctx, characterID)
if err != nil {
Expand All @@ -262,16 +267,67 @@ func (u *UI) processMails(ctx context.Context, characterID int32) {
continue
}
title := fmt.Sprintf("%s: New Mail from %s", characterName, m.From)
body := m.Subject
x := fyne.NewNotification(title, body)
u.fyneApp.SendNotification(x)
content := m.Subject
nf := fyne.NewNotification(title, content)
u.fyneApp.SendNotification(nf)
slog.Info("mail notification sent", "notif", nf)
if err := u.CharacterService.UpdateCharacterMailSetProcessed(ctx, m.ID); err != nil {
slog.Error("Failed to set mail as processed", "characterID", characterID, "id", m.MailID, "error", err)
return
}
}
}

func (u *UI) notifyContractUpdates(ctx context.Context, characterID int32) {
cc, err := u.CharacterService.ListCharacterContracts(ctx, characterID)
if err != nil {
slog.Error("Failed to fetch contracts for processing", "characterID", characterID, "error", err)
return
}
characterName := u.StatusCacheService.CharacterName(characterID)
earliest, _ := time.Parse(time.RFC3339, u.fyneApp.Preferences().String(settingNotifyContractsEarliest))
for _, c := range cc {
if c.UpdatedAt.Before(earliest) {
continue
}
if c.Status == c.StatusNotified {
continue
}
if c.Acceptor != nil && c.Acceptor.ID == characterID {
continue // ignore status changed caused by the current character
}
var content string
name := "'" + c.NameDisplay() + "'"
switch c.Type {
case app.ContractTypeCourier:
switch c.Status {
case app.ContractStatusInProgress:
content = fmt.Sprintf("Contract %s has been accepted by %s", name, c.ContractorDisplay())
case app.ContractStatusFinished:
content = fmt.Sprintf("Contract %s has been delivered", name)
case app.ContractStatusFailed:
content = fmt.Sprintf("Contract %s has been failed by %s", name, c.ContractorDisplay())
}
case app.ContractTypeItemExchange:
switch c.Status {
case app.ContractStatusFinished:
content = fmt.Sprintf("Contract %s has been accepted by %s", name, c.ContractorDisplay())
}
}
if content == "" {
continue
}
title := fmt.Sprintf("%s: Contract updated", characterName)
nf := fyne.NewNotification(title, content)
u.fyneApp.SendNotification(nf)
slog.Info("contract notification sent", "notif", nf)
if err := u.CharacterService.UpdateCharacterContractNotified(ctx, c.ID, c.Status); err != nil {
slog.Error("Failed to record contract notification", "characterID", characterID, "id", c.ID, "error", err)
return
}
}
}

func (u *UI) notifyExpiredExtractions(ctx context.Context, characterID int32) {
planets, err := u.CharacterService.ListCharacterPlanets(ctx, characterID)
if err != nil {
Expand All @@ -292,7 +348,9 @@ func (u *UI) notifyExpiredExtractions(ctx context.Context, characterID int32) {
title := fmt.Sprintf("%s: PI extraction expired", characterName)
extracted := strings.Join(p.ExtractedTypeNames(), ",")
content := fmt.Sprintf("Extraction expired at %s for %s", p.EvePlanet.Name, extracted)
u.fyneApp.SendNotification(fyne.NewNotification(title, content))
nf := fyne.NewNotification(title, content)
u.fyneApp.SendNotification(nf)
slog.Info("pi notification sent", "notif", nf)
if err := u.CharacterService.UpdateCharacterPlanetLastNotified(ctx, characterID, p.EvePlanet.ID, expiration); err != nil {
slog.Error("failed to update last notified", "error", err)
}
Expand All @@ -316,6 +374,8 @@ func (u *UI) notifyExpiredTraining(ctx context.Context, characterID int32) error
}
title := fmt.Sprintf("%s: No skill in training", c.EveCharacter.Name)
content := "There is currently no skill being trained for this character."
u.fyneApp.SendNotification(fyne.NewNotification(title, content))
nf := fyne.NewNotification(title, content)
u.fyneApp.SendNotification(nf)
slog.Info("training notification sent", "notif", nf)
return u.CharacterService.UpdateCharacterIsTrainingWatched(ctx, characterID, false)
}

0 comments on commit be0ec2e

Please sign in to comment.