Merge pull request #65 from VernissageApp/feature/user-profile-grid
#46 Feature/user profile grid
This commit is contained in:
commit
4e896e12b9
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>_XCCurrentVersionName</key>
|
||||
<string>Vernissage-013.xcdatamodel</string>
|
||||
<string>Vernissage-014.xcdatamodel</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
@ -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>
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -190,6 +190,7 @@
|
|||
F8B0885E29942E31002AB40A /* ThirdPartyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8B0885D29942E31002AB40A /* ThirdPartyView.swift */; };
|
||||
F8B0886029943498002AB40A /* OtherSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8B0885F29943498002AB40A /* OtherSectionView.swift */; };
|
||||
F8B08862299435C9002AB40A /* SupportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8B08861299435C9002AB40A /* SupportView.swift */; };
|
||||
F8C287A32A06B4C90072213F /* ImageScale.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8C287A22A06B4C90072213F /* ImageScale.swift */; };
|
||||
F8D5444329D4066C002225D6 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8D5444229D4066C002225D6 /* AppDelegate.swift */; };
|
||||
F8DF38E429DD68820047F1AA /* ViewOffsetKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8DF38E329DD68820047F1AA /* ViewOffsetKey.swift */; };
|
||||
F8DF38E629DDB98A0047F1AA /* SocialsSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8DF38E529DDB98A0047F1AA /* SocialsSectionView.swift */; };
|
||||
|
@ -246,6 +247,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>"; };
|
||||
|
@ -389,6 +391,7 @@
|
|||
F8B08861299435C9002AB40A /* SupportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportView.swift; sourceTree = "<group>"; };
|
||||
F8B3699A29D86EB600BE3808 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = "<group>"; };
|
||||
F8B3699B29D86EBD00BE3808 /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitignore; sourceTree = "<group>"; };
|
||||
F8C287A22A06B4C90072213F /* ImageScale.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageScale.swift; sourceTree = "<group>"; };
|
||||
F8C937A929882CA90004D782 /* Vernissage-001.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage-001.xcdatamodel"; sourceTree = "<group>"; };
|
||||
F8CAE64129B8F1AF001E0372 /* Vernissage-005.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage-005.xcdatamodel"; sourceTree = "<group>"; };
|
||||
F8D5444229D4066C002225D6 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
|
@ -530,6 +533,7 @@
|
|||
F8DF38E329DD68820047F1AA /* ViewOffsetKey.swift */,
|
||||
F871F21C29EF0D7000A351EF /* NavigationMenuItemDetails.swift */,
|
||||
F8624D3C29F2D3AC00204986 /* SelectedMenuItemDetails.swift */,
|
||||
F8C287A22A06B4C90072213F /* ImageScale.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1254,6 +1258,7 @@
|
|||
F89B5CC229D01BF700549F2F /* InstanceView.swift in Sources */,
|
||||
F825F0CB29F7CFC4008BD204 /* FollowRequestsView.swift in Sources */,
|
||||
F825F0C929F7A562008BD204 /* UserProfilePrivateAccountView.swift in Sources */,
|
||||
F8C287A32A06B4C90072213F /* ImageScale.swift in Sources */,
|
||||
F89F57B029D1C11200001EE3 /* RelationshipModel.swift in Sources */,
|
||||
F88AB05829B36B8200345EDE /* AccountsPhotoView.swift in Sources */,
|
||||
F85D4971296402DC00751DF7 /* AuthorizationService.swift in Sources */,
|
||||
|
@ -1316,7 +1321,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = VernissageWidget/VernissageWidgetExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 139;
|
||||
CURRENT_PROJECT_VERSION = 141;
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = VernissageWidget/Info.plist;
|
||||
|
@ -1327,7 +1332,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 +1349,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 = 141;
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = VernissageWidget/Info.plist;
|
||||
|
@ -1355,7 +1360,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 +1376,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = VernissageShare/VernissageShareExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 139;
|
||||
CURRENT_PROJECT_VERSION = 141;
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = VernissageShare/Info.plist;
|
||||
|
@ -1383,7 +1388,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 +1403,7 @@
|
|||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = VernissageShare/VernissageShareExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 139;
|
||||
CURRENT_PROJECT_VERSION = 141;
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = VernissageShare/Info.plist;
|
||||
|
@ -1410,7 +1415,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 +1552,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Vernissage/Vernissage.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 139;
|
||||
CURRENT_PROJECT_VERSION = 141;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Vernissage/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
|
@ -1566,7 +1571,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 +1594,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 = 141;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Vernissage/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
|
@ -1608,7 +1613,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 +1778,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 +1794,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;
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the Apache License 2.0.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum ImageScale {
|
||||
case orginalFullWidth
|
||||
case squareHalfWidth
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -240,18 +240,14 @@ struct StatusView: View {
|
|||
}
|
||||
|
||||
if let imageHeight = self.imageHeight, let imageWidth = self.imageWidth, imageHeight > 0 && imageWidth > 0 {
|
||||
return self.calculateHeight(width: Double(imageWidth), height: Double(imageHeight))
|
||||
let calculatedSize = ImageSizeService.shared.calculate(width: Double(imageWidth), height: Double(imageHeight))
|
||||
return calculatedSize.height
|
||||
}
|
||||
|
||||
// If we don't have image height and width in metadata, we have to use some constant height.
|
||||
return UIScreen.main.bounds.width * 0.75
|
||||
}
|
||||
|
||||
private func calculateHeight(width: Double, height: Double) -> CGFloat {
|
||||
let divider = width / UIScreen.main.bounds.size.width
|
||||
return height / divider
|
||||
}
|
||||
|
||||
private func getMainStatus(status: StatusModel) async throws -> StatusModel {
|
||||
guard let inReplyToId = status.inReplyToId else {
|
||||
return status
|
||||
|
|
|
@ -102,7 +102,7 @@ struct UserProfileHeaderView: View {
|
|||
.foregroundColor(.lightGrayColor.opacity(0.5))
|
||||
.font(.footnote)
|
||||
}
|
||||
.padding()
|
||||
.padding([.top, .leading, .trailing])
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
|
|
|
@ -24,12 +24,45 @@ 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 {
|
||||
if firstLoadFinished == true {
|
||||
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)
|
||||
ImageRowAsync(statusViewModel: item,
|
||||
withAvatar: false,
|
||||
imageScale: self.applicationState.showGridOnUserProfile ? .squareHalfWidth : .orginalFullWidth)
|
||||
.if(self.applicationState.showGridOnUserProfile) {
|
||||
$0.frame(width: UIScreen.main.bounds.width / 2, height: UIScreen.main.bounds.width / 2)
|
||||
}
|
||||
}
|
||||
|
||||
if allItemsLoaded == false && firstLoadFinished == true {
|
||||
|
@ -46,16 +79,16 @@ struct UserProfileStatusesView: View {
|
|||
Spacer()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LoadingIndicator()
|
||||
}
|
||||
}
|
||||
.onFirstAppear {
|
||||
do {
|
||||
try await self.loadStatuses()
|
||||
} catch {
|
||||
ErrorService.shared.handle(error, message: "global.error.errorDuringDownloadStatuses", showToastr: !Task.isCancelled)
|
||||
}
|
||||
} else {
|
||||
LoadingIndicator()
|
||||
.onFirstAppear {
|
||||
do {
|
||||
try await self.loadStatuses()
|
||||
} catch {
|
||||
ErrorService.shared.handle(error, message: "global.error.errorDuringDownloadStatuses", showToastr: !Task.isCancelled)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -101,5 +134,4 @@ struct UserProfileStatusesView: View {
|
|||
private func prefetch(statusModels: [StatusModel]) {
|
||||
imagePrefetcher.startPrefetching(with: statusModels.getAllImagesUrls())
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -14,13 +14,15 @@ struct ImageRowAsync: View {
|
|||
private let statusViewModel: StatusModel
|
||||
private let firstAttachment: AttachmentModel?
|
||||
private let showAvatar: Bool
|
||||
private let imageScale: ImageScale
|
||||
|
||||
@State private var selected: String
|
||||
@State private var imageHeight: Double
|
||||
@State private var imageWidth: Double
|
||||
|
||||
init(statusViewModel: StatusModel, withAvatar showAvatar: Bool = true) {
|
||||
init(statusViewModel: StatusModel, withAvatar showAvatar: Bool = true, imageScale: ImageScale = .orginalFullWidth) {
|
||||
self.showAvatar = showAvatar
|
||||
self.imageScale = imageScale
|
||||
self.statusViewModel = statusViewModel
|
||||
self.firstAttachment = statusViewModel.mediaAttachments.first
|
||||
self.selected = String.empty()
|
||||
|
@ -44,7 +46,11 @@ struct ImageRowAsync: View {
|
|||
|
||||
var body: some View {
|
||||
if statusViewModel.mediaAttachments.count == 1, let firstAttachment = self.firstAttachment {
|
||||
ImageRowItemAsync(statusViewModel: self.statusViewModel, attachment: firstAttachment, withAvatar: self.showAvatar) { (imageWidth, imageHeight) in
|
||||
ImageRowItemAsync(statusViewModel: self.statusViewModel,
|
||||
attachment: firstAttachment,
|
||||
withAvatar: self.showAvatar,
|
||||
imageScale: self.imageScale) { (imageWidth, imageHeight) in
|
||||
|
||||
// When we download image and calculate real size we have to change view size.
|
||||
if imageWidth != self.imageWidth || imageHeight != self.imageHeight {
|
||||
withAnimation(.linear(duration: 0.4)) {
|
||||
|
@ -53,11 +59,17 @@ struct ImageRowAsync: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.frame(width: self.imageWidth, height: self.imageHeight)
|
||||
.if(self.imageScale == .orginalFullWidth) {
|
||||
$0.frame(width: self.imageWidth, height: self.imageHeight)
|
||||
}
|
||||
} else {
|
||||
TabView(selection: $selected) {
|
||||
ForEach(statusViewModel.mediaAttachments, id: \.id) { attachment in
|
||||
ImageRowItemAsync(statusViewModel: self.statusViewModel, attachment: attachment, withAvatar: self.showAvatar) { (imageWidth, imageHeight) in
|
||||
ImageRowItemAsync(statusViewModel: self.statusViewModel,
|
||||
attachment: attachment,
|
||||
withAvatar: self.showAvatar,
|
||||
imageScale: self.imageScale) { (imageWidth, imageHeight) in
|
||||
|
||||
// When we download image and calculate real size we have to change view size (only when image is now visible).
|
||||
if attachment.id == self.selected {
|
||||
if imageWidth != self.imageWidth || imageHeight != self.imageHeight {
|
||||
|
@ -86,7 +98,9 @@ struct ImageRowAsync: View {
|
|||
}
|
||||
}
|
||||
})
|
||||
.frame(width: self.imageWidth, height: self.imageHeight)
|
||||
.if(self.imageScale == .orginalFullWidth) {
|
||||
$0.frame(width: self.imageWidth, height: self.imageHeight)
|
||||
}
|
||||
.tabViewStyle(.page(indexDisplayMode: .never))
|
||||
.overlay(CustomPageTabViewStyleView(pages: self.statusViewModel.mediaAttachments, currentId: $selected))
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ struct ImageRowItemAsync: View {
|
|||
private var attachment: AttachmentModel
|
||||
private let showAvatar: Bool
|
||||
private let imageFromCache: Bool
|
||||
private let imageScale: ImageScale
|
||||
|
||||
@State private var showThumbImage = false
|
||||
@State private var opacity = 1.0
|
||||
|
@ -31,8 +32,11 @@ struct ImageRowItemAsync: View {
|
|||
|
||||
init(statusViewModel: StatusModel,
|
||||
attachment: AttachmentModel,
|
||||
withAvatar showAvatar: Bool = true, onImageDownloaded: @escaping (_: Double, _: Double) -> Void) {
|
||||
withAvatar showAvatar: Bool = true,
|
||||
imageScale: ImageScale = .orginalFullWidth,
|
||||
onImageDownloaded: @escaping (_: Double, _: Double) -> Void) {
|
||||
self.showAvatar = showAvatar
|
||||
self.imageScale = imageScale
|
||||
self.statusViewModel = statusViewModel
|
||||
self.attachment = attachment
|
||||
self.onImageDownloaded = onImageDownloaded
|
||||
|
@ -45,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.imageScale == .orginalFullWidth ? self.statusViewModel.spoilerText : nil) {
|
||||
self.imageContainerView(image: image)
|
||||
.imageContextMenu(statusModel: self.statusViewModel,
|
||||
attachmentModel: self.attachment,
|
||||
|
@ -53,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 {
|
||||
|
@ -149,7 +155,10 @@ struct ImageRowItemAsync: View {
|
|||
private func imageView(image: Image) -> some View {
|
||||
image
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.scaledToFill()
|
||||
.if(self.imageScale == .squareHalfWidth) {
|
||||
$0.frame(width: UIScreen.main.bounds.width / 2, height: UIScreen.main.bounds.width / 2).clipped()
|
||||
}
|
||||
.onTapGesture(count: 2) {
|
||||
Task {
|
||||
// Update favourite in Pixelfed server.
|
||||
|
|
|
@ -224,7 +224,8 @@ struct ImageViewer: View {
|
|||
|
||||
private func calculateStartingOffset() -> CGSize {
|
||||
// Image size on the screen.
|
||||
let imageOnScreenHeight = self.calculateHeight(width: self.imageWidth, height: self.imageHeight)
|
||||
let calculatedSize = ImageSizeService.shared.calculate(width: self.imageWidth, height: self.imageHeight)
|
||||
let imageOnScreenHeight = calculatedSize.height
|
||||
|
||||
// Calculate full space for image.
|
||||
let safeAreaInsetsTop = UIApplication.shared.keyWindow?.safeAreaInsets.top ?? 20.0
|
||||
|
@ -254,9 +255,4 @@ struct ImageViewer: View {
|
|||
return 88.0
|
||||
}
|
||||
}
|
||||
|
||||
private func calculateHeight(width: Double, height: Double) -> CGFloat {
|
||||
let divider = width / UIScreen.main.bounds.size.width
|
||||
return height / divider
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue