Add user profile for other accounts
This commit is contained in:
parent
3c2ee8c592
commit
d2d4844469
|
@ -13,6 +13,16 @@
|
||||||
F80048062961850500E6868A /* StatusData+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = F80048022961850500E6868A /* StatusData+CoreDataProperties.swift */; };
|
F80048062961850500E6868A /* StatusData+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = F80048022961850500E6868A /* StatusData+CoreDataProperties.swift */; };
|
||||||
F80048082961E6DE00E6868A /* StatusDataHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F80048072961E6DE00E6868A /* StatusDataHandler.swift */; };
|
F80048082961E6DE00E6868A /* StatusDataHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F80048072961E6DE00E6868A /* StatusDataHandler.swift */; };
|
||||||
F800480A2961EA1900E6868A /* AttachmentDataHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F80048092961EA1900E6868A /* AttachmentDataHandler.swift */; };
|
F800480A2961EA1900E6868A /* AttachmentDataHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F80048092961EA1900E6868A /* AttachmentDataHandler.swift */; };
|
||||||
|
F8210DCF2966B600001D9973 /* ImageRowAsync.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8210DCE2966B600001D9973 /* ImageRowAsync.swift */; };
|
||||||
|
F8210DD52966BB7E001D9973 /* Nuke in Frameworks */ = {isa = PBXBuildFile; productRef = F8210DD42966BB7E001D9973 /* Nuke */; };
|
||||||
|
F8210DD72966BB7E001D9973 /* NukeExtensions in Frameworks */ = {isa = PBXBuildFile; productRef = F8210DD62966BB7E001D9973 /* NukeExtensions */; };
|
||||||
|
F8210DD92966BB7E001D9973 /* NukeUI in Frameworks */ = {isa = PBXBuildFile; productRef = F8210DD82966BB7E001D9973 /* NukeUI */; };
|
||||||
|
F8210DDD2966CF17001D9973 /* StatusData+Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8210DDC2966CF17001D9973 /* StatusData+Status.swift */; };
|
||||||
|
F8210DDF2966CFC7001D9973 /* AttachmentData+Attachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8210DDE2966CFC7001D9973 /* AttachmentData+Attachment.swift */; };
|
||||||
|
F8210DE12966D0C4001D9973 /* StatusService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8210DE02966D0C4001D9973 /* StatusService.swift */; };
|
||||||
|
F8210DE32966D256001D9973 /* Status+StatusData.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8210DE22966D256001D9973 /* Status+StatusData.swift */; };
|
||||||
|
F8210DE52966E160001D9973 /* Color+SystemColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8210DE42966E160001D9973 /* Color+SystemColors.swift */; };
|
||||||
|
F8210DE72966E1D1001D9973 /* Color+Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8210DE62966E1D1001D9973 /* Color+Assets.swift */; };
|
||||||
F8341F90295C636C009C8EE6 /* UIImage+Exif.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8341F8F295C636C009C8EE6 /* UIImage+Exif.swift */; };
|
F8341F90295C636C009C8EE6 /* UIImage+Exif.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8341F8F295C636C009C8EE6 /* UIImage+Exif.swift */; };
|
||||||
F8341F92295C63BB009C8EE6 /* ImageStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8341F91295C63BB009C8EE6 /* ImageStatus.swift */; };
|
F8341F92295C63BB009C8EE6 /* ImageStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8341F91295C63BB009C8EE6 /* ImageStatus.swift */; };
|
||||||
F83901A6295D8EC000456AE2 /* LabelIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = F83901A5295D8EC000456AE2 /* LabelIcon.swift */; };
|
F83901A6295D8EC000456AE2 /* LabelIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = F83901A5295D8EC000456AE2 /* LabelIcon.swift */; };
|
||||||
|
@ -64,6 +74,13 @@
|
||||||
F80048022961850500E6868A /* StatusData+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusData+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
F80048022961850500E6868A /* StatusData+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusData+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||||
F80048072961E6DE00E6868A /* StatusDataHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusDataHandler.swift; sourceTree = "<group>"; };
|
F80048072961E6DE00E6868A /* StatusDataHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusDataHandler.swift; sourceTree = "<group>"; };
|
||||||
F80048092961EA1900E6868A /* AttachmentDataHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentDataHandler.swift; sourceTree = "<group>"; };
|
F80048092961EA1900E6868A /* AttachmentDataHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentDataHandler.swift; 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>"; };
|
||||||
|
F8210DE02966D0C4001D9973 /* StatusService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusService.swift; sourceTree = "<group>"; };
|
||||||
|
F8210DE22966D256001D9973 /* Status+StatusData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Status+StatusData.swift"; sourceTree = "<group>"; };
|
||||||
|
F8210DE42966E160001D9973 /* Color+SystemColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+SystemColors.swift"; sourceTree = "<group>"; };
|
||||||
|
F8210DE62966E1D1001D9973 /* Color+Assets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Assets.swift"; sourceTree = "<group>"; };
|
||||||
F8341F8F295C636C009C8EE6 /* UIImage+Exif.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Exif.swift"; sourceTree = "<group>"; };
|
F8341F8F295C636C009C8EE6 /* UIImage+Exif.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Exif.swift"; sourceTree = "<group>"; };
|
||||||
F8341F91295C63BB009C8EE6 /* ImageStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageStatus.swift; sourceTree = "<group>"; };
|
F8341F91295C63BB009C8EE6 /* ImageStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageStatus.swift; sourceTree = "<group>"; };
|
||||||
F83901A5295D8EC000456AE2 /* LabelIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelIcon.swift; sourceTree = "<group>"; };
|
F83901A5295D8EC000456AE2 /* LabelIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelIcon.swift; sourceTree = "<group>"; };
|
||||||
|
@ -115,6 +132,9 @@
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
F866F6B729608467002E8F88 /* MastodonSwift in Frameworks */,
|
F866F6B729608467002E8F88 /* MastodonSwift in Frameworks */,
|
||||||
|
F8210DD52966BB7E001D9973 /* Nuke in Frameworks */,
|
||||||
|
F8210DD72966BB7E001D9973 /* NukeExtensions in Frameworks */,
|
||||||
|
F8210DD92966BB7E001D9973 /* NukeUI in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -143,6 +163,9 @@
|
||||||
F85D4980296417F700751DF7 /* MastodonClientAuthenticated+Context.swift */,
|
F85D4980296417F700751DF7 /* MastodonClientAuthenticated+Context.swift */,
|
||||||
F8A93D812965FF5D001D8331 /* MastodonClientAuthenticated+Account.swift */,
|
F8A93D812965FF5D001D8331 /* MastodonClientAuthenticated+Account.swift */,
|
||||||
F85D49862964334100751DF7 /* String+Date.swift */,
|
F85D49862964334100751DF7 /* String+Date.swift */,
|
||||||
|
F8210DE22966D256001D9973 /* Status+StatusData.swift */,
|
||||||
|
F8210DE42966E160001D9973 /* Color+SystemColors.swift */,
|
||||||
|
F8210DE62966E1D1001D9973 /* Color+Assets.swift */,
|
||||||
);
|
);
|
||||||
path = Extensions;
|
path = Extensions;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -163,9 +186,11 @@
|
||||||
F80047FF2961850500E6868A /* AttachmentData+CoreDataClass.swift */,
|
F80047FF2961850500E6868A /* AttachmentData+CoreDataClass.swift */,
|
||||||
F80048002961850500E6868A /* AttachmentData+CoreDataProperties.swift */,
|
F80048002961850500E6868A /* AttachmentData+CoreDataProperties.swift */,
|
||||||
F85D498229642FAC00751DF7 /* AttachmentData+Comperable.swift */,
|
F85D498229642FAC00751DF7 /* AttachmentData+Comperable.swift */,
|
||||||
|
F8210DDE2966CFC7001D9973 /* AttachmentData+Attachment.swift */,
|
||||||
F80048012961850500E6868A /* StatusData+CoreDataClass.swift */,
|
F80048012961850500E6868A /* StatusData+CoreDataClass.swift */,
|
||||||
F80048022961850500E6868A /* StatusData+CoreDataProperties.swift */,
|
F80048022961850500E6868A /* StatusData+CoreDataProperties.swift */,
|
||||||
F85D49842964301800751DF7 /* StatusData+Attachments.swift */,
|
F85D49842964301800751DF7 /* StatusData+Attachments.swift */,
|
||||||
|
F8210DDC2966CF17001D9973 /* StatusData+Status.swift */,
|
||||||
F866F69E296040A8002E8F88 /* ApplicationSettings+CoreDataClass.swift */,
|
F866F69E296040A8002E8F88 /* ApplicationSettings+CoreDataClass.swift */,
|
||||||
F866F69F296040A8002E8F88 /* ApplicationSettings+CoreDataProperties.swift */,
|
F866F69F296040A8002E8F88 /* ApplicationSettings+CoreDataProperties.swift */,
|
||||||
F88FAD28295F43B8009B20C9 /* AccountData+CoreDataClass.swift */,
|
F88FAD28295F43B8009B20C9 /* AccountData+CoreDataClass.swift */,
|
||||||
|
@ -193,6 +218,7 @@
|
||||||
F83901A5295D8EC000456AE2 /* LabelIcon.swift */,
|
F83901A5295D8EC000456AE2 /* LabelIcon.swift */,
|
||||||
F85D4972296406E700751DF7 /* BottomRight.swift */,
|
F85D4972296406E700751DF7 /* BottomRight.swift */,
|
||||||
F85D497629640A5200751DF7 /* ImageRow.swift */,
|
F85D497629640A5200751DF7 /* ImageRow.swift */,
|
||||||
|
F8210DCE2966B600001D9973 /* ImageRowAsync.swift */,
|
||||||
F85D497829640B9D00751DF7 /* ImagesCarousel.swift */,
|
F85D497829640B9D00751DF7 /* ImagesCarousel.swift */,
|
||||||
F85D497A29640C8200751DF7 /* UsernameRow.swift */,
|
F85D497A29640C8200751DF7 /* UsernameRow.swift */,
|
||||||
F85D497C29640D5900751DF7 /* InteractionRow.swift */,
|
F85D497C29640D5900751DF7 /* InteractionRow.swift */,
|
||||||
|
@ -252,6 +278,7 @@
|
||||||
F85D4970296402DC00751DF7 /* AuthorizationService.swift */,
|
F85D4970296402DC00751DF7 /* AuthorizationService.swift */,
|
||||||
F85D4974296407F100751DF7 /* TimelineService.swift */,
|
F85D4974296407F100751DF7 /* TimelineService.swift */,
|
||||||
F8A93D7F2965FED4001D8331 /* AccountService.swift */,
|
F8A93D7F2965FED4001D8331 /* AccountService.swift */,
|
||||||
|
F8210DE02966D0C4001D9973 /* StatusService.swift */,
|
||||||
);
|
);
|
||||||
path = Services;
|
path = Services;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -274,6 +301,9 @@
|
||||||
name = Vernissage;
|
name = Vernissage;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
F866F6B629608467002E8F88 /* MastodonSwift */,
|
F866F6B629608467002E8F88 /* MastodonSwift */,
|
||||||
|
F8210DD42966BB7E001D9973 /* Nuke */,
|
||||||
|
F8210DD62966BB7E001D9973 /* NukeExtensions */,
|
||||||
|
F8210DD82966BB7E001D9973 /* NukeUI */,
|
||||||
);
|
);
|
||||||
productName = Vernissage;
|
productName = Vernissage;
|
||||||
productReference = F88C2468295C37B80006098B /* Vernissage.app */;
|
productReference = F88C2468295C37B80006098B /* Vernissage.app */;
|
||||||
|
@ -305,6 +335,7 @@
|
||||||
mainGroup = F88C245F295C37B80006098B;
|
mainGroup = F88C245F295C37B80006098B;
|
||||||
packageReferences = (
|
packageReferences = (
|
||||||
F866F6B529608467002E8F88 /* XCRemoteSwiftPackageReference "Mastodon" */,
|
F866F6B529608467002E8F88 /* XCRemoteSwiftPackageReference "Mastodon" */,
|
||||||
|
F8210DD32966BB7E001D9973 /* XCRemoteSwiftPackageReference "Nuke" */,
|
||||||
);
|
);
|
||||||
productRefGroup = F88C2469295C37B80006098B /* Products */;
|
productRefGroup = F88C2469295C37B80006098B /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
|
@ -333,8 +364,10 @@
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
F85D497729640A5200751DF7 /* ImageRow.swift in Sources */,
|
F85D497729640A5200751DF7 /* ImageRow.swift in Sources */,
|
||||||
|
F8210DDF2966CFC7001D9973 /* AttachmentData+Attachment.swift in Sources */,
|
||||||
F80048082961E6DE00E6868A /* StatusDataHandler.swift in Sources */,
|
F80048082961E6DE00E6868A /* StatusDataHandler.swift in Sources */,
|
||||||
F866F6A0296040A8002E8F88 /* ApplicationSettings+CoreDataClass.swift in Sources */,
|
F866F6A0296040A8002E8F88 /* ApplicationSettings+CoreDataClass.swift in Sources */,
|
||||||
|
F8210DE52966E160001D9973 /* Color+SystemColors.swift in Sources */,
|
||||||
F88FAD23295F3FC4009B20C9 /* LocalFeedView.swift in Sources */,
|
F88FAD23295F3FC4009B20C9 /* LocalFeedView.swift in Sources */,
|
||||||
F88FAD2B295F43B8009B20C9 /* AccountData+CoreDataProperties.swift in Sources */,
|
F88FAD2B295F43B8009B20C9 /* AccountData+CoreDataProperties.swift in Sources */,
|
||||||
F85D4975296407F100751DF7 /* TimelineService.swift in Sources */,
|
F85D4975296407F100751DF7 /* TimelineService.swift in Sources */,
|
||||||
|
@ -342,9 +375,12 @@
|
||||||
F88FAD21295F3944009B20C9 /* HomeFeedView.swift in Sources */,
|
F88FAD21295F3944009B20C9 /* HomeFeedView.swift in Sources */,
|
||||||
F88C2475295C37BB0006098B /* CoreDataHandler.swift in Sources */,
|
F88C2475295C37BB0006098B /* CoreDataHandler.swift in Sources */,
|
||||||
F88FAD2A295F43B8009B20C9 /* AccountData+CoreDataClass.swift in Sources */,
|
F88FAD2A295F43B8009B20C9 /* AccountData+CoreDataClass.swift in Sources */,
|
||||||
|
F8210DE12966D0C4001D9973 /* StatusService.swift in Sources */,
|
||||||
F8A93D822965FF5D001D8331 /* MastodonClientAuthenticated+Account.swift in Sources */,
|
F8A93D822965FF5D001D8331 /* MastodonClientAuthenticated+Account.swift in Sources */,
|
||||||
F85D49872964334100751DF7 /* String+Date.swift in Sources */,
|
F85D49872964334100751DF7 /* String+Date.swift in Sources */,
|
||||||
F8341F92295C63BB009C8EE6 /* ImageStatus.swift in Sources */,
|
F8341F92295C63BB009C8EE6 /* ImageStatus.swift in Sources */,
|
||||||
|
F8210DDD2966CF17001D9973 /* StatusData+Status.swift in Sources */,
|
||||||
|
F8210DCF2966B600001D9973 /* ImageRowAsync.swift in Sources */,
|
||||||
F85D498329642FAC00751DF7 /* AttachmentData+Comperable.swift in Sources */,
|
F85D498329642FAC00751DF7 /* AttachmentData+Comperable.swift in Sources */,
|
||||||
F85D497B29640C8200751DF7 /* UsernameRow.swift in Sources */,
|
F85D497B29640C8200751DF7 /* UsernameRow.swift in Sources */,
|
||||||
F85D497929640B9D00751DF7 /* ImagesCarousel.swift in Sources */,
|
F85D497929640B9D00751DF7 /* ImagesCarousel.swift in Sources */,
|
||||||
|
@ -363,7 +399,9 @@
|
||||||
F85D497F296416C800751DF7 /* CommentsSection.swift in Sources */,
|
F85D497F296416C800751DF7 /* CommentsSection.swift in Sources */,
|
||||||
F88C2486295C48030006098B /* HTMLFotmattedText.swift in Sources */,
|
F88C2486295C48030006098B /* HTMLFotmattedText.swift in Sources */,
|
||||||
F866F6A529604194002E8F88 /* ApplicationSettingsHandler.swift in Sources */,
|
F866F6A529604194002E8F88 /* ApplicationSettingsHandler.swift in Sources */,
|
||||||
|
F8210DE32966D256001D9973 /* Status+StatusData.swift in Sources */,
|
||||||
F85D49852964301800751DF7 /* StatusData+Attachments.swift in Sources */,
|
F85D49852964301800751DF7 /* StatusData+Attachments.swift in Sources */,
|
||||||
|
F8210DE72966E1D1001D9973 /* Color+Assets.swift in Sources */,
|
||||||
F85D497D29640D5900751DF7 /* InteractionRow.swift in Sources */,
|
F85D497D29640D5900751DF7 /* InteractionRow.swift in Sources */,
|
||||||
F866F6A729604629002E8F88 /* SignInView.swift in Sources */,
|
F866F6A729604629002E8F88 /* SignInView.swift in Sources */,
|
||||||
F88C246C295C37B80006098B /* VernissageApp.swift in Sources */,
|
F88C246C295C37B80006098B /* VernissageApp.swift in Sources */,
|
||||||
|
@ -583,6 +621,14 @@
|
||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
/* Begin XCRemoteSwiftPackageReference section */
|
/* Begin XCRemoteSwiftPackageReference section */
|
||||||
|
F8210DD32966BB7E001D9973 /* XCRemoteSwiftPackageReference "Nuke" */ = {
|
||||||
|
isa = XCRemoteSwiftPackageReference;
|
||||||
|
repositoryURL = "https://github.com/kean/Nuke";
|
||||||
|
requirement = {
|
||||||
|
branch = master;
|
||||||
|
kind = branch;
|
||||||
|
};
|
||||||
|
};
|
||||||
F866F6B529608467002E8F88 /* XCRemoteSwiftPackageReference "Mastodon" */ = {
|
F866F6B529608467002E8F88 /* XCRemoteSwiftPackageReference "Mastodon" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
repositoryURL = "https://github.com/mczachurski/Mastodon.swift";
|
repositoryURL = "https://github.com/mczachurski/Mastodon.swift";
|
||||||
|
@ -594,6 +640,21 @@
|
||||||
/* End XCRemoteSwiftPackageReference section */
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
|
|
||||||
/* Begin XCSwiftPackageProductDependency section */
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
|
F8210DD42966BB7E001D9973 /* Nuke */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = F8210DD32966BB7E001D9973 /* XCRemoteSwiftPackageReference "Nuke" */;
|
||||||
|
productName = Nuke;
|
||||||
|
};
|
||||||
|
F8210DD62966BB7E001D9973 /* NukeExtensions */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = F8210DD32966BB7E001D9973 /* XCRemoteSwiftPackageReference "Nuke" */;
|
||||||
|
productName = NukeExtensions;
|
||||||
|
};
|
||||||
|
F8210DD82966BB7E001D9973 /* NukeUI */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = F8210DD32966BB7E001D9973 /* XCRemoteSwiftPackageReference "Nuke" */;
|
||||||
|
productName = NukeUI;
|
||||||
|
};
|
||||||
F866F6B629608467002E8F88 /* MastodonSwift */ = {
|
F866F6B629608467002E8F88 /* MastodonSwift */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = F866F6B529608467002E8F88 /* XCRemoteSwiftPackageReference "Mastodon" */;
|
package = F866F6B529608467002E8F88 /* XCRemoteSwiftPackageReference "Mastodon" */;
|
||||||
|
|
|
@ -2,12 +2,12 @@
|
||||||
"colors" : [
|
"colors" : [
|
||||||
{
|
{
|
||||||
"color" : {
|
"color" : {
|
||||||
"color-space" : "srgb",
|
"color-space" : "display-p3",
|
||||||
"components" : {
|
"components" : {
|
||||||
"alpha" : "1.000",
|
"alpha" : "1.000",
|
||||||
"blue" : "0.000",
|
"blue" : "68",
|
||||||
"green" : "0.000",
|
"green" : "87",
|
||||||
"red" : "0.000"
|
"red" : "255"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"idiom" : "universal"
|
"idiom" : "universal"
|
||||||
|
@ -23,9 +23,9 @@
|
||||||
"color-space" : "srgb",
|
"color-space" : "srgb",
|
||||||
"components" : {
|
"components" : {
|
||||||
"alpha" : "1.000",
|
"alpha" : "1.000",
|
||||||
"blue" : "1.000",
|
"blue" : "68",
|
||||||
"green" : "1.000",
|
"green" : "87",
|
||||||
"red" : "1.000"
|
"red" : "255"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"idiom" : "universal"
|
"idiom" : "universal"
|
|
@ -8,6 +8,9 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class AccountDataHandler {
|
class AccountDataHandler {
|
||||||
|
public static let shared = AccountDataHandler()
|
||||||
|
private init() { }
|
||||||
|
|
||||||
func getAccountsData() -> [AccountData] {
|
func getAccountsData() -> [AccountData] {
|
||||||
let context = CoreDataHandler.shared.container.viewContext
|
let context = CoreDataHandler.shared.container.viewContext
|
||||||
let fetchRequest = AccountData.fetchRequest()
|
let fetchRequest = AccountData.fetchRequest()
|
||||||
|
@ -21,9 +24,7 @@ class AccountDataHandler {
|
||||||
|
|
||||||
func getCurrentAccountData() -> AccountData? {
|
func getCurrentAccountData() -> AccountData? {
|
||||||
let accounts = self.getAccountsData()
|
let accounts = self.getAccountsData()
|
||||||
|
let defaultSettings = ApplicationSettingsHandler.shared.getDefaultSettings()
|
||||||
let applicationSettingsHandler = ApplicationSettingsHandler()
|
|
||||||
let defaultSettings = applicationSettingsHandler.getDefaultSettings()
|
|
||||||
|
|
||||||
let currentAccount = accounts.first { accountData in
|
let currentAccount = accounts.first { accountData in
|
||||||
accountData.id == defaultSettings.currentAccount
|
accountData.id == defaultSettings.currentAccount
|
||||||
|
|
|
@ -8,6 +8,9 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class ApplicationSettingsHandler {
|
class ApplicationSettingsHandler {
|
||||||
|
public static let shared = ApplicationSettingsHandler()
|
||||||
|
private init() { }
|
||||||
|
|
||||||
func getDefaultSettings() -> ApplicationSettings {
|
func getDefaultSettings() -> ApplicationSettings {
|
||||||
var settingsList: [ApplicationSettings] = []
|
var settingsList: [ApplicationSettings] = []
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
//
|
||||||
|
// https://mczachurski.dev
|
||||||
|
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import MastodonSwift
|
||||||
|
|
||||||
|
extension AttachmentData {
|
||||||
|
func copyFrom(_ attachment: Attachment) {
|
||||||
|
self.id = attachment.id
|
||||||
|
self.url = attachment.url
|
||||||
|
self.blurhash = attachment.blurhash
|
||||||
|
self.previewUrl = attachment.previewUrl
|
||||||
|
self.remoteUrl = attachment.remoteUrl
|
||||||
|
self.text = attachment.description
|
||||||
|
self.type = attachment.type.rawValue
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,9 @@ import Foundation
|
||||||
import CoreData
|
import CoreData
|
||||||
|
|
||||||
class AttachmentDataHandler {
|
class AttachmentDataHandler {
|
||||||
|
public static let shared = AttachmentDataHandler()
|
||||||
|
private init() { }
|
||||||
|
|
||||||
func getAttachmentsData() -> [AttachmentData] {
|
func getAttachmentsData() -> [AttachmentData] {
|
||||||
let context = CoreDataHandler.shared.container.viewContext
|
let context = CoreDataHandler.shared.container.viewContext
|
||||||
let fetchRequest = AttachmentData.fetchRequest()
|
let fetchRequest = AttachmentData.fetchRequest()
|
||||||
|
|
|
@ -9,9 +9,10 @@ import CoreData
|
||||||
|
|
||||||
public class CoreDataHandler {
|
public class CoreDataHandler {
|
||||||
public static let shared = CoreDataHandler()
|
public static let shared = CoreDataHandler()
|
||||||
|
|
||||||
public let container: NSPersistentContainer
|
public let container: NSPersistentContainer
|
||||||
|
|
||||||
init(inMemory: Bool = false) {
|
private init(inMemory: Bool = false) {
|
||||||
container = NSPersistentContainer(name: "Vernissage")
|
container = NSPersistentContainer(name: "Vernissage")
|
||||||
if inMemory {
|
if inMemory {
|
||||||
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
|
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
|
||||||
|
@ -56,6 +57,12 @@ public class CoreDataHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension CoreDataHandler {
|
||||||
|
public static var memory: CoreDataHandler = {
|
||||||
|
CoreDataHandler(inMemory: true)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
extension CoreDataHandler {
|
extension CoreDataHandler {
|
||||||
public static var preview: CoreDataHandler = {
|
public static var preview: CoreDataHandler = {
|
||||||
let result = CoreDataHandler(inMemory: true)
|
let result = CoreDataHandler(inMemory: true)
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
//
|
||||||
|
// https://mczachurski.dev
|
||||||
|
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import MastodonSwift
|
||||||
|
|
||||||
|
extension StatusData {
|
||||||
|
func copyFrom(_ status: Status) {
|
||||||
|
self.id = status.id
|
||||||
|
self.createdAt = status.createdAt
|
||||||
|
self.accountAvatar = status.account?.avatar
|
||||||
|
self.accountDisplayName = status.account?.displayName
|
||||||
|
self.accountId = status.account!.id
|
||||||
|
self.accountUsername = status.account!.username
|
||||||
|
self.applicationName = status.application?.name
|
||||||
|
self.applicationWebsite = status.application?.website
|
||||||
|
self.bookmarked = status.bookmarked
|
||||||
|
self.content = status.content
|
||||||
|
self.favourited = status.favourited
|
||||||
|
self.favouritesCount = Int32(status.favouritesCount)
|
||||||
|
self.inReplyToAccount = status.inReplyToAccount
|
||||||
|
self.inReplyToId = status.inReplyToId
|
||||||
|
self.muted = status.muted
|
||||||
|
self.pinned = status.pinned
|
||||||
|
self.reblogged = status.reblogged
|
||||||
|
self.reblogsCount = Int32(status.reblogsCount)
|
||||||
|
self.repliesCount = Int32(status.repliesCount)
|
||||||
|
self.sensitive = status.sensitive
|
||||||
|
self.spoilerText = status.spoilerText
|
||||||
|
self.uri = status.uri
|
||||||
|
self.url = status.url
|
||||||
|
self.visibility = status.visibility.rawValue
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,8 +7,12 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import CoreData
|
import CoreData
|
||||||
|
import MastodonSwift
|
||||||
|
|
||||||
class StatusDataHandler {
|
class StatusDataHandler {
|
||||||
|
public static let shared = StatusDataHandler()
|
||||||
|
private init() { }
|
||||||
|
|
||||||
func getStatusesData() -> [StatusData] {
|
func getStatusesData() -> [StatusData] {
|
||||||
let context = CoreDataHandler.shared.container.viewContext
|
let context = CoreDataHandler.shared.container.viewContext
|
||||||
let fetchRequest = StatusData.fetchRequest()
|
let fetchRequest = StatusData.fetchRequest()
|
||||||
|
@ -20,6 +24,21 @@ class StatusDataHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getStatusData(statusId: String) -> StatusData? {
|
||||||
|
let context = CoreDataHandler.shared.container.viewContext
|
||||||
|
let fetchRequest = StatusData.fetchRequest()
|
||||||
|
|
||||||
|
fetchRequest.fetchLimit = 1
|
||||||
|
fetchRequest.predicate = NSPredicate(format: "id = %@", statusId)
|
||||||
|
|
||||||
|
do {
|
||||||
|
return try context.fetch(fetchRequest).first
|
||||||
|
} catch {
|
||||||
|
print("Error during fetching accounts")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func getMaximumStatus(viewContext: NSManagedObjectContext? = nil) -> StatusData? {
|
func getMaximumStatus(viewContext: NSManagedObjectContext? = nil) -> StatusData? {
|
||||||
let context = viewContext ?? CoreDataHandler.shared.container.viewContext
|
let context = viewContext ?? CoreDataHandler.shared.container.viewContext
|
||||||
let fetchRequest = StatusData.fetchRequest()
|
let fetchRequest = StatusData.fetchRequest()
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
//
|
||||||
|
// https://mczachurski.dev
|
||||||
|
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension Color {
|
||||||
|
|
||||||
|
// MARK: - Text Colors
|
||||||
|
static let dangerColor = Color("DangerColor")
|
||||||
|
static let lightGrayColor = Color("LightGrayColor")
|
||||||
|
static let mainTextColor = Color("MainTextColor")
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
//
|
||||||
|
// https://mczachurski.dev
|
||||||
|
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension Color {
|
||||||
|
|
||||||
|
// MARK: - Text Colors
|
||||||
|
static let lightText = Color(UIColor.lightText)
|
||||||
|
static let darkText = Color(UIColor.darkText)
|
||||||
|
static let placeholderText = Color(UIColor.placeholderText)
|
||||||
|
|
||||||
|
// MARK: - Label Colors
|
||||||
|
static let label = Color(UIColor.label)
|
||||||
|
static let secondaryLabel = Color(UIColor.secondaryLabel)
|
||||||
|
static let tertiaryLabel = Color(UIColor.tertiaryLabel)
|
||||||
|
static let quaternaryLabel = Color(UIColor.quaternaryLabel)
|
||||||
|
|
||||||
|
// MARK: - Background Colors
|
||||||
|
static let systemBackground = Color(UIColor.systemBackground)
|
||||||
|
static let secondarySystemBackground = Color(UIColor.secondarySystemBackground)
|
||||||
|
static let tertiarySystemBackground = Color(UIColor.tertiarySystemBackground)
|
||||||
|
|
||||||
|
// MARK: - Fill Colors
|
||||||
|
static let systemFill = Color(UIColor.systemFill)
|
||||||
|
static let secondarySystemFill = Color(UIColor.secondarySystemFill)
|
||||||
|
static let tertiarySystemFill = Color(UIColor.tertiarySystemFill)
|
||||||
|
static let quaternarySystemFill = Color(UIColor.quaternarySystemFill)
|
||||||
|
|
||||||
|
// MARK: - Grouped Background Colors
|
||||||
|
static let systemGroupedBackground = Color(UIColor.systemGroupedBackground)
|
||||||
|
static let secondarySystemGroupedBackground = Color(UIColor.secondarySystemGroupedBackground)
|
||||||
|
static let tertiarySystemGroupedBackground = Color(UIColor.tertiarySystemGroupedBackground)
|
||||||
|
|
||||||
|
// MARK: - Gray Colors
|
||||||
|
static let systemGray = Color(UIColor.systemGray)
|
||||||
|
static let systemGray2 = Color(UIColor.systemGray2)
|
||||||
|
static let systemGray3 = Color(UIColor.systemGray3)
|
||||||
|
static let systemGray4 = Color(UIColor.systemGray4)
|
||||||
|
static let systemGray5 = Color(UIColor.systemGray5)
|
||||||
|
static let systemGray6 = Color(UIColor.systemGray6)
|
||||||
|
|
||||||
|
// MARK: - Other Colors
|
||||||
|
static let separator = Color(UIColor.separator)
|
||||||
|
static let opaqueSeparator = Color(UIColor.opaqueSeparator)
|
||||||
|
static let link = Color(UIColor.link)
|
||||||
|
|
||||||
|
// MARK: System Colors
|
||||||
|
static let systemBlue = Color(UIColor.systemBlue)
|
||||||
|
static let systemPurple = Color(UIColor.systemPurple)
|
||||||
|
static let systemGreen = Color(UIColor.systemGreen)
|
||||||
|
static let systemYellow = Color(UIColor.systemYellow)
|
||||||
|
static let systemOrange = Color(UIColor.systemOrange)
|
||||||
|
static let systemPink = Color(UIColor.systemPink)
|
||||||
|
static let systemRed = Color(UIColor.systemRed)
|
||||||
|
static let systemTeal = Color(UIColor.systemTeal)
|
||||||
|
static let systemIndigo = Color(UIColor.systemIndigo)
|
||||||
|
}
|
|
@ -18,4 +18,27 @@ extension MastodonClientAuthenticated {
|
||||||
let (data, _) = try await urlSession.data(for: request)
|
let (data, _) = try await urlSession.data(for: request)
|
||||||
return try JSONDecoder().decode(Account.self, from: data)
|
return try JSONDecoder().decode(Account.self, from: data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getRelationship(for accountId: String) async throws -> Relationship? {
|
||||||
|
let request = try Self.request(
|
||||||
|
for: baseURL,
|
||||||
|
target: Mastodon.Account.relationships([accountId]),
|
||||||
|
withBearerToken: token
|
||||||
|
)
|
||||||
|
|
||||||
|
let (data, _) = try await urlSession.data(for: request)
|
||||||
|
let relationships = try JSONDecoder().decode([Relationship].self, from: data)
|
||||||
|
return relationships.first
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStatuses(for accountId: String) async throws -> [Status] {
|
||||||
|
let request = try Self.request(
|
||||||
|
for: baseURL,
|
||||||
|
target: Mastodon.Account.statuses(accountId, true, true),
|
||||||
|
withBearerToken: token
|
||||||
|
)
|
||||||
|
|
||||||
|
let (data, _) = try await urlSession.data(for: request)
|
||||||
|
return try JSONDecoder().decode([Status].self, from: data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
//
|
||||||
|
// https://mczachurski.dev
|
||||||
|
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import MastodonSwift
|
||||||
|
|
||||||
|
extension Status {
|
||||||
|
func createStatusData() async throws -> StatusData {
|
||||||
|
let statusData = StatusDataHandler.shared.createStatusDataEntity(viewContext: CoreDataHandler.memory.container.viewContext)
|
||||||
|
statusData.copyFrom(self)
|
||||||
|
|
||||||
|
for attachment in self.mediaAttachments {
|
||||||
|
let imageData = try await RemoteFileService.shared.fetchData(url: attachment.url)
|
||||||
|
|
||||||
|
guard let imageData = imageData else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save attachment in database.
|
||||||
|
let attachmentData = AttachmentDataHandler.shared.createAttachmnentDataEntity(viewContext: CoreDataHandler.memory.container.viewContext)
|
||||||
|
|
||||||
|
attachmentData.copyFrom(attachment)
|
||||||
|
attachmentData.statusId = statusData.id
|
||||||
|
attachmentData.data = imageData
|
||||||
|
|
||||||
|
// TODO: read exif informatio
|
||||||
|
|
||||||
|
attachmentData.statusRelation = statusData
|
||||||
|
statusData.addToAttachmentRelation(attachmentData)
|
||||||
|
}
|
||||||
|
|
||||||
|
return statusData
|
||||||
|
}
|
||||||
|
}
|
|
@ -48,7 +48,7 @@ struct HTMLFormattedText: UIViewRepresentable {
|
||||||
|
|
||||||
let largeAttributes = [
|
let largeAttributes = [
|
||||||
NSAttributedString.Key.font: UIFont.systemFont(ofSize: CGFloat(self.fontSize)),
|
NSAttributedString.Key.font: UIFont.systemFont(ofSize: CGFloat(self.fontSize)),
|
||||||
NSAttributedString.Key.foregroundColor: UIColor(Color("MainTextColor"))
|
NSAttributedString.Key.foregroundColor: UIColor(Color.mainTextColor)
|
||||||
]
|
]
|
||||||
|
|
||||||
let linkAttributes = [
|
let linkAttributes = [
|
||||||
|
|
|
@ -9,6 +9,7 @@ import MastodonSwift
|
||||||
|
|
||||||
public class AccountService {
|
public class AccountService {
|
||||||
public static let shared = AccountService()
|
public static let shared = AccountService()
|
||||||
|
private init() { }
|
||||||
|
|
||||||
public func getAccount(withId accountId: String, and accountData: AccountData?) async throws -> Account? {
|
public func getAccount(withId accountId: String, and accountData: AccountData?) async throws -> Account? {
|
||||||
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
|
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
|
||||||
|
@ -18,4 +19,22 @@ public class AccountService {
|
||||||
let client = MastodonClient(baseURL: serverUrl).getAuthenticated(token: accessToken)
|
let client = MastodonClient(baseURL: serverUrl).getAuthenticated(token: accessToken)
|
||||||
return try await client.getAccount(for: accountId)
|
return try await client.getAccount(for: accountId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func getRelationship(withId accountId: String, forUser accountData: AccountData?) async throws -> Relationship? {
|
||||||
|
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let client = MastodonClient(baseURL: serverUrl).getAuthenticated(token: accessToken)
|
||||||
|
return try await client.getRelationship(for: accountId)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func getStatuses(forAccountId accountId: String, andContext accountData: AccountData?) async throws -> [Status] {
|
||||||
|
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
let client = MastodonClient(baseURL: serverUrl).getAuthenticated(token: accessToken)
|
||||||
|
return try await client.getStatuses(for: accountId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,10 @@ import MastodonSwift
|
||||||
|
|
||||||
public class AuthorizationService {
|
public class AuthorizationService {
|
||||||
public static let shared = AuthorizationService()
|
public static let shared = AuthorizationService()
|
||||||
|
private init() { }
|
||||||
|
|
||||||
public func verifyAccount(_ result: @escaping (AccountData?) -> Void) async {
|
public func verifyAccount(_ result: @escaping (AccountData?) -> Void) async {
|
||||||
let accountDataHandler = AccountDataHandler()
|
let currentAccount = AccountDataHandler.shared.getCurrentAccountData()
|
||||||
let currentAccount = accountDataHandler.getCurrentAccountData()
|
|
||||||
|
|
||||||
// When we dont have even one account stored in database then we have to ask user to enter server and sign in.
|
// When we dont have even one account stored in database then we have to ask user to enter server and sign in.
|
||||||
guard let accountData = currentAccount, let accessToken = accountData.accessToken else {
|
guard let accountData = currentAccount, let accessToken = accountData.accessToken else {
|
||||||
|
@ -65,8 +65,7 @@ public class AuthorizationService {
|
||||||
let account = try await authenticatedClient.verifyCredentials()
|
let account = try await authenticatedClient.verifyCredentials()
|
||||||
|
|
||||||
// Create account object in database.
|
// Create account object in database.
|
||||||
let accountDataHandler = AccountDataHandler()
|
let accountData = AccountDataHandler.shared.createAccountDataEntity()
|
||||||
let accountData = accountDataHandler.createAccountDataEntity()
|
|
||||||
|
|
||||||
accountData.id = account.id
|
accountData.id = account.id
|
||||||
accountData.username = account.username
|
accountData.username = account.username
|
||||||
|
@ -100,8 +99,7 @@ public class AuthorizationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set newly created account as current.
|
// Set newly created account as current.
|
||||||
let applicationSettingsHandler = ApplicationSettingsHandler()
|
let defaultSettings = ApplicationSettingsHandler.shared.getDefaultSettings()
|
||||||
let defaultSettings = applicationSettingsHandler.getDefaultSettings()
|
|
||||||
defaultSettings.currentAccount = accountData.id
|
defaultSettings.currentAccount = accountData.id
|
||||||
|
|
||||||
// Save account data in database and in application state.
|
// Save account data in database and in application state.
|
||||||
|
@ -158,8 +156,7 @@ public class AuthorizationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// We have to be sure that account id is saved as default account.
|
// We have to be sure that account id is saved as default account.
|
||||||
let applicationSettingsHandler = ApplicationSettingsHandler()
|
let defaultSettings = ApplicationSettingsHandler.shared.getDefaultSettings()
|
||||||
let defaultSettings = applicationSettingsHandler.getDefaultSettings()
|
|
||||||
defaultSettings.currentAccount = accountData.id
|
defaultSettings.currentAccount = accountData.id
|
||||||
|
|
||||||
// Save account data in database and in application state.
|
// Save account data in database and in application state.
|
||||||
|
|
|
@ -9,6 +9,7 @@ import Foundation
|
||||||
|
|
||||||
public class RemoteFileService {
|
public class RemoteFileService {
|
||||||
public static let shared = RemoteFileService()
|
public static let shared = RemoteFileService()
|
||||||
|
private init() { }
|
||||||
|
|
||||||
public func fetchData(url: URL) async throws -> Data? {
|
public func fetchData(url: URL) async throws -> Data? {
|
||||||
let urlRequest = URLRequest(url: url)
|
let urlRequest = URLRequest(url: url)
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
//
|
||||||
|
// https://mczachurski.dev
|
||||||
|
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import MastodonSwift
|
||||||
|
|
||||||
|
public class StatusService {
|
||||||
|
public static let shared = StatusService()
|
||||||
|
private init() { }
|
||||||
|
|
||||||
|
func copy(from status: Status, to statusData: StatusData) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,14 +10,14 @@ import MastodonSwift
|
||||||
|
|
||||||
public class TimelineService {
|
public class TimelineService {
|
||||||
public static let shared = TimelineService()
|
public static let shared = TimelineService()
|
||||||
|
private init() { }
|
||||||
|
|
||||||
public func onBottomOfList(for accountData: AccountData) async throws {
|
public func onBottomOfList(for accountData: AccountData) async throws {
|
||||||
// Load data from API and operate on CoreData on background context.
|
// Load data from API and operate on CoreData on background context.
|
||||||
let backgroundContext = CoreDataHandler.shared.newBackgroundContext()
|
let backgroundContext = CoreDataHandler.shared.newBackgroundContext()
|
||||||
|
|
||||||
// Get maximimum downloaded stauts id.
|
// Get maximimum downloaded stauts id.
|
||||||
let statusDataHandler = StatusDataHandler()
|
let oldestStatus = StatusDataHandler.shared.getMinimumtatus(viewContext: backgroundContext)
|
||||||
let oldestStatus = statusDataHandler.getMinimumtatus(viewContext: backgroundContext)
|
|
||||||
|
|
||||||
guard let oldestStatus = oldestStatus else {
|
guard let oldestStatus = oldestStatus else {
|
||||||
return
|
return
|
||||||
|
@ -31,40 +31,20 @@ public class TimelineService {
|
||||||
let backgroundContext = CoreDataHandler.shared.newBackgroundContext()
|
let backgroundContext = CoreDataHandler.shared.newBackgroundContext()
|
||||||
|
|
||||||
// Get maximimum downloaded stauts id.
|
// Get maximimum downloaded stauts id.
|
||||||
let statusDataHandler = StatusDataHandler()
|
let newestStatus = StatusDataHandler.shared.getMaximumStatus(viewContext: backgroundContext)
|
||||||
let newestStatus = statusDataHandler.getMaximumStatus(viewContext: backgroundContext)
|
|
||||||
|
|
||||||
try await self.loadData(for: accountData, on: backgroundContext, minId: newestStatus?.id)
|
try await self.loadData(for: accountData, on: backgroundContext, minId: newestStatus?.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getStatus(withId statusId: String, and accountData: AccountData) async throws -> Status? {
|
public func getStatus(withId statusId: String, and accountData: AccountData?) async throws -> Status? {
|
||||||
guard let accessToken = accountData.accessToken else {
|
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
let client = MastodonClient(baseURL: accountData.serverUrl).getAuthenticated(token: accessToken)
|
let client = MastodonClient(baseURL: serverUrl).getAuthenticated(token: accessToken)
|
||||||
return try await client.read(statusId: statusId)
|
return try await client.read(statusId: statusId)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func updateStatus(statusData: StatusData, and accountData: AccountData) async throws -> StatusData? {
|
|
||||||
guard let accessToken = accountData.accessToken else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load data from API and operate on CoreData on background context.
|
|
||||||
let backgroundContext = CoreDataHandler.shared.newBackgroundContext()
|
|
||||||
|
|
||||||
// Get new information from API.
|
|
||||||
let client = MastodonClient(baseURL: accountData.serverUrl).getAuthenticated(token: accessToken)
|
|
||||||
let status = try await client.read(statusId: statusData.id)
|
|
||||||
|
|
||||||
// Update status data in database.
|
|
||||||
try await self.updateStatusData(from: status, to: statusData, on: backgroundContext)
|
|
||||||
try backgroundContext.save()
|
|
||||||
|
|
||||||
return statusData
|
|
||||||
}
|
|
||||||
|
|
||||||
public func getComments(for statusId: String, and accountData: AccountData) async throws -> Context {
|
public func getComments(for statusId: String, and accountData: AccountData) async throws -> Context {
|
||||||
let client = MastodonClient(baseURL: accountData.serverUrl).getAuthenticated(token: accountData.accessToken ?? "")
|
let client = MastodonClient(baseURL: accountData.serverUrl).getAuthenticated(token: accountData.accessToken ?? "")
|
||||||
return try await client.getContext(for: statusId)
|
return try await client.getContext(for: statusId)
|
||||||
|
@ -79,45 +59,28 @@ public class TimelineService {
|
||||||
let client = MastodonClient(baseURL: accountData.serverUrl).getAuthenticated(token: accessToken)
|
let client = MastodonClient(baseURL: accountData.serverUrl).getAuthenticated(token: accessToken)
|
||||||
let statuses = try await client.getHomeTimeline(maxId: maxId, minId: minId, limit: 40)
|
let statuses = try await client.getHomeTimeline(maxId: maxId, minId: minId, limit: 40)
|
||||||
|
|
||||||
// Create handler for managing statuses in database.
|
|
||||||
let statusDataHandler = StatusDataHandler()
|
|
||||||
|
|
||||||
// Save status data in database.
|
// Save status data in database.
|
||||||
for status in statuses {
|
for status in statuses {
|
||||||
let statusData = statusDataHandler.createStatusDataEntity(viewContext: backgroundContext)
|
let statusData = StatusDataHandler.shared.createStatusDataEntity(viewContext: backgroundContext)
|
||||||
try await self.updateStatusData(from: status, to: statusData, on: backgroundContext)
|
try await self.copy(from: status, to: statusData, on: backgroundContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
try backgroundContext.save()
|
try backgroundContext.save()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateStatusData(from status: Status, to statusData: StatusData, on backgroundContext: NSManagedObjectContext) async throws {
|
public func updateStatus(_ statusData: StatusData, basedOn status: Status) async throws -> StatusData? {
|
||||||
statusData.id = status.id
|
// Load data from API and operate on CoreData on background context.
|
||||||
statusData.createdAt = status.createdAt
|
let backgroundContext = CoreDataHandler.shared.newBackgroundContext()
|
||||||
statusData.accountAvatar = status.account?.avatar
|
|
||||||
statusData.accountDisplayName = status.account?.displayName
|
|
||||||
statusData.accountId = status.account!.id
|
|
||||||
statusData.accountUsername = status.account!.username
|
|
||||||
statusData.applicationName = status.application?.name
|
|
||||||
statusData.applicationWebsite = status.application?.website
|
|
||||||
statusData.bookmarked = status.bookmarked
|
|
||||||
statusData.content = status.content
|
|
||||||
statusData.favourited = status.favourited
|
|
||||||
statusData.favouritesCount = Int32(status.favouritesCount)
|
|
||||||
statusData.inReplyToAccount = status.inReplyToAccount
|
|
||||||
statusData.inReplyToId = status.inReplyToId
|
|
||||||
statusData.muted = status.muted
|
|
||||||
statusData.pinned = status.pinned
|
|
||||||
statusData.reblogged = status.reblogged
|
|
||||||
statusData.reblogsCount = Int32(status.reblogsCount)
|
|
||||||
statusData.repliesCount = Int32(status.repliesCount)
|
|
||||||
statusData.sensitive = status.sensitive
|
|
||||||
statusData.spoilerText = status.spoilerText
|
|
||||||
statusData.uri = status.uri
|
|
||||||
statusData.url = status.url
|
|
||||||
statusData.visibility = status.visibility.rawValue
|
|
||||||
|
|
||||||
let attachmentDataHandler = AttachmentDataHandler()
|
// Update status data in database.
|
||||||
|
try await self.copy(from: status, to: statusData, on: backgroundContext)
|
||||||
|
try backgroundContext.save()
|
||||||
|
|
||||||
|
return statusData
|
||||||
|
}
|
||||||
|
|
||||||
|
private func copy(from status: Status, to statusData: StatusData, on backgroundContext: NSManagedObjectContext) async throws {
|
||||||
|
statusData.copyFrom(status)
|
||||||
|
|
||||||
for attachment in status.mediaAttachments {
|
for attachment in status.mediaAttachments {
|
||||||
let imageData = try await self.fetchImage(attachment: attachment)
|
let imageData = try await self.fetchImage(attachment: attachment)
|
||||||
|
@ -126,31 +89,16 @@ public class TimelineService {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
var exif = image.getExifData()
|
|
||||||
if let dict = exif as? [String: AnyObject] {
|
|
||||||
dict.keys.map { key in
|
|
||||||
print(key)
|
|
||||||
print(dict[key])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Save attachment in database.
|
// Save attachment in database.
|
||||||
let attachmentData = statusData.attachments().first { item in item.id == attachment.id }
|
let attachmentData = statusData.attachments().first { item in item.id == attachment.id }
|
||||||
?? attachmentDataHandler.createAttachmnentDataEntity(viewContext: backgroundContext)
|
?? AttachmentDataHandler.shared.createAttachmnentDataEntity(viewContext: backgroundContext)
|
||||||
|
|
||||||
attachmentData.id = attachment.id
|
|
||||||
attachmentData.url = attachment.url
|
|
||||||
attachmentData.blurhash = attachment.blurhash
|
|
||||||
attachmentData.previewUrl = attachment.previewUrl
|
|
||||||
attachmentData.remoteUrl = attachment.remoteUrl
|
|
||||||
attachmentData.text = attachment.description
|
|
||||||
attachmentData.type = attachment.type.rawValue
|
|
||||||
|
|
||||||
|
attachmentData.copyFrom(attachment)
|
||||||
attachmentData.statusId = statusData.id
|
attachmentData.statusId = statusData.id
|
||||||
attachmentData.data = imageData
|
attachmentData.data = imageData
|
||||||
|
|
||||||
|
// TODO: read exif information
|
||||||
|
|
||||||
if attachmentData.isInserted {
|
if attachmentData.isInserted {
|
||||||
attachmentData.statusRelation = statusData
|
attachmentData.statusRelation = statusData
|
||||||
statusData.addToAttachmentRelation(attachmentData)
|
statusData.addToAttachmentRelation(attachmentData)
|
||||||
|
|
|
@ -20,6 +20,7 @@ struct VernissageApp: App {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
switch applicationViewMode {
|
switch applicationViewMode {
|
||||||
case .loading:
|
case .loading:
|
||||||
|
// TODO: Loading splashscreen.
|
||||||
Text("Loading")
|
Text("Loading")
|
||||||
case .signIn:
|
case .signIn:
|
||||||
SignInView { viewMode in
|
SignInView { viewMode in
|
||||||
|
@ -43,6 +44,9 @@ struct VernissageApp: App {
|
||||||
self.applicationState.accountData = accountData
|
self.applicationState.accountData = accountData
|
||||||
self.applicationViewMode = .mainView
|
self.applicationViewMode = .mainView
|
||||||
})
|
})
|
||||||
|
|
||||||
|
URLCache.shared.memoryCapacity = 10_000_000 // ~10 MB memory space
|
||||||
|
URLCache.shared.diskCapacity = 1_000_000_000 // ~1GB disk cache space
|
||||||
}
|
}
|
||||||
.navigationViewStyle(.stack)
|
.navigationViewStyle(.stack)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,10 +10,13 @@ import AVFoundation
|
||||||
|
|
||||||
struct DetailsView: View {
|
struct DetailsView: View {
|
||||||
@EnvironmentObject var applicationState: ApplicationState
|
@EnvironmentObject var applicationState: ApplicationState
|
||||||
@ObservedObject public var statusData: StatusData
|
@State var statusId: String
|
||||||
|
|
||||||
|
@State private var statusData: StatusData?
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
|
if let statusData = self.statusData {
|
||||||
VStack (alignment: .leading) {
|
VStack (alignment: .leading) {
|
||||||
ImagesCarousel(attachments: statusData.attachments())
|
ImagesCarousel(attachments: statusData.attachments())
|
||||||
|
|
||||||
|
@ -35,7 +38,7 @@ struct DetailsView: View {
|
||||||
LabelIcon(iconName: "timelapse", value: "24.0 mm, f/1.8, 1/640s, ISO 100")
|
LabelIcon(iconName: "timelapse", value: "24.0 mm, f/1.8, 1/640s, ISO 100")
|
||||||
LabelIcon(iconName: "calendar", value: "2 Oct 2022")
|
LabelIcon(iconName: "calendar", value: "2 Oct 2022")
|
||||||
}
|
}
|
||||||
.foregroundColor(Color("LightGrayColor"))
|
.foregroundColor(Color.lightGrayColor)
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
Text("Uploaded")
|
Text("Uploaded")
|
||||||
|
@ -45,7 +48,7 @@ struct DetailsView: View {
|
||||||
Text("via \(applicationName)")
|
Text("via \(applicationName)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.foregroundColor(Color("LightGrayColor"))
|
.foregroundColor(Color.lightGrayColor)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
|
|
||||||
InteractionRow(statusData: statusData)
|
InteractionRow(statusData: statusData)
|
||||||
|
@ -55,19 +58,54 @@ struct DetailsView: View {
|
||||||
|
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.size(width: UIScreen.main.bounds.width, height: 4)
|
.size(width: UIScreen.main.bounds.width, height: 4)
|
||||||
.fill(Color("MainTextColor"))
|
.fill(Color.mainTextColor)
|
||||||
.opacity(0.1)
|
.opacity(0.1)
|
||||||
|
|
||||||
CommentsSection(statusId: statusData.id)
|
CommentsSection(statusId: statusData.id)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
VStack (alignment: .leading) {
|
||||||
|
Rectangle()
|
||||||
|
.fill(Color.placeholderText)
|
||||||
|
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.width)
|
||||||
|
.redacted(reason: .placeholder)
|
||||||
|
HStack (alignment: .center) {
|
||||||
|
Circle()
|
||||||
|
.fill(Color.placeholderText)
|
||||||
|
.frame(width: 48.0, height: 48.0)
|
||||||
|
.redacted(reason: .placeholder)
|
||||||
|
|
||||||
|
VStack (alignment: .leading) {
|
||||||
|
Text("Verylong Displayname")
|
||||||
|
.foregroundColor(Color.mainTextColor)
|
||||||
|
.redacted(reason: .placeholder)
|
||||||
|
Text("@username")
|
||||||
|
.foregroundColor(Color.lightGrayColor)
|
||||||
|
.font(.footnote)
|
||||||
|
.redacted(reason: .placeholder)
|
||||||
|
}
|
||||||
|
.padding(.leading, 8)
|
||||||
|
}.padding(8)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.navigationBarTitle("Details")
|
.navigationBarTitle("Details")
|
||||||
.onAppear {
|
.onAppear {
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
if let accountData = self.applicationState.accountData {
|
// Get status from API.
|
||||||
let timelineService = TimelineService()
|
let status = try await TimelineService.shared.getStatus(withId: self.statusId, and: self.applicationState.accountData)
|
||||||
_ = try await timelineService.updateStatus(statusData: self.statusData, and: accountData)
|
|
||||||
|
if let status {
|
||||||
|
// Get status from database.
|
||||||
|
let statusDataFromDatabase = StatusDataHandler.shared.getStatusData(statusId: self.statusId)
|
||||||
|
|
||||||
|
// If we have status in database then we can update data.
|
||||||
|
if let statusDataFromDatabase {
|
||||||
|
self.statusData = try await TimelineService.shared.updateStatus(statusDataFromDatabase, basedOn: status)
|
||||||
|
} else {
|
||||||
|
self.statusData = try await status.createStatusData()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
print("Error \(error.localizedDescription)")
|
print("Error \(error.localizedDescription)")
|
||||||
|
@ -79,6 +117,6 @@ struct DetailsView: View {
|
||||||
|
|
||||||
struct DetailsView_Previews: PreviewProvider {
|
struct DetailsView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
DetailsView(statusData: StatusData())
|
DetailsView(statusId: "123")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,8 +22,7 @@ struct HomeFeedView: View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
LazyVGrid(columns: gridColumns) {
|
LazyVGrid(columns: gridColumns) {
|
||||||
ForEach(dbStatuses, id: \.self) { item in
|
ForEach(dbStatuses, id: \.self) { item in
|
||||||
NavigationLink(destination:
|
NavigationLink(destination: DetailsView(statusId: item.id)
|
||||||
DetailsView(statusData: item)
|
|
||||||
.environmentObject(applicationState)) {
|
.environmentObject(applicationState)) {
|
||||||
ImageRow(attachments: item.attachments())
|
ImageRow(attachments: item.attachments())
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,7 +97,7 @@ struct MainView: View {
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
}
|
}
|
||||||
.frame(width: 150)
|
.frame(width: 150)
|
||||||
.foregroundColor(Color("MainTextColor"))
|
.foregroundColor(Color.mainTextColor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,20 +108,20 @@ struct MainView: View {
|
||||||
Menu {
|
Menu {
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
// Switch accounts...
|
// TODO: Switch accounts.
|
||||||
} label: {
|
} label: {
|
||||||
HStack {
|
HStack {
|
||||||
Text(self.applicationState.accountData?.displayName ?? self.applicationState.accountData?.username ?? "")
|
Text(self.applicationState.accountData?.displayName ?? self.applicationState.accountData?.username ?? "")
|
||||||
Image(systemName: "person.circle.fill")
|
Image(systemName: "person.circle.fill")
|
||||||
.resizable()
|
.resizable()
|
||||||
.foregroundColor(Color("MainTextColor"))
|
.foregroundColor(Color.mainTextColor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
// Open settings...
|
// TODO: Open settings.
|
||||||
} label: {
|
} label: {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Settings")
|
Text("Settings")
|
||||||
|
@ -138,7 +138,7 @@ struct MainView: View {
|
||||||
Image(systemName: "person.circle")
|
Image(systemName: "person.circle")
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 32.0, height: 32.0)
|
.frame(width: 32.0, height: 32.0)
|
||||||
.foregroundColor(Color("MainTextColor"))
|
.foregroundColor(Color.mainTextColor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ struct SignInView: View {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
|
// TODO: Rebild signin.
|
||||||
HStack {
|
HStack {
|
||||||
TextField(
|
TextField(
|
||||||
"Server address",
|
"Server address",
|
||||||
|
|
|
@ -13,11 +13,16 @@ struct UserProfileView: View {
|
||||||
@State public var accountDisplayName: String?
|
@State public var accountDisplayName: String?
|
||||||
@State public var accountUserName: String
|
@State public var accountUserName: String
|
||||||
@State private var account: Account? = nil
|
@State private var account: Account? = nil
|
||||||
|
@State private var relationship: Relationship? = nil
|
||||||
|
@State private var statuses: [Status] = []
|
||||||
|
|
||||||
|
private static let initialColumns = 1
|
||||||
|
@State private var gridColumns = Array(repeating: GridItem(.flexible()), count: initialColumns)
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading) {
|
ScrollView {
|
||||||
if let account = self.account {
|
if let account = self.account {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
AsyncImage(url: account.avatar) { image in
|
AsyncImage(url: account.avatar) { image in
|
||||||
image
|
image
|
||||||
|
@ -27,7 +32,7 @@ struct UserProfileView: View {
|
||||||
} placeholder: {
|
} placeholder: {
|
||||||
Image(systemName: "person.circle")
|
Image(systemName: "person.circle")
|
||||||
.resizable()
|
.resizable()
|
||||||
.foregroundColor(Color("MainTextColor"))
|
.foregroundColor(Color.mainTextColor)
|
||||||
}
|
}
|
||||||
.frame(width: 96.0, height: 96.0)
|
.frame(width: 96.0, height: 96.0)
|
||||||
|
|
||||||
|
@ -64,22 +69,25 @@ struct UserProfileView: View {
|
||||||
|
|
||||||
HStack (alignment: .center) {
|
HStack (alignment: .center) {
|
||||||
Text(account.displayName ?? account.username)
|
Text(account.displayName ?? account.username)
|
||||||
.foregroundColor(Color("DisplayNameColor"))
|
.foregroundColor(Color.mainTextColor)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.fontWeight(.bold)
|
.fontWeight(.bold)
|
||||||
Text("@\(account.username)")
|
Text("@\(account.username)")
|
||||||
.foregroundColor(Color("LightGrayColor"))
|
.foregroundColor(Color.lightGrayColor)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
// Folllow/Unfollow
|
// TODO: Folllow/Unfollow.
|
||||||
} label: {
|
} label: {
|
||||||
Text("Follow")
|
HStack {
|
||||||
|
Image(systemName: relationship?.following == true ? "person.badge.minus" : "person.badge.plus")
|
||||||
|
Text(relationship?.following == true ? "Unfollow" : (relationship?.followedBy == true ? "Follow back" : "Follow"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.buttonStyle(.borderedProminent)
|
.buttonStyle(.borderedProminent)
|
||||||
.tint(.accentColor)
|
.tint(relationship?.following == true ? Color.dangerColor : .accentColor)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,26 +98,36 @@ struct UserProfileView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
Text("Joined \(account.createdAt.toRelative(.isoDateTimeMilliSec))")
|
Text("Joined \(account.createdAt.toRelative(.isoDateTimeMilliSec))")
|
||||||
.foregroundColor(Color("LightGrayColor").opacity(0.5))
|
.foregroundColor(Color.lightGrayColor.opacity(0.5))
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
|
|
||||||
Spacer()
|
}
|
||||||
|
.padding()
|
||||||
|
|
||||||
|
LazyVGrid(columns: gridColumns) {
|
||||||
|
ForEach(self.statuses, id: \.id) { item in
|
||||||
|
NavigationLink(destination: DetailsView(statusId: item.id)
|
||||||
|
.environmentObject(applicationState)) {
|
||||||
|
ImageRowAsync(attachments: item.mediaAttachments)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
ProgressView()
|
ProgressView()
|
||||||
.progressViewStyle(CircularProgressViewStyle())
|
.progressViewStyle(CircularProgressViewStyle())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding()
|
|
||||||
.navigationBarTitle(self.accountDisplayName ?? self.accountUserName)
|
.navigationBarTitle(self.accountDisplayName ?? self.accountUserName)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
if let account = try await AccountService.shared.getAccount(
|
async let relationshipTask = AccountService.shared.getRelationship(withId: self.accountId, forUser: self.applicationState.accountData)
|
||||||
withId: self.accountId,
|
async let accountTask = AccountService.shared.getAccount(withId: self.accountId, and: self.applicationState.accountData)
|
||||||
and: self.applicationState.accountData
|
|
||||||
) {
|
(self.relationship, self.account) = try await (relationshipTask, accountTask)
|
||||||
self.account = account
|
|
||||||
}
|
self.statuses = try await AccountService.shared.getStatuses(forAccountId: self.accountId, andContext: self.applicationState.accountData)
|
||||||
} catch {
|
} catch {
|
||||||
print("Error \(error.localizedDescription)")
|
print("Error \(error.localizedDescription)")
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ struct CommentsSection: View {
|
||||||
} placeholder: {
|
} placeholder: {
|
||||||
Image(systemName: "person.circle")
|
Image(systemName: "person.circle")
|
||||||
.resizable()
|
.resizable()
|
||||||
.foregroundColor(Color("MainTextColor"))
|
.foregroundColor(Color.mainTextColor)
|
||||||
}
|
}
|
||||||
.frame(width: 32.0, height: 32.0)
|
.frame(width: 32.0, height: 32.0)
|
||||||
}
|
}
|
||||||
|
@ -45,17 +45,17 @@ struct CommentsSection: View {
|
||||||
VStack (alignment: .leading) {
|
VStack (alignment: .leading) {
|
||||||
HStack (alignment: .top) {
|
HStack (alignment: .top) {
|
||||||
Text(status.account?.displayName ?? status.account?.username ?? "")
|
Text(status.account?.displayName ?? status.account?.username ?? "")
|
||||||
.foregroundColor(Color("DisplayNameColor"))
|
.foregroundColor(Color.mainTextColor)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.fontWeight(.bold)
|
.fontWeight(.bold)
|
||||||
Text("@\(status.account?.username ?? "")")
|
Text("@\(status.account?.username ?? "")")
|
||||||
.foregroundColor(Color("LightGrayColor"))
|
.foregroundColor(Color.lightGrayColor)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Text(status.createdAt.toRelative(.isoDateTimeMilliSec))
|
Text(status.createdAt.toRelative(.isoDateTimeMilliSec))
|
||||||
.foregroundColor(Color("LightGrayColor").opacity(0.5))
|
.foregroundColor(Color.lightGrayColor.opacity(0.5))
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,14 +73,14 @@ struct CommentsSection: View {
|
||||||
.frame(minWidth: 0, maxWidth: .infinity)
|
.frame(minWidth: 0, maxWidth: .infinity)
|
||||||
.frame(height: status.mediaAttachments.count == 1 ? 200 : 100)
|
.frame(height: status.mediaAttachments.count == 1 ? 200 : 100)
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
.shadow(color: Color("MainTextColor").opacity(0.3), radius: 2)
|
.shadow(color: Color.mainTextColor.opacity(0.3), radius: 2)
|
||||||
} placeholder: {
|
} placeholder: {
|
||||||
Image(systemName: "photo")
|
Image(systemName: "photo")
|
||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(minWidth: 0, maxWidth: .infinity)
|
.frame(minWidth: 0, maxWidth: .infinity)
|
||||||
.frame(height: status.mediaAttachments.count == 1 ? 200 : 100)
|
.frame(height: status.mediaAttachments.count == 1 ? 200 : 100)
|
||||||
.foregroundColor(Color("MainTextColor"))
|
.foregroundColor(Color.mainTextColor)
|
||||||
.opacity(0.05)
|
.opacity(0.05)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,7 +98,7 @@ struct CommentsSection: View {
|
||||||
if withDivider {
|
if withDivider {
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.size(width: UIScreen.main.bounds.width, height: 4)
|
.size(width: UIScreen.main.bounds.width, height: 4)
|
||||||
.fill(Color("MainTextColor"))
|
.fill(Color.mainTextColor)
|
||||||
.opacity(0.1)
|
.opacity(0.1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,8 +29,6 @@ struct ImageRow: View {
|
||||||
}.padding()
|
}.padding()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Text("Error")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
//
|
||||||
|
// https://mczachurski.dev
|
||||||
|
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import MastodonSwift
|
||||||
|
import NukeUI
|
||||||
|
|
||||||
|
struct ImageRowAsync: View {
|
||||||
|
@State public var attachments: [Attachment]
|
||||||
|
@State private var imageHeight = UIScreen.main.bounds.width
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
if let attachment = attachments.first {
|
||||||
|
ZStack {
|
||||||
|
LazyImage(url: attachment.url, resizingMode: .fill)
|
||||||
|
.onSuccess({ imageResponse in
|
||||||
|
let imgHeight = imageResponse.image.size.height
|
||||||
|
let imgWidth = imageResponse.image.size.width
|
||||||
|
|
||||||
|
let divider = imgWidth / UIScreen.main.bounds.size.width
|
||||||
|
self.imageHeight = imgHeight / divider
|
||||||
|
})
|
||||||
|
.frame(height: self.imageHeight <= 0 ? UIScreen.main.bounds.width : self.imageHeight)
|
||||||
|
|
||||||
|
if let count = attachments.count, count > 1 {
|
||||||
|
BottomRight {
|
||||||
|
Text("1 / \(count)")
|
||||||
|
.padding(.horizontal, 6)
|
||||||
|
.padding(.vertical, 3)
|
||||||
|
.font(.caption2)
|
||||||
|
.foregroundColor(.black)
|
||||||
|
.background(.ultraThinMaterial, in: Capsule())
|
||||||
|
}.padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ImageRowAsync_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
ImageRow(attachments: [])
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,7 +12,7 @@ struct InteractionRow: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack (alignment: .top) {
|
HStack (alignment: .top) {
|
||||||
Button {
|
Button {
|
||||||
// Reply
|
// TODO: Reply.
|
||||||
} label: {
|
} label: {
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
Image(systemName: "message")
|
Image(systemName: "message")
|
||||||
|
@ -24,7 +24,7 @@ struct InteractionRow: View {
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
// Reboost
|
// TODO: Reboost.
|
||||||
} label: {
|
} label: {
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
Image(systemName: statusData.reblogged ? "paperplane.fill" : "paperplane")
|
Image(systemName: statusData.reblogged ? "paperplane.fill" : "paperplane")
|
||||||
|
@ -36,7 +36,7 @@ struct InteractionRow: View {
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
// Favorite
|
// TODO: Favorite.
|
||||||
} label: {
|
} label: {
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
Image(systemName: statusData.favourited ? "hand.thumbsup.fill" : "hand.thumbsup")
|
Image(systemName: statusData.favourited ? "hand.thumbsup.fill" : "hand.thumbsup")
|
||||||
|
@ -48,7 +48,7 @@ struct InteractionRow: View {
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
// Bookmark
|
// TODO: Bookmark.
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: statusData.bookmarked ? "bookmark.fill" : "bookmark")
|
Image(systemName: statusData.bookmarked ? "bookmark.fill" : "bookmark")
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ struct InteractionRow: View {
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
// Share
|
// TODO: Share.
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "square.and.arrow.up")
|
Image(systemName: "square.and.arrow.up")
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,15 +19,15 @@ struct UsernameRow: View {
|
||||||
} placeholder: {
|
} placeholder: {
|
||||||
Image(systemName: "person.circle")
|
Image(systemName: "person.circle")
|
||||||
.resizable()
|
.resizable()
|
||||||
.foregroundColor(Color("MainTextColor"))
|
.foregroundColor(Color.mainTextColor)
|
||||||
}
|
}
|
||||||
.frame(width: 48.0, height: 48.0)
|
.frame(width: 48.0, height: 48.0)
|
||||||
|
|
||||||
VStack (alignment: .leading) {
|
VStack (alignment: .leading) {
|
||||||
Text(statusData.accountDisplayName ?? statusData.accountUsername)
|
Text(statusData.accountDisplayName ?? statusData.accountUsername)
|
||||||
.foregroundColor(Color("DisplayNameColor"))
|
.foregroundColor(Color.mainTextColor)
|
||||||
Text("@\(statusData.accountUsername)")
|
Text("@\(statusData.accountUsername)")
|
||||||
.foregroundColor(Color("LightGrayColor"))
|
.foregroundColor(Color.lightGrayColor)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
}
|
}
|
||||||
.padding(.leading, 8)
|
.padding(.leading, 8)
|
||||||
|
|
Loading…
Reference in New Issue