feat(ios): tab tabanlı navigasyon ve okuma durumu takibi ekle
This commit is contained in:
@@ -3,192 +3,201 @@
|
|||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 46;
|
objectVersion = 54;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
0987C082DE634D36D5BF03DE /* ShelfSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C361617CEBCE704307DA0A9 /* ShelfSectionView.swift */; };
|
07D06DDC629661D80D4BB7A2 /* CategoryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27AEC6ED0A12CE62E5E7B42F /* CategoryViewModel.swift */; };
|
||||||
1DCD42AC02DDABACC54958C5 /* UserProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 923CE0DF37333FFAF75668D5 /* UserProfile.swift */; };
|
0E57D2E3B73CC64C45A13E5A /* AuthViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86CC67809D9F26DFB7F53280 /* AuthViewModel.swift */; };
|
||||||
245681EBBC7EB40F2733DD6B /* NetworkErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72DED90139ADCDD834AE33CF /* NetworkErrorView.swift */; };
|
1569FBE19975206A58F8E694 /* ReadingStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = C94DACFF2F104F68F5E72793 /* ReadingStatus.swift */; };
|
||||||
2C2B30975D5DC09342690C43 /* BlurFogOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC6F6AF589E6DD8E8EEF14CF /* BlurFogOverlay.swift */; };
|
17B5B7FBC0025E80FA65036F /* ScrewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 091D8A6C89ACBCDD9D022FB4 /* ScrewView.swift */; };
|
||||||
31B3807F97E8E1512FB5DEEA /* CategoryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C15179D9ED03860747BBC180 /* CategoryViewModel.swift */; };
|
1B120941F76A7C9ACC9B82C2 /* CategoryListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D4C72C39F36045BF4D5ECAF /* CategoryListView.swift */; };
|
||||||
438812F1044DE6EBEA5B42E6 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 72FE6CAAF6A00908AC19835F /* AVFoundation.framework */; };
|
256166E4812B7DF17BD32FD4 /* VisionKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DDA87F942C70D1FE96ED9C4 /* VisionKit.framework */; };
|
||||||
44B8242D7211EDD650FCA488 /* BarcodeScannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 471C2927142735752EA378A9 /* BarcodeScannerView.swift */; };
|
3DEF8AE33F0E47F9E942FAD6 /* AuthView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12C0E4FA7E8B3F62D198485B /* AuthView.swift */; };
|
||||||
4EE9C0CB6C3006780E8CDFA4 /* BookCoverCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 349C2A1F39419A03C31114C3 /* BookCoverCard.swift */; };
|
4FC8807E8263C91ADA8FA591 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 479C2FCDB84B962FF4BF68AE /* ImageCache.swift */; };
|
||||||
5B73E67A2A3873F06000D8AF /* VisionKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1744A67EB267139F1EBDAE55 /* VisionKit.framework */; };
|
546CB89AAC170EE1B541830D /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BC26B431A2E4633F2FC89CA /* HomeView.swift */; };
|
||||||
5DD75F144A0C411F2D7EF9C8 /* LibraryBook.swift in Sources */ = {isa = PBXBuildFile; fileRef = B93B9F6AC3711B6E32030129 /* LibraryBook.swift */; };
|
686D28482BE40BE453EFE0D3 /* mock_book_remote.json in Resources */ = {isa = PBXBuildFile; fileRef = 0E5A7ADF9962345CB78AC571 /* mock_book_remote.json */; };
|
||||||
6998E51506433C1B0A647330 /* BookibraApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F8155E61788F47524B11449 /* BookibraApp.swift */; };
|
69CAD3618DD77D79F462C76E /* SwiftData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 87EF451B4A1589BBCB00D7F8 /* SwiftData.framework */; };
|
||||||
6B60107AEE8C2489D19B1D9D /* mock_book_remote.json in Resources */ = {isa = PBXBuildFile; fileRef = 95A0AD7A2591540C4ED3252F /* mock_book_remote.json */; };
|
6E060ACBDE0AD85FDB2F8010 /* AuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC488BF2DA3B6B94C2958E8 /* AuthService.swift */; };
|
||||||
6E9DFC74E4EA2AC64A343E4C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0417E5217F2A37B2065F6DC9 /* Assets.xcassets */; };
|
6F398B380F29EF5C2C6E1653 /* APIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EDA9B5DE02839213D93F6A9 /* APIClient.swift */; };
|
||||||
7C130ABD8F4627EA3C0FB239 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2448E99A3D8CDBD5334901C7 /* ImageCache.swift */; };
|
6F59713411606CDC297CF733 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5EABC698DDB8062029E5E9FA /* Foundation.framework */; };
|
||||||
7C17970A1EC6CE66AB2B6962 /* BookDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96C4EA4FE11115BA86AB2802 /* BookDetailViewModel.swift */; };
|
72212EAE36C9151956F84262 /* ShelfSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0887E6542AB83E17C9490AF /* ShelfSectionView.swift */; };
|
||||||
7C5391EE19CD4370B0871A6F /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF18379D6C3B16C83CCBCF22 /* HomeView.swift */; };
|
76CDBAECDA13604B0A3EC338 /* BookDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 523E0F614C3E08527D265ADC /* BookDetailView.swift */; };
|
||||||
89E2012E58DEB2A6CA3191F9 /* AddBooksViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BB571FD5EA0CB9C15DBD1DB /* AddBooksViewModel.swift */; };
|
7FC47B691C19F4E6C1102606 /* BookCoverCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E0C3AF58827D6FBF93157B7 /* BookCoverCard.swift */; };
|
||||||
90E97C917EEA4B7B1D09F8FB /* BookRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EB98ADB94703A1CE74B79CD /* BookRemote.swift */; };
|
8287AA6A6111C8E14BA92E81 /* BarcodeScannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06BEE917DB0A13B5EA34D030 /* BarcodeScannerView.swift */; };
|
||||||
9DF5677130BA3D0F14C04B4B /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEB5F3DC625CEAE1BEC3911F /* HomeViewModel.swift */; };
|
865A3EBCDA84FBA692A31939 /* EmptyStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A850CCC78FC41E21559CE03A /* EmptyStateView.swift */; };
|
||||||
A08E9B72A9FAA96CC58C82B1 /* AuthView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 329388F7DE8EB288AEE98A23 /* AuthView.swift */; };
|
8707C5BBD8347B96B1E4DFB8 /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0563C287DF8A317E7F6D4A22 /* HomeViewModel.swift */; };
|
||||||
A5BC1762D555E6DC13C8664E /* AuthViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF5F3C9BA827C89A343166A4 /* AuthViewModel.swift */; };
|
919B2721944E5A9DF8F696AF /* KeychainStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF7DCABD0AFF317C4C25C1D3 /* KeychainStore.swift */; };
|
||||||
B404D223478123428719790C /* AppRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4DF6CD123627625C1967D42 /* AppRouter.swift */; };
|
9296F2BCE6EB7FFAC4F6A5B1 /* NetworkErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 026D1C09514A01EB0D4C7641 /* NetworkErrorView.swift */; };
|
||||||
B66DAE4BF97BCAC84740B541 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 20947201FBE7D30CD6F69E38 /* Localizable.strings */; };
|
971C5D3C6576578B4B4B8CBB /* BooksService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1F2AEEF3846E89EF58E8E2B /* BooksService.swift */; };
|
||||||
BDE45343461F02318DD86FDB /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1B0A599E6FA80C6DBB852EB5 /* Localizable.strings */; };
|
97FD0730276CEBBE6BDBCCA1 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B3CA8360A84C636945C83DD0 /* Localizable.strings */; };
|
||||||
C9656D40284BF3D44322AE99 /* BookDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A39116C7E45F12244CA1DC23 /* BookDetailView.swift */; };
|
9C71E8718C4820EFD2F50C17 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0A47804190A193B8EE3159EA /* Assets.xcassets */; };
|
||||||
CB31E95DBEF85410677B11E3 /* PrimaryPillButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE7B76B449982D19D91A67F3 /* PrimaryPillButton.swift */; };
|
A597F6A4E13688A21BD3FEAE /* LibraryBook.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB981066E192B332E6BB5E2 /* LibraryBook.swift */; };
|
||||||
CD404352E3F12BCAE64E4BAC /* ScrewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8DD9EB12817D19C6BC65306 /* ScrewView.swift */; };
|
A6E600A345801C9AF1CB1F10 /* AddBooksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46E960D3986FCFF358D1CC83 /* AddBooksView.swift */; };
|
||||||
D146299A54D7A660951C3075 /* AddBooksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DA01B4D0A49FA8E5D1E4F65 /* AddBooksView.swift */; };
|
AE16C52E1D5A765D0723F243 /* AppRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AC4F542A93E316D86251043 /* AppRouter.swift */; };
|
||||||
D2B224D07CFBA048947BCB22 /* SwiftData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 562B9464344AA711558F2AD0 /* SwiftData.framework */; };
|
B1E7E3F062B3B01ED5EB7019 /* BlurFogOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3448927BF4293DFAF113048 /* BlurFogOverlay.swift */; };
|
||||||
D6D4F249AA8A85A041C7D112 /* CategoryListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D117FECFDDAC2EDDC191CEC9 /* CategoryListView.swift */; };
|
B37F57B2BB478E72702A551E /* UserProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9547DCE4663825EA1267F3C /* UserProfile.swift */; };
|
||||||
E1E4040DBBACA7268F84998B /* KeychainStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 806F17D3E801BC96C6B5ED6D /* KeychainStore.swift */; };
|
C59A615F50B6A23903E02280 /* BookCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 216DFB59A593E4AD349FCE0F /* BookCardView.swift */; };
|
||||||
E7D483E62D94D20A511C6967 /* APIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = B618484EE9FC15B0DCA5E055 /* APIClient.swift */; };
|
CE6FC5E5744AA2A05C3B6049 /* BookRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3D6631E1FD7160883FC31F4 /* BookRemote.swift */; };
|
||||||
EAA4D823C890C98F37F64E1E /* BooksService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A77D7EF1C78724F79956D5B1 /* BooksService.swift */; };
|
D9D5AD82B32F16BD92E82238 /* PrimaryPillButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D60CE4B6FDA59B1114B8168 /* PrimaryPillButton.swift */; };
|
||||||
EC793F00D7851722C0DD1633 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B226CA006E6EEA7DB04F100 /* Theme.swift */; };
|
DC0602EC768B6265564FE8FD /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 395E4C35E6D3C362B550AF3B /* Localizable.strings */; };
|
||||||
F3452503ADD5962494ABB38D /* AuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAF98B022FA5BAA8F627DAAD /* AuthService.swift */; };
|
E0FC3411B06A5726172021BE /* AddBooksViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40AD40B111A582310571F11E /* AddBooksViewModel.swift */; };
|
||||||
F971998248197B0A0848FC88 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 192DFF277BADE93F9813BFFA /* Foundation.framework */; };
|
E318F3175C8B01CEA69F50C9 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67C4E14D093F318ECAB9FA6A /* Theme.swift */; };
|
||||||
|
EB4366DC44468E12BCE5177D /* BookibraApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8F25BA817C2459E1751520C /* BookibraApp.swift */; };
|
||||||
|
ECDD56903097A589E63C7D64 /* BookDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09E9BD1CDD8189A9BD8F93C0 /* BookDetailViewModel.swift */; };
|
||||||
|
F3E86067A1062297B95CE2CA /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DF373FA01D444E33C45D1B74 /* AVFoundation.framework */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
0417E5217F2A37B2065F6DC9 /* Assets.xcassets */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Resources/Assets.xcassets; sourceTree = "<group>"; };
|
026D1C09514A01EB0D4C7641 /* NetworkErrorView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NetworkErrorView.swift; path = DesignSystem/Components/NetworkErrorView.swift; sourceTree = "<group>"; };
|
||||||
1744A67EB267139F1EBDAE55 /* VisionKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = VisionKit.framework; path = System/Library/Frameworks/VisionKit.framework; sourceTree = "<group>"; };
|
0563C287DF8A317E7F6D4A22 /* HomeViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = HomeViewModel.swift; path = ViewModels/HomeViewModel.swift; sourceTree = "<group>"; };
|
||||||
192DFF277BADE93F9813BFFA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; };
|
06BEE917DB0A13B5EA34D030 /* BarcodeScannerView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BarcodeScannerView.swift; path = Views/AddBooks/BarcodeScannerView.swift; sourceTree = "<group>"; };
|
||||||
1B0A599E6FA80C6DBB852EB5 /* Localizable.strings */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.strings; name = Localizable.strings; path = Resources/tr.lproj/Localizable.strings; sourceTree = "<group>"; };
|
091D8A6C89ACBCDD9D022FB4 /* ScrewView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ScrewView.swift; path = DesignSystem/Components/ScrewView.swift; sourceTree = "<group>"; };
|
||||||
1EB98ADB94703A1CE74B79CD /* BookRemote.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BookRemote.swift; path = Models/BookRemote.swift; sourceTree = "<group>"; };
|
09E9BD1CDD8189A9BD8F93C0 /* BookDetailViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BookDetailViewModel.swift; path = ViewModels/BookDetailViewModel.swift; sourceTree = "<group>"; };
|
||||||
20947201FBE7D30CD6F69E38 /* Localizable.strings */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.strings; name = Localizable.strings; path = Resources/en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
0A47804190A193B8EE3159EA /* Assets.xcassets */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Resources/Assets.xcassets; sourceTree = "<group>"; };
|
||||||
2448E99A3D8CDBD5334901C7 /* ImageCache.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageCache.swift; path = Services/ImageCache.swift; sourceTree = "<group>"; };
|
0E5A7ADF9962345CB78AC571 /* mock_book_remote.json */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.json; name = mock_book_remote.json; path = Resources/mock_book_remote.json; sourceTree = "<group>"; };
|
||||||
2F8155E61788F47524B11449 /* BookibraApp.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BookibraApp.swift; path = App/BookibraApp.swift; sourceTree = "<group>"; };
|
12C0E4FA7E8B3F62D198485B /* AuthView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AuthView.swift; path = Views/Auth/AuthView.swift; sourceTree = "<group>"; };
|
||||||
329388F7DE8EB288AEE98A23 /* AuthView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AuthView.swift; path = Views/Auth/AuthView.swift; sourceTree = "<group>"; };
|
216DFB59A593E4AD349FCE0F /* BookCardView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BookCardView.swift; path = DesignSystem/Components/BookCardView.swift; sourceTree = "<group>"; };
|
||||||
349C2A1F39419A03C31114C3 /* BookCoverCard.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BookCoverCard.swift; path = DesignSystem/Components/BookCoverCard.swift; sourceTree = "<group>"; };
|
27AEC6ED0A12CE62E5E7B42F /* CategoryViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CategoryViewModel.swift; path = ViewModels/CategoryViewModel.swift; sourceTree = "<group>"; };
|
||||||
3B226CA006E6EEA7DB04F100 /* Theme.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Theme.swift; path = DesignSystem/Theme.swift; sourceTree = "<group>"; };
|
2D4C72C39F36045BF4D5ECAF /* CategoryListView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CategoryListView.swift; path = Views/Category/CategoryListView.swift; sourceTree = "<group>"; };
|
||||||
3BB571FD5EA0CB9C15DBD1DB /* AddBooksViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AddBooksViewModel.swift; path = ViewModels/AddBooksViewModel.swift; sourceTree = "<group>"; };
|
395E4C35E6D3C362B550AF3B /* Localizable.strings */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.strings; name = Localizable.strings; path = Resources/tr.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
471C2927142735752EA378A9 /* BarcodeScannerView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BarcodeScannerView.swift; path = Views/AddBooks/BarcodeScannerView.swift; sourceTree = "<group>"; };
|
3BC26B431A2E4633F2FC89CA /* HomeView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = HomeView.swift; path = Views/Home/HomeView.swift; sourceTree = "<group>"; };
|
||||||
4C361617CEBCE704307DA0A9 /* ShelfSectionView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ShelfSectionView.swift; path = DesignSystem/Components/ShelfSectionView.swift; sourceTree = "<group>"; };
|
3D60CE4B6FDA59B1114B8168 /* PrimaryPillButton.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PrimaryPillButton.swift; path = DesignSystem/Components/PrimaryPillButton.swift; sourceTree = "<group>"; };
|
||||||
4DA01B4D0A49FA8E5D1E4F65 /* AddBooksView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AddBooksView.swift; path = Views/AddBooks/AddBooksView.swift; sourceTree = "<group>"; };
|
3DDA87F942C70D1FE96ED9C4 /* VisionKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = VisionKit.framework; path = System/Library/Frameworks/VisionKit.framework; sourceTree = "<group>"; };
|
||||||
562B9464344AA711558F2AD0 /* SwiftData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftData.framework; path = System/Library/Frameworks/SwiftData.framework; sourceTree = "<group>"; };
|
40AD40B111A582310571F11E /* AddBooksViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AddBooksViewModel.swift; path = ViewModels/AddBooksViewModel.swift; sourceTree = "<group>"; };
|
||||||
72DED90139ADCDD834AE33CF /* NetworkErrorView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NetworkErrorView.swift; path = DesignSystem/Components/NetworkErrorView.swift; sourceTree = "<group>"; };
|
46E960D3986FCFF358D1CC83 /* AddBooksView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AddBooksView.swift; path = Views/AddBooks/AddBooksView.swift; sourceTree = "<group>"; };
|
||||||
72FE6CAAF6A00908AC19835F /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = "<group>"; };
|
479C2FCDB84B962FF4BF68AE /* ImageCache.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageCache.swift; path = Services/ImageCache.swift; sourceTree = "<group>"; };
|
||||||
7B6C1F4EB35DF0216BC86061 /* Bookibra.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Bookibra.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
4AC4F542A93E316D86251043 /* AppRouter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AppRouter.swift; path = App/AppRouter.swift; sourceTree = "<group>"; };
|
||||||
806F17D3E801BC96C6B5ED6D /* KeychainStore.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KeychainStore.swift; path = Services/KeychainStore.swift; sourceTree = "<group>"; };
|
4E0C3AF58827D6FBF93157B7 /* BookCoverCard.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BookCoverCard.swift; path = DesignSystem/Components/BookCoverCard.swift; sourceTree = "<group>"; };
|
||||||
923CE0DF37333FFAF75668D5 /* UserProfile.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UserProfile.swift; path = Models/UserProfile.swift; sourceTree = "<group>"; };
|
4EDA9B5DE02839213D93F6A9 /* APIClient.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = APIClient.swift; path = Services/APIClient.swift; sourceTree = "<group>"; };
|
||||||
95A0AD7A2591540C4ED3252F /* mock_book_remote.json */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.json; name = mock_book_remote.json; path = Resources/mock_book_remote.json; sourceTree = "<group>"; };
|
523E0F614C3E08527D265ADC /* BookDetailView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BookDetailView.swift; path = Views/Detail/BookDetailView.swift; sourceTree = "<group>"; };
|
||||||
96C4EA4FE11115BA86AB2802 /* BookDetailViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BookDetailViewModel.swift; path = ViewModels/BookDetailViewModel.swift; sourceTree = "<group>"; };
|
5EABC698DDB8062029E5E9FA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; };
|
||||||
98AAF8E8F7F53CA9CE81186B /* Release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Bookibra/Resources/Release.xcconfig; sourceTree = "<group>"; };
|
67C4E14D093F318ECAB9FA6A /* Theme.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Theme.swift; path = DesignSystem/Theme.swift; sourceTree = "<group>"; };
|
||||||
A39116C7E45F12244CA1DC23 /* BookDetailView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BookDetailView.swift; path = Views/Detail/BookDetailView.swift; sourceTree = "<group>"; };
|
86CC67809D9F26DFB7F53280 /* AuthViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AuthViewModel.swift; path = ViewModels/AuthViewModel.swift; sourceTree = "<group>"; };
|
||||||
A77D7EF1C78724F79956D5B1 /* BooksService.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BooksService.swift; path = Services/BooksService.swift; sourceTree = "<group>"; };
|
87EF451B4A1589BBCB00D7F8 /* SwiftData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftData.framework; path = System/Library/Frameworks/SwiftData.framework; sourceTree = "<group>"; };
|
||||||
AF5F3C9BA827C89A343166A4 /* AuthViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AuthViewModel.swift; path = ViewModels/AuthViewModel.swift; sourceTree = "<group>"; };
|
9D96BF3D1EBDBB9B18FE826D /* Bookibra.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Bookibra.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
AFD5A6DD3D2BFC014B9AB859 /* Debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Bookibra/Resources/Debug.xcconfig; sourceTree = "<group>"; };
|
A850CCC78FC41E21559CE03A /* EmptyStateView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = EmptyStateView.swift; path = DesignSystem/Components/EmptyStateView.swift; sourceTree = "<group>"; };
|
||||||
B618484EE9FC15B0DCA5E055 /* APIClient.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = APIClient.swift; path = Services/APIClient.swift; sourceTree = "<group>"; };
|
A8F25BA817C2459E1751520C /* BookibraApp.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BookibraApp.swift; path = App/BookibraApp.swift; sourceTree = "<group>"; };
|
||||||
B93B9F6AC3711B6E32030129 /* LibraryBook.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LibraryBook.swift; path = Models/LibraryBook.swift; sourceTree = "<group>"; };
|
B3448927BF4293DFAF113048 /* BlurFogOverlay.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BlurFogOverlay.swift; path = DesignSystem/Components/BlurFogOverlay.swift; sourceTree = "<group>"; };
|
||||||
BEB5F3DC625CEAE1BEC3911F /* HomeViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = HomeViewModel.swift; path = ViewModels/HomeViewModel.swift; sourceTree = "<group>"; };
|
B3CA8360A84C636945C83DD0 /* Localizable.strings */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.strings; name = Localizable.strings; path = Resources/en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
BF18379D6C3B16C83CCBCF22 /* HomeView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = HomeView.swift; path = Views/Home/HomeView.swift; sourceTree = "<group>"; };
|
B44591FBA4BB2AEA612510DD /* Debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Bookibra/Resources/Debug.xcconfig; sourceTree = "<group>"; };
|
||||||
C15179D9ED03860747BBC180 /* CategoryViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CategoryViewModel.swift; path = ViewModels/CategoryViewModel.swift; sourceTree = "<group>"; };
|
C0887E6542AB83E17C9490AF /* ShelfSectionView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ShelfSectionView.swift; path = DesignSystem/Components/ShelfSectionView.swift; sourceTree = "<group>"; };
|
||||||
C8DD9EB12817D19C6BC65306 /* ScrewView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ScrewView.swift; path = DesignSystem/Components/ScrewView.swift; sourceTree = "<group>"; };
|
C94DACFF2F104F68F5E72793 /* ReadingStatus.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ReadingStatus.swift; path = Models/ReadingStatus.swift; sourceTree = "<group>"; };
|
||||||
CAF98B022FA5BAA8F627DAAD /* AuthService.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AuthService.swift; path = Services/AuthService.swift; sourceTree = "<group>"; };
|
CAC488BF2DA3B6B94C2958E8 /* AuthService.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AuthService.swift; path = Services/AuthService.swift; sourceTree = "<group>"; };
|
||||||
D117FECFDDAC2EDDC191CEC9 /* CategoryListView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CategoryListView.swift; path = Views/Category/CategoryListView.swift; sourceTree = "<group>"; };
|
D1F2AEEF3846E89EF58E8E2B /* BooksService.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BooksService.swift; path = Services/BooksService.swift; sourceTree = "<group>"; };
|
||||||
DC6F6AF589E6DD8E8EEF14CF /* BlurFogOverlay.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BlurFogOverlay.swift; path = DesignSystem/Components/BlurFogOverlay.swift; sourceTree = "<group>"; };
|
D9547DCE4663825EA1267F3C /* UserProfile.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UserProfile.swift; path = Models/UserProfile.swift; sourceTree = "<group>"; };
|
||||||
E4DF6CD123627625C1967D42 /* AppRouter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AppRouter.swift; path = App/AppRouter.swift; sourceTree = "<group>"; };
|
DF373FA01D444E33C45D1B74 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = "<group>"; };
|
||||||
FE7B76B449982D19D91A67F3 /* PrimaryPillButton.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PrimaryPillButton.swift; path = DesignSystem/Components/PrimaryPillButton.swift; sourceTree = "<group>"; };
|
E3D6631E1FD7160883FC31F4 /* BookRemote.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BookRemote.swift; path = Models/BookRemote.swift; sourceTree = "<group>"; };
|
||||||
|
FDC0CE87CC54E1384BD6557B /* Release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Bookibra/Resources/Release.xcconfig; sourceTree = "<group>"; };
|
||||||
|
FF7DCABD0AFF317C4C25C1D3 /* KeychainStore.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KeychainStore.swift; path = Services/KeychainStore.swift; sourceTree = "<group>"; };
|
||||||
|
FFB981066E192B332E6BB5E2 /* LibraryBook.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LibraryBook.swift; path = Models/LibraryBook.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
96BCE7EBCFE3A1B03D95C0A5 /* Frameworks */ = {
|
E88BA262CD3F12CEBF38184A /* Frameworks */ = {
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
F971998248197B0A0848FC88 /* Foundation.framework in Frameworks */,
|
6F59713411606CDC297CF733 /* Foundation.framework in Frameworks */,
|
||||||
438812F1044DE6EBEA5B42E6 /* AVFoundation.framework in Frameworks */,
|
F3E86067A1062297B95CE2CA /* AVFoundation.framework in Frameworks */,
|
||||||
5B73E67A2A3873F06000D8AF /* VisionKit.framework in Frameworks */,
|
256166E4812B7DF17BD32FD4 /* VisionKit.framework in Frameworks */,
|
||||||
D2B224D07CFBA048947BCB22 /* SwiftData.framework in Frameworks */,
|
69CAD3618DD77D79F462C76E /* SwiftData.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
11824F4150C100F76F38DBD8 = {
|
1F97BBDD9672559E121C4FFA /* Frameworks */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
94F7452CA97CED98C86AF84E /* Products */,
|
71736683D1BB2B9A0FF730B9 /* iOS */,
|
||||||
8EB4884498D4ACBA5BB04312 /* Frameworks */,
|
DF373FA01D444E33C45D1B74 /* AVFoundation.framework */,
|
||||||
AFD5A6DD3D2BFC014B9AB859 /* Debug.xcconfig */,
|
3DDA87F942C70D1FE96ED9C4 /* VisionKit.framework */,
|
||||||
98AAF8E8F7F53CA9CE81186B /* Release.xcconfig */,
|
87EF451B4A1589BBCB00D7F8 /* SwiftData.framework */,
|
||||||
F2E80CFCFD2FE3BF793C4147 /* Bookibra */,
|
|
||||||
);
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
8EB4884498D4ACBA5BB04312 /* Frameworks */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
E592B0F1D9DDB4B2033A4A3D /* iOS */,
|
|
||||||
72FE6CAAF6A00908AC19835F /* AVFoundation.framework */,
|
|
||||||
1744A67EB267139F1EBDAE55 /* VisionKit.framework */,
|
|
||||||
562B9464344AA711558F2AD0 /* SwiftData.framework */,
|
|
||||||
);
|
);
|
||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
94F7452CA97CED98C86AF84E /* Products */ = {
|
3D7744C35B489BFB545D42AD /* Products */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
7B6C1F4EB35DF0216BC86061 /* Bookibra.app */,
|
9D96BF3D1EBDBB9B18FE826D /* Bookibra.app */,
|
||||||
);
|
);
|
||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
E592B0F1D9DDB4B2033A4A3D /* iOS */ = {
|
41E096AA37EBB337950112DD /* Bookibra */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
192DFF277BADE93F9813BFFA /* Foundation.framework */,
|
4AC4F542A93E316D86251043 /* AppRouter.swift */,
|
||||||
|
A8F25BA817C2459E1751520C /* BookibraApp.swift */,
|
||||||
|
B3448927BF4293DFAF113048 /* BlurFogOverlay.swift */,
|
||||||
|
216DFB59A593E4AD349FCE0F /* BookCardView.swift */,
|
||||||
|
4E0C3AF58827D6FBF93157B7 /* BookCoverCard.swift */,
|
||||||
|
A850CCC78FC41E21559CE03A /* EmptyStateView.swift */,
|
||||||
|
026D1C09514A01EB0D4C7641 /* NetworkErrorView.swift */,
|
||||||
|
3D60CE4B6FDA59B1114B8168 /* PrimaryPillButton.swift */,
|
||||||
|
091D8A6C89ACBCDD9D022FB4 /* ScrewView.swift */,
|
||||||
|
C0887E6542AB83E17C9490AF /* ShelfSectionView.swift */,
|
||||||
|
67C4E14D093F318ECAB9FA6A /* Theme.swift */,
|
||||||
|
E3D6631E1FD7160883FC31F4 /* BookRemote.swift */,
|
||||||
|
FFB981066E192B332E6BB5E2 /* LibraryBook.swift */,
|
||||||
|
C94DACFF2F104F68F5E72793 /* ReadingStatus.swift */,
|
||||||
|
D9547DCE4663825EA1267F3C /* UserProfile.swift */,
|
||||||
|
4EDA9B5DE02839213D93F6A9 /* APIClient.swift */,
|
||||||
|
CAC488BF2DA3B6B94C2958E8 /* AuthService.swift */,
|
||||||
|
D1F2AEEF3846E89EF58E8E2B /* BooksService.swift */,
|
||||||
|
479C2FCDB84B962FF4BF68AE /* ImageCache.swift */,
|
||||||
|
FF7DCABD0AFF317C4C25C1D3 /* KeychainStore.swift */,
|
||||||
|
40AD40B111A582310571F11E /* AddBooksViewModel.swift */,
|
||||||
|
86CC67809D9F26DFB7F53280 /* AuthViewModel.swift */,
|
||||||
|
09E9BD1CDD8189A9BD8F93C0 /* BookDetailViewModel.swift */,
|
||||||
|
27AEC6ED0A12CE62E5E7B42F /* CategoryViewModel.swift */,
|
||||||
|
0563C287DF8A317E7F6D4A22 /* HomeViewModel.swift */,
|
||||||
|
46E960D3986FCFF358D1CC83 /* AddBooksView.swift */,
|
||||||
|
06BEE917DB0A13B5EA34D030 /* BarcodeScannerView.swift */,
|
||||||
|
12C0E4FA7E8B3F62D198485B /* AuthView.swift */,
|
||||||
|
2D4C72C39F36045BF4D5ECAF /* CategoryListView.swift */,
|
||||||
|
523E0F614C3E08527D265ADC /* BookDetailView.swift */,
|
||||||
|
3BC26B431A2E4633F2FC89CA /* HomeView.swift */,
|
||||||
|
0A47804190A193B8EE3159EA /* Assets.xcassets */,
|
||||||
|
B3CA8360A84C636945C83DD0 /* Localizable.strings */,
|
||||||
|
395E4C35E6D3C362B550AF3B /* Localizable.strings */,
|
||||||
|
0E5A7ADF9962345CB78AC571 /* mock_book_remote.json */,
|
||||||
|
);
|
||||||
|
path = Bookibra;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
71736683D1BB2B9A0FF730B9 /* iOS */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
5EABC698DDB8062029E5E9FA /* Foundation.framework */,
|
||||||
);
|
);
|
||||||
name = iOS;
|
name = iOS;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
F2E80CFCFD2FE3BF793C4147 /* Bookibra */ = {
|
FC7D15935DED2E50DD94B7D5 = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
E4DF6CD123627625C1967D42 /* AppRouter.swift */,
|
3D7744C35B489BFB545D42AD /* Products */,
|
||||||
2F8155E61788F47524B11449 /* BookibraApp.swift */,
|
1F97BBDD9672559E121C4FFA /* Frameworks */,
|
||||||
DC6F6AF589E6DD8E8EEF14CF /* BlurFogOverlay.swift */,
|
B44591FBA4BB2AEA612510DD /* Debug.xcconfig */,
|
||||||
349C2A1F39419A03C31114C3 /* BookCoverCard.swift */,
|
FDC0CE87CC54E1384BD6557B /* Release.xcconfig */,
|
||||||
72DED90139ADCDD834AE33CF /* NetworkErrorView.swift */,
|
41E096AA37EBB337950112DD /* Bookibra */,
|
||||||
FE7B76B449982D19D91A67F3 /* PrimaryPillButton.swift */,
|
|
||||||
C8DD9EB12817D19C6BC65306 /* ScrewView.swift */,
|
|
||||||
4C361617CEBCE704307DA0A9 /* ShelfSectionView.swift */,
|
|
||||||
3B226CA006E6EEA7DB04F100 /* Theme.swift */,
|
|
||||||
1EB98ADB94703A1CE74B79CD /* BookRemote.swift */,
|
|
||||||
B93B9F6AC3711B6E32030129 /* LibraryBook.swift */,
|
|
||||||
923CE0DF37333FFAF75668D5 /* UserProfile.swift */,
|
|
||||||
B618484EE9FC15B0DCA5E055 /* APIClient.swift */,
|
|
||||||
CAF98B022FA5BAA8F627DAAD /* AuthService.swift */,
|
|
||||||
A77D7EF1C78724F79956D5B1 /* BooksService.swift */,
|
|
||||||
2448E99A3D8CDBD5334901C7 /* ImageCache.swift */,
|
|
||||||
806F17D3E801BC96C6B5ED6D /* KeychainStore.swift */,
|
|
||||||
3BB571FD5EA0CB9C15DBD1DB /* AddBooksViewModel.swift */,
|
|
||||||
AF5F3C9BA827C89A343166A4 /* AuthViewModel.swift */,
|
|
||||||
96C4EA4FE11115BA86AB2802 /* BookDetailViewModel.swift */,
|
|
||||||
C15179D9ED03860747BBC180 /* CategoryViewModel.swift */,
|
|
||||||
BEB5F3DC625CEAE1BEC3911F /* HomeViewModel.swift */,
|
|
||||||
4DA01B4D0A49FA8E5D1E4F65 /* AddBooksView.swift */,
|
|
||||||
471C2927142735752EA378A9 /* BarcodeScannerView.swift */,
|
|
||||||
329388F7DE8EB288AEE98A23 /* AuthView.swift */,
|
|
||||||
D117FECFDDAC2EDDC191CEC9 /* CategoryListView.swift */,
|
|
||||||
A39116C7E45F12244CA1DC23 /* BookDetailView.swift */,
|
|
||||||
BF18379D6C3B16C83CCBCF22 /* HomeView.swift */,
|
|
||||||
0417E5217F2A37B2065F6DC9 /* Assets.xcassets */,
|
|
||||||
20947201FBE7D30CD6F69E38 /* Localizable.strings */,
|
|
||||||
1B0A599E6FA80C6DBB852EB5 /* Localizable.strings */,
|
|
||||||
95A0AD7A2591540C4ED3252F /* mock_book_remote.json */,
|
|
||||||
);
|
);
|
||||||
path = Bookibra;
|
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
A0CA764B5F4498B2368FB4C7 /* Bookibra */ = {
|
0E74DD6C4F903703E3E4BCAD /* Bookibra */ = {
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 0D8C0A6CBE24CFFE953CC286 /* Build configuration list for PBXNativeTarget "Bookibra" */;
|
buildConfigurationList = 27C2348C30A81863879653CA /* Build configuration list for PBXNativeTarget "Bookibra" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
4B0B88378B618BDE3340051C /* Sources */,
|
D93C53F09F67A433B3053B5D /* Sources */,
|
||||||
96BCE7EBCFE3A1B03D95C0A5 /* Frameworks */,
|
E88BA262CD3F12CEBF38184A /* Frameworks */,
|
||||||
AD49F7B39587DC1F8AC10D9B /* Resources */,
|
4ED22AFE6C5FD2EAFD12AE94 /* Resources */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@@ -196,25 +205,20 @@
|
|||||||
);
|
);
|
||||||
name = Bookibra;
|
name = Bookibra;
|
||||||
productName = Bookibra;
|
productName = Bookibra;
|
||||||
productReference = 7B6C1F4EB35DF0216BC86061 /* Bookibra.app */;
|
productReference = 9D96BF3D1EBDBB9B18FE826D /* Bookibra.app */;
|
||||||
productType = "com.apple.product-type.application";
|
productType = "com.apple.product-type.application";
|
||||||
};
|
};
|
||||||
/* End PBXNativeTarget section */
|
/* End PBXNativeTarget section */
|
||||||
|
|
||||||
/* Begin PBXProject section */
|
/* Begin PBXProject section */
|
||||||
1229A2BA11F02DDB82E39253 /* Project object */ = {
|
4A57E4BC2562874685F10390 /* Project object */ = {
|
||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
|
BuildIndependentTargetsInParallel = YES;
|
||||||
LastSwiftUpdateCheck = 1600;
|
LastSwiftUpdateCheck = 1600;
|
||||||
LastUpgradeCheck = 1600;
|
LastUpgradeCheck = 2620;
|
||||||
TargetAttributes = {
|
|
||||||
A0CA764B5F4498B2368FB4C7 = {
|
|
||||||
DevelopmentTeam = S34SFUY9SC;
|
|
||||||
ProvisioningStyle = Automatic;
|
|
||||||
};
|
};
|
||||||
};
|
buildConfigurationList = 66162088EAD405EAB915FA6C /* Build configuration list for PBXProject "Bookibra" */;
|
||||||
};
|
|
||||||
buildConfigurationList = AAD88BDD3442AC750E1C238E /* Build configuration list for PBXProject "Bookibra" */;
|
|
||||||
compatibilityVersion = "Xcode 3.2";
|
compatibilityVersion = "Xcode 3.2";
|
||||||
developmentRegion = en;
|
developmentRegion = en;
|
||||||
hasScannedForEncodings = 0;
|
hasScannedForEncodings = 0;
|
||||||
@@ -222,92 +226,75 @@
|
|||||||
en,
|
en,
|
||||||
Base,
|
Base,
|
||||||
);
|
);
|
||||||
mainGroup = 11824F4150C100F76F38DBD8;
|
mainGroup = FC7D15935DED2E50DD94B7D5;
|
||||||
productRefGroup = 94F7452CA97CED98C86AF84E /* Products */;
|
productRefGroup = 3D7744C35B489BFB545D42AD /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
targets = (
|
targets = (
|
||||||
A0CA764B5F4498B2368FB4C7 /* Bookibra */,
|
0E74DD6C4F903703E3E4BCAD /* Bookibra */,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
/* End PBXProject section */
|
/* End PBXProject section */
|
||||||
|
|
||||||
/* Begin PBXResourcesBuildPhase section */
|
/* Begin PBXResourcesBuildPhase section */
|
||||||
AD49F7B39587DC1F8AC10D9B /* Resources */ = {
|
4ED22AFE6C5FD2EAFD12AE94 /* Resources */ = {
|
||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
6E9DFC74E4EA2AC64A343E4C /* Assets.xcassets in Resources */,
|
9C71E8718C4820EFD2F50C17 /* Assets.xcassets in Resources */,
|
||||||
B66DAE4BF97BCAC84740B541 /* Localizable.strings in Resources */,
|
97FD0730276CEBBE6BDBCCA1 /* Localizable.strings in Resources */,
|
||||||
BDE45343461F02318DD86FDB /* Localizable.strings in Resources */,
|
DC0602EC768B6265564FE8FD /* Localizable.strings in Resources */,
|
||||||
6B60107AEE8C2489D19B1D9D /* mock_book_remote.json in Resources */,
|
686D28482BE40BE453EFE0D3 /* mock_book_remote.json in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
/* End PBXResourcesBuildPhase section */
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
4B0B88378B618BDE3340051C /* Sources */ = {
|
D93C53F09F67A433B3053B5D /* Sources */ = {
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
B404D223478123428719790C /* AppRouter.swift in Sources */,
|
AE16C52E1D5A765D0723F243 /* AppRouter.swift in Sources */,
|
||||||
6998E51506433C1B0A647330 /* BookibraApp.swift in Sources */,
|
EB4366DC44468E12BCE5177D /* BookibraApp.swift in Sources */,
|
||||||
2C2B30975D5DC09342690C43 /* BlurFogOverlay.swift in Sources */,
|
B1E7E3F062B3B01ED5EB7019 /* BlurFogOverlay.swift in Sources */,
|
||||||
4EE9C0CB6C3006780E8CDFA4 /* BookCoverCard.swift in Sources */,
|
C59A615F50B6A23903E02280 /* BookCardView.swift in Sources */,
|
||||||
245681EBBC7EB40F2733DD6B /* NetworkErrorView.swift in Sources */,
|
7FC47B691C19F4E6C1102606 /* BookCoverCard.swift in Sources */,
|
||||||
CB31E95DBEF85410677B11E3 /* PrimaryPillButton.swift in Sources */,
|
865A3EBCDA84FBA692A31939 /* EmptyStateView.swift in Sources */,
|
||||||
CD404352E3F12BCAE64E4BAC /* ScrewView.swift in Sources */,
|
9296F2BCE6EB7FFAC4F6A5B1 /* NetworkErrorView.swift in Sources */,
|
||||||
0987C082DE634D36D5BF03DE /* ShelfSectionView.swift in Sources */,
|
D9D5AD82B32F16BD92E82238 /* PrimaryPillButton.swift in Sources */,
|
||||||
EC793F00D7851722C0DD1633 /* Theme.swift in Sources */,
|
17B5B7FBC0025E80FA65036F /* ScrewView.swift in Sources */,
|
||||||
90E97C917EEA4B7B1D09F8FB /* BookRemote.swift in Sources */,
|
72212EAE36C9151956F84262 /* ShelfSectionView.swift in Sources */,
|
||||||
5DD75F144A0C411F2D7EF9C8 /* LibraryBook.swift in Sources */,
|
E318F3175C8B01CEA69F50C9 /* Theme.swift in Sources */,
|
||||||
1DCD42AC02DDABACC54958C5 /* UserProfile.swift in Sources */,
|
CE6FC5E5744AA2A05C3B6049 /* BookRemote.swift in Sources */,
|
||||||
E7D483E62D94D20A511C6967 /* APIClient.swift in Sources */,
|
A597F6A4E13688A21BD3FEAE /* LibraryBook.swift in Sources */,
|
||||||
F3452503ADD5962494ABB38D /* AuthService.swift in Sources */,
|
1569FBE19975206A58F8E694 /* ReadingStatus.swift in Sources */,
|
||||||
EAA4D823C890C98F37F64E1E /* BooksService.swift in Sources */,
|
B37F57B2BB478E72702A551E /* UserProfile.swift in Sources */,
|
||||||
7C130ABD8F4627EA3C0FB239 /* ImageCache.swift in Sources */,
|
6F398B380F29EF5C2C6E1653 /* APIClient.swift in Sources */,
|
||||||
E1E4040DBBACA7268F84998B /* KeychainStore.swift in Sources */,
|
6E060ACBDE0AD85FDB2F8010 /* AuthService.swift in Sources */,
|
||||||
89E2012E58DEB2A6CA3191F9 /* AddBooksViewModel.swift in Sources */,
|
971C5D3C6576578B4B4B8CBB /* BooksService.swift in Sources */,
|
||||||
A5BC1762D555E6DC13C8664E /* AuthViewModel.swift in Sources */,
|
4FC8807E8263C91ADA8FA591 /* ImageCache.swift in Sources */,
|
||||||
7C17970A1EC6CE66AB2B6962 /* BookDetailViewModel.swift in Sources */,
|
919B2721944E5A9DF8F696AF /* KeychainStore.swift in Sources */,
|
||||||
31B3807F97E8E1512FB5DEEA /* CategoryViewModel.swift in Sources */,
|
E0FC3411B06A5726172021BE /* AddBooksViewModel.swift in Sources */,
|
||||||
9DF5677130BA3D0F14C04B4B /* HomeViewModel.swift in Sources */,
|
0E57D2E3B73CC64C45A13E5A /* AuthViewModel.swift in Sources */,
|
||||||
D146299A54D7A660951C3075 /* AddBooksView.swift in Sources */,
|
ECDD56903097A589E63C7D64 /* BookDetailViewModel.swift in Sources */,
|
||||||
44B8242D7211EDD650FCA488 /* BarcodeScannerView.swift in Sources */,
|
07D06DDC629661D80D4BB7A2 /* CategoryViewModel.swift in Sources */,
|
||||||
A08E9B72A9FAA96CC58C82B1 /* AuthView.swift in Sources */,
|
8707C5BBD8347B96B1E4DFB8 /* HomeViewModel.swift in Sources */,
|
||||||
D6D4F249AA8A85A041C7D112 /* CategoryListView.swift in Sources */,
|
A6E600A345801C9AF1CB1F10 /* AddBooksView.swift in Sources */,
|
||||||
C9656D40284BF3D44322AE99 /* BookDetailView.swift in Sources */,
|
8287AA6A6111C8E14BA92E81 /* BarcodeScannerView.swift in Sources */,
|
||||||
7C5391EE19CD4370B0871A6F /* HomeView.swift in Sources */,
|
3DEF8AE33F0E47F9E942FAD6 /* AuthView.swift in Sources */,
|
||||||
|
1B120941F76A7C9ACC9B82C2 /* CategoryListView.swift in Sources */,
|
||||||
|
76CDBAECDA13604B0A3EC338 /* BookDetailView.swift in Sources */,
|
||||||
|
546CB89AAC170EE1B541830D /* HomeView.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
/* End PBXSourcesBuildPhase section */
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin XCBuildConfiguration section */
|
/* Begin XCBuildConfiguration section */
|
||||||
180A253868383C3109315D0F /* Debug */ = {
|
53E3CF14C3FC84D36F571AEE /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = AFD5A6DD3D2BFC014B9AB859 /* Debug.xcconfig */;
|
baseConfigurationReference = FDC0CE87CC54E1384BD6557B /* Release.xcconfig */;
|
||||||
buildSettings = {
|
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
|
||||||
CLANG_ENABLE_OBJC_WEAK = NO;
|
|
||||||
DEVELOPMENT_TEAM = S34SFUY9SC;
|
|
||||||
GENERATE_INFOPLIST_FILE = NO;
|
|
||||||
INFOPLIST_FILE = Bookibra/Resources/Info.plist;
|
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.bookibra.ios;
|
|
||||||
SDKROOT = iphoneos;
|
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
|
||||||
SWIFT_VERSION = 5.9;
|
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
|
||||||
};
|
|
||||||
name = Debug;
|
|
||||||
};
|
|
||||||
2D538BCF427EE4B9D90BA1E5 /* Debug */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
baseConfigurationReference = AFD5A6DD3D2BFC014B9AB859 /* Debug.xcconfig */;
|
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
@@ -342,9 +329,120 @@
|
|||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEAD_CODE_STRIPPING = YES;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
MTL_FAST_MATH = YES;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.bookibra.ios;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||||
|
SWIFT_VERSION = 5.9;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
883CA3D20F5C26D151A2C437 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = FDC0CE87CC54E1384BD6557B /* Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
CLANG_ENABLE_OBJC_WEAK = NO;
|
||||||
|
GENERATE_INFOPLIST_FILE = NO;
|
||||||
|
INFOPLIST_FILE = Bookibra/Resources/Info.plist;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.bookibra.ios;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_VERSION = 5.9;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
VALIDATE_PRODUCT = YES;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
CDAF5161EEF6E3EE85E10530 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = B44591FBA4BB2AEA612510DD /* Debug.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
CLANG_ENABLE_OBJC_WEAK = NO;
|
||||||
|
GENERATE_INFOPLIST_FILE = NO;
|
||||||
|
INFOPLIST_FILE = Bookibra/Resources/Info.plist;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.bookibra.ios;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_VERSION = 5.9;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
F6310A668DF5F079EAD0B12C /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = B44591FBA4BB2AEA612510DD /* Debug.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
ENABLE_TESTABILITY = YES;
|
ENABLE_TESTABILITY = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
GCC_DYNAMIC_NO_PIC = NO;
|
GCC_DYNAMIC_NO_PIC = NO;
|
||||||
GCC_NO_COMMON_BLOCKS = YES;
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
@@ -366,6 +464,7 @@
|
|||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.bookibra.ios;
|
PRODUCT_BUNDLE_IDENTIFIER = com.bookibra.ios;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
SWIFT_VERSION = 5.9;
|
SWIFT_VERSION = 5.9;
|
||||||
@@ -373,109 +472,28 @@
|
|||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
4BC6D305A4C5E17883964793 /* Release */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
baseConfigurationReference = 98AAF8E8F7F53CA9CE81186B /* Release.xcconfig */;
|
|
||||||
buildSettings = {
|
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
|
||||||
CLANG_CXX_LIBRARY = "libc++";
|
|
||||||
CLANG_ENABLE_MODULES = YES;
|
|
||||||
CLANG_ENABLE_OBJC_ARC = YES;
|
|
||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
|
||||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
|
||||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
|
||||||
CLANG_WARN_COMMA = YES;
|
|
||||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
|
||||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
|
||||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
|
||||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
|
||||||
CLANG_WARN_EMPTY_BODY = YES;
|
|
||||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
|
||||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
|
||||||
CLANG_WARN_INT_CONVERSION = YES;
|
|
||||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
|
||||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
|
||||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
|
||||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
|
||||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
|
||||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
|
||||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
|
||||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
|
||||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
|
||||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
|
||||||
CODE_SIGN_STYLE = Automatic;
|
|
||||||
COPY_PHASE_STRIP = NO;
|
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
|
||||||
GCC_NO_COMMON_BLOCKS = YES;
|
|
||||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
|
||||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
|
||||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
|
||||||
MARKETING_VERSION = 1.0;
|
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
|
||||||
MTL_FAST_MATH = YES;
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.bookibra.ios;
|
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
|
||||||
SWIFT_VERSION = 5.9;
|
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
|
||||||
};
|
|
||||||
name = Release;
|
|
||||||
};
|
|
||||||
EE4DDF7AD06BABD0D78E43A0 /* Release */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
baseConfigurationReference = 98AAF8E8F7F53CA9CE81186B /* Release.xcconfig */;
|
|
||||||
buildSettings = {
|
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
|
||||||
CLANG_ENABLE_OBJC_WEAK = NO;
|
|
||||||
DEVELOPMENT_TEAM = S34SFUY9SC;
|
|
||||||
GENERATE_INFOPLIST_FILE = NO;
|
|
||||||
INFOPLIST_FILE = Bookibra/Resources/Info.plist;
|
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.bookibra.ios;
|
|
||||||
SDKROOT = iphoneos;
|
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
|
||||||
SWIFT_VERSION = 5.9;
|
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
|
||||||
VALIDATE_PRODUCT = YES;
|
|
||||||
};
|
|
||||||
name = Release;
|
|
||||||
};
|
|
||||||
/* End XCBuildConfiguration section */
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
/* Begin XCConfigurationList section */
|
/* Begin XCConfigurationList section */
|
||||||
0D8C0A6CBE24CFFE953CC286 /* Build configuration list for PBXNativeTarget "Bookibra" */ = {
|
27C2348C30A81863879653CA /* Build configuration list for PBXNativeTarget "Bookibra" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
EE4DDF7AD06BABD0D78E43A0 /* Release */,
|
883CA3D20F5C26D151A2C437 /* Release */,
|
||||||
180A253868383C3109315D0F /* Debug */,
|
CDAF5161EEF6E3EE85E10530 /* Debug */,
|
||||||
);
|
);
|
||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
AAD88BDD3442AC750E1C238E /* Build configuration list for PBXProject "Bookibra" */ = {
|
66162088EAD405EAB915FA6C /* Build configuration list for PBXProject "Bookibra" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
2D538BCF427EE4B9D90BA1E5 /* Debug */,
|
F6310A668DF5F079EAD0B12C /* Debug */,
|
||||||
4BC6D305A4C5E17883964793 /* Release */,
|
53E3CF14C3FC84D36F571AEE /* Release */,
|
||||||
);
|
);
|
||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
};
|
};
|
||||||
rootObject = 1229A2BA11F02DDB82E39253 /* Project object */;
|
rootObject = 4A57E4BC2562874685F10390 /* Project object */;
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@@ -3,17 +3,25 @@ import SwiftUI
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
final class AppRouter: ObservableObject {
|
final class AppRouter: ObservableObject {
|
||||||
|
enum Tab: Hashable {
|
||||||
|
case library
|
||||||
|
case discover
|
||||||
|
case stats
|
||||||
|
case profile
|
||||||
|
}
|
||||||
|
|
||||||
enum Route: Hashable {
|
enum Route: Hashable {
|
||||||
case addBooks
|
|
||||||
case category(name: String)
|
case category(name: String)
|
||||||
case detail(BookRemote)
|
case detail(BookRemote)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Published var isAuthenticated = false
|
@Published var isAuthenticated = false
|
||||||
|
@Published var selectedTab: Tab = .library
|
||||||
@Published var path: [Route] = []
|
@Published var path: [Route] = []
|
||||||
|
|
||||||
func resetToHome() {
|
func resetToHome() {
|
||||||
path.removeAll()
|
path.removeAll()
|
||||||
|
selectedTab = .library
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,15 +33,13 @@ private struct RootView: View {
|
|||||||
NavigationStack(path: $router.path) {
|
NavigationStack(path: $router.path) {
|
||||||
Group {
|
Group {
|
||||||
if router.isAuthenticated {
|
if router.isAuthenticated {
|
||||||
HomeView(viewModel: HomeViewModel())
|
MainTabView()
|
||||||
} else {
|
} else {
|
||||||
AuthView(viewModel: AuthViewModel(authService: dependencies.authService, keychain: dependencies.keychain))
|
AuthView(viewModel: AuthViewModel(authService: dependencies.authService, keychain: dependencies.keychain))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationDestination(for: AppRouter.Route.self) { route in
|
.navigationDestination(for: AppRouter.Route.self) { route in
|
||||||
switch route {
|
switch route {
|
||||||
case .addBooks:
|
|
||||||
AddBooksView(viewModel: AddBooksViewModel(booksService: dependencies.booksService))
|
|
||||||
case .category(let name):
|
case .category(let name):
|
||||||
CategoryListView(viewModel: CategoryViewModel(categoryName: name))
|
CategoryListView(viewModel: CategoryViewModel(categoryName: name))
|
||||||
case .detail(let book):
|
case .detail(let book):
|
||||||
@@ -70,3 +68,66 @@ private struct RootView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct MainTabView: View {
|
||||||
|
@EnvironmentObject private var router: AppRouter
|
||||||
|
@Environment(\.dependencies) private var dependencies
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
TabView(selection: $router.selectedTab) {
|
||||||
|
HomeView(viewModel: HomeViewModel())
|
||||||
|
.tabItem {
|
||||||
|
Label("Library", systemImage: "books.vertical")
|
||||||
|
}
|
||||||
|
.tag(AppRouter.Tab.library)
|
||||||
|
|
||||||
|
AddBooksView(viewModel: AddBooksViewModel(booksService: dependencies.booksService))
|
||||||
|
.tabItem {
|
||||||
|
Label("Discover", systemImage: "sparkle.magnifyingglass")
|
||||||
|
}
|
||||||
|
.tag(AppRouter.Tab.discover)
|
||||||
|
|
||||||
|
StatsPlaceholderView()
|
||||||
|
.tabItem {
|
||||||
|
Label("Stats", systemImage: "chart.line.uptrend.xyaxis")
|
||||||
|
}
|
||||||
|
.tag(AppRouter.Tab.stats)
|
||||||
|
|
||||||
|
ProfilePlaceholderView()
|
||||||
|
.tabItem {
|
||||||
|
Label("Profile", systemImage: "person.crop.circle")
|
||||||
|
}
|
||||||
|
.tag(AppRouter.Tab.profile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct StatsPlaceholderView: View {
|
||||||
|
var body: some View {
|
||||||
|
ContentUnavailableView {
|
||||||
|
Label("Stats", systemImage: "chart.bar.xaxis")
|
||||||
|
} description: {
|
||||||
|
Text("Reading analytics yakında eklenecek.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct ProfilePlaceholderView: View {
|
||||||
|
@EnvironmentObject private var router: AppRouter
|
||||||
|
@Environment(\.dependencies) private var dependencies
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
NavigationStack {
|
||||||
|
List {
|
||||||
|
Section("Session") {
|
||||||
|
Button("Logout", role: .destructive) {
|
||||||
|
_ = dependencies.keychain.delete(for: AuthViewModel.tokenKey)
|
||||||
|
router.isAuthenticated = false
|
||||||
|
router.resetToHome()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationTitle("Profile")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
68
ios/Bookibra/DesignSystem/Components/BookCardView.swift
Normal file
68
ios/Bookibra/DesignSystem/Components/BookCardView.swift
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct BookCardView: View {
|
||||||
|
let title: String
|
||||||
|
let author: String
|
||||||
|
let coverURL: URL?
|
||||||
|
let status: ReadingStatus
|
||||||
|
let progress: Double?
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: Theme.Spacing.small) {
|
||||||
|
AsyncImage(url: coverURL) { phase in
|
||||||
|
switch phase {
|
||||||
|
case .success(let image):
|
||||||
|
image.resizable().scaledToFill()
|
||||||
|
default:
|
||||||
|
ZStack {
|
||||||
|
RoundedRectangle(cornerRadius: Theme.Radius.image)
|
||||||
|
.fill(Color.gray.opacity(0.22))
|
||||||
|
Image(systemName: "book.closed")
|
||||||
|
.font(.title2)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(height: 170)
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: Theme.Radius.image, style: .continuous))
|
||||||
|
|
||||||
|
Text(title)
|
||||||
|
.font(.headline)
|
||||||
|
.lineLimit(2)
|
||||||
|
.minimumScaleFactor(0.85)
|
||||||
|
|
||||||
|
Text(author.isEmpty ? "Unknown Author" : author)
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
.lineLimit(1)
|
||||||
|
|
||||||
|
HStack(spacing: Theme.Spacing.xSmall) {
|
||||||
|
Image(systemName: status.symbol)
|
||||||
|
Text(status.title)
|
||||||
|
.lineLimit(1)
|
||||||
|
}
|
||||||
|
.font(.caption.weight(.semibold))
|
||||||
|
.padding(.horizontal, 8)
|
||||||
|
.padding(.vertical, 5)
|
||||||
|
.foregroundStyle(status.color)
|
||||||
|
.background(status.color.opacity(0.14), in: Capsule())
|
||||||
|
|
||||||
|
if let progress, status == .reading {
|
||||||
|
ProgressView(value: progress)
|
||||||
|
.tint(status.color)
|
||||||
|
.animation(.easeOut(duration: 0.35), value: progress)
|
||||||
|
.accessibilityLabel("Reading progress")
|
||||||
|
.accessibilityValue("\(Int(progress * 100)) percent")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(Theme.Spacing.medium)
|
||||||
|
.background(.background, in: RoundedRectangle(cornerRadius: Theme.Radius.card, style: .continuous))
|
||||||
|
.overlay {
|
||||||
|
RoundedRectangle(cornerRadius: Theme.Radius.card, style: .continuous)
|
||||||
|
.stroke(Color.primary.opacity(0.06), lineWidth: 1)
|
||||||
|
}
|
||||||
|
.shadow(color: Theme.Shadow.card.color, radius: Theme.Shadow.card.radius, y: Theme.Shadow.card.y)
|
||||||
|
.accessibilityElement(children: .combine)
|
||||||
|
.accessibilityLabel("\(title), \(author), \(status.title)")
|
||||||
|
}
|
||||||
|
}
|
||||||
34
ios/Bookibra/DesignSystem/Components/EmptyStateView.swift
Normal file
34
ios/Bookibra/DesignSystem/Components/EmptyStateView.swift
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct EmptyStateView: View {
|
||||||
|
let symbol: String
|
||||||
|
let title: String
|
||||||
|
let message: String
|
||||||
|
let buttonTitle: String?
|
||||||
|
let action: (() -> Void)?
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(spacing: Theme.Spacing.medium) {
|
||||||
|
Image(systemName: symbol)
|
||||||
|
.font(.system(size: 44, weight: .semibold))
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
|
||||||
|
Text(title)
|
||||||
|
.font(.title3.weight(.semibold))
|
||||||
|
|
||||||
|
Text(message)
|
||||||
|
.font(.body)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
|
||||||
|
if let buttonTitle, let action {
|
||||||
|
Button(buttonTitle, action: action)
|
||||||
|
.buttonStyle(.borderedProminent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(24)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,4 +26,21 @@ enum Theme {
|
|||||||
static func headerSerif(size: CGFloat) -> Font {
|
static func headerSerif(size: CGFloat) -> Font {
|
||||||
.custom("NewYork-Regular", size: size, relativeTo: .largeTitle)
|
.custom("NewYork-Regular", size: size, relativeTo: .largeTitle)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum Spacing {
|
||||||
|
static let xSmall: CGFloat = 6
|
||||||
|
static let small: CGFloat = 10
|
||||||
|
static let medium: CGFloat = 16
|
||||||
|
static let large: CGFloat = 24
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Radius {
|
||||||
|
static let card: CGFloat = 14
|
||||||
|
static let image: CGFloat = 10
|
||||||
|
static let pill: CGFloat = 999
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Shadow {
|
||||||
|
static let card = (color: Color.black.opacity(0.14), radius: CGFloat(8), y: CGFloat(4))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ struct BookRemote: Codable, Identifiable, Hashable {
|
|||||||
let categories: [String]
|
let categories: [String]
|
||||||
let publisher: String?
|
let publisher: String?
|
||||||
let sourceLocale: String?
|
let sourceLocale: String?
|
||||||
|
let readingStatus: ReadingStatus?
|
||||||
|
let readingProgress: Double?
|
||||||
|
|
||||||
init(
|
init(
|
||||||
remoteId: String? = nil,
|
remoteId: String? = nil,
|
||||||
@@ -30,7 +32,9 @@ struct BookRemote: Codable, Identifiable, Hashable {
|
|||||||
pageCount: Int? = nil,
|
pageCount: Int? = nil,
|
||||||
categories: [String] = [],
|
categories: [String] = [],
|
||||||
publisher: String? = nil,
|
publisher: String? = nil,
|
||||||
sourceLocale: String? = nil
|
sourceLocale: String? = nil,
|
||||||
|
readingStatus: ReadingStatus? = nil,
|
||||||
|
readingProgress: Double? = nil
|
||||||
) {
|
) {
|
||||||
self.remoteId = remoteId
|
self.remoteId = remoteId
|
||||||
self.title = title
|
self.title = title
|
||||||
@@ -45,6 +49,8 @@ struct BookRemote: Codable, Identifiable, Hashable {
|
|||||||
self.categories = categories
|
self.categories = categories
|
||||||
self.publisher = publisher
|
self.publisher = publisher
|
||||||
self.sourceLocale = sourceLocale
|
self.sourceLocale = sourceLocale
|
||||||
|
self.readingStatus = readingStatus
|
||||||
|
self.readingProgress = readingProgress
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ final class LibraryBook {
|
|||||||
var language: String?
|
var language: String?
|
||||||
var sourceLocale: String?
|
var sourceLocale: String?
|
||||||
var remotePayloadJson: String?
|
var remotePayloadJson: String?
|
||||||
|
// Optional tutulur ki eski store'lar migration sırasında kırılmasın.
|
||||||
|
var statusRaw: String?
|
||||||
|
var readingProgress: Double?
|
||||||
|
|
||||||
init(
|
init(
|
||||||
localId: UUID = UUID(),
|
localId: UUID = UUID(),
|
||||||
@@ -30,7 +33,9 @@ final class LibraryBook {
|
|||||||
dateAdded: Date = .now,
|
dateAdded: Date = .now,
|
||||||
language: String? = nil,
|
language: String? = nil,
|
||||||
sourceLocale: String? = nil,
|
sourceLocale: String? = nil,
|
||||||
remotePayloadJson: String? = nil
|
remotePayloadJson: String? = nil,
|
||||||
|
statusRaw: String = ReadingStatus.wantToRead.rawValue,
|
||||||
|
readingProgress: Double = 0
|
||||||
) {
|
) {
|
||||||
self.localId = localId
|
self.localId = localId
|
||||||
self.title = title
|
self.title = title
|
||||||
@@ -45,6 +50,8 @@ final class LibraryBook {
|
|||||||
self.language = language
|
self.language = language
|
||||||
self.sourceLocale = sourceLocale
|
self.sourceLocale = sourceLocale
|
||||||
self.remotePayloadJson = remotePayloadJson
|
self.remotePayloadJson = remotePayloadJson
|
||||||
|
self.statusRaw = statusRaw
|
||||||
|
self.readingProgress = min(max(readingProgress, 0), 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
var authors: [String] {
|
var authors: [String] {
|
||||||
@@ -60,4 +67,14 @@ final class LibraryBook {
|
|||||||
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
|
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
|
||||||
.filter { !$0.isEmpty }
|
.filter { !$0.isEmpty }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var status: ReadingStatus {
|
||||||
|
get { ReadingStatus(rawValue: statusRaw ?? "") ?? .wantToRead }
|
||||||
|
set { statusRaw = newValue.rawValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
var readingProgressValue: Double {
|
||||||
|
get { min(max(readingProgress ?? 0, 0), 1) }
|
||||||
|
set { readingProgress = min(max(newValue, 0), 1) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
34
ios/Bookibra/Models/ReadingStatus.swift
Normal file
34
ios/Bookibra/Models/ReadingStatus.swift
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import Foundation
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
enum ReadingStatus: String, CaseIterable, Codable, Identifiable {
|
||||||
|
case wantToRead
|
||||||
|
case reading
|
||||||
|
case finished
|
||||||
|
|
||||||
|
var id: String { rawValue }
|
||||||
|
|
||||||
|
var title: String {
|
||||||
|
switch self {
|
||||||
|
case .wantToRead: return "Want to Read"
|
||||||
|
case .reading: return "Reading"
|
||||||
|
case .finished: return "Finished"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var symbol: String {
|
||||||
|
switch self {
|
||||||
|
case .wantToRead: return "bookmark"
|
||||||
|
case .reading: return "book"
|
||||||
|
case .finished: return "checkmark.circle"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var color: Color {
|
||||||
|
switch self {
|
||||||
|
case .wantToRead: return .orange
|
||||||
|
case .reading: return .blue
|
||||||
|
case .finished: return .green
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,7 +37,9 @@ final class BookDetailViewModel: ObservableObject {
|
|||||||
summary: book.description,
|
summary: book.description,
|
||||||
language: book.language,
|
language: book.language,
|
||||||
sourceLocale: book.sourceLocale,
|
sourceLocale: book.sourceLocale,
|
||||||
remotePayloadJson: nil
|
remotePayloadJson: nil,
|
||||||
|
statusRaw: (book.readingStatus ?? .wantToRead).rawValue,
|
||||||
|
readingProgress: book.readingProgress ?? 0
|
||||||
)
|
)
|
||||||
context.insert(local)
|
context.insert(local)
|
||||||
isInLibrary = true
|
isInLibrary = true
|
||||||
|
|||||||
@@ -55,7 +55,9 @@ final class HomeViewModel: ObservableObject {
|
|||||||
language: local.language,
|
language: local.language,
|
||||||
description: local.summary,
|
description: local.summary,
|
||||||
categories: local.categories,
|
categories: local.categories,
|
||||||
sourceLocale: local.sourceLocale
|
sourceLocale: local.sourceLocale,
|
||||||
|
readingStatus: local.status,
|
||||||
|
readingProgress: local.readingProgressValue
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,75 +1,46 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import SwiftData
|
||||||
|
|
||||||
struct AddBooksView: View {
|
struct AddBooksView: View {
|
||||||
|
enum FlowStep: Int {
|
||||||
|
case source
|
||||||
|
case confirm
|
||||||
|
case categorize
|
||||||
|
case success
|
||||||
|
}
|
||||||
|
|
||||||
@EnvironmentObject private var router: AppRouter
|
@EnvironmentObject private var router: AppRouter
|
||||||
|
@Environment(\.modelContext) private var modelContext
|
||||||
@ObservedObject var viewModel: AddBooksViewModel
|
@ObservedObject var viewModel: AddBooksViewModel
|
||||||
|
|
||||||
|
@State private var step: FlowStep = .source
|
||||||
|
@State private var selectedBook: BookRemote?
|
||||||
|
@State private var selectedCategory = "Design"
|
||||||
|
@State private var selectedStatus: ReadingStatus = .wantToRead
|
||||||
|
@State private var manualISBN = ""
|
||||||
|
@State private var startReadingNow = false
|
||||||
|
@State private var animateSuccess = false
|
||||||
|
|
||||||
|
private let defaultCategories = ["Design", "Psychology", "Novels"]
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 12) {
|
ScrollView {
|
||||||
Picker("Mode", selection: $viewModel.mode) {
|
VStack(spacing: Theme.Spacing.medium) {
|
||||||
ForEach(AddBooksViewModel.Mode.allCases, id: \.self) { mode in
|
progressHeader
|
||||||
Text(mode.title).tag(mode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.pickerStyle(.segmented)
|
|
||||||
|
|
||||||
Group {
|
switch step {
|
||||||
switch viewModel.mode {
|
case .source:
|
||||||
case .title:
|
sourceStep
|
||||||
titleSearch
|
case .confirm:
|
||||||
case .scan:
|
confirmStep
|
||||||
scanSearch
|
case .categorize:
|
||||||
case .filter:
|
categorizeStep
|
||||||
filterSearch
|
case .success:
|
||||||
}
|
successStep
|
||||||
}
|
|
||||||
|
|
||||||
if let error = viewModel.errorMessage {
|
|
||||||
NetworkErrorView(message: error) {
|
|
||||||
Task { await viewModel.searchByTitle() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List(viewModel.results, id: \.id) { book in
|
|
||||||
Button {
|
|
||||||
router.path.append(.detail(book))
|
|
||||||
} label: {
|
|
||||||
HStack(spacing: 12) {
|
|
||||||
AsyncImage(url: book.coverImageUrl) { phase in
|
|
||||||
if let image = phase.image {
|
|
||||||
image.resizable().scaledToFill()
|
|
||||||
} else {
|
|
||||||
RoundedRectangle(cornerRadius: 8).fill(.gray.opacity(0.2))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(width: 48, height: 72)
|
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
|
||||||
Text(book.title)
|
|
||||||
.font(.headline)
|
|
||||||
.lineLimit(2)
|
|
||||||
Text(book.authors.joined(separator: ", "))
|
|
||||||
.font(.subheadline)
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
.lineLimit(1)
|
|
||||||
if let year = book.publishedYear {
|
|
||||||
Text("\(year)")
|
|
||||||
.font(.caption)
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.listStyle(.plain)
|
|
||||||
.overlay {
|
|
||||||
if viewModel.results.isEmpty, !viewModel.isLoading {
|
|
||||||
Text(String(localized: "common.empty"))
|
|
||||||
.font(.subheadline)
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.frame(maxWidth: .infinity, alignment: .top)
|
||||||
|
.padding(.bottom, 24)
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 16)
|
.padding(.horizontal, 16)
|
||||||
.navigationTitle(String(localized: "add.title"))
|
.navigationTitle(String(localized: "add.title"))
|
||||||
@@ -80,25 +51,58 @@ struct AddBooksView: View {
|
|||||||
.controlSize(.large)
|
.controlSize(.large)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.sensoryFeedback(.success, trigger: animateSuccess)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var titleSearch: some View {
|
private var progressHeader: some View {
|
||||||
|
HStack(spacing: 8) {
|
||||||
|
ForEach(0..<4, id: \.self) { index in
|
||||||
|
Capsule()
|
||||||
|
.fill(index <= step.rawValue ? Color.accentColor : Color.gray.opacity(0.25))
|
||||||
|
.frame(height: 6)
|
||||||
|
.animation(.easeOut(duration: 0.2), value: step.rawValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.top, 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var sourceStep: some View {
|
||||||
|
VStack(spacing: Theme.Spacing.medium) {
|
||||||
|
Picker("Mode", selection: $viewModel.mode) {
|
||||||
|
ForEach(AddBooksViewModel.Mode.allCases, id: \.self) { mode in
|
||||||
|
Text(mode.title).tag(mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.pickerStyle(.segmented)
|
||||||
|
|
||||||
|
Group {
|
||||||
|
switch viewModel.mode {
|
||||||
|
case .title:
|
||||||
TextField(String(localized: "add.searchPlaceholder"), text: $viewModel.titleQuery)
|
TextField(String(localized: "add.searchPlaceholder"), text: $viewModel.titleQuery)
|
||||||
.textFieldStyle(.roundedBorder)
|
.textFieldStyle(.roundedBorder)
|
||||||
.onChange(of: viewModel.titleQuery) { _, _ in
|
.onChange(of: viewModel.titleQuery) { _, _ in
|
||||||
viewModel.titleChanged()
|
viewModel.titleChanged()
|
||||||
}
|
}
|
||||||
}
|
case .scan:
|
||||||
|
VStack(spacing: 10) {
|
||||||
private var scanSearch: some View {
|
|
||||||
BarcodeScannerView { isbn in
|
BarcodeScannerView { isbn in
|
||||||
Task { await viewModel.searchByISBN(isbn) }
|
Task { await viewModel.searchByISBN(isbn) }
|
||||||
}
|
}
|
||||||
.frame(height: 260)
|
.frame(height: 240)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 16))
|
.clipShape(RoundedRectangle(cornerRadius: 16))
|
||||||
}
|
|
||||||
|
|
||||||
private var filterSearch: some View {
|
HStack {
|
||||||
|
TextField("ISBN manuel gir", text: $manualISBN)
|
||||||
|
.textFieldStyle(.roundedBorder)
|
||||||
|
.keyboardType(.numberPad)
|
||||||
|
|
||||||
|
Button("Ara") {
|
||||||
|
Task { await viewModel.searchByISBN(manualISBN) }
|
||||||
|
}
|
||||||
|
.buttonStyle(.borderedProminent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case .filter:
|
||||||
VStack(spacing: 8) {
|
VStack(spacing: 8) {
|
||||||
TextField("Title", text: $viewModel.filterTitle)
|
TextField("Title", text: $viewModel.filterTitle)
|
||||||
.textFieldStyle(.roundedBorder)
|
.textFieldStyle(.roundedBorder)
|
||||||
@@ -113,3 +117,182 @@ struct AddBooksView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let error = viewModel.errorMessage {
|
||||||
|
NetworkErrorView(message: error) {
|
||||||
|
Task { await viewModel.searchByTitle() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if viewModel.results.isEmpty, !viewModel.isLoading {
|
||||||
|
EmptyStateView(
|
||||||
|
symbol: "magnifyingglass",
|
||||||
|
title: "Sonuç bulunamadı",
|
||||||
|
message: "Farklı bir başlık dene veya ISBN'i manuel gir.",
|
||||||
|
buttonTitle: nil,
|
||||||
|
action: nil
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
LazyVStack(spacing: 10) {
|
||||||
|
ForEach(viewModel.results, id: \.id) { book in
|
||||||
|
Button {
|
||||||
|
selectedBook = book
|
||||||
|
step = .confirm
|
||||||
|
} label: {
|
||||||
|
HStack(spacing: 12) {
|
||||||
|
AsyncImage(url: book.coverImageUrl) { image in
|
||||||
|
image.resizable().scaledToFill()
|
||||||
|
} placeholder: {
|
||||||
|
RoundedRectangle(cornerRadius: 8).fill(.gray.opacity(0.2))
|
||||||
|
}
|
||||||
|
.frame(width: 48, height: 72)
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
|
Text(book.title)
|
||||||
|
.font(.headline)
|
||||||
|
.lineLimit(2)
|
||||||
|
Text(book.authors.joined(separator: ", "))
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
.lineLimit(1)
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
Image(systemName: "chevron.right")
|
||||||
|
.foregroundStyle(.tertiary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
.padding(12)
|
||||||
|
.background(Color.white.opacity(0.75), in: RoundedRectangle(cornerRadius: 12, style: .continuous))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var confirmStep: some View {
|
||||||
|
VStack(alignment: .leading, spacing: Theme.Spacing.medium) {
|
||||||
|
if let book = selectedBook {
|
||||||
|
BookCardView(
|
||||||
|
title: book.title,
|
||||||
|
author: book.authors.first ?? "",
|
||||||
|
coverURL: book.coverImageUrl,
|
||||||
|
status: .wantToRead,
|
||||||
|
progress: nil
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
Button("Geri") { step = .source }
|
||||||
|
.buttonStyle(.bordered)
|
||||||
|
Spacer()
|
||||||
|
Button("Bilgileri Onayla") { step = .categorize }
|
||||||
|
.buttonStyle(.borderedProminent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var categorizeStep: some View {
|
||||||
|
VStack(alignment: .leading, spacing: Theme.Spacing.medium) {
|
||||||
|
Text("Kategori & Durum")
|
||||||
|
.font(.title3.weight(.semibold))
|
||||||
|
|
||||||
|
Picker("Kategori", selection: $selectedCategory) {
|
||||||
|
ForEach(defaultCategories, id: \.self) { category in
|
||||||
|
Text(category).tag(category)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.pickerStyle(.menu)
|
||||||
|
|
||||||
|
Picker("Durum", selection: $selectedStatus) {
|
||||||
|
ForEach(ReadingStatus.allCases) { status in
|
||||||
|
Text(status.title).tag(status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.pickerStyle(.segmented)
|
||||||
|
|
||||||
|
Toggle("Hemen okumaya başla", isOn: $startReadingNow)
|
||||||
|
|
||||||
|
Button("Kitabı Kaydet") {
|
||||||
|
saveSelectedBook()
|
||||||
|
}
|
||||||
|
.buttonStyle(.borderedProminent)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .trailing)
|
||||||
|
|
||||||
|
Button("Geri") { step = .confirm }
|
||||||
|
.buttonStyle(.bordered)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var successStep: some View {
|
||||||
|
VStack(spacing: Theme.Spacing.large) {
|
||||||
|
Image(systemName: "checkmark.circle.fill")
|
||||||
|
.font(.system(size: 72))
|
||||||
|
.foregroundStyle(.green)
|
||||||
|
.scaleEffect(animateSuccess ? 1 : 0.7)
|
||||||
|
.opacity(animateSuccess ? 1 : 0.4)
|
||||||
|
.animation(.spring(response: 0.5, dampingFraction: 0.7), value: animateSuccess)
|
||||||
|
|
||||||
|
Text("Kitap başarıyla eklendi")
|
||||||
|
.font(.title3.weight(.semibold))
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
Button("Yeni Kitap Ekle") {
|
||||||
|
resetFlow()
|
||||||
|
}
|
||||||
|
.buttonStyle(.bordered)
|
||||||
|
|
||||||
|
Button("Kitaplığa Dön") {
|
||||||
|
router.selectedTab = .library
|
||||||
|
resetFlow()
|
||||||
|
}
|
||||||
|
.buttonStyle(.borderedProminent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
|
.onAppear { animateSuccess = true }
|
||||||
|
}
|
||||||
|
|
||||||
|
private func saveSelectedBook() {
|
||||||
|
guard let book = selectedBook else { return }
|
||||||
|
|
||||||
|
let progress = startReadingNow ? 0.1 : (selectedStatus == .finished ? 1 : 0)
|
||||||
|
let status = startReadingNow ? ReadingStatus.reading : selectedStatus
|
||||||
|
|
||||||
|
let local = LibraryBook(
|
||||||
|
title: book.title,
|
||||||
|
authorsString: book.authors.joined(separator: ", "),
|
||||||
|
coverUrlString: book.coverImageUrl?.absoluteString,
|
||||||
|
isbn10: book.isbn10,
|
||||||
|
isbn13: book.isbn13,
|
||||||
|
publishedYear: book.publishedYear,
|
||||||
|
categoriesString: selectedCategory,
|
||||||
|
summary: book.description,
|
||||||
|
language: book.language,
|
||||||
|
sourceLocale: book.sourceLocale,
|
||||||
|
remotePayloadJson: nil,
|
||||||
|
statusRaw: status.rawValue,
|
||||||
|
readingProgress: progress
|
||||||
|
)
|
||||||
|
|
||||||
|
modelContext.insert(local)
|
||||||
|
try? modelContext.save()
|
||||||
|
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
||||||
|
|
||||||
|
withAnimation(.easeInOut(duration: 0.25)) {
|
||||||
|
step = .success
|
||||||
|
animateSuccess = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func resetFlow() {
|
||||||
|
selectedBook = nil
|
||||||
|
selectedCategory = defaultCategories[0]
|
||||||
|
selectedStatus = .wantToRead
|
||||||
|
startReadingNow = false
|
||||||
|
manualISBN = ""
|
||||||
|
step = .source
|
||||||
|
animateSuccess = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,20 +4,62 @@ import VisionKit
|
|||||||
|
|
||||||
struct BarcodeScannerView: View {
|
struct BarcodeScannerView: View {
|
||||||
let onScanned: (String) -> Void
|
let onScanned: (String) -> Void
|
||||||
|
@State private var cameraAuthorized = AVCaptureDevice.authorizationStatus(for: .video) == .authorized
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Group {
|
Group {
|
||||||
|
if cameraAuthorized {
|
||||||
if DataScannerViewController.isSupported, DataScannerViewController.isAvailable {
|
if DataScannerViewController.isSupported, DataScannerViewController.isAvailable {
|
||||||
DataScannerRepresentable(onScanned: onScanned)
|
DataScannerRepresentable(onScanned: onScanned)
|
||||||
} else {
|
} else {
|
||||||
AVScannerRepresentable(onScanned: onScanned)
|
AVScannerRepresentable(onScanned: onScanned)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
scannerUnavailableView
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.background(Color.black.opacity(0.85))
|
||||||
.overlay {
|
.overlay {
|
||||||
RoundedRectangle(cornerRadius: 16)
|
RoundedRectangle(cornerRadius: 16)
|
||||||
.stroke(Color.white.opacity(0.75), lineWidth: 2)
|
.stroke(Color.white.opacity(0.75), lineWidth: 2)
|
||||||
}
|
}
|
||||||
.accessibilityLabel("ISBN barkod tarayıcı")
|
.accessibilityLabel("ISBN barkod tarayıcı")
|
||||||
|
.onAppear {
|
||||||
|
requestCameraPermissionIfNeeded()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var scannerUnavailableView: some View {
|
||||||
|
VStack(spacing: 10) {
|
||||||
|
Image(systemName: "camera.viewfinder")
|
||||||
|
.font(.system(size: 30))
|
||||||
|
.foregroundStyle(.white.opacity(0.9))
|
||||||
|
Text("Kamera erişimi gerekli")
|
||||||
|
.font(.headline)
|
||||||
|
.foregroundStyle(.white)
|
||||||
|
Text("Barkod taramak için Ayarlar > Gizlilik > Kamera üzerinden izin verin.")
|
||||||
|
.font(.footnote)
|
||||||
|
.foregroundStyle(.white.opacity(0.85))
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.padding(.horizontal, 12)
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func requestCameraPermissionIfNeeded() {
|
||||||
|
let status = AVCaptureDevice.authorizationStatus(for: .video)
|
||||||
|
switch status {
|
||||||
|
case .authorized:
|
||||||
|
cameraAuthorized = true
|
||||||
|
case .notDetermined:
|
||||||
|
AVCaptureDevice.requestAccess(for: .video) { granted in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
cameraAuthorized = granted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
cameraAuthorized = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +1,30 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import SwiftData
|
import SwiftData
|
||||||
|
import UIKit
|
||||||
|
|
||||||
struct CategoryListView: View {
|
struct CategoryListView: View {
|
||||||
@EnvironmentObject private var router: AppRouter
|
@EnvironmentObject private var router: AppRouter
|
||||||
|
@Environment(\.modelContext) private var modelContext
|
||||||
@Query(sort: \LibraryBook.dateAdded, order: .reverse) private var allBooks: [LibraryBook]
|
@Query(sort: \LibraryBook.dateAdded, order: .reverse) private var allBooks: [LibraryBook]
|
||||||
@ObservedObject var viewModel: CategoryViewModel
|
@ObservedObject var viewModel: CategoryViewModel
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
let books = viewModel.books(from: allBooks)
|
let books = viewModel.books(from: allBooks)
|
||||||
|
|
||||||
VStack {
|
List {
|
||||||
HStack {
|
controlRow
|
||||||
TextField("Search", text: $viewModel.searchText)
|
|
||||||
.textFieldStyle(.roundedBorder)
|
|
||||||
Menu {
|
|
||||||
ForEach(CategoryViewModel.SortOption.allCases, id: \.self) { option in
|
|
||||||
Button(option.rawValue) { viewModel.sortOption = option }
|
|
||||||
}
|
|
||||||
} label: {
|
|
||||||
Image(systemName: "arrow.up.arrow.down.circle")
|
|
||||||
.font(.title3)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(.horizontal)
|
|
||||||
|
|
||||||
ScrollView {
|
if books.isEmpty {
|
||||||
LazyVGrid(columns: [GridItem(.adaptive(minimum: 104), spacing: 12)], spacing: 16) {
|
EmptyStateView(
|
||||||
|
symbol: "books.vertical",
|
||||||
|
title: "Bu kategoride kitap yok",
|
||||||
|
message: "Kitap ekleyerek bu rafı doldurabilirsin.",
|
||||||
|
buttonTitle: "Kitap Keşfet",
|
||||||
|
action: { router.selectedTab = .discover }
|
||||||
|
)
|
||||||
|
.listRowSeparator(.hidden)
|
||||||
|
.listRowBackground(Color.clear)
|
||||||
|
} else {
|
||||||
ForEach(books, id: \.localId) { book in
|
ForEach(books, id: \.localId) { book in
|
||||||
let remote = BookRemote(
|
let remote = BookRemote(
|
||||||
title: book.title,
|
title: book.title,
|
||||||
@@ -36,36 +35,77 @@ struct CategoryListView: View {
|
|||||||
coverImageUrl: book.coverUrlString.flatMap(URL.init(string:)),
|
coverImageUrl: book.coverUrlString.flatMap(URL.init(string:)),
|
||||||
language: book.language,
|
language: book.language,
|
||||||
description: book.summary,
|
description: book.summary,
|
||||||
categories: book.categories
|
categories: book.categories,
|
||||||
|
readingStatus: book.status,
|
||||||
|
readingProgress: book.readingProgressValue
|
||||||
)
|
)
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
router.path.append(.detail(remote))
|
router.path.append(.detail(remote))
|
||||||
} label: {
|
} label: {
|
||||||
VStack(alignment: .leading, spacing: 6) {
|
BookCardView(
|
||||||
AsyncImage(url: remote.coverImageUrl) { phase in
|
title: remote.title,
|
||||||
if let image = phase.image {
|
author: remote.authors.first ?? "",
|
||||||
image.resizable().scaledToFill()
|
coverURL: remote.coverImageUrl,
|
||||||
} else {
|
status: remote.readingStatus ?? .wantToRead,
|
||||||
RoundedRectangle(cornerRadius: 10).fill(Color.gray.opacity(0.2))
|
progress: remote.readingProgress
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
.buttonStyle(.plain)
|
||||||
.frame(height: 154)
|
.listRowInsets(EdgeInsets(top: 10, leading: 16, bottom: 10, trailing: 16))
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
.listRowSeparator(.hidden)
|
||||||
|
.listRowBackground(Color.clear)
|
||||||
Text(book.title)
|
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||||
.font(.caption)
|
statusButton(.finished, for: book)
|
||||||
.lineLimit(2)
|
statusButton(.reading, for: book)
|
||||||
.foregroundStyle(.primary)
|
statusButton(.wantToRead, for: book)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.horizontal)
|
.scrollContentBackground(.hidden)
|
||||||
}
|
.background(Theme.background.ignoresSafeArea())
|
||||||
}
|
|
||||||
.navigationTitle(viewModel.categoryName)
|
.navigationTitle(viewModel.categoryName)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.background(Theme.background.ignoresSafeArea())
|
}
|
||||||
|
|
||||||
|
private var controlRow: some View {
|
||||||
|
HStack(spacing: 10) {
|
||||||
|
TextField("Kategori içinde ara", text: $viewModel.searchText)
|
||||||
|
.textFieldStyle(.roundedBorder)
|
||||||
|
|
||||||
|
Menu {
|
||||||
|
ForEach(CategoryViewModel.SortOption.allCases, id: \.self) { option in
|
||||||
|
Button(option.rawValue) { viewModel.sortOption = option }
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Label("Sırala", systemImage: "arrow.up.arrow.down.circle")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.listRowSeparator(.hidden)
|
||||||
|
.listRowBackground(Color.clear)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func statusButton(_ status: ReadingStatus, for book: LibraryBook) -> some View {
|
||||||
|
Button {
|
||||||
|
withAnimation(.spring(duration: 0.3)) {
|
||||||
|
book.status = status
|
||||||
|
if status == .finished { book.readingProgressValue = 1 }
|
||||||
|
if status == .wantToRead { book.readingProgressValue = 0 }
|
||||||
|
try? modelContext.save()
|
||||||
|
}
|
||||||
|
UIImpactFeedbackGenerator(style: .soft).impactOccurred()
|
||||||
|
} label: {
|
||||||
|
Label(status.title, systemImage: status.symbol)
|
||||||
|
}
|
||||||
|
.tint(color(for: status))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func color(for status: ReadingStatus) -> Color {
|
||||||
|
switch status {
|
||||||
|
case .wantToRead: return .orange
|
||||||
|
case .reading: return .blue
|
||||||
|
case .finished: return .green
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,9 @@ struct HomeView: View {
|
|||||||
BlurFogOverlay()
|
BlurFogOverlay()
|
||||||
.frame(height: 96)
|
.frame(height: 96)
|
||||||
PrimaryPillButton(title: String(localized: "home.addBooks")) {
|
PrimaryPillButton(title: String(localized: "home.addBooks")) {
|
||||||
router.path.append(.addBooks)
|
withAnimation(.easeInOut(duration: 0.2)) {
|
||||||
|
router.selectedTab = .discover
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 24)
|
.padding(.horizontal, 24)
|
||||||
.padding(.bottom, 12)
|
.padding(.bottom, 12)
|
||||||
|
|||||||
Reference in New Issue
Block a user