Add switch for changing user profile photos grid view

This commit is contained in:
Marcin Czachurski 2023-05-06 11:47:13 +02:00
parent f2d8c5c47f
commit 2ca4b42b30
11 changed files with 179 additions and 26 deletions

View File

@ -33,6 +33,7 @@ extension ApplicationSettings {
@NSManaged public var showFavouritesOnTimeline: Bool
@NSManaged public var showAltIconOnTimeline: Bool
@NSManaged public var warnAboutMissingAlt: Bool
@NSManaged public var showGridOnUserProfile: Bool
@NSManaged public var customNavigationMenuItem1: Int32
@NSManaged public var customNavigationMenuItem2: Int32

View File

@ -58,6 +58,7 @@ class ApplicationSettingsHandler {
applicationState.showFavouritesOnTimeline = defaultSettings.showFavouritesOnTimeline
applicationState.showAltIconOnTimeline = defaultSettings.showAltIconOnTimeline
applicationState.warnAboutMissingAlt = defaultSettings.warnAboutMissingAlt
applicationState.showGridOnUserProfile = defaultSettings.showGridOnUserProfile
if let menuPosition = MenuPosition(rawValue: Int(defaultSettings.menuPosition)) {
applicationState.menuPosition = menuPosition
@ -190,6 +191,12 @@ class ApplicationSettingsHandler {
CoreDataHandler.shared.save()
}
func set(showGridOnUserProfile: Bool) {
let defaultSettings = self.get()
defaultSettings.showGridOnUserProfile = showGridOnUserProfile
CoreDataHandler.shared.save()
}
private func createApplicationSettingsEntity(viewContext: NSManagedObjectContext? = nil) -> ApplicationSettings {
let context = viewContext ?? CoreDataHandler.shared.container.viewContext
return ApplicationSettings(context: context)

View File

@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>_XCCurrentVersionName</key>
<string>Vernissage-013.xcdatamodel</string>
<string>Vernissage-014.xcdatamodel</string>
</dict>
</plist>

View File

@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21754" systemVersion="22E261" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="AccountData" representedClassName="AccountData" syncable="YES">
<attribute name="accessToken" optional="YES" attributeType="String"/>
<attribute name="acct" attributeType="String"/>
<attribute name="avatar" optional="YES" attributeType="URI"/>
<attribute name="avatarData" optional="YES" attributeType="Binary"/>
<attribute name="clientId" attributeType="String"/>
<attribute name="clientSecret" attributeType="String"/>
<attribute name="clientVapidKey" attributeType="String"/>
<attribute name="createdAt" attributeType="String"/>
<attribute name="displayName" optional="YES" attributeType="String"/>
<attribute name="followersCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="followingCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="header" optional="YES" attributeType="URI"/>
<attribute name="id" attributeType="String"/>
<attribute name="lastSeenStatusId" optional="YES" attributeType="String"/>
<attribute name="locked" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="note" optional="YES" attributeType="String"/>
<attribute name="refreshToken" optional="YES" attributeType="String"/>
<attribute name="serverUrl" attributeType="URI"/>
<attribute name="statusesCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="url" optional="YES" attributeType="URI"/>
<attribute name="username" attributeType="String"/>
<relationship name="statuses" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="StatusData" inverseName="pixelfedAccount" inverseEntity="StatusData"/>
</entity>
<entity name="ApplicationSettings" representedClassName="ApplicationSettings" syncable="YES">
<attribute name="activeIcon" attributeType="String" defaultValueString="Default"/>
<attribute name="avatarShape" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="YES"/>
<attribute name="currentAccount" optional="YES" attributeType="String"/>
<attribute name="customNavigationMenuItem1" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="YES"/>
<attribute name="customNavigationMenuItem2" attributeType="Integer 32" defaultValueString="2" usesScalarValueType="YES"/>
<attribute name="customNavigationMenuItem3" attributeType="Integer 32" defaultValueString="5" usesScalarValueType="YES"/>
<attribute name="hapticAnimationEnabled" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="hapticButtonPressEnabled" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="hapticNotificationEnabled" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="hapticRefreshEnabled" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="hapticTabSelectionEnabled" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="lastRefreshTokens" attributeType="Date" defaultDateTimeInterval="694256400" usesScalarValueType="NO"/>
<attribute name="menuPosition" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="YES"/>
<attribute name="showAltIconOnTimeline" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="showAvatarsOnTimeline" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="showFavouritesOnTimeline" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="showGridOnUserProfile" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="showPhotoDescription" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="showSensitive" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="theme" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="tintColor" attributeType="Integer 32" defaultValueString="2" usesScalarValueType="YES"/>
<attribute name="warnAboutMissingAlt" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
</entity>
<entity name="AttachmentData" representedClassName="AttachmentData" syncable="YES">
<attribute name="blurhash" optional="YES" attributeType="String"/>
<attribute name="data" optional="YES" attributeType="Binary" allowsExternalBinaryDataStorage="YES"/>
<attribute name="exifCamera" optional="YES" attributeType="String"/>
<attribute name="exifCreatedDate" optional="YES" attributeType="String"/>
<attribute name="exifExposure" optional="YES" attributeType="String"/>
<attribute name="exifLens" optional="YES" attributeType="String"/>
<attribute name="id" attributeType="String"/>
<attribute name="metaImageHeight" optional="YES" attributeType="Integer 32" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="metaImageWidth" optional="YES" attributeType="Integer 32" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="order" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="previewUrl" optional="YES" attributeType="URI"/>
<attribute name="remoteUrl" optional="YES" attributeType="URI"/>
<attribute name="statusId" attributeType="String"/>
<attribute name="text" optional="YES" attributeType="String"/>
<attribute name="type" attributeType="String"/>
<attribute name="url" attributeType="URI"/>
<relationship name="statusRelation" maxCount="1" deletionRule="Nullify" destinationEntity="StatusData" inverseName="attachmentsRelation" inverseEntity="StatusData"/>
</entity>
<entity name="StatusData" representedClassName="StatusData" syncable="YES">
<attribute name="accountAvatar" optional="YES" attributeType="URI"/>
<attribute name="accountDisplayName" optional="YES" attributeType="String"/>
<attribute name="accountId" attributeType="String"/>
<attribute name="accountUsername" optional="YES" attributeType="String"/>
<attribute name="applicationName" optional="YES" attributeType="String"/>
<attribute name="applicationWebsite" optional="YES" attributeType="URI"/>
<attribute name="bookmarked" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="content" attributeType="String"/>
<attribute name="createdAt" attributeType="String"/>
<attribute name="favourited" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="favouritesCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="id" attributeType="String"/>
<attribute name="inReplyToAccount" optional="YES" attributeType="String"/>
<attribute name="inReplyToId" optional="YES" attributeType="String"/>
<attribute name="muted" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="pinned" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="reblogged" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="rebloggedAccountAvatar" optional="YES" attributeType="URI"/>
<attribute name="rebloggedAccountDisplayName" optional="YES" attributeType="String"/>
<attribute name="rebloggedAccountId" optional="YES" attributeType="String"/>
<attribute name="rebloggedAccountUsername" optional="YES" attributeType="String"/>
<attribute name="rebloggedStatusId" optional="YES" attributeType="String"/>
<attribute name="reblogsCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="repliesCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="sensitive" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="spoilerText" optional="YES" attributeType="String"/>
<attribute name="uri" attributeType="String"/>
<attribute name="url" optional="YES" attributeType="URI"/>
<attribute name="visibility" attributeType="String"/>
<relationship name="attachmentsRelation" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="AttachmentData" inverseName="statusRelation" inverseEntity="AttachmentData"/>
<relationship name="pixelfedAccount" maxCount="1" deletionRule="Nullify" destinationEntity="AccountData" inverseName="statuses" inverseEntity="AccountData"/>
</entity>
</model>

View File

@ -104,6 +104,9 @@ public class ApplicationState: ObservableObject {
/// Show warning about missing ALT texts on compose screen.
@Published public var warnAboutMissingAlt = true
/// Show grid of photos on user profile.
@Published public var showGridOnUserProfile = false
public func changeApplicationState(accountModel: AccountModel, instance: Instance?, lastSeenStatusId: String?) {
self.account = accountModel
self.lastSeenStatusId = lastSeenStatusId

View File

@ -131,7 +131,9 @@ public class PixelfedClientAuthenticated: PixelfedClientProtocol {
return try JSONDecoder().decode(type, from: data)
} catch {
let json = String(data: data, encoding: .utf8)!
print(json)
print(String(describing: error))
throw error
}
@ -158,7 +160,9 @@ public class PixelfedClientAuthenticated: PixelfedClientProtocol {
return Linkable(data: decoded, link: link)
} catch {
let json = String(data: data, encoding: .utf8)!
print(json)
print(String(describing: error))
throw error
}

View File

@ -246,6 +246,7 @@
F808641329756666009F035C /* NotificationRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationRowView.swift; sourceTree = "<group>"; };
F8121CA7298A86D600B466C7 /* InstanceRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceRowView.swift; sourceTree = "<group>"; };
F815F60B29E49CF20044566B /* Avatar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Avatar.swift; sourceTree = "<group>"; };
F8206A032A06547600E19412 /* Vernissage-014.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage-014.xcdatamodel"; sourceTree = "<group>"; };
F8210DCE2966B600001D9973 /* ImageRowAsync.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRowAsync.swift; sourceTree = "<group>"; };
F8210DDC2966CF17001D9973 /* StatusData+Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusData+Status.swift"; sourceTree = "<group>"; };
F8210DDE2966CFC7001D9973 /* AttachmentData+Attachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttachmentData+Attachment.swift"; sourceTree = "<group>"; };
@ -1316,7 +1317,7 @@
CODE_SIGN_ENTITLEMENTS = VernissageWidget/VernissageWidgetExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 139;
CURRENT_PROJECT_VERSION = 140;
DEVELOPMENT_TEAM = B2U9FEKYP8;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = VernissageWidget/Info.plist;
@ -1327,7 +1328,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.8.0;
MARKETING_VERSION = 1.9.0;
PRODUCT_BUNDLE_IDENTIFIER = dev.mczachurski.vernissage.widget;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@ -1344,7 +1345,7 @@
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_ENTITLEMENTS = VernissageWidget/VernissageWidgetExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 139;
CURRENT_PROJECT_VERSION = 140;
DEVELOPMENT_TEAM = B2U9FEKYP8;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = VernissageWidget/Info.plist;
@ -1355,7 +1356,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.8.0;
MARKETING_VERSION = 1.9.0;
PRODUCT_BUNDLE_IDENTIFIER = dev.mczachurski.vernissage.widget;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@ -1371,7 +1372,7 @@
CODE_SIGN_ENTITLEMENTS = VernissageShare/VernissageShareExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 139;
CURRENT_PROJECT_VERSION = 140;
DEVELOPMENT_TEAM = B2U9FEKYP8;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = VernissageShare/Info.plist;
@ -1383,7 +1384,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.8.0;
MARKETING_VERSION = 1.9.0;
PRODUCT_BUNDLE_IDENTIFIER = dev.mczachurski.vernissage.share;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@ -1398,7 +1399,7 @@
buildSettings = {
CODE_SIGN_ENTITLEMENTS = VernissageShare/VernissageShareExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 139;
CURRENT_PROJECT_VERSION = 140;
DEVELOPMENT_TEAM = B2U9FEKYP8;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = VernissageShare/Info.plist;
@ -1410,7 +1411,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.8.0;
MARKETING_VERSION = 1.9.0;
PRODUCT_BUNDLE_IDENTIFIER = dev.mczachurski.vernissage.share;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@ -1547,7 +1548,7 @@
CODE_SIGN_ENTITLEMENTS = Vernissage/Vernissage.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 139;
CURRENT_PROJECT_VERSION = 140;
DEVELOPMENT_ASSET_PATHS = "\"Vernissage/Preview Content\"";
DEVELOPMENT_TEAM = B2U9FEKYP8;
ENABLE_PREVIEWS = YES;
@ -1566,7 +1567,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.8.0;
MARKETING_VERSION = 1.9.0;
PRODUCT_BUNDLE_IDENTIFIER = dev.mczachurski.vernissage;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -1589,7 +1590,7 @@
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
CODE_SIGN_ENTITLEMENTS = Vernissage/Vernissage.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 139;
CURRENT_PROJECT_VERSION = 140;
DEVELOPMENT_ASSET_PATHS = "\"Vernissage/Preview Content\"";
DEVELOPMENT_TEAM = B2U9FEKYP8;
ENABLE_PREVIEWS = YES;
@ -1608,7 +1609,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.8.0;
MARKETING_VERSION = 1.9.0;
PRODUCT_BUNDLE_IDENTIFIER = dev.mczachurski.vernissage;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
@ -1773,6 +1774,7 @@
F88C2476295C37BB0006098B /* Vernissage.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
F8206A032A06547600E19412 /* Vernissage-014.xcdatamodel */,
F865B4D42A0252FB008ACDFC /* Vernissage-013.xcdatamodel */,
F8EF3C8B29FC3A5F00CBFF7C /* Vernissage-012.xcdatamodel */,
F871F21F29EF0FEC00A351EF /* Vernissage-011.xcdatamodel */,
@ -1788,7 +1790,7 @@
F8C937A929882CA90004D782 /* Vernissage-001.xcdatamodel */,
F88C2477295C37BB0006098B /* Vernissage.xcdatamodel */,
);
currentVersion = F865B4D42A0252FB008ACDFC /* Vernissage-013.xcdatamodel */;
currentVersion = F8206A032A06547600E19412 /* Vernissage-014.xcdatamodel */;
path = Vernissage.xcdatamodeld;
sourceTree = "<group>";
versionGroupType = wrapper.xcdatamodel;

View File

@ -103,8 +103,10 @@ struct HomeFeedView: View {
if let account = self.applicationState.account {
if let lastSeenStatusId = try await HomeTimelineService.shared.refreshTimeline(for: account) {
try await HomeTimelineService.shared.save(lastSeenStatusId: lastSeenStatusId, for: account)
self.applicationState.lastSeenStatusId = lastSeenStatusId
}
asyncAfter(0.35) {
self.applicationState.amountOfNewStatuses = 0
}
}

View File

@ -102,7 +102,7 @@ struct UserProfileHeaderView: View {
.foregroundColor(.lightGrayColor.opacity(0.5))
.font(.footnote)
}
.padding()
.padding([.top, .leading, .trailing])
}
@ViewBuilder

View File

@ -24,14 +24,43 @@ struct UserProfileStatusesView: View {
private let defaultLimit = 20
private let imagePrefetcher = ImagePrefetcher(destination: .diskCache)
private let singleGrids = [GridItem(.flexible(), spacing: 10)]
private let dubleGrid = [GridItem(.flexible(), spacing: 10), GridItem(.flexible(), spacing: 0)]
var body: some View {
// LazyVStack(alignment: .center) {
if firstLoadFinished == true {
LazyVGrid(columns: [GridItem(.flexible(), spacing: 10), GridItem(.flexible(), spacing: 0)], spacing: 5) {
HStack {
Spacer()
Button {
withAnimation {
self.applicationState.showGridOnUserProfile = false
ApplicationSettingsHandler.shared.set(showGridOnUserProfile: false)
}
} label: {
Image(systemName: "rectangle.grid.1x2.fill")
.foregroundColor(self.applicationState.showGridOnUserProfile ? .lightGrayColor : .accentColor)
.padding(.trailing, 8)
.padding(.bottom, 8)
}
Button {
withAnimation {
self.applicationState.showGridOnUserProfile = true
ApplicationSettingsHandler.shared.set(showGridOnUserProfile: true)
}
} label: {
Image(systemName: "rectangle.grid.2x2.fill")
.foregroundColor(self.applicationState.showGridOnUserProfile ? .accentColor : .lightGrayColor)
.padding(.trailing, 16)
.padding(.bottom, 8)
}
}
LazyVGrid(columns: self.applicationState.showGridOnUserProfile ? dubleGrid : singleGrids, spacing: 5) {
ForEach(self.statusViewModels, id: \.id) { item in
ImageRowAsync(statusViewModel: item, withAvatar: false, clipToSquare: true)
.frame(width: UIScreen.main.bounds.width / 2, height: UIScreen.main.bounds.width / 2)
ImageRowAsync(statusViewModel: item, withAvatar: false, clipToSquare: self.applicationState.showGridOnUserProfile)
.if(self.applicationState.showGridOnUserProfile) {
$0.frame(width: UIScreen.main.bounds.width / 2, height: UIScreen.main.bounds.width / 2)
}
}
if allItemsLoaded == false && firstLoadFinished == true {

View File

@ -49,7 +49,7 @@ struct ImageRowItemAsync: View {
if let image = state.image {
if self.statusViewModel.sensitive && !self.applicationState.showSensitive {
ZStack {
ContentWarning(spoilerText: self.statusViewModel.spoilerText) {
ContentWarning(spoilerText: self.clipToSquare ? nil : self.statusViewModel.spoilerText) {
self.imageContainerView(image: image)
.imageContextMenu(statusModel: self.statusViewModel,
attachmentModel: self.attachment,
@ -57,11 +57,13 @@ struct ImageRowItemAsync: View {
} blurred: {
ZStack {
BlurredImage(blurhash: attachment.blurhash)
ImageAvatar(displayName: self.statusViewModel.account.displayNameWithoutEmojis,
avatarUrl: self.statusViewModel.account.avatar) {
self.routerPath.navigate(to: .userProfile(accountId: self.statusViewModel.account.id,
accountDisplayName: self.statusViewModel.account.displayNameWithoutEmojis,
accountUserName: self.statusViewModel.account.acct))
if self.showAvatar {
ImageAvatar(displayName: self.statusViewModel.account.displayNameWithoutEmojis,
avatarUrl: self.statusViewModel.account.avatar) {
self.routerPath.navigate(to: .userProfile(accountId: self.statusViewModel.account.id,
accountDisplayName: self.statusViewModel.account.displayNameWithoutEmojis,
accountUserName: self.statusViewModel.account.acct))
}
}
}
.onTapGesture {