Merge pull request #1073 from mastodon/ios-14-advanced-settings
Migrate existing settings to new Design
This commit is contained in:
commit
b48a66c018
@ -709,59 +709,60 @@
|
||||
"title": "Post from %s"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Settings",
|
||||
"section": {
|
||||
"overview": {
|
||||
"title": "Settings",
|
||||
"general": "General",
|
||||
"notifications": "Notifications",
|
||||
"support_mastodon": "Support Mastodon",
|
||||
"about_mastodon": "About Mastodon",
|
||||
"logout": "Logout %@"
|
||||
}
|
||||
|
||||
"about_mastodon": {
|
||||
"title": "About",
|
||||
"more_settings": "Even More Settings",
|
||||
"contribute_to_mastodon": "Contribute to Mastodon",
|
||||
"privacy_policy": "Privacy Policy",
|
||||
"clear_media_storage": "Clear Media Storage"
|
||||
},
|
||||
"general": {
|
||||
"title": "General",
|
||||
"appearance": {
|
||||
"title": "Appearance",
|
||||
"automatic": "Automatic",
|
||||
"light": "Always Light",
|
||||
"dark": "Always Dark"
|
||||
"section_title": "Appearance",
|
||||
"dark": "Dark",
|
||||
"light": "Light",
|
||||
"system": "Use Device Appearance"
|
||||
},
|
||||
"look_and_feel": {
|
||||
"title": "Look and Feel",
|
||||
"use_system": "Use System",
|
||||
"really_dark": "Really Dark",
|
||||
"sorta_dark": "Sorta Dark",
|
||||
"light": "Light"
|
||||
"design": {
|
||||
"section_title": "Design",
|
||||
"show_animations": "Play Animated Avatars and Emoji"
|
||||
},
|
||||
"notifications": {
|
||||
"title": "Notifications",
|
||||
"favorites": "Favorites my post",
|
||||
"follows": "Follows me",
|
||||
"boosts": "Reblogs my post",
|
||||
"mentions": "Mentions me",
|
||||
"trigger": {
|
||||
"anyone": "anyone",
|
||||
"follower": "a follower",
|
||||
"follow": "anyone I follow",
|
||||
"noone": "no one",
|
||||
"title": "Notify me when"
|
||||
}
|
||||
},
|
||||
"preference": {
|
||||
"title": "Preferences",
|
||||
"disable_avatar_animation": "Disable animated avatars",
|
||||
"disable_emoji_animation": "Disable animated emojis",
|
||||
"using_default_browser": "Use default browser to open links",
|
||||
"open_links_in_mastodon": "Open links in Mastodon"
|
||||
},
|
||||
"boring_zone": {
|
||||
"title": "The Boring Zone",
|
||||
"account_settings": "Account Settings",
|
||||
"terms": "Terms of Service",
|
||||
"privacy": "Privacy Policy"
|
||||
},
|
||||
"spicy_zone": {
|
||||
"title": "The Spicy Zone",
|
||||
"clear": "Clear Media Cache",
|
||||
"signout": "Sign Out"
|
||||
"links": {
|
||||
"section_title": "Links",
|
||||
"open_in_mastodon": "Open in Mastodon",
|
||||
"open_in_browser": "Open in Browser"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
"mastodon_description": "Mastodon is open source software. You can report issues on GitHub at %s (%s)"
|
||||
},
|
||||
"keyboard": {
|
||||
"close_settings_window": "Close Settings Window"
|
||||
"notifications": {
|
||||
"title": "Notifications",
|
||||
"policy": {
|
||||
"title": "Get Notifications from",
|
||||
"anyone": "Anyone",
|
||||
"followers": "People who follow you",
|
||||
"follow": "People you follow",
|
||||
"noone": "No one"
|
||||
},
|
||||
"alert": {
|
||||
"mentions_and_replies": "Mentions & Replies",
|
||||
"boosts": "Boosts",
|
||||
"favorites": "Favorites",
|
||||
"new_followers": "New Followers"
|
||||
},
|
||||
"disabled": {
|
||||
"notification_hint": "Turn on notifications from your device settings to see updates on your lock screen.",
|
||||
"go_to_settings": "Go to Notification Settings"
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
"report": {
|
||||
|
@ -655,6 +655,10 @@
|
||||
"profile": "Go to @%s@%s",
|
||||
"url": "Open URL in Mastodon",
|
||||
"hashtag": "Go to #%s",
|
||||
"no_user": {
|
||||
"title": "No User Account Found",
|
||||
"message": "There's no Useraccount \"%s\" on %s"
|
||||
}
|
||||
"empty_state": {
|
||||
"no_results": "No results"
|
||||
},
|
||||
@ -705,59 +709,60 @@
|
||||
"title": "Post from %s"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Settings",
|
||||
"section": {
|
||||
"overview": {
|
||||
"title": "Settings",
|
||||
"general": "General",
|
||||
"notifications": "Notifications",
|
||||
"support_mastodon": "Support Mastodon",
|
||||
"about_mastodon": "About Mastodon",
|
||||
"logout": "Logout %@"
|
||||
}
|
||||
|
||||
"about_mastodon": {
|
||||
"title": "About",
|
||||
"more_settings": "Even More Settings",
|
||||
"contribute_to_mastodon": "Contribute to Mastodon",
|
||||
"privacy_policy": "Privacy Policy",
|
||||
"clear_media_storage": "Clear Media Storage"
|
||||
},
|
||||
"general": {
|
||||
"title": "General",
|
||||
"appearance": {
|
||||
"title": "Appearance",
|
||||
"automatic": "Automatic",
|
||||
"light": "Always Light",
|
||||
"dark": "Always Dark"
|
||||
"section_title": "Appearance",
|
||||
"dark": "Dark",
|
||||
"light": "Light",
|
||||
"system": "Use Device Appearance"
|
||||
},
|
||||
"look_and_feel": {
|
||||
"title": "Look and Feel",
|
||||
"use_system": "Use System",
|
||||
"really_dark": "Really Dark",
|
||||
"sorta_dark": "Sorta Dark",
|
||||
"light": "Light"
|
||||
"design": {
|
||||
"section_title": "Design",
|
||||
"show_animations": "Play Animated Avatars and Emoji"
|
||||
},
|
||||
"notifications": {
|
||||
"title": "Notifications",
|
||||
"favorites": "Favorites my post",
|
||||
"follows": "Follows me",
|
||||
"boosts": "Reblogs my post",
|
||||
"mentions": "Mentions me",
|
||||
"trigger": {
|
||||
"anyone": "anyone",
|
||||
"follower": "a follower",
|
||||
"follow": "anyone I follow",
|
||||
"noone": "no one",
|
||||
"title": "Notify me when"
|
||||
}
|
||||
},
|
||||
"preference": {
|
||||
"title": "Preferences",
|
||||
"disable_avatar_animation": "Disable animated avatars",
|
||||
"disable_emoji_animation": "Disable animated emojis",
|
||||
"using_default_browser": "Use default browser to open links",
|
||||
"open_links_in_mastodon": "Open links in Mastodon"
|
||||
},
|
||||
"boring_zone": {
|
||||
"title": "The Boring Zone",
|
||||
"account_settings": "Account Settings",
|
||||
"terms": "Terms of Service",
|
||||
"privacy": "Privacy Policy"
|
||||
},
|
||||
"spicy_zone": {
|
||||
"title": "The Spicy Zone",
|
||||
"clear": "Clear Media Cache",
|
||||
"signout": "Sign Out"
|
||||
"links": {
|
||||
"section_title": "Links",
|
||||
"open_in_mastodon": "Open in Mastodon",
|
||||
"open_in_browser": "Open in Browser"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
"mastodon_description": "Mastodon is open source software. You can report issues on GitHub at %s (%s)"
|
||||
},
|
||||
"keyboard": {
|
||||
"close_settings_window": "Close Settings Window"
|
||||
"notifications": {
|
||||
"title": "Notifications",
|
||||
"policy": {
|
||||
"title": "Get Notifications from",
|
||||
"anyone": "Anyone",
|
||||
"followers": "People who follow you",
|
||||
"follow": "People you follow",
|
||||
"noone": "No one"
|
||||
},
|
||||
"alert": {
|
||||
"mentions_and_replies": "Mentions & Replies",
|
||||
"boosts": "Boosts",
|
||||
"favorites": "Favorites",
|
||||
"new_followers": "New Followers"
|
||||
},
|
||||
"disabled": {
|
||||
"notification_hint": "Turn on notifications from your device settings to see updates on your lock screen.",
|
||||
"go_to_settings": "Go to Notification Settings"
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
"report": {
|
||||
|
@ -709,59 +709,60 @@
|
||||
"title": "Post from %s"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Settings",
|
||||
"section": {
|
||||
"overview": {
|
||||
"title": "Settings",
|
||||
"general": "General",
|
||||
"notifications": "Notifications",
|
||||
"support_mastodon": "Support Mastodon",
|
||||
"about_mastodon": "About Mastodon",
|
||||
"logout": "Logout %@"
|
||||
}
|
||||
|
||||
"about_mastodon": {
|
||||
"title": "About",
|
||||
"more_settings": "Even More Settings",
|
||||
"contribute_to_mastodon": "Contribute to Mastodon",
|
||||
"privacy_policy": "Privacy Policy",
|
||||
"clear_media_storage": "Clear Media Storage"
|
||||
},
|
||||
"general": {
|
||||
"title": "General",
|
||||
"appearance": {
|
||||
"title": "Appearance",
|
||||
"automatic": "Automatic",
|
||||
"light": "Always Light",
|
||||
"dark": "Always Dark"
|
||||
"section_title": "Appearance",
|
||||
"dark": "Dark",
|
||||
"light": "Light",
|
||||
"system": "Use Device Appearance"
|
||||
},
|
||||
"look_and_feel": {
|
||||
"title": "Look and Feel",
|
||||
"use_system": "Use System",
|
||||
"really_dark": "Really Dark",
|
||||
"sorta_dark": "Sorta Dark",
|
||||
"light": "Light"
|
||||
"design": {
|
||||
"section_title": "Design",
|
||||
"show_animations": "Play Animated Avatars and Emoji"
|
||||
},
|
||||
"notifications": {
|
||||
"title": "Notifications",
|
||||
"favorites": "Favorites my post",
|
||||
"follows": "Follows me",
|
||||
"boosts": "Reblogs my post",
|
||||
"mentions": "Mentions me",
|
||||
"trigger": {
|
||||
"anyone": "anyone",
|
||||
"follower": "a follower",
|
||||
"follow": "anyone I follow",
|
||||
"noone": "no one",
|
||||
"title": "Notify me when"
|
||||
}
|
||||
},
|
||||
"preference": {
|
||||
"title": "Preferences",
|
||||
"disable_avatar_animation": "Disable animated avatars",
|
||||
"disable_emoji_animation": "Disable animated emojis",
|
||||
"using_default_browser": "Use default browser to open links",
|
||||
"open_links_in_mastodon": "Open links in Mastodon"
|
||||
},
|
||||
"boring_zone": {
|
||||
"title": "The Boring Zone",
|
||||
"account_settings": "Account Settings",
|
||||
"terms": "Terms of Service",
|
||||
"privacy": "Privacy Policy"
|
||||
},
|
||||
"spicy_zone": {
|
||||
"title": "The Spicy Zone",
|
||||
"clear": "Clear Media Cache",
|
||||
"signout": "Sign Out"
|
||||
"links": {
|
||||
"section_title": "Links",
|
||||
"open_in_mastodon": "Open in Mastodon",
|
||||
"open_in_browser": "Open in Browser"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
"mastodon_description": "Mastodon is open source software. You can report issues on GitHub at %s (%s)"
|
||||
},
|
||||
"keyboard": {
|
||||
"close_settings_window": "Close Settings Window"
|
||||
"notifications": {
|
||||
"title": "Notifications",
|
||||
"policy": {
|
||||
"title": "Get Notifications from",
|
||||
"anyone": "Anyone",
|
||||
"followers": "People who follow you",
|
||||
"follow": "People you follow",
|
||||
"noone": "No one"
|
||||
},
|
||||
"alert": {
|
||||
"mentions_and_replies": "Mentions & Replies",
|
||||
"boosts": "Boosts",
|
||||
"favorites": "Favorites",
|
||||
"new_followers": "New Followers"
|
||||
},
|
||||
"disabled": {
|
||||
"notification_hint": "Turn on notifications from your device settings to see updates on your lock screen.",
|
||||
"go_to_settings": "Go to Notification Settings"
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
"report": {
|
||||
|
@ -103,11 +103,6 @@
|
||||
357FEEBA29523D660021C9DC /* MastodonSDKDynamic in Frameworks */ = {isa = PBXBuildFile; productRef = 357FEEB929523D660021C9DC /* MastodonSDKDynamic */; };
|
||||
5B24BBDA262DB14800A9381B /* ReportViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B24BBD7262DB14800A9381B /* ReportViewModel.swift */; };
|
||||
5B24BBDB262DB14800A9381B /* ReportStatusViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B24BBD8262DB14800A9381B /* ReportStatusViewModel+Diffable.swift */; };
|
||||
5B90C45E262599800002E742 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B90C456262599800002E742 /* SettingsViewModel.swift */; };
|
||||
5B90C45F262599800002E742 /* SettingsToggleTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B90C459262599800002E742 /* SettingsToggleTableViewCell.swift */; };
|
||||
5B90C460262599800002E742 /* SettingsAppearanceTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B90C45A262599800002E742 /* SettingsAppearanceTableViewCell.swift */; };
|
||||
5B90C461262599800002E742 /* SettingsLinkTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B90C45B262599800002E742 /* SettingsLinkTableViewCell.swift */; };
|
||||
5B90C462262599800002E742 /* SettingsSectionHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B90C45C262599800002E742 /* SettingsSectionHeader.swift */; };
|
||||
5BB04FD5262E7AFF0043BFF6 /* ReportViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BB04FD4262E7AFF0043BFF6 /* ReportViewController.swift */; };
|
||||
5BB04FF5262F0E6D0043BFF6 /* ReportSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BB04FF4262F0E6D0043BFF6 /* ReportSection.swift */; };
|
||||
5D0393902612D259007FE196 /* WebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D03938F2612D259007FE196 /* WebViewController.swift */; };
|
||||
@ -140,6 +135,14 @@
|
||||
D81A22752AB4643200905D71 /* SearchResultsOverviewTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D81A22742AB4643200905D71 /* SearchResultsOverviewTableViewController.swift */; };
|
||||
D81A22782AB4782400905D71 /* SearchResultOverviewSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D81A22772AB4782400905D71 /* SearchResultOverviewSection.swift */; };
|
||||
D81A227B2AB47B9A00905D71 /* SearchResultDefaultSectionTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D81A227A2AB47B9A00905D71 /* SearchResultDefaultSectionTableViewCell.swift */; };
|
||||
D81D12462A4E1861005009D4 /* PolicySelectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D81D12452A4E1861005009D4 /* PolicySelectionViewController.swift */; };
|
||||
D81D124B2A4E1914005009D4 /* ToggleTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D81D124A2A4E1914005009D4 /* ToggleTableViewCell.swift */; };
|
||||
D82BD7532ABC44C2009A374A /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F9170E2A4B47EF008A5370 /* Coordinator.swift */; };
|
||||
D82BD7552ABC73AF009A374A /* NotificationPolicyTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D82BD7542ABC73AF009A374A /* NotificationPolicyTableViewCell.swift */; };
|
||||
D8318A802A4466D300C0FB73 /* SettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8318A7F2A4466D300C0FB73 /* SettingsCoordinator.swift */; };
|
||||
D8318A862A4468C700C0FB73 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8318A852A4468C700C0FB73 /* SettingsViewController.swift */; };
|
||||
D8318A882A4468D300C0FB73 /* NotificationSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8318A872A4468D300C0FB73 /* NotificationSettingsViewController.swift */; };
|
||||
D8318A8A2A4468DC00C0FB73 /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8318A892A4468DC00C0FB73 /* AboutViewController.swift */; };
|
||||
D8363B1629469CE200A74079 /* OnboardingNextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8363B1529469CE200A74079 /* OnboardingNextView.swift */; };
|
||||
D87BFC8B291D5C6B00FEE264 /* MastodonLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D87BFC8A291D5C6B00FEE264 /* MastodonLoginView.swift */; };
|
||||
D87BFC8D291EB81200FEE264 /* MastodonLoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D87BFC8C291EB81200FEE264 /* MastodonLoginViewModel.swift */; };
|
||||
@ -147,15 +150,29 @@
|
||||
D886FBD329DF710F00272017 /* WelcomeSeparatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D886FBD229DF710F00272017 /* WelcomeSeparatorView.swift */; };
|
||||
D8916DC029211BE500124085 /* ContentSizedTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8916DBF29211BE500124085 /* ContentSizedTableView.swift */; };
|
||||
D8A6AB6C291C5136003AB663 /* MastodonLoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8A6AB6B291C5136003AB663 /* MastodonLoginViewController.swift */; };
|
||||
D8B5E4EE2A4EB8930008970C /* NotificationSettingTableViewToggleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8B5E4ED2A4EB8920008970C /* NotificationSettingTableViewToggleCell.swift */; };
|
||||
D8B5E4F02A4EB8A00008970C /* NotificationSettingTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8B5E4EF2A4EB8A00008970C /* NotificationSettingTableViewCell.swift */; };
|
||||
D8B5E4F22A4EBCF90008970C /* NotificationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8B5E4F12A4EBCF90008970C /* NotificationSettings.swift */; };
|
||||
D8B5E4F42A4ED0240008970C /* NotificationSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8B5E4F32A4ED0240008970C /* NotificationSettingsViewModel.swift */; };
|
||||
D8BE30B32A179E26006B8270 /* SuggestionAccountTableViewFooter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8BE30B22A179E26006B8270 /* SuggestionAccountTableViewFooter.swift */; };
|
||||
D8BEBCB62A1B7FFD0004F475 /* SuggestionAccountTableViewCell+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8BEBCB52A1B7FFD0004F475 /* SuggestionAccountTableViewCell+ViewModel.swift */; };
|
||||
D8D688F62AB869CB000F651A /* SearchResultsProfileTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8D688F52AB869CB000F651A /* SearchResultsProfileTableViewCell.swift */; };
|
||||
D8D688F92AB8B970000F651A /* SearchResultOverviewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8D688F82AB8B970000F651A /* SearchResultOverviewCoordinator.swift */; };
|
||||
D8E5C346296DAB84007E76A7 /* DataSourceFacade+Status+History.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8E5C345296DAB84007E76A7 /* DataSourceFacade+Status+History.swift */; };
|
||||
D8E5C349296DB8A3007E76A7 /* StatusEditHistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8E5C348296DB8A3007E76A7 /* StatusEditHistoryViewController.swift */; };
|
||||
D8ECC8102AC31EA400AE0818 /* NotificationSettingsDisabledTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8ECC80F2AC31EA400AE0818 /* NotificationSettingsDisabledTableViewCell.swift */; };
|
||||
D8F0372C29D232730027DE2E /* HashtagIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F0372B29D232730027DE2E /* HashtagIntentHandler.swift */; };
|
||||
D8F8A03A29CA5C15000195DD /* HashtagWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F8A03929CA5C15000195DD /* HashtagWidgetView.swift */; };
|
||||
D8F8A03C29CA5CB6000195DD /* HashtagWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F8A03B29CA5CB6000195DD /* HashtagWidget.swift */; };
|
||||
D8F917012A4AD8A5008A5370 /* SettingsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F917002A4AD8A4008A5370 /* SettingsTableViewCell.swift */; };
|
||||
D8F917032A4B063D008A5370 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F917022A4B063D008A5370 /* Settings.swift */; };
|
||||
D8F917062A4B0791008A5370 /* GeneralSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F917052A4B0791008A5370 /* GeneralSettings.swift */; };
|
||||
D8F917082A4B0B16008A5370 /* GeneralSettingSelectionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F917072A4B0B16008A5370 /* GeneralSettingSelectionCell.swift */; };
|
||||
D8F9170B2A4B2C80008A5370 /* AboutSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F9170A2A4B2C80008A5370 /* AboutSettings.swift */; };
|
||||
D8F9170D2A4B3C6F008A5370 /* AboutMastodonTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F9170C2A4B3C6F008A5370 /* AboutMastodonTableViewCell.swift */; };
|
||||
D8F917112A4C6B40008A5370 /* GeneralSettingToggleTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F917102A4C6B40008A5370 /* GeneralSettingToggleTableViewCell.swift */; };
|
||||
D8F917122A4C6B67008A5370 /* GeneralSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8318A832A4468A800C0FB73 /* GeneralSettingsViewController.swift */; };
|
||||
D8F917142A4D74C3008A5370 /* GeneralSettingsDiffableTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F917132A4D74C3008A5370 /* GeneralSettingsDiffableTableViewDataSource.swift */; };
|
||||
DB0009A626AEE5DC009B9D2D /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = DB0009A926AEE5DC009B9D2D /* Intents.intentdefinition */; settings = {ATTRIBUTES = (codegen, ); }; };
|
||||
DB0009A726AEE5DC009B9D2D /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = DB0009A926AEE5DC009B9D2D /* Intents.intentdefinition */; };
|
||||
DB023D26279FFB0A005AC798 /* ShareActivityProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB023D25279FFB0A005AC798 /* ShareActivityProvider.swift */; };
|
||||
@ -244,7 +261,6 @@
|
||||
DB427DE225BAA00100D1B89D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DB427DE025BAA00100D1B89D /* LaunchScreen.storyboard */; };
|
||||
DB427DED25BAA00100D1B89D /* MastodonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DEC25BAA00100D1B89D /* MastodonTests.swift */; };
|
||||
DB427DF825BAA00100D1B89D /* MastodonUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DF725BAA00100D1B89D /* MastodonUITests.swift */; };
|
||||
DB443CD42694627B00159B29 /* AppearanceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB443CD32694627B00159B29 /* AppearanceView.swift */; };
|
||||
DB4481B925EE289600BEFB67 /* UITableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4481B825EE289600BEFB67 /* UITableView.swift */; };
|
||||
DB45FAB625CA5485005A8AC7 /* UIAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB45FAB525CA5485005A8AC7 /* UIAlertController.swift */; };
|
||||
DB45FAD725CA6C76005A8AC7 /* UIBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB45FAD625CA6C76005A8AC7 /* UIBarButtonItem.swift */; };
|
||||
@ -338,9 +354,6 @@
|
||||
DB6B7500272FF73800C70B6E /* UserTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6B74FF272FF73800C70B6E /* UserTableViewCell.swift */; };
|
||||
DB6B750427300B4000C70B6E /* TimelineFooterTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6B750327300B4000C70B6E /* TimelineFooterTableViewCell.swift */; };
|
||||
DB6D9F3526351B7A008423CD /* NotificationService+Decrypt.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D9F3426351B7A008423CD /* NotificationService+Decrypt.swift */; };
|
||||
DB6D9F7D26358ED4008423CD /* SettingsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D9F7C26358ED4008423CD /* SettingsSection.swift */; };
|
||||
DB6D9F8426358EEC008423CD /* SettingsItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D9F8326358EEC008423CD /* SettingsItem.swift */; };
|
||||
DB6D9F9726367249008423CD /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D9F9626367249008423CD /* SettingsViewController.swift */; };
|
||||
DB72601C25E36A2100235243 /* MastodonServerRulesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB72601B25E36A2100235243 /* MastodonServerRulesViewController.swift */; };
|
||||
DB72602725E36A6F00235243 /* MastodonServerRulesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB72602625E36A6F00235243 /* MastodonServerRulesViewModel.swift */; };
|
||||
DB7274F4273BB9B200577D95 /* ListBatchFetchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7274F3273BB9B200577D95 /* ListBatchFetchViewModel.swift */; };
|
||||
@ -384,7 +397,6 @@
|
||||
DB98EB6227B215EB0082E365 /* ReportResultViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98EB6127B215EB0082E365 /* ReportResultViewController.swift */; };
|
||||
DB98EB6527B216500082E365 /* ReportResultViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98EB6427B216500082E365 /* ReportResultViewModel.swift */; };
|
||||
DB98EB6927B21A7C0082E365 /* ReportResultActionTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98EB6827B21A7C0082E365 /* ReportResultActionTableViewCell.swift */; };
|
||||
DB98EB6B27B243470082E365 /* SettingsAppearanceTableViewCell+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98EB6A27B243470082E365 /* SettingsAppearanceTableViewCell+ViewModel.swift */; };
|
||||
DB9D6BE925E4F5340051B173 /* SearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9D6BE825E4F5340051B173 /* SearchViewController.swift */; };
|
||||
DB9D6BF825E4F5690051B173 /* NotificationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9D6BF725E4F5690051B173 /* NotificationViewController.swift */; };
|
||||
DB9D6BFF25E4F5940051B173 /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9D6BFE25E4F5940051B173 /* ProfileViewController.swift */; };
|
||||
@ -717,11 +729,6 @@
|
||||
46DAB0EBDDFB678347CD96FF /* Pods-MastodonTests.asdk - release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MastodonTests.asdk - release.xcconfig"; path = "Target Support Files/Pods-MastodonTests/Pods-MastodonTests.asdk - release.xcconfig"; sourceTree = "<group>"; };
|
||||
5B24BBD7262DB14800A9381B /* ReportViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReportViewModel.swift; sourceTree = "<group>"; };
|
||||
5B24BBD8262DB14800A9381B /* ReportStatusViewModel+Diffable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ReportStatusViewModel+Diffable.swift"; sourceTree = "<group>"; };
|
||||
5B90C456262599800002E742 /* SettingsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = "<group>"; };
|
||||
5B90C459262599800002E742 /* SettingsToggleTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsToggleTableViewCell.swift; sourceTree = "<group>"; };
|
||||
5B90C45A262599800002E742 /* SettingsAppearanceTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsAppearanceTableViewCell.swift; sourceTree = "<group>"; };
|
||||
5B90C45B262599800002E742 /* SettingsLinkTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsLinkTableViewCell.swift; sourceTree = "<group>"; };
|
||||
5B90C45C262599800002E742 /* SettingsSectionHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsSectionHeader.swift; sourceTree = "<group>"; };
|
||||
5BB04FD4262E7AFF0043BFF6 /* ReportViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportViewController.swift; sourceTree = "<group>"; };
|
||||
5BB04FF4262F0E6D0043BFF6 /* ReportSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportSection.swift; sourceTree = "<group>"; };
|
||||
5CE45680252519F42FEA2D13 /* Pods-ShareActionExtension.asdk - release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareActionExtension.asdk - release.xcconfig"; path = "Target Support Files/Pods-ShareActionExtension/Pods-ShareActionExtension.asdk - release.xcconfig"; sourceTree = "<group>"; };
|
||||
@ -774,10 +781,19 @@
|
||||
D81A22742AB4643200905D71 /* SearchResultsOverviewTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultsOverviewTableViewController.swift; sourceTree = "<group>"; };
|
||||
D81A22772AB4782400905D71 /* SearchResultOverviewSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultOverviewSection.swift; sourceTree = "<group>"; };
|
||||
D81A227A2AB47B9A00905D71 /* SearchResultDefaultSectionTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultDefaultSectionTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D81D12452A4E1861005009D4 /* PolicySelectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolicySelectionViewController.swift; sourceTree = "<group>"; };
|
||||
D81D124A2A4E1914005009D4 /* ToggleTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D82463522A52B47B00A3DBDD /* be */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = be; path = be.lproj/Intents.strings; sourceTree = "<group>"; };
|
||||
D82463532A52B47B00A3DBDD /* be */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = be; path = be.lproj/WidgetExtension.strings; sourceTree = "<group>"; };
|
||||
D82463542A52B47B00A3DBDD /* be */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = be; path = be.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
D82463552A52B47B00A3DBDD /* be */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = be; path = be.lproj/Intents.stringsdict; sourceTree = "<group>"; };
|
||||
D82BD7512ABC42D6009A374A /* Coordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coordinator.swift; sourceTree = "<group>"; };
|
||||
D82BD7542ABC73AF009A374A /* NotificationPolicyTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPolicyTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D8318A7F2A4466D300C0FB73 /* SettingsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCoordinator.swift; sourceTree = "<group>"; };
|
||||
D8318A832A4468A800C0FB73 /* GeneralSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralSettingsViewController.swift; sourceTree = "<group>"; };
|
||||
D8318A852A4468C700C0FB73 /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = "<group>"; };
|
||||
D8318A872A4468D300C0FB73 /* NotificationSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsViewController.swift; sourceTree = "<group>"; };
|
||||
D8318A892A4468DC00C0FB73 /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = "<group>"; };
|
||||
D8363B1529469CE200A74079 /* OnboardingNextView.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = OnboardingNextView.swift; sourceTree = "<group>"; tabWidth = 4; };
|
||||
D87BFC8A291D5C6B00FEE264 /* MastodonLoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginView.swift; sourceTree = "<group>"; };
|
||||
D87BFC8C291EB81200FEE264 /* MastodonLoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginViewModel.swift; sourceTree = "<group>"; };
|
||||
@ -795,15 +811,29 @@
|
||||
D8A6FE6429325F5900666A47 /* StringsConvertor */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = StringsConvertor; sourceTree = "<group>"; };
|
||||
D8A6FE6529325F5900666A47 /* ios-infoPlist.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "ios-infoPlist.json"; sourceTree = "<group>"; };
|
||||
D8A6FE6629325F5900666A47 /* Localizable.stringsdict */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; path = Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
D8B5E4ED2A4EB8920008970C /* NotificationSettingTableViewToggleCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationSettingTableViewToggleCell.swift; sourceTree = "<group>"; };
|
||||
D8B5E4EF2A4EB8A00008970C /* NotificationSettingTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D8B5E4F12A4EBCF90008970C /* NotificationSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettings.swift; sourceTree = "<group>"; };
|
||||
D8B5E4F32A4ED0240008970C /* NotificationSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsViewModel.swift; sourceTree = "<group>"; };
|
||||
D8BE30B22A179E26006B8270 /* SuggestionAccountTableViewFooter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionAccountTableViewFooter.swift; sourceTree = "<group>"; };
|
||||
D8BEBCB52A1B7FFD0004F475 /* SuggestionAccountTableViewCell+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SuggestionAccountTableViewCell+ViewModel.swift"; sourceTree = "<group>"; };
|
||||
D8D688F52AB869CB000F651A /* SearchResultsProfileTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultsProfileTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D8D688F82AB8B970000F651A /* SearchResultOverviewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultOverviewCoordinator.swift; sourceTree = "<group>"; };
|
||||
D8E5C345296DAB84007E76A7 /* DataSourceFacade+Status+History.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+Status+History.swift"; sourceTree = "<group>"; };
|
||||
D8E5C348296DB8A3007E76A7 /* StatusEditHistoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusEditHistoryViewController.swift; sourceTree = "<group>"; };
|
||||
D8ECC80F2AC31EA400AE0818 /* NotificationSettingsDisabledTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsDisabledTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D8F0372B29D232730027DE2E /* HashtagIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagIntentHandler.swift; sourceTree = "<group>"; };
|
||||
D8F8A03929CA5C15000195DD /* HashtagWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagWidgetView.swift; sourceTree = "<group>"; };
|
||||
D8F8A03B29CA5CB6000195DD /* HashtagWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagWidget.swift; sourceTree = "<group>"; };
|
||||
D8F917002A4AD8A4008A5370 /* SettingsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D8F917022A4B063D008A5370 /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = "<group>"; };
|
||||
D8F917052A4B0791008A5370 /* GeneralSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralSettings.swift; sourceTree = "<group>"; };
|
||||
D8F917072A4B0B16008A5370 /* GeneralSettingSelectionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralSettingSelectionCell.swift; sourceTree = "<group>"; };
|
||||
D8F9170A2A4B2C80008A5370 /* AboutSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutSettings.swift; sourceTree = "<group>"; };
|
||||
D8F9170C2A4B3C6F008A5370 /* AboutMastodonTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutMastodonTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D8F9170E2A4B47EF008A5370 /* Coordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coordinator.swift; sourceTree = "<group>"; };
|
||||
D8F917102A4C6B40008A5370 /* GeneralSettingToggleTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralSettingToggleTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D8F917132A4D74C3008A5370 /* GeneralSettingsDiffableTableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralSettingsDiffableTableViewDataSource.swift; sourceTree = "<group>"; };
|
||||
DB0009A826AEE5DC009B9D2D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; name = Base; path = Base.lproj/Intents.intentdefinition; sourceTree = "<group>"; };
|
||||
DB0009AD26AEE5E4009B9D2D /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Intents.strings; sourceTree = "<group>"; };
|
||||
DB023D25279FFB0A005AC798 /* ShareActivityProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareActivityProvider.swift; sourceTree = "<group>"; };
|
||||
@ -902,7 +932,6 @@
|
||||
DB427DF325BAA00100D1B89D /* MastodonUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MastodonUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DB427DF725BAA00100D1B89D /* MastodonUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonUITests.swift; sourceTree = "<group>"; };
|
||||
DB427DF925BAA00100D1B89D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
DB443CD32694627B00159B29 /* AppearanceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearanceView.swift; sourceTree = "<group>"; };
|
||||
DB4481B825EE289600BEFB67 /* UITableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITableView.swift; sourceTree = "<group>"; };
|
||||
DB45FAB525CA5485005A8AC7 /* UIAlertController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIAlertController.swift; sourceTree = "<group>"; };
|
||||
DB45FAD625CA6C76005A8AC7 /* UIBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIBarButtonItem.swift; sourceTree = "<group>"; };
|
||||
@ -1026,9 +1055,6 @@
|
||||
DB6B74FF272FF73800C70B6E /* UserTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserTableViewCell.swift; sourceTree = "<group>"; };
|
||||
DB6B750327300B4000C70B6E /* TimelineFooterTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineFooterTableViewCell.swift; sourceTree = "<group>"; };
|
||||
DB6D9F3426351B7A008423CD /* NotificationService+Decrypt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NotificationService+Decrypt.swift"; sourceTree = "<group>"; };
|
||||
DB6D9F7C26358ED4008423CD /* SettingsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSection.swift; sourceTree = "<group>"; };
|
||||
DB6D9F8326358EEC008423CD /* SettingsItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsItem.swift; sourceTree = "<group>"; };
|
||||
DB6D9F9626367249008423CD /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = "<group>"; };
|
||||
DB72601B25E36A2100235243 /* MastodonServerRulesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonServerRulesViewController.swift; sourceTree = "<group>"; };
|
||||
DB72602625E36A6F00235243 /* MastodonServerRulesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonServerRulesViewModel.swift; sourceTree = "<group>"; };
|
||||
DB7274F3273BB9B200577D95 /* ListBatchFetchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListBatchFetchViewModel.swift; sourceTree = "<group>"; };
|
||||
@ -1088,7 +1114,6 @@
|
||||
DB98EB6127B215EB0082E365 /* ReportResultViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportResultViewController.swift; sourceTree = "<group>"; };
|
||||
DB98EB6427B216500082E365 /* ReportResultViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportResultViewModel.swift; sourceTree = "<group>"; };
|
||||
DB98EB6827B21A7C0082E365 /* ReportResultActionTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportResultActionTableViewCell.swift; sourceTree = "<group>"; };
|
||||
DB98EB6A27B243470082E365 /* SettingsAppearanceTableViewCell+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SettingsAppearanceTableViewCell+ViewModel.swift"; sourceTree = "<group>"; };
|
||||
DB9D6BE825E4F5340051B173 /* SearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewController.swift; sourceTree = "<group>"; };
|
||||
DB9D6BF725E4F5690051B173 /* NotificationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationViewController.swift; sourceTree = "<group>"; };
|
||||
DB9D6BFE25E4F5940051B173 /* ProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = "<group>"; };
|
||||
@ -1610,7 +1635,6 @@
|
||||
DB0617F727855B010030EE79 /* Notification */,
|
||||
DB4F097726A039A200D62E92 /* Search */,
|
||||
DB3E6FE52806A5BA00B035AE /* Discovery */,
|
||||
DB0617FA27855B660030EE79 /* Settings */,
|
||||
);
|
||||
path = Diffable;
|
||||
sourceTree = "<group>";
|
||||
@ -1728,34 +1752,16 @@
|
||||
5B90C455262599800002E742 /* Settings */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5B90C458262599800002E742 /* Cell */,
|
||||
5B90C457262599800002E742 /* View */,
|
||||
DB6D9F9626367249008423CD /* SettingsViewController.swift */,
|
||||
5B90C456262599800002E742 /* SettingsViewModel.swift */,
|
||||
D81D12492A4E190A005009D4 /* Shared */,
|
||||
D8F916FF2A4AD898008A5370 /* Settings Overview */,
|
||||
D8F917042A4B0657008A5370 /* General Settings */,
|
||||
D81D12432A4E181C005009D4 /* Notification Settings */,
|
||||
D8F917092A4B2AFF008A5370 /* About Mastodon */,
|
||||
D8318A7F2A4466D300C0FB73 /* SettingsCoordinator.swift */,
|
||||
);
|
||||
path = Settings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5B90C457262599800002E742 /* View */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5B90C45C262599800002E742 /* SettingsSectionHeader.swift */,
|
||||
DB443CD32694627B00159B29 /* AppearanceView.swift */,
|
||||
);
|
||||
path = View;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5B90C458262599800002E742 /* Cell */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5B90C45A262599800002E742 /* SettingsAppearanceTableViewCell.swift */,
|
||||
DB98EB6A27B243470082E365 /* SettingsAppearanceTableViewCell+ViewModel.swift */,
|
||||
5B90C459262599800002E742 /* SettingsToggleTableViewCell.swift */,
|
||||
5B90C45B262599800002E742 /* SettingsLinkTableViewCell.swift */,
|
||||
);
|
||||
path = Cell;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5D03938E2612D200007FE196 /* Webview */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -1807,6 +1813,26 @@
|
||||
path = Cells;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D81D12432A4E181C005009D4 /* Notification Settings */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D8ECC80D2AC31E0F00AE0818 /* Policy Selection */,
|
||||
D8ECC80E2AC31E2C00AE0818 /* Cells */,
|
||||
D8318A872A4468D300C0FB73 /* NotificationSettingsViewController.swift */,
|
||||
D8B5E4F12A4EBCF90008970C /* NotificationSettings.swift */,
|
||||
D8B5E4F32A4ED0240008970C /* NotificationSettingsViewModel.swift */,
|
||||
);
|
||||
path = "Notification Settings";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D81D12492A4E190A005009D4 /* Shared */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D81D124A2A4E1914005009D4 /* ToggleTableViewCell.swift */,
|
||||
);
|
||||
path = Shared;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D8A6AB68291C50F3003AB663 /* Login */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -1843,6 +1869,25 @@
|
||||
path = "Edit History";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D8ECC80D2AC31E0F00AE0818 /* Policy Selection */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D81D12452A4E1861005009D4 /* PolicySelectionViewController.swift */,
|
||||
D82BD7542ABC73AF009A374A /* NotificationPolicyTableViewCell.swift */,
|
||||
);
|
||||
path = "Policy Selection";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D8ECC80E2AC31E2C00AE0818 /* Cells */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D8B5E4ED2A4EB8920008970C /* NotificationSettingTableViewToggleCell.swift */,
|
||||
D8B5E4EF2A4EB8A00008970C /* NotificationSettingTableViewCell.swift */,
|
||||
D8ECC80F2AC31EA400AE0818 /* NotificationSettingsDisabledTableViewCell.swift */,
|
||||
);
|
||||
path = Cells;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D8F8A03829CA5C02000195DD /* Hashtag */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -1852,6 +1897,38 @@
|
||||
path = Hashtag;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D8F916FF2A4AD898008A5370 /* Settings Overview */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D8F917002A4AD8A4008A5370 /* SettingsTableViewCell.swift */,
|
||||
D8318A852A4468C700C0FB73 /* SettingsViewController.swift */,
|
||||
D8F917022A4B063D008A5370 /* Settings.swift */,
|
||||
);
|
||||
path = "Settings Overview";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D8F917042A4B0657008A5370 /* General Settings */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D8318A832A4468A800C0FB73 /* GeneralSettingsViewController.swift */,
|
||||
D8F917052A4B0791008A5370 /* GeneralSettings.swift */,
|
||||
D8F917102A4C6B40008A5370 /* GeneralSettingToggleTableViewCell.swift */,
|
||||
D8F917072A4B0B16008A5370 /* GeneralSettingSelectionCell.swift */,
|
||||
D8F917132A4D74C3008A5370 /* GeneralSettingsDiffableTableViewDataSource.swift */,
|
||||
);
|
||||
path = "General Settings";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D8F917092A4B2AFF008A5370 /* About Mastodon */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D8318A892A4468DC00C0FB73 /* AboutViewController.swift */,
|
||||
D8F9170A2A4B2C80008A5370 /* AboutSettings.swift */,
|
||||
D8F9170C2A4B3C6F008A5370 /* AboutMastodonTableViewCell.swift */,
|
||||
);
|
||||
path = "About Mastodon";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DB01409B25C40BB600F9F3CF /* Onboarding */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -1895,15 +1972,6 @@
|
||||
path = Profile;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DB0617FA27855B660030EE79 /* Settings */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DB6D9F7C26358ED4008423CD /* SettingsSection.swift */,
|
||||
DB6D9F8326358EEC008423CD /* SettingsItem.swift */,
|
||||
);
|
||||
path = Settings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DB0618082785B2790030EE79 /* Cell */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -2473,6 +2541,7 @@
|
||||
children = (
|
||||
DB8AF54225C13647002E6C99 /* SceneCoordinator.swift */,
|
||||
DB8AF54325C13647002E6C99 /* NeedsDependency.swift */,
|
||||
D8F9170E2A4B47EF008A5370 /* Coordinator.swift */,
|
||||
);
|
||||
path = Coordinator;
|
||||
sourceTree = "<group>";
|
||||
@ -2575,6 +2644,7 @@
|
||||
CD92E0F10BDE4FE7C4B999F2 /* Pods_MastodonTests.framework */,
|
||||
2DA7D05025CA545E00804E11 /* LoadMoreConfigurableTableViewContainer.swift */,
|
||||
DB1FD45F25F278AF004CFCFC /* CategoryPickerSection.swift */,
|
||||
D82BD7512ABC42D6009A374A /* Coordinator.swift */,
|
||||
);
|
||||
name = "Recovered References";
|
||||
sourceTree = "<group>";
|
||||
@ -3392,7 +3462,6 @@
|
||||
};
|
||||
DB025B8E278D6448002F581E /* Run Sourcery: Core Data */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 12;
|
||||
files = (
|
||||
);
|
||||
@ -3411,7 +3480,6 @@
|
||||
};
|
||||
DB3D100425BAA71500EAA174 /* Run SwiftGen */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 12;
|
||||
files = (
|
||||
);
|
||||
@ -3430,7 +3498,6 @@
|
||||
};
|
||||
DB697DD2278F48D5004EF2F7 /* Run Sourcery */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 12;
|
||||
files = (
|
||||
);
|
||||
@ -3560,7 +3627,6 @@
|
||||
2D571B2F26004EC000540450 /* NavigationBarProgressView.swift in Sources */,
|
||||
D8099078294BC8A30050219F /* PrivacyTableViewController.swift in Sources */,
|
||||
0FAA101225E105390017CCDE /* PrimaryActionButton.swift in Sources */,
|
||||
DB443CD42694627B00159B29 /* AppearanceView.swift in Sources */,
|
||||
DBF1D24E269DAF5D00C1C08A /* SearchDetailViewController.swift in Sources */,
|
||||
62FD27D52893708A00B205C5 /* BookmarkViewModel+Diffable.swift in Sources */,
|
||||
DB72602725E36A6F00235243 /* MastodonServerRulesViewModel.swift in Sources */,
|
||||
@ -3587,6 +3653,7 @@
|
||||
DB63F767279A5EB300455B82 /* NotificationTimelineViewModel.swift in Sources */,
|
||||
2D607AD826242FC500B70763 /* NotificationViewModel.swift in Sources */,
|
||||
DB5B54AB2833C12A00DEF8B2 /* RebloggedByViewController.swift in Sources */,
|
||||
D81D12462A4E1861005009D4 /* PolicySelectionViewController.swift in Sources */,
|
||||
DB848E33282B62A800A302CC /* ReportResultView.swift in Sources */,
|
||||
DBABE3EC25ECAC4B00879EE5 /* WelcomeIllustrationView.swift in Sources */,
|
||||
DB0FCB9C27980AB6006C02E2 /* HashtagTimelineViewController+DataSourceProvider.swift in Sources */,
|
||||
@ -3631,17 +3698,18 @@
|
||||
DB603113279EBEBA00A935FE /* DataSourceFacade+Block.swift in Sources */,
|
||||
DB63F777279A9A2A00455B82 /* NotificationView+Configuration.swift in Sources */,
|
||||
DB029E95266A20430062874E /* MastodonAuthenticationController.swift in Sources */,
|
||||
5B90C461262599800002E742 /* SettingsLinkTableViewCell.swift in Sources */,
|
||||
DB6180DD263918E30018D199 /* MediaPreviewViewController.swift in Sources */,
|
||||
DBE3CDEC261C6B2900430CC6 /* FavoriteViewController.swift in Sources */,
|
||||
DB938EE62623F50700E5B6C1 /* ThreadViewController.swift in Sources */,
|
||||
DB6180F426391D110018D199 /* MediaPreviewImageView.swift in Sources */,
|
||||
DBF9814A265E24F500E4BA07 /* ProfileFieldCollectionViewHeaderFooterView.swift in Sources */,
|
||||
2D939AB525EDD8A90076FA61 /* String.swift in Sources */,
|
||||
D8B5E4F22A4EBCF90008970C /* NotificationSettings.swift in Sources */,
|
||||
DB4481B925EE289600BEFB67 /* UITableView.swift in Sources */,
|
||||
DB5B54B02833C24200DEF8B2 /* FavoritedByViewController+DataSourceProvider.swift in Sources */,
|
||||
DBE3CDBB261C427900430CC6 /* TimelineHeaderTableViewCell.swift in Sources */,
|
||||
DB159C2B27A17BAC0068DC77 /* DataSourceFacade+Media.swift in Sources */,
|
||||
D8F917082A4B0B16008A5370 /* GeneralSettingSelectionCell.swift in Sources */,
|
||||
0FAA101C25E10E760017CCDE /* UIFont.swift in Sources */,
|
||||
2D38F1D525CD465300561493 /* HomeTimelineViewController.swift in Sources */,
|
||||
DB6180E926391BDF0018D199 /* MediaHostToMediaPreviewViewControllerAnimatedTransitioning.swift in Sources */,
|
||||
@ -3658,9 +3726,9 @@
|
||||
2D38F1EB25CD477000561493 /* HomeTimelineViewModel+LoadLatestState.swift in Sources */,
|
||||
DB5B7295273112B100081888 /* FollowingListViewController.swift in Sources */,
|
||||
0F202201261326E6000C64BF /* HashtagTimelineViewModel.swift in Sources */,
|
||||
DB6D9F9726367249008423CD /* SettingsViewController.swift in Sources */,
|
||||
DB63F7452799056400455B82 /* HashtagTableViewCell.swift in Sources */,
|
||||
DBAE3FAF26172FC0004B8251 /* RemoteProfileViewModel.swift in Sources */,
|
||||
D82BD7552ABC73AF009A374A /* NotificationPolicyTableViewCell.swift in Sources */,
|
||||
DB3EA8EB281B7E0700598866 /* DiscoveryCommunityViewModel+State.swift in Sources */,
|
||||
DB0F8150264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift in Sources */,
|
||||
DB98EB5327B0F9890082E365 /* ReportHeadlineTableViewCell.swift in Sources */,
|
||||
@ -3669,6 +3737,7 @@
|
||||
DB5B549D2833A67400DEF8B2 /* FamiliarFollowersViewModel.swift in Sources */,
|
||||
DBB9759C262462E1004620BD /* ThreadMetaView.swift in Sources */,
|
||||
DB5B729E273113F300081888 /* FollowingListViewModel+State.swift in Sources */,
|
||||
D8B5E4F02A4EB8A00008970C /* NotificationSettingTableViewCell.swift in Sources */,
|
||||
DBF9814C265E339500E4BA07 /* ProfileFieldAddEntryCollectionViewCell.swift in Sources */,
|
||||
DB63F76227996B6600455B82 /* SearchHistoryViewController+DataSourceProvider.swift in Sources */,
|
||||
DBA5E7AB263BD3F5004598BB /* TimelineTableViewCellContextMenuConfiguration.swift in Sources */,
|
||||
@ -3680,13 +3749,15 @@
|
||||
DB6180E02639194B0018D199 /* MediaPreviewPagingViewController.swift in Sources */,
|
||||
D8E5C349296DB8A3007E76A7 /* StatusEditHistoryViewController.swift in Sources */,
|
||||
DBE0822425CD3F1E00FD6BBD /* MastodonRegisterViewModel.swift in Sources */,
|
||||
5B90C45E262599800002E742 /* SettingsViewModel.swift in Sources */,
|
||||
D8318A882A4468D300C0FB73 /* NotificationSettingsViewController.swift in Sources */,
|
||||
DB5B54A12833A89600DEF8B2 /* FamiliarFollowersViewController+DataSourceProvider.swift in Sources */,
|
||||
2D82B9FF25E7863200E36F0F /* OnboardingViewControllerAppearance.swift in Sources */,
|
||||
DB025B78278D606A002F581E /* StatusItem.swift in Sources */,
|
||||
D82BD7532ABC44C2009A374A /* Coordinator.swift in Sources */,
|
||||
DB697DD4278F4927004EF2F7 /* StatusTableViewCellDelegate.swift in Sources */,
|
||||
2A506CF6292D040100059C37 /* HashtagTimelineHeaderView.swift in Sources */,
|
||||
DBA5E7A5263BD28C004598BB /* ContextMenuImagePreviewViewModel.swift in Sources */,
|
||||
D81D124B2A4E1914005009D4 /* ToggleTableViewCell.swift in Sources */,
|
||||
DB3E6FF52807C40300B035AE /* DiscoveryForYouViewController.swift in Sources */,
|
||||
D809907A294BC9390050219F /* PrivacyTableViewCell.swift in Sources */,
|
||||
2D38F1E525CD46C100561493 /* HomeTimelineViewModel.swift in Sources */,
|
||||
@ -3710,7 +3781,6 @@
|
||||
DB7F48452620241000796008 /* ProfileHeaderViewModel.swift in Sources */,
|
||||
DB0A322E280EE9FD001729D2 /* DiscoveryIntroBannerView.swift in Sources */,
|
||||
2D3F9E0425DFA133004262D9 /* UITapGestureRecognizer.swift in Sources */,
|
||||
5B90C45F262599800002E742 /* SettingsToggleTableViewCell.swift in Sources */,
|
||||
2D694A7425F9EB4E0038ADDC /* ContentWarningOverlayView.swift in Sources */,
|
||||
DB0FCB7827957678006C02E2 /* DataSourceProvider+UITableViewDelegate.swift in Sources */,
|
||||
5D0393902612D259007FE196 /* WebViewController.swift in Sources */,
|
||||
@ -3719,7 +3789,7 @@
|
||||
DB6B74F2272FB67600C70B6E /* FollowerListViewModel.swift in Sources */,
|
||||
0F20222D261457EE000C64BF /* HashtagTimelineViewModel+State.swift in Sources */,
|
||||
DB0009A626AEE5DC009B9D2D /* Intents.intentdefinition in Sources */,
|
||||
5B90C462262599800002E742 /* SettingsSectionHeader.swift in Sources */,
|
||||
D8F917032A4B063D008A5370 /* Settings.swift in Sources */,
|
||||
5DF1056425F887CB00D6C0D4 /* AVPlayer.swift in Sources */,
|
||||
DB3E6FEF2806D82600B035AE /* DiscoveryNewsViewModel.swift in Sources */,
|
||||
DBC7A672260C897100E57475 /* StatusContentWarningEditorView.swift in Sources */,
|
||||
@ -3746,11 +3816,12 @@
|
||||
DBDFF197280556D900557A48 /* DiscoveryPostsViewModel+State.swift in Sources */,
|
||||
DB0FCB7A279576A2006C02E2 /* DataSourceFacade+Thread.swift in Sources */,
|
||||
DB9F58EF26EF491E00E7BBE9 /* AccountListViewModel.swift in Sources */,
|
||||
DB6D9F7D26358ED4008423CD /* SettingsSection.swift in Sources */,
|
||||
DB0FCB9A2797F7AD006C02E2 /* UserView+Configuration.swift in Sources */,
|
||||
DB023D2827A0FABD005AC798 /* NotificationTableViewCellDelegate.swift in Sources */,
|
||||
DB8AF55025C13703002E6C99 /* MainTabBarController.swift in Sources */,
|
||||
DB023D2C27A10464005AC798 /* NotificationTimelineViewController+DataSourceProvider.swift in Sources */,
|
||||
D8F917062A4B0791008A5370 /* GeneralSettings.swift in Sources */,
|
||||
D8F917012A4AD8A5008A5370 /* SettingsTableViewCell.swift in Sources */,
|
||||
DB9D6BE925E4F5340051B173 /* SearchViewController.swift in Sources */,
|
||||
DBF1D257269DBAC600C1C08A /* SearchDetailViewModel.swift in Sources */,
|
||||
DBB45B5927B39FE4002DC5A7 /* MediaPreviewVideoViewModel.swift in Sources */,
|
||||
@ -3765,6 +3836,7 @@
|
||||
DB0FCB942797E2B0006C02E2 /* SearchResultViewModel+Diffable.swift in Sources */,
|
||||
DB63F752279944AA00455B82 /* SearchHistorySectionHeaderCollectionReusableView.swift in Sources */,
|
||||
DB3E6FDD2806A40F00B035AE /* DiscoveryHashtagsViewController.swift in Sources */,
|
||||
D8F9170B2A4B2C80008A5370 /* AboutSettings.swift in Sources */,
|
||||
DB938EED2623F79B00E5B6C1 /* ThreadViewModel.swift in Sources */,
|
||||
DB6988DE2848D11C002398EF /* PagerTabStripNavigateable.swift in Sources */,
|
||||
2DCB73FD2615C13900EC03D4 /* SearchRecommendCollectionHeader.swift in Sources */,
|
||||
@ -3785,7 +3857,6 @@
|
||||
0FB3D33825E6401400AAD544 /* PickServerCell.swift in Sources */,
|
||||
6213AF5C28939C8A00BCADB6 /* BookmarkViewModel+State.swift in Sources */,
|
||||
D807C6C029DE197900A4E17C /* EducationViewController.swift in Sources */,
|
||||
DB6D9F8426358EEC008423CD /* SettingsItem.swift in Sources */,
|
||||
2D364F7825E66D8300204FDC /* MastodonResendEmailViewModel.swift in Sources */,
|
||||
DBEFCD7B282A162400C0ABEA /* ReportReasonView.swift in Sources */,
|
||||
D8E5C346296DAB84007E76A7 /* DataSourceFacade+Status+History.swift in Sources */,
|
||||
@ -3801,6 +3872,7 @@
|
||||
DBA5E7A9263BD3A4004598BB /* ContextMenuImagePreviewViewController.swift in Sources */,
|
||||
DBF156E22702DA6900EC00B7 /* UIStatusBarManager+HandleTapAction.m in Sources */,
|
||||
2D38F20825CD491300561493 /* DisposeBagCollectable.swift in Sources */,
|
||||
D8F9170D2A4B3C6F008A5370 /* AboutMastodonTableViewCell.swift in Sources */,
|
||||
DB697DE1278F5296004EF2F7 /* DataSourceFacade+Model.swift in Sources */,
|
||||
DBCC3B8F26148F7B0045B23D /* CachedProfileViewModel.swift in Sources */,
|
||||
DB4F097526A037F500D62E92 /* SearchHistoryViewModel.swift in Sources */,
|
||||
@ -3824,6 +3896,7 @@
|
||||
DB4F097D26A03A5B00D62E92 /* SearchHistoryItem.swift in Sources */,
|
||||
DBD5B1FA27BD013700BD6B38 /* DataSourceProvider+StatusTableViewControllerNavigateable.swift in Sources */,
|
||||
D809907C294D25510050219F /* PrivacyViewModel.swift in Sources */,
|
||||
D8318A862A4468C700C0FB73 /* SettingsViewController.swift in Sources */,
|
||||
DB5B549A2833A60400DEF8B2 /* FamiliarFollowersViewController.swift in Sources */,
|
||||
DB3E6FE22806A50100B035AE /* DiscoveryHashtagsViewModel+Diffable.swift in Sources */,
|
||||
DB427DD625BAA00100D1B89D /* AppDelegate.swift in Sources */,
|
||||
@ -3837,7 +3910,6 @@
|
||||
DBFEEC96279BDC67004F81DD /* ProfileAboutViewController.swift in Sources */,
|
||||
DB63F74F2799405600455B82 /* SearchHistoryViewModel+Diffable.swift in Sources */,
|
||||
DB0EF72B26FDB1D200347686 /* SidebarListCollectionViewCell.swift in Sources */,
|
||||
5B90C460262599800002E742 /* SettingsAppearanceTableViewCell.swift in Sources */,
|
||||
DB63F74D27993F5B00455B82 /* SearchHistoryUserCollectionViewCell.swift in Sources */,
|
||||
DB8AF54425C13647002E6C99 /* SceneCoordinator.swift in Sources */,
|
||||
DB1D84382657B275000346B3 /* SegmentedControlNavigateable.swift in Sources */,
|
||||
@ -3848,14 +3920,15 @@
|
||||
DB0FCB862796BDA1006C02E2 /* SearchSection.swift in Sources */,
|
||||
DB1D61CF26F1B33600DA8662 /* WelcomeViewModel.swift in Sources */,
|
||||
D8BEBCB62A1B7FFD0004F475 /* SuggestionAccountTableViewCell+ViewModel.swift in Sources */,
|
||||
D8B5E4F42A4ED0240008970C /* NotificationSettingsViewModel.swift in Sources */,
|
||||
DBD376B2269302A4007FEC24 /* UITableViewCell.swift in Sources */,
|
||||
DB4F0966269ED52200D62E92 /* SearchResultViewModel.swift in Sources */,
|
||||
D8F917142A4D74C3008A5370 /* GeneralSettingsDiffableTableViewDataSource.swift in Sources */,
|
||||
DB6180FA26391F2E0018D199 /* MediaPreviewViewModel.swift in Sources */,
|
||||
2D7631A825C1535600929FB9 /* StatusTableViewCell.swift in Sources */,
|
||||
DB6B7500272FF73800C70B6E /* UserTableViewCell.swift in Sources */,
|
||||
DB1D842E26552C4D000346B3 /* StatusTableViewControllerNavigateable.swift in Sources */,
|
||||
DB938F1F2624382F00E5B6C1 /* ThreadViewModel+Diffable.swift in Sources */,
|
||||
DB98EB6B27B243470082E365 /* SettingsAppearanceTableViewCell+ViewModel.swift in Sources */,
|
||||
DBB45B5B27B3A109002DC5A7 /* MediaPreviewTransitionViewController.swift in Sources */,
|
||||
DB427DD825BAA00100D1B89D /* SceneDelegate.swift in Sources */,
|
||||
DB4932B926F31AD300EF46D4 /* BadgeButton.swift in Sources */,
|
||||
@ -3878,6 +3951,7 @@
|
||||
2A82294F29262EE000D2A1F7 /* AppContext+NextAccount.swift in Sources */,
|
||||
DBB525302611EBF3002F1F29 /* ProfilePagingViewModel.swift in Sources */,
|
||||
DB9F58EC26EF435000E7BBE9 /* AccountViewController.swift in Sources */,
|
||||
D8318A802A4466D300C0FB73 /* SettingsCoordinator.swift in Sources */,
|
||||
DB3E6FF12806D96900B035AE /* DiscoveryNewsViewModel+Diffable.swift in Sources */,
|
||||
DB3E6FF82807C45300B035AE /* DiscoveryForYouViewModel.swift in Sources */,
|
||||
DB0F9D56283EB46200379AF8 /* ProfileHeaderView+Configuration.swift in Sources */,
|
||||
@ -3885,7 +3959,10 @@
|
||||
DBB525412611ED54002F1F29 /* ProfileHeaderViewController.swift in Sources */,
|
||||
DBDFF19A28055A1400557A48 /* DiscoveryViewController.swift in Sources */,
|
||||
DB9D6BFF25E4F5940051B173 /* ProfileViewController.swift in Sources */,
|
||||
D8F917112A4C6B40008A5370 /* GeneralSettingToggleTableViewCell.swift in Sources */,
|
||||
D8B5E4EE2A4EB8930008970C /* NotificationSettingTableViewToggleCell.swift in Sources */,
|
||||
DB3EA8F1281B9EF600598866 /* DiscoveryCommunityViewModel+Diffable.swift in Sources */,
|
||||
D8318A8A2A4468DC00C0FB73 /* AboutViewController.swift in Sources */,
|
||||
85BC11B32932414900E191CD /* AltTextViewController.swift in Sources */,
|
||||
DB63F775279A997D00455B82 /* NotificationTableViewCell+ViewModel.swift in Sources */,
|
||||
DB98EB5927B109890082E365 /* ReportSupplementaryViewController.swift in Sources */,
|
||||
@ -3894,11 +3971,13 @@
|
||||
DB63F74B279914A000455B82 /* FollowingListViewController+DataSourceProvider.swift in Sources */,
|
||||
DBEFCD7D282A2A3B00C0ABEA /* ReportServerRulesViewController.swift in Sources */,
|
||||
DBB525362611ECEB002F1F29 /* UserTimelineViewController.swift in Sources */,
|
||||
D8F917122A4C6B67008A5370 /* GeneralSettingsViewController.swift in Sources */,
|
||||
DB98EB4927B0F0CD0082E365 /* ReportStatusTableViewCell.swift in Sources */,
|
||||
855149CA29606D6400943D96 /* PortraitAlertController.swift in Sources */,
|
||||
DBF3B7412733EB9400E21627 /* MastodonLocalCode.swift in Sources */,
|
||||
DB98EB6527B216500082E365 /* ReportResultViewModel.swift in Sources */,
|
||||
DB4F096A269EDAD200D62E92 /* SearchResultViewModel+State.swift in Sources */,
|
||||
D8ECC8102AC31EA400AE0818 /* NotificationSettingsDisabledTableViewCell.swift in Sources */,
|
||||
5BB04FF5262F0E6D0043BFF6 /* ReportSection.swift in Sources */,
|
||||
DBEFCD82282A2AB100C0ABEA /* ReportServerRulesView.swift in Sources */,
|
||||
DBA94436265CBB7400C537E1 /* ProfileFieldItem.swift in Sources */,
|
||||
|
7
Mastodon/Coordinator/Coordinator.swift
Normal file
7
Mastodon/Coordinator/Coordinator.swift
Normal file
@ -0,0 +1,7 @@
|
||||
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol Coordinator {
|
||||
func start()
|
||||
}
|
@ -30,6 +30,9 @@ final public class SceneCoordinator {
|
||||
private(set) weak var splitViewController: RootSplitViewController?
|
||||
|
||||
private(set) var secondaryStackHashValues = Set<Int>()
|
||||
var childCoordinator: Coordinator?
|
||||
|
||||
private var mastodonAuthenticationController: MastodonAuthenticationController?
|
||||
|
||||
init(
|
||||
scene: UIScene,
|
||||
@ -99,18 +102,18 @@ final public class SceneCoordinator {
|
||||
let notificationID = String(pushNotification.notificationID)
|
||||
|
||||
switch type {
|
||||
case .follow:
|
||||
let profileViewModel = RemoteProfileViewModel(context: appContext, authContext: authContext, notificationID: notificationID)
|
||||
_ = self.present(scene: .profile(viewModel: profileViewModel), from: from, transition: .show)
|
||||
case .followRequest:
|
||||
// do nothing
|
||||
break
|
||||
case .mention, .reblog, .favourite, .poll, .status:
|
||||
let threadViewModel = RemoteThreadViewModel(context: appContext, authContext: authContext, notificationID: notificationID)
|
||||
_ = self.present(scene: .thread(viewModel: threadViewModel), from: from, transition: .show)
|
||||
case ._other:
|
||||
assertionFailure()
|
||||
break
|
||||
case .follow:
|
||||
let profileViewModel = RemoteProfileViewModel(context: appContext, authContext: authContext, notificationID: notificationID)
|
||||
_ = self.present(scene: .profile(viewModel: profileViewModel), from: from, transition: .show)
|
||||
case .followRequest:
|
||||
// do nothing
|
||||
break
|
||||
case .mention, .reblog, .favourite, .poll, .status:
|
||||
let threadViewModel = RemoteThreadViewModel(context: appContext, authContext: authContext, notificationID: notificationID)
|
||||
_ = self.present(scene: .thread(viewModel: threadViewModel), from: from, transition: .show)
|
||||
case ._other:
|
||||
assertionFailure()
|
||||
break
|
||||
}
|
||||
} // end DispatchQueue.main.async
|
||||
|
||||
@ -137,6 +140,7 @@ extension SceneCoordinator {
|
||||
case safariPresent(animated: Bool, completion: (() -> Void)? = nil)
|
||||
case alertController(animated: Bool, completion: (() -> Void)? = nil)
|
||||
case activityViewControllerPresent(animated: Bool, completion: (() -> Void)? = nil)
|
||||
case none
|
||||
}
|
||||
|
||||
enum Scene {
|
||||
@ -165,7 +169,7 @@ extension SceneCoordinator {
|
||||
|
||||
// Hashtag Timeline
|
||||
case hashtagTimeline(viewModel: HashtagTimelineViewModel)
|
||||
|
||||
|
||||
// profile
|
||||
case accountList(viewModel: AccountListViewModel)
|
||||
case profile(viewModel: ProfileViewModel)
|
||||
@ -179,7 +183,7 @@ extension SceneCoordinator {
|
||||
case followedTags(viewModel: FollowedTagsViewModel)
|
||||
|
||||
// setting
|
||||
case settings(viewModel: SettingsViewModel)
|
||||
case settings(setting: Setting)
|
||||
|
||||
// report
|
||||
case report(viewModel: ReportViewModel)
|
||||
@ -201,19 +205,19 @@ extension SceneCoordinator {
|
||||
|
||||
var isOnboarding: Bool {
|
||||
switch self {
|
||||
case .welcome,
|
||||
.mastodonPickServer,
|
||||
.mastodonRegister,
|
||||
.mastodonLogin,
|
||||
.mastodonServerRules,
|
||||
.mastodonConfirmEmail,
|
||||
.mastodonResendEmail:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
case .welcome,
|
||||
.mastodonPickServer,
|
||||
.mastodonRegister,
|
||||
.mastodonLogin,
|
||||
.mastodonServerRules,
|
||||
.mastodonConfirmEmail,
|
||||
.mastodonResendEmail:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
} // end enum Scene { }
|
||||
} // end enum Scene { }
|
||||
}
|
||||
|
||||
extension SceneCoordinator {
|
||||
@ -228,16 +232,16 @@ extension SceneCoordinator {
|
||||
self.authContext = _authContext
|
||||
|
||||
switch UIDevice.current.userInterfaceIdiom {
|
||||
case .phone:
|
||||
let viewController = MainTabBarController(context: appContext, coordinator: self, authContext: _authContext)
|
||||
self.splitViewController = nil
|
||||
self.tabBarController = viewController
|
||||
rootViewController = viewController
|
||||
default:
|
||||
let splitViewController = RootSplitViewController(context: appContext, coordinator: self, authContext: _authContext)
|
||||
self.splitViewController = splitViewController
|
||||
self.tabBarController = splitViewController.contentSplitViewController.mainTabBarController
|
||||
rootViewController = splitViewController
|
||||
case .phone:
|
||||
let viewController = MainTabBarController(context: appContext, coordinator: self, authContext: _authContext)
|
||||
self.splitViewController = nil
|
||||
self.tabBarController = viewController
|
||||
rootViewController = viewController
|
||||
default:
|
||||
let splitViewController = RootSplitViewController(context: appContext, coordinator: self, authContext: _authContext)
|
||||
self.splitViewController = splitViewController
|
||||
self.tabBarController = splitViewController.contentSplitViewController.mainTabBarController
|
||||
rootViewController = splitViewController
|
||||
}
|
||||
sceneDelegate.window?.rootViewController = rootViewController // base: main
|
||||
|
||||
@ -263,7 +267,7 @@ extension SceneCoordinator {
|
||||
@MainActor
|
||||
@discardableResult
|
||||
func present(scene: Scene, from sender: UIViewController? = nil, transition: Transition) -> UIViewController? {
|
||||
guard let viewController = get(scene: scene) else {
|
||||
guard let viewController = get(scene: scene, from: sender) else {
|
||||
return nil
|
||||
}
|
||||
guard var presentingViewController = sender ?? sceneDelegate.window?.rootViewController?.topMost else {
|
||||
@ -272,16 +276,16 @@ extension SceneCoordinator {
|
||||
// adapt for child controller
|
||||
if let navigationControllerVisibleViewController = presentingViewController.navigationController?.visibleViewController {
|
||||
switch viewController {
|
||||
case is ProfileViewController:
|
||||
let title: String = {
|
||||
let title = navigationControllerVisibleViewController.navigationItem.title ?? ""
|
||||
return title.count > 10 ? "" : title
|
||||
}()
|
||||
let barButtonItem = UIBarButtonItem(title: title, style: .plain, target: nil, action: nil)
|
||||
barButtonItem.tintColor = .white
|
||||
navigationControllerVisibleViewController.navigationItem.backBarButtonItem = barButtonItem
|
||||
default:
|
||||
navigationControllerVisibleViewController.navigationItem.backBarButtonItem = nil
|
||||
case is ProfileViewController:
|
||||
let title: String = {
|
||||
let title = navigationControllerVisibleViewController.navigationItem.title ?? ""
|
||||
return title.count > 10 ? "" : title
|
||||
}()
|
||||
let barButtonItem = UIBarButtonItem(title: title, style: .plain, target: nil, action: nil)
|
||||
barButtonItem.tintColor = .white
|
||||
navigationControllerVisibleViewController.navigationItem.backBarButtonItem = barButtonItem
|
||||
default:
|
||||
navigationControllerVisibleViewController.navigationItem.backBarButtonItem = nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -292,69 +296,72 @@ extension SceneCoordinator {
|
||||
}
|
||||
|
||||
switch transition {
|
||||
case .show:
|
||||
presentingViewController.show(viewController, sender: sender)
|
||||
case .showDetail:
|
||||
secondaryStackHashValues.insert(viewController.hashValue)
|
||||
let navigationController = AdaptiveStatusBarStyleNavigationController(rootViewController: viewController)
|
||||
presentingViewController.showDetailViewController(navigationController, sender: sender)
|
||||
|
||||
case .modal(let animated, let completion):
|
||||
let modalNavigationController: UINavigationController = {
|
||||
if scene.isOnboarding {
|
||||
return OnboardingNavigationController(rootViewController: viewController)
|
||||
} else {
|
||||
return AdaptiveStatusBarStyleNavigationController(rootViewController: viewController)
|
||||
}
|
||||
}()
|
||||
modalNavigationController.modalPresentationCapturesStatusBarAppearance = true
|
||||
if let adaptivePresentationControllerDelegate = viewController as? UIAdaptivePresentationControllerDelegate {
|
||||
modalNavigationController.presentationController?.delegate = adaptivePresentationControllerDelegate
|
||||
}
|
||||
presentingViewController.present(modalNavigationController, animated: animated, completion: completion)
|
||||
case .none:
|
||||
// do nothing
|
||||
break
|
||||
case .show:
|
||||
presentingViewController.show(viewController, sender: sender)
|
||||
case .showDetail:
|
||||
secondaryStackHashValues.insert(viewController.hashValue)
|
||||
let navigationController = AdaptiveStatusBarStyleNavigationController(rootViewController: viewController)
|
||||
presentingViewController.showDetailViewController(navigationController, sender: sender)
|
||||
|
||||
case .panModal:
|
||||
guard let panModalPresentable = viewController as? PanModalPresentable & UIViewController else {
|
||||
assertionFailure()
|
||||
return nil
|
||||
}
|
||||
|
||||
// https://github.com/slackhq/PanModal/issues/74#issuecomment-572426441
|
||||
panModalPresentable.modalPresentationStyle = .custom
|
||||
panModalPresentable.modalPresentationCapturesStatusBarAppearance = true
|
||||
panModalPresentable.transitioningDelegate = PanModalPresentationDelegate.default
|
||||
presentingViewController.present(panModalPresentable, animated: true, completion: nil)
|
||||
//presentingViewController.presentPanModal(panModalPresentable)
|
||||
case .popover(let sourceView):
|
||||
viewController.modalPresentationStyle = .popover
|
||||
viewController.popoverPresentationController?.sourceView = sourceView
|
||||
(splitViewController ?? presentingViewController)?.present(viewController, animated: true, completion: nil)
|
||||
case .custom(let transitioningDelegate):
|
||||
viewController.modalPresentationStyle = .custom
|
||||
viewController.transitioningDelegate = transitioningDelegate
|
||||
viewController.modalPresentationCapturesStatusBarAppearance = true
|
||||
(splitViewController ?? presentingViewController)?.present(viewController, animated: true, completion: nil)
|
||||
|
||||
case .customPush(let animated):
|
||||
// set delegate in view controller
|
||||
assert(sender?.navigationController?.delegate != nil)
|
||||
sender?.navigationController?.pushViewController(viewController, animated: animated)
|
||||
|
||||
case .safariPresent(let animated, let completion):
|
||||
if UserDefaults.shared.preferredUsingDefaultBrowser, case let .safari(url) = scene {
|
||||
UIApplication.shared.open(url, options: [:], completionHandler: nil)
|
||||
} else {
|
||||
case .modal(let animated, let completion):
|
||||
let modalNavigationController: UINavigationController = {
|
||||
if scene.isOnboarding {
|
||||
return OnboardingNavigationController(rootViewController: viewController)
|
||||
} else {
|
||||
return AdaptiveStatusBarStyleNavigationController(rootViewController: viewController)
|
||||
}
|
||||
}()
|
||||
modalNavigationController.modalPresentationCapturesStatusBarAppearance = true
|
||||
if let adaptivePresentationControllerDelegate = viewController as? UIAdaptivePresentationControllerDelegate {
|
||||
modalNavigationController.presentationController?.delegate = adaptivePresentationControllerDelegate
|
||||
}
|
||||
presentingViewController.present(modalNavigationController, animated: animated, completion: completion)
|
||||
|
||||
case .panModal:
|
||||
guard let panModalPresentable = viewController as? PanModalPresentable & UIViewController else {
|
||||
assertionFailure()
|
||||
return nil
|
||||
}
|
||||
|
||||
// https://github.com/slackhq/PanModal/issues/74#issuecomment-572426441
|
||||
panModalPresentable.modalPresentationStyle = .custom
|
||||
panModalPresentable.modalPresentationCapturesStatusBarAppearance = true
|
||||
panModalPresentable.transitioningDelegate = PanModalPresentationDelegate.default
|
||||
presentingViewController.present(panModalPresentable, animated: true, completion: nil)
|
||||
//presentingViewController.presentPanModal(panModalPresentable)
|
||||
case .popover(let sourceView):
|
||||
viewController.modalPresentationStyle = .popover
|
||||
viewController.popoverPresentationController?.sourceView = sourceView
|
||||
(splitViewController ?? presentingViewController)?.present(viewController, animated: true, completion: nil)
|
||||
case .custom(let transitioningDelegate):
|
||||
viewController.modalPresentationStyle = .custom
|
||||
viewController.transitioningDelegate = transitioningDelegate
|
||||
viewController.modalPresentationCapturesStatusBarAppearance = true
|
||||
(splitViewController ?? presentingViewController)?.present(viewController, animated: true, completion: nil)
|
||||
|
||||
case .customPush(let animated):
|
||||
// set delegate in view controller
|
||||
assert(sender?.navigationController?.delegate != nil)
|
||||
sender?.navigationController?.pushViewController(viewController, animated: animated)
|
||||
|
||||
case .safariPresent(let animated, let completion):
|
||||
if UserDefaults.shared.preferredUsingDefaultBrowser, case let .safari(url) = scene {
|
||||
UIApplication.shared.open(url, options: [:], completionHandler: nil)
|
||||
} else {
|
||||
viewController.modalPresentationCapturesStatusBarAppearance = true
|
||||
presentingViewController.present(viewController, animated: animated, completion: completion)
|
||||
}
|
||||
|
||||
case .alertController(let animated, let completion):
|
||||
viewController.modalPresentationCapturesStatusBarAppearance = true
|
||||
presentingViewController.present(viewController, animated: animated, completion: completion)
|
||||
|
||||
case .activityViewControllerPresent(let animated, let completion):
|
||||
viewController.modalPresentationCapturesStatusBarAppearance = true
|
||||
presentingViewController.present(viewController, animated: animated, completion: completion)
|
||||
}
|
||||
|
||||
case .alertController(let animated, let completion):
|
||||
viewController.modalPresentationCapturesStatusBarAppearance = true
|
||||
presentingViewController.present(viewController, animated: animated, completion: completion)
|
||||
|
||||
case .activityViewControllerPresent(let animated, let completion):
|
||||
viewController.modalPresentationCapturesStatusBarAppearance = true
|
||||
presentingViewController.present(viewController, animated: animated, completion: completion)
|
||||
}
|
||||
|
||||
return viewController
|
||||
@ -373,7 +380,7 @@ extension SceneCoordinator {
|
||||
|
||||
private extension SceneCoordinator {
|
||||
|
||||
func get(scene: Scene) -> UIViewController? {
|
||||
func get(scene: Scene, from sender: UIViewController? = nil) -> UIViewController? {
|
||||
let viewController: UIViewController?
|
||||
|
||||
switch scene {
|
||||
@ -414,8 +421,6 @@ private extension SceneCoordinator {
|
||||
let _viewController = WebViewController()
|
||||
_viewController.viewModel = viewModel
|
||||
viewController = _viewController
|
||||
|
||||
|
||||
case .searchDetail(let viewModel):
|
||||
let _viewController = SearchDetailViewController(appContext: appContext, sceneCoordinator: self, authContext: viewModel.authContext)
|
||||
_viewController.viewModel = viewModel
|
||||
@ -426,8 +431,6 @@ private extension SceneCoordinator {
|
||||
searchResultViewController.coordinator = self
|
||||
searchResultViewController.viewModel = viewModel
|
||||
viewController = searchResultViewController
|
||||
|
||||
|
||||
case .compose(let viewModel):
|
||||
let _viewController = ComposeViewController(viewModel: viewModel)
|
||||
viewController = _viewController
|
||||
@ -519,7 +522,7 @@ private extension SceneCoordinator {
|
||||
_viewController.preferredBarTintColor = SystemTheme.navigationBarBackgroundColor
|
||||
_viewController.preferredControlTintColor = Asset.Colors.Brand.blurple.color
|
||||
viewController = _viewController
|
||||
|
||||
|
||||
case .alertController(let alertController):
|
||||
if let popoverPresentationController = alertController.popoverPresentationController {
|
||||
assert(
|
||||
@ -533,10 +536,23 @@ private extension SceneCoordinator {
|
||||
activityViewController.popoverPresentationController?.sourceView = sourceView
|
||||
activityViewController.popoverPresentationController?.barButtonItem = barButtonItem
|
||||
viewController = activityViewController
|
||||
case .settings(let viewModel):
|
||||
let _viewController = SettingsViewController()
|
||||
_viewController.viewModel = viewModel
|
||||
viewController = _viewController
|
||||
case .settings(let setting):
|
||||
guard let presentedOn = sender,
|
||||
let accountName = authContext?.mastodonAuthenticationBox.authenticationRecord.object(in: appContext.managedObjectContext)?.username,
|
||||
let authContext
|
||||
else { return nil }
|
||||
|
||||
let settingsCoordinator = SettingsCoordinator(presentedOn: presentedOn,
|
||||
accountName: accountName,
|
||||
setting: setting,
|
||||
appContext: appContext,
|
||||
authContext: authContext)
|
||||
settingsCoordinator.delegate = self
|
||||
settingsCoordinator.start()
|
||||
|
||||
viewController = settingsCoordinator.navigationController
|
||||
childCoordinator = settingsCoordinator
|
||||
|
||||
case .editStatus(let viewModel):
|
||||
let composeViewController = ComposeViewController(viewModel: viewModel)
|
||||
viewController = composeViewController
|
||||
@ -556,11 +572,83 @@ private extension SceneCoordinator {
|
||||
//MARK: - MastodonLoginViewControllerDelegate
|
||||
|
||||
extension SceneCoordinator: MastodonLoginViewControllerDelegate {
|
||||
func backButtonPressed(_ viewController: MastodonLoginViewController) {
|
||||
viewController.navigationController?.popViewController(animated: true)
|
||||
}
|
||||
func backButtonPressed(_ viewController: MastodonLoginViewController) {
|
||||
viewController.navigationController?.popViewController(animated: true)
|
||||
}
|
||||
|
||||
func nextButtonPressed(_ viewController: MastodonLoginViewController) {
|
||||
viewController.login()
|
||||
}
|
||||
func nextButtonPressed(_ viewController: MastodonLoginViewController) {
|
||||
viewController.login()
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - SettingsCoordinatorDelegate
|
||||
extension SceneCoordinator: SettingsCoordinatorDelegate {
|
||||
func logout(_ settingsCoordinator: SettingsCoordinator) {
|
||||
let alertController = UIAlertController(
|
||||
title: L10n.Common.Alerts.SignOut.title,
|
||||
message: L10n.Common.Alerts.SignOut.message,
|
||||
preferredStyle: .actionSheet
|
||||
)
|
||||
|
||||
let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .cancel)
|
||||
let signOutAction = UIAlertAction(title: L10n.Common.Alerts.SignOut.confirm, style: .destructive) { [weak self] _ in
|
||||
guard let self, let authContext = self.authContext else { return }
|
||||
|
||||
self.appContext.notificationService.clearNotificationCountForActiveUser()
|
||||
|
||||
Task { @MainActor in
|
||||
try await self.appContext.authenticationService.signOutMastodonUser(
|
||||
authenticationBox: authContext.mastodonAuthenticationBox
|
||||
)
|
||||
|
||||
self.setup()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
alertController.addAction(cancelAction)
|
||||
alertController.addAction(signOutAction)
|
||||
|
||||
settingsCoordinator.navigationController.present(alertController, animated: true)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func openGithubURL(_ settingsCoordinator: SettingsCoordinator) {
|
||||
guard let githubURL = URL(string: "https://github.com/mastodon/mastodon-ios") else { return }
|
||||
|
||||
_ = present(
|
||||
scene: .safari(url: githubURL),
|
||||
from: settingsCoordinator.navigationController,
|
||||
transition: .safariPresent(animated: true)
|
||||
)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func openPrivacyURL(_ settingsCoordinator: SettingsCoordinator) {
|
||||
guard let authContext else { return }
|
||||
|
||||
let domain = authContext.mastodonAuthenticationBox.domain
|
||||
let privacyURL = Mastodon.API.privacyURL(domain: domain)
|
||||
|
||||
_ = present(scene: .safari(url: privacyURL),
|
||||
from: settingsCoordinator.navigationController,
|
||||
transition: .safariPresent(animated: true))
|
||||
|
||||
}
|
||||
|
||||
func openProfileSettingsURL(_ settingsCoordinator: SettingsCoordinator) {
|
||||
guard let authContext else { return }
|
||||
|
||||
let domain = authContext.mastodonAuthenticationBox.domain
|
||||
let profileSettingsURL = Mastodon.API.profileSettingsURL(domain: domain)
|
||||
|
||||
let authenticationController = MastodonAuthenticationController(context: appContext, authenticateURL: profileSettingsURL)
|
||||
|
||||
authenticationController.authenticationSession?.presentationContextProvider = settingsCoordinator
|
||||
authenticationController.authenticationSession?.start()
|
||||
|
||||
self.mastodonAuthenticationController = authenticationController
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,115 +0,0 @@
|
||||
//
|
||||
// SettingsItem.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-4-25.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonAsset
|
||||
import MastodonLocalization
|
||||
|
||||
enum SettingsItem {
|
||||
case appearance(record: ManagedObjectRecord<Setting>)
|
||||
case preference(settingRecord: ManagedObjectRecord<Setting>, preferenceType: PreferenceType)
|
||||
case notification(settingRecord: ManagedObjectRecord<Setting>, switchMode: NotificationSwitchMode)
|
||||
case boringZone(item: Link)
|
||||
case spicyZone(item: Link)
|
||||
}
|
||||
|
||||
extension SettingsItem {
|
||||
|
||||
enum AppearanceMode: String {
|
||||
case system
|
||||
case dark
|
||||
case light
|
||||
}
|
||||
|
||||
enum NotificationSwitchMode: CaseIterable, Hashable {
|
||||
case favorite
|
||||
case follow
|
||||
case reblog
|
||||
case mention
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
case .favorite: return L10n.Scene.Settings.Section.Notifications.favorites
|
||||
case .follow: return L10n.Scene.Settings.Section.Notifications.follows
|
||||
case .reblog: return L10n.Scene.Settings.Section.Notifications.boosts
|
||||
case .mention: return L10n.Scene.Settings.Section.Notifications.mentions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum PreferenceType: CaseIterable {
|
||||
case disableAvatarAnimation
|
||||
case disableEmojiAnimation
|
||||
case useDefaultBrowser
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
case .disableAvatarAnimation: return L10n.Scene.Settings.Section.Preference.disableAvatarAnimation
|
||||
case .disableEmojiAnimation: return L10n.Scene.Settings.Section.Preference.disableEmojiAnimation
|
||||
case .useDefaultBrowser: return L10n.Scene.Settings.Section.Preference.usingDefaultBrowser
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Link: CaseIterable, Hashable {
|
||||
case accountSettings
|
||||
case github
|
||||
case termsOfService
|
||||
case privacyPolicy
|
||||
case clearMediaCache
|
||||
case signOut
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
case .accountSettings: return L10n.Scene.Settings.Section.BoringZone.accountSettings
|
||||
case .github: return "GitHub"
|
||||
case .termsOfService: return L10n.Scene.Settings.Section.BoringZone.terms
|
||||
case .privacyPolicy: return L10n.Scene.Settings.Section.BoringZone.privacy
|
||||
case .clearMediaCache: return L10n.Scene.Settings.Section.SpicyZone.clear
|
||||
case .signOut: return L10n.Scene.Settings.Section.SpicyZone.signout
|
||||
}
|
||||
}
|
||||
|
||||
var textColor: UIColor? {
|
||||
switch self {
|
||||
case .accountSettings: return nil // tintColor
|
||||
case .github: return nil
|
||||
case .termsOfService: return nil
|
||||
case .privacyPolicy: return nil
|
||||
case .clearMediaCache: return .systemRed
|
||||
case .signOut: return .systemRed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension SettingsItem: Hashable {
|
||||
func hash(into hasher: inout Hasher) {
|
||||
switch self {
|
||||
case .appearance(let record):
|
||||
hasher.combine(String(describing: SettingsItem.AppearanceMode.self))
|
||||
hasher.combine(record)
|
||||
case .notification(let settingObjectID, let switchMode):
|
||||
hasher.combine(String(describing: SettingsItem.notification.self))
|
||||
hasher.combine(settingObjectID)
|
||||
hasher.combine(switchMode)
|
||||
case .preference(let settingObjectID, let preferenceType):
|
||||
hasher.combine(String(describing: SettingsItem.preference.self))
|
||||
hasher.combine(settingObjectID)
|
||||
hasher.combine(preferenceType)
|
||||
case .boringZone(let link):
|
||||
hasher.combine(String(describing: SettingsItem.boringZone.self))
|
||||
hasher.combine(link)
|
||||
case .spicyZone(let link):
|
||||
hasher.combine(String(describing: SettingsItem.spicyZone.self))
|
||||
hasher.combine(link)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,142 +0,0 @@
|
||||
//
|
||||
// SettingsSection.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-4-25.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonAsset
|
||||
import MastodonCore
|
||||
import MastodonLocalization
|
||||
|
||||
enum SettingsSection: Hashable {
|
||||
case appearance
|
||||
case preference
|
||||
case notifications
|
||||
case boringZone
|
||||
case spicyZone
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
case .appearance: return L10n.Scene.Settings.Section.LookAndFeel.title
|
||||
case .preference: return ""
|
||||
case .notifications: return L10n.Scene.Settings.Section.Notifications.title
|
||||
case .boringZone: return L10n.Scene.Settings.Section.BoringZone.title
|
||||
case .spicyZone: return L10n.Scene.Settings.Section.SpicyZone.title
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SettingsSection {
|
||||
static func tableViewDiffableDataSource(
|
||||
for tableView: UITableView,
|
||||
managedObjectContext: NSManagedObjectContext,
|
||||
settingsAppearanceTableViewCellDelegate: SettingsAppearanceTableViewCellDelegate,
|
||||
settingsToggleCellDelegate: SettingsToggleCellDelegate
|
||||
) -> UITableViewDiffableDataSource<SettingsSection, SettingsItem> {
|
||||
UITableViewDiffableDataSource(tableView: tableView) { [
|
||||
weak settingsAppearanceTableViewCellDelegate,
|
||||
weak settingsToggleCellDelegate
|
||||
] tableView, indexPath, item -> UITableViewCell? in
|
||||
switch item {
|
||||
case .appearance:
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: SettingsAppearanceTableViewCell.self), for: indexPath) as! SettingsAppearanceTableViewCell
|
||||
cell.delegate = settingsAppearanceTableViewCellDelegate
|
||||
return cell
|
||||
case .preference(let record, _):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: SettingsToggleTableViewCell.self), for: indexPath) as! SettingsToggleTableViewCell
|
||||
cell.delegate = settingsToggleCellDelegate
|
||||
managedObjectContext.performAndWait {
|
||||
guard let setting = record.object(in: managedObjectContext) else { return }
|
||||
SettingsSection.configureSettingToggle(cell: cell, item: item, setting: setting)
|
||||
|
||||
ManagedObjectObserver.observe(object: setting)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveCompletion: { _ in
|
||||
// do nothing
|
||||
}, receiveValue: { [weak cell] change in
|
||||
guard let cell = cell else { return }
|
||||
guard case .update(let object) = change.changeType,
|
||||
let setting = object as? Setting else { return }
|
||||
SettingsSection.configureSettingToggle(cell: cell, item: item, setting: setting)
|
||||
})
|
||||
.store(in: &cell.disposeBag)
|
||||
}
|
||||
return cell
|
||||
case .notification(let record, let switchMode):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: SettingsToggleTableViewCell.self), for: indexPath) as! SettingsToggleTableViewCell
|
||||
managedObjectContext.performAndWait {
|
||||
guard let setting = record.object(in: managedObjectContext) else { return }
|
||||
if let subscription = setting.activeSubscription {
|
||||
SettingsSection.configureSettingToggle(cell: cell, switchMode: switchMode, subscription: subscription)
|
||||
}
|
||||
ManagedObjectObserver.observe(object: setting)
|
||||
.sink(receiveCompletion: { _ in
|
||||
// do nothing
|
||||
}, receiveValue: { [weak cell] change in
|
||||
guard let cell = cell else { return }
|
||||
guard case .update(let object) = change.changeType,
|
||||
let setting = object as? Setting else { return }
|
||||
guard let subscription = setting.activeSubscription else { return }
|
||||
SettingsSection.configureSettingToggle(cell: cell, switchMode: switchMode, subscription: subscription)
|
||||
})
|
||||
.store(in: &cell.disposeBag)
|
||||
}
|
||||
cell.delegate = settingsToggleCellDelegate
|
||||
return cell
|
||||
case .boringZone(let item),
|
||||
.spicyZone(let item):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: SettingsLinkTableViewCell.self), for: indexPath) as! SettingsLinkTableViewCell
|
||||
cell.update(with: item)
|
||||
return cell
|
||||
} // end switch
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SettingsSection {
|
||||
|
||||
public static func configureSettingToggle(
|
||||
cell: SettingsToggleTableViewCell,
|
||||
item: SettingsItem,
|
||||
setting: Setting
|
||||
) {
|
||||
switch item {
|
||||
case .preference(_, let preferenceType):
|
||||
cell.textLabel?.text = preferenceType.title
|
||||
|
||||
switch preferenceType {
|
||||
case .disableAvatarAnimation:
|
||||
cell.switchButton.isOn = setting.preferredStaticAvatar
|
||||
case .disableEmojiAnimation:
|
||||
cell.switchButton.isOn = setting.preferredStaticEmoji
|
||||
case .useDefaultBrowser:
|
||||
cell.switchButton.isOn = setting.preferredUsingDefaultBrowser
|
||||
}
|
||||
|
||||
default:
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
|
||||
public static func configureSettingToggle(
|
||||
cell: SettingsToggleTableViewCell,
|
||||
switchMode: SettingsItem.NotificationSwitchMode,
|
||||
subscription: NotificationSubscription
|
||||
) {
|
||||
cell.textLabel?.text = switchMode.title
|
||||
|
||||
let enabled: Bool?
|
||||
switch switchMode {
|
||||
case .favorite: enabled = subscription.alert.favourite
|
||||
case .follow: enabled = subscription.alert.follow
|
||||
case .reblog: enabled = subscription.alert.reblog
|
||||
case .mention: enabled = subscription.alert.mention
|
||||
}
|
||||
cell.update(enabled: enabled)
|
||||
}
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
// Generated using Sourcery 1.9.0 — https://github.com/krzysztofzablocki/Sourcery
|
||||
// Generated using Sourcery 1.9.2 — https://github.com/krzysztofzablocki/Sourcery
|
||||
// DO NOT EDIT
|
||||
|
||||
// sourcery:inline:DiscoveryCommunityViewController.AutoGenerateTableViewDelegate
|
||||
|
@ -371,8 +371,8 @@ extension HomeTimelineViewController {
|
||||
|
||||
@objc private func settingBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
||||
guard let setting = context.settingService.currentSetting.value else { return }
|
||||
let settingsViewModel = SettingsViewModel(context: context, authContext: viewModel.authContext, setting: setting)
|
||||
_ = coordinator.present(scene: .settings(viewModel: settingsViewModel), from: self, transition: .modal(animated: true, completion: nil))
|
||||
|
||||
_ = coordinator.present(scene: .settings(setting: setting), from: self, transition: .none)
|
||||
}
|
||||
|
||||
@objc private func refreshControlValueChanged(_ sender: RefreshControl) {
|
||||
|
@ -72,22 +72,6 @@ final class HomeTimelineNavigationBarTitleViewModel {
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
// context.statusPublishService.latestPublishingComposeViewModel
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .sink { [weak self] composeViewModel in
|
||||
// guard let self = self else { return }
|
||||
// guard let composeViewModel = composeViewModel,
|
||||
// let state = composeViewModel.publishStateMachine.currentState else {
|
||||
// self.isPublishingPost.value = false
|
||||
// self.isPublished.value = false
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// self.isPublishingPost.value = state is ComposeViewModel.PublishState.Publishing || state is ComposeViewModel.PublishState.Fail
|
||||
// self.isPublished.value = state is ComposeViewModel.PublishState.Finish
|
||||
// }
|
||||
// .store(in: &disposeBag)
|
||||
|
||||
Publishers.CombineLatest4(
|
||||
hasNewPosts.eraseToAnyPublisher(),
|
||||
isOffline.eraseToAnyPublisher(),
|
||||
|
@ -15,8 +15,6 @@ import MastodonCore
|
||||
import MastodonLocalization
|
||||
|
||||
final class MastodonServerRulesViewController: UIViewController, NeedsDependency {
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
private var observations = Set<NSKeyValueObservation>()
|
||||
|
||||
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
|
||||
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
||||
|
@ -517,8 +517,8 @@ extension ProfileViewController {
|
||||
|
||||
@objc private func settingBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
||||
guard let setting = context.settingService.currentSetting.value else { return }
|
||||
let settingsViewModel = SettingsViewModel(context: context, authContext: viewModel.authContext, setting: setting)
|
||||
_ = coordinator.present(scene: .settings(viewModel: settingsViewModel), from: self, transition: .modal(animated: true, completion: nil))
|
||||
|
||||
_ = coordinator.present(scene: .settings(setting: setting), from: self, transition: .none)
|
||||
}
|
||||
|
||||
@objc private func shareBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
||||
|
@ -214,32 +214,7 @@ extension MainTabBarController {
|
||||
.store(in: &disposeBag)
|
||||
|
||||
// handle post failure
|
||||
// FIXME: refacotr
|
||||
// context.statusPublishService
|
||||
// .latestPublishingComposeViewModel
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .sink { [weak self] composeViewModel in
|
||||
// guard let self = self else { return }
|
||||
// guard let composeViewModel = composeViewModel else { return }
|
||||
// guard let currentState = composeViewModel.publishStateMachine.currentState else { return }
|
||||
// guard currentState is ComposeViewModel.PublishState.Fail else { return }
|
||||
//
|
||||
// let alertController = UIAlertController(title: L10n.Common.Alerts.PublishPostFailure.title, message: L10n.Common.Alerts.PublishPostFailure.message, preferredStyle: .alert)
|
||||
// let discardAction = UIAlertAction(title: L10n.Common.Controls.Actions.discard, style: .destructive) { [weak self, weak composeViewModel] _ in
|
||||
// guard let self = self else { return }
|
||||
// guard let composeViewModel = composeViewModel else { return }
|
||||
// self.context.statusPublishService.remove(composeViewModel: composeViewModel)
|
||||
// }
|
||||
// alertController.addAction(discardAction)
|
||||
// let retryAction = UIAlertAction(title: L10n.Common.Controls.Actions.tryAgain, style: .default) { [weak composeViewModel] _ in
|
||||
// guard let composeViewModel = composeViewModel else { return }
|
||||
// composeViewModel.publishStateMachine.enter(ComposeViewModel.PublishState.Publishing.self)
|
||||
// }
|
||||
// alertController.addAction(retryAction)
|
||||
// self.present(alertController, animated: true, completion: nil)
|
||||
// }
|
||||
// .store(in: &disposeBag)
|
||||
|
||||
|
||||
// handle push notification.
|
||||
// toggle entry when finish fetch latest notification
|
||||
Publishers.CombineLatest(
|
||||
@ -677,10 +652,9 @@ extension MainTabBarController {
|
||||
}
|
||||
|
||||
@objc private func openSettingsKeyCommandHandler(_ sender: UIKeyCommand) {
|
||||
guard let authContext = self.authContext else { return }
|
||||
guard let setting = context.settingService.currentSetting.value else { return }
|
||||
let settingsViewModel = SettingsViewModel(context: context, authContext: authContext, setting: setting)
|
||||
_ = coordinator.present(scene: .settings(viewModel: settingsViewModel), from: nil, transition: .modal(animated: true, completion: nil))
|
||||
|
||||
_ = coordinator.present(scene: .settings(setting: setting), from: self, transition: .none)
|
||||
}
|
||||
|
||||
@objc private func composeNewPostKeyCommandHandler(_ sender: UIKeyCommand) {
|
||||
|
@ -191,10 +191,9 @@ extension SidebarViewController: UICollectionViewDelegate {
|
||||
case .tab(let tab):
|
||||
delegate?.sidebarViewController(self, didSelectTab: tab)
|
||||
case .setting:
|
||||
guard let authContext = viewModel.authContext else { return }
|
||||
guard let setting = context.settingService.currentSetting.value else { return }
|
||||
let settingsViewModel = SettingsViewModel(context: context, authContext: authContext, setting: setting)
|
||||
_ = coordinator.present(scene: .settings(viewModel: settingsViewModel), from: self, transition: .modal(animated: true, completion: nil))
|
||||
|
||||
_ = coordinator.present(scene: .settings(setting: setting), from: self, transition: .none)
|
||||
case .compose:
|
||||
assertionFailure()
|
||||
}
|
||||
|
@ -5,10 +5,6 @@ import MastodonCore
|
||||
import MastodonSDK
|
||||
import MastodonLocalization
|
||||
|
||||
protocol Coordinator {
|
||||
func start()
|
||||
}
|
||||
|
||||
class SearchResultOverviewCoordinator: Coordinator {
|
||||
|
||||
let overviewViewController: SearchResultsOverviewTableViewController
|
||||
|
@ -0,0 +1,18 @@
|
||||
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import MastodonAsset
|
||||
|
||||
class AboutMastodonTableViewCell: UITableViewCell {
|
||||
static let reuseIdentifier = "AboutMastodonTableViewCell"
|
||||
|
||||
func configure(with entry: AboutSettingsEntry) {
|
||||
var contentConfiguration = UIListContentConfiguration.valueCell()
|
||||
|
||||
contentConfiguration.text = entry.text
|
||||
contentConfiguration.secondaryText = entry.secondaryText
|
||||
contentConfiguration.textProperties.color = Asset.Colors.Brand.blurple.color
|
||||
|
||||
self.contentConfiguration = contentConfiguration
|
||||
}
|
||||
}
|
38
Mastodon/Scene/Settings/About Mastodon/AboutSettings.swift
Normal file
38
Mastodon/Scene/Settings/About Mastodon/AboutSettings.swift
Normal file
@ -0,0 +1,38 @@
|
||||
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import MastodonCore
|
||||
import MastodonLocalization
|
||||
|
||||
struct AboutSettingsSection: Hashable {
|
||||
let entries: [AboutSettingsEntry]
|
||||
}
|
||||
|
||||
enum AboutSettingsEntry: Hashable {
|
||||
case evenMoreSettings
|
||||
case contributeToMastodon
|
||||
case privacyPolicy
|
||||
case clearMediaCache(Int)
|
||||
|
||||
var text: String {
|
||||
switch self {
|
||||
case .evenMoreSettings:
|
||||
return L10n.Scene.Settings.AboutMastodon.moreSettings
|
||||
case .contributeToMastodon:
|
||||
return L10n.Scene.Settings.AboutMastodon.contributeToMastodon
|
||||
case .privacyPolicy:
|
||||
return L10n.Scene.Settings.AboutMastodon.privacyPolicy
|
||||
case .clearMediaCache(_):
|
||||
return L10n.Scene.Settings.AboutMastodon.cleaerMediaStorage
|
||||
}
|
||||
}
|
||||
|
||||
var secondaryText: String? {
|
||||
switch self {
|
||||
case .evenMoreSettings, .contributeToMastodon, .privacyPolicy:
|
||||
return nil
|
||||
case .clearMediaCache(let mediaStorage):
|
||||
return AppContext.byteCountFormatter.string(fromByteCount: Int64(mediaStorage))
|
||||
}
|
||||
}
|
||||
}
|
136
Mastodon/Scene/Settings/About Mastodon/AboutViewController.swift
Normal file
136
Mastodon/Scene/Settings/About Mastodon/AboutViewController.swift
Normal file
@ -0,0 +1,136 @@
|
||||
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import MastodonCore
|
||||
import MastodonLocalization
|
||||
import MastodonAsset
|
||||
|
||||
protocol AboutViewControllerDelegate: AnyObject {
|
||||
func didSelect(_ viewController: AboutViewController, entry: AboutSettingsEntry)
|
||||
}
|
||||
|
||||
class AboutViewController: UIViewController {
|
||||
|
||||
let tableView: UITableView
|
||||
let tableFooterView: UIView
|
||||
let versionLabel: UILabel
|
||||
|
||||
private(set) var sections: [AboutSettingsSection] = []
|
||||
var tableViewDataSource: UITableViewDiffableDataSource<AboutSettingsSection, AboutSettingsEntry>?
|
||||
weak var delegate: AboutViewControllerDelegate?
|
||||
|
||||
init() {
|
||||
|
||||
tableView = UITableView(frame: .zero, style: .insetGrouped)
|
||||
tableView.translatesAutoresizingMaskIntoConstraints = false
|
||||
tableView.register(AboutMastodonTableViewCell.self, forCellReuseIdentifier: AboutMastodonTableViewCell.reuseIdentifier)
|
||||
|
||||
versionLabel = UILabel()
|
||||
versionLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
versionLabel.text = "Mastodon for iOS v\(UIApplication.appVersion()) (\(UIApplication.appBuild()))"
|
||||
versionLabel.font = UIFontMetrics(forTextStyle: .footnote).scaledFont(for: .systemFont(ofSize: 12, weight: .regular))
|
||||
versionLabel.textColor = Asset.Colors.Label.secondary.color
|
||||
versionLabel.numberOfLines = 0
|
||||
versionLabel.textAlignment = .center
|
||||
|
||||
tableFooterView = UIView()
|
||||
tableFooterView.addSubview(versionLabel)
|
||||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
||||
let tableViewDataSource = UITableViewDiffableDataSource<AboutSettingsSection, AboutSettingsEntry>(tableView: tableView) { [weak self] tableView, indexPath, itemIdentifier in
|
||||
|
||||
guard let self,
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: AboutMastodonTableViewCell.reuseIdentifier, for: indexPath) as? AboutMastodonTableViewCell else { fatalError("WTF?? Wrong Cell dude!") }
|
||||
|
||||
let entry = self.sections[indexPath.section].entries[indexPath.row]
|
||||
cell.configure(with: entry)
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
tableView.delegate = self
|
||||
tableView.dataSource = tableViewDataSource
|
||||
tableView.tableFooterView = tableFooterView
|
||||
|
||||
self.tableViewDataSource = tableViewDataSource
|
||||
|
||||
view.addSubview(tableView)
|
||||
view.backgroundColor = .systemGroupedBackground
|
||||
title = L10n.Scene.Settings.AboutMastodon.title
|
||||
|
||||
setupConstraints()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
update(with:
|
||||
[AboutSettingsSection(entries: [
|
||||
.evenMoreSettings,
|
||||
.contributeToMastodon,
|
||||
.privacyPolicy
|
||||
]),
|
||||
AboutSettingsSection(entries: [
|
||||
.clearMediaCache(AppContext.shared.currentDiskUsage())
|
||||
])]
|
||||
)
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
guard let footerView = self.tableView.tableFooterView else {
|
||||
return
|
||||
}
|
||||
|
||||
let width = self.tableView.bounds.size.width
|
||||
let size = footerView.systemLayoutSizeFitting(CGSize(width: width, height: UIView.layoutFittingCompressedSize.height))
|
||||
if footerView.frame.size.height != size.height {
|
||||
footerView.frame.size.height = size.height
|
||||
self.tableView.tableFooterView = footerView
|
||||
}
|
||||
}
|
||||
|
||||
private func setupConstraints() {
|
||||
let constraints = [
|
||||
tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
|
||||
tableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
|
||||
view.trailingAnchor.constraint(equalTo: tableView.safeAreaLayoutGuide.trailingAnchor),
|
||||
view.bottomAnchor.constraint(equalTo: tableView.safeAreaLayoutGuide.bottomAnchor),
|
||||
|
||||
versionLabel.topAnchor.constraint(equalTo: tableFooterView.topAnchor, constant: 8),
|
||||
versionLabel.leadingAnchor.constraint(equalTo: tableFooterView.leadingAnchor),
|
||||
tableFooterView.trailingAnchor.constraint(equalTo: versionLabel.trailingAnchor),
|
||||
tableFooterView.bottomAnchor.constraint(equalTo: versionLabel.bottomAnchor, constant: 16),
|
||||
|
||||
]
|
||||
|
||||
NSLayoutConstraint.activate(constraints)
|
||||
}
|
||||
|
||||
|
||||
func update(with sections: [AboutSettingsSection]) {
|
||||
self.sections = sections
|
||||
|
||||
var snapshot = NSDiffableDataSourceSnapshot<AboutSettingsSection, AboutSettingsEntry>()
|
||||
|
||||
for section in sections {
|
||||
snapshot.appendSections([section])
|
||||
snapshot.appendItems(section.entries)
|
||||
}
|
||||
|
||||
tableViewDataSource?.apply(snapshot, animatingDifferences: false)
|
||||
}
|
||||
}
|
||||
|
||||
extension AboutViewController: UITableViewDelegate {
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
|
||||
let entry = sections[indexPath.section].entries[indexPath.row]
|
||||
delegate?.didSelect(self, entry: entry)
|
||||
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
}
|
||||
}
|
||||
|
@ -1,59 +0,0 @@
|
||||
//
|
||||
// SettingsAppearanceTableViewCell+ViewModel.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK on 2022-2-8.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreDataStack
|
||||
|
||||
extension SettingsAppearanceTableViewCell {
|
||||
final class ViewModel: ObservableObject {
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
private var observations = Set<NSKeyValueObservation>()
|
||||
|
||||
// input
|
||||
@Published public var customUserInterfaceStyle: UIUserInterfaceStyle = .unspecified
|
||||
|
||||
// output
|
||||
@Published public var appearanceMode: SettingsItem.AppearanceMode = .system
|
||||
|
||||
init() {
|
||||
UserDefaults.shared.observe(\.customUserInterfaceStyle, options: [.initial, .new]) { [weak self] defaults, _ in
|
||||
guard let self = self else { return }
|
||||
self.customUserInterfaceStyle = defaults.customUserInterfaceStyle
|
||||
}
|
||||
.store(in: &observations)
|
||||
}
|
||||
|
||||
public func prepareForReuse() {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SettingsAppearanceTableViewCell.ViewModel {
|
||||
func bind(cell: SettingsAppearanceTableViewCell) {
|
||||
$customUserInterfaceStyle.removeDuplicates()
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { customUserInterfaceStyle in
|
||||
cell.appearanceViews.forEach { view in
|
||||
view.selected = false
|
||||
}
|
||||
|
||||
switch customUserInterfaceStyle {
|
||||
case .unspecified:
|
||||
cell.systemAppearanceView.selected = true
|
||||
case .dark:
|
||||
cell.darkAppearanceView.selected = true
|
||||
case .light:
|
||||
cell.lightAppearanceView.selected = true
|
||||
@unknown default:
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
}
|
@ -1,135 +0,0 @@
|
||||
//
|
||||
// SettingsAppearanceTableViewCell.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by ihugo on 2021/4/8.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Combine
|
||||
import MastodonAsset
|
||||
import MastodonLocalization
|
||||
|
||||
protocol SettingsAppearanceTableViewCellDelegate: AnyObject {
|
||||
func settingsAppearanceTableViewCell(_ cell: SettingsAppearanceTableViewCell, didSelectAppearanceMode appearanceMode: SettingsItem.AppearanceMode)
|
||||
}
|
||||
|
||||
class SettingsAppearanceTableViewCell: UITableViewCell {
|
||||
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
var observations = Set<NSKeyValueObservation>()
|
||||
|
||||
static let spacing: CGFloat = 28
|
||||
|
||||
weak var delegate: SettingsAppearanceTableViewCellDelegate?
|
||||
|
||||
public private(set) var viewModel = ViewModel()
|
||||
|
||||
lazy var stackView: UIStackView = {
|
||||
let view = UIStackView()
|
||||
view.axis = .horizontal
|
||||
view.distribution = .fillEqually
|
||||
view.spacing = SettingsAppearanceTableViewCell.spacing
|
||||
return view
|
||||
}()
|
||||
|
||||
let systemAppearanceView = AppearanceView(
|
||||
image: Asset.Settings.automatic.image,
|
||||
title: L10n.Scene.Settings.Section.Appearance.automatic
|
||||
)
|
||||
let darkAppearanceView = AppearanceView(
|
||||
image: Asset.Settings.dark.image,
|
||||
title: L10n.Scene.Settings.Section.Appearance.dark
|
||||
)
|
||||
let lightAppearanceView = AppearanceView(
|
||||
image: Asset.Settings.light.image,
|
||||
title: L10n.Scene.Settings.Section.Appearance.light
|
||||
)
|
||||
|
||||
var appearanceViews: [AppearanceView] {
|
||||
return [
|
||||
systemAppearanceView,
|
||||
darkAppearanceView,
|
||||
lightAppearanceView,
|
||||
]
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
|
||||
disposeBag.removeAll()
|
||||
observations.removeAll()
|
||||
viewModel.prepareForReuse()
|
||||
}
|
||||
|
||||
// MARK: - Methods
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
setupUI()
|
||||
viewModel.bind(cell: self)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
// remove separator line in section of group tableview
|
||||
for subview in self.subviews {
|
||||
if subview != self.contentView && subview.frame.width == self.frame.width {
|
||||
subview.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
// remove grouped style table corner radius
|
||||
layer.cornerRadius = 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension SettingsAppearanceTableViewCell {
|
||||
|
||||
// MARK: Private methods
|
||||
private func setupUI() {
|
||||
backgroundColor = .clear
|
||||
selectionStyle = .none
|
||||
|
||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
contentView.addSubview(stackView)
|
||||
stackView.pinToParent()
|
||||
|
||||
stackView.addArrangedSubview(systemAppearanceView)
|
||||
stackView.addArrangedSubview(darkAppearanceView)
|
||||
stackView.addArrangedSubview(lightAppearanceView)
|
||||
|
||||
appearanceViews.forEach { view in
|
||||
let tapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer
|
||||
view.addGestureRecognizer(tapGestureRecognizer)
|
||||
tapGestureRecognizer.addTarget(self, action: #selector(SettingsAppearanceTableViewCell.appearanceViewDidPressed(_:)))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
extension SettingsAppearanceTableViewCell {
|
||||
@objc func appearanceViewDidPressed(_ sender: UITapGestureRecognizer) {
|
||||
let mode: SettingsItem.AppearanceMode
|
||||
|
||||
switch sender.view {
|
||||
case systemAppearanceView:
|
||||
mode = .system
|
||||
case darkAppearanceView:
|
||||
mode = .dark
|
||||
case lightAppearanceView:
|
||||
mode = .light
|
||||
default:
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
delegate?.settingsAppearanceTableViewCell(self, didSelectAppearanceMode: mode)
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
//
|
||||
// SettingsLinkTableViewCell.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by ihugo on 2021/4/8.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class SettingsLinkTableViewCell: UITableViewCell {
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
|
||||
selectionStyle = .none
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func setHighlighted(_ highlighted: Bool, animated: Bool) {
|
||||
super.setHighlighted(highlighted, animated: animated)
|
||||
textLabel?.alpha = highlighted ? 0.6 : 1.0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Methods
|
||||
extension SettingsLinkTableViewCell {
|
||||
func update(with link: SettingsItem.Link) {
|
||||
textLabel?.text = link.title
|
||||
textLabel?.textColor = link.textColor
|
||||
}
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
//
|
||||
// SettingsToggleTableViewCell.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by ihugo on 2021/4/8.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Combine
|
||||
import MastodonAsset
|
||||
import MastodonLocalization
|
||||
|
||||
protocol SettingsToggleCellDelegate: AnyObject {
|
||||
func settingsToggleCell(_ cell: SettingsToggleTableViewCell, switchValueDidChange switch: UISwitch)
|
||||
}
|
||||
|
||||
class SettingsToggleTableViewCell: UITableViewCell {
|
||||
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
|
||||
private(set) lazy var switchButton: UISwitch = {
|
||||
let view = UISwitch(frame:.zero)
|
||||
view.onTintColor = Asset.Colors.Brand.blurple.color
|
||||
return view
|
||||
}()
|
||||
|
||||
weak var delegate: SettingsToggleCellDelegate?
|
||||
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
|
||||
disposeBag.removeAll()
|
||||
}
|
||||
|
||||
// MARK: - Methods
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: .default, reuseIdentifier: reuseIdentifier)
|
||||
setupUI()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
setupUI()
|
||||
}
|
||||
|
||||
// MARK: Private methods
|
||||
private func setupUI() {
|
||||
selectionStyle = .none
|
||||
accessoryView = switchButton
|
||||
textLabel?.numberOfLines = 0
|
||||
|
||||
switchButton.addTarget(self, action: #selector(switchValueDidChange(sender:)), for: .valueChanged)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
extension SettingsToggleTableViewCell {
|
||||
|
||||
@objc private func switchValueDidChange(sender: UISwitch) {
|
||||
guard let delegate = delegate else { return }
|
||||
delegate.settingsToggleCell(self, switchValueDidChange: sender)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension SettingsToggleTableViewCell {
|
||||
|
||||
func update(enabled: Bool?) {
|
||||
switchButton.isEnabled = enabled != nil
|
||||
textLabel?.textColor = enabled != nil ? Asset.Colors.Label.primary.color : Asset.Colors.Label.secondary.color
|
||||
switchButton.isOn = enabled ?? false
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import MastodonAsset
|
||||
|
||||
class GeneralSettingSelectionCell: UITableViewCell {
|
||||
static let reuseIdentifier = "GeneralSettingSelectionCell"
|
||||
|
||||
func configure(with setting: GeneralSetting, viewModel: GeneralSettingsViewModel) {
|
||||
switch setting {
|
||||
case .appearance(let appearanceSetting):
|
||||
configureAppearanceSetting(appearanceSetting: appearanceSetting, viewModel: viewModel)
|
||||
case .design(_):
|
||||
// only for appearance and open links
|
||||
assertionFailure("Wrong Setting!")
|
||||
case .openLinksIn(let openLinkSetting):
|
||||
configureOpenLinkSetting(openLinkSetting: openLinkSetting, viewModel: viewModel)
|
||||
}
|
||||
}
|
||||
|
||||
private func configureAppearanceSetting(appearanceSetting: GeneralSetting.Appearance, viewModel: GeneralSettingsViewModel) {
|
||||
var content = defaultContentConfiguration()
|
||||
content.text = appearanceSetting.title
|
||||
tintColor = Asset.Colors.Brand.blurple.color
|
||||
|
||||
if viewModel.selectedAppearence == appearanceSetting {
|
||||
accessoryType = .checkmark
|
||||
} else {
|
||||
accessoryType = .none
|
||||
}
|
||||
|
||||
contentConfiguration = content
|
||||
}
|
||||
|
||||
private func configureOpenLinkSetting(openLinkSetting: GeneralSetting.OpenLinksIn, viewModel: GeneralSettingsViewModel) {
|
||||
var content = defaultContentConfiguration()
|
||||
content.text = openLinkSetting.title
|
||||
tintColor = Asset.Colors.Brand.blurple.color
|
||||
|
||||
if viewModel.selectedOpenLinks == openLinkSetting {
|
||||
accessoryType = .checkmark
|
||||
} else {
|
||||
accessoryType = .none
|
||||
}
|
||||
|
||||
contentConfiguration = content
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import MastodonAsset
|
||||
|
||||
protocol GeneralSettingToggleTableViewCellDelegate: AnyObject {
|
||||
func toggle(_ cell: GeneralSettingToggleTableViewCell, setting: GeneralSetting, isOn: Bool)
|
||||
}
|
||||
|
||||
class GeneralSettingToggleTableViewCell: ToggleTableViewCell {
|
||||
override class var reuseIdentifier: String {
|
||||
return "GeneralSettingToggleCell"
|
||||
}
|
||||
|
||||
weak var delegate: GeneralSettingToggleTableViewCellDelegate?
|
||||
var setting: GeneralSetting?
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
toggle.addTarget(self, action: #selector(GeneralSettingToggleTableViewCell.toggleValueChanged(_:)), for: .valueChanged)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||
|
||||
func configure(with setting: GeneralSetting, viewModel: GeneralSettingsViewModel) {
|
||||
|
||||
self.setting = setting
|
||||
|
||||
switch setting {
|
||||
case .appearance(_), .openLinksIn(_):
|
||||
assertionFailure("Only for Design")
|
||||
case .design(let designSetting):
|
||||
label.text = designSetting.title
|
||||
|
||||
switch designSetting {
|
||||
case .showAnimations:
|
||||
toggle.isOn = viewModel.playAnimations
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
func toggleValueChanged(_ sender: UISwitch) {
|
||||
guard let setting else { return }
|
||||
|
||||
delegate?.toggle(self, setting: setting, isOn: sender.isOn)
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import MastodonLocalization
|
||||
|
||||
struct GeneralSettingsSection: Hashable {
|
||||
let type: GeneralSettingsSectionType
|
||||
let entries: [GeneralSetting]
|
||||
}
|
||||
|
||||
enum GeneralSettingsSectionType: Hashable {
|
||||
case appearance
|
||||
case design
|
||||
case links
|
||||
|
||||
var sectionTitle: String {
|
||||
switch self {
|
||||
case .appearance:
|
||||
return L10n.Scene.Settings.General.Appearance.sectionTitle
|
||||
case .design:
|
||||
return L10n.Scene.Settings.General.Design.sectionTitle
|
||||
case .links:
|
||||
return L10n.Scene.Settings.General.Links.sectionTitle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum GeneralSetting: Hashable {
|
||||
|
||||
case appearance(Appearance)
|
||||
case design(Design)
|
||||
case openLinksIn(OpenLinksIn)
|
||||
|
||||
enum Appearance: Int, CaseIterable {
|
||||
case light = 1
|
||||
case dark = 2
|
||||
case system = 0
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
case .light:
|
||||
return L10n.Scene.Settings.General.Appearance.light
|
||||
case .dark:
|
||||
return L10n.Scene.Settings.General.Appearance.dark
|
||||
case .system:
|
||||
return L10n.Scene.Settings.General.Appearance.system
|
||||
}
|
||||
}
|
||||
|
||||
var interfaceStyle: UIUserInterfaceStyle {
|
||||
.init(rawValue: rawValue) ?? .unspecified
|
||||
}
|
||||
}
|
||||
|
||||
enum Design: Hashable {
|
||||
case showAnimations
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
case .showAnimations:
|
||||
return L10n.Scene.Settings.General.Design.showAnimations
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum OpenLinksIn: Hashable, CaseIterable {
|
||||
case mastodon
|
||||
case browser
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
case .mastodon:
|
||||
return L10n.Scene.Settings.General.Links.openInMastodon
|
||||
case .browser:
|
||||
return L10n.Scene.Settings.General.Links.openInBrowser
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
class GeneralSettingsDiffableTableViewDataSource: UITableViewDiffableDataSource<GeneralSettingsSection, GeneralSetting> {
|
||||
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
guard let settingsSection = sectionIdentifier(for: section) else { return nil }
|
||||
|
||||
return settingsSection.type.sectionTitle.uppercased()
|
||||
}
|
||||
}
|
@ -0,0 +1,169 @@
|
||||
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import MastodonSDK
|
||||
import CoreDataStack
|
||||
import MastodonLocalization
|
||||
|
||||
struct GeneralSettingsViewModel {
|
||||
var selectedAppearence: GeneralSetting.Appearance
|
||||
var playAnimations: Bool
|
||||
var selectedOpenLinks: GeneralSetting.OpenLinksIn
|
||||
}
|
||||
|
||||
protocol GeneralSettingsViewControllerDelegate: AnyObject {
|
||||
func save(_ viewController: UIViewController, setting: Setting, viewModel: GeneralSettingsViewModel)
|
||||
}
|
||||
|
||||
class GeneralSettingsViewController: UIViewController {
|
||||
|
||||
weak var delegate: GeneralSettingsViewControllerDelegate?
|
||||
let tableView: UITableView
|
||||
|
||||
var tableViewDataSource: GeneralSettingsDiffableTableViewDataSource?
|
||||
|
||||
private(set) var viewModel: GeneralSettingsViewModel
|
||||
let setting: Setting
|
||||
|
||||
let sections: [GeneralSettingsSection]
|
||||
|
||||
init(setting: Setting) {
|
||||
tableView = UITableView(frame: .zero, style: .insetGrouped)
|
||||
tableView.translatesAutoresizingMaskIntoConstraints = false
|
||||
tableView.register(GeneralSettingSelectionCell.self, forCellReuseIdentifier: GeneralSettingSelectionCell.reuseIdentifier)
|
||||
tableView.register(GeneralSettingToggleTableViewCell.self, forCellReuseIdentifier: GeneralSettingToggleTableViewCell.reuseIdentifier)
|
||||
|
||||
sections = [
|
||||
GeneralSettingsSection(type: .appearance, entries: [
|
||||
.appearance(.light),
|
||||
.appearance(.dark),
|
||||
.appearance(.system)
|
||||
]),
|
||||
GeneralSettingsSection(type: .design, entries: [
|
||||
.design(.showAnimations)
|
||||
]),
|
||||
GeneralSettingsSection(type: .links, entries: [
|
||||
.openLinksIn(.mastodon),
|
||||
.openLinksIn(.browser),
|
||||
])
|
||||
]
|
||||
|
||||
let openLinksIn: GeneralSetting.OpenLinksIn
|
||||
if UserDefaults.shared.preferredUsingDefaultBrowser {
|
||||
openLinksIn = .browser
|
||||
} else {
|
||||
openLinksIn = .mastodon
|
||||
}
|
||||
let playAnimations = (UserDefaults.shared.preferredStaticAvatar == false && UserDefaults.shared.preferredStaticEmoji == false)
|
||||
viewModel = GeneralSettingsViewModel(
|
||||
selectedAppearence: GeneralSetting.Appearance(rawValue: UserDefaults.shared.customUserInterfaceStyle.rawValue) ?? .system,
|
||||
playAnimations: playAnimations,
|
||||
selectedOpenLinks: openLinksIn
|
||||
)
|
||||
|
||||
self.setting = setting
|
||||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
||||
tableView.delegate = self
|
||||
|
||||
let tableViewDataSource = GeneralSettingsDiffableTableViewDataSource(tableView: tableView, cellProvider: { tableView, indexPath, itemIdentifier in
|
||||
let cell: UITableViewCell
|
||||
switch itemIdentifier {
|
||||
case .appearance(let setting):
|
||||
guard let selectionCell = tableView.dequeueReusableCell(withIdentifier: GeneralSettingSelectionCell.reuseIdentifier, for: indexPath) as? GeneralSettingSelectionCell else { fatalError("WTF? Wrong Cell!") }
|
||||
|
||||
selectionCell.configure(with: .appearance(setting), viewModel: self.viewModel)
|
||||
cell = selectionCell
|
||||
case .design(let setting):
|
||||
guard let toggleCell = tableView.dequeueReusableCell(withIdentifier: GeneralSettingToggleTableViewCell.reuseIdentifier, for: indexPath) as? GeneralSettingToggleTableViewCell else { fatalError("WTF? Wrong Cell!") }
|
||||
|
||||
toggleCell.configure(with: .design(setting), viewModel: self.viewModel)
|
||||
toggleCell.delegate = self
|
||||
|
||||
cell = toggleCell
|
||||
case .openLinksIn(let setting):
|
||||
guard let selectionCell = tableView.dequeueReusableCell(withIdentifier: GeneralSettingSelectionCell.reuseIdentifier, for: indexPath) as? GeneralSettingSelectionCell else { fatalError("WTF? Wrong Cell!") }
|
||||
|
||||
selectionCell.configure(with: .openLinksIn(setting), viewModel: self.viewModel)
|
||||
|
||||
cell = selectionCell
|
||||
}
|
||||
|
||||
return cell
|
||||
})
|
||||
|
||||
self.tableViewDataSource = tableViewDataSource
|
||||
|
||||
view.backgroundColor = .systemGroupedBackground
|
||||
view.addSubview(tableView)
|
||||
tableView.pinTo(to: view)
|
||||
|
||||
title = L10n.Scene.Settings.General.title
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
var snapshot = NSDiffableDataSourceSnapshot<GeneralSettingsSection, GeneralSetting>()
|
||||
|
||||
for section in sections {
|
||||
snapshot.appendSections([section])
|
||||
snapshot.appendItems(section.entries)
|
||||
}
|
||||
|
||||
tableViewDataSource?.apply(snapshot, animatingDifferences: false)
|
||||
}
|
||||
}
|
||||
|
||||
extension GeneralSettingsViewController: UITableViewDelegate {
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
|
||||
// switch section
|
||||
|
||||
let section = sections[indexPath.section].entries[indexPath.row]
|
||||
|
||||
switch section {
|
||||
case .appearance(let appearanceOption):
|
||||
viewModel.selectedAppearence = appearanceOption
|
||||
|
||||
if let snapshot = tableViewDataSource?.snapshot() {
|
||||
tableViewDataSource?.applySnapshotUsingReloadData(snapshot)
|
||||
}
|
||||
case .design(let design):
|
||||
guard let cell = tableView.cellForRow(at: indexPath) as? GeneralSettingToggleTableViewCell else { return}
|
||||
|
||||
let newValue = (cell.toggle.isOn == false)
|
||||
cell.toggle.setOn(newValue, animated: true)
|
||||
|
||||
toggle(cell, setting: .design(design), isOn: newValue)
|
||||
case .openLinksIn(let openLinksInOption):
|
||||
viewModel.selectedOpenLinks = openLinksInOption
|
||||
|
||||
if let snapshot = tableViewDataSource?.snapshot() {
|
||||
tableViewDataSource?.applySnapshotUsingReloadData(snapshot)
|
||||
}
|
||||
}
|
||||
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
delegate?.save(self, setting: setting, viewModel: viewModel)
|
||||
}
|
||||
}
|
||||
|
||||
extension GeneralSettingsViewController: GeneralSettingToggleTableViewCellDelegate {
|
||||
func toggle(_ cell: GeneralSettingToggleTableViewCell, setting: GeneralSetting, isOn: Bool) {
|
||||
switch setting {
|
||||
case .appearance(_), .openLinksIn(_):
|
||||
assertionFailure("No toggle")
|
||||
case .design(let designSetting):
|
||||
switch designSetting {
|
||||
case .showAnimations:
|
||||
viewModel.playAnimations = isOn
|
||||
}
|
||||
}
|
||||
|
||||
delegate?.save(self, setting: self.setting, viewModel: viewModel)
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import MastodonLocalization
|
||||
|
||||
class NotificationSettingTableViewCell: UITableViewCell {
|
||||
static let reuseIdentifier = "NotificationSettingTableViewCell"
|
||||
|
||||
func configure(with entry: NotificationSettingEntry, viewModel: NotificationSettingsViewModel, notificationsEnabled: Bool) {
|
||||
|
||||
isUserInteractionEnabled = notificationsEnabled
|
||||
|
||||
guard case .policy = entry else { return }
|
||||
|
||||
var content = UIListContentConfiguration.valueCell()
|
||||
content.text = L10n.Scene.Settings.Notifications.Policy.title
|
||||
content.secondaryText = viewModel.selectedPolicy.title
|
||||
if notificationsEnabled {
|
||||
content.textProperties.color = .label
|
||||
content.secondaryTextProperties.color = .secondaryLabel
|
||||
} else {
|
||||
content.textProperties.color = .secondaryLabel
|
||||
content.secondaryTextProperties.color = .tertiaryLabel
|
||||
}
|
||||
|
||||
contentConfiguration = content
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
protocol NotificationSettingToggleCellDelegate: AnyObject {
|
||||
func toggleValueChanged(_ tableViewCell: NotificationSettingTableViewToggleCell, alert: NotificationAlert, newValue: Bool)
|
||||
}
|
||||
|
||||
class NotificationSettingTableViewToggleCell: ToggleTableViewCell {
|
||||
|
||||
override class var reuseIdentifier: String {
|
||||
return "NotificationSettingToggleCell"
|
||||
}
|
||||
|
||||
var alert: NotificationAlert?
|
||||
|
||||
weak var delegate: NotificationSettingToggleCellDelegate?
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
|
||||
toggle.addTarget(self, action: #selector(NotificationSettingTableViewToggleCell.toggleValueChanged(_:)), for: .valueChanged)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||
|
||||
func configure(with alert: NotificationAlert, viewModel: NotificationSettingsViewModel, notificationsEnabled: Bool) {
|
||||
|
||||
isUserInteractionEnabled = notificationsEnabled
|
||||
self.alert = alert
|
||||
|
||||
let toggleIsOn: Bool
|
||||
switch alert {
|
||||
case .mentionsAndReplies:
|
||||
toggleIsOn = viewModel.notifyMentions
|
||||
case .boosts:
|
||||
toggleIsOn = viewModel.notifyBoosts
|
||||
case .favorites:
|
||||
toggleIsOn = viewModel.notifyFavorites
|
||||
case .newFollowers:
|
||||
toggleIsOn = viewModel.notifyNewFollowers
|
||||
}
|
||||
|
||||
label.text = alert.title
|
||||
if notificationsEnabled {
|
||||
label.textColor = .label
|
||||
} else {
|
||||
label.textColor = .secondaryLabel
|
||||
}
|
||||
toggle.isOn = toggleIsOn && notificationsEnabled
|
||||
toggle.isEnabled = notificationsEnabled
|
||||
}
|
||||
|
||||
@objc
|
||||
func toggleValueChanged(_ sender: UISwitch) {
|
||||
guard let alert else { return }
|
||||
|
||||
delegate?.toggleValueChanged(self, alert: alert, newValue: sender.isOn)
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import MastodonAsset
|
||||
import MastodonLocalization
|
||||
|
||||
|
||||
fileprivate extension CGFloat {
|
||||
static let padding: Self = 16
|
||||
static let appBadgeHeight: Self = 34
|
||||
}
|
||||
|
||||
class NotificationSettingsDisabledTableViewCell: UITableViewCell {
|
||||
|
||||
static let reuseIdentifier = "NotificationSettingsDisabledTableViewCell"
|
||||
|
||||
let appBadgeImageView: UIImageView
|
||||
let notificationHintLabel: UILabel
|
||||
let goToSettingsLabel: UILabel
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
|
||||
appBadgeImageView = UIImageView(image: UIImage(systemName: "app.badge.fill"))
|
||||
appBadgeImageView.tintColor = Asset.Colors.Brand.blurple.color
|
||||
appBadgeImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
notificationHintLabel = UILabel()
|
||||
notificationHintLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
notificationHintLabel.numberOfLines = 0
|
||||
notificationHintLabel.textColor = .label
|
||||
notificationHintLabel.text = L10n.Scene.Settings.Notifications.Disabled.notificationHint
|
||||
notificationHintLabel.font = UIFontMetrics(forTextStyle: .callout).scaledFont(for: .systemFont(ofSize: 16, weight: .regular))
|
||||
|
||||
goToSettingsLabel = UILabel()
|
||||
goToSettingsLabel.textColor = Asset.Colors.Brand.blurple.color
|
||||
goToSettingsLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
goToSettingsLabel.text = L10n.Scene.Settings.Notifications.Disabled.goToSettings
|
||||
goToSettingsLabel.font = UIFontMetrics(forTextStyle: .callout).scaledFont(for: .systemFont(ofSize: 16, weight: .bold))
|
||||
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
|
||||
contentView.addSubview(appBadgeImageView)
|
||||
contentView.addSubview(notificationHintLabel)
|
||||
contentView.addSubview(goToSettingsLabel)
|
||||
|
||||
backgroundColor = Asset.Colors.Brand.blurple.color.withAlphaComponent(0.15)
|
||||
|
||||
setupConstraints()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||
|
||||
private func setupConstraints() {
|
||||
let constraints: [NSLayoutConstraint] = [
|
||||
appBadgeImageView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: .padding),
|
||||
appBadgeImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: .padding),
|
||||
appBadgeImageView.heightAnchor.constraint(equalToConstant: .appBadgeHeight),
|
||||
appBadgeImageView.widthAnchor.constraint(equalTo: appBadgeImageView.heightAnchor),
|
||||
|
||||
notificationHintLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: .padding),
|
||||
notificationHintLabel.leadingAnchor.constraint(equalTo: appBadgeImageView.trailingAnchor, constant: .padding),
|
||||
contentView.trailingAnchor.constraint(equalTo: notificationHintLabel.trailingAnchor, constant: .padding),
|
||||
|
||||
goToSettingsLabel.topAnchor.constraint(equalTo: notificationHintLabel.bottomAnchor, constant: .padding/2),
|
||||
goToSettingsLabel.leadingAnchor.constraint(equalTo: notificationHintLabel.leadingAnchor),
|
||||
contentView.trailingAnchor.constraint(equalTo: goToSettingsLabel.trailingAnchor, constant: .padding),
|
||||
contentView.bottomAnchor.constraint(equalTo: goToSettingsLabel.bottomAnchor, constant: .padding),
|
||||
]
|
||||
|
||||
NSLayoutConstraint.activate(constraints)
|
||||
}
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import MastodonLocalization
|
||||
import MastodonSDK
|
||||
import CoreDataStack
|
||||
|
||||
struct NotificationSettingsSection: Hashable {
|
||||
let entries: [NotificationSettingEntry]
|
||||
}
|
||||
|
||||
enum NotificationSettingEntry: Hashable {
|
||||
case notificationDisabled
|
||||
case policy
|
||||
case alert(NotificationAlert)
|
||||
}
|
||||
|
||||
struct NotificationPolicySection: Hashable {
|
||||
let entries: [NotificationPolicy]
|
||||
}
|
||||
|
||||
enum NotificationPolicy: Hashable, CaseIterable {
|
||||
case anyone
|
||||
case followers
|
||||
case follow
|
||||
case noone
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
case .anyone:
|
||||
return L10n.Scene.Settings.Notifications.Policy.anyone
|
||||
case .followers:
|
||||
return L10n.Scene.Settings.Notifications.Policy.followers
|
||||
case .follow:
|
||||
return L10n.Scene.Settings.Notifications.Policy.follow
|
||||
case .noone:
|
||||
return L10n.Scene.Settings.Notifications.Policy.noone
|
||||
}
|
||||
}
|
||||
|
||||
var subscriptionPolicy: Mastodon.API.Subscriptions.Policy {
|
||||
switch self {
|
||||
case .anyone:
|
||||
return .all
|
||||
case .followers:
|
||||
return .follower
|
||||
case .follow:
|
||||
return .followed
|
||||
case .noone:
|
||||
return .none
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum NotificationAlert: Hashable, CaseIterable {
|
||||
case mentionsAndReplies
|
||||
case boosts
|
||||
case favorites
|
||||
case newFollowers
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
|
||||
case .mentionsAndReplies:
|
||||
return L10n.Scene.Settings.Notifications.Alert.mentionsAndReplies
|
||||
case .boosts:
|
||||
return L10n.Scene.Settings.Notifications.Alert.boosts
|
||||
case .favorites:
|
||||
return L10n.Scene.Settings.Notifications.Alert.favorites
|
||||
case .newFollowers:
|
||||
return L10n.Scene.Settings.Notifications.Alert.newFollowers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Subscription {
|
||||
var notificationPolicy: NotificationPolicy? {
|
||||
guard let policy else { return nil }
|
||||
|
||||
switch policy {
|
||||
case .all:
|
||||
return .anyone
|
||||
case .followed:
|
||||
return .follow
|
||||
case .follower:
|
||||
return .followers
|
||||
case .none:
|
||||
return .noone
|
||||
case ._other(_):
|
||||
return .noone
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,162 @@
|
||||
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import CoreDataStack
|
||||
import MastodonLocalization
|
||||
|
||||
protocol NotificationSettingsViewControllerDelegate: AnyObject {
|
||||
func viewWillDisappear(_ viewController: UIViewController, viewModel: NotificationSettingsViewModel)
|
||||
func showPolicyList(_ viewController: UIViewController, viewModel: NotificationSettingsViewModel)
|
||||
func showNotificationSettings(_ viewController: UIViewController)
|
||||
}
|
||||
|
||||
class NotificationSettingsViewController: UIViewController {
|
||||
|
||||
weak var delegate: NotificationSettingsViewControllerDelegate?
|
||||
|
||||
let tableView: UITableView
|
||||
var tableViewDataSource: UITableViewDiffableDataSource<NotificationSettingsSection, NotificationSettingEntry>?
|
||||
|
||||
let sections: [NotificationSettingsSection]
|
||||
var viewModel: NotificationSettingsViewModel
|
||||
|
||||
init(currentSetting: Setting?, notificationsEnabled: Bool) {
|
||||
let activeSubscription = currentSetting?.activeSubscription
|
||||
let alert = activeSubscription?.alert
|
||||
viewModel = NotificationSettingsViewModel(selectedPolicy: activeSubscription?.notificationPolicy ?? .noone,
|
||||
notifyMentions: alert?.mention ?? false,
|
||||
notifyBoosts: alert?.reblog ?? false,
|
||||
notifyFavorites: alert?.favourite ?? false,
|
||||
notifyNewFollowers: alert?.follow ?? false)
|
||||
|
||||
if notificationsEnabled {
|
||||
sections = [
|
||||
NotificationSettingsSection(entries: [.policy]),
|
||||
NotificationSettingsSection(entries: NotificationAlert.allCases.map { NotificationSettingEntry.alert($0) } )
|
||||
]
|
||||
} else {
|
||||
sections = [
|
||||
NotificationSettingsSection(entries: [.notificationDisabled]),
|
||||
NotificationSettingsSection(entries: [.policy]),
|
||||
NotificationSettingsSection(entries: NotificationAlert.allCases.map { NotificationSettingEntry.alert($0) } )
|
||||
]
|
||||
}
|
||||
|
||||
tableView = UITableView(frame: .zero, style: .insetGrouped)
|
||||
tableView.translatesAutoresizingMaskIntoConstraints = false
|
||||
tableView.register(NotificationSettingTableViewCell.self, forCellReuseIdentifier: NotificationSettingTableViewCell.reuseIdentifier)
|
||||
tableView.register(NotificationSettingTableViewToggleCell.self, forCellReuseIdentifier: NotificationSettingTableViewToggleCell.reuseIdentifier)
|
||||
tableView.register(NotificationSettingsDisabledTableViewCell.self, forCellReuseIdentifier: NotificationSettingsDisabledTableViewCell.reuseIdentifier)
|
||||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
||||
let tableViewDataSource = UITableViewDiffableDataSource<NotificationSettingsSection, NotificationSettingEntry>(tableView: tableView) { [ weak self] tableView, indexPath, itemIdentifier in
|
||||
|
||||
let cell: UITableViewCell
|
||||
|
||||
switch itemIdentifier {
|
||||
case .notificationDisabled:
|
||||
guard let notificationsDisabledCell = tableView.dequeueReusableCell(withIdentifier: NotificationSettingsDisabledTableViewCell.reuseIdentifier, for: indexPath) as? NotificationSettingsDisabledTableViewCell else { fatalError("WTF Wrong cell!?") }
|
||||
|
||||
cell = notificationsDisabledCell
|
||||
|
||||
case .policy:
|
||||
guard let self,
|
||||
let notificationCell = tableView.dequeueReusableCell(withIdentifier: NotificationSettingTableViewCell.reuseIdentifier, for: indexPath) as? NotificationSettingTableViewCell else { fatalError("WTF Wrong cell!?") }
|
||||
|
||||
notificationCell.configure(with: .policy, viewModel: self.viewModel, notificationsEnabled: notificationsEnabled)
|
||||
cell = notificationCell
|
||||
|
||||
case .alert(let alert):
|
||||
guard let self,
|
||||
let toggleCell = tableView.dequeueReusableCell(withIdentifier: NotificationSettingTableViewToggleCell.reuseIdentifier, for: indexPath) as? NotificationSettingTableViewToggleCell else { fatalError("WTF Wrong cell!?") }
|
||||
|
||||
toggleCell.configure(with: alert, viewModel: self.viewModel, notificationsEnabled: notificationsEnabled)
|
||||
toggleCell.delegate = self
|
||||
cell = toggleCell
|
||||
}
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
tableView.dataSource = tableViewDataSource
|
||||
tableView.delegate = self
|
||||
self.tableViewDataSource = tableViewDataSource
|
||||
|
||||
view.backgroundColor = .systemGroupedBackground
|
||||
view.addSubview(tableView)
|
||||
tableView.pinToParent()
|
||||
|
||||
title = L10n.Scene.Settings.Notifications.title
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
var snapshot = NSDiffableDataSourceSnapshot<NotificationSettingsSection, NotificationSettingEntry>()
|
||||
|
||||
for section in sections {
|
||||
snapshot.appendSections([section])
|
||||
snapshot.appendItems(section.entries)
|
||||
}
|
||||
|
||||
tableViewDataSource?.apply(snapshot, animatingDifferences: false)
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
if let snapshot = tableViewDataSource?.snapshot() {
|
||||
tableViewDataSource?.applySnapshotUsingReloadData(snapshot)
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
|
||||
delegate?.viewWillDisappear(self, viewModel: viewModel)
|
||||
}
|
||||
}
|
||||
|
||||
extension NotificationSettingsViewController: UITableViewDelegate {
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
|
||||
let entry = sections[indexPath.section].entries[indexPath.row]
|
||||
switch entry {
|
||||
case .alert(let alert):
|
||||
|
||||
guard let cell = tableView.cellForRow(at: indexPath) as? NotificationSettingTableViewToggleCell else { return }
|
||||
|
||||
let newValue = (cell.toggle.isOn == false)
|
||||
cell.toggle.setOn(newValue, animated: true)
|
||||
|
||||
toggleValueChanged(cell, alert: alert, newValue: newValue)
|
||||
|
||||
case .policy:
|
||||
delegate?.showPolicyList(self, viewModel: viewModel)
|
||||
case .notificationDisabled:
|
||||
delegate?.showNotificationSettings(self)
|
||||
}
|
||||
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
extension NotificationSettingsViewController: NotificationSettingToggleCellDelegate {
|
||||
func toggleValueChanged(_ tableViewCell: NotificationSettingTableViewToggleCell, alert: NotificationAlert, newValue: Bool) {
|
||||
switch alert {
|
||||
case .mentionsAndReplies:
|
||||
viewModel.notifyMentions = newValue
|
||||
case .boosts:
|
||||
viewModel.notifyBoosts = newValue
|
||||
case .favorites:
|
||||
viewModel.notifyFavorites = newValue
|
||||
case .newFollowers:
|
||||
viewModel.notifyNewFollowers = newValue
|
||||
}
|
||||
|
||||
viewModel.updated = true
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
class NotificationSettingsViewModel {
|
||||
|
||||
var selectedPolicy: NotificationPolicy
|
||||
var notifyMentions: Bool
|
||||
var notifyBoosts: Bool
|
||||
var notifyFavorites: Bool
|
||||
var notifyNewFollowers: Bool
|
||||
|
||||
var updated: Bool
|
||||
|
||||
init(selectedPolicy: NotificationPolicy, notifyMentions: Bool, notifyBoosts: Bool, notifyFavorites: Bool, notifyNewFollowers: Bool) {
|
||||
self.selectedPolicy = selectedPolicy
|
||||
self.notifyMentions = notifyMentions
|
||||
self.notifyBoosts = notifyBoosts
|
||||
self.notifyFavorites = notifyFavorites
|
||||
self.notifyNewFollowers = notifyNewFollowers
|
||||
|
||||
self.updated = false
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import MastodonAsset
|
||||
|
||||
class NotificationPolicyTableViewCell: UITableViewCell {
|
||||
static let reuseIdentifier = "NotificationPolicyTableViewCell"
|
||||
|
||||
func configure(with policy: NotificationPolicy, selectedPolicy: NotificationPolicy) {
|
||||
var content = UIListContentConfiguration.cell()
|
||||
content.text = policy.title
|
||||
tintColor = Asset.Colors.Brand.blurple.color
|
||||
|
||||
if policy == selectedPolicy {
|
||||
accessoryType = .checkmark
|
||||
} else {
|
||||
accessoryType = .none
|
||||
}
|
||||
|
||||
contentConfiguration = content
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import MastodonLocalization
|
||||
|
||||
protocol PolicySelectionViewControllerDelegate: AnyObject {
|
||||
func newPolicySelected(_ viewController: PolicySelectionViewController, newPolicy: NotificationPolicy)
|
||||
}
|
||||
|
||||
class PolicySelectionViewController: UIViewController {
|
||||
|
||||
weak var delegate: PolicySelectionViewControllerDelegate?
|
||||
|
||||
let tableView: UITableView
|
||||
var dataSource: UITableViewDiffableDataSource<NotificationPolicySection, NotificationPolicy>?
|
||||
|
||||
var viewModel: NotificationSettingsViewModel
|
||||
let sections = [NotificationPolicySection(entries: NotificationPolicy.allCases)]
|
||||
|
||||
init(viewModel: NotificationSettingsViewModel) {
|
||||
|
||||
self.viewModel = viewModel
|
||||
tableView = UITableView(frame: .zero, style: .insetGrouped)
|
||||
tableView.translatesAutoresizingMaskIntoConstraints = false
|
||||
tableView.register(NotificationPolicyTableViewCell.self, forCellReuseIdentifier: NotificationPolicyTableViewCell.reuseIdentifier)
|
||||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
||||
let dataSource = UITableViewDiffableDataSource<NotificationPolicySection, NotificationPolicy>(tableView: tableView) { [weak self] tableView, indexPath, itemIdentifier in
|
||||
|
||||
guard let self, let cell = tableView.dequeueReusableCell(withIdentifier: NotificationPolicyTableViewCell.reuseIdentifier, for: indexPath) as? NotificationPolicyTableViewCell else {
|
||||
fatalError("WTF Wrong cell?!")
|
||||
}
|
||||
|
||||
let policy = self.sections[indexPath.section].entries[indexPath.row]
|
||||
cell.configure(with: policy, selectedPolicy: self.viewModel.selectedPolicy)
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
view.addSubview(tableView)
|
||||
view.backgroundColor = .systemGroupedBackground
|
||||
|
||||
tableView.pinToParent()
|
||||
tableView.delegate = self
|
||||
tableView.dataSource = dataSource
|
||||
|
||||
self.dataSource = dataSource
|
||||
title = L10n.Scene.Settings.Notifications.Policy.title
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
var snapshot = NSDiffableDataSourceSnapshot<NotificationPolicySection, NotificationPolicy>()
|
||||
snapshot.appendSections(sections)
|
||||
snapshot.appendItems(NotificationPolicy.allCases)
|
||||
|
||||
dataSource?.apply(snapshot)
|
||||
}
|
||||
}
|
||||
|
||||
extension PolicySelectionViewController: UITableViewDelegate {
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
|
||||
let newPolicy = sections[indexPath.section].entries[indexPath.row]
|
||||
viewModel.selectedPolicy = newPolicy
|
||||
viewModel.updated = true
|
||||
|
||||
if let dataSource {
|
||||
dataSource.applySnapshotUsingReloadData(dataSource.snapshot())
|
||||
}
|
||||
|
||||
delegate?.newPolicySelected(self, newPolicy: newPolicy)
|
||||
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
}
|
||||
}
|
72
Mastodon/Scene/Settings/Settings Overview/Settings.swift
Normal file
72
Mastodon/Scene/Settings/Settings Overview/Settings.swift
Normal file
@ -0,0 +1,72 @@
|
||||
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import MastodonLocalization
|
||||
|
||||
struct SettingsSection: Hashable {
|
||||
let entries: [SettingsEntry]
|
||||
}
|
||||
|
||||
enum SettingsEntry: Hashable {
|
||||
case general
|
||||
case notifications
|
||||
case aboutMastodon
|
||||
case logout(accountName: String)
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
case .general:
|
||||
return L10n.Scene.Settings.Overview.general
|
||||
case .notifications:
|
||||
return L10n.Scene.Settings.Overview.notifications
|
||||
case .aboutMastodon:
|
||||
return L10n.Scene.Settings.Overview.aboutMastodon
|
||||
case .logout(let accountName):
|
||||
return L10n.Scene.Settings.Overview.logout(accountName)
|
||||
}
|
||||
}
|
||||
|
||||
var accessoryType: UITableViewCell.AccessoryType {
|
||||
switch self {
|
||||
case .general, .notifications, .aboutMastodon, .logout(_):
|
||||
return .disclosureIndicator
|
||||
}
|
||||
}
|
||||
|
||||
var icon: UIImage? {
|
||||
switch self {
|
||||
case .general:
|
||||
return UIImage(systemName: "gear")
|
||||
case .notifications:
|
||||
return UIImage(systemName: "bell.badge")
|
||||
case .aboutMastodon:
|
||||
return UIImage(systemName: "info.circle.fill")
|
||||
case .logout(_):
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var iconBackgroundColor: UIColor? {
|
||||
switch self {
|
||||
case .general:
|
||||
return .systemGray
|
||||
case .notifications:
|
||||
return .systemRed
|
||||
case .aboutMastodon:
|
||||
return .systemPurple
|
||||
case .logout(_):
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var textColor: UIColor {
|
||||
switch self {
|
||||
case .general, .notifications, .aboutMastodon:
|
||||
return .label
|
||||
case .logout(_):
|
||||
return .red
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
class SettingsTableViewCell: UITableViewCell {
|
||||
|
||||
static let reuseIdentifier = "SettingsTableViewCell"
|
||||
|
||||
let iconImageView: UIImageView
|
||||
let iconImageBackgroundView: UIView
|
||||
let titleLabel: UILabel
|
||||
|
||||
private let contentStackView: UIStackView
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
|
||||
iconImageView = UIImageView()
|
||||
iconImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
iconImageBackgroundView = UIView()
|
||||
iconImageBackgroundView.addSubview(iconImageView)
|
||||
|
||||
titleLabel = UILabel()
|
||||
|
||||
contentStackView = UIStackView(arrangedSubviews: [iconImageBackgroundView, titleLabel])
|
||||
contentStackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
contentStackView.axis = .horizontal
|
||||
contentStackView.alignment = .center
|
||||
contentStackView.spacing = 16
|
||||
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
|
||||
contentView.addSubview(contentStackView)
|
||||
setupConstraints()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||
|
||||
private func setupConstraints() {
|
||||
let constraints = [
|
||||
contentStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
|
||||
contentStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
|
||||
contentView.trailingAnchor.constraint(equalTo: contentStackView.trailingAnchor, constant: 8),
|
||||
contentView.bottomAnchor.constraint(equalTo: contentStackView.bottomAnchor, constant: 8),
|
||||
|
||||
iconImageBackgroundView.heightAnchor.constraint(equalToConstant: 30),
|
||||
iconImageBackgroundView.widthAnchor.constraint(equalTo: iconImageBackgroundView.heightAnchor),
|
||||
|
||||
iconImageView.centerYAnchor.constraint(equalTo: iconImageBackgroundView.centerYAnchor),
|
||||
iconImageView.centerXAnchor.constraint(equalTo: iconImageBackgroundView.centerXAnchor),
|
||||
iconImageView.widthAnchor.constraint(equalToConstant: 20),
|
||||
|
||||
titleLabel.topAnchor.constraint(greaterThanOrEqualTo: contentView.topAnchor, constant: 12),
|
||||
contentView.bottomAnchor.constraint(greaterThanOrEqualTo: titleLabel.bottomAnchor, constant: 12),
|
||||
]
|
||||
|
||||
NSLayoutConstraint.activate(constraints)
|
||||
}
|
||||
|
||||
func update(with entry: SettingsEntry) {
|
||||
titleLabel.textColor = entry.textColor
|
||||
titleLabel.text = entry.title
|
||||
|
||||
if let icon = entry.icon {
|
||||
iconImageView.image = icon
|
||||
iconImageView.tintColor = .white
|
||||
iconImageBackgroundView.isHidden = false
|
||||
} else {
|
||||
iconImageBackgroundView.isHidden = true
|
||||
}
|
||||
|
||||
iconImageBackgroundView.layer.cornerRadius = 5
|
||||
iconImageBackgroundView.backgroundColor = entry.iconBackgroundColor
|
||||
|
||||
accessoryType = entry.accessoryType
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,91 @@
|
||||
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import MastodonLocalization
|
||||
|
||||
protocol SettingsViewControllerDelegate: AnyObject {
|
||||
func done(_ viewController: UIViewController)
|
||||
func didSelect(_ viewController: UIViewController, entry: SettingsEntry)
|
||||
}
|
||||
|
||||
class SettingsViewController: UIViewController {
|
||||
|
||||
let sections: [SettingsSection]
|
||||
|
||||
weak var delegate: SettingsViewControllerDelegate?
|
||||
var tableViewDataSource: UITableViewDiffableDataSource<SettingsSection, SettingsEntry>?
|
||||
let tableView: UITableView
|
||||
|
||||
init(accountName: String) {
|
||||
|
||||
sections = [
|
||||
.init(entries: [.general, .notifications]),
|
||||
.init(entries: [.aboutMastodon]),
|
||||
.init(entries: [.logout(accountName: accountName)])
|
||||
]
|
||||
|
||||
tableView = UITableView(frame: .zero, style: .insetGrouped)
|
||||
tableView.translatesAutoresizingMaskIntoConstraints = false
|
||||
tableView.register(SettingsTableViewCell.self, forCellReuseIdentifier: SettingsTableViewCell.reuseIdentifier)
|
||||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
||||
let tableViewDataSource = UITableViewDiffableDataSource<SettingsSection, SettingsEntry>(tableView: tableView) { [weak self] tableView, indexPath, itemIdentifier in
|
||||
guard let self,
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.reuseIdentifier, for: indexPath) as? SettingsTableViewCell
|
||||
else { fatalError("Wrong cell WTF??") }
|
||||
|
||||
let entry = self.sections[indexPath.section].entries[indexPath.row]
|
||||
cell.update(with: entry)
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
tableView.dataSource = tableViewDataSource
|
||||
tableView.delegate = self
|
||||
|
||||
self.tableViewDataSource = tableViewDataSource
|
||||
|
||||
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(SettingsViewController.done(_:)))
|
||||
|
||||
view.backgroundColor = .systemGroupedBackground
|
||||
view.addSubview(tableView)
|
||||
|
||||
title = L10n.Scene.Settings.Overview.title
|
||||
|
||||
tableView.pinToParent()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
var snapshot = NSDiffableDataSourceSnapshot<SettingsSection, SettingsEntry>()
|
||||
|
||||
for section in sections {
|
||||
snapshot.appendSections([section])
|
||||
snapshot.appendItems(section.entries)
|
||||
}
|
||||
|
||||
tableViewDataSource?.apply(snapshot)
|
||||
}
|
||||
|
||||
//MARK: Actions
|
||||
|
||||
@objc
|
||||
func done(_ sender: Any) {
|
||||
delegate?.done(self)
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: UITableViewDelegate
|
||||
extension SettingsViewController: UITableViewDelegate {
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
let entry = sections[indexPath.section].entries[indexPath.row]
|
||||
|
||||
delegate?.didSelect(self, entry: entry)
|
||||
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
}
|
||||
}
|
191
Mastodon/Scene/Settings/SettingsCoordinator.swift
Normal file
191
Mastodon/Scene/Settings/SettingsCoordinator.swift
Normal file
@ -0,0 +1,191 @@
|
||||
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import AuthenticationServices
|
||||
import MastodonCore
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
import Combine
|
||||
|
||||
protocol SettingsCoordinatorDelegate: AnyObject {
|
||||
func logout(_ settingsCoordinator: SettingsCoordinator)
|
||||
func openGithubURL(_ settingsCoordinator: SettingsCoordinator)
|
||||
func openPrivacyURL(_ settingsCoordinator: SettingsCoordinator)
|
||||
func openProfileSettingsURL(_ settingsCoordinator: SettingsCoordinator)
|
||||
}
|
||||
|
||||
class SettingsCoordinator: NSObject, Coordinator {
|
||||
|
||||
let navigationController: UINavigationController
|
||||
let presentedOn: UIViewController
|
||||
|
||||
weak var delegate: SettingsCoordinatorDelegate?
|
||||
private let settingsViewController: SettingsViewController
|
||||
|
||||
let setting: Setting
|
||||
let appContext: AppContext
|
||||
let authContext: AuthContext
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
|
||||
init(presentedOn: UIViewController, accountName: String, setting: Setting, appContext: AppContext, authContext: AuthContext) {
|
||||
self.presentedOn = presentedOn
|
||||
navigationController = UINavigationController()
|
||||
self.setting = setting
|
||||
self.appContext = appContext
|
||||
self.authContext = authContext
|
||||
|
||||
settingsViewController = SettingsViewController(accountName: accountName)
|
||||
}
|
||||
|
||||
func start() {
|
||||
settingsViewController.delegate = self
|
||||
|
||||
navigationController.pushViewController(settingsViewController, animated: false)
|
||||
presentedOn.present(navigationController, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - SettingsViewControllerDelegate
|
||||
extension SettingsCoordinator: SettingsViewControllerDelegate {
|
||||
func done(_ viewController: UIViewController) {
|
||||
viewController.dismiss(animated: true)
|
||||
}
|
||||
|
||||
func didSelect(_ viewController: UIViewController, entry: SettingsEntry) {
|
||||
switch entry {
|
||||
case .general:
|
||||
let generalSettingsViewController = GeneralSettingsViewController(setting: setting)
|
||||
generalSettingsViewController.delegate = self
|
||||
|
||||
navigationController.pushViewController(generalSettingsViewController, animated: true)
|
||||
case .notifications:
|
||||
|
||||
let currentSetting = appContext.settingService.currentSetting.value
|
||||
let notificationsEnabled = appContext.notificationService.isNotificationPermissionGranted.value
|
||||
let notificationViewController = NotificationSettingsViewController(currentSetting: currentSetting, notificationsEnabled: notificationsEnabled)
|
||||
notificationViewController.delegate = self
|
||||
|
||||
self.navigationController.pushViewController(notificationViewController, animated: true)
|
||||
|
||||
case .aboutMastodon:
|
||||
let aboutViewController = AboutViewController()
|
||||
aboutViewController.delegate = self
|
||||
|
||||
navigationController.pushViewController(aboutViewController, animated: true)
|
||||
case .logout(_):
|
||||
delegate?.logout(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - AboutViewControllerDelegate
|
||||
extension SettingsCoordinator: AboutViewControllerDelegate {
|
||||
func didSelect(_ viewController: AboutViewController, entry: AboutSettingsEntry) {
|
||||
switch entry {
|
||||
case .evenMoreSettings:
|
||||
delegate?.openProfileSettingsURL(self)
|
||||
case .contributeToMastodon:
|
||||
delegate?.openGithubURL(self)
|
||||
case .privacyPolicy:
|
||||
delegate?.openPrivacyURL(self)
|
||||
case .clearMediaCache(_):
|
||||
//FIXME: maybe we should inject an AppContext/AuthContext here instead of delegating everything to SceneCoordinator?
|
||||
AppContext.shared.purgeCache()
|
||||
viewController.update(with:
|
||||
[AboutSettingsSection(entries: [
|
||||
.evenMoreSettings,
|
||||
.contributeToMastodon,
|
||||
.privacyPolicy
|
||||
]),
|
||||
AboutSettingsSection(entries: [
|
||||
.clearMediaCache(AppContext.shared.currentDiskUsage())
|
||||
])]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - ASWebAuthenticationPresentationContextProviding
|
||||
extension SettingsCoordinator: ASWebAuthenticationPresentationContextProviding {
|
||||
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
|
||||
return navigationController.view.window!
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - GeneralSettingsViewControllerDelegate
|
||||
extension SettingsCoordinator: GeneralSettingsViewControllerDelegate {
|
||||
func save(_ viewController: UIViewController, setting: Setting, viewModel: GeneralSettingsViewModel) {
|
||||
UserDefaults.shared.customUserInterfaceStyle = viewModel.selectedAppearence.interfaceStyle
|
||||
UserDefaults.shared.preferredStaticEmoji = viewModel.playAnimations == false
|
||||
UserDefaults.shared.preferredStaticAvatar = viewModel.playAnimations == false
|
||||
UserDefaults.shared.preferredUsingDefaultBrowser = viewModel.selectedOpenLinks == .browser
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - NotificationSettingsViewControllerDelegate
|
||||
extension SettingsCoordinator: NotificationSettingsViewControllerDelegate {
|
||||
func showPolicyList(_ viewController: UIViewController, viewModel: NotificationSettingsViewModel) {
|
||||
let policyListViewController = PolicySelectionViewController(viewModel: viewModel)
|
||||
policyListViewController.delegate = self
|
||||
|
||||
navigationController.pushViewController(policyListViewController, animated: true)
|
||||
}
|
||||
|
||||
func viewWillDisappear(_ viewController: UIViewController, viewModel: NotificationSettingsViewModel) {
|
||||
|
||||
guard viewModel.updated else { return }
|
||||
|
||||
let authenticationBox = authContext.mastodonAuthenticationBox
|
||||
guard let subscription = setting.activeSubscription,
|
||||
setting.domain == authenticationBox.domain,
|
||||
setting.userID == authenticationBox.userID,
|
||||
let legacyViewModel = appContext.notificationService.dequeueNotificationViewModel(mastodonAuthenticationBox: authenticationBox), let deviceToken = appContext.notificationService.deviceToken.value else { return }
|
||||
|
||||
let queryData = Mastodon.API.Subscriptions.QueryData(
|
||||
policy: viewModel.selectedPolicy.subscriptionPolicy,
|
||||
alerts: Mastodon.API.Subscriptions.QueryData.Alerts(
|
||||
favourite: viewModel.notifyFavorites,
|
||||
follow: viewModel.notifyNewFollowers,
|
||||
reblog: viewModel.notifyBoosts,
|
||||
mention: viewModel.notifyMentions,
|
||||
poll: subscription.alert.poll
|
||||
)
|
||||
)
|
||||
let query = legacyViewModel.createSubscribeQuery(
|
||||
deviceToken: deviceToken,
|
||||
queryData: queryData,
|
||||
mastodonAuthenticationBox: authenticationBox
|
||||
)
|
||||
|
||||
appContext.apiService.createSubscription(
|
||||
subscriptionObjectID: subscription.objectID,
|
||||
query: query,
|
||||
mastodonAuthenticationBox: authenticationBox
|
||||
).sink(receiveCompletion: { completion in
|
||||
print(completion)
|
||||
}, receiveValue: { output in
|
||||
print(output)
|
||||
})
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
|
||||
func showNotificationSettings(_ viewController: UIViewController) {
|
||||
if #available(iOS 16.0, *) {
|
||||
if let url = URL(string: UIApplication.openNotificationSettingsURLString) {
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
} else {
|
||||
if let url = URL(string: UIApplication.openSettingsURLString) {
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - PolicySelectionViewControllerDelegate
|
||||
extension SettingsCoordinator: PolicySelectionViewControllerDelegate {
|
||||
func newPolicySelected(_ viewController: PolicySelectionViewController, newPolicy: NotificationPolicy) {
|
||||
self.setting.activeSubscription?.policyRaw = newPolicy.subscriptionPolicy.rawValue
|
||||
try? self.appContext.managedObjectContext.save()
|
||||
}
|
||||
}
|
@ -1,562 +0,0 @@
|
||||
//
|
||||
// SettingsViewController.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by ihugo on 2021/4/7.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import AuthenticationServices
|
||||
import MetaTextKit
|
||||
import MastodonSDK
|
||||
import MastodonMeta
|
||||
import MastodonAsset
|
||||
import MastodonCore
|
||||
import MastodonUI
|
||||
import MastodonLocalization
|
||||
|
||||
class SettingsViewController: UIViewController, NeedsDependency {
|
||||
|
||||
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
|
||||
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
||||
|
||||
var viewModel: SettingsViewModel! { willSet { precondition(!isViewLoaded) } }
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
var notificationPolicySubscription: AnyCancellable?
|
||||
|
||||
var triggerMenu: UIMenu {
|
||||
let anyone = L10n.Scene.Settings.Section.Notifications.Trigger.anyone
|
||||
let follower = L10n.Scene.Settings.Section.Notifications.Trigger.follower
|
||||
let follow = L10n.Scene.Settings.Section.Notifications.Trigger.follow
|
||||
let noOne = L10n.Scene.Settings.Section.Notifications.Trigger.noone
|
||||
let menu = UIMenu(
|
||||
image: nil,
|
||||
identifier: nil,
|
||||
options: .displayInline,
|
||||
children: [
|
||||
UIAction(title: anyone, image: UIImage(systemName: "person.3"), attributes: []) { [weak self] action in
|
||||
self?.updateTrigger(policy: .all)
|
||||
},
|
||||
UIAction(title: follower, image: UIImage(systemName: "person.crop.circle.badge.plus"), attributes: []) { [weak self] action in
|
||||
self?.updateTrigger(policy: .follower)
|
||||
},
|
||||
UIAction(title: follow, image: UIImage(systemName: "person.crop.circle.badge.checkmark"), attributes: []) { [weak self] action in
|
||||
self?.updateTrigger(policy: .followed)
|
||||
},
|
||||
UIAction(title: noOne, image: UIImage(systemName: "nosign"), attributes: []) { [weak self] action in
|
||||
self?.updateTrigger(policy: .none)
|
||||
},
|
||||
]
|
||||
)
|
||||
return menu
|
||||
}
|
||||
|
||||
private let notifySectionHeaderStackView: UIStackView = {
|
||||
let view = UIStackView()
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.isLayoutMarginsRelativeArrangement = true
|
||||
view.axis = .horizontal
|
||||
view.spacing = 4
|
||||
return view
|
||||
}()
|
||||
|
||||
let notifyLabel = UILabel()
|
||||
private(set) lazy var notifySectionHeader: UIView = {
|
||||
let view = notifySectionHeaderStackView
|
||||
|
||||
notifyLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
notifyLabel.adjustsFontForContentSizeCategory = true
|
||||
notifyLabel.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: UIFont.systemFont(ofSize: 20, weight: .semibold))
|
||||
notifyLabel.textColor = Asset.Colors.Label.primary.color
|
||||
notifyLabel.text = L10n.Scene.Settings.Section.Notifications.Trigger.title
|
||||
notifyLabel.adjustsFontSizeToFitWidth = true
|
||||
notifyLabel.minimumScaleFactor = 0.5
|
||||
|
||||
view.addArrangedSubview(notifyLabel)
|
||||
view.addArrangedSubview(whoButton)
|
||||
whoButton.setContentHuggingPriority(.defaultHigh + 1, for: .horizontal)
|
||||
whoButton.setContentHuggingPriority(.defaultHigh + 1, for: .vertical)
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
private(set) lazy var whoButton: UIButton = {
|
||||
let whoButton = UIButton(type: .roundedRect)
|
||||
whoButton.menu = triggerMenu
|
||||
whoButton.showsMenuAsPrimaryAction = true
|
||||
whoButton.setBackgroundColor(Asset.Colors.battleshipGrey.color, for: .normal)
|
||||
whoButton.setTitleColor(Asset.Colors.Label.primary.color, for: .normal)
|
||||
whoButton.titleLabel?.adjustsFontForContentSizeCategory = true
|
||||
whoButton.titleLabel?.font = UIFontMetrics(forTextStyle: .title3).scaledFont(for: UIFont.systemFont(ofSize: 20, weight: .semibold))
|
||||
whoButton.contentEdgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
|
||||
whoButton.layer.cornerRadius = 10
|
||||
whoButton.clipsToBounds = true
|
||||
whoButton.titleLabel?.adjustsFontSizeToFitWidth = true
|
||||
whoButton.titleLabel?.minimumScaleFactor = 0.5
|
||||
return whoButton
|
||||
}()
|
||||
|
||||
private(set) lazy var tableView: UITableView = {
|
||||
// init with a frame to fix a conflict ('UIView-Encapsulated-Layout-Width' UIStackView:0x7f8c2b6c0590.width == 0)
|
||||
let tableView = UITableView(frame: CGRect(x: 0, y: 0, width: 320, height: 320), style: .insetGrouped)
|
||||
tableView.translatesAutoresizingMaskIntoConstraints = false
|
||||
tableView.delegate = self
|
||||
tableView.rowHeight = UITableView.automaticDimension
|
||||
tableView.backgroundColor = .clear
|
||||
tableView.separatorColor = SystemTheme.separator
|
||||
|
||||
tableView.register(SettingsAppearanceTableViewCell.self, forCellReuseIdentifier: String(describing: SettingsAppearanceTableViewCell.self))
|
||||
tableView.register(SettingsToggleTableViewCell.self, forCellReuseIdentifier: String(describing: SettingsToggleTableViewCell.self))
|
||||
tableView.register(SettingsLinkTableViewCell.self, forCellReuseIdentifier: String(describing: SettingsLinkTableViewCell.self))
|
||||
return tableView
|
||||
}()
|
||||
|
||||
let tableFooterLabel = MetaLabel(style: .settingTableFooter)
|
||||
lazy var tableFooterView: UIView = {
|
||||
// init with a frame to fix a conflict ('UIView-Encapsulated-Layout-Height' UIStackView:0x7ffe41e47da0.height == 0)
|
||||
let view = UIStackView(frame: CGRect(x: 0, y: 0, width: 320, height: 320))
|
||||
view.isLayoutMarginsRelativeArrangement = true
|
||||
view.layoutMargins = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
|
||||
view.axis = .vertical
|
||||
view.alignment = .center
|
||||
|
||||
// tableFooterLabel.linkDelegate = self
|
||||
view.addArrangedSubview(tableFooterLabel)
|
||||
return view
|
||||
}()
|
||||
}
|
||||
|
||||
extension SettingsViewController {
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
setupView()
|
||||
bindViewModel()
|
||||
|
||||
viewModel.viewDidLoad.send()
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
// make large title not collapsed
|
||||
navigationController?.navigationBar.sizeToFit()
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
guard let footerView = self.tableView.tableFooterView else {
|
||||
return
|
||||
}
|
||||
|
||||
let width = self.tableView.bounds.size.width
|
||||
let size = footerView.systemLayoutSizeFitting(CGSize(width: width, height: UIView.layoutFittingCompressedSize.height))
|
||||
if footerView.frame.size.height != size.height {
|
||||
footerView.frame.size.height = size.height
|
||||
self.tableView.tableFooterView = footerView
|
||||
}
|
||||
}
|
||||
|
||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||
super.traitCollectionDidChange(previousTraitCollection)
|
||||
|
||||
updateSectionHeaderStackViewLayout()
|
||||
}
|
||||
|
||||
|
||||
// MAKR: - Private methods
|
||||
private func updateSectionHeaderStackViewLayout() {
|
||||
// accessibility
|
||||
if traitCollection.preferredContentSizeCategory < .accessibilityMedium {
|
||||
notifySectionHeaderStackView.axis = .horizontal
|
||||
notifyLabel.numberOfLines = 1
|
||||
} else {
|
||||
notifySectionHeaderStackView.axis = .vertical
|
||||
notifyLabel.numberOfLines = 0
|
||||
}
|
||||
}
|
||||
|
||||
private func bindViewModel() {
|
||||
self.whoButton.setTitle(viewModel.setting.value.activeSubscription?.policy.title, for: .normal)
|
||||
viewModel.setting
|
||||
.sink { [weak self] setting in
|
||||
guard let self = self else { return }
|
||||
self.notificationPolicySubscription = ManagedObjectObserver.observe(object: setting)
|
||||
.sink { _ in
|
||||
// do nothing
|
||||
} receiveValue: { [weak self] change in
|
||||
guard let self = self else { return }
|
||||
guard case let .update(object) = change.changeType,
|
||||
let setting = object as? Setting else { return }
|
||||
if let activeSubscription = setting.activeSubscription {
|
||||
self.whoButton.setTitle(activeSubscription.policy.title, for: .normal)
|
||||
} else {
|
||||
// assertionFailure()
|
||||
}
|
||||
}
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
let footer = "Mastodon for iOS v\(UIApplication.appVersion()) (\(UIApplication.appBuild()))"
|
||||
let metaContent = PlaintextMetaContent(string: footer)
|
||||
tableFooterLabel.configure(content: metaContent)
|
||||
}
|
||||
|
||||
private func setupView() {
|
||||
setupBackgroundColor()
|
||||
|
||||
setupNavigation()
|
||||
view.addSubview(tableView)
|
||||
tableView.pinToParent()
|
||||
setupTableView()
|
||||
|
||||
updateSectionHeaderStackViewLayout()
|
||||
}
|
||||
|
||||
private func setupBackgroundColor() {
|
||||
view.backgroundColor = UIColor(dynamicProvider: { traitCollection in
|
||||
switch traitCollection.userInterfaceLevel {
|
||||
case .elevated where traitCollection.userInterfaceStyle == .dark:
|
||||
return SystemTheme.systemElevatedBackgroundColor
|
||||
default:
|
||||
return .secondarySystemBackground
|
||||
}
|
||||
})
|
||||
|
||||
tableView.separatorColor = SystemTheme.separator
|
||||
}
|
||||
|
||||
private func setupNavigation() {
|
||||
navigationController?.navigationBar.prefersLargeTitles = true
|
||||
navigationItem.rightBarButtonItem
|
||||
= UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.done,
|
||||
target: self,
|
||||
action: #selector(doneButtonDidClick))
|
||||
navigationItem.title = L10n.Scene.Settings.title
|
||||
}
|
||||
|
||||
private func setupTableView() {
|
||||
viewModel.setupDiffableDataSource(
|
||||
for: tableView,
|
||||
settingsAppearanceTableViewCellDelegate: self,
|
||||
settingsToggleCellDelegate: self
|
||||
)
|
||||
tableView.tableFooterView = tableFooterView
|
||||
}
|
||||
|
||||
func alertToSignOut() {
|
||||
let alertController = UIAlertController(
|
||||
title: L10n.Common.Alerts.SignOut.title,
|
||||
message: L10n.Common.Alerts.SignOut.message,
|
||||
preferredStyle: .alert
|
||||
)
|
||||
|
||||
let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .cancel, handler: nil)
|
||||
let signOutAction = UIAlertAction(title: L10n.Common.Alerts.SignOut.confirm, style: .destructive) { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
self.signOut()
|
||||
}
|
||||
alertController.addAction(cancelAction)
|
||||
alertController.addAction(signOutAction)
|
||||
_ = self.coordinator.present(
|
||||
scene: .alertController(alertController: alertController),
|
||||
from: self,
|
||||
transition: .alertController(animated: true, completion: nil)
|
||||
)
|
||||
}
|
||||
|
||||
func signOut() {
|
||||
// clear badge before sign-out
|
||||
context.notificationService.clearNotificationCountForActiveUser()
|
||||
|
||||
Task { @MainActor in
|
||||
try await context.authenticationService.signOutMastodonUser(
|
||||
authenticationBox: viewModel.authContext.mastodonAuthenticationBox
|
||||
)
|
||||
self.coordinator.setup()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Mark: - Actions
|
||||
extension SettingsViewController {
|
||||
@objc private func doneButtonDidClick() {
|
||||
dismiss(animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UITableViewDelegate
|
||||
extension SettingsViewController: UITableViewDelegate {
|
||||
|
||||
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
||||
let sections = viewModel.dataSource.snapshot().sectionIdentifiers
|
||||
guard section < sections.count else { return nil }
|
||||
|
||||
let sectionIdentifier = sections[section]
|
||||
|
||||
let header: SettingsSectionHeader
|
||||
switch sectionIdentifier {
|
||||
case .preference:
|
||||
return UIView()
|
||||
case .notifications:
|
||||
header = SettingsSectionHeader(
|
||||
frame: CGRect(x: 0, y: 0, width: 375, height: 66),
|
||||
customView: notifySectionHeader)
|
||||
header.update(title: sectionIdentifier.title)
|
||||
default:
|
||||
header = SettingsSectionHeader(frame: CGRect(x: 0, y: 0, width: 375, height: 66))
|
||||
header.update(title: sectionIdentifier.title)
|
||||
}
|
||||
header.preservesSuperviewLayoutMargins = true
|
||||
|
||||
return header
|
||||
}
|
||||
|
||||
// remove the gap of table's footer
|
||||
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
|
||||
return UIView()
|
||||
}
|
||||
|
||||
// remove the gap of table's footer
|
||||
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
|
||||
return CGFloat.leastNonzeroMagnitude
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
guard let dataSource = viewModel.dataSource else { return }
|
||||
guard let item = dataSource.itemIdentifier(for: indexPath) else { return }
|
||||
|
||||
switch item {
|
||||
case .appearance:
|
||||
// do nothing
|
||||
break
|
||||
case .notification:
|
||||
// do nothing
|
||||
break
|
||||
case .preference:
|
||||
// do nothing
|
||||
break
|
||||
case .boringZone(let link), .spicyZone(let link):
|
||||
let feedbackGenerator = UIImpactFeedbackGenerator(style: .light)
|
||||
feedbackGenerator.impactOccurred()
|
||||
switch link {
|
||||
case .accountSettings:
|
||||
let domain = viewModel.authContext.mastodonAuthenticationBox.domain
|
||||
guard let url = URL(string: "https://\(domain)/auth/edit") else { return }
|
||||
viewModel.openAuthenticationPage(authenticateURL: url, presentationContextProvider: self)
|
||||
case .github:
|
||||
guard let url = URL(string: "https://github.com/mastodon/mastodon-ios") else { break }
|
||||
_ = coordinator.present(
|
||||
scene: .safari(url: url),
|
||||
from: self,
|
||||
transition: .safariPresent(animated: true, completion: nil)
|
||||
)
|
||||
case .termsOfService, .privacyPolicy:
|
||||
// same URL
|
||||
guard let url = viewModel.privacyURL else { break }
|
||||
_ = coordinator.present(
|
||||
scene: .safari(url: url),
|
||||
from: self,
|
||||
transition: .safariPresent(animated: true, completion: nil)
|
||||
)
|
||||
case .clearMediaCache:
|
||||
context.purgeCache()
|
||||
.receive(on: RunLoop.main)
|
||||
.sink { [weak self] byteCount in
|
||||
guard let self = self else { return }
|
||||
let byteCountFormatted = AppContext.byteCountFormatter.string(fromByteCount: Int64(byteCount))
|
||||
let alertController = UIAlertController(
|
||||
title: L10n.Common.Alerts.CleanCache.title,
|
||||
message: L10n.Common.Alerts.CleanCache.message(byteCountFormatted),
|
||||
preferredStyle: .alert
|
||||
)
|
||||
let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default, handler: nil)
|
||||
alertController.addAction(okAction)
|
||||
_ = self.coordinator.present(scene: .alertController(alertController: alertController), from: nil, transition: .alertController(animated: true, completion: nil))
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
case .signOut:
|
||||
feedbackGenerator.impactOccurred()
|
||||
alertToSignOut()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update setting into core data
|
||||
extension SettingsViewController {
|
||||
func updateTrigger(policy: Mastodon.API.Subscriptions.Policy) {
|
||||
let objectID = self.viewModel.setting.value.objectID
|
||||
let managedObjectContext = context.backgroundManagedObjectContext
|
||||
|
||||
managedObjectContext.performChanges {
|
||||
let setting = managedObjectContext.object(with: objectID) as! Setting
|
||||
let (subscription, _) = APIService.CoreData.createOrFetchSubscription(
|
||||
into: managedObjectContext,
|
||||
setting: setting,
|
||||
policy: policy
|
||||
)
|
||||
let now = Date()
|
||||
subscription.update(activedAt: now)
|
||||
setting.didUpdate(at: now)
|
||||
}
|
||||
.sink { _ in
|
||||
// do nothing
|
||||
} receiveValue: { _ in
|
||||
// do nothing
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SettingsAppearanceTableViewCellDelegate
|
||||
extension SettingsViewController: SettingsAppearanceTableViewCellDelegate {
|
||||
func settingsAppearanceTableViewCell(
|
||||
_ cell: SettingsAppearanceTableViewCell,
|
||||
didSelectAppearanceMode appearanceMode: SettingsItem.AppearanceMode
|
||||
) {
|
||||
guard let dataSource = viewModel.dataSource else { return }
|
||||
guard let indexPath = tableView.indexPath(for: cell) else { return }
|
||||
let item = dataSource.itemIdentifier(for: indexPath)
|
||||
guard case .appearance = item else { return }
|
||||
|
||||
Task { @MainActor in
|
||||
switch appearanceMode {
|
||||
case .system:
|
||||
UserDefaults.shared.customUserInterfaceStyle = .unspecified
|
||||
case .dark:
|
||||
UserDefaults.shared.customUserInterfaceStyle = .dark
|
||||
case .light:
|
||||
UserDefaults.shared.customUserInterfaceStyle = .light
|
||||
}
|
||||
|
||||
let feedbackGenerator = UIImpactFeedbackGenerator(style: .light)
|
||||
feedbackGenerator.impactOccurred()
|
||||
} // end Task
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension SettingsViewController: SettingsToggleCellDelegate {
|
||||
func settingsToggleCell(_ cell: SettingsToggleTableViewCell, switchValueDidChange switch: UISwitch) {
|
||||
guard let dataSource = viewModel.dataSource else { return }
|
||||
guard let indexPath = tableView.indexPath(for: cell) else { return }
|
||||
|
||||
let isOn = `switch`.isOn
|
||||
let item = dataSource.itemIdentifier(for: indexPath)
|
||||
|
||||
switch item {
|
||||
case .notification(let record, let switchMode):
|
||||
let managedObjectContext = context.backgroundManagedObjectContext
|
||||
managedObjectContext.performChanges {
|
||||
guard let setting = record.object(in: managedObjectContext) else { return }
|
||||
guard let subscription = setting.activeSubscription else { return }
|
||||
let alert = subscription.alert
|
||||
switch switchMode {
|
||||
case .favorite: alert.update(favourite: isOn)
|
||||
case .follow: alert.update(follow: isOn)
|
||||
case .reblog: alert.update(reblog: isOn)
|
||||
case .mention: alert.update(mention: isOn)
|
||||
}
|
||||
// trigger setting update
|
||||
alert.subscription.setting?.didUpdate(at: Date())
|
||||
}
|
||||
.sink { _ in
|
||||
// do nothing
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
case .preference(let record, let preferenceType):
|
||||
let managedObjectContext = context.backgroundManagedObjectContext
|
||||
managedObjectContext.performChanges {
|
||||
guard let setting = record.object(in: managedObjectContext) else { return }
|
||||
switch preferenceType {
|
||||
case .disableAvatarAnimation:
|
||||
setting.update(preferredStaticAvatar: isOn)
|
||||
case .disableEmojiAnimation:
|
||||
setting.update(preferredStaticEmoji: isOn)
|
||||
case .useDefaultBrowser:
|
||||
setting.update(preferredUsingDefaultBrowser: isOn)
|
||||
}
|
||||
}
|
||||
.sink { result in
|
||||
switch result {
|
||||
case .success:
|
||||
switch preferenceType {
|
||||
case .disableAvatarAnimation:
|
||||
UserDefaults.shared.preferredStaticAvatar = isOn
|
||||
case .disableEmojiAnimation:
|
||||
UserDefaults.shared.preferredStaticEmoji = isOn
|
||||
case .useDefaultBrowser:
|
||||
UserDefaults.shared.preferredUsingDefaultBrowser = isOn
|
||||
}
|
||||
case .failure(let error):
|
||||
assertionFailure(error.localizedDescription)
|
||||
break
|
||||
}
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
default:
|
||||
assertionFailure()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - MetaLabelDelegate
|
||||
extension SettingsViewController: MetaLabelDelegate {
|
||||
func metaLabel(_ metaLabel: MetaLabel, didSelectMeta meta: Meta) {
|
||||
switch meta {
|
||||
case .url(_, _, let url, _):
|
||||
guard let url = URL(string: url) else { return }
|
||||
_ = coordinator.present(scene: .safari(url: url), from: self, transition: .safariPresent(animated: true, completion: nil))
|
||||
default:
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ASAuthorizationControllerPresentationContextProviding
|
||||
extension SettingsViewController: ASWebAuthenticationPresentationContextProviding {
|
||||
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
|
||||
return view.window!
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UIAdaptivePresentationControllerDelegate
|
||||
extension SettingsViewController: UIAdaptivePresentationControllerDelegate {
|
||||
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
|
||||
return .pageSheet
|
||||
}
|
||||
}
|
||||
|
||||
extension SettingsViewController {
|
||||
|
||||
var closeKeyCommand: UIKeyCommand {
|
||||
UIKeyCommand(
|
||||
title: L10n.Scene.Settings.Keyboard.closeSettingsWindow,
|
||||
image: nil,
|
||||
action: #selector(SettingsViewController.closeSettingsWindowKeyCommandHandler(_:)),
|
||||
input: "w",
|
||||
modifierFlags: .command,
|
||||
propertyList: nil,
|
||||
alternates: [],
|
||||
discoverabilityTitle: nil,
|
||||
attributes: [],
|
||||
state: .off
|
||||
)
|
||||
}
|
||||
|
||||
override var keyCommands: [UIKeyCommand]? {
|
||||
return [closeKeyCommand]
|
||||
}
|
||||
|
||||
@objc private func closeSettingsWindowKeyCommandHandler(_ sender: UIKeyCommand) {
|
||||
dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
}
|
@ -1,164 +0,0 @@
|
||||
//
|
||||
// SettingsViewModel.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by ihugo on 2021/4/7.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import Foundation
|
||||
import MastodonSDK
|
||||
import UIKit
|
||||
import AuthenticationServices
|
||||
import MastodonCore
|
||||
|
||||
class SettingsViewModel {
|
||||
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
|
||||
// input
|
||||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
var mastodonAuthenticationController: MastodonAuthenticationController?
|
||||
|
||||
let setting: CurrentValueSubject<Setting, Never>
|
||||
var updateDisposeBag = Set<AnyCancellable>()
|
||||
var createDisposeBag = Set<AnyCancellable>()
|
||||
|
||||
let viewDidLoad = PassthroughSubject<Void, Never>()
|
||||
|
||||
// output
|
||||
var dataSource: UITableViewDiffableDataSource<SettingsSection, SettingsItem>!
|
||||
/// create a subscription when:
|
||||
/// - does not has one
|
||||
/// - does not find subscription for selected trigger when change trigger
|
||||
let createSubscriptionSubject = PassthroughSubject<(triggerBy: String, values: [Bool?]), Never>()
|
||||
let currentInstance = CurrentValueSubject<Mastodon.Entity.Instance?, Never>(nil)
|
||||
|
||||
/// update a subscription when:
|
||||
/// - change switch for specified alerts
|
||||
let updateSubscriptionSubject = PassthroughSubject<(triggerBy: String, values: [Bool?]), Never>()
|
||||
|
||||
lazy var privacyURL: URL? = {
|
||||
let domain = authContext.mastodonAuthenticationBox.domain
|
||||
return Mastodon.API.privacyURL(domain: domain)
|
||||
}()
|
||||
|
||||
init(context: AppContext, authContext: AuthContext, setting: Setting) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
self.setting = CurrentValueSubject(setting)
|
||||
|
||||
self.setting
|
||||
.sink(receiveValue: { [weak self] setting in
|
||||
guard let self = self else { return }
|
||||
self.processDataSource(setting)
|
||||
})
|
||||
.store(in: &disposeBag)
|
||||
|
||||
context.apiService.instance(domain: authContext.mastodonAuthenticationBox.domain)
|
||||
.sink { [weak self] completion in
|
||||
guard let self = self else { return }
|
||||
switch completion {
|
||||
case .failure(_):
|
||||
self.currentInstance.value = nil
|
||||
case .finished:
|
||||
break
|
||||
}
|
||||
} receiveValue: { [weak self] response in
|
||||
guard let self = self else { return }
|
||||
self.currentInstance.value = response.value
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
}
|
||||
|
||||
extension SettingsViewModel {
|
||||
|
||||
func openAuthenticationPage(
|
||||
authenticateURL: URL,
|
||||
presentationContextProvider: ASWebAuthenticationPresentationContextProviding
|
||||
) {
|
||||
let authenticationController = MastodonAuthenticationController(
|
||||
context: self.context,
|
||||
authenticateURL: authenticateURL
|
||||
)
|
||||
|
||||
self.mastodonAuthenticationController = authenticationController
|
||||
authenticationController.authenticationSession?.presentationContextProvider = presentationContextProvider
|
||||
authenticationController.authenticationSession?.start()
|
||||
}
|
||||
|
||||
// MARK: - Private methods
|
||||
private func processDataSource(_ setting: Setting) {
|
||||
guard let dataSource = self.dataSource else { return }
|
||||
var snapshot = NSDiffableDataSourceSnapshot<SettingsSection, SettingsItem>()
|
||||
|
||||
// appearance
|
||||
let appearanceItems = [
|
||||
SettingsItem.appearance(record: .init(objectID: setting.objectID))
|
||||
]
|
||||
snapshot.appendSections([.appearance])
|
||||
snapshot.appendItems(appearanceItems, toSection: .appearance)
|
||||
|
||||
// preference
|
||||
snapshot.appendSections([.preference])
|
||||
let preferenceItems: [SettingsItem] = SettingsItem.PreferenceType.allCases.map { preferenceType in
|
||||
SettingsItem.preference(settingRecord: .init(objectID: setting.objectID), preferenceType: preferenceType)
|
||||
}
|
||||
snapshot.appendItems(preferenceItems,toSection: .preference)
|
||||
|
||||
// notification
|
||||
let notificationItems = SettingsItem.NotificationSwitchMode.allCases.map { mode in
|
||||
SettingsItem.notification(settingRecord: .init(objectID: setting.objectID), switchMode: mode)
|
||||
}
|
||||
snapshot.appendSections([.notifications])
|
||||
snapshot.appendItems(notificationItems, toSection: .notifications)
|
||||
|
||||
// boring zone
|
||||
let boringZoneSettingsItems: [SettingsItem] = {
|
||||
let links: [SettingsItem.Link] = [
|
||||
.accountSettings,
|
||||
.github,
|
||||
.termsOfService,
|
||||
.privacyPolicy
|
||||
]
|
||||
let items = links.map { SettingsItem.boringZone(item: $0) }
|
||||
return items
|
||||
}()
|
||||
snapshot.appendSections([.boringZone])
|
||||
snapshot.appendItems(boringZoneSettingsItems, toSection: .boringZone)
|
||||
|
||||
let spicyZoneSettingsItems: [SettingsItem] = {
|
||||
let links: [SettingsItem.Link] = [
|
||||
.clearMediaCache,
|
||||
.signOut
|
||||
]
|
||||
let items = links.map { SettingsItem.spicyZone(item: $0) }
|
||||
return items
|
||||
}()
|
||||
snapshot.appendSections([.spicyZone])
|
||||
snapshot.appendItems(spicyZoneSettingsItems, toSection: .spicyZone)
|
||||
|
||||
dataSource.apply(snapshot, animatingDifferences: false)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension SettingsViewModel {
|
||||
func setupDiffableDataSource(
|
||||
for tableView: UITableView,
|
||||
settingsAppearanceTableViewCellDelegate: SettingsAppearanceTableViewCellDelegate,
|
||||
settingsToggleCellDelegate: SettingsToggleCellDelegate
|
||||
) {
|
||||
dataSource = SettingsSection.tableViewDiffableDataSource(
|
||||
for: tableView,
|
||||
managedObjectContext: context.managedObjectContext,
|
||||
settingsAppearanceTableViewCellDelegate: settingsAppearanceTableViewCellDelegate,
|
||||
settingsToggleCellDelegate: settingsToggleCellDelegate
|
||||
)
|
||||
processDataSource(self.setting.value)
|
||||
}
|
||||
}
|
47
Mastodon/Scene/Settings/Shared/ToggleTableViewCell.swift
Normal file
47
Mastodon/Scene/Settings/Shared/ToggleTableViewCell.swift
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import MastodonAsset
|
||||
|
||||
class ToggleTableViewCell: UITableViewCell {
|
||||
class var reuseIdentifier: String {
|
||||
return "ToggleTableViewCell"
|
||||
}
|
||||
|
||||
let label: UILabel
|
||||
let toggle: UISwitch
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
|
||||
label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .regular))
|
||||
label.numberOfLines = 0
|
||||
|
||||
toggle = UISwitch()
|
||||
toggle.translatesAutoresizingMaskIntoConstraints = false
|
||||
toggle.onTintColor = Asset.Colors.Brand.blurple.color
|
||||
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
|
||||
contentView.addSubview(label)
|
||||
contentView.addSubview(toggle)
|
||||
setupConstraints()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||
|
||||
private func setupConstraints() {
|
||||
let constraints = [
|
||||
label.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 11),
|
||||
label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
|
||||
contentView.bottomAnchor.constraint(equalTo: label.bottomAnchor, constant: 11),
|
||||
|
||||
toggle.leadingAnchor.constraint(greaterThanOrEqualTo: label.trailingAnchor, constant: 16),
|
||||
toggle.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
|
||||
contentView.trailingAnchor.constraint(equalTo: toggle.trailingAnchor, constant: 16)
|
||||
|
||||
]
|
||||
NSLayoutConstraint.activate(constraints)
|
||||
}
|
||||
}
|
@ -1,147 +0,0 @@
|
||||
//
|
||||
// AppearanceView.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-7-6.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import MastodonAsset
|
||||
import MastodonLocalization
|
||||
import MastodonUI
|
||||
|
||||
class AppearanceView: UIView {
|
||||
|
||||
let imageViewShadowBackgroundContainer = ShadowBackgroundContainer()
|
||||
lazy var imageView: UIImageView = {
|
||||
let view = UIImageView()
|
||||
view.contentMode = .scaleAspectFill
|
||||
view.layer.masksToBounds = true
|
||||
view.layer.cornerRadius = 4
|
||||
view.layer.cornerCurve = .continuous
|
||||
// accessibility
|
||||
view.accessibilityIgnoresInvertColors = true
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var titleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .systemFont(ofSize: 12, weight: .regular)
|
||||
label.textColor = Asset.Colors.Label.primary.color
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
lazy var checkmarkButton: UIButton = {
|
||||
let button = UIButton()
|
||||
button.isUserInteractionEnabled = false
|
||||
button.setImage(UIImage(systemName: "circle"), for: .normal)
|
||||
button.setImage(UIImage(systemName: "checkmark.circle.fill"), for: .selected)
|
||||
button.imageView?.preferredSymbolConfiguration = UIImage.SymbolConfiguration(textStyle: .body)
|
||||
button.imageView?.tintColor = Asset.Colors.Label.primary.color
|
||||
button.imageView?.contentMode = .scaleAspectFill
|
||||
return button
|
||||
}()
|
||||
|
||||
lazy var stackView: UIStackView = {
|
||||
let view = UIStackView()
|
||||
view.axis = .vertical
|
||||
view.spacing = 8
|
||||
view.distribution = .equalSpacing
|
||||
return view
|
||||
}()
|
||||
|
||||
var selected: Bool = false {
|
||||
didSet { setNeedsLayout() }
|
||||
}
|
||||
|
||||
// MARK: - Methods
|
||||
init(image: UIImage?, title: String) {
|
||||
super.init(frame: .zero)
|
||||
setupUI()
|
||||
|
||||
imageView.image = image
|
||||
titleLabel.text = title
|
||||
}
|
||||
|
||||
override var isAccessibilityElement: Bool {
|
||||
get { return true }
|
||||
set { }
|
||||
|
||||
}
|
||||
override var accessibilityLabel: String? {
|
||||
get { titleLabel.text }
|
||||
set { }
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension AppearanceView {
|
||||
|
||||
private func setupUI() {
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
imageViewShadowBackgroundContainer.addSubview(imageView)
|
||||
imageView.pinToParent()
|
||||
imageViewShadowBackgroundContainer.cornerRadius = 4
|
||||
|
||||
stackView.addArrangedSubview(imageViewShadowBackgroundContainer)
|
||||
stackView.addArrangedSubview(titleLabel)
|
||||
stackView.addArrangedSubview(checkmarkButton)
|
||||
|
||||
addSubview(stackView)
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackView.pinToParent()
|
||||
NSLayoutConstraint.activate([
|
||||
imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: 121.0 / 100.0), // height / width
|
||||
])
|
||||
}
|
||||
|
||||
private func configureForSelection() {
|
||||
if selected {
|
||||
accessibilityTraits.insert(.selected)
|
||||
} else {
|
||||
accessibilityTraits.remove(.selected)
|
||||
}
|
||||
|
||||
checkmarkButton.isSelected = selected
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
configureForSelection()
|
||||
}
|
||||
|
||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||
super.traitCollectionDidChange(previousTraitCollection)
|
||||
|
||||
setNeedsLayout()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension AppearanceView {
|
||||
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
super.touchesBegan(touches, with: event)
|
||||
self.alpha = 0.5
|
||||
}
|
||||
|
||||
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
super.touchesEnded(touches, with: event)
|
||||
UIView.animate(withDuration: 0.33) {
|
||||
self.alpha = 1
|
||||
}
|
||||
}
|
||||
|
||||
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
super.touchesCancelled(touches, with: event)
|
||||
UIView.animate(withDuration: 0.33) {
|
||||
self.alpha = 1
|
||||
}
|
||||
}
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
//
|
||||
// SettingsSectionHeader.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by ihugo on 2021/4/8.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import MastodonAsset
|
||||
import MastodonLocalization
|
||||
|
||||
struct GroupedTableViewConstraints {
|
||||
static let topMargin: CGFloat = 40
|
||||
static let bottomMargin: CGFloat = 10
|
||||
}
|
||||
|
||||
/// section header which supports add a custom view blelow the title
|
||||
class SettingsSectionHeader: UIView {
|
||||
lazy var titleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.font = .systemFont(ofSize: 13, weight: .regular)
|
||||
label.textColor = Asset.Colors.Label.secondary.color
|
||||
return label
|
||||
}()
|
||||
|
||||
lazy var stackView: UIStackView = {
|
||||
let view = UIStackView()
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.isLayoutMarginsRelativeArrangement = true
|
||||
view.layoutMargins = UIEdgeInsets(
|
||||
top: GroupedTableViewConstraints.topMargin,
|
||||
left: 0,
|
||||
bottom: GroupedTableViewConstraints.bottomMargin,
|
||||
right: 0
|
||||
)
|
||||
view.axis = .vertical
|
||||
return view
|
||||
}()
|
||||
|
||||
init(frame: CGRect, customView: UIView? = nil) {
|
||||
super.init(frame: frame)
|
||||
|
||||
backgroundColor = .clear
|
||||
|
||||
stackView.addArrangedSubview(titleLabel)
|
||||
if let view = customView {
|
||||
stackView.addArrangedSubview(view)
|
||||
}
|
||||
|
||||
addSubview(stackView)
|
||||
NSLayoutConstraint.activate([
|
||||
stackView.leadingAnchor.constraint(equalTo: self.readableContentGuide.leadingAnchor),
|
||||
stackView.trailingAnchor.constraint(lessThanOrEqualTo: self.readableContentGuide.trailingAnchor),
|
||||
stackView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
|
||||
stackView.topAnchor.constraint(equalTo: self.topAnchor),
|
||||
])
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(title: String?) {
|
||||
titleLabel.text = title?.uppercased()
|
||||
}
|
||||
}
|
@ -3,6 +3,6 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>_XCCurrentVersionName</key>
|
||||
<string>CoreData 8.xcdatamodel</string>
|
||||
<string>CoreData 9.xcdatamodel</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -0,0 +1,288 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22222" systemVersion="22G74" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="Application" representedClassName="CoreDataStack.Application" syncable="YES">
|
||||
<attribute name="identifier" optional="YES" attributeType="UUID" usesScalarValueType="NO"/>
|
||||
<attribute name="name" attributeType="String"/>
|
||||
<attribute name="vapidKey" optional="YES" attributeType="String"/>
|
||||
<attribute name="website" optional="YES" attributeType="String"/>
|
||||
<relationship name="status" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="application" inverseEntity="Status"/>
|
||||
</entity>
|
||||
<entity name="Card" representedClassName="CoreDataStack.Card" syncable="YES">
|
||||
<attribute name="authorName" optional="YES" attributeType="String"/>
|
||||
<attribute name="authorURLRaw" optional="YES" attributeType="String"/>
|
||||
<attribute name="blurhash" optional="YES" attributeType="String"/>
|
||||
<attribute name="desc" attributeType="String"/>
|
||||
<attribute name="embedURLRaw" optional="YES" attributeType="String"/>
|
||||
<attribute name="height" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="html" optional="YES" attributeType="String"/>
|
||||
<attribute name="image" optional="YES" attributeType="String"/>
|
||||
<attribute name="providerName" optional="YES" attributeType="String"/>
|
||||
<attribute name="providerURLRaw" optional="YES" attributeType="String"/>
|
||||
<attribute name="title" attributeType="String"/>
|
||||
<attribute name="typeRaw" attributeType="String"/>
|
||||
<attribute name="urlRaw" attributeType="String"/>
|
||||
<attribute name="width" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="status" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="card" inverseEntity="Status"/>
|
||||
</entity>
|
||||
<entity name="DomainBlock" representedClassName="CoreDataStack.DomainBlock" syncable="YES">
|
||||
<attribute name="blockedDomain" attributeType="String"/>
|
||||
<attribute name="createAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="domain" attributeType="String"/>
|
||||
<attribute name="userID" attributeType="String"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="userID"/>
|
||||
<constraint value="domain"/>
|
||||
<constraint value="blockedDomain"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="Emoji" representedClassName="CoreDataStack.Emoji" syncable="YES">
|
||||
<attribute name="category" optional="YES" attributeType="String"/>
|
||||
<attribute name="createAt" attributeType="Date" defaultDateTimeInterval="631123200" usesScalarValueType="NO"/>
|
||||
<attribute name="identifier" attributeType="UUID" usesScalarValueType="NO"/>
|
||||
<attribute name="shortcode" attributeType="String"/>
|
||||
<attribute name="staticURL" attributeType="String"/>
|
||||
<attribute name="url" attributeType="String"/>
|
||||
<attribute name="visibleInPicker" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
</entity>
|
||||
<entity name="Feed" representedClassName="CoreDataStack.Feed" syncable="YES">
|
||||
<attribute name="acctRaw" optional="YES" attributeType="String"/>
|
||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="hasMore" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="isLoadingMore" transient="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="kindRaw" attributeType="String"/>
|
||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="notification" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Notification" inverseName="feeds" inverseEntity="Notification"/>
|
||||
<relationship name="status" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="feeds" inverseEntity="Status"/>
|
||||
</entity>
|
||||
<entity name="Instance" representedClassName="CoreDataStack.Instance" syncable="YES">
|
||||
<attribute name="configurationRaw" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="configurationV2Raw" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="domain" attributeType="String"/>
|
||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="version" optional="YES" attributeType="String"/>
|
||||
<relationship name="authentications" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonAuthentication" inverseName="instance" inverseEntity="MastodonAuthentication"/>
|
||||
</entity>
|
||||
<entity name="MastodonAuthentication" representedClassName="CoreDataStack.MastodonAuthentication" syncable="YES">
|
||||
<attribute name="activedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="appAccessToken" attributeType="String"/>
|
||||
<attribute name="clientID" attributeType="String"/>
|
||||
<attribute name="clientSecret" attributeType="String"/>
|
||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="domain" attributeType="String"/>
|
||||
<attribute name="identifier" attributeType="UUID" usesScalarValueType="NO"/>
|
||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="userAccessToken" attributeType="String"/>
|
||||
<attribute name="userID" attributeType="String"/>
|
||||
<attribute name="username" attributeType="String"/>
|
||||
<relationship name="instance" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Instance" inverseName="authentications" inverseEntity="Instance"/>
|
||||
<relationship name="user" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="mastodonAuthentication" inverseEntity="MastodonUser"/>
|
||||
</entity>
|
||||
<entity name="MastodonUser" representedClassName="CoreDataStack.MastodonUser" syncable="YES">
|
||||
<attribute name="acct" attributeType="String"/>
|
||||
<attribute name="avatar" attributeType="String"/>
|
||||
<attribute name="avatarStatic" optional="YES" attributeType="String"/>
|
||||
<attribute name="bot" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="displayName" attributeType="String"/>
|
||||
<attribute name="domain" attributeType="String"/>
|
||||
<attribute name="emojis" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="fields" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="followersCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="followingCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="header" attributeType="String"/>
|
||||
<attribute name="headerStatic" optional="YES" attributeType="String"/>
|
||||
<attribute name="id" attributeType="String"/>
|
||||
<attribute name="identifier" attributeType="String"/>
|
||||
<attribute name="locked" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="note" optional="YES" attributeType="String"/>
|
||||
<attribute name="statusesCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="suspended" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="url" optional="YES" attributeType="String"/>
|
||||
<attribute name="username" attributeType="String"/>
|
||||
<relationship name="blocking" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="blockingBy" inverseEntity="MastodonUser"/>
|
||||
<relationship name="blockingBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="blocking" inverseEntity="MastodonUser"/>
|
||||
<relationship name="bookmarked" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Status" inverseName="bookmarkedBy" inverseEntity="Status"/>
|
||||
<relationship name="domainBlocking" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="domainBlockingBy" inverseEntity="MastodonUser"/>
|
||||
<relationship name="domainBlockingBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="domainBlocking" inverseEntity="MastodonUser"/>
|
||||
<relationship name="endorsed" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="endorsedBy" inverseEntity="MastodonUser"/>
|
||||
<relationship name="endorsedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="endorsed" inverseEntity="MastodonUser"/>
|
||||
<relationship name="favourite" toMany="YES" deletionRule="Nullify" destinationEntity="Status" inverseName="favouritedBy" inverseEntity="Status"/>
|
||||
<relationship name="followedTags" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Tag" inverseName="followedBy" inverseEntity="Tag"/>
|
||||
<relationship name="following" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="followingBy" inverseEntity="MastodonUser"/>
|
||||
<relationship name="followingBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="following" inverseEntity="MastodonUser"/>
|
||||
<relationship name="followRequested" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="followRequestedBy" inverseEntity="MastodonUser"/>
|
||||
<relationship name="followRequestedBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="followRequested" inverseEntity="MastodonUser"/>
|
||||
<relationship name="mastodonAuthentication" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonAuthentication" inverseName="user" inverseEntity="MastodonAuthentication"/>
|
||||
<relationship name="muted" toMany="YES" deletionRule="Nullify" destinationEntity="Status" inverseName="mutedBy" inverseEntity="Status"/>
|
||||
<relationship name="muting" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="mutingBy" inverseEntity="MastodonUser"/>
|
||||
<relationship name="mutingBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="muting" inverseEntity="MastodonUser"/>
|
||||
<relationship name="notifications" toMany="YES" deletionRule="Nullify" destinationEntity="Notification" inverseName="account" inverseEntity="Notification"/>
|
||||
<relationship name="pinnedStatus" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="pinnedBy" inverseEntity="Status"/>
|
||||
<relationship name="privateNotes" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="PrivateNote" inverseName="to" inverseEntity="PrivateNote"/>
|
||||
<relationship name="privateNotesTo" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="PrivateNote" inverseName="from" inverseEntity="PrivateNote"/>
|
||||
<relationship name="reblogged" toMany="YES" deletionRule="Nullify" destinationEntity="Status" inverseName="rebloggedBy" inverseEntity="Status"/>
|
||||
<relationship name="searchHistories" toMany="YES" deletionRule="Nullify" destinationEntity="SearchHistory" inverseName="account" inverseEntity="SearchHistory"/>
|
||||
<relationship name="showingReblogs" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="showingReblogsBy" inverseEntity="MastodonUser"/>
|
||||
<relationship name="showingReblogsBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="showingReblogs" inverseEntity="MastodonUser"/>
|
||||
<relationship name="statuses" toMany="YES" deletionRule="Nullify" destinationEntity="Status" inverseName="author" inverseEntity="Status"/>
|
||||
<relationship name="votePollOptions" toMany="YES" deletionRule="Nullify" destinationEntity="PollOption" inverseName="votedBy" inverseEntity="PollOption"/>
|
||||
<relationship name="votePolls" toMany="YES" deletionRule="Nullify" destinationEntity="Poll" inverseName="votedBy" inverseEntity="Poll"/>
|
||||
</entity>
|
||||
<entity name="Notification" representedClassName="CoreDataStack.Notification" syncable="YES">
|
||||
<attribute name="createAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="domain" attributeType="String"/>
|
||||
<attribute name="followRequestState" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="id" attributeType="String"/>
|
||||
<attribute name="transientFollowRequestState" optional="YES" transient="YES" attributeType="Binary"/>
|
||||
<attribute name="typeRaw" attributeType="String"/>
|
||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="userID" attributeType="String"/>
|
||||
<relationship name="account" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="notifications" inverseEntity="MastodonUser"/>
|
||||
<relationship name="feeds" toMany="YES" deletionRule="Cascade" destinationEntity="Feed" inverseName="notification" inverseEntity="Feed"/>
|
||||
<relationship name="status" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="notifications" inverseEntity="Status"/>
|
||||
</entity>
|
||||
<entity name="Poll" representedClassName="CoreDataStack.Poll" syncable="YES">
|
||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="domain" attributeType="String" defaultValueString=""/>
|
||||
<attribute name="expired" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="expiresAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="id" attributeType="String"/>
|
||||
<attribute name="isVoting" transient="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="multiple" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="votersCount" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="votesCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="options" toMany="YES" deletionRule="Cascade" destinationEntity="PollOption" inverseName="poll" inverseEntity="PollOption"/>
|
||||
<relationship name="status" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="poll" inverseEntity="Status"/>
|
||||
<relationship name="votedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="votePolls" inverseEntity="MastodonUser"/>
|
||||
</entity>
|
||||
<entity name="PollOption" representedClassName="CoreDataStack.PollOption" syncable="YES">
|
||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="index" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="isSelected" transient="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="title" attributeType="String"/>
|
||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="votesCount" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="poll" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Poll" inverseName="options" inverseEntity="Poll"/>
|
||||
<relationship name="votedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="votePollOptions" inverseEntity="MastodonUser"/>
|
||||
</entity>
|
||||
<entity name="PrivateNote" representedClassName="CoreDataStack.PrivateNote" syncable="YES">
|
||||
<attribute name="note" optional="YES" attributeType="String"/>
|
||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="from" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="privateNotesTo" inverseEntity="MastodonUser"/>
|
||||
<relationship name="to" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="privateNotes" inverseEntity="MastodonUser"/>
|
||||
</entity>
|
||||
<entity name="SearchHistory" representedClassName="CoreDataStack.SearchHistory" syncable="YES">
|
||||
<attribute name="createAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="domain" attributeType="String" defaultValueString=""/>
|
||||
<attribute name="identifier" attributeType="UUID" usesScalarValueType="NO"/>
|
||||
<attribute name="updatedAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="userID" attributeType="String" defaultValueString=""/>
|
||||
<relationship name="account" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="searchHistories" inverseEntity="MastodonUser"/>
|
||||
<relationship name="hashtag" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Tag" inverseName="searchHistories" inverseEntity="Tag"/>
|
||||
<relationship name="status" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="searchHistories" inverseEntity="Status"/>
|
||||
</entity>
|
||||
<entity name="Setting" representedClassName="CoreDataStack.Setting" syncable="YES">
|
||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="domain" attributeType="String"/>
|
||||
<attribute name="rawRecentLanguages" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="userID" attributeType="String"/>
|
||||
<relationship name="subscriptions" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Subscription" inverseName="setting" inverseEntity="Subscription"/>
|
||||
</entity>
|
||||
<entity name="Status" representedClassName="CoreDataStack.Status" syncable="YES">
|
||||
<attribute name="attachments" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="content" attributeType="String"/>
|
||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="deletedAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="domain" attributeType="String"/>
|
||||
<attribute name="editedAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="emojis" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="favouritesCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" attributeType="String"/>
|
||||
<attribute name="identifier" attributeType="String"/>
|
||||
<attribute name="inReplyToAccountID" optional="YES" attributeType="String"/>
|
||||
<attribute name="inReplyToID" optional="YES" attributeType="String"/>
|
||||
<attribute name="isSensitiveToggled" transient="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="language" optional="YES" attributeType="String"/>
|
||||
<attribute name="mentions" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="reblogsCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="repliesCount" optional="YES" attributeType="Integer 64" usesScalarValueType="NO"/>
|
||||
<attribute name="revealedAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="sensitive" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="spoilerText" optional="YES" attributeType="String"/>
|
||||
<attribute name="text" optional="YES" attributeType="String"/>
|
||||
<attribute name="translatedContent" optional="YES" transient="YES" attributeType="Transformable"/>
|
||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="uri" attributeType="String"/>
|
||||
<attribute name="url" optional="YES" attributeType="String"/>
|
||||
<attribute name="visibilityRaw" optional="YES" attributeType="String" elementID="visibility"/>
|
||||
<relationship name="application" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Application" inverseName="status" inverseEntity="Application"/>
|
||||
<relationship name="author" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="statuses" inverseEntity="MastodonUser"/>
|
||||
<relationship name="bookmarkedBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="bookmarked" inverseEntity="MastodonUser"/>
|
||||
<relationship name="card" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Card" inverseName="status" inverseEntity="Card"/>
|
||||
<relationship name="editHistory" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="StatusEdit" inverseName="status" inverseEntity="StatusEdit"/>
|
||||
<relationship name="favouritedBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="favourite" inverseEntity="MastodonUser"/>
|
||||
<relationship name="feeds" toMany="YES" deletionRule="Cascade" destinationEntity="Feed" inverseName="status" inverseEntity="Feed"/>
|
||||
<relationship name="mutedBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="muted" inverseEntity="MastodonUser"/>
|
||||
<relationship name="notifications" toMany="YES" deletionRule="Cascade" destinationEntity="Notification" inverseName="status" inverseEntity="Notification"/>
|
||||
<relationship name="pinnedBy" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="pinnedStatus" inverseEntity="MastodonUser"/>
|
||||
<relationship name="poll" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="Poll" inverseName="status" inverseEntity="Poll"/>
|
||||
<relationship name="reblog" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="reblogFrom" inverseEntity="Status"/>
|
||||
<relationship name="reblogFrom" toMany="YES" deletionRule="Cascade" destinationEntity="Status" inverseName="reblog" inverseEntity="Status"/>
|
||||
<relationship name="rebloggedBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="reblogged" inverseEntity="MastodonUser"/>
|
||||
<relationship name="replyFrom" toMany="YES" deletionRule="Nullify" destinationEntity="Status" inverseName="replyTo" inverseEntity="Status"/>
|
||||
<relationship name="replyTo" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="replyFrom" inverseEntity="Status"/>
|
||||
<relationship name="searchHistories" toMany="YES" deletionRule="Cascade" destinationEntity="SearchHistory" inverseName="status" inverseEntity="SearchHistory"/>
|
||||
</entity>
|
||||
<entity name="StatusEdit" representedClassName="CoreDataStack.StatusEdit" syncable="YES">
|
||||
<attribute name="attachments" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="content" optional="YES" attributeType="String"/>
|
||||
<attribute name="createdAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="emojis" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="poll" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="sensitive" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="spoilerText" optional="YES" attributeType="String"/>
|
||||
<relationship name="author" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser"/>
|
||||
<relationship name="status" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="editHistory" inverseEntity="Status"/>
|
||||
</entity>
|
||||
<entity name="Subscription" representedClassName="CoreDataStack.Subscription" syncable="YES">
|
||||
<attribute name="activedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="endpoint" optional="YES" attributeType="String"/>
|
||||
<attribute name="id" optional="YES" attributeType="String"/>
|
||||
<attribute name="policyRaw" attributeType="String"/>
|
||||
<attribute name="serverKey" optional="YES" attributeType="String"/>
|
||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="userToken" optional="YES" attributeType="String"/>
|
||||
<relationship name="alert" maxCount="1" deletionRule="Cascade" destinationEntity="SubscriptionAlerts" inverseName="subscription" inverseEntity="SubscriptionAlerts"/>
|
||||
<relationship name="setting" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Setting" inverseName="subscriptions" inverseEntity="Setting"/>
|
||||
</entity>
|
||||
<entity name="SubscriptionAlerts" representedClassName="CoreDataStack.SubscriptionAlerts" syncable="YES">
|
||||
<attribute name="createdAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="favouriteRaw" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="followRaw" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="followRequestRaw" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="mentionRaw" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="pollRaw" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="reblogRaw" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="updatedAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="subscription" maxCount="1" deletionRule="Nullify" destinationEntity="Subscription" inverseName="alert" inverseEntity="Subscription"/>
|
||||
</entity>
|
||||
<entity name="Tag" representedClassName="CoreDataStack.Tag" syncable="YES">
|
||||
<attribute name="createAt" attributeType="Date" defaultDateTimeInterval="631123200" usesScalarValueType="NO"/>
|
||||
<attribute name="domain" attributeType="String" defaultValueString=""/>
|
||||
<attribute name="following" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="histories" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="identifier" attributeType="UUID" usesScalarValueType="NO"/>
|
||||
<attribute name="name" attributeType="String"/>
|
||||
<attribute name="updatedAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="url" attributeType="String"/>
|
||||
<relationship name="followedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="followedTags" inverseEntity="MastodonUser"/>
|
||||
<relationship name="searchHistories" toMany="YES" deletionRule="Nullify" destinationEntity="SearchHistory" inverseName="hashtag" inverseEntity="SearchHistory"/>
|
||||
</entity>
|
||||
</model>
|
@ -13,12 +13,6 @@ public final class Setting: NSManagedObject {
|
||||
@NSManaged public var domain: String
|
||||
@NSManaged public var userID: String
|
||||
|
||||
// @NSManaged public var appearanceRaw: String
|
||||
@NSManaged public var preferredTrueBlackDarkMode: Bool
|
||||
@NSManaged public var preferredStaticAvatar: Bool
|
||||
@NSManaged public var preferredStaticEmoji: Bool
|
||||
@NSManaged public var preferredUsingDefaultBrowser: Bool
|
||||
|
||||
@NSManaged public private(set) var createdAt: Date
|
||||
@NSManaged public private(set) var updatedAt: Date
|
||||
|
||||
@ -54,62 +48,27 @@ extension Setting {
|
||||
property: Property
|
||||
) -> Setting {
|
||||
let setting: Setting = context.insertObject()
|
||||
// setting.appearanceRaw = property.appearanceRaw
|
||||
setting.domain = property.domain
|
||||
setting.userID = property.userID
|
||||
return setting
|
||||
}
|
||||
|
||||
// public func update(appearanceRaw: String) {
|
||||
// guard appearanceRaw != self.appearanceRaw else { return }
|
||||
// self.appearanceRaw = appearanceRaw
|
||||
// didUpdate(at: Date())
|
||||
// }
|
||||
|
||||
public func update(preferredTrueBlackDarkMode: Bool) {
|
||||
guard preferredTrueBlackDarkMode != self.preferredTrueBlackDarkMode else { return }
|
||||
self.preferredTrueBlackDarkMode = preferredTrueBlackDarkMode
|
||||
didUpdate(at: Date())
|
||||
}
|
||||
|
||||
public func update(preferredStaticAvatar: Bool) {
|
||||
guard preferredStaticAvatar != self.preferredStaticAvatar else { return }
|
||||
self.preferredStaticAvatar = preferredStaticAvatar
|
||||
didUpdate(at: Date())
|
||||
}
|
||||
|
||||
public func update(preferredStaticEmoji: Bool) {
|
||||
guard preferredStaticEmoji != self.preferredStaticEmoji else { return }
|
||||
self.preferredStaticEmoji = preferredStaticEmoji
|
||||
didUpdate(at: Date())
|
||||
}
|
||||
|
||||
public func update(preferredUsingDefaultBrowser: Bool) {
|
||||
guard preferredUsingDefaultBrowser != self.preferredUsingDefaultBrowser else { return }
|
||||
self.preferredUsingDefaultBrowser = preferredUsingDefaultBrowser
|
||||
didUpdate(at: Date())
|
||||
}
|
||||
|
||||
public func didUpdate(at networkDate: Date) {
|
||||
self.updatedAt = networkDate
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Setting {
|
||||
public struct Property {
|
||||
public let domain: String
|
||||
public let userID: String
|
||||
// public let appearanceRaw: String
|
||||
|
||||
public init(
|
||||
domain: String,
|
||||
userID: String
|
||||
// appearanceRaw: String
|
||||
) {
|
||||
self.domain = domain
|
||||
self.userID = userID
|
||||
// self.appearanceRaw = appearanceRaw
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -127,5 +86,13 @@ extension Setting {
|
||||
#keyPath(Setting.userID), userID
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension Setting {
|
||||
public var activeSubscription: Subscription? {
|
||||
return (subscriptions ?? Set())
|
||||
.sorted(by: { $0.activedAt > $1.activedAt })
|
||||
.first
|
||||
}
|
||||
}
|
||||
|
@ -1,38 +0,0 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.969",
|
||||
"green" : "0.949",
|
||||
"red" : "0.949"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.129",
|
||||
"green" : "0.106",
|
||||
"red" : "0.098"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
@ -200,9 +200,6 @@ public enum Asset {
|
||||
public static let background = ColorAsset(name: "Scene/Report/background")
|
||||
public static let reportBanner = ColorAsset(name: "Scene/Report/report.banner")
|
||||
}
|
||||
public enum Setting {
|
||||
public static let background = ColorAsset(name: "Scene/Setting/background")
|
||||
}
|
||||
public enum Sidebar {
|
||||
public static let logo = ImageAsset(name: "Scene/Sidebar/logo")
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ public class AppContext: ObservableObject {
|
||||
public let apiService: APIService
|
||||
public let authenticationService: AuthenticationService
|
||||
public let emojiService: EmojiService
|
||||
// public let statusPublishService = StatusPublishService()
|
||||
|
||||
public let publisherService: PublisherService
|
||||
public let notificationService: NotificationService
|
||||
public let settingService: SettingService
|
||||
@ -126,70 +126,51 @@ extension AppContext {
|
||||
return formatter
|
||||
}()
|
||||
|
||||
private static let purgeCacheWorkingQueue = DispatchQueue(label: "org.joinmastodon.app.AppContext.purgeCacheWorkingQueue")
|
||||
|
||||
public func purgeCache() -> AnyPublisher<ByteCount, Never> {
|
||||
Publishers.MergeMany([
|
||||
AppContext.purgeAlamofireImageCache(),
|
||||
AppContext.purgeTemporaryDirectory(),
|
||||
])
|
||||
.reduce(0, +)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
private static func purgeAlamofireImageCache() -> AnyPublisher<ByteCount, Never> {
|
||||
Future<ByteCount, Never> { promise in
|
||||
AppContext.purgeCacheWorkingQueue.async {
|
||||
// clean image cache for AlamofireImage
|
||||
let diskBytes = ImageDownloader.defaultURLCache().currentDiskUsage
|
||||
ImageDownloader.defaultURLCache().removeAllCachedResponses()
|
||||
let currentDiskBytes = ImageDownloader.defaultURLCache().currentDiskUsage
|
||||
let purgedDiskBytes = max(0, diskBytes - currentDiskBytes)
|
||||
promise(.success(purgedDiskBytes))
|
||||
public func purgeCache() {
|
||||
ImageDownloader.defaultURLCache().removeAllCachedResponses()
|
||||
|
||||
let fileManager = FileManager.default
|
||||
let temporaryDirectoryURL = fileManager.temporaryDirectory
|
||||
let fileKeys: [URLResourceKey] = [.fileSizeKey, .isDirectoryKey]
|
||||
|
||||
if let directoryEnumerator = fileManager.enumerator(
|
||||
at: temporaryDirectoryURL,
|
||||
includingPropertiesForKeys: fileKeys,
|
||||
options: .skipsHiddenFiles) {
|
||||
for case let fileURL as URL in directoryEnumerator {
|
||||
guard let resourceValues = try? fileURL.resourceValues(forKeys: Set(fileKeys)),
|
||||
resourceValues.isDirectory == false else {
|
||||
continue
|
||||
}
|
||||
|
||||
try? fileManager.removeItem(at: fileURL)
|
||||
}
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
private static func purgeTemporaryDirectory() -> AnyPublisher<ByteCount, Never> {
|
||||
Future<ByteCount, Never> { promise in
|
||||
AppContext.purgeCacheWorkingQueue.async {
|
||||
let fileManager = FileManager.default
|
||||
let temporaryDirectoryURL = fileManager.temporaryDirectory
|
||||
|
||||
let resourceKeys = Set<URLResourceKey>([.fileSizeKey, .isDirectoryKey])
|
||||
guard let directoryEnumerator = fileManager.enumerator(
|
||||
at: temporaryDirectoryURL,
|
||||
includingPropertiesForKeys: Array(resourceKeys),
|
||||
options: .skipsHiddenFiles
|
||||
) else {
|
||||
promise(.success(0))
|
||||
return
|
||||
}
|
||||
|
||||
var fileURLs: [URL] = []
|
||||
var totalFileSizeInBytes = 0
|
||||
for case let fileURL as URL in directoryEnumerator {
|
||||
guard let resourceValues = try? fileURL.resourceValues(forKeys: resourceKeys),
|
||||
let isDirectory = resourceValues.isDirectory else {
|
||||
continue
|
||||
}
|
||||
|
||||
guard !isDirectory else {
|
||||
continue
|
||||
}
|
||||
fileURLs.append(fileURL)
|
||||
totalFileSizeInBytes += resourceValues.fileSize ?? 0
|
||||
}
|
||||
|
||||
for fileURL in fileURLs {
|
||||
try? fileManager.removeItem(at: fileURL)
|
||||
}
|
||||
|
||||
promise(.success(totalFileSizeInBytes))
|
||||
}
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
// In Bytes
|
||||
public func currentDiskUsage() -> Int {
|
||||
let alamoFireDiskBytes = ImageDownloader.defaultURLCache().currentDiskUsage
|
||||
|
||||
var tempFilesDiskBytes = 0
|
||||
let fileManager = FileManager.default
|
||||
let temporaryDirectoryURL = fileManager.temporaryDirectory
|
||||
let fileKeys: [URLResourceKey] = [.fileSizeKey, .isDirectoryKey]
|
||||
|
||||
if let directoryEnumerator = fileManager.enumerator(
|
||||
at: temporaryDirectoryURL,
|
||||
includingPropertiesForKeys: fileKeys,
|
||||
options: .skipsHiddenFiles) {
|
||||
for case let fileURL as URL in directoryEnumerator {
|
||||
guard let resourceValues = try? fileURL.resourceValues(forKeys: Set(fileKeys)),
|
||||
resourceValues.isDirectory == false else {
|
||||
continue
|
||||
}
|
||||
|
||||
tempFilesDiskBytes += resourceValues.fileSize ?? 0
|
||||
}
|
||||
}
|
||||
|
||||
return alamoFireDiskBytes + tempFilesDiskBytes
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +0,0 @@
|
||||
//
|
||||
// Setting.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-4-25.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
extension Setting {
|
||||
|
||||
// var appearance: SettingsItem.AppearanceMode {
|
||||
// return SettingsItem.AppearanceMode(rawValue: appearanceRaw) ?? .automatic
|
||||
// }
|
||||
|
||||
public var activeSubscription: Subscription? {
|
||||
return (subscriptions ?? Set())
|
||||
.sorted(by: { $0.activedAt > $1.activedAt })
|
||||
.first
|
||||
}
|
||||
|
||||
}
|
@ -13,8 +13,8 @@ public typealias NotificationSubscription = Subscription
|
||||
|
||||
extension Subscription {
|
||||
|
||||
public var policy: Mastodon.API.Subscriptions.Policy {
|
||||
return Mastodon.API.Subscriptions.Policy(rawValue: policyRaw) ?? .all
|
||||
public var policy: Mastodon.API.Subscriptions.Policy? {
|
||||
return Mastodon.API.Subscriptions.Policy(rawValue: policyRaw)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import MastodonSDK
|
||||
|
||||
extension APIService {
|
||||
|
||||
func createSubscription(
|
||||
public func createSubscription(
|
||||
subscriptionObjectID: NSManagedObjectID,
|
||||
query: Mastodon.API.Subscriptions.CreateSubscriptionQuery,
|
||||
mastodonAuthenticationBox: MastodonAuthenticationBox
|
||||
@ -34,6 +34,12 @@ extension APIService {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
subscription.alert.update(favourite: response.value.alerts.favourite)
|
||||
subscription.alert.update(reblog: response.value.alerts.reblog)
|
||||
subscription.alert.update(follow: response.value.alerts.follow)
|
||||
subscription.alert.update(mention: response.value.alerts.mention)
|
||||
|
||||
subscription.endpoint = response.value.endpoint
|
||||
subscription.serverKey = response.value.serverKey
|
||||
subscription.userToken = authorization.accessToken
|
||||
|
@ -127,7 +127,7 @@ extension NotificationService {
|
||||
|
||||
extension NotificationService {
|
||||
|
||||
func dequeueNotificationViewModel(
|
||||
public func dequeueNotificationViewModel(
|
||||
mastodonAuthenticationBox: MastodonAuthenticationBox
|
||||
) -> NotificationViewModel? {
|
||||
var _notificationSubscription: NotificationViewModel?
|
||||
@ -281,7 +281,7 @@ extension NotificationService {
|
||||
}
|
||||
|
||||
extension NotificationService.NotificationViewModel {
|
||||
func createSubscribeQuery(
|
||||
public func createSubscribeQuery(
|
||||
deviceToken: Data,
|
||||
queryData: Mastodon.API.Subscriptions.QueryData,
|
||||
mastodonAuthenticationBox: MastodonAuthenticationBox
|
||||
|
@ -17,8 +17,6 @@ public final class SettingService {
|
||||
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
|
||||
private var currentSettingUpdateSubscription: AnyCancellable?
|
||||
|
||||
// input
|
||||
weak var apiService: APIService?
|
||||
weak var authenticationService: AuthenticationService?
|
||||
@ -85,29 +83,6 @@ public final class SettingService {
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
// observe current setting
|
||||
currentSetting
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] setting in
|
||||
guard let self = self else { return }
|
||||
guard let setting = setting else {
|
||||
self.currentSettingUpdateSubscription = nil
|
||||
return
|
||||
}
|
||||
|
||||
SettingService.updatePreference(setting: setting)
|
||||
self.currentSettingUpdateSubscription = ManagedObjectObserver.observe(object: setting)
|
||||
.sink(receiveCompletion: { _ in
|
||||
// do nothing
|
||||
}, receiveValue: { change in
|
||||
guard case .update(let object) = change.changeType,
|
||||
let setting = object as? Setting else { return }
|
||||
|
||||
SettingService.updatePreference(setting: setting)
|
||||
})
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
Publishers.CombineLatest3(
|
||||
notificationService.deviceToken,
|
||||
currentSetting.eraseToAnyPublisher(),
|
||||
@ -171,26 +146,4 @@ extension SettingService {
|
||||
alertController.addAction(cancelAction)
|
||||
return alertController
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension SettingService {
|
||||
|
||||
static func updatePreference(setting: Setting) {
|
||||
// set avatar mode
|
||||
if UserDefaults.shared.preferredStaticAvatar != setting.preferredStaticAvatar {
|
||||
UserDefaults.shared.preferredStaticAvatar = setting.preferredStaticAvatar
|
||||
}
|
||||
|
||||
// set emoji mode
|
||||
if UserDefaults.shared.preferredStaticEmoji != setting.preferredStaticEmoji {
|
||||
UserDefaults.shared.preferredStaticEmoji = setting.preferredStaticEmoji
|
||||
}
|
||||
|
||||
// set browser
|
||||
if UserDefaults.shared.preferredUsingDefaultBrowser != setting.preferredUsingDefaultBrowser {
|
||||
UserDefaults.shared.preferredUsingDefaultBrowser = setting.preferredUsingDefaultBrowser
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,28 +0,0 @@
|
||||
//
|
||||
// StatusPublishService.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-3-26.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Intents
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
import UIKit
|
||||
|
||||
public final class StatusPublishService {
|
||||
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
|
||||
let workingQueue = DispatchQueue(label: "org.joinmastodon.app.StatusPublishService.working-queue")
|
||||
|
||||
// input
|
||||
// var viewModels = CurrentValueSubject<[ComposeViewModel], Never>([]) // use strong reference to retain the view models
|
||||
|
||||
// output
|
||||
let composeViewModelDidUpdatePublisher = PassthroughSubject<Void, Never>()
|
||||
// let latestPublishingComposeViewModel = CurrentValueSubject<ComposeViewModel?, Never>(nil)
|
||||
}
|
@ -1416,18 +1416,104 @@ public enum L10n {
|
||||
}
|
||||
}
|
||||
public enum Settings {
|
||||
/// Settings
|
||||
public static let title = L10n.tr("Localizable", "Scene.Settings.Title", fallback: "Settings")
|
||||
public enum AboutMastodon {
|
||||
/// Clear Media Storage
|
||||
public static let cleaerMediaStorage = L10n.tr("Localizable", "Scene.Settings.AboutMastodon.CleaerMediaStorage", fallback: "Clear Media Storage")
|
||||
/// Contribute to Mastodon
|
||||
public static let contributeToMastodon = L10n.tr("Localizable", "Scene.Settings.AboutMastodon.ContributeToMastodon", fallback: "Contribute to Mastodon")
|
||||
/// Even More Settings
|
||||
public static let moreSettings = L10n.tr("Localizable", "Scene.Settings.AboutMastodon.MoreSettings", fallback: "Even More Settings")
|
||||
/// Privacy Policy
|
||||
public static let privacyPolicy = L10n.tr("Localizable", "Scene.Settings.AboutMastodon.PrivacyPolicy", fallback: "Privacy Policy")
|
||||
/// About
|
||||
public static let title = L10n.tr("Localizable", "Scene.Settings.AboutMastodon.Title", fallback: "About")
|
||||
}
|
||||
public enum Footer {
|
||||
/// Mastodon is open source software. You can report issues on GitHub at %@ (%@)
|
||||
public static func mastodonDescription(_ p1: Any, _ p2: Any) -> String {
|
||||
return L10n.tr("Localizable", "Scene.Settings.Footer.MastodonDescription", String(describing: p1), String(describing: p2), fallback: "Mastodon is open source software. You can report issues on GitHub at %@ (%@)")
|
||||
}
|
||||
}
|
||||
public enum General {
|
||||
/// General
|
||||
public static let title = L10n.tr("Localizable", "Scene.Settings.General.Title", fallback: "General")
|
||||
public enum Appearance {
|
||||
/// Dark
|
||||
public static let dark = L10n.tr("Localizable", "Scene.Settings.General.Appearance.Dark", fallback: "Dark")
|
||||
/// Light
|
||||
public static let light = L10n.tr("Localizable", "Scene.Settings.General.Appearance.Light", fallback: "Light")
|
||||
/// Appearance
|
||||
public static let sectionTitle = L10n.tr("Localizable", "Scene.Settings.General.Appearance.SectionTitle", fallback: "Appearance")
|
||||
/// Use Device Appearance
|
||||
public static let system = L10n.tr("Localizable", "Scene.Settings.General.Appearance.System", fallback: "Use Device Appearance")
|
||||
}
|
||||
public enum Design {
|
||||
/// Design
|
||||
public static let sectionTitle = L10n.tr("Localizable", "Scene.Settings.General.Design.SectionTitle", fallback: "Design")
|
||||
/// Play Animated Avatars and Emoji
|
||||
public static let showAnimations = L10n.tr("Localizable", "Scene.Settings.General.Design.ShowAnimations", fallback: "Play Animated Avatars and Emoji")
|
||||
}
|
||||
public enum Links {
|
||||
/// Open in Browser
|
||||
public static let openInBrowser = L10n.tr("Localizable", "Scene.Settings.General.Links.OpenInBrowser", fallback: "Open in Browser")
|
||||
/// Open in Mastodon
|
||||
public static let openInMastodon = L10n.tr("Localizable", "Scene.Settings.General.Links.OpenInMastodon", fallback: "Open in Mastodon")
|
||||
/// Links
|
||||
public static let sectionTitle = L10n.tr("Localizable", "Scene.Settings.General.Links.SectionTitle", fallback: "Links")
|
||||
}
|
||||
}
|
||||
public enum Keyboard {
|
||||
/// Close Settings Window
|
||||
public static let closeSettingsWindow = L10n.tr("Localizable", "Scene.Settings.Keyboard.CloseSettingsWindow", fallback: "Close Settings Window")
|
||||
}
|
||||
public enum Notifications {
|
||||
/// Notifications
|
||||
public static let title = L10n.tr("Localizable", "Scene.Settings.Notifications.Title", fallback: "Notifications")
|
||||
public enum Alert {
|
||||
/// Boosts
|
||||
public static let boosts = L10n.tr("Localizable", "Scene.Settings.Notifications.Alert.Boosts", fallback: "Boosts")
|
||||
/// Favorites
|
||||
public static let favorites = L10n.tr("Localizable", "Scene.Settings.Notifications.Alert.Favorites", fallback: "Favorites")
|
||||
/// Mentions & Replies
|
||||
public static let mentionsAndReplies = L10n.tr("Localizable", "Scene.Settings.Notifications.Alert.MentionsAndReplies", fallback: "Mentions & Replies")
|
||||
/// New Followers
|
||||
public static let newFollowers = L10n.tr("Localizable", "Scene.Settings.Notifications.Alert.NewFollowers", fallback: "New Followers")
|
||||
}
|
||||
public enum Disabled {
|
||||
/// Go to Notification Settings
|
||||
public static let goToSettings = L10n.tr("Localizable", "Scene.Settings.Notifications.Disabled.GoToSettings", fallback: "Go to Notification Settings")
|
||||
/// Turn on notifications from your device settings to see updates on your lock screen.
|
||||
public static let notificationHint = L10n.tr("Localizable", "Scene.Settings.Notifications.Disabled.NotificationHint", fallback: "Turn on notifications from your device settings to see updates on your lock screen.")
|
||||
}
|
||||
public enum Policy {
|
||||
/// Anyone
|
||||
public static let anyone = L10n.tr("Localizable", "Scene.Settings.Notifications.Policy.Anyone", fallback: "Anyone")
|
||||
/// People you follow
|
||||
public static let follow = L10n.tr("Localizable", "Scene.Settings.Notifications.Policy.Follow", fallback: "People you follow")
|
||||
/// People who follow you
|
||||
public static let followers = L10n.tr("Localizable", "Scene.Settings.Notifications.Policy.Followers", fallback: "People who follow you")
|
||||
/// No one
|
||||
public static let noone = L10n.tr("Localizable", "Scene.Settings.Notifications.Policy.Noone", fallback: "No one")
|
||||
/// Get Notifications from
|
||||
public static let title = L10n.tr("Localizable", "Scene.Settings.Notifications.Policy.Title", fallback: "Get Notifications from")
|
||||
}
|
||||
}
|
||||
public enum Overview {
|
||||
/// About Mastodon
|
||||
public static let aboutMastodon = L10n.tr("Localizable", "Scene.Settings.Overview.AboutMastodon", fallback: "About Mastodon")
|
||||
/// General
|
||||
public static let general = L10n.tr("Localizable", "Scene.Settings.Overview.General", fallback: "General")
|
||||
/// Logout %@
|
||||
public static func logout(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "Scene.Settings.Overview.Logout", String(describing: p1), fallback: "Logout %@")
|
||||
}
|
||||
/// Notifications
|
||||
public static let notifications = L10n.tr("Localizable", "Scene.Settings.Overview.Notifications", fallback: "Notifications")
|
||||
/// Support Mastodon
|
||||
public static let supportMastodon = L10n.tr("Localizable", "Scene.Settings.Overview.SupportMastodon", fallback: "Support Mastodon")
|
||||
/// Settings
|
||||
public static let title = L10n.tr("Localizable", "Scene.Settings.Overview.Title", fallback: "Settings")
|
||||
}
|
||||
public enum Section {
|
||||
public enum Appearance {
|
||||
/// Automatic
|
||||
|
@ -522,7 +522,44 @@ uploaded to Mastodon.";
|
||||
"Scene.Settings.Section.SpicyZone.Clear" = "Clear Media Cache";
|
||||
"Scene.Settings.Section.SpicyZone.Signout" = "Sign Out";
|
||||
"Scene.Settings.Section.SpicyZone.Title" = "The Spicy Zone";
|
||||
"Scene.Settings.Title" = "Settings";
|
||||
|
||||
"Scene.Settings.Overview.Title" = "Settings";
|
||||
"Scene.Settings.Overview.General" = "General";
|
||||
"Scene.Settings.Overview.Notifications" = "Notifications";
|
||||
"Scene.Settings.Overview.SupportMastodon" = "Support Mastodon";
|
||||
"Scene.Settings.Overview.AboutMastodon" = "About Mastodon";
|
||||
"Scene.Settings.Overview.Logout" = "Logout %@";
|
||||
|
||||
"Scene.Settings.AboutMastodon.Title" = "About";
|
||||
"Scene.Settings.AboutMastodon.MoreSettings" = "Even More Settings";
|
||||
"Scene.Settings.AboutMastodon.ContributeToMastodon" = "Contribute to Mastodon";
|
||||
"Scene.Settings.AboutMastodon.PrivacyPolicy" = "Privacy Policy";
|
||||
"Scene.Settings.AboutMastodon.CleaerMediaStorage" = "Clear Media Storage";
|
||||
|
||||
"Scene.Settings.General.Title" = "General";
|
||||
"Scene.Settings.General.Appearance.SectionTitle" = "Appearance";
|
||||
"Scene.Settings.General.Appearance.Dark" = "Dark";
|
||||
"Scene.Settings.General.Appearance.Light" = "Light";
|
||||
"Scene.Settings.General.Appearance.System" = "Use Device Appearance";
|
||||
"Scene.Settings.General.Design.SectionTitle" = "Design";
|
||||
"Scene.Settings.General.Design.ShowAnimations" = "Play Animated Avatars and Emoji";
|
||||
"Scene.Settings.General.Links.SectionTitle" = "Links";
|
||||
"Scene.Settings.General.Links.OpenInMastodon" = "Open in Mastodon";
|
||||
"Scene.Settings.General.Links.OpenInBrowser" = "Open in Browser";
|
||||
|
||||
"Scene.Settings.Notifications.Title" = "Notifications";
|
||||
"Scene.Settings.Notifications.Policy.Title" = "Get Notifications from";
|
||||
"Scene.Settings.Notifications.Policy.Anyone" = "Anyone";
|
||||
"Scene.Settings.Notifications.Policy.Followers" = "People who follow you";
|
||||
"Scene.Settings.Notifications.Policy.Follow" = "People you follow";
|
||||
"Scene.Settings.Notifications.Policy.Noone" = "No one";
|
||||
"Scene.Settings.Notifications.Alert.MentionsAndReplies" = "Mentions & Replies";
|
||||
"Scene.Settings.Notifications.Alert.Boosts" = "Boosts";
|
||||
"Scene.Settings.Notifications.Alert.Favorites" = "Favorites";
|
||||
"Scene.Settings.Notifications.Alert.NewFollowers" = "New Followers";
|
||||
"Scene.Settings.Notifications.Disabled.NotificationHint" = "Turn on notifications from your device settings to see updates on your lock screen.";
|
||||
"Scene.Settings.Notifications.Disabled.GoToSettings" = "Go to Notification Settings";
|
||||
|
||||
"Scene.SuggestionAccount.FollowAll" = "Follow all";
|
||||
"Scene.SuggestionAccount.Title" = "Popular on Mastodon";
|
||||
"Scene.Thread.BackTitle" = "Post";
|
||||
|
@ -522,7 +522,44 @@ uploaded to Mastodon.";
|
||||
"Scene.Settings.Section.SpicyZone.Clear" = "Clear Media Cache";
|
||||
"Scene.Settings.Section.SpicyZone.Signout" = "Sign Out";
|
||||
"Scene.Settings.Section.SpicyZone.Title" = "The Spicy Zone";
|
||||
"Scene.Settings.Title" = "Settings";
|
||||
|
||||
"Scene.Settings.Overview.Title" = "Settings";
|
||||
"Scene.Settings.Overview.General" = "General";
|
||||
"Scene.Settings.Overview.Notifications" = "Notifications";
|
||||
"Scene.Settings.Overview.SupportMastodon" = "Support Mastodon";
|
||||
"Scene.Settings.Overview.AboutMastodon" = "About Mastodon";
|
||||
"Scene.Settings.Overview.Logout" = "Logout %@";
|
||||
|
||||
"Scene.Settings.AboutMastodon.Title" = "About";
|
||||
"Scene.Settings.AboutMastodon.MoreSettings" = "Even More Settings";
|
||||
"Scene.Settings.AboutMastodon.ContributeToMastodon" = "Contribute to Mastodon";
|
||||
"Scene.Settings.AboutMastodon.PrivacyPolicy" = "Privacy Policy";
|
||||
"Scene.Settings.AboutMastodon.CleaerMediaStorage" = "Clear Media Storage";
|
||||
|
||||
"Scene.Settings.General.Title" = "General";
|
||||
"Scene.Settings.General.Appearance.SectionTitle" = "Appearance";
|
||||
"Scene.Settings.General.Appearance.Dark" = "Dark";
|
||||
"Scene.Settings.General.Appearance.Light" = "Light";
|
||||
"Scene.Settings.General.Appearance.System" = "Use Device Appearance";
|
||||
"Scene.Settings.General.Design.SectionTitle" = "Design";
|
||||
"Scene.Settings.General.Design.ShowAnimations" = "Play Animated Avatars and Emoji";
|
||||
"Scene.Settings.General.Links.SectionTitle" = "Links";
|
||||
"Scene.Settings.General.Links.OpenInMastodon" = "Open in Mastodon";
|
||||
"Scene.Settings.General.Links.OpenInBrowser" = "Open in Browser";
|
||||
|
||||
"Scene.Settings.Notifications.Title" = "Notifications";
|
||||
"Scene.Settings.Notifications.Policy.Title" = "Get Notifications from";
|
||||
"Scene.Settings.Notifications.Policy.Anyone" = "Anyone";
|
||||
"Scene.Settings.Notifications.Policy.Followers" = "People who follow you";
|
||||
"Scene.Settings.Notifications.Policy.Follow" = "People you follow";
|
||||
"Scene.Settings.Notifications.Policy.Noone" = "No one";
|
||||
"Scene.Settings.Notifications.Alert.MentionsAndReplies" = "Mentions & Replies";
|
||||
"Scene.Settings.Notifications.Alert.Boosts" = "Boosts";
|
||||
"Scene.Settings.Notifications.Alert.Favorites" = "Favorites";
|
||||
"Scene.Settings.Notifications.Alert.NewFollowers" = "New Followers";
|
||||
"Scene.Settings.Notifications.Disabled.NotificationHint" = "Turn on notifications from your device settings to see updates on your lock screen.";
|
||||
"Scene.Settings.Notifications.Disabled.GoToSettings" = "Go to Notification Settings";
|
||||
|
||||
"Scene.SuggestionAccount.FollowAll" = "Follow all";
|
||||
"Scene.SuggestionAccount.Title" = "Popular on Mastodon";
|
||||
"Scene.Thread.BackTitle" = "Post";
|
||||
|
@ -95,6 +95,10 @@ extension Mastodon.API {
|
||||
public static func privacyURL(domain: String) -> URL {
|
||||
return URL(string: "\(URL.httpScheme(domain: domain))://" + domain + "/terms")!
|
||||
}
|
||||
|
||||
public static func profileSettingsURL(domain: String) -> URL {
|
||||
return URL(string: "\(URL.httpScheme(domain: domain))://" + domain + "/auth/edit")!
|
||||
}
|
||||
}
|
||||
|
||||
extension Mastodon.API {
|
||||
|
@ -25,7 +25,6 @@ extension MetaLabel {
|
||||
case profileCardFamiliarFollowerFooter
|
||||
case recommendAccountName
|
||||
case titleView
|
||||
case settingTableFooter
|
||||
case autoCompletion
|
||||
case accountListName
|
||||
case accountListUsername
|
||||
@ -111,13 +110,6 @@ extension MetaLabel {
|
||||
font = .systemFont(ofSize: 18, weight: .semibold)
|
||||
textColor = .white
|
||||
|
||||
case .settingTableFooter:
|
||||
font = .preferredFont(forTextStyle: .footnote)
|
||||
textColor = Asset.Colors.Label.secondary.color
|
||||
numberOfLines = 0
|
||||
textContainer.maximumNumberOfLines = 0
|
||||
paragraphStyle.alignment = .center
|
||||
|
||||
case .autoCompletion:
|
||||
font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 17, weight: .semibold), maximumPointSize: 22)
|
||||
textColor = Asset.Colors.Brand.blurple.color
|
||||
|
2
Podfile
2
Podfile
@ -17,8 +17,6 @@ target 'Mastodon' do
|
||||
pod 'Kanna', '~> 5.2.2'
|
||||
pod 'Sourcery', '~> 1.9'
|
||||
|
||||
# DEBUG
|
||||
|
||||
target 'MastodonTests' do
|
||||
inherit! :search_paths
|
||||
# Pods for testing
|
||||
|
10
Podfile.lock
10
Podfile.lock
@ -1,8 +1,8 @@
|
||||
PODS:
|
||||
- Kanna (5.2.7)
|
||||
- Sourcery (1.9.0):
|
||||
- Sourcery/CLI-Only (= 1.9.0)
|
||||
- Sourcery/CLI-Only (1.9.0)
|
||||
- Sourcery (1.9.2):
|
||||
- Sourcery/CLI-Only (= 1.9.2)
|
||||
- Sourcery/CLI-Only (1.9.2)
|
||||
- SwiftGen (6.6.2)
|
||||
- XLPagerTabStrip (9.0.0)
|
||||
|
||||
@ -21,10 +21,10 @@ SPEC REPOS:
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
Kanna: 01cfbddc127f5ff0963692f285fcbc8a9d62d234
|
||||
Sourcery: d7c59d100e55bf59123c8ae7d65a24cf30748979
|
||||
Sourcery: 179539341c2261068528cd15a31837b7238fd901
|
||||
SwiftGen: 1366a7f71aeef49954ca5a63ba4bef6b0f24138c
|
||||
XLPagerTabStrip: 61c57fd61f611ee5f01ff1495ad6fbee8bf496c5
|
||||
|
||||
PODFILE CHECKSUM: 698a840245d400e5a1c93345481965cc16067dc0
|
||||
PODFILE CHECKSUM: 8c962b3cbb4c225f1e57fb2e4ca03d1f22c45e5e
|
||||
|
||||
COCOAPODS: 1.12.1
|
||||
|
Loading…
x
Reference in New Issue
Block a user