Haptic feedback
This commit is contained in:
parent
b3c542588c
commit
34a12eae75
|
@ -24,7 +24,6 @@
|
||||||
F8210DE52966E160001D9973 /* Color+SystemColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8210DE42966E160001D9973 /* Color+SystemColors.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 */; };
|
F8210DE72966E1D1001D9973 /* Color+Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8210DE62966E1D1001D9973 /* Color+Assets.swift */; };
|
||||||
F8210DEA2966E4F9001D9973 /* AnimatePlaceholderModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8210DE92966E4F9001D9973 /* AnimatePlaceholderModifier.swift */; };
|
F8210DEA2966E4F9001D9973 /* AnimatePlaceholderModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8210DE92966E4F9001D9973 /* AnimatePlaceholderModifier.swift */; };
|
||||||
F8210DEC2966F30C001D9973 /* UserFeedbackService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8210DEB2966F30C001D9973 /* UserFeedbackService.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 */; };
|
||||||
|
@ -57,7 +56,7 @@
|
||||||
F88C2473295C37BB0006098B /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F88C2472295C37BB0006098B /* Preview Assets.xcassets */; };
|
F88C2473295C37BB0006098B /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F88C2472295C37BB0006098B /* Preview Assets.xcassets */; };
|
||||||
F88C2475295C37BB0006098B /* CoreDataHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88C2474295C37BB0006098B /* CoreDataHandler.swift */; };
|
F88C2475295C37BB0006098B /* CoreDataHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88C2474295C37BB0006098B /* CoreDataHandler.swift */; };
|
||||||
F88C2478295C37BB0006098B /* Vernissage.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = F88C2476295C37BB0006098B /* Vernissage.xcdatamodeld */; };
|
F88C2478295C37BB0006098B /* Vernissage.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = F88C2476295C37BB0006098B /* Vernissage.xcdatamodeld */; };
|
||||||
F88C2482295C3A4F0006098B /* DetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88C2481295C3A4F0006098B /* DetailsView.swift */; };
|
F88C2482295C3A4F0006098B /* StatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88C2481295C3A4F0006098B /* StatusView.swift */; };
|
||||||
F88C2486295C48030006098B /* HTMLFotmattedText.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88C2485295C48030006098B /* HTMLFotmattedText.swift */; };
|
F88C2486295C48030006098B /* HTMLFotmattedText.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88C2485295C48030006098B /* HTMLFotmattedText.swift */; };
|
||||||
F88FAD21295F3944009B20C9 /* HomeFeedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88FAD20295F3944009B20C9 /* HomeFeedView.swift */; };
|
F88FAD21295F3944009B20C9 /* HomeFeedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88FAD20295F3944009B20C9 /* HomeFeedView.swift */; };
|
||||||
F88FAD23295F3FC4009B20C9 /* LocalFeedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88FAD22295F3FC4009B20C9 /* LocalFeedView.swift */; };
|
F88FAD23295F3FC4009B20C9 /* LocalFeedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88FAD22295F3FC4009B20C9 /* LocalFeedView.swift */; };
|
||||||
|
@ -67,6 +66,9 @@
|
||||||
F88FAD2B295F43B8009B20C9 /* AccountData+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88FAD29295F43B8009B20C9 /* AccountData+CoreDataProperties.swift */; };
|
F88FAD2B295F43B8009B20C9 /* AccountData+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88FAD29295F43B8009B20C9 /* AccountData+CoreDataProperties.swift */; };
|
||||||
F88FAD2D295F4AD7009B20C9 /* ApplicationState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88FAD2C295F4AD7009B20C9 /* ApplicationState.swift */; };
|
F88FAD2D295F4AD7009B20C9 /* ApplicationState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88FAD2C295F4AD7009B20C9 /* ApplicationState.swift */; };
|
||||||
F88FAD32295F5029009B20C9 /* RemoteFileService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88FAD31295F5029009B20C9 /* RemoteFileService.swift */; };
|
F88FAD32295F5029009B20C9 /* RemoteFileService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88FAD31295F5029009B20C9 /* RemoteFileService.swift */; };
|
||||||
|
F897978829681B9C00B22335 /* UserAvatar.swift in Sources */ = {isa = PBXBuildFile; fileRef = F897978729681B9C00B22335 /* UserAvatar.swift */; };
|
||||||
|
F897978A2968314A00B22335 /* LoadingIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89797892968314A00B22335 /* LoadingIndicator.swift */; };
|
||||||
|
F897978D2968369600B22335 /* HapticService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F897978C2968369600B22335 /* HapticService.swift */; };
|
||||||
F8A93D7E2965FD89001D8331 /* UserProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A93D7D2965FD89001D8331 /* UserProfileView.swift */; };
|
F8A93D7E2965FD89001D8331 /* UserProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A93D7D2965FD89001D8331 /* UserProfileView.swift */; };
|
||||||
F8A93D802965FED4001D8331 /* AccountService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A93D7F2965FED4001D8331 /* AccountService.swift */; };
|
F8A93D802965FED4001D8331 /* AccountService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A93D7F2965FED4001D8331 /* AccountService.swift */; };
|
||||||
F8A93D822965FF5D001D8331 /* MastodonClientAuthenticated+Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A93D812965FF5D001D8331 /* MastodonClientAuthenticated+Account.swift */; };
|
F8A93D822965FF5D001D8331 /* MastodonClientAuthenticated+Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A93D812965FF5D001D8331 /* MastodonClientAuthenticated+Account.swift */; };
|
||||||
|
@ -87,7 +89,6 @@
|
||||||
F8210DE42966E160001D9973 /* Color+SystemColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+SystemColors.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>"; };
|
F8210DE62966E1D1001D9973 /* Color+Assets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Assets.swift"; sourceTree = "<group>"; };
|
||||||
F8210DE92966E4F9001D9973 /* AnimatePlaceholderModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatePlaceholderModifier.swift; sourceTree = "<group>"; };
|
F8210DE92966E4F9001D9973 /* AnimatePlaceholderModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatePlaceholderModifier.swift; sourceTree = "<group>"; };
|
||||||
F8210DEB2966F30C001D9973 /* UserFeedbackService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserFeedbackService.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>"; };
|
||||||
|
@ -121,7 +122,7 @@
|
||||||
F88C2472295C37BB0006098B /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
F88C2472295C37BB0006098B /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||||
F88C2474295C37BB0006098B /* CoreDataHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataHandler.swift; sourceTree = "<group>"; };
|
F88C2474295C37BB0006098B /* CoreDataHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataHandler.swift; sourceTree = "<group>"; };
|
||||||
F88C2477295C37BB0006098B /* Vernissage.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Vernissage.xcdatamodel; sourceTree = "<group>"; };
|
F88C2477295C37BB0006098B /* Vernissage.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Vernissage.xcdatamodel; sourceTree = "<group>"; };
|
||||||
F88C2481295C3A4F0006098B /* DetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailsView.swift; sourceTree = "<group>"; };
|
F88C2481295C3A4F0006098B /* StatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusView.swift; sourceTree = "<group>"; };
|
||||||
F88C2485295C48030006098B /* HTMLFotmattedText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTMLFotmattedText.swift; sourceTree = "<group>"; };
|
F88C2485295C48030006098B /* HTMLFotmattedText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTMLFotmattedText.swift; sourceTree = "<group>"; };
|
||||||
F88FAD20295F3944009B20C9 /* HomeFeedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeFeedView.swift; sourceTree = "<group>"; };
|
F88FAD20295F3944009B20C9 /* HomeFeedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeFeedView.swift; sourceTree = "<group>"; };
|
||||||
F88FAD22295F3FC4009B20C9 /* LocalFeedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalFeedView.swift; sourceTree = "<group>"; };
|
F88FAD22295F3FC4009B20C9 /* LocalFeedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalFeedView.swift; sourceTree = "<group>"; };
|
||||||
|
@ -131,6 +132,9 @@
|
||||||
F88FAD29295F43B8009B20C9 /* AccountData+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccountData+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
F88FAD29295F43B8009B20C9 /* AccountData+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccountData+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||||
F88FAD2C295F4AD7009B20C9 /* ApplicationState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationState.swift; sourceTree = "<group>"; };
|
F88FAD2C295F4AD7009B20C9 /* ApplicationState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationState.swift; sourceTree = "<group>"; };
|
||||||
F88FAD31295F5029009B20C9 /* RemoteFileService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteFileService.swift; sourceTree = "<group>"; };
|
F88FAD31295F5029009B20C9 /* RemoteFileService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteFileService.swift; sourceTree = "<group>"; };
|
||||||
|
F897978729681B9C00B22335 /* UserAvatar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAvatar.swift; sourceTree = "<group>"; };
|
||||||
|
F89797892968314A00B22335 /* LoadingIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingIndicator.swift; sourceTree = "<group>"; };
|
||||||
|
F897978C2968369600B22335 /* HapticService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HapticService.swift; sourceTree = "<group>"; };
|
||||||
F8A93D7D2965FD89001D8331 /* UserProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileView.swift; sourceTree = "<group>"; };
|
F8A93D7D2965FD89001D8331 /* UserProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileView.swift; sourceTree = "<group>"; };
|
||||||
F8A93D7F2965FED4001D8331 /* AccountService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountService.swift; sourceTree = "<group>"; };
|
F8A93D7F2965FED4001D8331 /* AccountService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountService.swift; sourceTree = "<group>"; };
|
||||||
F8A93D812965FF5D001D8331 /* MastodonClientAuthenticated+Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonClientAuthenticated+Account.swift"; sourceTree = "<group>"; };
|
F8A93D812965FF5D001D8331 /* MastodonClientAuthenticated+Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonClientAuthenticated+Account.swift"; sourceTree = "<group>"; };
|
||||||
|
@ -162,7 +166,7 @@
|
||||||
F8341F93295C63E2009C8EE6 /* Views */ = {
|
F8341F93295C63E2009C8EE6 /* Views */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
F88C2481295C3A4F0006098B /* DetailsView.swift */,
|
F88C2481295C3A4F0006098B /* StatusView.swift */,
|
||||||
F88C246D295C37B80006098B /* MainView.swift */,
|
F88C246D295C37B80006098B /* MainView.swift */,
|
||||||
F88FAD20295F3944009B20C9 /* HomeFeedView.swift */,
|
F88FAD20295F3944009B20C9 /* HomeFeedView.swift */,
|
||||||
F88FAD22295F3FC4009B20C9 /* LocalFeedView.swift */,
|
F88FAD22295F3FC4009B20C9 /* LocalFeedView.swift */,
|
||||||
|
@ -243,6 +247,8 @@
|
||||||
F85D497A29640C8200751DF7 /* UsernameRow.swift */,
|
F85D497A29640C8200751DF7 /* UsernameRow.swift */,
|
||||||
F85D497C29640D5900751DF7 /* InteractionRow.swift */,
|
F85D497C29640D5900751DF7 /* InteractionRow.swift */,
|
||||||
F85D497E296416C800751DF7 /* CommentsSection.swift */,
|
F85D497E296416C800751DF7 /* CommentsSection.swift */,
|
||||||
|
F897978729681B9C00B22335 /* UserAvatar.swift */,
|
||||||
|
F89797892968314A00B22335 /* LoadingIndicator.swift */,
|
||||||
);
|
);
|
||||||
path = Widgets;
|
path = Widgets;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -267,6 +273,7 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
F866F6A829604FFF002E8F88 /* Info.plist */,
|
F866F6A829604FFF002E8F88 /* Info.plist */,
|
||||||
|
F897978B2968367E00B22335 /* Haptics */,
|
||||||
F8210DE82966E4D8001D9973 /* Modifiers */,
|
F8210DE82966E4D8001D9973 /* Modifiers */,
|
||||||
F88FAD30295F5010009B20C9 /* Services */,
|
F88FAD30295F5010009B20C9 /* Services */,
|
||||||
F83901A2295D863B00456AE2 /* Widgets */,
|
F83901A2295D863B00456AE2 /* Widgets */,
|
||||||
|
@ -300,12 +307,19 @@
|
||||||
F85D4974296407F100751DF7 /* TimelineService.swift */,
|
F85D4974296407F100751DF7 /* TimelineService.swift */,
|
||||||
F8A93D7F2965FED4001D8331 /* AccountService.swift */,
|
F8A93D7F2965FED4001D8331 /* AccountService.swift */,
|
||||||
F8210DE02966D0C4001D9973 /* StatusService.swift */,
|
F8210DE02966D0C4001D9973 /* StatusService.swift */,
|
||||||
F8210DEB2966F30C001D9973 /* UserFeedbackService.swift */,
|
|
||||||
F85DBF92296760790069BF89 /* CacheAvatarService.swift */,
|
F85DBF92296760790069BF89 /* CacheAvatarService.swift */,
|
||||||
);
|
);
|
||||||
path = Services;
|
path = Services;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
F897978B2968367E00B22335 /* Haptics */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
F897978C2968369600B22335 /* HapticService.swift */,
|
||||||
|
);
|
||||||
|
path = Haptics;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
|
@ -391,6 +405,7 @@
|
||||||
F80048082961E6DE00E6868A /* StatusDataHandler.swift in Sources */,
|
F80048082961E6DE00E6868A /* StatusDataHandler.swift in Sources */,
|
||||||
F866F6A0296040A8002E8F88 /* ApplicationSettings+CoreDataClass.swift in Sources */,
|
F866F6A0296040A8002E8F88 /* ApplicationSettings+CoreDataClass.swift in Sources */,
|
||||||
F8210DEA2966E4F9001D9973 /* AnimatePlaceholderModifier.swift in Sources */,
|
F8210DEA2966E4F9001D9973 /* AnimatePlaceholderModifier.swift in Sources */,
|
||||||
|
F897978A2968314A00B22335 /* LoadingIndicator.swift in Sources */,
|
||||||
F8210DE52966E160001D9973 /* Color+SystemColors.swift in Sources */,
|
F8210DE52966E160001D9973 /* Color+SystemColors.swift in Sources */,
|
||||||
F85DBF93296760790069BF89 /* CacheAvatarService.swift in Sources */,
|
F85DBF93296760790069BF89 /* CacheAvatarService.swift in Sources */,
|
||||||
F88FAD23295F3FC4009B20C9 /* LocalFeedView.swift in Sources */,
|
F88FAD23295F3FC4009B20C9 /* LocalFeedView.swift in Sources */,
|
||||||
|
@ -405,6 +420,7 @@
|
||||||
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 */,
|
||||||
|
F897978829681B9C00B22335 /* UserAvatar.swift in Sources */,
|
||||||
F8210DDD2966CF17001D9973 /* StatusData+Status.swift in Sources */,
|
F8210DDD2966CF17001D9973 /* StatusData+Status.swift in Sources */,
|
||||||
F8210DCF2966B600001D9973 /* ImageRowAsync.swift in Sources */,
|
F8210DCF2966B600001D9973 /* ImageRowAsync.swift in Sources */,
|
||||||
F85D498329642FAC00751DF7 /* AttachmentData+Comperable.swift in Sources */,
|
F85D498329642FAC00751DF7 /* AttachmentData+Comperable.swift in Sources */,
|
||||||
|
@ -416,12 +432,13 @@
|
||||||
F85DBF912967385F0069BF89 /* FollowingView.swift in Sources */,
|
F85DBF912967385F0069BF89 /* FollowingView.swift in Sources */,
|
||||||
F800480A2961EA1900E6868A /* AttachmentDataHandler.swift in Sources */,
|
F800480A2961EA1900E6868A /* AttachmentDataHandler.swift in Sources */,
|
||||||
F80048032961850500E6868A /* AttachmentData+CoreDataClass.swift in Sources */,
|
F80048032961850500E6868A /* AttachmentData+CoreDataClass.swift in Sources */,
|
||||||
|
F897978D2968369600B22335 /* HapticService.swift in Sources */,
|
||||||
F8341F90295C636C009C8EE6 /* UIImage+Exif.swift in Sources */,
|
F8341F90295C636C009C8EE6 /* UIImage+Exif.swift in Sources */,
|
||||||
F8A93D7E2965FD89001D8331 /* UserProfileView.swift in Sources */,
|
F8A93D7E2965FD89001D8331 /* UserProfileView.swift in Sources */,
|
||||||
F85D4981296417F700751DF7 /* MastodonClientAuthenticated+Context.swift in Sources */,
|
F85D4981296417F700751DF7 /* MastodonClientAuthenticated+Context.swift in Sources */,
|
||||||
F88C246E295C37B80006098B /* MainView.swift in Sources */,
|
F88C246E295C37B80006098B /* MainView.swift in Sources */,
|
||||||
F88C2478295C37BB0006098B /* Vernissage.xcdatamodeld in Sources */,
|
F88C2478295C37BB0006098B /* Vernissage.xcdatamodeld in Sources */,
|
||||||
F88C2482295C3A4F0006098B /* DetailsView.swift in Sources */,
|
F88C2482295C3A4F0006098B /* StatusView.swift in Sources */,
|
||||||
F866F6A329604161002E8F88 /* AccountDataHandler.swift in Sources */,
|
F866F6A329604161002E8F88 /* AccountDataHandler.swift in Sources */,
|
||||||
F85D497F296416C800751DF7 /* CommentsSection.swift in Sources */,
|
F85D497F296416C800751DF7 /* CommentsSection.swift in Sources */,
|
||||||
F88C2486295C48030006098B /* HTMLFotmattedText.swift in Sources */,
|
F88C2486295C48030006098B /* HTMLFotmattedText.swift in Sources */,
|
||||||
|
@ -442,7 +459,6 @@
|
||||||
F8A93D802965FED4001D8331 /* AccountService.swift in Sources */,
|
F8A93D802965FED4001D8331 /* AccountService.swift in Sources */,
|
||||||
F866F6AA29605AFA002E8F88 /* SceneDelegate.swift in Sources */,
|
F866F6AA29605AFA002E8F88 /* SceneDelegate.swift in Sources */,
|
||||||
F85D4973296406E700751DF7 /* BottomRight.swift in Sources */,
|
F85D4973296406E700751DF7 /* BottomRight.swift in Sources */,
|
||||||
F8210DEC2966F30C001D9973 /* UserFeedbackService.swift in Sources */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|
|
@ -26,7 +26,7 @@ struct HTMLFormattedText: UIViewRepresentable {
|
||||||
textView.isUserInteractionEnabled = false
|
textView.isUserInteractionEnabled = false
|
||||||
textView.translatesAutoresizingMaskIntoConstraints = false
|
textView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
textView.isScrollEnabled = false
|
textView.isScrollEnabled = false
|
||||||
textView.backgroundColor = UIColor(Color.clear)
|
textView.backgroundColor = UIColor(.clear)
|
||||||
|
|
||||||
return textView
|
return textView
|
||||||
}
|
}
|
||||||
|
@ -48,12 +48,12 @@ 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(.mainTextColor)
|
||||||
]
|
]
|
||||||
|
|
||||||
let linkAttributes = [
|
let linkAttributes = [
|
||||||
NSAttributedString.Key.font: UIFont.systemFont(ofSize: CGFloat(self.fontSize)),
|
NSAttributedString.Key.font: UIFont.systemFont(ofSize: CGFloat(self.fontSize)),
|
||||||
NSAttributedString.Key.foregroundColor: UIColor(Color.accentColor)
|
NSAttributedString.Key.foregroundColor: UIColor(.accentColor)
|
||||||
]
|
]
|
||||||
|
|
||||||
if let attributedString = try? NSMutableAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil) {
|
if let attributedString = try? NSMutableAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil) {
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
//
|
||||||
|
// https://mczachurski.dev
|
||||||
|
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
//
|
||||||
|
|
||||||
|
import CoreHaptics
|
||||||
|
|
||||||
|
public final class HapticService: ObservableObject {
|
||||||
|
public static let shared = HapticService()
|
||||||
|
|
||||||
|
private let hapticEngine: CHHapticEngine?
|
||||||
|
private var needsToRestart = false
|
||||||
|
|
||||||
|
/// Fires a transient haptic event with the given intensity and sharpness (0-1).
|
||||||
|
public func touch(intensity: Float = 0.75, sharpness: Float = 0.5) {
|
||||||
|
do {
|
||||||
|
let event = CHHapticEvent(
|
||||||
|
eventType: .hapticTransient,
|
||||||
|
parameters: [
|
||||||
|
CHHapticEventParameter(parameterID: .hapticIntensity, value: intensity),
|
||||||
|
CHHapticEventParameter(parameterID: .hapticSharpness, value: sharpness)
|
||||||
|
],
|
||||||
|
relativeTime: 0)
|
||||||
|
|
||||||
|
let pattern = try CHHapticPattern(events: [event], parameters: [])
|
||||||
|
let player = try hapticEngine?.makePlayer(with: pattern)
|
||||||
|
|
||||||
|
if needsToRestart {
|
||||||
|
try? start()
|
||||||
|
}
|
||||||
|
|
||||||
|
try player?.start(atTime: CHHapticTimeImmediate)
|
||||||
|
} catch {
|
||||||
|
print("Error \(error.localizedDescription)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private init() {
|
||||||
|
hapticEngine = try? CHHapticEngine()
|
||||||
|
hapticEngine?.resetHandler = resetHandler
|
||||||
|
hapticEngine?.stoppedHandler = restartHandler
|
||||||
|
hapticEngine?.playsHapticsOnly = true
|
||||||
|
try? start()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stops the internal CHHapticEngine. Should be called when your app enters the background.
|
||||||
|
public func stop(completionHandler: CHHapticEngine.CompletionHandler? = nil) {
|
||||||
|
hapticEngine?.stop(completionHandler: completionHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Starts the internal CHHapticEngine. Should be called when your app enters the foreground.
|
||||||
|
public func start() throws {
|
||||||
|
try hapticEngine?.start()
|
||||||
|
needsToRestart = false
|
||||||
|
}
|
||||||
|
|
||||||
|
private func resetHandler() {
|
||||||
|
do {
|
||||||
|
try start()
|
||||||
|
} catch {
|
||||||
|
needsToRestart = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func restartHandler(_ reasonForStopping: CHHapticEngine.StoppedReason? = nil) {
|
||||||
|
resetHandler()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -9,7 +9,8 @@ import Foundation
|
||||||
|
|
||||||
public class ApplicationState: ObservableObject {
|
public class ApplicationState: ObservableObject {
|
||||||
public static let shared = ApplicationState()
|
public static let shared = ApplicationState()
|
||||||
|
private init() { }
|
||||||
|
|
||||||
@Published var accountData: AccountData?
|
@Published var accountData: AccountData?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,13 +13,28 @@ public class CacheAvatarService {
|
||||||
private init() { }
|
private init() { }
|
||||||
|
|
||||||
private var cache: Dictionary<String, UIImage> = [:]
|
private var cache: Dictionary<String, UIImage> = [:]
|
||||||
|
|
||||||
func addImage(for id: String, data: Data) {
|
func addImage(for id: String, data: Data) {
|
||||||
if let uiImage = UIImage(data: data) {
|
if let uiImage = UIImage(data: data) {
|
||||||
self.cache[id] = uiImage
|
self.cache[id] = uiImage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func downloadImage(for accountId: String?, avatarUrl: URL?) async {
|
||||||
|
guard let accountId, let avatarUrl else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
let avatarData = try await RemoteFileService.shared.fetchData(url: avatarUrl)
|
||||||
|
if let avatarData {
|
||||||
|
CacheAvatarService.shared.addImage(for: accountId, data: avatarData)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
print("Error \(error.localizedDescription)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func getImage(for id: String) -> UIImage? {
|
func getImage(for id: String) -> UIImage? {
|
||||||
return self.cache[id]
|
return self.cache[id]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
//
|
|
||||||
// https://mczachurski.dev
|
|
||||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
|
||||||
// Licensed under the MIT License.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import AVFoundation
|
|
||||||
|
|
||||||
public class UserFeedbackService {
|
|
||||||
public static let shared = UserFeedbackService()
|
|
||||||
private init() { }
|
|
||||||
|
|
||||||
func send() {
|
|
||||||
AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
|
|
||||||
// AudioServicesPlaySystemSound(1016)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -49,6 +49,12 @@ struct VernissageApp: App {
|
||||||
URLCache.shared.diskCapacity = 1_000_000_000 // ~1GB disk cache space
|
URLCache.shared.diskCapacity = 1_000_000_000 // ~1GB disk cache space
|
||||||
}
|
}
|
||||||
.navigationViewStyle(.stack)
|
.navigationViewStyle(.stack)
|
||||||
|
.onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in
|
||||||
|
try? HapticService.shared.start()
|
||||||
|
}
|
||||||
|
.onReceive(NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification)) { _ in
|
||||||
|
HapticService.shared.stop()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,25 +14,27 @@ struct FollowersView: View {
|
||||||
@State private var accounts: [Account] = []
|
@State private var accounts: [Account] = []
|
||||||
@State private var page = 1
|
@State private var page = 1
|
||||||
@State private var allItemsLoaded = false
|
@State private var allItemsLoaded = false
|
||||||
|
@State private var firstLoadFinished = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
List(accounts, id: \.id) { account in
|
List {
|
||||||
NavigationLink(destination: UserProfileView(
|
ForEach(accounts, id: \.id) { account in
|
||||||
accountId: account.id,
|
NavigationLink(destination: UserProfileView(
|
||||||
accountDisplayName: account.displayName,
|
accountId: account.id,
|
||||||
accountUserName: account.acct)
|
accountDisplayName: account.displayName,
|
||||||
.environmentObject(applicationState)) {
|
accountUserName: account.acct)
|
||||||
UsernameRow(accountAvatar: account.avatar,
|
.environmentObject(applicationState)) {
|
||||||
accountDisplayName: account.displayName,
|
UsernameRow(accountAvatar: account.avatar,
|
||||||
accountUsername: account.acct,
|
accountDisplayName: account.displayName,
|
||||||
cachedAvatar: CacheAvatarService.shared.getImage(for: account.id))
|
accountUsername: account.acct,
|
||||||
}
|
cachedAvatar: CacheAvatarService.shared.getImage(for: account.id))
|
||||||
|
}
|
||||||
if allItemsLoaded == false && accounts.last?.id == account.id {
|
}
|
||||||
|
|
||||||
|
if allItemsLoaded == false && firstLoadFinished {
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
Spacer()
|
Spacer()
|
||||||
ProgressView()
|
LoadingIndicator()
|
||||||
.progressViewStyle(CircularProgressViewStyle())
|
|
||||||
.onAppear {
|
.onAppear {
|
||||||
Task {
|
Task {
|
||||||
self.page = self.page + 1
|
self.page = self.page + 1
|
||||||
|
@ -42,6 +44,10 @@ struct FollowersView: View {
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}.overlay {
|
||||||
|
if firstLoadFinished == false {
|
||||||
|
LoadingIndicator()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.navigationBarTitle("Followers")
|
.navigationBarTitle("Followers")
|
||||||
.listStyle(PlainListStyle())
|
.listStyle(PlainListStyle())
|
||||||
|
@ -51,6 +57,7 @@ struct FollowersView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
await self.loadAccounts(page: self.page)
|
await self.loadAccounts(page: self.page)
|
||||||
|
self.firstLoadFinished = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,25 +73,20 @@ struct FollowersView: View {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for account in accountsFromApi {
|
await self.downloadAvatars(accounts: accountsFromApi)
|
||||||
guard let avatarUrl = account.avatar else {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
if let avatarData = try await RemoteFileService.shared.fetchData(url: avatarUrl) {
|
|
||||||
CacheAvatarService.shared.addImage(for: account.id, data: avatarData)
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
print("Error \(error.localizedDescription)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.accounts.append(contentsOf: accountsFromApi)
|
self.accounts.append(contentsOf: accountsFromApi)
|
||||||
} catch {
|
} catch {
|
||||||
print("Error \(error.localizedDescription)")
|
print("Error \(error.localizedDescription)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func downloadAvatars(accounts: [Account]) async {
|
||||||
|
await withTaskGroup(of: Void.self) { group in
|
||||||
|
for account in accounts {
|
||||||
|
group.addTask { await CacheAvatarService.shared.downloadImage(for: account.id, avatarUrl: account.avatar) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FollowersView_Previews: PreviewProvider {
|
struct FollowersView_Previews: PreviewProvider {
|
||||||
|
|
|
@ -14,25 +14,27 @@ struct FollowingView: View {
|
||||||
@State private var accounts: [Account] = []
|
@State private var accounts: [Account] = []
|
||||||
@State private var page = 1
|
@State private var page = 1
|
||||||
@State private var allItemsLoaded = false
|
@State private var allItemsLoaded = false
|
||||||
|
@State private var firstLoadFinished = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
List(accounts, id: \.id) { account in
|
List {
|
||||||
NavigationLink(destination: UserProfileView(
|
ForEach(accounts, id: \.id) { account in
|
||||||
accountId: account.id,
|
NavigationLink(destination: UserProfileView(
|
||||||
accountDisplayName: account.displayName,
|
accountId: account.id,
|
||||||
accountUserName: account.acct)
|
accountDisplayName: account.displayName,
|
||||||
.environmentObject(applicationState)) {
|
accountUserName: account.acct)
|
||||||
UsernameRow(accountAvatar: account.avatar,
|
.environmentObject(applicationState)) {
|
||||||
accountDisplayName: account.displayName,
|
UsernameRow(accountAvatar: account.avatar,
|
||||||
accountUsername: account.acct,
|
accountDisplayName: account.displayName,
|
||||||
cachedAvatar: CacheAvatarService.shared.getImage(for: account.id))
|
accountUsername: account.acct,
|
||||||
}
|
cachedAvatar: CacheAvatarService.shared.getImage(for: account.id))
|
||||||
|
}
|
||||||
if allItemsLoaded == false && accounts.last?.id == account.id {
|
}
|
||||||
|
|
||||||
|
if allItemsLoaded == false && firstLoadFinished {
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
Spacer()
|
Spacer()
|
||||||
ProgressView()
|
LoadingIndicator()
|
||||||
.progressViewStyle(CircularProgressViewStyle())
|
|
||||||
.onAppear {
|
.onAppear {
|
||||||
Task {
|
Task {
|
||||||
self.page = self.page + 1
|
self.page = self.page + 1
|
||||||
|
@ -42,6 +44,10 @@ struct FollowingView: View {
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}.overlay {
|
||||||
|
if firstLoadFinished == false {
|
||||||
|
LoadingIndicator()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.navigationBarTitle("Following")
|
.navigationBarTitle("Following")
|
||||||
.listStyle(PlainListStyle())
|
.listStyle(PlainListStyle())
|
||||||
|
@ -51,6 +57,7 @@ struct FollowingView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
await self.loadAccounts(page: self.page)
|
await self.loadAccounts(page: self.page)
|
||||||
|
self.firstLoadFinished = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,29 +73,24 @@ struct FollowingView: View {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for account in accountsFromApi {
|
await self.downloadAvatars(accounts: accountsFromApi)
|
||||||
guard let avatarUrl = account.avatar else {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
if let avatarData = try await RemoteFileService.shared.fetchData(url: avatarUrl) {
|
|
||||||
CacheAvatarService.shared.addImage(for: account.id, data: avatarData)
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
print("Error \(error.localizedDescription)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.accounts.append(contentsOf: accountsFromApi)
|
self.accounts.append(contentsOf: accountsFromApi)
|
||||||
} catch {
|
} catch {
|
||||||
print("Error \(error.localizedDescription)")
|
print("Error \(error.localizedDescription)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func downloadAvatars(accounts: [Account]) async {
|
||||||
|
await withTaskGroup(of: Void.self) { group in
|
||||||
|
for account in accounts {
|
||||||
|
group.addTask { await CacheAvatarService.shared.downloadImage(for: account.id, avatarUrl: account.avatar) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FollowingView_Previews: PreviewProvider {
|
struct FollowingView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
FollowingView(accountId: "")
|
FollowersView(accountId: "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,14 +22,13 @@ 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: DetailsView(statusId: item.id)
|
NavigationLink(destination: StatusView(statusId: item.id)
|
||||||
.environmentObject(applicationState)) {
|
.environmentObject(applicationState)) {
|
||||||
ImageRow(attachments: item.attachments())
|
ImageRow(attachments: item.attachments())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ProgressView()
|
LoadingIndicator()
|
||||||
.progressViewStyle(CircularProgressViewStyle())
|
|
||||||
.onAppear {
|
.onAppear {
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
|
@ -45,8 +44,7 @@ struct HomeFeedView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
if showLoading {
|
if showLoading {
|
||||||
ProgressView()
|
LoadingIndicator()
|
||||||
.progressViewStyle(CircularProgressViewStyle())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.refreshable {
|
.refreshable {
|
||||||
|
|
|
@ -47,7 +47,7 @@ struct MainView: View {
|
||||||
if let accountData = self.applicationState.accountData {
|
if let accountData = self.applicationState.accountData {
|
||||||
UserProfileView(accountId: accountData.id,
|
UserProfileView(accountId: accountData.id,
|
||||||
accountDisplayName: accountData.displayName,
|
accountDisplayName: accountData.displayName,
|
||||||
accountUserName: accountData.username)
|
accountUserName: accountData.acct)
|
||||||
}
|
}
|
||||||
case .notifications:
|
case .notifications:
|
||||||
NotificationsView()
|
NotificationsView()
|
||||||
|
@ -112,7 +112,7 @@ struct MainView: View {
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
}
|
}
|
||||||
.frame(width: 150)
|
.frame(width: 150)
|
||||||
.foregroundColor(Color.mainTextColor)
|
.foregroundColor(.mainTextColor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -126,10 +126,10 @@ struct MainView: View {
|
||||||
// TODO: 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?.acct ?? "")
|
||||||
Image(systemName: "person.circle.fill")
|
Image(systemName: "person.circle.fill")
|
||||||
.resizable()
|
.resizable()
|
||||||
.foregroundColor(Color.mainTextColor)
|
.foregroundColor(.mainTextColor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,7 +153,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(.mainTextColor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import SwiftUI
|
||||||
import MastodonSwift
|
import MastodonSwift
|
||||||
import AVFoundation
|
import AVFoundation
|
||||||
|
|
||||||
struct DetailsView: View {
|
struct StatusView: View {
|
||||||
@EnvironmentObject var applicationState: ApplicationState
|
@EnvironmentObject var applicationState: ApplicationState
|
||||||
@State var statusId: String
|
@State var statusId: String
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ struct DetailsView: View {
|
||||||
LabelIcon(iconName: "calendar", value: "2 Oct 2022")
|
LabelIcon(iconName: "calendar", value: "2 Oct 2022")
|
||||||
}
|
}
|
||||||
.padding(.bottom, 2)
|
.padding(.bottom, 2)
|
||||||
.foregroundColor(Color.lightGrayColor)
|
.foregroundColor(.lightGrayColor)
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
Text("Uploaded")
|
Text("Uploaded")
|
||||||
|
@ -51,7 +51,7 @@ struct DetailsView: View {
|
||||||
Text("via \(applicationName)")
|
Text("via \(applicationName)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.foregroundColor(Color.lightGrayColor)
|
.foregroundColor(.lightGrayColor)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
|
|
||||||
InteractionRow(statusData: statusData)
|
InteractionRow(statusData: statusData)
|
||||||
|
@ -72,10 +72,10 @@ struct DetailsView: View {
|
||||||
accountUsername: "@username")
|
accountUsername: "@username")
|
||||||
|
|
||||||
Text("Lorem ispum text something")
|
Text("Lorem ispum text something")
|
||||||
.foregroundColor(Color.lightGrayColor)
|
.foregroundColor(.lightGrayColor)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
Text("Lorem ispum text something sdf sdfsdf sdfdsfsdfsdf")
|
Text("Lorem ispum text something sdf sdfsdf sdfdsfsdfsdf")
|
||||||
.foregroundColor(Color.lightGrayColor)
|
.foregroundColor(.lightGrayColor)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
|
|
||||||
LabelIcon(iconName: "camera", value: "SONY ILCE-7M3")
|
LabelIcon(iconName: "camera", value: "SONY ILCE-7M3")
|
||||||
|
@ -114,8 +114,8 @@ struct DetailsView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DetailsView_Previews: PreviewProvider {
|
struct StatusView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
DetailsView(statusId: "123")
|
StatusView(statusId: "123")
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -8,13 +8,15 @@ import SwiftUI
|
||||||
import MastodonSwift
|
import MastodonSwift
|
||||||
|
|
||||||
struct UserProfileView: View {
|
struct UserProfileView: View {
|
||||||
@EnvironmentObject var applicationState: ApplicationState
|
@EnvironmentObject private var applicationState: ApplicationState
|
||||||
|
|
||||||
@State public var accountId: String
|
@State public var accountId: String
|
||||||
@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 relationship: Relationship? = nil
|
||||||
@State private var statuses: [Status] = []
|
@State private var statuses: [Status] = []
|
||||||
|
@State private var isDuringRelationshipAction = false
|
||||||
|
|
||||||
private static let initialColumns = 1
|
private static let initialColumns = 1
|
||||||
@State private var gridColumns = Array(repeating: GridItem(.flexible()), count: initialColumns)
|
@State private var gridColumns = Array(repeating: GridItem(.flexible()), count: initialColumns)
|
||||||
|
@ -24,17 +26,7 @@ struct UserProfileView: View {
|
||||||
if let account = self.account {
|
if let account = self.account {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
AsyncImage(url: account.avatar) { image in
|
UserAvatar(accountAvatar: account.avatar, width: 96, height: 96)
|
||||||
image
|
|
||||||
.resizable()
|
|
||||||
.clipShape(Circle())
|
|
||||||
.aspectRatio(contentMode: .fit)
|
|
||||||
} placeholder: {
|
|
||||||
Image(systemName: "person.circle")
|
|
||||||
.resizable()
|
|
||||||
.foregroundColor(Color.mainTextColor)
|
|
||||||
}
|
|
||||||
.frame(width: 96.0, height: 96.0)
|
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
@ -58,7 +50,7 @@ struct UserProfileView: View {
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.opacity(0.6)
|
.opacity(0.6)
|
||||||
}
|
}
|
||||||
}.foregroundColor(Color.mainTextColor)
|
}.foregroundColor(.mainTextColor)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
@ -72,17 +64,17 @@ struct UserProfileView: View {
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.opacity(0.6)
|
.opacity(0.6)
|
||||||
}
|
}
|
||||||
}.foregroundColor(Color.mainTextColor)
|
}.foregroundColor(.mainTextColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
HStack (alignment: .center) {
|
HStack (alignment: .center) {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
Text(account.displayName ?? account.username)
|
Text(account.displayName ?? account.acct)
|
||||||
.foregroundColor(Color.mainTextColor)
|
.foregroundColor(.mainTextColor)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.fontWeight(.bold)
|
.fontWeight(.bold)
|
||||||
Text("@\(account.username)")
|
Text("@\(account.acct)")
|
||||||
.foregroundColor(Color.lightGrayColor)
|
.foregroundColor(.lightGrayColor)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,12 +83,17 @@ struct UserProfileView: View {
|
||||||
if self.applicationState.accountData?.id != self.accountId {
|
if self.applicationState.accountData?.id != self.accountId {
|
||||||
Button {
|
Button {
|
||||||
Task {
|
Task {
|
||||||
|
Task { @MainActor in
|
||||||
|
self.isDuringRelationshipAction = false
|
||||||
|
}
|
||||||
|
|
||||||
|
HapticService.shared.touch()
|
||||||
|
self.isDuringRelationshipAction = true
|
||||||
do {
|
do {
|
||||||
if let relationship = try await AccountService.shared.follow(
|
if let relationship = try await AccountService.shared.follow(
|
||||||
forAccountId: self.accountId,
|
forAccountId: self.accountId,
|
||||||
andContext: self.applicationState.accountData
|
andContext: self.applicationState.accountData
|
||||||
) {
|
) {
|
||||||
UserFeedbackService.shared.send()
|
|
||||||
self.relationship = relationship
|
self.relationship = relationship
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
@ -104,13 +101,18 @@ struct UserProfileView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
HStack {
|
if isDuringRelationshipAction {
|
||||||
Image(systemName: relationship?.following == true ? "person.badge.minus" : "person.badge.plus")
|
LoadingIndicator()
|
||||||
Text(relationship?.following == true ? "Unfollow" : (relationship?.followedBy == true ? "Follow back" : "Follow"))
|
} else {
|
||||||
|
HStack {
|
||||||
|
Image(systemName: relationship?.following == true ? "person.badge.minus" : "person.badge.plus")
|
||||||
|
Text(relationship?.following == true ? "Unfollow" : (relationship?.followedBy == true ? "Follow back" : "Follow"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.disabled(isDuringRelationshipAction)
|
||||||
.buttonStyle(.borderedProminent)
|
.buttonStyle(.borderedProminent)
|
||||||
.tint(relationship?.following == true ? Color.dangerColor : .accentColor)
|
.tint(relationship?.following == true ? .dangerColor : .accentColor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,7 +123,7 @@ struct UserProfileView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
Text("Joined \(account.createdAt.toRelative(.isoDateTimeMilliSec))")
|
Text("Joined \(account.createdAt.toRelative(.isoDateTimeMilliSec))")
|
||||||
.foregroundColor(Color.lightGrayColor.opacity(0.5))
|
.foregroundColor(.lightGrayColor.opacity(0.5))
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -129,7 +131,7 @@ struct UserProfileView: View {
|
||||||
|
|
||||||
LazyVGrid(columns: gridColumns) {
|
LazyVGrid(columns: gridColumns) {
|
||||||
ForEach(self.statuses, id: \.id) { item in
|
ForEach(self.statuses, id: \.id) { item in
|
||||||
NavigationLink(destination: DetailsView(statusId: item.id)
|
NavigationLink(destination: StatusView(statusId: item.id)
|
||||||
.environmentObject(applicationState)) {
|
.environmentObject(applicationState)) {
|
||||||
ImageRowAsync(attachments: item.mediaAttachments)
|
ImageRowAsync(attachments: item.mediaAttachments)
|
||||||
}
|
}
|
||||||
|
@ -137,8 +139,7 @@ struct UserProfileView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
ProgressView()
|
LoadingIndicator()
|
||||||
.progressViewStyle(CircularProgressViewStyle())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationBarTitle(self.accountDisplayName ?? self.accountUserName)
|
.navigationBarTitle(self.accountDisplayName ?? self.accountUserName)
|
||||||
|
@ -147,7 +148,8 @@ struct UserProfileView: View {
|
||||||
do {
|
do {
|
||||||
async let relationshipTask = AccountService.shared.getRelationship(withId: self.accountId, forUser: self.applicationState.accountData)
|
async let relationshipTask = AccountService.shared.getRelationship(withId: self.accountId, forUser: self.applicationState.accountData)
|
||||||
async let accountTask = AccountService.shared.getAccount(withId: self.accountId, and: self.applicationState.accountData)
|
async let accountTask = AccountService.shared.getAccount(withId: self.accountId, and: self.applicationState.accountData)
|
||||||
|
|
||||||
|
// Wait for download account and relationships.
|
||||||
(self.relationship, self.account) = try await (relationshipTask, accountTask)
|
(self.relationship, self.account) = try await (relationshipTask, accountTask)
|
||||||
|
|
||||||
self.statuses = try await AccountService.shared.getStatuses(forAccountId: self.accountId, andContext: self.applicationState.accountData)
|
self.statuses = try await AccountService.shared.getStatuses(forAccountId: self.accountId, andContext: self.applicationState.accountData)
|
||||||
|
|
|
@ -34,7 +34,7 @@ struct CommentsSection: View {
|
||||||
NavigationLink(destination: UserProfileView(
|
NavigationLink(destination: UserProfileView(
|
||||||
accountId: account.id,
|
accountId: account.id,
|
||||||
accountDisplayName: account.displayName,
|
accountDisplayName: account.displayName,
|
||||||
accountUserName: account.username)
|
accountUserName: account.acct)
|
||||||
.environmentObject(applicationState)) {
|
.environmentObject(applicationState)) {
|
||||||
AsyncImage(url: account.avatar) { image in
|
AsyncImage(url: account.avatar) { image in
|
||||||
image
|
image
|
||||||
|
@ -44,7 +44,7 @@ struct CommentsSection: View {
|
||||||
} placeholder: {
|
} placeholder: {
|
||||||
Image(systemName: "person.circle")
|
Image(systemName: "person.circle")
|
||||||
.resizable()
|
.resizable()
|
||||||
.foregroundColor(Color.mainTextColor)
|
.foregroundColor(.mainTextColor)
|
||||||
}
|
}
|
||||||
.frame(width: 32.0, height: 32.0)
|
.frame(width: 32.0, height: 32.0)
|
||||||
}
|
}
|
||||||
|
@ -52,23 +52,20 @@ 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?.acct ?? "")
|
||||||
.foregroundColor(Color.mainTextColor)
|
.foregroundColor(.mainTextColor)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.fontWeight(.bold)
|
.fontWeight(.bold)
|
||||||
Text("@\(status.account?.username ?? "")")
|
|
||||||
.foregroundColor(Color.lightGrayColor)
|
|
||||||
.font(.footnote)
|
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Text(status.createdAt.toRelative(.isoDateTimeMilliSec))
|
Text(status.createdAt.toRelative(.isoDateTimeMilliSec))
|
||||||
.foregroundColor(Color.lightGrayColor.opacity(0.5))
|
.foregroundColor(.lightGrayColor.opacity(0.5))
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
}
|
}
|
||||||
|
|
||||||
HTMLFormattedText(status.content, withFontSize: 14, andWidth: contentWidth)
|
HTMLFormattedText(status.content, withFontSize: 14, andWidth: contentWidth)
|
||||||
.padding(.top, -10)
|
.padding(.top, -16)
|
||||||
.padding(.leading, -4)
|
.padding(.leading, -4)
|
||||||
|
|
||||||
if status.mediaAttachments.count > 0 {
|
if status.mediaAttachments.count > 0 {
|
||||||
|
@ -81,14 +78,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: .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(.mainTextColor)
|
||||||
.opacity(0.05)
|
.opacity(0.05)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,12 +9,12 @@ import SwiftUI
|
||||||
struct InteractionRow: View {
|
struct InteractionRow: View {
|
||||||
@EnvironmentObject var applicationState: ApplicationState
|
@EnvironmentObject var applicationState: ApplicationState
|
||||||
@ObservedObject public var statusData: StatusData
|
@ObservedObject public var statusData: StatusData
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack (alignment: .top) {
|
HStack (alignment: .top) {
|
||||||
Button {
|
Button {
|
||||||
// TODO: Reply.
|
// TODO: Reply.
|
||||||
UserFeedbackService.shared.send()
|
HapticService.shared.touch()
|
||||||
} label: {
|
} label: {
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
Image(systemName: "message")
|
Image(systemName: "message")
|
||||||
|
@ -27,6 +27,8 @@ struct InteractionRow: View {
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
Task {
|
Task {
|
||||||
|
HapticService.shared.touch()
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let status = self.statusData.reblogged
|
let status = self.statusData.reblogged
|
||||||
? try await StatusService.shared.unboost(statusId: self.statusData.id, accountData: self.applicationState.accountData)
|
? try await StatusService.shared.unboost(statusId: self.statusData.id, accountData: self.applicationState.accountData)
|
||||||
|
@ -38,7 +40,6 @@ struct InteractionRow: View {
|
||||||
: Int32(status.reblogsCount)
|
: Int32(status.reblogsCount)
|
||||||
|
|
||||||
self.statusData.reblogged = status.reblogged
|
self.statusData.reblogged = status.reblogged
|
||||||
UserFeedbackService.shared.send()
|
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
print("Error \(error.localizedDescription)")
|
print("Error \(error.localizedDescription)")
|
||||||
|
@ -56,6 +57,8 @@ struct InteractionRow: View {
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
Task {
|
Task {
|
||||||
|
HapticService.shared.touch()
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let status = self.statusData.favourited
|
let status = self.statusData.favourited
|
||||||
? try await StatusService.shared.unfavourite(statusId: self.statusData.id, accountData: self.applicationState.accountData)
|
? try await StatusService.shared.unfavourite(statusId: self.statusData.id, accountData: self.applicationState.accountData)
|
||||||
|
@ -67,7 +70,6 @@ struct InteractionRow: View {
|
||||||
: Int32(status.favouritesCount)
|
: Int32(status.favouritesCount)
|
||||||
|
|
||||||
self.statusData.favourited = status.favourited
|
self.statusData.favourited = status.favourited
|
||||||
UserFeedbackService.shared.send()
|
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
print("Error \(error.localizedDescription)")
|
print("Error \(error.localizedDescription)")
|
||||||
|
@ -85,13 +87,14 @@ struct InteractionRow: View {
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
Task {
|
Task {
|
||||||
|
HapticService.shared.touch()
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let status = self.statusData.bookmarked
|
_ = self.statusData.bookmarked
|
||||||
? try await StatusService.shared.unbookmark(statusId: self.statusData.id, accountData: self.applicationState.accountData)
|
? try await StatusService.shared.unbookmark(statusId: self.statusData.id, accountData: self.applicationState.accountData)
|
||||||
: try await StatusService.shared.bookmark(statusId: self.statusData.id, accountData: self.applicationState.accountData)
|
: try await StatusService.shared.bookmark(statusId: self.statusData.id, accountData: self.applicationState.accountData)
|
||||||
|
|
||||||
self.statusData.bookmarked.toggle()
|
self.statusData.bookmarked.toggle()
|
||||||
UserFeedbackService.shared.send()
|
|
||||||
} catch {
|
} catch {
|
||||||
print("Error \(error.localizedDescription)")
|
print("Error \(error.localizedDescription)")
|
||||||
}
|
}
|
||||||
|
@ -104,14 +107,14 @@ struct InteractionRow: View {
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
// TODO: Share.
|
// TODO: Share.
|
||||||
UserFeedbackService.shared.send()
|
HapticService.shared.touch()
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "square.and.arrow.up")
|
Image(systemName: "square.and.arrow.up")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.font(.title3)
|
.font(.title3)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
.foregroundColor(Color.accentColor)
|
.foregroundColor(.accentColor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
//
|
||||||
|
// https://mczachurski.dev
|
||||||
|
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct LoadingIndicator: View {
|
||||||
|
var body: some View {
|
||||||
|
ProgressView()
|
||||||
|
.progressViewStyle(CircularProgressViewStyle())
|
||||||
|
.tint(.mainTextColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LoadingIndicator_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
LoadingIndicator()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
//
|
||||||
|
// https://mczachurski.dev
|
||||||
|
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct UserAvatar: View {
|
||||||
|
@State public var accountAvatar: URL?
|
||||||
|
@State public var cachedAvatar: UIImage?
|
||||||
|
@State public var width = 48.0
|
||||||
|
@State public var height = 48.0
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
if let cachedAvatar {
|
||||||
|
Image(uiImage: cachedAvatar)
|
||||||
|
.resizable()
|
||||||
|
.clipShape(Circle())
|
||||||
|
.aspectRatio(contentMode: .fit)
|
||||||
|
.frame(width: self.width, height: self.height)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
AsyncImage(url: self.accountAvatar) { image in
|
||||||
|
image
|
||||||
|
.resizable()
|
||||||
|
.clipShape(Circle())
|
||||||
|
.aspectRatio(contentMode: .fit)
|
||||||
|
} placeholder: {
|
||||||
|
Image(systemName: "person.circle")
|
||||||
|
.resizable()
|
||||||
|
.clipShape(Circle())
|
||||||
|
.aspectRatio(contentMode: .fit)
|
||||||
|
.foregroundColor(.mainTextColor)
|
||||||
|
}
|
||||||
|
.frame(width: self.width, height: self.height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UserAvatar_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
UserAvatar()
|
||||||
|
.previewLayout(.fixed(width: 128, height: 128))
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,34 +15,16 @@ struct UsernameRow: View {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack (alignment: .center) {
|
HStack (alignment: .center) {
|
||||||
if let cachedAvatar {
|
UserAvatar(accountAvatar: accountAvatar,
|
||||||
Image(uiImage: cachedAvatar)
|
cachedAvatar: cachedAvatar,
|
||||||
.resizable()
|
width: 48,
|
||||||
.clipShape(Circle())
|
height: 48)
|
||||||
.aspectRatio(contentMode: .fit)
|
|
||||||
.frame(width: 48.0, height: 48.0)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
AsyncImage(url: accountAvatar) { image in
|
|
||||||
image
|
|
||||||
.resizable()
|
|
||||||
.clipShape(Circle())
|
|
||||||
.aspectRatio(contentMode: .fit)
|
|
||||||
} placeholder: {
|
|
||||||
Image(systemName: "person.circle")
|
|
||||||
.resizable()
|
|
||||||
.clipShape(Circle())
|
|
||||||
.aspectRatio(contentMode: .fit)
|
|
||||||
.foregroundColor(Color.mainTextColor)
|
|
||||||
}
|
|
||||||
.frame(width: 48.0, height: 48.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
VStack (alignment: .leading) {
|
VStack (alignment: .leading) {
|
||||||
Text(accountDisplayName ?? accountUsername)
|
Text(accountDisplayName ?? accountUsername)
|
||||||
.foregroundColor(Color.mainTextColor)
|
.foregroundColor(.mainTextColor)
|
||||||
Text("@\(accountUsername)")
|
Text("@\(accountUsername)")
|
||||||
.foregroundColor(Color.lightGrayColor)
|
.foregroundColor(.lightGrayColor)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
}
|
}
|
||||||
.padding(.leading, 8)
|
.padding(.leading, 8)
|
||||||
|
@ -52,6 +34,7 @@ struct UsernameRow: View {
|
||||||
|
|
||||||
struct UsernameRow_Previews: PreviewProvider {
|
struct UsernameRow_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
UsernameRow(accountUsername: "")
|
UsernameRow(accountDisplayName: "John Doe", accountUsername: "johndoe@mastodon.xx")
|
||||||
|
.previewLayout(.fixed(width: 320, height: 64))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue