From 3744f54445464d29e11c2f622f3fe6d9938811d7 Mon Sep 17 00:00:00 2001 From: riddim-developer-bot Date: Wed, 27 May 2026 23:34:11 -0400 Subject: [PATCH] Apply adaptive reading width to long-form lists --- ios/epac/Views/Bills/BillDetailView.swift | 1 + ios/epac/Views/Chat/SpeechView.swift | 1 + .../Expenditures/ExpenditureDetailView.swift | 1 + ios/epac/Views/Home/HomeFeedView.swift | 1 + .../Views/Members/MemberProfileView.swift | 333 +++++++++--------- .../Members/MemberVotingHistoryView.swift | 1 + .../Members/MemberVotingRecordView.swift | 1 + .../Views/Petitions/PetitionDetailView.swift | 1 + ios/epac/Views/Petitions/PetitionsView.swift | 1 + ios/epac/Views/Search/SearchView.swift | 3 + 10 files changed, 179 insertions(+), 165 deletions(-) diff --git a/ios/epac/Views/Bills/BillDetailView.swift b/ios/epac/Views/Bills/BillDetailView.swift index bcb53869..ed55c0e8 100644 --- a/ios/epac/Views/Bills/BillDetailView.swift +++ b/ios/epac/Views/Bills/BillDetailView.swift @@ -134,6 +134,7 @@ struct BillDetailView: View { } } .listStyle(.insetGrouped) + .adaptiveReadingWidth() .navigationTitle(bill.number) .navigationBarTitleDisplayMode(.inline) .toolbar { diff --git a/ios/epac/Views/Chat/SpeechView.swift b/ios/epac/Views/Chat/SpeechView.swift index 385c6b06..8e56864e 100644 --- a/ios/epac/Views/Chat/SpeechView.swift +++ b/ios/epac/Views/Chat/SpeechView.swift @@ -211,6 +211,7 @@ struct SpeechView: View { .padding(.horizontal) .padding(.bottom, SpeechLayout.bottomBadgePadding) } + .adaptiveReadingWidth() .simultaneousGesture( TapGesture() .onEnded { diff --git a/ios/epac/Views/Expenditures/ExpenditureDetailView.swift b/ios/epac/Views/Expenditures/ExpenditureDetailView.swift index 16a78e72..bf62cec9 100644 --- a/ios/epac/Views/Expenditures/ExpenditureDetailView.swift +++ b/ios/epac/Views/Expenditures/ExpenditureDetailView.swift @@ -162,6 +162,7 @@ struct ExpenditureDetailView: View { } } .listStyle(.plain) + .adaptiveReadingWidth() .navigationTitle("\(expenditure.firstName) \(expenditure.lastName) (\(String(expenditure.year)) Q\(expenditure.quarter))") .toolbar { ToolbarItem(placement: .topBarTrailing) { diff --git a/ios/epac/Views/Home/HomeFeedView.swift b/ios/epac/Views/Home/HomeFeedView.swift index 9eeb5851..c9d1d168 100644 --- a/ios/epac/Views/Home/HomeFeedView.swift +++ b/ios/epac/Views/Home/HomeFeedView.swift @@ -95,6 +95,7 @@ struct HomeFeedView: View { } } .listStyle(.insetGrouped) + .adaptiveReadingWidth() .accessibilityIdentifier("home-feed-scroll") .refreshable { await loadFeed() diff --git a/ios/epac/Views/Members/MemberProfileView.swift b/ios/epac/Views/Members/MemberProfileView.swift index c285705c..6c2d2164 100644 --- a/ios/epac/Views/Members/MemberProfileView.swift +++ b/ios/epac/Views/Members/MemberProfileView.swift @@ -143,124 +143,186 @@ struct MemberProfileView: View { var body: some View { ScrollView { - VStack(alignment: .center, spacing: MemberProfileLayout.profileStackSpacing) { - MemberAvatar(member: member) - .frame(width: MemberProfileLayout.avatarSize, height: MemberProfileLayout.avatarSize) - .padding(.top, MemberProfileLayout.avatarTopPadding) + Group { + VStack(alignment: .center, spacing: MemberProfileLayout.profileStackSpacing) { + MemberAvatar(member: member) + .frame(width: MemberProfileLayout.avatarSize, height: MemberProfileLayout.avatarSize) + .padding(.top, MemberProfileLayout.avatarTopPadding) + + MemberHighlightsCard(member: member) + + PartyLineScoreView(member: member) + + VStack(alignment: .leading, spacing: MemberProfileLayout.cardSpacing) { + NavigationLink(destination: partyDestination(for: member.party)) { + HStack(spacing: 0) { + ProfileDetailRow(icon: "flag.fill", label: "Party", value: member.party.fullName) + Image(systemName: "chevron.right") + .font(.caption) + .foregroundStyle(.tertiary) + } + } + .foregroundStyle(.primary) + .accessibilityIdentifier("mp-profile-party-link") + ProfileDetailRow(icon: "mappin.and.ellipse", label: "Riding", value: member.riding) + ProfileDetailRow(icon: "location.fill", label: "Province", value: member.province.rawValue) + } + .padding() + .background(Color(.secondarySystemBackground)) + .cornerRadius(MemberProfileLayout.cardCornerRadius) - MemberHighlightsCard(member: member) + if let position = cabinetPosition { + CabinetPositionSection(position: position) + } - PartyLineScoreView(member: member) + if member.email != nil || member.hillPhone != nil || member.constituencyPhone != nil || member.constituencyAddress != nil { + contactSection + } else if !member.contactFetched { + HStack(spacing: MemberProfileLayout.inlineSpacing) { + ProgressView().scaleEffect(MemberProfileLayout.loadingScale) + Text(NSLocalizedString("member.contact.loading", comment: "")) + .font(.caption) + .foregroundStyle(.secondary) + } + .padding() + .frame(maxWidth: .infinity, alignment: .leading) + .background(Color(.secondarySystemBackground)) + .cornerRadius(MemberProfileLayout.cardCornerRadius) + } - VStack(alignment: .leading, spacing: MemberProfileLayout.cardSpacing) { - NavigationLink(destination: partyDestination(for: member.party)) { - HStack(spacing: 0) { - ProfileDetailRow(icon: "flag.fill", label: "Party", value: member.party.fullName) + NavigationLink(destination: MemberVotingRecordView(member: member)) { + HStack { + Label(NSLocalizedString("voting.title", comment: ""), systemImage: "checkmark.ballot") + Spacer() Image(systemName: "chevron.right") .font(.caption) .foregroundStyle(.tertiary) } + .padding() + .background(Color(.secondarySystemBackground)) + .cornerRadius(MemberProfileLayout.cardCornerRadius) } .foregroundStyle(.primary) - .accessibilityIdentifier("mp-profile-party-link") - ProfileDetailRow(icon: "mappin.and.ellipse", label: "Riding", value: member.riding) - ProfileDetailRow(icon: "location.fill", label: "Province", value: member.province.rawValue) - } - .padding() - .background(Color(.secondarySystemBackground)) - .cornerRadius(MemberProfileLayout.cardCornerRadius) - if let position = cabinetPosition { - CabinetPositionSection(position: position) - } - - if member.email != nil || member.hillPhone != nil || member.constituencyPhone != nil || member.constituencyAddress != nil { - contactSection - } else if !member.contactFetched { - HStack(spacing: MemberProfileLayout.inlineSpacing) { - ProgressView().scaleEffect(MemberProfileLayout.loadingScale) - Text(NSLocalizedString("member.contact.loading", comment: "")) - .font(.caption) - .foregroundStyle(.secondary) + NavigationLink(destination: RidingElectionHistoryView(member: member)) { + HStack { + Label("Riding History", systemImage: "chart.bar.xaxis.ascending") + Spacer() + Image(systemName: "chevron.right") + .font(.caption) + .foregroundStyle(.tertiary) + } + .padding() + .background(Color(.secondarySystemBackground)) + .cornerRadius(MemberProfileLayout.cardCornerRadius) } - .padding() - .frame(maxWidth: .infinity, alignment: .leading) - .background(Color(.secondarySystemBackground)) - .cornerRadius(MemberProfileLayout.cardCornerRadius) - } + .foregroundStyle(.primary) - NavigationLink(destination: MemberVotingRecordView(member: member)) { - HStack { - Label(NSLocalizedString("voting.title", comment: ""), systemImage: "checkmark.ballot") - Spacer() - Image(systemName: "chevron.right") - .font(.caption) - .foregroundStyle(.tertiary) - } + // MARK: Lobbying section + DisclosureGroup( + isExpanded: $showLobbying, + content: { + if lobbyingComms.isEmpty && lobbyingLoaded { + Text(NSLocalizedString("lobbying.empty.title", comment: "")) + .font(.caption) + .foregroundStyle(.secondary) + .padding(.vertical, MemberProfileLayout.statCardVerticalPadding) + } else { + ForEach(lobbyingComms.prefix(MemberProfileLayout.lobbyingPreviewLimit)) { comm in + VStack(alignment: .leading, spacing: MemberProfileLayout.compactTextSpacing) { + Text(comm.organizationName) + .font(.subheadline) + .fixedSize(horizontal: false, vertical: true) + if !comm.subjectMatter.isEmpty { + Text(comm.subjectMatter) + .font(.caption2) + .foregroundStyle(.secondary) + .fixedSize(horizontal: false, vertical: true) + } + if let d = comm.communicationDate { + Text(d, style: .date) + .font(.caption2) + .foregroundStyle(.secondary) + } + } + .padding(.vertical, MemberProfileLayout.rowVerticalPadding) + } + if !lobbyingComms.isEmpty { + NavigationLink(destination: LobbyingView(member: member)) { + Text(String(format: NSLocalizedString("lobbying.seeAll", comment: ""), lobbyingComms.count)) + .font(.caption) + .foregroundStyle(.tint) + } + .accessibilityIdentifier("accountability-lobbying-link") + } + } + }, + label: { + HStack { + Image(systemName: "person.fill.badge.plus") + .foregroundStyle(.tint) + Text(NSLocalizedString("lobbying.sectionTitle", comment: "")) + .font(.subheadline) + .fontWeight(.semibold) + } + } + ) .padding() - .background(Color(.secondarySystemBackground)) + .background(Color.appSurface) .cornerRadius(MemberProfileLayout.cardCornerRadius) - } - .foregroundStyle(.primary) - - NavigationLink(destination: RidingElectionHistoryView(member: member)) { - HStack { - Label("Riding History", systemImage: "chart.bar.xaxis.ascending") - Spacer() - Image(systemName: "chevron.right") - .font(.caption) - .foregroundStyle(.tertiary) + .onChange(of: showLobbying) { _, isExpanded in + if isExpanded && !lobbyingLoaded { + // Capture primitive name values on the main actor before async hop. + let ln = member.lastName + let fn = member.firstName + Task { + lobbyingComms = await LobbyistService.fetchCommunications(lastName: ln, firstName: fn) + lobbyingLoaded = true + } + } } - .padding() - .background(Color(.secondarySystemBackground)) - .cornerRadius(MemberProfileLayout.cardCornerRadius) } - .foregroundStyle(.primary) - // MARK: Lobbying section + // MARK: Ethics disclosures + let ethicsInvestigations = EthicsInvestigationsDatabase.investigations(for: member.lastName) DisclosureGroup( - isExpanded: $showLobbying, + isExpanded: $showEthics, content: { - if lobbyingComms.isEmpty && lobbyingLoaded { - Text(NSLocalizedString("lobbying.empty.title", comment: "")) - .font(.caption) - .foregroundStyle(.secondary) - .padding(.vertical, MemberProfileLayout.statCardVerticalPadding) + if ethicsInvestigations.isEmpty { + VStack(alignment: .leading, spacing: MemberProfileLayout.badgeHorizontalPadding) { + Text("No Commissioner reports found for this MP.") + .font(.caption) + .foregroundStyle(.secondary) + .padding(.vertical, MemberProfileLayout.sectionTopPadding) + Link("View annual compliance status (CIEC)", destination: EthicsInvestigationsDatabase.complianceStatusURL) + .font(.caption) + Link("Public registry — disclosures and statements", destination: EthicsInvestigationsDatabase.registryURL) + .font(.caption) + } } else { - ForEach(lobbyingComms.prefix(MemberProfileLayout.lobbyingPreviewLimit)) { comm in + ForEach(ethicsInvestigations) { investigation in VStack(alignment: .leading, spacing: MemberProfileLayout.compactTextSpacing) { - Text(comm.organizationName) + Text(investigation.reportTitle) .font(.subheadline) - .fixedSize(horizontal: false, vertical: true) - if !comm.subjectMatter.isEmpty { - Text(comm.subjectMatter) - .font(.caption2) - .foregroundStyle(.secondary) - .fixedSize(horizontal: false, vertical: true) - } - if let d = comm.communicationDate { - Text(d, style: .date) - .font(.caption2) - .foregroundStyle(.secondary) - } - } - .padding(.vertical, MemberProfileLayout.rowVerticalPadding) - } - if !lobbyingComms.isEmpty { - NavigationLink(destination: LobbyingView(member: member)) { - Text(String(format: NSLocalizedString("lobbying.seeAll", comment: ""), lobbyingComms.count)) - .font(.caption) + Text("\(investigation.type) · \(EthicsInvestigationsDatabase.formattedDate(investigation.date))") + .font(.caption2) + .foregroundStyle(.secondary) + Link("Read report →", destination: investigation.pageURL) + .font(.caption2) .foregroundStyle(.tint) } - .accessibilityIdentifier("accountability-lobbying-link") + .padding(.vertical, MemberProfileLayout.rowVerticalPadding) } + Link("All Commissioner reports", destination: EthicsInvestigationsDatabase.commissionerURL) + .font(.caption) + .foregroundStyle(.tint) } }, label: { HStack { - Image(systemName: "person.fill.badge.plus") + Image(systemName: "checkmark.shield.fill") .foregroundStyle(.tint) - Text(NSLocalizedString("lobbying.sectionTitle", comment: "")) + Text("Ethics Disclosures") .font(.subheadline) .fontWeight(.semibold) } @@ -269,84 +331,25 @@ struct MemberProfileView: View { .padding() .background(Color.appSurface) .cornerRadius(MemberProfileLayout.cardCornerRadius) - .onChange(of: showLobbying) { _, isExpanded in - if isExpanded && !lobbyingLoaded { - // Capture primitive name values on the main actor before async hop. - let ln = member.lastName - let fn = member.firstName - Task { - lobbyingComms = await LobbyistService.fetchCommunications(lastName: ln, firstName: fn) - lobbyingLoaded = true - } - } - } - } - // MARK: Ethics disclosures - let ethicsInvestigations = EthicsInvestigationsDatabase.investigations(for: member.lastName) - DisclosureGroup( - isExpanded: $showEthics, - content: { - if ethicsInvestigations.isEmpty { - VStack(alignment: .leading, spacing: MemberProfileLayout.badgeHorizontalPadding) { - Text("No Commissioner reports found for this MP.") - .font(.caption) - .foregroundStyle(.secondary) - .padding(.vertical, MemberProfileLayout.sectionTopPadding) - Link("View annual compliance status (CIEC)", destination: EthicsInvestigationsDatabase.complianceStatusURL) - .font(.caption) - Link("Public registry — disclosures and statements", destination: EthicsInvestigationsDatabase.registryURL) - .font(.caption) - } - } else { - ForEach(ethicsInvestigations) { investigation in - VStack(alignment: .leading, spacing: MemberProfileLayout.compactTextSpacing) { - Text(investigation.reportTitle) - .font(.subheadline) - Text("\(investigation.type) · \(EthicsInvestigationsDatabase.formattedDate(investigation.date))") - .font(.caption2) - .foregroundStyle(.secondary) - Link("Read report →", destination: investigation.pageURL) - .font(.caption2) - .foregroundStyle(.tint) - } - .padding(.vertical, MemberProfileLayout.rowVerticalPadding) - } - Link("All Commissioner reports", destination: EthicsInvestigationsDatabase.commissionerURL) - .font(.caption) - .foregroundStyle(.tint) - } - }, - label: { - HStack { - Image(systemName: "checkmark.shield.fill") - .foregroundStyle(.tint) - Text("Ethics Disclosures") - .font(.subheadline) - .fontWeight(.semibold) - } - } - ) - .padding() - .background(Color.appSurface) - .cornerRadius(MemberProfileLayout.cardCornerRadius) - - // MARK: Written Questions - WrittenQuestionsSection(member: member) - - // Siri shortcut tip — lets users add "Open MP profile in epac" to Shortcuts - ShortcutsLink() - .shortcutsLinkStyle(.automaticOutline) - .padding(.top, MemberProfileLayout.sectionTopPadding) - .accessibilityLabel("Add epac to Siri and Shortcuts") - - #if DEBUG - Text("Member ID: \(member.memberID)") - .font(.caption2) - .foregroundStyle(.tertiary) - .padding(.top, MemberProfileLayout.sectionTopPadding) - .frame(maxWidth: .infinity) - #endif + // MARK: Written Questions + WrittenQuestionsSection(member: member) + + // Siri shortcut tip — lets users add "Open MP profile in epac" to Shortcuts + ShortcutsLink() + .shortcutsLinkStyle(.automaticOutline) + .padding(.top, MemberProfileLayout.sectionTopPadding) + .accessibilityLabel("Add epac to Siri and Shortcuts") + + #if DEBUG + Text("Member ID: \(member.memberID)") + .font(.caption2) + .foregroundStyle(.tertiary) + .padding(.top, MemberProfileLayout.sectionTopPadding) + .frame(maxWidth: .infinity) + #endif + } + .adaptiveReadingWidth() } .accessibilityIdentifier("mp-profile-scroll") .padding() diff --git a/ios/epac/Views/Members/MemberVotingHistoryView.swift b/ios/epac/Views/Members/MemberVotingHistoryView.swift index 40ae8d61..1340e7b3 100644 --- a/ios/epac/Views/Members/MemberVotingHistoryView.swift +++ b/ios/epac/Views/Members/MemberVotingHistoryView.swift @@ -86,6 +86,7 @@ struct MemberVotingHistoryView: View { } } .listStyle(.plain) + .adaptiveReadingWidth() .accessibilityIdentifier("vote-detail-mp-list") .refreshable { await loadVotes(forceRefresh: true) diff --git a/ios/epac/Views/Members/MemberVotingRecordView.swift b/ios/epac/Views/Members/MemberVotingRecordView.swift index 84bac7b5..a3484cc9 100644 --- a/ios/epac/Views/Members/MemberVotingRecordView.swift +++ b/ios/epac/Views/Members/MemberVotingRecordView.swift @@ -172,6 +172,7 @@ struct MemberVotingRecordView: View { } } .listStyle(.insetGrouped) + .adaptiveReadingWidth() .accessibilityIdentifier("vote-detail-mp-list") .refreshable { guard member.memberID > 0 else { return } diff --git a/ios/epac/Views/Petitions/PetitionDetailView.swift b/ios/epac/Views/Petitions/PetitionDetailView.swift index ec6336d1..9bc4b9ad 100644 --- a/ios/epac/Views/Petitions/PetitionDetailView.swift +++ b/ios/epac/Views/Petitions/PetitionDetailView.swift @@ -97,6 +97,7 @@ struct PetitionDetailView: View { } } .listStyle(.insetGrouped) + .adaptiveReadingWidth() .navigationTitle(petition.id) .navigationBarTitleDisplayMode(.inline) } diff --git a/ios/epac/Views/Petitions/PetitionsView.swift b/ios/epac/Views/Petitions/PetitionsView.swift index 00abe835..5c1faf60 100644 --- a/ios/epac/Views/Petitions/PetitionsView.swift +++ b/ios/epac/Views/Petitions/PetitionsView.swift @@ -77,6 +77,7 @@ struct PetitionsView: View { } } .listStyle(.plain) + .adaptiveReadingWidth() .refreshable { petitions = [] await load() diff --git a/ios/epac/Views/Search/SearchView.swift b/ios/epac/Views/Search/SearchView.swift index 514e02a1..ab0b5684 100644 --- a/ios/epac/Views/Search/SearchView.swift +++ b/ios/epac/Views/Search/SearchView.swift @@ -184,6 +184,7 @@ struct SearchView: View { .accessibilityElement(children: .combine) } } + .adaptiveReadingWidth() } if !viewModel.searchResults.bills.isEmpty { @@ -200,6 +201,7 @@ struct SearchView: View { } } } + .adaptiveReadingWidth() } if !viewModel.searchResults.debates.isEmpty { @@ -222,6 +224,7 @@ struct SearchView: View { .accessibilityHint(result.hansardDate.formatted(date: .long, time: .omitted)) } } + .adaptiveReadingWidth() } } .listStyle(.insetGrouped)