summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Juick.xcodeproj/project.pbxproj165
-rw-r--r--Juick/AppDelegate.swift125
-rw-r--r--Juick/Helpers/Data+Hex.swift16
-rw-r--r--Juick/Helpers/LoadableState.swift25
-rw-r--r--Juick/Helpers/NSAttributedString_Entities.h19
-rw-r--r--Juick/Helpers/NSAttributedString_Entities.m90
-rw-r--r--Juick/ImageFetcher.swift28
-rw-r--r--Juick/Main.storyboard16
-rw-r--r--Juick/MessageFetcher.swift46
-rw-r--r--Juick/Model/Attachment.m29
-rw-r--r--Juick/Model/Message.h12
-rw-r--r--Juick/Model/Message.m2
-rw-r--r--Juick/Model/User.h1
-rw-r--r--Juick/SceneDelegate.swift63
-rw-r--r--Juick/Supporting Files/Juick-Bridging-Header.h3
-rw-r--r--Juick/Supporting Files/Juick-Info.plist17
-rw-r--r--Juick/Supporting Files/main.m16
-rw-r--r--Juick/Views/ActivityIndicator.swift28
-rw-r--r--Juick/Views/AttributedLabelView.swift107
-rw-r--r--Juick/Views/ContentView.swift47
-rw-r--r--Juick/Views/FeedView.swift66
-rw-r--r--Juick/Views/LoadableImageView.swift35
-rw-r--r--Juick/Views/MessageView.swift51
23 files changed, 939 insertions, 68 deletions
diff --git a/Juick.xcodeproj/project.pbxproj b/Juick.xcodeproj/project.pbxproj
index 8ab2162..619ef46 100644
--- a/Juick.xcodeproj/project.pbxproj
+++ b/Juick.xcodeproj/project.pbxproj
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 46;
+ objectVersion = 52;
objects = {
/* Begin PBXBuildFile section */
@@ -14,7 +14,6 @@
77317BAE181BBE8500D60005 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 77317BAD181BBE8500D60005 /* CoreGraphics.framework */; };
77317BB0181BBE8500D60005 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 77317BAF181BBE8500D60005 /* UIKit.framework */; };
77317BB6181BBE8500D60005 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 77317BB4181BBE8500D60005 /* InfoPlist.strings */; };
- 77317BB8181BBE8500D60005 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 77317BB7181BBE8500D60005 /* main.m */; };
77317BBC181BBE8500D60005 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 77317BBB181BBE8500D60005 /* AppDelegate.m */; };
77317BC2181BBE8500D60005 /* MessagesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 77317BC1181BBE8500D60005 /* MessagesViewController.m */; };
77317BC7181BBE8500D60005 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 77317BC6181BBE8500D60005 /* Images.xcassets */; };
@@ -36,6 +35,8 @@
774746AD239F82A10001C7F9 /* NSDate+TimeAgo.m in Sources */ = {isa = PBXBuildFile; fileRef = 774746AC239F82A10001C7F9 /* NSDate+TimeAgo.m */; };
774746B6239F872A0001C7F9 /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 774746B5239F872A0001C7F9 /* CoreServices.framework */; };
774C98CD25126C070073C70A /* Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = 774C98CC25126C070073C70A /* Service.swift */; };
+ 774E6B52251AB5A4006B5D5F /* AttributedLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 774E6B51251AB5A4006B5D5F /* AttributedLabelView.swift */; };
+ 774E6B64251AC4D2006B5D5F /* NSAttributedString_Entities.m in Sources */ = {isa = PBXBuildFile; fileRef = 774E6B57251AB743006B5D5F /* NSAttributedString_Entities.m */; };
7761133821766A3000D350CD /* ContentLoadingCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 7761133621766A3000D350CD /* ContentLoadingCell.m */; };
7761133921766A3000D350CD /* ContentLoadingCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7761133721766A3000D350CD /* ContentLoadingCell.xib */; };
7761135D21790B0300D350CD /* JuickPush.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 7761135521790B0200D350CD /* JuickPush.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
@@ -60,9 +61,21 @@
77C67EEC18283F2D00427098 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 77C67EEB18283F2D00427098 /* QuartzCore.framework */; };
77C6ADDE1F770EB2000AEA8C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 77C6ADDD1F770EB2000AEA8C /* Main.storyboard */; };
77C6ADE41F7717BC000AEA8C /* ThreadViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 77C6ADE31F7717BC000AEA8C /* ThreadViewController.m */; };
+ 77CEB65825129F550055FF30 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77CEB65425129F550055FF30 /* SceneDelegate.swift */; };
+ 77CEB65925129F550055FF30 /* ImageFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77CEB65525129F550055FF30 /* ImageFetcher.swift */; };
+ 77CEB65A25129F550055FF30 /* MessageFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77CEB65625129F550055FF30 /* MessageFetcher.swift */; };
+ 77CEB65B25129F550055FF30 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77CEB65725129F550055FF30 /* AppDelegate.swift */; };
+ 77CEB66425129F7E0055FF30 /* MessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77CEB65F25129F7E0055FF30 /* MessageView.swift */; };
+ 77CEB66525129F7E0055FF30 /* ActivityIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77CEB66025129F7E0055FF30 /* ActivityIndicator.swift */; };
+ 77CEB66625129F7E0055FF30 /* LoadableImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77CEB66125129F7E0055FF30 /* LoadableImageView.swift */; };
+ 77CEB66725129F7E0055FF30 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77CEB66225129F7E0055FF30 /* ContentView.swift */; };
+ 77CEB66825129F7E0055FF30 /* FeedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77CEB66325129F7E0055FF30 /* FeedView.swift */; };
+ 77CEB66D25129F980055FF30 /* LoadableState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77CEB66C25129F980055FF30 /* LoadableState.swift */; };
+ 77CEB6802512A8BF0055FF30 /* Data+Hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77CEB67F2512A8BF0055FF30 /* Data+Hex.swift */; };
77E35A82189A5B5A00B2D216 /* LoginViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 77E35A81189A5B5A00B2D216 /* LoginViewController.m */; };
77E61A5B1FD467FC00B4E304 /* QuoteView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 77E61A5A1FD467FC00B4E304 /* QuoteView.xib */; };
77E61A5E1FD4682B00B4E304 /* QuoteView.m in Sources */ = {isa = PBXBuildFile; fileRef = 77E61A5D1FD4682B00B4E304 /* QuoteView.m */; };
+ 77E7E73A252CE95200957295 /* Atributika in Frameworks */ = {isa = PBXBuildFile; productRef = 77E7E739252CE95200957295 /* Atributika */; };
77F2B6A2251249F300E42F6F /* JuickTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77F2B6A1251249F300E42F6F /* JuickTests.swift */; };
77FCADDF1D6A50DA00CBA649 /* API.m in Sources */ = {isa = PBXBuildFile; fileRef = 77FCADDE1D6A50DA00CBA649 /* API.m */; };
/* End PBXBuildFile section */
@@ -112,7 +125,6 @@
77317BAF181BBE8500D60005 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
77317BB3181BBE8500D60005 /* Juick-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Juick-Info.plist"; sourceTree = "<group>"; };
77317BB5181BBE8500D60005 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
- 77317BB7181BBE8500D60005 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
77317BB9181BBE8500D60005 /* Juick-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Juick-Prefix.pch"; sourceTree = "<group>"; };
77317BBA181BBE8500D60005 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
77317BBB181BBE8500D60005 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
@@ -149,6 +161,9 @@
774746B5239F872A0001C7F9 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/System/Library/Frameworks/CoreServices.framework; sourceTree = DEVELOPER_DIR; };
774C98C0251263720073C70A /* Juick-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Juick-Bridging-Header.h"; sourceTree = "<group>"; };
774C98CC25126C070073C70A /* Service.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Service.swift; sourceTree = "<group>"; };
+ 774E6B51251AB5A4006B5D5F /* AttributedLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedLabelView.swift; sourceTree = "<group>"; };
+ 774E6B56251AB70B006B5D5F /* NSAttributedString_Entities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NSAttributedString_Entities.h; sourceTree = "<group>"; };
+ 774E6B57251AB743006B5D5F /* NSAttributedString_Entities.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NSAttributedString_Entities.m; sourceTree = "<group>"; };
7761133521766A3000D350CD /* ContentLoadingCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ContentLoadingCell.h; sourceTree = "<group>"; };
7761133621766A3000D350CD /* ContentLoadingCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ContentLoadingCell.m; sourceTree = "<group>"; };
7761133721766A3000D350CD /* ContentLoadingCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ContentLoadingCell.xib; sourceTree = "<group>"; };
@@ -191,6 +206,18 @@
77C6ADDD1F770EB2000AEA8C /* Main.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = "<group>"; };
77C6ADE21F7717BC000AEA8C /* ThreadViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ThreadViewController.h; sourceTree = "<group>"; };
77C6ADE31F7717BC000AEA8C /* ThreadViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ThreadViewController.m; sourceTree = "<group>"; };
+ 77CEB65425129F550055FF30 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
+ 77CEB65525129F550055FF30 /* ImageFetcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageFetcher.swift; sourceTree = "<group>"; };
+ 77CEB65625129F550055FF30 /* MessageFetcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageFetcher.swift; sourceTree = "<group>"; };
+ 77CEB65725129F550055FF30 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
+ 77CEB65F25129F7E0055FF30 /* MessageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageView.swift; sourceTree = "<group>"; };
+ 77CEB66025129F7E0055FF30 /* ActivityIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityIndicator.swift; sourceTree = "<group>"; };
+ 77CEB66125129F7E0055FF30 /* LoadableImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadableImageView.swift; sourceTree = "<group>"; };
+ 77CEB66225129F7E0055FF30 /* ContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
+ 77CEB66325129F7E0055FF30 /* FeedView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedView.swift; sourceTree = "<group>"; };
+ 77CEB66C25129F980055FF30 /* LoadableState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadableState.swift; sourceTree = "<group>"; };
+ 77CEB67F2512A8BF0055FF30 /* Data+Hex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Hex.swift"; sourceTree = "<group>"; };
+ 77CEB6872512AB680055FF30 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/System/iOSSupport/System/Library/Frameworks/SwiftUI.framework; sourceTree = DEVELOPER_DIR; };
77D40AB8218B5BD60074E14F /* Local.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Local.xcconfig; sourceTree = "<group>"; };
77D40ABB218B5CC90074E14F /* Production.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Production.xcconfig; sourceTree = "<group>"; };
77E35A80189A5B5A00B2D216 /* LoginViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LoginViewController.h; sourceTree = "<group>"; };
@@ -218,6 +245,7 @@
77317BB0181BBE8500D60005 /* UIKit.framework in Frameworks */,
77317BAC181BBE8500D60005 /* Foundation.framework in Frameworks */,
774746B6239F872A0001C7F9 /* CoreServices.framework in Frameworks */,
+ 77E7E73A252CE95200957295 /* Atributika in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -263,6 +291,7 @@
77317BAA181BBE8500D60005 /* Frameworks */ = {
isa = PBXGroup;
children = (
+ 77CEB6872512AB680055FF30 /* SwiftUI.framework */,
774746B5239F872A0001C7F9 /* CoreServices.framework */,
774528CB1F93EE9F004D110B /* libz.tbd */,
774528C91F93EE83004D110B /* AssetsLibrary.framework */,
@@ -283,23 +312,27 @@
isa = PBXGroup;
children = (
774528CD1F96B39C004D110B /* Juick.entitlements */,
+ 77FCADE01D6A50EC00CBA649 /* API.h */,
+ 77317BBA181BBE8500D60005 /* AppDelegate.h */,
+ 77FCADDE1D6A50DA00CBA649 /* API.m */,
+ 77317BBB181BBE8500D60005 /* AppDelegate.m */,
773F23341F76ED5D00B5B0DF /* Splash.png */,
773F23351F76ED5D00B5B0DF /* Splash@2x.png */,
773F23361F76ED5E00B5B0DF /* Splash@3x.png */,
773F23321F76ECAC00B5B0DF /* LaunchScreen.storyboard */,
+ 77C6ADDD1F770EB2000AEA8C /* Main.storyboard */,
+ 77CEB65725129F550055FF30 /* AppDelegate.swift */,
+ 77CEB65525129F550055FF30 /* ImageFetcher.swift */,
+ 77CEB65625129F550055FF30 /* MessageFetcher.swift */,
+ 77CEB65425129F550055FF30 /* SceneDelegate.swift */,
+ 774C98CC25126C070073C70A /* Service.swift */,
+ 77C364912243FAEF0017522C /* Colors.xcassets */,
+ 77317BC6181BBE8500D60005 /* Images.xcassets */,
774DD4601D735E1D00C7F290 /* Helpers */,
774DD45D1D735DDD00C7F290 /* Model */,
77317BB2181BBE8500D60005 /* Supporting Files */,
774DD45F1D735E0300C7F290 /* ViewControllers */,
77FFC0151D5FD13C003BD81A /* Views */,
- 77FCADE01D6A50EC00CBA649 /* API.h */,
- 77FCADDE1D6A50DA00CBA649 /* API.m */,
- 77317BBA181BBE8500D60005 /* AppDelegate.h */,
- 77317BBB181BBE8500D60005 /* AppDelegate.m */,
- 77317BC6181BBE8500D60005 /* Images.xcassets */,
- 77C6ADDD1F770EB2000AEA8C /* Main.storyboard */,
- 77C364912243FAEF0017522C /* Colors.xcassets */,
- 774C98CC25126C070073C70A /* Service.swift */,
);
path = Juick;
sourceTree = "<group>";
@@ -312,7 +345,6 @@
77317BB4181BBE8500D60005 /* InfoPlist.strings */,
77317BB3181BBE8500D60005 /* Juick-Info.plist */,
77317BB9181BBE8500D60005 /* Juick-Prefix.pch */,
- 77317BB7181BBE8500D60005 /* main.m */,
77D40AB8218B5BD60074E14F /* Local.xcconfig */,
77D40ABB218B5CC90074E14F /* Production.xcconfig */,
);
@@ -379,6 +411,7 @@
774DD4601D735E1D00C7F290 /* Helpers */ = {
isa = PBXGroup;
children = (
+ 77CEB66C25129F980055FF30 /* LoadableState.swift */,
77C67EE31828342000427098 /* NSURL+PathParameters.h */,
77C67EE41828342000427098 /* NSURL+PathParameters.m */,
77B09992189D0B9900A84F59 /* UIImage+Utils.h */,
@@ -387,6 +420,9 @@
7785605F2343D24E00BB37A2 /* NSData+Hex.m */,
774746AB239F82A10001C7F9 /* NSDate+TimeAgo.h */,
774746AC239F82A10001C7F9 /* NSDate+TimeAgo.m */,
+ 77CEB67F2512A8BF0055FF30 /* Data+Hex.swift */,
+ 774E6B56251AB70B006B5D5F /* NSAttributedString_Entities.h */,
+ 774E6B57251AB743006B5D5F /* NSAttributedString_Entities.m */,
);
path = Helpers;
sourceTree = "<group>";
@@ -414,6 +450,11 @@
77FFC0151D5FD13C003BD81A /* Views */ = {
isa = PBXGroup;
children = (
+ 77CEB66025129F7E0055FF30 /* ActivityIndicator.swift */,
+ 77CEB66225129F7E0055FF30 /* ContentView.swift */,
+ 77CEB66325129F7E0055FF30 /* FeedView.swift */,
+ 77CEB66125129F7E0055FF30 /* LoadableImageView.swift */,
+ 77CEB65F25129F7E0055FF30 /* MessageView.swift */,
773E63A1204BE036008B8F8D /* BubbleMessageCell.h */,
773E63A2204BE036008B8F8D /* BubbleMessageCell.m */,
773E6391204BCAD6008B8F8D /* BubbleMessageCell.xib */,
@@ -432,6 +473,7 @@
7761133521766A3000D350CD /* ContentLoadingCell.h */,
7761133621766A3000D350CD /* ContentLoadingCell.m */,
7761133721766A3000D350CD /* ContentLoadingCell.xib */,
+ 774E6B51251AB5A4006B5D5F /* AttributedLabelView.swift */,
);
path = Views;
sourceTree = "<group>";
@@ -454,6 +496,9 @@
7761135C21790B0300D350CD /* PBXTargetDependency */,
);
name = Juick;
+ packageProductDependencies = (
+ 77E7E739252CE95200957295 /* Atributika */,
+ );
productName = Juick;
productReference = 77317BA8181BBE8500D60005 /* Juick.app */;
productType = "com.apple.product-type.application";
@@ -541,6 +586,9 @@
Base,
);
mainGroup = 77317B9F181BBE8500D60005;
+ packageReferences = (
+ 77E7E738252CE95200957295 /* XCRemoteSwiftPackageReference "Atributika" */,
+ );
productRefGroup = 77317BA9181BBE8500D60005 /* Products */;
projectDirPath = "";
projectRoot = "";
@@ -596,9 +644,15 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 774E6B64251AC4D2006B5D5F /* NSAttributedString_Entities.m in Sources */,
77C36498224417FC0017522C /* DiscussionsController.m in Sources */,
+ 77CEB65A25129F550055FF30 /* MessageFetcher.swift in Sources */,
+ 77CEB66825129F7E0055FF30 /* FeedView.swift in Sources */,
+ 77CEB66525129F7E0055FF30 /* ActivityIndicator.swift in Sources */,
770C86E325117D78009B6404 /* UIView+Shimmer.m in Sources */,
+ 77CEB66D25129F980055FF30 /* LoadableState.swift in Sources */,
7761133821766A3000D350CD /* ContentLoadingCell.m in Sources */,
+ 77CEB65925129F550055FF30 /* ImageFetcher.swift in Sources */,
77E61A5E1FD4682B00B4E304 /* QuoteView.m in Sources */,
77317BBC181BBE8500D60005 /* AppDelegate.m in Sources */,
776C41C11FD3FF6E0063B82E /* FeedViewController.m in Sources */,
@@ -606,8 +660,12 @@
77A0954A181F1F25002BDECD /* Message.m in Sources */,
773E63A0204BDF16008B8F8D /* ChatViewController.m in Sources */,
773E639D204BD0F2008B8F8D /* Chat.m in Sources */,
+ 77CEB65B25129F550055FF30 /* AppDelegate.swift in Sources */,
+ 77CEB66425129F7E0055FF30 /* MessageView.swift in Sources */,
77B8DCD62093FC03000DBB04 /* BlogViewController.m in Sources */,
773E639A204BCE6D008B8F8D /* DialogsViewController.m in Sources */,
+ 774E6B52251AB5A4006B5D5F /* AttributedLabelView.swift in Sources */,
+ 77CEB6802512A8BF0055FF30 /* Data+Hex.swift in Sources */,
772B4E6C2199811E0029706E /* Entity.m in Sources */,
77B09994189D0B9900A84F59 /* UIImage+Utils.m in Sources */,
77317BC2181BBE8500D60005 /* MessagesViewController.m in Sources */,
@@ -617,14 +675,16 @@
77975A1D182B6E9A00410C2B /* NewPostViewController.m in Sources */,
774746AD239F82A10001C7F9 /* NSDate+TimeAgo.m in Sources */,
77C36495224417E90017522C /* DiscoverViewController.m in Sources */,
+ 77CEB66725129F7E0055FF30 /* ContentView.swift in Sources */,
77C67EE51828342000427098 /* NSURL+PathParameters.m in Sources */,
77E35A82189A5B5A00B2D216 /* LoginViewController.m in Sources */,
773E6397204BCB64008B8F8D /* ConversationCell.m in Sources */,
778560602343D24E00BB37A2 /* NSData+Hex.m in Sources */,
774C98CD25126C070073C70A /* Service.swift in Sources */,
+ 77CEB66625129F7E0055FF30 /* LoadableImageView.swift in Sources */,
776C41BD1FD3EF180063B82E /* MessageCell.m in Sources */,
- 77317BB8181BBE8500D60005 /* main.m in Sources */,
77B8B39C207A5629005CB20C /* MessageInputView.m in Sources */,
+ 77CEB65825129F550055FF30 /* SceneDelegate.swift in Sources */,
77C3648C2241B3060017522C /* DeviceRegistration.m in Sources */,
774528C21F930C06004D110B /* Attachment.m in Sources */,
778560632344CF6F00BB37A2 /* JuickNavigationController.m in Sources */,
@@ -726,7 +786,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 14.0;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
@@ -776,7 +836,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 14.0;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
@@ -797,7 +857,10 @@
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "Juick/Supporting Files/Juick-Prefix.pch";
INFOPLIST_FILE = "Juick/Supporting Files/Juick-Info.plist";
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
PRODUCT_BUNDLE_IDENTIFIER = "com.juick.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE = "";
@@ -823,7 +886,10 @@
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "Juick/Supporting Files/Juick-Prefix.pch";
INFOPLIST_FILE = "Juick/Supporting Files/Juick-Info.plist";
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
PRODUCT_BUNDLE_IDENTIFIER = "com.juick.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE = "";
@@ -856,7 +922,11 @@
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
INFOPLIST_FILE = JuickPush/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.juick.Juick.JuickPush;
@@ -889,7 +959,11 @@
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
INFOPLIST_FILE = JuickPush/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.juick.Juick.JuickPush;
@@ -953,7 +1027,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 14.0;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
@@ -974,7 +1048,10 @@
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "Juick/Supporting Files/Juick-Prefix.pch";
INFOPLIST_FILE = "Juick/Supporting Files/Juick-Info.plist";
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
PRODUCT_BUNDLE_IDENTIFIER = "com.juick.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE = "";
@@ -1008,7 +1085,11 @@
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
INFOPLIST_FILE = JuickPush/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.juick.Juick.JuickPush;
@@ -1038,7 +1119,11 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
INFOPLIST_FILE = JuickTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.juick.JuickTests;
@@ -1067,7 +1152,11 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
INFOPLIST_FILE = JuickTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.juick.JuickTests;
@@ -1096,12 +1185,17 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
INFOPLIST_FILE = JuickTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.juick.JuickTests;
PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = Juick;
@@ -1152,6 +1246,25 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
+
+/* Begin XCRemoteSwiftPackageReference section */
+ 77E7E738252CE95200957295 /* XCRemoteSwiftPackageReference "Atributika" */ = {
+ isa = XCRemoteSwiftPackageReference;
+ repositoryURL = "https://github.com/psharanda/Atributika.git";
+ requirement = {
+ kind = upToNextMajorVersion;
+ minimumVersion = 4.9.9;
+ };
+ };
+/* End XCRemoteSwiftPackageReference section */
+
+/* Begin XCSwiftPackageProductDependency section */
+ 77E7E739252CE95200957295 /* Atributika */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 77E7E738252CE95200957295 /* XCRemoteSwiftPackageReference "Atributika" */;
+ productName = Atributika;
+ };
+/* End XCSwiftPackageProductDependency section */
};
rootObject = 77317BA0181BBE8500D60005 /* Project object */;
}
diff --git a/Juick/AppDelegate.swift b/Juick/AppDelegate.swift
new file mode 100644
index 0000000..d1062f4
--- /dev/null
+++ b/Juick/AppDelegate.swift
@@ -0,0 +1,125 @@
+//
+// AppDelegate.swift
+// tst
+//
+// Created by Vitaly Takmazov on 10.12.2019.
+// Copyright © 2019 com.juick. All rights reserved.
+//
+
+import UIKit
+import CoreData
+
+@UIApplicationMain
+class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
+ lazy var api : API = {
+ return API()
+ }()
+ lazy var sharedDateFormatter : DateFormatter = {
+ let dateFormatter = DateFormatter()
+ dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
+ dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
+ return dateFormatter
+ }()
+ func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
+ // cleanup synchronized credentials which are not used anymore
+ let allCreds = URLCredentialStorage.shared.allCredentials
+ for (space, _) in allCreds {
+ if space.host == "api.juick.com" {
+ allCreds[space]?.values.forEach {
+ URLCredentialStorage.shared.remove($0, for: space, options: [NSURLCredentialStorageRemoveSynchronizableCredentials:true])
+ }
+ }
+ }
+ #if !targetEnvironment(simulator)
+ registerForRemoteNotifications()
+ #endif
+ if let userInfo = launchOptions?[UIApplication.LaunchOptionsKey.remoteNotification] {
+ parseNotificationPayload(userInfo: userInfo as! [AnyHashable : Any])
+ }
+ return true
+ }
+
+ func registerForRemoteNotifications() {
+ let center = UNUserNotificationCenter.current()
+ center.delegate = self
+ center.requestAuthorization(options: [.sound, .alert, .badge]) { (granted, error) in
+ if (error == nil) {
+ OperationQueue.main.addOperation {
+ UIApplication.shared.registerForRemoteNotifications()
+ }
+ }
+ }
+ }
+
+ func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
+ let token = deviceToken.hexString
+ OperationQueue().addOperation {
+ let registration = DeviceRegistration()
+ registration.type = "apns"
+ registration.token = token
+ self.api.refreshDeviceRegistration(registration, callback: {
+ (success) in
+ debugPrint("Successfully refreshed registration with \(token)")
+ })
+ }
+ }
+
+ func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
+ debugPrint("APNS error: \(error.localizedDescription)")
+ }
+ func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
+ if userInfo["service"] as? Bool ?? false {
+ if let user = User.fromJSON(userInfo["user"] as? [AnyHashable : Any]) {
+ application.applicationIconBadgeNumber = user.unreadCount
+ }
+ }
+ }
+ // Called when a notification is delivered to a foreground app.
+ func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
+ completionHandler([.sound, .alert, .badge])
+ }
+ //Called to let your app know which action was selected by the user for a given notification.
+ func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
+ let userInfo = response.notification.request.content.userInfo
+ parseNotificationPayload(userInfo: userInfo)
+ OperationQueue.main.addOperation {
+ // TODO: initialize correct tab
+ }
+ }
+
+ var pushedThread : Int?
+ var pushedReplyId : Int?
+ var pushedUname : String?
+
+ func parseNotificationPayload(userInfo:[AnyHashable: Any]) {
+ self.pushedThread = userInfo["mid"] as? Int;
+ self.pushedUname = userInfo["uname"] as? String;
+ self.pushedReplyId = userInfo["rid"] as? Int;
+ }
+
+ func cleanupPushedData() {
+ self.pushedUname = nil;
+ self.pushedThread = nil;
+ self.pushedReplyId = nil;
+ }
+
+ static var shared : AppDelegate {
+ return UIApplication.shared.delegate as! AppDelegate
+ }
+
+ // MARK: UISceneSession Lifecycle
+
+ func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
+ // Called when a new scene session is being created.
+ // Use this method to select a configuration to create the new scene with.
+ return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
+ }
+
+ func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
+ // Called when the user discards a scene session.
+ // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
+ // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
+ }
+
+}
+
diff --git a/Juick/Helpers/Data+Hex.swift b/Juick/Helpers/Data+Hex.swift
new file mode 100644
index 0000000..69c170d
--- /dev/null
+++ b/Juick/Helpers/Data+Hex.swift
@@ -0,0 +1,16 @@
+//
+// Data+Hex.swift
+// Juick
+//
+// Created by Vitaly Takmazov on 16.09.2020.
+// Copyright © 2020 com.juick. All rights reserved.
+//
+
+import Foundation
+
+extension Data {
+ var hexString: String {
+ let hexString = map { String(format: "%02.2hhx", $0) }.joined()
+ return hexString
+ }
+}
diff --git a/Juick/Helpers/LoadableState.swift b/Juick/Helpers/LoadableState.swift
new file mode 100644
index 0000000..a45edb2
--- /dev/null
+++ b/Juick/Helpers/LoadableState.swift
@@ -0,0 +1,25 @@
+//
+// LoadableState.swift
+// tst
+//
+// Created by Vitaly Takmazov on 10.12.2019.
+// Copyright © 2019 com.juick. All rights reserved.
+//
+
+import Foundation
+
+enum LoadableState<T> {
+ case loading
+ case fetched(Result<T, FetchError>)
+}
+
+enum FetchError: Error {
+ case error(String)
+
+ var localizedDescription: String {
+ switch self {
+ case .error(let message):
+ return message
+ }
+ }
+}
diff --git a/Juick/Helpers/NSAttributedString_Entities.h b/Juick/Helpers/NSAttributedString_Entities.h
new file mode 100644
index 0000000..691d3d4
--- /dev/null
+++ b/Juick/Helpers/NSAttributedString_Entities.h
@@ -0,0 +1,19 @@
+//
+// NSAttributedString+NSAttributedString_Entities.h
+// Juick
+//
+// Created by Vitaly Takmazov on 23.09.2020.
+// Copyright © 2020 com.juick. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface NSAttributedString (Entities)
+
++(NSAttributedString *) attributedStringFromMessage:(Message *)message;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Juick/Helpers/NSAttributedString_Entities.m b/Juick/Helpers/NSAttributedString_Entities.m
new file mode 100644
index 0000000..3f7159b
--- /dev/null
+++ b/Juick/Helpers/NSAttributedString_Entities.m
@@ -0,0 +1,90 @@
+//
+// NSAttributedString+NSAttributedString.h
+// Juick
+//
+// Created by Vitaly Takmazov on 23.09.2020.
+// Copyright © 2020 com.juick. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@implementation NSAttributedString (Entities)
+
+NSMutableParagraphStyle *quoteStyle;
+UIFont *boldFont;
+UIFont *italicFont;
+
+__attribute__((constructor))
+static void initialize_fonts() {
+ quoteStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
+ quoteStyle.firstLineHeadIndent = 12.0f;
+ quoteStyle.headIndent = 12.0f;
+ quoteStyle.paragraphSpacing = 6.0f;
+ UIFontDescriptor* fontDescriptor = [UIFontDescriptor
+ preferredFontDescriptorWithTextStyle:UIFontTextStyleBody];
+ UIFontDescriptor* boldFontDescriptor = [fontDescriptor
+ fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitBold];
+ boldFont = [UIFont fontWithDescriptor:boldFontDescriptor size: 0.0];
+ UIFontDescriptor* italicFontDescriptor = [fontDescriptor
+ fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitItalic];
+ italicFont = [UIFont fontWithDescriptor:italicFontDescriptor size: 0.0];
+}
+
++(NSAttributedString *) attributedStringFromMessage:(Message *)msg {
+ if (msg.text) {
+ NSMutableAttributedString *txt = [[NSMutableAttributedString alloc]
+ initWithString:msg.text
+ attributes:@{NSFontAttributeName:[UIFont preferredFontForTextStyle:UIFontTextStyleBody],
+ NSForegroundColorAttributeName:[UIColor colorNamed:@"Text"]
+ }];
+ [txt beginEditing];
+ for (Entity *entity in msg.entities) {
+ NSUInteger start = entity.start ? [entity.start unsignedIntegerValue] : 0;
+ NSUInteger end = entity.end ? [entity.end unsignedIntegerValue] : 0;
+ NSString *text = entity.text ? entity.text : @"";
+ NSRange currentRange = NSMakeRange(start, end - start);
+ [txt addAttribute:@"displayText" value:text range:currentRange];
+ if ([entity.type isEqualToString:@"a"]) {
+ [txt addAttribute:NSLinkAttributeName value:entity.link range:currentRange];
+ }
+ if ([entity.type isEqualToString:@"q"]) {
+ [txt addAttribute:NSForegroundColorAttributeName value:[UIColor colorNamed:@"Muted"] range:currentRange];
+ [txt addAttribute:NSParagraphStyleAttributeName value:quoteStyle range:currentRange];
+ }
+ if ([entity.type isEqualToString:@"u"]) {
+ [txt addAttribute:NSUnderlineStyleAttributeName value:@(NSUnderlineStyleSingle) range:currentRange];
+ }
+
+ if ([entity.type isEqualToString:@"b"]) {
+ [txt addAttribute:NSFontAttributeName value:boldFont range:currentRange];
+ }
+ if ([entity.type isEqualToString:@"i"]) {
+ [txt addAttribute:NSFontAttributeName value:italicFont range:currentRange];
+ }
+ }
+ [txt enumerateAttribute:@"displayText" inRange:NSMakeRange(0, [txt length]) options:0 usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
+ if (value) {
+ [txt replaceCharactersInRange:range withString:value];
+ }
+ }];
+ if ([msg.tags count] > 0) {
+ NSString *tagsList = [NSString stringWithFormat:@"%@\n", [msg.tags componentsJoinedByString:@", "]];
+ [txt insertAttributedString:[[NSAttributedString alloc]
+ initWithString:tagsList
+ attributes:@{
+ NSFontAttributeName:italicFont,
+ NSForegroundColorAttributeName:[UIColor colorNamed:@"Muted"]
+ }] atIndex:0];
+ }
+ [txt endEditing];
+ return txt;
+ } else {
+ return [[NSAttributedString alloc] initWithString:@""];
+ }
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Juick/ImageFetcher.swift b/Juick/ImageFetcher.swift
new file mode 100644
index 0000000..f76c0ba
--- /dev/null
+++ b/Juick/ImageFetcher.swift
@@ -0,0 +1,28 @@
+//
+// ImageFetcher.swift
+// tst
+//
+// Created by Vitaly Takmazov on 10.12.2019.
+// Copyright © 2019 com.juick. All rights reserved.
+//
+
+import Foundation
+import Combine
+
+class ImageFetcher: ObservableObject {
+
+ @Published var data: Data = Data()
+
+ init(url: String) {
+ guard let imageUrl = URL(string: url) else {
+ return
+ }
+
+ URLSession.shared.dataTask(with: imageUrl) { (data, _, _) in
+ guard let data = data else { return }
+ DispatchQueue.main.async { [weak self] in
+ self?.data = data
+ }
+ }.resume()
+ }
+}
diff --git a/Juick/Main.storyboard b/Juick/Main.storyboard
index 5d99c97..3e7e571 100644
--- a/Juick/Main.storyboard
+++ b/Juick/Main.storyboard
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16096" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="Rbr-km-xhI">
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17156" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="Rbr-km-xhI">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
- <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17125"/>
<capability name="Named colors" minToolsVersion="9.0"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
@@ -96,7 +96,7 @@
<objects>
<viewController id="4g9-hM-bzq" customClass="LoginViewController" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="ak5-5Q-P4e">
- <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
+ <rect key="frame" x="0.0" y="0.0" width="375" height="647"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<stackView opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" axis="vertical" spacing="24" translatesAutoresizingMaskIntoConstraints="NO" id="axR-g5-sfd">
@@ -127,7 +127,7 @@
<userDefinedRuntimeAttribute type="string" keyPath="placeholder" value="Password..."/>
</userDefinedRuntimeAttributes>
</textField>
- <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="gPQ-xI-b5J">
+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="gPQ-xI-b5J">
<rect key="frame" x="0.0" y="280.5" width="351" height="33"/>
<color key="backgroundColor" name="Chat"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
@@ -141,13 +141,13 @@
</subviews>
</stackView>
</subviews>
+ <viewLayoutGuide key="safeArea" id="fue-ZI-ech"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="fue-ZI-ech" firstAttribute="trailing" secondItem="axR-g5-sfd" secondAttribute="trailing" constant="12" id="CP5-In-mwY"/>
<constraint firstItem="axR-g5-sfd" firstAttribute="leading" secondItem="fue-ZI-ech" secondAttribute="leading" constant="12" id="YIV-CU-Vyy"/>
<constraint firstItem="axR-g5-sfd" firstAttribute="top" secondItem="fue-ZI-ech" secondAttribute="top" constant="12" id="ZUC-uk-RH7"/>
</constraints>
- <viewLayoutGuide key="safeArea" id="fue-ZI-ech"/>
</view>
<navigationItem key="navigationItem" id="QaM-45-gms"/>
<connections>
@@ -231,7 +231,7 @@
<objects>
<viewController title="New post" hidesBottomBarWhenPushed="YES" id="rr1-jx-MLx" customClass="NewPostViewController" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="7ep-bO-aeZ">
- <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
+ <rect key="frame" x="0.0" y="0.0" width="375" height="647"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="XYj-Y4-lfM">
@@ -241,6 +241,7 @@
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
</textView>
</subviews>
+ <viewLayoutGuide key="safeArea" id="UJz-7C-l1q"/>
<color key="backgroundColor" name="TextBackground"/>
<constraints>
<constraint firstItem="UJz-7C-l1q" firstAttribute="top" secondItem="XYj-Y4-lfM" secondAttribute="top" constant="4" id="5aN-lI-YBC"/>
@@ -248,7 +249,6 @@
<constraint firstItem="XYj-Y4-lfM" firstAttribute="leading" secondItem="UJz-7C-l1q" secondAttribute="leading" constant="4" id="aw6-Uf-tSS"/>
<constraint firstItem="UJz-7C-l1q" firstAttribute="trailing" secondItem="XYj-Y4-lfM" secondAttribute="trailing" constant="4" id="gTz-QO-1CL"/>
</constraints>
- <viewLayoutGuide key="safeArea" id="UJz-7C-l1q"/>
</view>
<navigationItem key="navigationItem" id="Yd6-Yh-gtd">
<barButtonItem key="leftBarButtonItem" systemItem="cancel" id="pNy-rM-fck">
@@ -443,7 +443,7 @@
<color red="0.97254901960784312" green="0.97254901960784312" blue="0.97254901960784312" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
<namedColor name="TextBackground">
- <color red="0.99215686274509807" green="0.99215686274509807" blue="0.99607843137254903" alpha="0.75" colorSpace="custom" customColorSpace="sRGB"/>
+ <color red="0.99199998378753662" green="0.99199998378753662" blue="0.99599999189376831" alpha="0.75" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
<namedColor name="Title">
<color red="0.23529411764705882" green="0.46666666666666667" blue="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
diff --git a/Juick/MessageFetcher.swift b/Juick/MessageFetcher.swift
new file mode 100644
index 0000000..b478e9e
--- /dev/null
+++ b/Juick/MessageFetcher.swift
@@ -0,0 +1,46 @@
+//
+// MessageFetcher.swift
+// tst
+//
+// Created by Vitaly Takmazov on 10.12.2019.
+// Copyright © 2019 com.juick. All rights reserved.
+//
+
+import Foundation
+import Combine
+
+class MessageFetcher: ObservableObject {
+
+ typealias Feed = [MessageData]
+
+ struct MessageData : Identifiable {
+ var id : String {
+ get {
+ return "\(message.mid.stringValue)-\(message.rid?.intValue ?? 0)"
+ }
+ }
+ var message : Message
+ }
+
+ @Published var state: LoadableState<Feed> = .loading
+
+ init(url: String) {
+ guard let apiUrl = URL(string: url) else {
+ state = .fetched(.failure(.error("Malformed API URL.")))
+ return
+ }
+
+ AppDelegate.shared.api.pullNext(fromPath: url, params: nil) { (messages, error) in
+ if let error = error {
+ self.state = .fetched(.failure(.error(error.localizedDescription)))
+ return
+ }
+
+ guard let messages = messages else {
+ self.state = .fetched(.failure(.error("Malformed response data")))
+ return
+ }
+ self.state = .fetched(.success(messages.map { return MessageData(message: $0) } ))
+ }
+ }
+}
diff --git a/Juick/Model/Attachment.m b/Juick/Model/Attachment.m
index 006b72e..e0de2ca 100644
--- a/Juick/Model/Attachment.m
+++ b/Juick/Model/Attachment.m
@@ -11,20 +11,23 @@
@implementation Attachment
+(Attachment *) fromJSON:(NSDictionary *)jsonData {
- Attachment *attachment = [Attachment new];
- attachment.url = jsonData[@"url"];
- attachment.width = jsonData[@"width"];
- attachment.height = jsonData[@"height"];
- if (jsonData[@"small"]) {
- attachment.small = [Attachment fromJSON:jsonData[@"small"]];
+ if (jsonData[@"url"]) {
+ Attachment *attachment = [Attachment new];
+ attachment.url = jsonData[@"url"];
+ attachment.width = jsonData[@"width"];
+ attachment.height = jsonData[@"height"];
+ if (jsonData[@"small"]) {
+ attachment.small = [Attachment fromJSON:jsonData[@"small"]];
+ }
+ if (jsonData[@"medium"]) {
+ attachment.medium = [Attachment fromJSON:jsonData[@"medium"]];
+ }
+ if (jsonData[@"thumbnail"]) {
+ attachment.thumbnail = [Attachment fromJSON:jsonData[@"thumbnail"]];
+ }
+ return attachment;
}
- if (jsonData[@"medium"]) {
- attachment.medium = [Attachment fromJSON:jsonData[@"medium"]];
- }
- if (jsonData[@"thumbnail"]) {
- attachment.thumbnail = [Attachment fromJSON:jsonData[@"thumbnail"]];
- }
- return attachment;
+ return nil;
}
@end
diff --git a/Juick/Model/Message.h b/Juick/Model/Message.h
index 33d9911..5c85b1e 100644
--- a/Juick/Model/Message.h
+++ b/Juick/Model/Message.h
@@ -11,22 +11,26 @@
#import "Attachment.h"
#import "Entity.h"
+NS_ASSUME_NONNULL_BEGIN
+
@interface Message : NSObject
-@property NSNumber *mid;
-@property NSNumber *rid;
+@property NSNumber * _Nonnull mid;
+@property NSNumber * _Nullable rid;
@property User *user;
-@property NSString *text;
+@property NSString * _Nullable text;
@property NSArray<NSString *> *tags;
@property NSArray<Entity *> *entities;
@property NSString *timestamp;
@property NSString *attach;
@property NSString *repliesBy;
@property NSNumber *repliesCount;
-@property Attachment *attachment;
+@property Attachment * _Nullable attachment;
@property BOOL service;
@property BOOL unread;
+(Message *) fromJSON:(NSDictionary *)jsonData;
@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Juick/Model/Message.m b/Juick/Model/Message.m
index d39be54..afc499e 100644
--- a/Juick/Model/Message.m
+++ b/Juick/Model/Message.m
@@ -13,7 +13,7 @@
+ (Message *) fromJSON:(NSDictionary *)jsonData {
Message * message = [Message new];
message.mid = jsonData[@"mid"];
- message.rid = jsonData[@"rid"];
+ message.rid = [NSNumber numberWithInt:jsonData[@"rid"]];
message.text = jsonData[@"body"];
message.attach = jsonData[@"photo"][@"small"];
message.repliesCount = jsonData[@"replies"];
diff --git a/Juick/Model/User.h b/Juick/Model/User.h
index 4bbf332..5941a90 100644
--- a/Juick/Model/User.h
+++ b/Juick/Model/User.h
@@ -7,6 +7,7 @@
//
@import Foundation;
+@import UIKit;
@interface User : NSObject
@property (nonatomic, strong) NSString *uname;
diff --git a/Juick/SceneDelegate.swift b/Juick/SceneDelegate.swift
new file mode 100644
index 0000000..024538a
--- /dev/null
+++ b/Juick/SceneDelegate.swift
@@ -0,0 +1,63 @@
+//
+// SceneDelegate.swift
+// tst
+//
+// Created by Vitaly Takmazov on 10.12.2019.
+// Copyright © 2019 com.juick. All rights reserved.
+//
+
+import UIKit
+import SwiftUI
+
+class SceneDelegate: UIResponder, UIWindowSceneDelegate {
+
+ var window: UIWindow?
+
+
+ func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
+ // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
+ // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
+ // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
+
+ let contentView = ContentView()
+
+ // Use a UIHostingController as window root view controller.
+ if let windowScene = scene as? UIWindowScene {
+ let window = UIWindow(windowScene: windowScene)
+ window.rootViewController = UIHostingController(rootView: contentView)
+ self.window = window
+ self.window?.tintColor = UIColor(named: "Title")
+ window.makeKeyAndVisible()
+ }
+ }
+
+ func sceneDidDisconnect(_ scene: UIScene) {
+ // Called as the scene is being released by the system.
+ // This occurs shortly after the scene enters the background, or when its session is discarded.
+ // Release any resources associated with this scene that can be re-created the next time the scene connects.
+ // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
+ }
+
+ func sceneDidBecomeActive(_ scene: UIScene) {
+ // Called when the scene has moved from an inactive state to an active state.
+ // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
+ }
+
+ func sceneWillResignActive(_ scene: UIScene) {
+ // Called when the scene will move from an active state to an inactive state.
+ // This may occur due to temporary interruptions (ex. an incoming phone call).
+ }
+
+ func sceneWillEnterForeground(_ scene: UIScene) {
+ // Called as the scene transitions from the background to the foreground.
+ // Use this method to undo the changes made on entering the background.
+ }
+
+ func sceneDidEnterBackground(_ scene: UIScene) {
+ // Called as the scene transitions from the foreground to the background.
+ // Use this method to save data, release shared resources, and store enough scene-specific state information
+ // to restore the scene back to its current state.
+ }
+
+
+}
diff --git a/Juick/Supporting Files/Juick-Bridging-Header.h b/Juick/Supporting Files/Juick-Bridging-Header.h
index e11d920..03d88eb 100644
--- a/Juick/Supporting Files/Juick-Bridging-Header.h
+++ b/Juick/Supporting Files/Juick-Bridging-Header.h
@@ -1,3 +1,6 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
+#import "API.h"
+#import "DeviceRegistration.h"
+#import "NSAttributedString_Entities.h"
diff --git a/Juick/Supporting Files/Juick-Info.plist b/Juick/Supporting Files/Juick-Info.plist
index 46c6650..c706fb6 100644
--- a/Juick/Supporting Files/Juick-Info.plist
+++ b/Juick/Supporting Files/Juick-Info.plist
@@ -2,6 +2,23 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
+ <key>UIApplicationSceneManifest</key>
+ <dict>
+ <key>UIApplicationSupportsMultipleScenes</key>
+ <false/>
+ <key>UISceneConfigurations</key>
+ <dict>
+ <key>UIWindowSceneSessionRoleApplication</key>
+ <array>
+ <dict>
+ <key>UISceneConfigurationName</key>
+ <string>Default Configuration</string>
+ <key>UISceneDelegateClassName</key>
+ <string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
+ </dict>
+ </array>
+ </dict>
+ </dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
diff --git a/Juick/Supporting Files/main.m b/Juick/Supporting Files/main.m
deleted file mode 100644
index 8e01106..0000000
--- a/Juick/Supporting Files/main.m
+++ /dev/null
@@ -1,16 +0,0 @@
-//
-// main.m
-// Juick
-//
-// Created by Vitaly Takmazov on 26.10.13.
-// Copyright (c) 2013 com.juick. All rights reserved.
-//
-
-#import <UIKit/UIKit.h>
-
-int main(int argc, char * argv[])
-{
- @autoreleasepool {
- return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
- }
-}
diff --git a/Juick/Views/ActivityIndicator.swift b/Juick/Views/ActivityIndicator.swift
new file mode 100644
index 0000000..5b160fd
--- /dev/null
+++ b/Juick/Views/ActivityIndicator.swift
@@ -0,0 +1,28 @@
+//
+// ActivityIndicator.swift
+// tst
+//
+// Created by Vitaly Takmazov on 10.12.2019.
+// Copyright © 2019 com.juick. All rights reserved.
+//
+
+import Foundation
+import SwiftUI
+
+struct ActivityIndicator: UIViewRepresentable {
+ let style: UIActivityIndicatorView.Style
+
+ func makeUIView(context: UIViewRepresentableContext<ActivityIndicator>) -> UIActivityIndicatorView {
+ return UIActivityIndicatorView(style: style)
+ }
+
+ func updateUIView(_ uiView: UIActivityIndicatorView, context: UIViewRepresentableContext<ActivityIndicator>) {
+ uiView.startAnimating()
+ }
+}
+
+struct ActivityIndicator_Previews: PreviewProvider {
+ static var previews: some View {
+ /*@START_MENU_TOKEN@*/Text("Hello, World!")/*@END_MENU_TOKEN@*/
+ }
+}
diff --git a/Juick/Views/AttributedLabelView.swift b/Juick/Views/AttributedLabelView.swift
new file mode 100644
index 0000000..08051a9
--- /dev/null
+++ b/Juick/Views/AttributedLabelView.swift
@@ -0,0 +1,107 @@
+//
+// AttributedLabelView.swift
+// Demo
+//
+// Created by Ernesto Rivera on 5/14/20.
+// Copyright © 2020 Atributika. All rights reserved.
+//
+import SwiftUI
+import Atributika
+
+@available(iOS 13.0, *)
+struct AttributedLabelView: UIViewRepresentable
+{
+ var attributedText: AttributedText?
+ var configureLabel: ((AttributedLabel) -> Void)? = nil
+
+ @State var maxWidth: CGFloat = 300
+
+ typealias UIViewType = MaxWidthAttributedLabel
+
+ func makeUIView(context: Context) -> MaxWidthAttributedLabel
+ {
+ let view = MaxWidthAttributedLabel()
+ configureLabel?(view)
+ return view
+ }
+
+ func updateUIView(_ uiView: MaxWidthAttributedLabel, context: Context)
+ {
+ uiView.attributedText = attributedText
+ uiView.maxWidth = maxWidth
+ }
+
+ class MaxWidthAttributedLabel: AttributedLabel
+ {
+ var maxWidth: CGFloat!
+
+ open override var intrinsicContentSize: CGSize
+ {
+ sizeThatFits(CGSize(width: maxWidth, height: .infinity))
+ }
+ }
+}
+
+@available(iOS 13.0, *)
+struct AttributtedLabelView_Previews: PreviewProvider
+{
+ static var previews: some View
+ {
+ let all = Style.font(UIFont.preferredFont(forTextStyle: .body))
+ let link = Style("a")
+ .foregroundColor(Color(named: "Title") ?? .blue, .normal)
+ .foregroundColor(.brown, .highlighted)
+ let configureLabel: ((AttributedLabel) -> Void) = { label in
+ label.numberOfLines = 0
+ label.textColor = .label
+ }
+
+ return GeometryReader { geometry in
+ List {
+ AttributedLabelView(attributedText: """
+Denny JA: Dengan RT ini, anda ikut memenangkan Jokowi-JK. Pilih hghghg
+ hghfghfgh
+ fghfgh
+ fgh
+ gfh
+ fgh
+ dipercaya (Jokowi) dan pengalaman (JK). #DJoJK
+"""
+ .style(tags: link)
+ .styleHashtags(link)
+ .styleMentions(link)
+ .styleLinks(link)
+ .styleAll(all), configureLabel: configureLabel, maxWidth: geometry.size.width)
+ .fixedSize(horizontal: true, vertical: true)
+ .padding(EdgeInsets(top: 12, leading: 12, bottom: 12, trailing: 24))
+ .listRowInsets(EdgeInsets())
+ AttributedLabelView(attributedText: "@e2F If only Bradley's arm was longer. Best ever. 😊 #oscars https://m/p<br>Check this <a href=\"https://github.com/psharanda/Atributika\">link</a>"
+ .style(tags: link)
+ .styleHashtags(link)
+ .styleMentions(link)
+ .styleLinks(link)
+ .styleAll(all), configureLabel: configureLabel)
+ .fixedSize(horizontal: true, vertical: true)
+ .padding()
+ .listRowInsets(EdgeInsets())
+ AttributedLabelView(attributedText: """
+ # A big message
+ - With *mentions* [Ernesto Test Account](user://91010061)
+ - **Bold** text
+ ## Also
+ > Quotes
+ 1. Some `code`
+ 2. And data detectors (801) 917 4444, email@dot.com and http://apple.com
+ """
+ .style(tags: link)
+ .styleHashtags(link)
+ .styleMentions(link)
+ .styleLinks(link)
+ .styleAll(all), configureLabel: configureLabel)
+ .fixedSize(horizontal: true, vertical: true)
+ .listRowInsets(EdgeInsets())
+ .padding()
+ }.listRowInsets(EdgeInsets())
+ }
+ }
+}
diff --git a/Juick/Views/ContentView.swift b/Juick/Views/ContentView.swift
new file mode 100644
index 0000000..9a9f45b
--- /dev/null
+++ b/Juick/Views/ContentView.swift
@@ -0,0 +1,47 @@
+//
+// ContentView.swift
+// tst
+//
+// Created by Vitaly Takmazov on 10.12.2019.
+// Copyright © 2019 com.juick. All rights reserved.
+//
+
+import SwiftUI
+import Combine
+
+struct ContentView: View {
+ @State var selectedView = 0
+ var body: some View {
+ NavigationView {
+ TabView(selection: $selectedView) {
+ FeedView(url: "https://api.juick.com/messages?popular=1", type: .messages)
+ .tabItem {
+ Image("ei-clock")
+ Text("Today")
+ }.tag(0)
+ FeedView(url: "https://api.juick.com/messages/discussions", type: .messages)
+ .tabItem {
+ Image("ei-bell")
+ Text("Discussions")
+ }.tag(1)
+ FeedView(url: "https://api.juick.com/messages", type: .messages)
+ .tabItem {
+ Image("ei-envelope")
+ Text("Chats")
+ }.tag(2)
+ FeedView(url: "https://api.juick.com/messages", type: .messages)
+ .tabItem {
+ Image("ei-search")
+ Text("Discover")
+ }.tag(3)
+ }
+ .navigationBarTitle("Juick", displayMode: .inline)
+ }
+ }
+}
+
+struct ContentView_Previews: PreviewProvider {
+ static var previews: some View {
+ ContentView()
+ }
+}
diff --git a/Juick/Views/FeedView.swift b/Juick/Views/FeedView.swift
new file mode 100644
index 0000000..fe3aaed
--- /dev/null
+++ b/Juick/Views/FeedView.swift
@@ -0,0 +1,66 @@
+//
+// FeedView.swift
+// tst
+//
+// Created by Vitaly Takmazov on 10.12.2019.
+// Copyright © 2019 com.juick. All rights reserved.
+//
+
+import SwiftUI
+
+enum FeedType {
+ case messages
+ case thread
+}
+
+struct FeedView: View {
+ @ObservedObject var messageFetcher : MessageFetcher
+
+ let feedType : FeedType
+
+ init(url: String, type: FeedType) {
+ feedType = type
+ messageFetcher = MessageFetcher(url: url)
+ }
+
+ private var stateContent: AnyView {
+ switch messageFetcher.state {
+ case .loading:
+ return AnyView(
+ ActivityIndicator(style: .medium)
+ )
+ case .fetched(let result):
+ switch result {
+ case .failure(let error):
+ return AnyView(
+ Text(error.localizedDescription)
+ )
+ case .success(let root):
+ return AnyView(
+ List(root) { (message: MessageFetcher.MessageData) in
+ let destination = feedType == .thread ? AnyView(Text("YO")) : AnyView(FeedView(url: "https://api.juick.com/thread?mid=\(message.message.mid.stringValue)", type: .thread))
+ ZStack {
+ MessageView(message: message.message).listRowInsets(.none)
+ NavigationLink(
+ destination: destination) {
+ EmptyView()
+ }
+ }
+ }
+ .listRowInsets(.none)
+ )
+ }
+ }
+ }
+
+ var body: some View {
+ stateContent
+ .navigationBarTitle(Text("Messages"), displayMode: .inline)
+ }
+}
+
+struct FeedView_Previews: PreviewProvider {
+ static var previews: some View {
+ FeedView(url: "https://api.juick.com/messages", type: .messages)
+ }
+}
diff --git a/Juick/Views/LoadableImageView.swift b/Juick/Views/LoadableImageView.swift
new file mode 100644
index 0000000..e639602
--- /dev/null
+++ b/Juick/Views/LoadableImageView.swift
@@ -0,0 +1,35 @@
+//
+// LoadableImageView.swift
+// tst
+//
+// Created by Vitaly Takmazov on 10.12.2019.
+// Copyright © 2019 com.juick. All rights reserved.
+//
+
+import SwiftUI
+
+struct LoadableImageView: View {
+ @ObservedObject var imageFetcher: ImageFetcher
+
+ init(with urlString: String) {
+ imageFetcher = ImageFetcher(url: urlString)
+ }
+
+ var body: some View {
+ if let image = UIImage(data: imageFetcher.data) {
+ return AnyView(
+ Image(uiImage: image).resizable()
+ )
+ } else {
+ return AnyView(
+ ActivityIndicator(style: .medium)
+ )
+ }
+ }
+}
+
+struct LoadableImageView_Previews: PreviewProvider {
+ static var previews: some View {
+ LoadableImageView(with: "https://i.juick.com/a/1.png")
+ }
+}
diff --git a/Juick/Views/MessageView.swift b/Juick/Views/MessageView.swift
new file mode 100644
index 0000000..ffd0f19
--- /dev/null
+++ b/Juick/Views/MessageView.swift
@@ -0,0 +1,51 @@
+//
+// MessageView.swift
+// tst
+//
+// Created by Vitaly Takmazov on 10.12.2019.
+// Copyright © 2019 com.juick. All rights reserved.
+//
+
+import SwiftUI
+import Atributika
+
+struct MessageView: View {
+ var message: Message
+ let all = Style.font(UIFont.preferredFont(forTextStyle: .body))
+ let link = Style("a")
+ .foregroundColor(.blue, .normal)
+ .foregroundColor(.brown, .highlighted)
+ let configureLabel: ((AttributedLabel) -> Void) = { label in
+ label.numberOfLines = 0
+ label.textColor = .label
+ }
+ var body: some View {
+ VStack(alignment: .leading) {
+ HStack {
+ LoadableImageView(with: message.user.avatar ?? "")
+ .frame(width: 48, height: 48, alignment: .center)
+ Text(message.user.uname)
+ .font(.headline)
+ .foregroundColor(.accentColor)
+ .fixedSize(horizontal: true, vertical: false)
+ }
+ AttributedLabelView(attributedText: (message.text ?? "")
+ .style(tags: link)
+ .styleHashtags(link)
+ .styleMentions(link)
+ .styleLinks(link)
+ .styleAll(all), configureLabel: configureLabel)
+ .fixedSize(horizontal: true, vertical: true)
+ message.attachment.map {
+ LoadableImageView(with: $0.url).scaledToFit()
+ }
+ }
+ }
+}
+
+struct MessageView_Previews: PreviewProvider {
+ static let msg = Message()
+ static var previews: some View {
+ MessageView(message: msg)
+ }
+}