Compare commits
6 Commits
bf350882b4
...
62c6c6d2c5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62c6c6d2c5 | ||
|
|
3eb9b13f86 | ||
|
|
698f18624c | ||
|
|
25dfd09568 | ||
|
|
3356aec1cd | ||
|
|
6afccd6445 |
34
GoogleService-Info.plist
Normal file
34
GoogleService-Info.plist
Normal file
@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CLIENT_ID</key>
|
||||
<string>77785854711-kedg65qete3dhcmtbda2nn0n4e2sa9h9.apps.googleusercontent.com</string>
|
||||
<key>REVERSED_CLIENT_ID</key>
|
||||
<string>com.googleusercontent.apps.77785854711-kedg65qete3dhcmtbda2nn0n4e2sa9h9</string>
|
||||
<key>API_KEY</key>
|
||||
<string>AIzaSyBXhDkQ_dGzAchKiBCsEJL78fAKd77EBHg</string>
|
||||
<key>GCM_SENDER_ID</key>
|
||||
<string>77785854711</string>
|
||||
<key>PLIST_VERSION</key>
|
||||
<string>1</string>
|
||||
<key>BUNDLE_ID</key>
|
||||
<string>com.tunpok.ios.dudu-tweet</string>
|
||||
<key>PROJECT_ID</key>
|
||||
<string>twitter-22a99</string>
|
||||
<key>STORAGE_BUCKET</key>
|
||||
<string>twitter-22a99.appspot.com</string>
|
||||
<key>IS_ADS_ENABLED</key>
|
||||
<false></false>
|
||||
<key>IS_ANALYTICS_ENABLED</key>
|
||||
<false></false>
|
||||
<key>IS_APPINVITE_ENABLED</key>
|
||||
<true></true>
|
||||
<key>IS_GCM_ENABLED</key>
|
||||
<true></true>
|
||||
<key>IS_SIGNIN_ENABLED</key>
|
||||
<true></true>
|
||||
<key>GOOGLE_APP_ID</key>
|
||||
<string>1:77785854711:ios:00f7d7cd05ce7e37a719f2</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@ -19,6 +19,30 @@
|
||||
24A59ABE2A003108009C9E3E /* MessagesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A59ABD2A003108009C9E3E /* MessagesView.swift */; };
|
||||
24A59AC22A003249009C9E3E /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A59AC12A003249009C9E3E /* ProfileView.swift */; };
|
||||
24A59AC42A003A52009C9E3E /* TwewtFilterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A59AC32A003A52009C9E3E /* TwewtFilterViewModel.swift */; };
|
||||
24A59AC92A00BA81009C9E3E /* UserRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A59AC82A00BA81009C9E3E /* UserRowView.swift */; };
|
||||
24A59ACE2A00BDCB009C9E3E /* SideMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A59ACD2A00BDCB009C9E3E /* SideMenuView.swift */; };
|
||||
24A59AD22A00BE14009C9E3E /* SideMenuViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A59AD12A00BE14009C9E3E /* SideMenuViewModel.swift */; };
|
||||
24A59AD42A00C07D009C9E3E /* UserStatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A59AD32A00C07D009C9E3E /* UserStatsView.swift */; };
|
||||
24A59AD62A00CA82009C9E3E /* SideMenuOptionRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A59AD52A00CA82009C9E3E /* SideMenuOptionRowView.swift */; };
|
||||
24A59ADD2A00DB9F009C9E3E /* NewTweetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A59ADC2A00DB9F009C9E3E /* NewTweetView.swift */; };
|
||||
24A59ADF2A00DCC2009C9E3E /* TextArea.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A59ADE2A00DCC2009C9E3E /* TextArea.swift */; };
|
||||
24A59AE42A00EF1F009C9E3E /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A59AE32A00EF1F009C9E3E /* LoginView.swift */; };
|
||||
24A59AE62A00EF3A009C9E3E /* RegistrationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A59AE52A00EF3A009C9E3E /* RegistrationView.swift */; };
|
||||
24A59AE82A00F106009C9E3E /* RoundedShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A59AE72A00F106009C9E3E /* RoundedShape.swift */; };
|
||||
24A59AEA2A00F672009C9E3E /* CustomInputField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A59AE92A00F672009C9E3E /* CustomInputField.swift */; };
|
||||
24A59AED2A00F942009C9E3E /* AuthHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A59AEC2A00F942009C9E3E /* AuthHeaderView.swift */; };
|
||||
24A59AEF2A010142009C9E3E /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 24A59AEE2A010142009C9E3E /* GoogleService-Info.plist */; };
|
||||
24A59AF22A010411009C9E3E /* FirebaseAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 24A59AF12A010411009C9E3E /* FirebaseAuth */; };
|
||||
24A59AF42A010411009C9E3E /* FirebaseFirestore in Frameworks */ = {isa = PBXBuildFile; productRef = 24A59AF32A010411009C9E3E /* FirebaseFirestore */; };
|
||||
24A59AF62A010411009C9E3E /* FirebaseFirestoreSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 24A59AF52A010411009C9E3E /* FirebaseFirestoreSwift */; };
|
||||
24A59AF82A010411009C9E3E /* FirebaseStorage in Frameworks */ = {isa = PBXBuildFile; productRef = 24A59AF72A010411009C9E3E /* FirebaseStorage */; };
|
||||
24A59AFA2A01081F009C9E3E /* AuthViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A59AF92A01081F009C9E3E /* AuthViewModel.swift */; };
|
||||
24FA4D992A012A5D002D202A /* ProfilePhotoSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24FA4D982A012A5D002D202A /* ProfilePhotoSelectorView.swift */; };
|
||||
24FA4D9C2A012E1A002D202A /* ImagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24FA4D9B2A012E1A002D202A /* ImagePicker.swift */; };
|
||||
24FA4D9E2A0134CB002D202A /* ImageUploader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24FA4D9D2A0134CB002D202A /* ImageUploader.swift */; };
|
||||
24FA4DA02A013B24002D202A /* UserService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24FA4D9F2A013B24002D202A /* UserService.swift */; };
|
||||
24FA4DA22A013D2E002D202A /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24FA4DA12A013D2E002D202A /* User.swift */; };
|
||||
24FA4DA52A0142E2002D202A /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 24FA4DA42A0142E2002D202A /* Kingfisher */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
@ -36,6 +60,25 @@
|
||||
24A59ABD2A003108009C9E3E /* MessagesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagesView.swift; sourceTree = "<group>"; };
|
||||
24A59AC12A003249009C9E3E /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = "<group>"; };
|
||||
24A59AC32A003A52009C9E3E /* TwewtFilterViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwewtFilterViewModel.swift; sourceTree = "<group>"; };
|
||||
24A59AC82A00BA81009C9E3E /* UserRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserRowView.swift; sourceTree = "<group>"; };
|
||||
24A59ACD2A00BDCB009C9E3E /* SideMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideMenuView.swift; sourceTree = "<group>"; };
|
||||
24A59AD12A00BE14009C9E3E /* SideMenuViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideMenuViewModel.swift; sourceTree = "<group>"; };
|
||||
24A59AD32A00C07D009C9E3E /* UserStatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserStatsView.swift; sourceTree = "<group>"; };
|
||||
24A59AD52A00CA82009C9E3E /* SideMenuOptionRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideMenuOptionRowView.swift; sourceTree = "<group>"; };
|
||||
24A59ADC2A00DB9F009C9E3E /* NewTweetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTweetView.swift; sourceTree = "<group>"; };
|
||||
24A59ADE2A00DCC2009C9E3E /* TextArea.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextArea.swift; sourceTree = "<group>"; };
|
||||
24A59AE32A00EF1F009C9E3E /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = "<group>"; };
|
||||
24A59AE52A00EF3A009C9E3E /* RegistrationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationView.swift; sourceTree = "<group>"; };
|
||||
24A59AE72A00F106009C9E3E /* RoundedShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedShape.swift; sourceTree = "<group>"; };
|
||||
24A59AE92A00F672009C9E3E /* CustomInputField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomInputField.swift; sourceTree = "<group>"; };
|
||||
24A59AEC2A00F942009C9E3E /* AuthHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthHeaderView.swift; sourceTree = "<group>"; };
|
||||
24A59AEE2A010142009C9E3E /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "../../GoogleService-Info.plist"; sourceTree = "<group>"; };
|
||||
24A59AF92A01081F009C9E3E /* AuthViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthViewModel.swift; sourceTree = "<group>"; };
|
||||
24FA4D982A012A5D002D202A /* ProfilePhotoSelectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePhotoSelectorView.swift; sourceTree = "<group>"; };
|
||||
24FA4D9B2A012E1A002D202A /* ImagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePicker.swift; sourceTree = "<group>"; };
|
||||
24FA4D9D2A0134CB002D202A /* ImageUploader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageUploader.swift; sourceTree = "<group>"; };
|
||||
24FA4D9F2A013B24002D202A /* UserService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserService.swift; sourceTree = "<group>"; };
|
||||
24FA4DA12A013D2E002D202A /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@ -43,6 +86,11 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
24A59AF22A010411009C9E3E /* FirebaseAuth in Frameworks */,
|
||||
24A59AF82A010411009C9E3E /* FirebaseStorage in Frameworks */,
|
||||
24A59AF42A010411009C9E3E /* FirebaseFirestore in Frameworks */,
|
||||
24A59AF62A010411009C9E3E /* FirebaseFirestoreSwift in Frameworks */,
|
||||
24FA4DA52A0142E2002D202A /* Kingfisher in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -68,6 +116,7 @@
|
||||
2492CC0B2A000EB00086C525 /* dudu-tweet */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
24FA4D9A2A012E05002D202A /* Utils */,
|
||||
2492CC1F2A0022990086C525 /* Extensions */,
|
||||
2492CC1E2A0022970086C525 /* Model */,
|
||||
2492CC1D2A0022960086C525 /* Service */,
|
||||
@ -75,6 +124,7 @@
|
||||
2492CC0C2A000EB00086C525 /* dudu_tweetApp.swift */,
|
||||
2492CC0E2A000EB00086C525 /* ContentView.swift */,
|
||||
2492CC102A000EB10086C525 /* Assets.xcassets */,
|
||||
24A59AEE2A010142009C9E3E /* GoogleService-Info.plist */,
|
||||
2492CC122A000EB10086C525 /* dudu_tweet.entitlements */,
|
||||
2492CC132A000EB10086C525 /* Preview Content */,
|
||||
);
|
||||
@ -92,6 +142,9 @@
|
||||
2492CC1B2A00228F0086C525 /* Core */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
24A59AE02A00EEE8009C9E3E /* Authentication */,
|
||||
24A59AD72A00DB49009C9E3E /* UploadTweet */,
|
||||
24A59ACA2A00BDA1009C9E3E /* SideMenu */,
|
||||
2492CC212A0022C30086C525 /* Components */,
|
||||
24A59AB82A00308E009C9E3E /* Profile */,
|
||||
24A59AB72A00305E009C9E3E /* Explore */,
|
||||
@ -106,6 +159,8 @@
|
||||
2492CC1D2A0022960086C525 /* Service */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
24FA4D9D2A0134CB002D202A /* ImageUploader.swift */,
|
||||
24FA4D9F2A013B24002D202A /* UserService.swift */,
|
||||
);
|
||||
path = Service;
|
||||
sourceTree = "<group>";
|
||||
@ -113,6 +168,7 @@
|
||||
2492CC1E2A0022970086C525 /* Model */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
24FA4DA12A013D2E002D202A /* User.swift */,
|
||||
);
|
||||
path = Model;
|
||||
sourceTree = "<group>";
|
||||
@ -136,7 +192,12 @@
|
||||
2492CC212A0022C30086C525 /* Components */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
24A59AEB2A00F92D009C9E3E /* Authentication */,
|
||||
24A59AC72A00BA6A009C9E3E /* Users */,
|
||||
2492CC262A0025A50086C525 /* Tweets */,
|
||||
24A59ADE2A00DCC2009C9E3E /* TextArea.swift */,
|
||||
24A59AE72A00F106009C9E3E /* RoundedShape.swift */,
|
||||
24A59AE92A00F672009C9E3E /* CustomInputField.swift */,
|
||||
);
|
||||
path = Components;
|
||||
sourceTree = "<group>";
|
||||
@ -191,7 +252,8 @@
|
||||
24A59AB72A00305E009C9E3E /* Explore */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
24A59AB92A0030CB009C9E3E /* ExploreView.swift */,
|
||||
24A59AC62A00BA47009C9E3E /* Views */,
|
||||
24A59AC52A00BA3A009C9E3E /* ViewModels */,
|
||||
);
|
||||
path = Explore;
|
||||
sourceTree = "<group>";
|
||||
@ -221,6 +283,123 @@
|
||||
path = ViewModels;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
24A59AC52A00BA3A009C9E3E /* ViewModels */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
);
|
||||
path = ViewModels;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
24A59AC62A00BA47009C9E3E /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
24A59AB92A0030CB009C9E3E /* ExploreView.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
24A59AC72A00BA6A009C9E3E /* Users */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
24A59AC82A00BA81009C9E3E /* UserRowView.swift */,
|
||||
24A59AD32A00C07D009C9E3E /* UserStatsView.swift */,
|
||||
);
|
||||
path = Users;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
24A59ACA2A00BDA1009C9E3E /* SideMenu */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
24A59ACC2A00BDB7009C9E3E /* ViewModels */,
|
||||
24A59ACB2A00BDAF009C9E3E /* Views */,
|
||||
);
|
||||
path = SideMenu;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
24A59ACB2A00BDAF009C9E3E /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
24A59ACD2A00BDCB009C9E3E /* SideMenuView.swift */,
|
||||
24A59AD52A00CA82009C9E3E /* SideMenuOptionRowView.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
24A59ACC2A00BDB7009C9E3E /* ViewModels */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
24A59AD12A00BE14009C9E3E /* SideMenuViewModel.swift */,
|
||||
);
|
||||
path = ViewModels;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
24A59AD72A00DB49009C9E3E /* UploadTweet */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
24A59AD92A00DB67009C9E3E /* ViewModels */,
|
||||
24A59AD82A00DB5A009C9E3E /* Views */,
|
||||
);
|
||||
path = UploadTweet;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
24A59AD82A00DB5A009C9E3E /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
24A59ADC2A00DB9F009C9E3E /* NewTweetView.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
24A59AD92A00DB67009C9E3E /* ViewModels */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
);
|
||||
path = ViewModels;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
24A59AE02A00EEE8009C9E3E /* Authentication */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
24A59AE22A00EEF8009C9E3E /* ViewModels */,
|
||||
24A59AE12A00EEF0009C9E3E /* Views */,
|
||||
);
|
||||
path = Authentication;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
24A59AE12A00EEF0009C9E3E /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
24A59AE32A00EF1F009C9E3E /* LoginView.swift */,
|
||||
24A59AE52A00EF3A009C9E3E /* RegistrationView.swift */,
|
||||
24FA4D982A012A5D002D202A /* ProfilePhotoSelectorView.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
24A59AE22A00EEF8009C9E3E /* ViewModels */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
24A59AF92A01081F009C9E3E /* AuthViewModel.swift */,
|
||||
);
|
||||
path = ViewModels;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
24A59AEB2A00F92D009C9E3E /* Authentication */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
24A59AEC2A00F942009C9E3E /* AuthHeaderView.swift */,
|
||||
);
|
||||
path = Authentication;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
24FA4D9A2A012E05002D202A /* Utils */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
24FA4D9B2A012E1A002D202A /* ImagePicker.swift */,
|
||||
);
|
||||
path = Utils;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
@ -237,6 +416,13 @@
|
||||
dependencies = (
|
||||
);
|
||||
name = "dudu-tweet";
|
||||
packageProductDependencies = (
|
||||
24A59AF12A010411009C9E3E /* FirebaseAuth */,
|
||||
24A59AF32A010411009C9E3E /* FirebaseFirestore */,
|
||||
24A59AF52A010411009C9E3E /* FirebaseFirestoreSwift */,
|
||||
24A59AF72A010411009C9E3E /* FirebaseStorage */,
|
||||
24FA4DA42A0142E2002D202A /* Kingfisher */,
|
||||
);
|
||||
productName = "dudu-tweet";
|
||||
productReference = 2492CC092A000EB00086C525 /* dudu-tweet.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
@ -265,6 +451,10 @@
|
||||
Base,
|
||||
);
|
||||
mainGroup = 2492CC002A000EB00086C525;
|
||||
packageReferences = (
|
||||
24A59AF02A010411009C9E3E /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */,
|
||||
24FA4DA32A0142E2002D202A /* XCRemoteSwiftPackageReference "Kingfisher" */,
|
||||
);
|
||||
productRefGroup = 2492CC0A2A000EB00086C525 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
@ -281,6 +471,7 @@
|
||||
files = (
|
||||
2492CC152A000EB10086C525 /* Preview Assets.xcassets in Resources */,
|
||||
2492CC112A000EB10086C525 /* Assets.xcassets in Resources */,
|
||||
24A59AEF2A010142009C9E3E /* GoogleService-Info.plist in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -293,14 +484,32 @@
|
||||
files = (
|
||||
24A59ABE2A003108009C9E3E /* MessagesView.swift in Sources */,
|
||||
24A59AC22A003249009C9E3E /* ProfileView.swift in Sources */,
|
||||
24FA4DA02A013B24002D202A /* UserService.swift in Sources */,
|
||||
24FA4D9C2A012E1A002D202A /* ImagePicker.swift in Sources */,
|
||||
2492CC0F2A000EB00086C525 /* ContentView.swift in Sources */,
|
||||
2492CC282A0025DD0086C525 /* TweetRowView.swift in Sources */,
|
||||
24FA4D9E2A0134CB002D202A /* ImageUploader.swift in Sources */,
|
||||
24A59AEA2A00F672009C9E3E /* CustomInputField.swift in Sources */,
|
||||
24A59AC42A003A52009C9E3E /* TwewtFilterViewModel.swift in Sources */,
|
||||
2492CC252A0023220086C525 /* FeedView.swift in Sources */,
|
||||
24A59ACE2A00BDCB009C9E3E /* SideMenuView.swift in Sources */,
|
||||
24FA4D992A012A5D002D202A /* ProfilePhotoSelectorView.swift in Sources */,
|
||||
2492CC0D2A000EB00086C525 /* dudu_tweetApp.swift in Sources */,
|
||||
24A59ABA2A0030CB009C9E3E /* ExploreView.swift in Sources */,
|
||||
24A59AD22A00BE14009C9E3E /* SideMenuViewModel.swift in Sources */,
|
||||
24A59ADF2A00DCC2009C9E3E /* TextArea.swift in Sources */,
|
||||
24A59AD62A00CA82009C9E3E /* SideMenuOptionRowView.swift in Sources */,
|
||||
24A59AE42A00EF1F009C9E3E /* LoginView.swift in Sources */,
|
||||
24A59AE62A00EF3A009C9E3E /* RegistrationView.swift in Sources */,
|
||||
24A59ADD2A00DB9F009C9E3E /* NewTweetView.swift in Sources */,
|
||||
24FA4DA22A013D2E002D202A /* User.swift in Sources */,
|
||||
24A59AED2A00F942009C9E3E /* AuthHeaderView.swift in Sources */,
|
||||
24A59AFA2A01081F009C9E3E /* AuthViewModel.swift in Sources */,
|
||||
24A59AE82A00F106009C9E3E /* RoundedShape.swift in Sources */,
|
||||
24A59AC92A00BA81009C9E3E /* UserRowView.swift in Sources */,
|
||||
24A59AB42A002EB8009C9E3E /* MainTabView.swift in Sources */,
|
||||
24A59ABC2A0030EC009C9E3E /* NotificationsView.swift in Sources */,
|
||||
24A59AD42A00C07D009C9E3E /* UserStatsView.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -514,6 +723,53 @@
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
24A59AF02A010411009C9E3E /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/firebase/firebase-ios-sdk";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 9.0.0;
|
||||
};
|
||||
};
|
||||
24FA4DA32A0142E2002D202A /* XCRemoteSwiftPackageReference "Kingfisher" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/onevcat/Kingfisher.git";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 7.0.0;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
24A59AF12A010411009C9E3E /* FirebaseAuth */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 24A59AF02A010411009C9E3E /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */;
|
||||
productName = FirebaseAuth;
|
||||
};
|
||||
24A59AF32A010411009C9E3E /* FirebaseFirestore */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 24A59AF02A010411009C9E3E /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */;
|
||||
productName = FirebaseFirestore;
|
||||
};
|
||||
24A59AF52A010411009C9E3E /* FirebaseFirestoreSwift */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 24A59AF02A010411009C9E3E /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */;
|
||||
productName = FirebaseFirestoreSwift;
|
||||
};
|
||||
24A59AF72A010411009C9E3E /* FirebaseStorage */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 24A59AF02A010411009C9E3E /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */;
|
||||
productName = FirebaseStorage;
|
||||
};
|
||||
24FA4DA42A0142E2002D202A /* Kingfisher */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 24FA4DA32A0142E2002D202A /* XCRemoteSwiftPackageReference "Kingfisher" */;
|
||||
productName = Kingfisher;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = 2492CC012A000EB00086C525 /* Project object */;
|
||||
}
|
||||
|
||||
@ -0,0 +1,122 @@
|
||||
{
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "abseil-cpp-swiftpm",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/firebase/abseil-cpp-SwiftPM.git",
|
||||
"state" : {
|
||||
"revision" : "583de9bd60f66b40e78d08599cc92036c2e7e4e1",
|
||||
"version" : "0.20220203.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "boringssl-swiftpm",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/firebase/boringssl-SwiftPM.git",
|
||||
"state" : {
|
||||
"revision" : "dd3eda2b05a3f459fc3073695ad1b28659066eab",
|
||||
"version" : "0.9.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "firebase-ios-sdk",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/firebase/firebase-ios-sdk",
|
||||
"state" : {
|
||||
"revision" : "7e80c25b51c2ffa238879b07fbfc5baa54bb3050",
|
||||
"version" : "9.6.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "googleappmeasurement",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/GoogleAppMeasurement.git",
|
||||
"state" : {
|
||||
"revision" : "c1cfde8067668027b23a42c29d11c246152fe046",
|
||||
"version" : "9.6.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "googledatatransport",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/GoogleDataTransport.git",
|
||||
"state" : {
|
||||
"revision" : "cc7265b8e3906304e6e81f32c1662a94bbae2357",
|
||||
"version" : "9.2.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "googleutilities",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/GoogleUtilities.git",
|
||||
"state" : {
|
||||
"revision" : "871d43135925cde39ef7421d8723ce47edfdcc39",
|
||||
"version" : "7.11.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "grpc-ios",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/grpc/grpc-ios.git",
|
||||
"state" : {
|
||||
"revision" : "8440b914756e0d26d4f4d054a1c1581daedfc5b6",
|
||||
"version" : "1.44.3-grpc"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "gtm-session-fetcher",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/gtm-session-fetcher.git",
|
||||
"state" : {
|
||||
"revision" : "5ccda3981422a84186387dbb763ba739178b529c",
|
||||
"version" : "2.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "kingfisher",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/onevcat/Kingfisher.git",
|
||||
"state" : {
|
||||
"revision" : "af4be924ad984cf4d16f4ae4df424e79a443d435",
|
||||
"version" : "7.6.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "leveldb",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/firebase/leveldb.git",
|
||||
"state" : {
|
||||
"revision" : "0706abcc6b0bd9cedfbb015ba840e4a780b5159b",
|
||||
"version" : "1.22.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "nanopb",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/firebase/nanopb.git",
|
||||
"state" : {
|
||||
"revision" : "819d0a2173aff699fb8c364b6fb906f7cdb1a692",
|
||||
"version" : "2.30909.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "promises",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/promises.git",
|
||||
"state" : {
|
||||
"revision" : "ec957ccddbcc710ccc64c9dcbd4c7006fcf8b73a",
|
||||
"version" : "2.2.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-protobuf",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-protobuf.git",
|
||||
"state" : {
|
||||
"revision" : "0af9125c4eae12a4973fb66574c53a54962a9e1e",
|
||||
"version" : "1.21.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 2
|
||||
}
|
||||
Binary file not shown.
@ -4,10 +4,31 @@
|
||||
<dict>
|
||||
<key>SchemeUserState</key>
|
||||
<dict>
|
||||
<key>Promises (Playground) 1.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>2</integer>
|
||||
</dict>
|
||||
<key>Promises (Playground) 2.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>3</integer>
|
||||
</dict>
|
||||
<key>Promises (Playground).xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<key>dudu-tweet.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>0</integer>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>SuppressBuildableAutocreation</key>
|
||||
|
||||
@ -6,16 +6,22 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Kingfisher
|
||||
|
||||
struct ContentView: View {
|
||||
@State private var showMenu = false
|
||||
@EnvironmentObject var viewModel: AuthViewModel
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Image(systemName: "globe")
|
||||
.imageScale(.large)
|
||||
.foregroundColor(.accentColor)
|
||||
Text("Hello, world!")
|
||||
Group {
|
||||
// no user loggin
|
||||
if viewModel.userSession == nil {
|
||||
LoginView()
|
||||
} else {
|
||||
// have a logged in user
|
||||
mainInterfaceView
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,3 +30,52 @@ struct ContentView_Previews: PreviewProvider {
|
||||
ContentView()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension ContentView {
|
||||
var mainInterfaceView: some View {
|
||||
ZStack(alignment: .topLeading) {
|
||||
MainTabView()
|
||||
.toolbar(showMenu ? .hidden : .visible)
|
||||
|
||||
if showMenu {
|
||||
ZStack {
|
||||
Color(.black)
|
||||
.opacity(showMenu ? 0.25 : 0.0)
|
||||
}.onTapGesture {
|
||||
withAnimation(.easeInOut) {
|
||||
showMenu = false
|
||||
}
|
||||
}
|
||||
.ignoresSafeArea()
|
||||
}
|
||||
|
||||
SideMenuView()
|
||||
.frame(width: 300)
|
||||
.offset(x: showMenu ? 0: -300, y: 0)
|
||||
.background(showMenu ? Color.white: Color.clear)
|
||||
}
|
||||
.navigationTitle("Home")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
if let user = viewModel.currentUser {
|
||||
Button {
|
||||
withAnimation(.easeInOut) {
|
||||
showMenu.toggle()
|
||||
}
|
||||
} label: {
|
||||
KFImage(URL(string: user.profileImageUrl))
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.clipShape(Circle())
|
||||
.frame(width: 32, height: 32)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
showMenu = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,88 @@
|
||||
//
|
||||
// AuthViewModel.swift
|
||||
// dudu-tweet
|
||||
//
|
||||
// Created by ching on 2023/5/2.
|
||||
//
|
||||
|
||||
import Firebase
|
||||
import SwiftUI
|
||||
|
||||
class AuthViewModel: ObservableObject {
|
||||
@Published var userSession: FirebaseAuth.User?
|
||||
@Published var didAuthenticateUser = false
|
||||
@Published var currentUser: User?
|
||||
private var tempUserSession: FirebaseAuth.User?
|
||||
|
||||
private let service = UserService()
|
||||
|
||||
init() {
|
||||
self.userSession = Auth.auth().currentUser
|
||||
self.fetchUser()
|
||||
print("DEBUG: user session is \(self.userSession?.uid)")
|
||||
}
|
||||
|
||||
func login(withEmail email: String, password: String) {
|
||||
print("DEBUG: login with email \(email)")
|
||||
Auth.auth().signIn(withEmail: email, password: password) { result, error in
|
||||
if let error = error {
|
||||
print("DEBUG: Failed to sign in with error \(error.localizedDescription)")
|
||||
return
|
||||
}
|
||||
|
||||
guard let user = result?.user else { return }
|
||||
self.userSession = user
|
||||
self.fetchUser()
|
||||
|
||||
print("DEBUG: Did log user \(user.uid) in.")
|
||||
}
|
||||
}
|
||||
|
||||
func register(withEmail email: String, password: String, fullname: String, username: String) {
|
||||
print("DEBUG: register with email \(email)")
|
||||
Auth.auth().createUser(withEmail: email, password: password) { result, error in
|
||||
if let error = error {
|
||||
print("DEBUG: Failed to register with error \(error.localizedDescription)")
|
||||
return
|
||||
}
|
||||
|
||||
guard let user = result?.user else { return }
|
||||
self.tempUserSession = user
|
||||
|
||||
let data = ["email": email,
|
||||
"username": username.lowercased(),
|
||||
"fullname": fullname]
|
||||
Firestore.firestore().collection("users")
|
||||
.document(user.uid)
|
||||
.setData(data) { _ in
|
||||
print("DEBUG: setData completion block called...")
|
||||
self.didAuthenticateUser = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func signOut() {
|
||||
userSession = nil
|
||||
try? Auth.auth().signOut()
|
||||
}
|
||||
|
||||
func uploadProfileImage(_ image: UIImage) {
|
||||
guard let uid = tempUserSession?.uid else { return }
|
||||
|
||||
ImageUploader.uploadImage(image: image) { profileImageUrl in
|
||||
Firestore.firestore().collection("users")
|
||||
.document(uid)
|
||||
.updateData(["profileImageUrl": profileImageUrl]) { _ in
|
||||
self.userSession = self.tempUserSession
|
||||
self.fetchUser()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fetchUser() {
|
||||
guard let uid = self.userSession?.uid else { return }
|
||||
service.fetchUser(withUid: uid) { user in
|
||||
self.currentUser = user
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,89 @@
|
||||
//
|
||||
// LoginView.swift
|
||||
// dudu-tweet
|
||||
//
|
||||
// Created by ching on 2023/5/2.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct LoginView: View {
|
||||
@State private var email = ""
|
||||
@State private var password = ""
|
||||
|
||||
@EnvironmentObject var viewModel: AuthViewModel
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
AuthHeaderView(title1: "Hello.", title2: "Welcome Back")
|
||||
VStack(spacing: 40) {
|
||||
CustomInputField(imageName: "envelope",
|
||||
palceholderText: "Email",
|
||||
text: $email)
|
||||
|
||||
CustomInputField(imageName: "lock",
|
||||
palceholderText: "Password",
|
||||
isSecureField: true,
|
||||
text: $password)
|
||||
}
|
||||
.padding(.horizontal, 32)
|
||||
.padding(.top, 44)
|
||||
|
||||
HStack {
|
||||
Spacer()
|
||||
NavigationLink {
|
||||
Text("Reset password view")
|
||||
} label: {
|
||||
Text("Forgot Password?")
|
||||
.font(.caption)
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(Color(.systemBlue))
|
||||
.padding(.top)
|
||||
.padding(.trailing, 24)
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
viewModel.login(withEmail: email, password: password)
|
||||
} label: {
|
||||
Text("Sign In")
|
||||
.font(.headline)
|
||||
.foregroundColor(.white)
|
||||
.frame(width: 340, height: 50)
|
||||
.background(Color(.systemBlue))
|
||||
.clipShape(Capsule())
|
||||
.padding()
|
||||
}
|
||||
.shadow(color: .gray.opacity(0.5), radius: 10, x:0, y:0)
|
||||
|
||||
Spacer()
|
||||
|
||||
NavigationLink {
|
||||
RegistrationView()
|
||||
// .navigationBarHidden(true)
|
||||
.toolbar(.hidden)
|
||||
} label: {
|
||||
HStack {
|
||||
Text("Don't have an account?")
|
||||
.font(.footnote)
|
||||
|
||||
Text("Sign Up")
|
||||
.font(.footnote)
|
||||
.fontWeight(.semibold)
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 32)
|
||||
.foregroundColor(Color(.systemBlue))
|
||||
}
|
||||
.ignoresSafeArea()
|
||||
// .navigationBarHidden(true)
|
||||
.toolbar(.hidden)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
struct LoginView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
LoginView()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,82 @@
|
||||
//
|
||||
// ProfilePhotoSelectorView.swift
|
||||
// dudu-tweet
|
||||
//
|
||||
// Created by ching on 2023/5/2.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ProfilePhotoSelectorView: View {
|
||||
@State private var showImagePicker = false
|
||||
@State private var selectedImage: UIImage?
|
||||
@State private var profileImage: Image?
|
||||
@EnvironmentObject var viewModel: AuthViewModel
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
AuthHeaderView(title1: "Create your account", title2: "Add a profile photo")
|
||||
|
||||
Button {
|
||||
print("Pick image here..")
|
||||
showImagePicker.toggle()
|
||||
} label: {
|
||||
if let profileImage = profileImage {
|
||||
profileImage
|
||||
.resizable()
|
||||
.modifier(ProfileImageModifier())
|
||||
} else {
|
||||
Image(systemName: "plus.circle")
|
||||
.resizable()
|
||||
.modifier(ProfileImageModifier())
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $showImagePicker, onDismiss: loadImage) {
|
||||
ImagePicker(selectedImage: $selectedImage)
|
||||
}
|
||||
.padding(.top, 44)
|
||||
|
||||
if let selectedImage = selectedImage {
|
||||
Button {
|
||||
print("DEBUG: Finishing register user")
|
||||
viewModel.uploadProfileImage(selectedImage)
|
||||
} label: {
|
||||
Text("Continue")
|
||||
.font(.headline)
|
||||
.foregroundColor(.white)
|
||||
.frame(width: 340, height: 50)
|
||||
.background(Color(.systemBlue))
|
||||
.clipShape(Capsule())
|
||||
.padding()
|
||||
}
|
||||
.shadow(color: .gray.opacity(0.5), radius: 10, x:0, y:0)
|
||||
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
}
|
||||
.ignoresSafeArea()
|
||||
}
|
||||
|
||||
func loadImage() {
|
||||
guard let selectedImage = selectedImage else { return }
|
||||
profileImage = Image(uiImage: selectedImage)
|
||||
}
|
||||
}
|
||||
|
||||
private struct ProfileImageModifier: ViewModifier {
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.foregroundColor(Color(.systemBlue))
|
||||
.scaledToFit()
|
||||
.frame(width: 180, height: 180)
|
||||
.clipShape(Circle())
|
||||
}
|
||||
}
|
||||
|
||||
struct ProfilePhotoSelectorView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ProfilePhotoSelectorView()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,89 @@
|
||||
//
|
||||
// RegistrationView.swift
|
||||
// dudu-tweet
|
||||
//
|
||||
// Created by ching on 2023/5/2.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct RegistrationView: View {
|
||||
@State private var email = ""
|
||||
@State private var username = ""
|
||||
@State private var fullname = ""
|
||||
@State private var password = ""
|
||||
|
||||
// @Environment(\.presentationMode) var presentationMode
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@EnvironmentObject var viewModel: AuthViewModel
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
AuthHeaderView(title1: "Get started.",
|
||||
title2: "Create your account")
|
||||
|
||||
NavigationLink(destination: ProfilePhotoSelectorView(),
|
||||
isActive: $viewModel.didAuthenticateUser,
|
||||
label: {})
|
||||
|
||||
VStack(spacing: 40) {
|
||||
CustomInputField(imageName: "envelope",
|
||||
palceholderText: "Email",
|
||||
text: $email)
|
||||
|
||||
CustomInputField(imageName: "person",
|
||||
palceholderText: "Username",
|
||||
text: $username)
|
||||
|
||||
CustomInputField(imageName: "person",
|
||||
palceholderText: "Full Name",
|
||||
text: $fullname)
|
||||
|
||||
CustomInputField(imageName: "lock",
|
||||
palceholderText: "Password",
|
||||
isSecureField: true,
|
||||
text: $password)
|
||||
}
|
||||
.padding(32)
|
||||
|
||||
Button {
|
||||
viewModel.register(withEmail: email,
|
||||
password: password,
|
||||
fullname: fullname,
|
||||
username: username)
|
||||
} label: {
|
||||
Text("Sign Up")
|
||||
.font(.headline)
|
||||
.foregroundColor(.white)
|
||||
.frame(width: 340, height: 50)
|
||||
.background(Color(.systemBlue))
|
||||
.clipShape(Capsule())
|
||||
.padding()
|
||||
}
|
||||
.shadow(color: .gray.opacity(0.5), radius: 10, x: 0, y: 0)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
dismiss()
|
||||
} label: {
|
||||
HStack {
|
||||
Text("Already have an account?")
|
||||
.font(.footnote)
|
||||
|
||||
Text("Sign In")
|
||||
.font(.footnote)
|
||||
.fontWeight(.semibold)
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 32)
|
||||
}
|
||||
.ignoresSafeArea()
|
||||
}
|
||||
}
|
||||
|
||||
struct RegistrationView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
RegistrationView()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
//
|
||||
// AuthHeaderView.swift
|
||||
// dudu-tweet
|
||||
//
|
||||
// Created by ching on 2023/5/2.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct AuthHeaderView: View {
|
||||
let title1: String
|
||||
let title2: String
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack {Spacer()}
|
||||
|
||||
Text(title1)
|
||||
.font(.largeTitle)
|
||||
.fontWeight(.semibold)
|
||||
|
||||
Text(title2)
|
||||
.font(.largeTitle)
|
||||
.fontWeight(.semibold)
|
||||
|
||||
}
|
||||
.frame(height: 260)
|
||||
.padding(.leading)
|
||||
.background(Color(.systemBlue))
|
||||
.foregroundColor(.white)
|
||||
.clipShape(RoundedShape(corners: [.bottomRight]))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
struct AuthenticationHeader_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
AuthHeaderView(title1: "hello,", title2: "world")
|
||||
}
|
||||
}
|
||||
43
dudu-tweet/dudu-tweet/Core/Components/CustomInputField.swift
Normal file
43
dudu-tweet/dudu-tweet/Core/Components/CustomInputField.swift
Normal file
@ -0,0 +1,43 @@
|
||||
//
|
||||
// CustomInputField.swift
|
||||
// dudu-tweet
|
||||
//
|
||||
// Created by ching on 2023/5/2.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct CustomInputField: View {
|
||||
let imageName: String
|
||||
let palceholderText: String
|
||||
var isSecureField: Bool? = false
|
||||
@Binding var text: String
|
||||
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
HStack {
|
||||
Image(systemName: imageName)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 20, height: 20)
|
||||
.foregroundColor(Color(.darkGray))
|
||||
if isSecureField ?? false {
|
||||
SecureField(palceholderText, text: $text)
|
||||
} else {
|
||||
TextField(palceholderText, text: $text)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Divider()
|
||||
.background(Color(.darkGray))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CustomInputField_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
CustomInputField(imageName: "envelope", palceholderText: "Email", text: .constant(""))
|
||||
}
|
||||
}
|
||||
17
dudu-tweet/dudu-tweet/Core/Components/RoundedShape.swift
Normal file
17
dudu-tweet/dudu-tweet/Core/Components/RoundedShape.swift
Normal file
@ -0,0 +1,17 @@
|
||||
//
|
||||
// RoundedShape.swift
|
||||
// dudu-tweet
|
||||
//
|
||||
// Created by ching on 2023/5/2.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct RoundedShape: Shape {
|
||||
var corners: UIRectCorner
|
||||
|
||||
func path(in rect: CGRect) -> Path {
|
||||
let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: 80, height: 80))
|
||||
return Path(path.cgPath)
|
||||
}
|
||||
}
|
||||
40
dudu-tweet/dudu-tweet/Core/Components/TextArea.swift
Normal file
40
dudu-tweet/dudu-tweet/Core/Components/TextArea.swift
Normal file
@ -0,0 +1,40 @@
|
||||
//
|
||||
// TextArea.swift
|
||||
// dudu-tweet
|
||||
//
|
||||
// Created by ching on 2023/5/2.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct TextArea: View {
|
||||
@Binding var text: String
|
||||
let placeholder: String
|
||||
|
||||
init(_ placeholder: String, text: Binding<String>) {
|
||||
self.placeholder = placeholder
|
||||
self._text = text
|
||||
UITextView.appearance().backgroundColor = .clear
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack(alignment: .topLeading) {
|
||||
TextEditor(text: $text)
|
||||
.padding(4)
|
||||
|
||||
if text.isEmpty {
|
||||
Text(placeholder)
|
||||
.foregroundColor(Color(.placeholderText))
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 12)
|
||||
}
|
||||
}
|
||||
.font(.body)
|
||||
}
|
||||
}
|
||||
|
||||
// struct TextArea_Previews: PreviewProvider {
|
||||
// static var previews: some View {
|
||||
// TextArea("preview text")
|
||||
// }
|
||||
// }
|
||||
@ -0,0 +1,34 @@
|
||||
//
|
||||
// UserRowView.swift
|
||||
// dudu-tweet
|
||||
//
|
||||
// Created by ching on 2023/5/2.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct UserRowView: View {
|
||||
var body: some View {
|
||||
HStack(spacing: 12) {
|
||||
Circle()
|
||||
.frame(width: 48, height: 48)
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text("Lith")
|
||||
.font(.subheadline).bold()
|
||||
.foregroundColor(.black)
|
||||
Text("李四")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.padding(.vertical, 4)
|
||||
}
|
||||
}
|
||||
|
||||
struct UserRowView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
UserRowView()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
//
|
||||
// UserStatsView.swift
|
||||
// dudu-tweet
|
||||
//
|
||||
// Created by ching on 2023/5/2.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct UserStatsView: View {
|
||||
var body: some View {
|
||||
HStack(spacing: 24) {
|
||||
HStack(spacing: 4) {
|
||||
Text("13214")
|
||||
.bold()
|
||||
.font(.subheadline)
|
||||
Text("Following")
|
||||
.font(.caption)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
HStack(spacing: 4) {
|
||||
Text("995353")
|
||||
.bold()
|
||||
.font(.subheadline)
|
||||
Text("Followers")
|
||||
.font(.caption)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct UserStatsView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
UserStatsView()
|
||||
}
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
//
|
||||
// ExploreView.swift
|
||||
// dudu-tweet
|
||||
//
|
||||
// Created by ching on 2023/5/2.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ExploreView: View {
|
||||
var body: some View {
|
||||
Text("Explore view")
|
||||
}
|
||||
}
|
||||
|
||||
struct ExploreView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ExploreView()
|
||||
}
|
||||
}
|
||||
37
dudu-tweet/dudu-tweet/Core/Explore/Views/ExploreView.swift
Normal file
37
dudu-tweet/dudu-tweet/Core/Explore/Views/ExploreView.swift
Normal file
@ -0,0 +1,37 @@
|
||||
//
|
||||
// ExploreView.swift
|
||||
// dudu-tweet
|
||||
//
|
||||
// Created by ching on 2023/5/2.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ExploreView: View {
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
VStack {
|
||||
ScrollView {
|
||||
LazyVStack {
|
||||
ForEach(0...25, id: \.self) { _ in
|
||||
NavigationLink {
|
||||
// ProfileView()
|
||||
} label: {
|
||||
UserRowView()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Explore")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ExploreView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ExploreView()
|
||||
}
|
||||
}
|
||||
@ -8,13 +8,35 @@
|
||||
import SwiftUI
|
||||
|
||||
struct FeedView: View {
|
||||
@State private var showNewTweetView = false
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
LazyVStack {
|
||||
ForEach(0 ... 20, id: \.self) { _ in
|
||||
TweetRowView()
|
||||
ZStack(alignment: .bottomTrailing) {
|
||||
ScrollView {
|
||||
LazyVStack {
|
||||
ForEach(0 ... 20, id: \.self) { _ in
|
||||
TweetRowView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
showNewTweetView.toggle()
|
||||
} label: {
|
||||
Image(systemName: "square.and.pencil")
|
||||
.resizable()
|
||||
.renderingMode(.template)
|
||||
.frame(width: 28, height: 28)
|
||||
.padding()
|
||||
}
|
||||
.background(Color(.systemBlue))
|
||||
.foregroundColor(.white)
|
||||
.clipShape(Circle())
|
||||
.padding()
|
||||
.fullScreenCover(isPresented: $showNewTweetView) {
|
||||
NewTweetView()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,11 +6,20 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Kingfisher
|
||||
|
||||
struct ProfileView: View {
|
||||
@State private var selectedFilter: TweetFilterViewModel = .tweets
|
||||
// @Environment(\.presentationMode) var mode // 用来从explore 页面跳转过来之后跳转回去
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
private let user: User
|
||||
|
||||
@Namespace var animation
|
||||
|
||||
init(user: User) {
|
||||
self.user = user
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
headerView
|
||||
@ -25,12 +34,17 @@ struct ProfileView: View {
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.toolbar(.hidden)
|
||||
}
|
||||
}
|
||||
|
||||
struct ProfileView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ProfileView()
|
||||
ProfileView(user: User(id: NSUUID().uuidString,
|
||||
username: "zhang3",
|
||||
fullname: "张三",
|
||||
profileImageUrl: "",
|
||||
email: "zhang3@gmail.com"))
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,16 +55,20 @@ extension ProfileView {
|
||||
.ignoresSafeArea()
|
||||
VStack {
|
||||
Button {
|
||||
// action here
|
||||
// mode.wrappedValue.dismiss()
|
||||
dismiss()
|
||||
} label: {
|
||||
Image(systemName: "arrow.left")
|
||||
.resizable()
|
||||
.frame(width: 20, height: 16)
|
||||
.foregroundColor(.white)
|
||||
.offset(x: 16, y: 12)
|
||||
.offset(x: 16, y: -4)
|
||||
}
|
||||
|
||||
Circle()
|
||||
KFImage(URL(string: user.profileImageUrl))
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.clipShape(Circle())
|
||||
.frame(width: 72, height: 72)
|
||||
.offset(x: 16, y: 24)
|
||||
}
|
||||
@ -82,12 +100,12 @@ extension ProfileView {
|
||||
var userInfoDetails: some View {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
HStack {
|
||||
Text("Zhang San")
|
||||
Text(user.fullname)
|
||||
.font(.title2).bold()
|
||||
Image(systemName: "checkmark.seal.fill")
|
||||
.foregroundColor(Color(.systemBlue))
|
||||
}
|
||||
Text("@zhang3")
|
||||
Text("@\(user.username)")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.gray)
|
||||
Text("一个普通人")
|
||||
@ -107,25 +125,8 @@ extension ProfileView {
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
|
||||
HStack(spacing: 24) {
|
||||
HStack(spacing: 4) {
|
||||
Text("13214")
|
||||
.bold()
|
||||
.font(.subheadline)
|
||||
Text("Following")
|
||||
.font(.caption)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
HStack(spacing: 4) {
|
||||
Text("995353")
|
||||
.bold()
|
||||
.font(.subheadline)
|
||||
Text("Followers")
|
||||
.font(.caption)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
.padding(.vertical)
|
||||
UserStatsView()
|
||||
.padding(.vertical)
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
|
||||
@ -0,0 +1,33 @@
|
||||
//
|
||||
// SideMenuViewModel.swift
|
||||
// dudu-tweet
|
||||
//
|
||||
// Created by ching on 2023/5/2.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum SideMenuViewModel: Int, CaseIterable {
|
||||
case profile
|
||||
case lists
|
||||
case bookmarks
|
||||
case logout
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
case .profile: return "Profile"
|
||||
case .lists: return "Lists"
|
||||
case .bookmarks: return "Bookmarks"
|
||||
case .logout: return "Logout"
|
||||
}
|
||||
}
|
||||
|
||||
var imageName: String {
|
||||
switch self {
|
||||
case .profile: return "person"
|
||||
case .lists: return "list.bullet"
|
||||
case .bookmarks: return "bookmark"
|
||||
case .logout: return "arrow.left.square"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
//
|
||||
// SideMenuOptionRowView.swift
|
||||
// dudu-tweet
|
||||
//
|
||||
// Created by ching on 2023/5/2.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct SideMenuOptionRowView: View {
|
||||
let viewModel: SideMenuViewModel
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing:16) {
|
||||
Image(systemName: viewModel.imageName)
|
||||
.font(.headline)
|
||||
.foregroundColor(.gray)
|
||||
|
||||
Text(viewModel.title)
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.black)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.frame(height: 40)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
}
|
||||
|
||||
struct SideMenuOptionRowView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SideMenuOptionRowView(viewModel: .profile)
|
||||
}
|
||||
}
|
||||
67
dudu-tweet/dudu-tweet/Core/SideMenu/Views/SideMenuView.swift
Normal file
67
dudu-tweet/dudu-tweet/Core/SideMenu/Views/SideMenuView.swift
Normal file
@ -0,0 +1,67 @@
|
||||
//
|
||||
// SideMenuView.swift
|
||||
// dudu-tweet
|
||||
//
|
||||
// Created by ching on 2023/5/2.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Kingfisher
|
||||
|
||||
struct SideMenuView: View {
|
||||
|
||||
@EnvironmentObject var authViewModel: AuthViewModel
|
||||
|
||||
var body: some View {
|
||||
if let user = authViewModel.currentUser {
|
||||
VStack(alignment: .leading, spacing: 32) {
|
||||
VStack(alignment: .leading) {
|
||||
KFImage(URL(string: user.profileImageUrl))
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.clipShape(Circle())
|
||||
.frame(width: 48, height: 48)
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(user.fullname)
|
||||
.font(.headline)
|
||||
Text("@\(user.username)")
|
||||
.font(.caption)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
|
||||
UserStatsView()
|
||||
.padding(.vertical)
|
||||
}
|
||||
.padding(.leading)
|
||||
|
||||
ForEach(SideMenuViewModel.allCases, id: \.rawValue) { viewModel in
|
||||
if viewModel == .profile {
|
||||
NavigationLink {
|
||||
ProfileView(user: user)
|
||||
} label: {
|
||||
SideMenuOptionRowView(viewModel: viewModel)
|
||||
}
|
||||
} else if viewModel == .logout {
|
||||
Button {
|
||||
authViewModel.signOut()
|
||||
} label: {
|
||||
SideMenuOptionRowView(viewModel: viewModel)
|
||||
}
|
||||
|
||||
} else {
|
||||
SideMenuOptionRowView(viewModel: viewModel)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SideMenuView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SideMenuView()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
//
|
||||
// NewTweetView.swift
|
||||
// dudu-tweet
|
||||
//
|
||||
// Created by ching on 2023/5/2.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct NewTweetView: View {
|
||||
@State private var caption = ""
|
||||
// @Environment(\.presentationMode) var presentationMode
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
HStack {
|
||||
Button {
|
||||
// presentationMode.wrappedValue.dismiss()
|
||||
dismiss()
|
||||
} label: {
|
||||
Text("Cancel")
|
||||
.foregroundColor(Color(.systemBlue))
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
print("tweet this..")
|
||||
} label: {
|
||||
Text("Tweet")
|
||||
.bold()
|
||||
.padding(.horizontal)
|
||||
.padding(.vertical, 8)
|
||||
.background(Color(.systemBlue))
|
||||
.foregroundColor(.white)
|
||||
.clipShape(Capsule())
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
|
||||
HStack(alignment: .top) {
|
||||
Circle()
|
||||
.frame(width: 64, height: 64)
|
||||
TextArea("What's happening?", text: $caption)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct NewTweetView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
NewTweetView()
|
||||
}
|
||||
}
|
||||
16
dudu-tweet/dudu-tweet/Model/User.swift
Normal file
16
dudu-tweet/dudu-tweet/Model/User.swift
Normal file
@ -0,0 +1,16 @@
|
||||
//
|
||||
// User.swift
|
||||
// dudu-tweet
|
||||
//
|
||||
// Created by ching on 2023/5/2.
|
||||
//
|
||||
|
||||
import FirebaseFirestoreSwift
|
||||
|
||||
struct User: Identifiable, Decodable {
|
||||
@DocumentID var id: String?
|
||||
let username: String
|
||||
let fullname: String
|
||||
let profileImageUrl: String
|
||||
let email: String
|
||||
}
|
||||
31
dudu-tweet/dudu-tweet/Service/ImageUploader.swift
Normal file
31
dudu-tweet/dudu-tweet/Service/ImageUploader.swift
Normal file
@ -0,0 +1,31 @@
|
||||
//
|
||||
// ImageUploader.swift
|
||||
// dudu-tweet
|
||||
//
|
||||
// Created by ching on 2023/5/2.
|
||||
//
|
||||
|
||||
import Firebase
|
||||
import FirebaseStorage
|
||||
import UIKit
|
||||
|
||||
struct ImageUploader {
|
||||
static func uploadImage(image: UIImage, completion: @escaping (String) -> Void) {
|
||||
guard let imageData = image.jpegData(compressionQuality: 0.5) else { return }
|
||||
|
||||
let filename = NSUUID().uuidString
|
||||
let ref = Storage.storage().reference(withPath: "/profile_image/\(filename)")
|
||||
|
||||
ref.putData(imageData, metadata: nil) { _, error in
|
||||
if let error = error {
|
||||
print("DEBUG: Failed to upload image with error \(error.localizedDescription)")
|
||||
return
|
||||
}
|
||||
|
||||
ref.downloadURL { imageUrl, _ in
|
||||
guard let imageUrl = imageUrl?.absoluteURL else { return }
|
||||
completion(imageUrl.absoluteString)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
24
dudu-tweet/dudu-tweet/Service/UserService.swift
Normal file
24
dudu-tweet/dudu-tweet/Service/UserService.swift
Normal file
@ -0,0 +1,24 @@
|
||||
//
|
||||
// UserService.swift
|
||||
// dudu-tweet
|
||||
//
|
||||
// Created by ching on 2023/5/2.
|
||||
//
|
||||
|
||||
import Firebase
|
||||
import FirebaseFirestoreSwift
|
||||
|
||||
struct UserService {
|
||||
func fetchUser(withUid uid: String, completion: @escaping(User) -> Void) {
|
||||
print("DEBUG: Fetching user info..")
|
||||
Firestore.firestore().collection("users")
|
||||
.document(uid)
|
||||
.getDocument { snapshot, _ in
|
||||
guard let snapshot = snapshot else { return }
|
||||
guard let user = try? snapshot.data(as: User.self) else { return }
|
||||
print("DEBUG: Username is \(user.username)")
|
||||
print("DEBUG: User id is \(user.id)")
|
||||
completion(user)
|
||||
}
|
||||
}
|
||||
}
|
||||
44
dudu-tweet/dudu-tweet/Utils/ImagePicker.swift
Normal file
44
dudu-tweet/dudu-tweet/Utils/ImagePicker.swift
Normal file
@ -0,0 +1,44 @@
|
||||
//
|
||||
// ImagePicker.swift
|
||||
// dudu-tweet
|
||||
//
|
||||
// Created by ching on 2023/5/2.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ImagePicker: UIViewControllerRepresentable {
|
||||
@Binding var selectedImage: UIImage?
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
return Coordinator(self)
|
||||
}
|
||||
|
||||
func makeUIViewController(context: Context) -> some UIViewController {
|
||||
let picker = UIImagePickerController()
|
||||
picker.delegate = context.coordinator
|
||||
return picker
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
extension ImagePicker {
|
||||
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
|
||||
|
||||
let parent: ImagePicker
|
||||
|
||||
init(_ parent: ImagePicker) {
|
||||
self.parent = parent
|
||||
}
|
||||
|
||||
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
|
||||
guard let image = info[.originalImage] as? UIImage else { return }
|
||||
parent.selectedImage = image
|
||||
parent.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6,12 +6,25 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Firebase
|
||||
|
||||
@main
|
||||
struct dudu_tweetApp: App {
|
||||
|
||||
@StateObject var viewModel = AuthViewModel()
|
||||
|
||||
|
||||
init() {
|
||||
FirebaseApp.configure()
|
||||
}
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
MainTabView()
|
||||
NavigationView {
|
||||
ContentView()
|
||||
// ProfilePhotoSelectorView()
|
||||
}
|
||||
.environmentObject(viewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user