├── .github └── workflows │ └── build.yml ├── .gitmodules ├── FeedFilterSettingsViewController.h ├── FeedFilterSettingsViewController.x ├── Makefile ├── Preferences.h ├── RedditFilter.plist ├── Resources ├── Settings.x ├── Tweak.xm ├── control ├── include ├── AppSettingsViewController.h ├── AttributedLabelRegular.h ├── BaseLabel.h ├── BaseTableReusableView.h ├── BaseTableViewCell.h ├── BaseTableViewController.h ├── BaseView.h ├── Carousel.h ├── Comment.h ├── DeprecatedBaseViewController.h ├── ImageLabelContentView.h ├── ImageLabelTableViewCell.h ├── LayoutGuidance.h ├── Post.h ├── ToggleImageTableViewCell.h └── ViewLabelTableViewCell.h └── layout └── Library └── Application Support └── RedditFilter.bundle └── en.lproj └── Localizable.strings /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | workflow_dispatch: 6 | inputs: 7 | create_release: 8 | description: "Build IPA and create draft release" 9 | default: false 10 | type: boolean 11 | ipa_url: 12 | description: "Direct URL to Decrypted IPA file" 13 | type: string 14 | required: false 15 | 16 | jobs: 17 | build: 18 | runs-on: ubuntu-22.04 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | with: 23 | submodules: recursive 24 | 25 | - name: Get package info 26 | id: package_info 27 | run: | 28 | version=$(cat Makefile | grep "PACKAGE_VERSION =" | cut -d' ' -f3) 29 | if [ -z $version ]; then 30 | version=$(cat control | grep "Version:" | cut -d' ' -f2) 31 | fi 32 | echo "id=$(cat control | grep "Package:" | cut -d' ' -f2)" >> $GITHUB_OUTPUT 33 | echo "version=$version" >> $GITHUB_OUTPUT 34 | 35 | - name: Install dependencies 36 | run: | 37 | sudo apt update 38 | sudo apt install -y rsync 39 | 40 | - name: Install theos-jailed dependencies 41 | if: ${{ inputs.create_release }} 42 | run: | 43 | sudo apt update 44 | sudo apt install -y build-essential checkinstall git autoconf automake libtool-bin llvm xmlstarlet 45 | curl -L https://github.com/libimobiledevice/libplist/releases/download/2.6.0/libplist-2.6.0.tar.bz2 | bzip2 -d | tar -x 46 | cd libplist* 47 | ./configure 48 | sudo make install 49 | sudo ldconfig 50 | 51 | - name: Download IPA (Auto) 52 | if: ${{ inputs.create_release && !inputs.ipa_url }} 53 | uses: level3tjg/decryptedappstore-action@main 54 | with: 55 | appstore_url: "https://apps.apple.com/us/app/reddit/id1064216828" 56 | cache: true 57 | path: ${{ github.workspace }}/App.ipa 58 | token: ${{ secrets.DECRYPTEDAPPSTORE_SESSION_TOKEN }} 59 | 60 | - name: Download IPA (Manual) 61 | if: ${{ inputs.create_release && inputs.ipa_url }} 62 | run: | 63 | curl -Lo "${{ github.workspace }}/App.ipa" "${{ inputs.ipa_url }}" 64 | zip -T "${{ github.workspace }}/App.ipa" 65 | 66 | - name: Get IPA Info 67 | if: ${{ inputs.create_release }} 68 | id: ipa_info 69 | run: | 70 | info=$(unzip -p "${{ github.workspace }}/App.ipa" Payload/*.app/Info.plist) 71 | echo "bundle-id=$(echo $info | xmlstarlet sel -t -v "/plist/dict/key[text()=\"CFBundleIdentifier\"]/following-sibling::*[1]/text()")" >> $GITHUB_OUTPUT 72 | echo "version=$(echo $info | xmlstarlet sel -t -v "/plist/dict/key[text()=\"CFBundleShortVersionString\"]/following-sibling::*[1]/text()")" >> $GITHUB_OUTPUT 73 | 74 | - name: Setup theos 75 | uses: level3tjg/theos-action@main 76 | with: 77 | cache: true 78 | cache-dir-theos: ${{ github.workspace }}/theos 79 | cache-dir-sdks: ${{ github.workspace }}/theos/sdks 80 | 81 | - name: Checkout theos-jailed 82 | if: ${{ inputs.create_release }} 83 | uses: actions/checkout@v4 84 | with: 85 | repository: level3tjg/theos-jailed 86 | path: theos-jailed 87 | submodules: recursive 88 | 89 | - name: Install theos-jailed 90 | if: ${{ inputs.create_release }} 91 | run: | 92 | ./theos-jailed/install 93 | 94 | - name: Build rootless deb 95 | run: make package 96 | env: 97 | FINALPACKAGE: ${{ inputs.create_release }} 98 | THEOS_PACKAGE_SCHEME: rootless 99 | 100 | - name: Build rootful deb 101 | run: make clean package 102 | env: 103 | FINALPACKAGE: ${{ inputs.create_release }} 104 | 105 | - name: Build IPA 106 | if: ${{ inputs.create_release }} 107 | run: make package 108 | env: 109 | FINALPACKAGE: 1 110 | SIDELOADED: 1 111 | IPA: ${{ github.workspace }}/App.ipa 112 | APP_VERSION: ${{ steps.ipa_info.outputs.version }} 113 | 114 | - name: Upload artifacts 115 | uses: actions/upload-artifact@v4 116 | with: 117 | name: ${{ steps.package_info.outputs.id }}_${{ steps.package_info.outputs.version }} 118 | path: packages/* 119 | 120 | - name: Create release 121 | if: ${{ inputs.create_release }} 122 | uses: softprops/action-gh-release@v2 123 | with: 124 | draft: true 125 | files: packages/* 126 | tag_name: v${{ steps.ipa_info.outputs.version }}-${{ steps.package_info.outputs.version }} -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "RedditSideloadFix"] 2 | path = RedditSideloadFix 3 | url = https://github.com/level3tjg/RedditSideloadFix 4 | -------------------------------------------------------------------------------- /FeedFilterSettingsViewController.h: -------------------------------------------------------------------------------- 1 | #import "Preferences.h" 2 | #import 3 | #import 4 | #import 5 | #import 6 | #import 7 | 8 | @interface UIView () 9 | @property(nonatomic, readonly, assign) CGFloat frameWidth; 10 | - (void)associatePropertySetter:(SEL)propertySetter 11 | withThemePropertyGetter:(SEL)themePropertyGetter; 12 | @end 13 | 14 | @interface UIImage () 15 | - (UIImage *)imageScaledToSize:(CGSize)size; 16 | @end 17 | 18 | @interface FeedFilterSettingsViewController : BaseTableViewController 19 | @end -------------------------------------------------------------------------------- /FeedFilterSettingsViewController.x: -------------------------------------------------------------------------------- 1 | #import "FeedFilterSettingsViewController.h" 2 | 3 | extern NSBundle *redditFilterBundle; 4 | extern UIImage *iconWithName(NSString *iconName); 5 | extern Class CoreClass(NSString *name); 6 | 7 | #define LOC(x, d) [redditFilterBundle localizedStringForKey:x value:d table:nil] 8 | 9 | %subclass FeedFilterSettingsViewController : BaseTableViewController 10 | %new 11 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 12 | return 1; 13 | } 14 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 15 | switch (section) { 16 | case 0: 17 | return 7; 18 | default: 19 | return 0; 20 | } 21 | } 22 | - (UITableViewCell *)tableView:(UITableView *)tableView 23 | cellForRowAtIndexPath:(NSIndexPath *)indexPath { 24 | NSString *mainLabelText; 25 | NSString *detailLabelText; 26 | NSArray *iconNames; 27 | ToggleImageTableViewCell *toggleCell; 28 | ImageLabelTableViewCell *cell; 29 | switch (indexPath.section) { 30 | case 0: { 31 | toggleCell = [tableView dequeueReusableCellWithIdentifier:kToggleCellID 32 | forIndexPath:indexPath]; 33 | 34 | switch (indexPath.row) { 35 | case 0: 36 | mainLabelText = LOC(@"filter.settings.promoted.title", @"Promoted"); 37 | iconNames = @[ @"icon_tag" ]; 38 | toggleCell.accessorySwitch.on = 39 | ![NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterPromoted]; 40 | [toggleCell.accessorySwitch addTarget:self 41 | action:@selector(didTogglePromotedSwitch:) 42 | forControlEvents:UIControlEventValueChanged]; 43 | break; 44 | case 1: 45 | mainLabelText = LOC(@"filter.settings.recommended.title", @"Recommended"); 46 | iconNames = @[ @"icon_spam" ]; 47 | toggleCell.accessorySwitch.on = 48 | ![NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterRecommended]; 49 | [toggleCell.accessorySwitch addTarget:self 50 | action:@selector(didToggleRecommendedSwitch:) 51 | forControlEvents:UIControlEventValueChanged]; 52 | break; 53 | case 2: 54 | mainLabelText = LOC(@"filter.settings.livestreams.title", @"Livestreams"); 55 | iconNames = @[ @"icon_videocamera", @"icon_video_camera" ]; 56 | toggleCell.accessorySwitch.on = 57 | ![NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterLivestreams]; 58 | [toggleCell.accessorySwitch addTarget:self 59 | action:@selector(didToggleLivestreamsSwitch:) 60 | forControlEvents:UIControlEventValueChanged]; 61 | break; 62 | case 3: 63 | mainLabelText = LOC(@"filter.settings.nsfw.title", @"NSFW"); 64 | iconNames = @[ @"icon_nsfw_outline", @"icon_nsfw" ]; 65 | toggleCell.accessorySwitch.on = 66 | ![NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterNSFW]; 67 | [toggleCell.accessorySwitch addTarget:self 68 | action:@selector(didToggleNsfwSwitch:) 69 | forControlEvents:UIControlEventValueChanged]; 70 | break; 71 | case 4: 72 | mainLabelText = LOC(@"filter.settings.awards.title", @"Awards"); 73 | detailLabelText = 74 | LOC(@"filter.settings.awards.subtitle", @"Show awards on posts and comments"); 75 | iconNames = @[ @"icon_gift_fill", @"icon_award", @"icon-award-outline" ]; 76 | toggleCell.accessorySwitch.on = 77 | ![NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterAwards]; 78 | [toggleCell.accessorySwitch addTarget:self 79 | action:@selector(didToggleAwardsSwitch:) 80 | forControlEvents:UIControlEventValueChanged]; 81 | break; 82 | case 5: 83 | mainLabelText = LOC(@"filter.settings.scores.title", @"Scores"); 84 | detailLabelText = 85 | LOC(@"filter.settings.scores.subtitle", @"Show vote count on posts and comments"); 86 | iconNames = @[ @"icon_upvote" ]; 87 | toggleCell.accessorySwitch.on = 88 | ![NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterScores]; 89 | [toggleCell.accessorySwitch addTarget:self 90 | action:@selector(didToggleScoresSwitch:) 91 | forControlEvents:UIControlEventValueChanged]; 92 | break; 93 | case 6: 94 | mainLabelText = LOC(@"filter.settings.automod.title", @"AutoMod"); 95 | detailLabelText = 96 | LOC(@"filter.settings.automod.subtitle", @"Auto collapse AutoMod comments"); 97 | iconNames = @[ @"icon_mod" ]; 98 | toggleCell.accessorySwitch.on = 99 | [NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterAutoCollapseAutoMod]; 100 | [toggleCell.accessorySwitch addTarget:self 101 | action:@selector(didToggleAutoCollapseAutoModSwitch:) 102 | forControlEvents:UIControlEventValueChanged]; 103 | break; 104 | default: 105 | return nil; 106 | } 107 | 108 | cell = toggleCell; 109 | break; 110 | } 111 | default: 112 | return nil; 113 | } 114 | 115 | ([cell respondsToSelector:@selector(mainLabel)] ? cell.mainLabel : cell.imageLabelView.mainLabel) 116 | .text = mainLabelText; 117 | 118 | ([cell respondsToSelector:@selector(detailLabel)] ? cell.detailLabel 119 | : cell.imageLabelView.detailLabel) 120 | .text = detailLabelText; 121 | 122 | UIImage *iconImage; 123 | for (NSString *iconName in iconNames) { 124 | iconImage = iconWithName(iconName); 125 | if (iconImage) break; 126 | } 127 | 128 | if (iconImage) { 129 | UIImage *displayImage = [[iconImage imageScaledToSize:CGSizeMake(20, 20)] 130 | imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; 131 | if ([cell respondsToSelector:@selector(setDisplayImage:)]) 132 | cell.displayImage = displayImage; 133 | else 134 | cell.imageLabelView.imageView.image = displayImage; 135 | } 136 | 137 | return cell; 138 | } 139 | %new 140 | - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { 141 | BaseLabel *label = [%c(BaseLabel) labelWithSubheaderFont]; 142 | LayoutGuidance *layoutGuidance = [%c(LayoutGuidance) currentGuidance]; 143 | label.frame = CGRectMake(layoutGuidance.gridPadding, 0, 144 | layoutGuidance.maxContentWidth - layoutGuidance.gridPaddingDouble, 40.0); 145 | [label associatePropertySetter:@selector(setTextColor:) 146 | withThemePropertyGetter:@selector(metaTextColor)]; 147 | BaseTableReusableView *headerView = [[%c(BaseTableReusableView) alloc] 148 | initWithFrame:CGRectMake(0, 0, tableView.frameWidth, 40.0)]; 149 | [headerView.contentView addSubview:label]; 150 | [headerView associatePropertySetter:@selector(setBackgroundColor:) 151 | withThemePropertyGetter:@selector(canvasColor)]; 152 | switch (section) { 153 | case 0: 154 | label.text = [LOC(@"filter.settings.header", @"Filters") uppercaseString]; 155 | break; 156 | default: 157 | return nil; 158 | } 159 | return headerView; 160 | } 161 | %new 162 | - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { 163 | return 40.0; 164 | } 165 | %new 166 | - (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section { 167 | BaseLabel *label = [%c(BaseLabel) labelWithSubheaderFont]; 168 | LayoutGuidance *layoutGuidance = [%c(LayoutGuidance) currentGuidance]; 169 | label.frame = CGRectMake(layoutGuidance.gridPadding, 0, 170 | layoutGuidance.maxContentWidth - layoutGuidance.gridPaddingDouble, 40.0); 171 | [label associatePropertySetter:@selector(setTextColor:) 172 | withThemePropertyGetter:@selector(metaTextColor)]; 173 | BaseTableReusableView *footerView = [[%c(BaseTableReusableView) alloc] 174 | initWithFrame:CGRectMake(0, 0, tableView.frameWidth, 40.0)]; 175 | [footerView.contentView addSubview:label]; 176 | [footerView associatePropertySetter:@selector(setBackgroundColor:) 177 | withThemePropertyGetter:@selector(canvasColor)]; 178 | switch (section) { 179 | case 0: 180 | label.text = LOC(@"filter.settings.footer", @"Filter specific types of posts from your feed"); 181 | break; 182 | default: 183 | return nil; 184 | } 185 | return footerView; 186 | } 187 | %new 188 | - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section { 189 | return 40.0; 190 | } 191 | - (void)viewDidLoad { 192 | %orig; 193 | self.title = LOC(@"filter.settings.title", @"Feed filter"); 194 | [self.tableView registerClass:CoreClass(@"ToggleImageTableViewCell") 195 | forCellReuseIdentifier:kToggleCellID]; 196 | [self.tableView registerClass:CoreClass(@"ImageLabelTableViewCell") 197 | forCellReuseIdentifier:kLabelCellID]; 198 | } 199 | %new 200 | - (void)didTogglePromotedSwitch:(UISwitch *)sender { 201 | [NSUserDefaults.standardUserDefaults setBool:!sender.on forKey:kRedditFilterPromoted]; 202 | } 203 | %new 204 | - (void)didToggleRecommendedSwitch:(UISwitch *)sender { 205 | [NSUserDefaults.standardUserDefaults setBool:!sender.on forKey:kRedditFilterRecommended]; 206 | } 207 | %new 208 | - (void)didToggleLivestreamsSwitch:(UISwitch *)sender { 209 | [NSUserDefaults.standardUserDefaults setBool:!sender.on forKey:kRedditFilterLivestreams]; 210 | } 211 | %new 212 | - (void)didToggleNsfwSwitch:(UISwitch *)sender { 213 | [NSUserDefaults.standardUserDefaults setBool:!sender.on forKey:kRedditFilterNSFW]; 214 | } 215 | %new 216 | - (void)didToggleAwardsSwitch:(UISwitch *)sender { 217 | [NSUserDefaults.standardUserDefaults setBool:!sender.on forKey:kRedditFilterAwards]; 218 | } 219 | %new 220 | - (void)didToggleScoresSwitch:(UISwitch *)sender { 221 | [NSUserDefaults.standardUserDefaults setBool:!sender.on forKey:kRedditFilterScores]; 222 | } 223 | %new 224 | - (void)didToggleAutoCollapseAutoModSwitch:(UISwitch *)sender { 225 | [NSUserDefaults.standardUserDefaults setBool:sender.on forKey:kRedditFilterAutoCollapseAutoMod]; 226 | } 227 | %end 228 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | export LOGOS_DEFAULT_GENERATOR = internal 2 | 3 | TARGET := iphone:clang:latest:11.0 4 | INSTALL_TARGET_PROCESSES = RedditApp Reddit 5 | 6 | ARCHS = arm64 7 | 8 | PACKAGE_VERSION = 1.1.7 9 | ifdef APP_VERSION 10 | PACKAGE_VERSION := $(APP_VERSION)-$(PACKAGE_VERSION) 11 | endif 12 | 13 | ifeq ($(SIDELOADED),1) 14 | export MODULES = jailed 15 | CODESIGN_IPA = 0 16 | endif 17 | 18 | include $(THEOS)/makefiles/common.mk 19 | 20 | TWEAK_NAME = RedditFilter 21 | 22 | $(TWEAK_NAME)_FILES = $(wildcard *.x*) 23 | $(TWEAK_NAME)_CFLAGS = -fobjc-arc -Iinclude -Wno-module-import-in-extern-c 24 | $(TWEAK_NAME)_INJECT_DYLIBS = $(THEOS_OBJ_DIR)/RedditSideloadFix.dylib 25 | 26 | ifeq ($(SIDELOADED),1) 27 | SUBPROJECTS += RedditSideloadFix 28 | include $(THEOS_MAKE_PATH)/aggregate.mk 29 | endif 30 | 31 | include $(THEOS_MAKE_PATH)/tweak.mk -------------------------------------------------------------------------------- /Preferences.h: -------------------------------------------------------------------------------- 1 | #define kRedditFilterPromoted @"kRedditFilterPromoted" 2 | #define kRedditFilterRecommended @"kRedditFilterRecommended" 3 | #define kRedditFilterLivestreams @"kRedditFilterLivestreams" 4 | #define kRedditFilterNSFW @"kRedditFilterNSFW" 5 | #define kRedditFilterAwards @"kRedditFilterAwards" 6 | #define kRedditFilterScores @"kRedditFilterScores" 7 | #define kRedditFilterAutoCollapseAutoMod @"kRedditFilterAutoCollapseAutoMod" 8 | 9 | #define kToggleCellID @"kToggleCellID" 10 | #define kLabelCellID @"kLabelCellID" -------------------------------------------------------------------------------- /RedditFilter.plist: -------------------------------------------------------------------------------- 1 | { Filter = { Bundles = ( "com.reddit.Reddit" ); }; } 2 | -------------------------------------------------------------------------------- /Resources: -------------------------------------------------------------------------------- 1 | layout/Library/Application Support -------------------------------------------------------------------------------- /Settings.x: -------------------------------------------------------------------------------- 1 | #import 2 | #import "FeedFilterSettingsViewController.h" 3 | 4 | NSBundle *redditFilterBundle; 5 | 6 | extern UIImage *iconWithName(NSString *iconName); 7 | extern NSString *localizedString(NSString *key, NSString *table); 8 | 9 | @interface AppSettingsViewController () 10 | @property(nonatomic, assign) NSInteger feedFilterSectionIndex; 11 | @end 12 | 13 | %hook NSURLSession 14 | - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request 15 | completionHandler:(void (^)(NSData *data, NSURLResponse *response, 16 | NSError *error))completionHandler { 17 | if (![request.URL.host hasPrefix:@"gql"] || !request.HTTPBody) return %orig; 18 | NSError *error; 19 | NSDictionary *json = [NSJSONSerialization JSONObjectWithData:request.HTTPBody 20 | options:0 21 | error:&error]; 22 | if (error || ![json[@"operationName"] isEqualToString:@"GetAllExperimentVariants"]) 23 | return %orig; 24 | void (^newCompletionHandler)(NSData *, NSURLResponse *, NSError *) = 25 | ^(NSData *data, NSURLResponse *response, NSError *error) { 26 | if (error || !data) return completionHandler(data, response, error); 27 | NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data 28 | options:NSJSONReadingMutableContainers 29 | error:&error]; 30 | if (error || !json) return completionHandler(data, response, error); 31 | for (NSMutableDictionary *experimentVariant in json[@"data"][@"experimentVariants"]) 32 | if ([experimentVariant[@"experimentName"] isEqualToString:@"ios_swiftui_app_settings"]) 33 | experimentVariant[@"name"] = @"disabled"; 34 | data = [NSJSONSerialization dataWithJSONObject:json options:0 error:nil]; 35 | completionHandler(data, response, error); 36 | }; 37 | return %orig(request, newCompletionHandler); 38 | } 39 | %end 40 | 41 | %hook AppSettingsViewController 42 | %property(nonatomic, assign) NSInteger feedFilterSectionIndex; 43 | - (void)viewDidLoad { 44 | %orig; 45 | for (int section = 0; section < [self numberOfSectionsInTableView:self.tableView]; section++) { 46 | BaseTableReusableView *headerView = (BaseTableReusableView *)[self tableView:self.tableView 47 | viewForHeaderInSection:section]; 48 | if (!headerView) continue; 49 | BaseLabel *label = headerView.contentView.subviews[0]; 50 | for (NSString *key in @[ @"drawer.settings.feedOptions", @"drawer.settings.viewOptions" ]) { 51 | if ([label.text isEqualToString:[localizedString(key, @"user") uppercaseString]]) { 52 | self.feedFilterSectionIndex = section; 53 | return; 54 | } 55 | } 56 | } 57 | self.feedFilterSectionIndex = 2; 58 | } 59 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 60 | NSInteger result = %orig; 61 | if (section == self.feedFilterSectionIndex) result++; 62 | return result; 63 | } 64 | - (UITableViewCell *)tableView:(UITableView *)tableView 65 | cellForRowAtIndexPath:(NSIndexPath *)indexPath { 66 | if (indexPath.section == self.feedFilterSectionIndex && 67 | indexPath.row == [self tableView:tableView numberOfRowsInSection:indexPath.section] - 1) { 68 | UIImage *iconImage = [iconWithName(@"icon_filter") ?: iconWithName(@"icon-filter-outline") 69 | imageScaledToSize:CGSizeMake(20, 20)]; 70 | UIImage *accessoryIconImage = 71 | [iconWithName(@"icon_forward") imageScaledToSize:CGSizeMake(20, 20)]; 72 | ImageLabelTableViewCell *cell = 73 | [self dequeueSettingsCellForTableView:tableView 74 | indexPath:indexPath 75 | leadingImage:iconImage 76 | text:[redditFilterBundle 77 | localizedStringForKey:@"filter.settings.title" 78 | value:@"Feed filter" 79 | table:nil]]; 80 | [cell setCustomAccessoryImage:accessoryIconImage]; 81 | return cell; 82 | } 83 | return %orig; 84 | } 85 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 86 | if (indexPath.section == self.feedFilterSectionIndex && 87 | indexPath.row == [self tableView:tableView numberOfRowsInSection:indexPath.section] - 1) { 88 | [self.navigationController 89 | pushViewController:[(FeedFilterSettingsViewController *)[objc_getClass( 90 | "FeedFilterSettingsViewController") alloc] 91 | initWithStyle:UITableViewStyleGrouped] 92 | animated:YES]; 93 | return; 94 | } 95 | %orig; 96 | } 97 | %end 98 | 99 | %ctor { 100 | redditFilterBundle = [NSBundle bundleWithPath:[NSBundle.mainBundle pathForResource:@"RedditFilter" 101 | ofType:@"bundle"]]; 102 | if (!redditFilterBundle) 103 | redditFilterBundle = [NSBundle bundleWithPath:@THEOS_PACKAGE_INSTALL_PREFIX 104 | @"/Library/Application Support/RedditFilter.bundle"]; 105 | } 106 | -------------------------------------------------------------------------------- /Tweak.xm: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | #import 5 | #import 6 | #import 7 | #import 8 | #import 9 | #import "Preferences.h" 10 | 11 | static NSMutableArray *assetBundles; 12 | 13 | extern "C" UIImage *iconWithName(NSString *iconName) { 14 | NSArray *commonIconSizes = @[ 15 | @"24", 16 | @"20", 17 | @"16", 18 | ]; 19 | UIImage *iconImage; 20 | for (NSBundle *bundle in assetBundles) { 21 | for (NSString *iconSize in commonIconSizes) { 22 | if (iconImage) break; 23 | iconImage = [UIImage imageNamed:[NSString stringWithFormat:@"%@_%@", iconName, iconSize] 24 | inBundle:bundle 25 | compatibleWithTraitCollection:nil]; 26 | } 27 | if (!iconImage) 28 | iconImage = [UIImage imageNamed:iconName inBundle:bundle compatibleWithTraitCollection:nil]; 29 | } 30 | return iconImage; 31 | } 32 | 33 | extern "C" NSString *localizedString(NSString *key, NSString *table) { 34 | for (NSBundle *bundle in assetBundles) { 35 | NSString *localizedString = [bundle localizedStringForKey:key value:nil table:table]; 36 | if (![localizedString isEqualToString:key]) return localizedString; 37 | } 38 | return nil; 39 | } 40 | 41 | extern "C" Class CoreClass(NSString *name) { 42 | Class cls = NSClassFromString(name); 43 | NSArray *prefixes = @[ 44 | @"Reddit.", 45 | @"RedditCore.", 46 | @"RedditCoreModels.", 47 | @"RedditCore_RedditCoreModels.", 48 | @"RedditUI.", 49 | ]; 50 | for (NSString *prefix in prefixes) { 51 | if (cls) break; 52 | cls = NSClassFromString([prefix stringByAppendingString:name]); 53 | } 54 | return cls; 55 | } 56 | 57 | static BOOL shouldFilterObject(id object) { 58 | NSString *className = NSStringFromClass(object_getClass(object)); 59 | BOOL isAdPost = [className hasSuffix:@"AdPost"] || 60 | ([object respondsToSelector:@selector(isAdPost)] && ((Post *)object).isAdPost) || 61 | ([object respondsToSelector:@selector(isPromotedUserPostAd)] && 62 | [(Post *)object isPromotedUserPostAd]) || 63 | ([object respondsToSelector:@selector(isPromotedCommunityPostAd)] && 64 | [(Post *)object isPromotedCommunityPostAd]); 65 | BOOL isRecommendation = [className containsString:@"Recommend"]; 66 | BOOL isLivestream = [className containsString:@"Stream"]; 67 | BOOL isNSFW = [object respondsToSelector:@selector(isNSFW)] && ((Post *)object).isNSFW; 68 | if ([NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterPromoted] && isAdPost) 69 | return YES; 70 | if ([NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterRecommended] && isRecommendation) 71 | return YES; 72 | if ([NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterLivestreams] && isLivestream) 73 | return YES; 74 | if ([NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterNSFW] && isNSFW) return YES; 75 | return NO; 76 | } 77 | 78 | static NSArray *filteredObjects(NSArray *objects) { 79 | return [objects filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL( 80 | id object, NSDictionary *bindings) { 81 | return !shouldFilterObject(object); 82 | }]]; 83 | } 84 | 85 | static void filterNode(NSMutableDictionary *node) { 86 | if (![node isKindOfClass:NSMutableDictionary.class]) return; 87 | 88 | // Regular post 89 | if ([node[@"__typename"] isEqualToString:@"SubredditPost"]) { 90 | if ([NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterAwards]) { 91 | node[@"awardings"] = @[]; 92 | node[@"isGildable"] = @NO; 93 | } 94 | 95 | if ([NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterScores]) 96 | node[@"isScoreHidden"] = @YES; 97 | 98 | if ([NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterNSFW] && 99 | [node[@"isNsfw"] boolValue]) 100 | node[@"isHidden"] = @YES; 101 | } 102 | if ([node[@"__typename"] isEqualToString:@"CellGroup"]) { 103 | for (NSMutableDictionary *cell in node[@"cells"]) { 104 | if ([cell[@"__typename"] isEqualToString:@"ActionCell"]) { 105 | if ([NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterAwards]) { 106 | cell[@"isAwardHidden"] = @YES; 107 | cell[@"goldenUpvoteInfo"][@"isGildable"] = @NO; 108 | } 109 | 110 | if ([NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterScores]) 111 | cell[@"isScoreHidden"] = @YES; 112 | } 113 | } 114 | } 115 | 116 | // Ad post 117 | if ([NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterPromoted]) { 118 | if ([node[@"__typename"] isEqualToString:@"AdPost"]) node[@"isHidden"] = @YES; 119 | if ([node[@"__typename"] isEqualToString:@"CellGroup"] && 120 | [node[@"adPayload"] isKindOfClass:NSDictionary.class]) 121 | node[@"cells"] = @[]; 122 | } 123 | 124 | // Recommendation 125 | if ([NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterRecommended]) { 126 | if ([node[@"__typename"] isEqualToString:@"CellGroup"] && 127 | ![node[@"recommendationContext"] isEqual:[NSNull null]]) 128 | if (!([node[@"recommendationContext"][@"typeName"] 129 | isEqualToString:@"PopularRecommendationContext"] && 130 | [node[@"recommendationContext"][@"isContextHidden"] boolValue])) 131 | node[@"cells"] = @[]; 132 | } 133 | 134 | // Comment 135 | if ([node[@"__typename"] isEqualToString:@"Comment"]) { 136 | if ([NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterAwards]) { 137 | node[@"awardings"] = @[]; 138 | node[@"isGildable"] = @NO; 139 | } 140 | 141 | if ([NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterScores]) 142 | node[@"isScoreHidden"] = @YES; 143 | 144 | if ([node[@"authorInfo"] isKindOfClass:NSDictionary.class] && 145 | [node[@"authorInfo"][@"id"] isEqualToString:@"t2_6l4z3"] && 146 | [NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterAutoCollapseAutoMod]) 147 | node[@"isInitiallyCollapsed"] = @YES; 148 | } 149 | } 150 | 151 | %hook NSURLSession 152 | - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request 153 | completionHandler:(void (^)(NSData *data, NSURLResponse *response, 154 | NSError *error))completionHandler { 155 | if (![request.URL.host hasPrefix:@"gql"] && ![request.URL.host hasPrefix:@"oauth"]) 156 | return %orig; 157 | void (^newCompletionHandler)(NSData *, NSURLResponse *, NSError *) = 158 | ^(NSData *data, NSURLResponse *response, NSError *error) { 159 | if (error || !data) return completionHandler(data, response, error); 160 | NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data 161 | options:NSJSONReadingMutableContainers 162 | error:&error]; 163 | if (error || !json) return completionHandler(data, response, error); 164 | if ([json isKindOfClass:NSDictionary.class]) { 165 | if (json[@"data"] && [json[@"data"] isKindOfClass:NSDictionary.class]) { 166 | NSDictionary *data = json[@"data"]; 167 | NSMutableDictionary *root = data.allValues.firstObject; 168 | if ([root isKindOfClass:NSDictionary.class]) { 169 | if ([root.allValues.firstObject isKindOfClass:NSDictionary.class] && 170 | root.allValues.firstObject[@"edges"]) 171 | for (NSMutableDictionary *edge in root.allValues.firstObject[@"edges"]) 172 | filterNode(edge[@"node"]); 173 | 174 | if (root[@"commentForest"]) 175 | for (NSMutableDictionary *tree in root[@"commentForest"][@"trees"]) 176 | filterNode(tree[@"node"]); 177 | 178 | if (root[@"commentsPageAds"] && 179 | [NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterPromoted]) 180 | root[@"commentsPageAds"] = @[]; 181 | 182 | if (root[@"commentTreeAds"] && 183 | [NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterPromoted]) 184 | root[@"commentTreeAds"] = @[]; 185 | 186 | if (root[@"recommendations"] && 187 | [NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterRecommended]) 188 | root[@"recommendations"] = @[]; 189 | 190 | } else if ([root isKindOfClass:NSArray.class]) { 191 | for (NSMutableDictionary *node in (NSArray *)root) filterNode(node); 192 | } 193 | } 194 | } 195 | data = [NSJSONSerialization dataWithJSONObject:json options:0 error:nil]; 196 | completionHandler(data, response, error); 197 | }; 198 | return %orig(request, newCompletionHandler); 199 | } 200 | %end 201 | 202 | // Only necessary for older app versions 203 | %group Legacy 204 | 205 | %hook Listing 206 | - (void)fetchNextPage:(id (^)(NSArray *, id))completionHandler { 207 | id (^newCompletionHandler)(NSArray *, id) = ^(NSArray *objects, id _) { 208 | return completionHandler(filteredObjects(objects), _); 209 | }; 210 | return %orig(newCompletionHandler); 211 | } 212 | %end 213 | 214 | %hook FeedNetworkSource 215 | - (NSArray *)postsAndCommentsFromData:(id)data { 216 | return filteredObjects(%orig); 217 | } 218 | %end 219 | 220 | %hook PostDetailPresenter 221 | - (BOOL)shouldFetchCommentAdPost { 222 | return [NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterPromoted] ? NO 223 | : %orig; 224 | } 225 | - (BOOL)shouldFetchAdditionalCommentAdPosts { 226 | return [NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterPromoted] ? NO 227 | : %orig; 228 | } 229 | %end 230 | 231 | %hook StreamManager 232 | - (instancetype)initWithAccountContext:(id)accountContext 233 | source:(NSInteger)source 234 | deeplinkSubredditName:(id)deeplinkSubredditName 235 | streamingConfig:(id)streamingConfig { 236 | if ([NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterLivestreams]) return nil; 237 | return %orig; 238 | } 239 | - (instancetype)initWithService:(id)service 240 | source:(NSInteger)source 241 | deeplinkSubredditName:(id)deeplinkSubredditName 242 | streamingConfig:(id)streamingConfig { 243 | if ([NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterLivestreams]) return nil; 244 | return %orig; 245 | } 246 | %end 247 | 248 | %hook Carousel 249 | - (BOOL)isHiddenByUserWithAccountSettings:(id)accountSettings { 250 | return ([NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterRecommended] && 251 | ([self.analyticType containsString:@"recommended"] || 252 | [self.analyticType containsString:@"similar"] || 253 | [self.analyticType containsString:@"popular"])) || 254 | %orig; 255 | } 256 | %end 257 | 258 | %hook QuickActionViewModel 259 | - (void)fetchActions { 260 | if ([NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterRecommended]) return; 261 | %orig; 262 | } 263 | %end 264 | 265 | %hook Post 266 | - (NSArray *)awardingTotals { 267 | return [NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterAwards] ? nil 268 | : %orig; 269 | } 270 | - (NSUInteger)totalAwardsReceived { 271 | return [NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterAwards] ? 0 272 | : %orig; 273 | } 274 | - (BOOL)canAward { 275 | return [NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterAwards] ? NO 276 | : %orig; 277 | } 278 | - (BOOL)isScoreHidden { 279 | return [NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterScores] ? YES 280 | : %orig; 281 | } 282 | %end 283 | 284 | %hook Comment 285 | - (NSArray *)awardingTotals { 286 | return [NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterAwards] ? nil 287 | : %orig; 288 | } 289 | - (NSUInteger)totalAwardsReceived { 290 | return [NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterAwards] ? 0 291 | : %orig; 292 | } 293 | - (BOOL)canAward { 294 | return [NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterAwards] ? NO 295 | : %orig; 296 | } 297 | - (BOOL)shouldHighlightForHighAward { 298 | return [NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterAwards] ? NO 299 | : %orig; 300 | } 301 | - (BOOL)isScoreHidden { 302 | return [NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterScores] ? YES 303 | : %orig; 304 | } 305 | - (BOOL)shouldAutoCollapse { 306 | return [NSUserDefaults.standardUserDefaults boolForKey:kRedditFilterAutoCollapseAutoMod] && 307 | [((Comment *)self).authorPk isEqualToString:@"t2_6l4z3"] 308 | ? YES 309 | : %orig; 310 | } 311 | %end 312 | 313 | %hook ToggleImageTableViewCell 314 | - (void)updateConstraints { 315 | %orig; 316 | UIStackView *horizontalStackView = 317 | [self respondsToSelector:@selector(imageLabelView)] 318 | ? [self imageLabelView].horizontalStackView 319 | : object_getIvar(self, 320 | class_getInstanceVariable(object_getClass(self), "horizontalStackView")); 321 | UILabel *detailLabel = [self respondsToSelector:@selector(imageLabelView)] 322 | ? [self imageLabelView].detailLabel 323 | : [self detailLabel]; 324 | if (!horizontalStackView || !detailLabel) return; 325 | if (detailLabel.text) { 326 | UIView *contentView = [self contentView]; 327 | [contentView addConstraints:@[ 328 | [NSLayoutConstraint constraintWithItem:detailLabel 329 | attribute:NSLayoutAttributeHeight 330 | relatedBy:NSLayoutRelationEqual 331 | toItem:horizontalStackView 332 | attribute:NSLayoutAttributeHeight 333 | multiplier:.33 334 | constant:0], 335 | [NSLayoutConstraint constraintWithItem:horizontalStackView 336 | attribute:NSLayoutAttributeHeight 337 | relatedBy:NSLayoutRelationEqual 338 | toItem:contentView 339 | attribute:NSLayoutAttributeHeight 340 | multiplier:1 341 | constant:0], 342 | [NSLayoutConstraint constraintWithItem:horizontalStackView 343 | attribute:NSLayoutAttributeCenterY 344 | relatedBy:NSLayoutRelationEqual 345 | toItem:contentView 346 | attribute:NSLayoutAttributeCenterY 347 | multiplier:1 348 | constant:0] 349 | ]]; 350 | } 351 | } 352 | %end 353 | 354 | %end 355 | 356 | %ctor { 357 | assetBundles = [NSMutableArray new]; 358 | [assetBundles addObject:NSBundle.mainBundle]; 359 | for (NSString *file in 360 | [NSFileManager.defaultManager contentsOfDirectoryAtPath:NSBundle.mainBundle.bundlePath 361 | error:nil]) { 362 | if (![file hasSuffix:@"bundle"]) continue; 363 | NSBundle *bundle = [NSBundle 364 | bundleWithPath:[NSBundle.mainBundle pathForResource:[file stringByDeletingPathExtension] 365 | ofType:@"bundle"]]; 366 | if (bundle) [assetBundles addObject:bundle]; 367 | } 368 | for (NSString *file in [NSFileManager.defaultManager 369 | contentsOfDirectoryAtPath:[NSBundle.mainBundle.bundlePath 370 | stringByAppendingPathComponent:@"Frameworks"] 371 | error:nil]) { 372 | if (![file hasSuffix:@"framework"]) continue; 373 | NSBundle *bundle = [NSBundle 374 | bundleWithPath:[NSBundle.mainBundle pathForResource:[file stringByDeletingPathExtension] 375 | ofType:@"framework" 376 | inDirectory:@"Frameworks"]]; 377 | if (bundle) [assetBundles addObject:bundle]; 378 | } 379 | %init; 380 | %init(Legacy, Comment = CoreClass(@"Comment"), Post = CoreClass(@"Post"), 381 | QuickActionViewModel = CoreClass(@"QuickActionViewModel"), 382 | StreamManager = CoreClass(@"StreamManager"), 383 | ToggleImageTableViewCell = CoreClass(@"ToggleImageTableViewCell")); 384 | } 385 | -------------------------------------------------------------------------------- /control: -------------------------------------------------------------------------------- 1 | Package: com.level3tjg.redditfilter 2 | Name: RedditFilter 3 | Version: 1.0.0 4 | Architecture: iphoneos-arm 5 | Description: Filter out unwanted content from your Reddit feed 6 | Depiction: https://level3tjg.me/repo/depictions/?p=com.level3tjg.redditfilter 7 | Maintainer: level3tjg 8 | Author: level3tjg 9 | Section: Tweaks 10 | Depends: mobilesubstrate (>= 0.9.5000) 11 | -------------------------------------------------------------------------------- /include/AppSettingsViewController.h: -------------------------------------------------------------------------------- 1 | #import "BaseTableViewController.h" 2 | #import "ImageLabelTableViewCell.h" 3 | 4 | @interface AppSettingsViewController : BaseTableViewController 5 | - (ImageLabelTableViewCell *) 6 | dequeueSettingsCellForTableView:(UITableView *)tableView 7 | indexPath:(NSIndexPath *)indexPath 8 | leadingImage:(UIImage *)leadingImage 9 | text:(NSString *)text; 10 | @end -------------------------------------------------------------------------------- /include/AttributedLabelRegular.h: -------------------------------------------------------------------------------- 1 | #import "BaseLabel.h" 2 | 3 | @interface AttributedLabelRegular : BaseLabel 4 | @end -------------------------------------------------------------------------------- /include/BaseLabel.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface BaseLabel : UILabel 4 | + (instancetype)labelWithSubheaderFont; 5 | @end -------------------------------------------------------------------------------- /include/BaseTableReusableView.h: -------------------------------------------------------------------------------- 1 | #import "BaseView.h" 2 | 3 | @interface BaseTableReusableView : BaseView 4 | @property(nonatomic, strong) UIView *contentView; 5 | @end -------------------------------------------------------------------------------- /include/BaseTableViewCell.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface BaseTableViewCell : UITableViewCell 4 | @end 5 | -------------------------------------------------------------------------------- /include/BaseTableViewController.h: -------------------------------------------------------------------------------- 1 | #import "DeprecatedBaseViewController.h" 2 | 3 | @interface BaseTableViewController 4 | : DeprecatedBaseViewController 5 | @property(nonatomic, strong) UITableView *tableView; 6 | - (instancetype)initWithStyle:(UITableViewStyle)style; 7 | @end -------------------------------------------------------------------------------- /include/BaseView.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface BaseView : UIView 4 | @end -------------------------------------------------------------------------------- /include/Carousel.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface Carousel 4 | @property NSString *analyticType; 5 | @end -------------------------------------------------------------------------------- /include/Comment.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface Comment : NSObject 4 | @property(nonatomic, strong) NSString *authorPk; 5 | @end -------------------------------------------------------------------------------- /include/DeprecatedBaseViewController.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface DeprecatedBaseViewController : UIViewController 4 | @end -------------------------------------------------------------------------------- /include/ImageLabelContentView.h: -------------------------------------------------------------------------------- 1 | #import "AttributedLabelRegular.h" 2 | #import "BaseView.h" 3 | 4 | @interface ImageLabelContentView : BaseView 5 | @property(nonatomic, readonly, strong) UIImageView *imageView; 6 | @property(nonatomic, readonly, strong) AttributedLabelRegular *mainLabel; 7 | @property(nonatomic, readonly, strong) BaseLabel *detailLabel; 8 | @property(nonatomic, strong) UIStackView *horizontalStackView; 9 | @end -------------------------------------------------------------------------------- /include/ImageLabelTableViewCell.h: -------------------------------------------------------------------------------- 1 | #import "ImageLabelContentView.h" 2 | #import "ViewLabelTableViewCell.h" 3 | 4 | @interface ImageLabelTableViewCell : ViewLabelTableViewCell 5 | @property(nonatomic, strong) ImageLabelContentView *imageLabelView; 6 | @property(nonatomic, strong) UIImage *displayImage; 7 | - (void)setCustomAccessoryImage:(UIImage *)customAccessoryImage; 8 | @end -------------------------------------------------------------------------------- /include/LayoutGuidance.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface LayoutGuidance : NSObject 4 | @property(class, nonatomic, readonly) LayoutGuidance *currentGuidance; 5 | @property(nonatomic, readonly, assign) CGFloat gridPadding; 6 | @property(nonatomic, readonly, assign) CGFloat maxContentWidth; 7 | @property(nonatomic, readonly, assign) CGFloat gridPaddingDouble; 8 | @end -------------------------------------------------------------------------------- /include/Post.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface Post : NSObject 4 | @property(nonatomic, assign) BOOL isAdPost; 5 | @property(nonatomic, assign) BOOL isNSFW; 6 | - (BOOL)isPromotedUserPostAd; 7 | - (BOOL)isPromotedCommunityPostAd; 8 | @end -------------------------------------------------------------------------------- /include/ToggleImageTableViewCell.h: -------------------------------------------------------------------------------- 1 | #import "ImageLabelTableViewCell.h" 2 | 3 | @interface ToggleImageTableViewCell : ImageLabelTableViewCell 4 | @property(nonatomic, strong) UISwitch *accessorySwitch; 5 | @end -------------------------------------------------------------------------------- /include/ViewLabelTableViewCell.h: -------------------------------------------------------------------------------- 1 | #import "AttributedLabelRegular.h" 2 | #import "BaseTableViewCell.h" 3 | 4 | @interface ViewLabelTableViewCell : BaseTableViewCell { 5 | UIStackView *labelsStackView; 6 | UIStackView *horizontalStackView; 7 | } 8 | @property(nonatomic, readonly, strong) AttributedLabelRegular *mainLabel; 9 | @property(nonatomic, readonly, strong) BaseLabel *detailLabel; 10 | @end -------------------------------------------------------------------------------- /layout/Library/Application Support/RedditFilter.bundle/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "filter.settings.title"="Feed filter"; 2 | "filter.settings.header"="Filters"; 3 | "filter.settings.footer"="Filter specific types of posts from your feed."; 4 | "filter.settings.promoted.title"="Promoted"; 5 | "filter.settings.recommended.title"="Recommended"; 6 | "filter.settings.livestreams.title"="Livestreams"; 7 | "filter.settings.nsfw.title"="NSFW"; 8 | "filter.settings.awards.title"="Awards"; 9 | "filter.settings.awards.subtitle"="Show awards on posts and comments"; 10 | "filter.settings.scores.title"="Scores"; 11 | "filter.settings.scores.subtitle"="Show vote count on posts and comments"; 12 | "filter.settings.automod.title"="AutoMod"; 13 | "filter.settings.automod.subtitle"="Auto collapse AutoMod comments"; --------------------------------------------------------------------------------