Add seasonal features to neighbours: implement season markers and headings, enhance activity filtering for 'me' scope, and update styles for better visibility.

This commit is contained in:
Brenda Wallace committed 2026-10-19 20:53:26 +13:00
1 parent ecb84e35ba
commit fa33d7b911
8 files changed
+180 -4

No files matched your search

+12
View File
@@ -21,6 +21,18 @@
.neighbours-side { flex: 1 1 300px; min-width: 0; }
.neighbours-season {
margin: 28px 0 4px;
padding: 6px 12px;
font-size: 0.95rem;
font-weight: 700;
color: #fff;
background: #3f5e2b;
border-radius: 4px;
&:first-child { margin-top: 0; }
}
.neighbours-day {
display: flex;
gap: 10px;
+31
View File
@@ -1,6 +1,8 @@
# frozen_string_literal: true
module NeighboursHelper
include SeasonsHelper
PICTURES_SHOWN = 8
LAUNCHED_ON = Date.new(2026, 10, 14)
NEW_FOR = 3.months
@@ -25,6 +27,31 @@ module NeighboursHelper
day == today - 1 ? t('neighbours.day.yesterday', date:) : date
end
SEASON_STARTS = [3, 6, 9, 12].freeze
# The first day of the season a date is in. Both hemispheres change season
# on the same days, so this tells us where a marker goes whichever half of
# the world someone gardens in.
def season_start(date)
month = SEASON_STARTS.select { |start| start <= date.month }.max
month ? Date.new(date.year, month, 1) : Date.new(date.year - 1, 12, 1)
end
# "Spring 2026", or "Spring 2026 · Autumn in the northern hemisphere" when
# the feed has people in both halves of the world. Their own hemisphere is
# not named, unless they have not said where they live.
def season_heading(start, neighbours)
hemispheres = neighbours.hemispheres
return if hemispheres.empty?
mine = hemisphere(neighbours.member.latitude)
names = hemispheres.map do |half|
season = t("seasons.#{season_in(half, start)}")
hemispheres.one? || half == mine ? season : t('neighbours.season.in_hemisphere', season:, hemisphere: t("neighbours.season.#{half}"))
end
"#{names.first} #{start.year}#{names.drop(1).map { |name| " · #{name}" }.join}"
end
# Where someone is and when they did it: "Upper Hutt · 2 hours ago", or
# "from 7:40am" when they did several things in a day.
def entry_when(entry)
@@ -61,6 +88,10 @@ module NeighboursHelper
end
end
def season_in(hemisphere, start)
SEASON_MONTHS.fetch(hemisphere).find { |_season, months| months.include?(start.month) }.first
end
def subject_label(subject)
case subject
when Garden then t('neighbours.subject.garden', name: subject.name)
+19 -3
View File
@@ -5,7 +5,9 @@
# Nearby widens in rings, close by first and then further out, until there are
# enough people to show.
class Neighbours
SCOPES = %w(auto following nearby anywhere).freeze
include SeasonsHelper
SCOPES = %w(auto following nearby anywhere me).freeze
RADII_KM = [25, 150].freeze
WORLD_KM = 20_000
MIN_NEARBY = 3
@@ -66,7 +68,7 @@ class Neighbours
end
def shows_nearby?
scope != 'following'
%w(following me).exclude?(scope)
end
def located?
@@ -117,7 +119,7 @@ class Neighbours
# Who the page is about, for its headings: people they follow and people
# near them, or only one, or nearby once it has widened to somewhere.
def audience
return :following if scope == 'following'
return scope.to_sym if %w(following me).include?(scope)
return (widened? ? :all : :following_and_near) if followed.any? && shows_following?
reach || :near
@@ -125,6 +127,8 @@ class Neighbours
# What to say under the title: what they follow and how nearby went.
def lede
return :me if scope == 'me'
if followed.empty? then nobody_close? ? :widened : :none
elsif few_followed? then :few
else :following
@@ -145,6 +149,16 @@ class Neighbours
@feed
end
# The hemispheres the feed spans, their own first, so each season marker can
# name the season in each. Empty when nobody in it has said where they live.
def hemispheres
@hemispheres ||= begin
latitudes = Member.where(id: feed_owner_ids).where.not(latitude: nil).distinct.pluck(Arel.sql('latitude < 0'))
theirs = latitudes.map { |south| south ? :southern : :northern }
([hemisphere(member.latitude)] + theirs).compact.uniq
end
end
def more_feed?
build_feed unless defined?(@feed)
@more_feed
@@ -185,6 +199,8 @@ class Neighbours
end
def feed_owner_ids
return [member.id] if scope == 'me'
@feed_owner_ids ||= (shows_following? ? followed_ids : []) + nearby_members.map(&:id)
end
+7 -1
View File
@@ -1,6 +1,12 @@
-# Days of the feed. `after` is the last day already on the page, so a day that
-# carries on from it does not get a second heading.
-# carries on from it does not get a second heading, and neither does a season
-# marker when it is still the same season.
- previous_season = after && season_start(after)
- neighbours.feed.each_with_index do |day, index|
- season = season_start(day.day)
- if season != previous_season && (heading = season_heading(season, neighbours))
%h2.neighbours-season= heading
- previous_season = season
- unless index.zero? && day.day == after
%h3.neighbours-day= day_heading(day.day)
= render partial: 'neighbours/entry', collection: day.updates, as: :entry
+9
View File
@@ -497,6 +497,9 @@ en:
following_and_near:
feed: People you follow and gardeners near you
posts: From people you follow and near you
me:
feed: Just you
posts: From you
near:
feed: Gardeners near you
posts: From gardeners near you
@@ -552,6 +555,10 @@ en:
region: Within %{km} km
widened: We widened the search until we found some.
you: you
season:
in_hemisphere: "%{season} in the %{hemisphere} hemisphere"
northern: northern
southern: southern
show:
add_location_html: "%{link} to see gardeners near you."
add_location_link: Add where you live
@@ -559,12 +566,14 @@ en:
lede:
few: You follow a few people. Showing them first, then those close to you.
following: What the gardeners you follow, and the ones near you, are up to.
me: Just what you have been up to.
none: You are not following anyone yet, so this shows gardeners near you.
widened: Nobody you follow, and nobody close by yet, so we looked further out.
scope:
anywhere: Anywhere
auto: Auto
following: People I follow
me: Me
nearby: Nearby
title: My friends & neighbours
welcome:
+27
View File
@@ -68,6 +68,33 @@ describe 'My friends and neighbours' do
expect(page).to have_css('article', text: /friendly.*Planned an activity, Weed the beans/m)
end
it 'shows only their own activity under Me' do
friend = create(:member, login_name: 'friendly')
member.follows.create!(followed: friend)
create(:planting, owner: friend, garden: friend.gardens.first, planted_at: Time.zone.today)
create(:harvest, owner: member, harvested_at: Time.zone.today)
visit neighbours_path(scope: 'me')
expect(page).to have_css('article', text: /Harvested/).and have_no_css('article', text: /friendly/)
end
it 'marks where the season changes' do
create(:harvest, owner: member, harvested_at: Date.new(2026, 10, 20))
create(:harvest, owner: member, harvested_at: Date.new(2026, 8, 20))
visit neighbours_path(scope: 'me')
expect(page).to have_css('h2.neighbours-season', text: 'Spring 2026').and have_css('h2.neighbours-season', text: 'Winter 2026')
end
it 'names the season in the other hemisphere too' do
friend = create_located_member('Oslo', 59.9, 10.7, login_name: 'norwegian')
member.follows.create!(followed: friend)
create(:harvest, owner: friend, harvested_at: Date.new(2026, 10, 20))
visit neighbours_path
expect(page).to have_css('h2.neighbours-season', text: 'Spring 2026 · Autumn in the northern hemisphere')
end
it 'stops suggesting someone who is dismissed' do
crop = create(:crop)
create(:planting, owner: member, garden: member.gardens.first, crop:)
+35
View File
@@ -0,0 +1,35 @@
# frozen_string_literal: true
require 'rails_helper'
describe NeighboursHelper do
describe '#season_start' do
it 'is the first of the month a season begins in' do
expect(helper.season_start(Date.new(2026, 10, 20))).to eq Date.new(2026, 9, 1)
end
it 'belongs to the year before in January' do
expect(helper.season_start(Date.new(2027, 1, 15))).to eq Date.new(2026, 12, 1)
end
end
describe '#season_heading' do
let(:start) { Date.new(2026, 12, 1) }
let(:member) { build(:member, latitude: -41.29) }
it 'names the season in the southern hemisphere' do
neighbours = instance_double(Neighbours, member:, hemispheres: [:southern])
expect(helper.season_heading(start, neighbours)).to eq 'Summer 2026'
end
it 'names both when the feed spans the world' do
neighbours = instance_double(Neighbours, member:, hemispheres: %i(southern northern))
expect(helper.season_heading(start, neighbours)).to eq 'Summer 2026 · Winter in the northern hemisphere'
end
it 'is nothing when nobody has said where they live' do
neighbours = instance_double(Neighbours, member:, hemispheres: [])
expect(helper.season_heading(start, neighbours)).to be_nil
end
end
end
+40
View File
@@ -161,6 +161,46 @@ describe Neighbours do
end
end
describe 'with the me scope' do
subject(:neighbours) { described_class.new(member, scope: 'me') }
let(:friend) { create(:member) }
before { member.follows.create!(followed: friend) }
it 'has what the member did' do
planting = create(:planting, owner: member, garden: member.gardens.first, planted_at: Date.new(2026, 10, 1))
expect(neighbours.feed.flat_map(&:updates).flat_map(&:items).map(&:record)).to eq [planting]
end
it 'leaves out the people they follow' do
create(:planting, owner: friend, garden: friend.gardens.first, planted_at: Date.new(2026, 10, 1))
expect(neighbours.feed).to be_empty
end
it 'does not look for people nearby' do
expect(neighbours.shows_nearby?).to be false
end
end
describe '#hemispheres' do
it 'has their own first, then the other' do
friend = create_located_member('Oslo', 59.9, 10.7)
member.follows.create!(followed: friend)
expect(described_class.new(member).hemispheres).to eq %i(southern northern)
end
it 'has one when everyone is in the same half' do
friend = create_located_member('Auckland', -36.85, 174.76)
member.follows.create!(followed: friend)
expect(described_class.new(member).hemispheres).to eq [:southern]
end
end
describe '#feed photos and layouts' do
let(:friend) { create(:member) }
let(:planting) { create(:planting, owner: friend, garden: friend.gardens.first, planted_at: Date.new(2026, 10, 1)) }