diff --git a/Source/Settings.xm b/Source/Settings.xm index c182f43..0c9bae0 100644 --- a/Source/Settings.xm +++ b/Source/Settings.xm @@ -372,7 +372,9 @@ static const NSInteger YTLiteSection = 789; createSectionGestureSelector(@"BOTTOM_SECTION", @"playerGestureBottomSelection"), // Pickers for configuration settings deadzonePicker, - sensitivityPicker + sensitivityPicker, + // Toggle for haptic feedback + BASIC_SWITCH(LOC(@"PLAYER_GESTURES_HAPTIC_FEEDBACK"), nil, @"playerGesturesHapticFeedback_enabled"), ]; YTSettingsPickerViewController *picker = [[%c(YTSettingsPickerViewController) alloc] initWithNavTitle:LOC(@"Player Gestures (Beta)") pickerSectionTitle:nil rows:rows selectedItemIndex:NSNotFound parentResponder:[self parentResponder]]; [settingsViewController pushViewController:picker]; diff --git a/YTLitePlus.h b/YTLitePlus.h index 57184f1..bc6de48 100644 --- a/YTLitePlus.h +++ b/YTLitePlus.h @@ -48,6 +48,7 @@ #import "Tweaks/YouTubeHeader/YTWatchLayerViewController.h" #import "Tweaks/YouTubeHeader/YTPageStyleController.h" #import "Tweaks/YouTubeHeader/YTRightNavigationButtons.h" +#import "Tweaks/YouTubeHeader/YTInlinePlayerBarView.h" #define LOC(x) [tweakBundle localizedStringForKey:x value:nil table:nil] #define YT_BUNDLE_ID @"com.google.ios.youtube" @@ -155,6 +156,10 @@ typedef NS_ENUM(NSUInteger, GestureSection) { @end // Player Gestures - @bhackel +@interface YTFineScrubberFilmstripView : UIView +@end +@interface YTFineScrubberFilmstripCollectionView : UICollectionView +@end @interface YTPlayerViewController (YTLitePlus) @property (nonatomic, retain) UIPanGestureRecognizer *YTLitePlusPanGesture; - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer; @@ -164,6 +169,20 @@ typedef NS_ENUM(NSUInteger, GestureSection) { @interface MPVolumeController : NSObject @property (nonatomic, assign, readwrite) float volumeValue; @end +@interface YTPlayerBarController (YTLitePlus) +- (void)didScrub:(UIPanGestureRecognizer *)gestureRecognizer; +- (void)startScrubbing; +- (void)didScrubToPoint:(CGPoint)point; +- (void)endScrubbingForSeekSource:(int)seekSource; +@end +@interface YTMainAppVideoPlayerOverlayViewController (YTLitePlus) +@property (nonatomic, strong, readwrite) YTPlayerBarController *playerBarController; +@end +@interface YTInlinePlayerBarContainerView (YTLitePlus) +@property UIPanGestureRecognizer *scrubGestureRecognizer; +@property (nonatomic, strong, readwrite) YTFineScrubberFilmstripView *fineScrubberFilmstrip; +- (CGFloat)scrubXForScrubRange:(CGFloat)scrubRange; +@end // Hide Collapse Button - @arichornlover @interface YTMainAppControlsOverlayView (YTLitePlus) diff --git a/YTLitePlus.xm b/YTLitePlus.xm index 9214f64..3bb632e 100644 --- a/YTLitePlus.xm +++ b/YTLitePlus.xm @@ -641,7 +641,7 @@ BOOL isTabSelected = NO; %end // Gestures - @bhackel -%group playerGestures +%group gPlayerGestures %hook YTWatchLayerViewController // invoked when the player view controller is either created or destroyed - (void)watchController:(YTWatchController *)watchController didSetPlayerViewController:(YTPlayerViewController *)playerViewController { @@ -662,14 +662,22 @@ BOOL isTabSelected = NO; %hook YTPlayerViewController // the pan gesture that will be created and added to the player view %property (nonatomic, retain) UIPanGestureRecognizer *YTLitePlusPanGesture; +/** + * This method is called when the pan gesture is started, changed, or ended. It handles + * 12 different possible cases depending on the configuration: 3 zones with 4 choices + * for each zone. The zones are horizontal sections that divide the player into + * 3 equal parts. The choices are volume, brightness, seek, and disabled. + * There is also a deadzone that can be configured in the settings. + * There are 4 logical states: initial, changed in deadzone, changed, end. + */ %new - (void)YTLitePlusHandlePanGesture:(UIPanGestureRecognizer *)panGestureRecognizer { // Haptic feedback generator - static UIImpactFeedbackGenerator *feedbackGenerator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleMedium]; + static UIImpactFeedbackGenerator *feedbackGenerator; // Variables for storing initial values to be adjusted static float initialVolume; static float initialBrightness; - static CGFloat currentTime; + static CGFloat initialTime; // Flag to determine if the pan gesture is valid static BOOL isValidHorizontalPan = NO; // Variable to store the section of the screen the gesture is in @@ -678,62 +686,128 @@ BOOL isTabSelected = NO; static CGPoint startLocation; // Variable to track the X translation when exiting the deadzone static CGFloat deadzoneStartingXTranslation; + // Variable to track the X translation of the pan gesture after exiting the deadzone + static CGFloat adjustedTranslationX; + // Variable used to smooth out the X translation + static CGFloat smoothedTranslationX = 0; + // Constant for the filter constant to change responsiveness + static const CGFloat filterConstant = 0.1; // Constant for the deadzone radius that can be changed in the settings static CGFloat deadzoneRadius = (CGFloat)GetFloat(@"playerGesturesDeadzone"); // Constant for the sensitivity factor that can be changed in the settings static CGFloat sensitivityFactor = (CGFloat)GetFloat(@"playerGesturesSensitivity"); - -/***** Helper functions *****/ - // Helper function to adjust brightness - void (^adjustBrightness)(CGFloat, CGFloat) = ^(CGFloat translationX, CGFloat initialBrightness) { - float newBrightness = initialBrightness + ((translationX / 1000.0) * sensitivityFactor); - newBrightness = fmaxf(fminf(newBrightness, 1.0), 0.0); - [[UIScreen mainScreen] setBrightness:newBrightness]; - }; - // Helper function to adjust volume - void (^adjustVolume)(CGFloat, CGFloat) = ^(CGFloat translationX, CGFloat initialVolume) { - float newVolume = initialVolume + ((translationX / 1000.0) * sensitivityFactor); - newVolume = fmaxf(fminf(newVolume, 1.0), 0.0); - // https://stackoverflow.com/questions/50737943/how-to-change-volume-programmatically-on-ios-11-4 - MPVolumeView *volumeView = [[MPVolumeView alloc] init]; - UISlider *volumeViewSlider = nil; + // Objects for modifying the system volume + static MPVolumeView *volumeView; + static UISlider *volumeViewSlider; + // Get objects that should only be initialized once + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + volumeView = [[MPVolumeView alloc] init]; for (UIView *view in volumeView.subviews) { if ([view isKindOfClass:[UISlider class]]) { volumeViewSlider = (UISlider *)view; break; } } + feedbackGenerator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleMedium]; + }); + // Get objects used to seek nicely in the video player + static YTMainAppVideoPlayerOverlayViewController *mainVideoPlayerController = (YTMainAppVideoPlayerOverlayViewController *)self.childViewControllers.firstObject; + static YTPlayerBarController *playerBarController = mainVideoPlayerController.playerBarController; + static YTInlinePlayerBarContainerView *playerBar = playerBarController.playerBar; + +/***** Helper functions for adjusting player state *****/ + // Helper function to adjust brightness + void (^adjustBrightness)(CGFloat, CGFloat) = ^(CGFloat translationX, CGFloat initialBrightness) { + float brightnessSensitivityFactor = 3; + float newBrightness = initialBrightness + ((translationX / 1000.0) * sensitivityFactor * brightnessSensitivityFactor); + newBrightness = fmaxf(fminf(newBrightness, 1.0), 0.0); + [[UIScreen mainScreen] setBrightness:newBrightness]; + }; + + // Helper function to adjust volume + void (^adjustVolume)(CGFloat, CGFloat) = ^(CGFloat translationX, CGFloat initialVolume) { + float volumeSensitivityFactor = 3.0; + float newVolume = initialVolume + ((translationX / 1000.0) * sensitivityFactor * volumeSensitivityFactor); + newVolume = fmaxf(fminf(newVolume, 1.0), 0.0); + // Improve smoothness - ignore if the volume is within 0.01 of the current volume + CGFloat currentVolume = [[AVAudioSession sharedInstance] outputVolume]; + if (fabs(newVolume - currentVolume) < 0.01 && currentVolume > 0.01 && currentVolume < 0.99) { + return; + } + // https://stackoverflow.com/questions/50737943/how-to-change-volume-programmatically-on-ios-11-4 + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ volumeViewSlider.value = newVolume; }); }; + // Helper function to adjust seek time - void (^adjustSeek)(CGFloat, CGFloat) = ^(CGFloat translationX, CGFloat currentTime) { - // Calculate a seek fraction based on the horizontal translation - CGFloat totalDuration = self.currentVideoTotalMediaTime; - CGFloat viewWidth = self.view.bounds.size.width; - CGFloat seekFraction = (translationX / viewWidth); - // Seek to the new time based on the calculated offset - CGFloat sensitivityFactor = 1; // Adjust this value to make seeking less sensitive - seekFraction = sensitivityFactor * seekFraction; - CGFloat seekTime = currentTime + totalDuration * seekFraction; - [self seekToTime:seekTime]; + void (^adjustSeek)(CGFloat, CGFloat) = ^(CGFloat translationX, CGFloat initialTime) { + // Get the location in view for the current video time + CGFloat totalTime = self.currentVideoTotalMediaTime; + CGFloat videoFraction = initialTime / totalTime; + CGFloat initialTimeXPosition = [playerBar scrubXForScrubRange:videoFraction]; + // Calculate the new seek X position + CGFloat sensitivityFactor = 1; // Adjust this value to make seeking more/less sensitive + CGFloat newSeekXPosition = initialTimeXPosition + translationX * sensitivityFactor; + // Create a CGPoint using this new X position + CGPoint newSeekPoint = CGPointMake(newSeekXPosition, 0); + // Send this to a seek method in the player bar controller + [playerBarController didScrubToPoint:newSeekPoint]; }; - // Helper function to run the selected gesture action - void (^runSelectedGesture)(NSString*, CGFloat, CGFloat, CGFloat, CGFloat) - = ^(NSString *sectionKey, CGFloat translationX, CGFloat initialBrightness, CGFloat initialVolume, CGFloat currentTime) { + + // Helper function to smooth out the X translation + CGFloat (^applyLowPassFilter)(CGFloat) = ^(CGFloat newTranslation) { + smoothedTranslationX = filterConstant * newTranslation + (1 - filterConstant) * smoothedTranslationX; + return smoothedTranslationX; + }; + +/***** Helper functions for running the selected gesture *****/ + // Helper function to run any setup for the selected gesture mode + void (^runSelectedGestureSetup)(NSString*) = ^(NSString *sectionKey) { + // Determine the selected gesture mode using the section key + GestureMode selectedGestureMode = (GestureMode)GetInteger(sectionKey); + // Handle the setup based on the selected mode + switch (selectedGestureMode) { + case GestureModeVolume: + initialVolume = [[AVAudioSession sharedInstance] outputVolume]; + break; + case GestureModeBrightness: + initialBrightness = [UIScreen mainScreen].brightness; + break; + case GestureModeSeek: + initialTime = self.currentVideoMediaTime; + // Start a seek action + [playerBarController startScrubbing]; + break; + case GestureModeDisabled: + // Do nothing if the gesture is disabled + break; + default: + // Show an alert if the gesture mode is invalid + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Invalid Gesture Mode" message:@"Please report this bug." preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]; + [alertController addAction:okAction]; + [self presentViewController:alertController animated:YES completion:nil]; + break; + } + }; + + // Helper function to run the selected gesture action when the gesture changes + void (^runSelectedGestureChanged)(NSString*) = ^(NSString *sectionKey) { // Determine the selected gesture mode using the section key GestureMode selectedGestureMode = (GestureMode)GetInteger(sectionKey); // Handle the gesture action based on the selected mode switch (selectedGestureMode) { case GestureModeVolume: - adjustVolume(translationX, initialVolume); + adjustVolume(adjustedTranslationX, initialVolume); break; case GestureModeBrightness: - adjustBrightness(translationX, initialBrightness); + adjustBrightness(adjustedTranslationX, initialBrightness); break; case GestureModeSeek: - adjustSeek(translationX, currentTime); + adjustSeek(adjustedTranslationX, initialTime); break; case GestureModeDisabled: // Do nothing if the gesture is disabled @@ -747,6 +821,31 @@ BOOL isTabSelected = NO; break; } }; + + // Helper function to run the selected gesture action when the gesture ends + void (^runSelectedGestureEnded)(NSString*) = ^(NSString *sectionKey) { + // Determine the selected gesture mode using the section key + GestureMode selectedGestureMode = (GestureMode)GetInteger(sectionKey); + // Handle the gesture action based on the selected mode + switch (selectedGestureMode) { + case GestureModeVolume: + break; + case GestureModeBrightness: + break; + case GestureModeSeek: + [playerBarController endScrubbingForSeekSource:0]; + break; + case GestureModeDisabled: + break; + default: + // Show an alert if the gesture mode is invalid + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Invalid Gesture Mode" message:@"Please report this bug." preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]; + [alertController addAction:okAction]; + [self presentViewController:alertController animated:YES completion:nil]; + break; + } + }; /***** End of Helper functions *****/ // Handle gesture based on current gesture state @@ -754,37 +853,29 @@ BOOL isTabSelected = NO; // Get the gesture's start position startLocation = [panGestureRecognizer locationInView:self.view]; CGFloat viewHeight = self.view.bounds.size.height; - - // Determine the section based on the start position - // by dividing the view into thirds + // Determine the section based on the start position by dividing the view into thirds if (startLocation.y <= viewHeight / 3.0) { gestureSection = GestureSectionTop; - // Cancel the gesture if the mode is disabled - if (GetInteger(@"playerGestureTopSelection") == GestureModeDisabled) { - panGestureRecognizer.state = UIGestureRecognizerStateCancelled; - return; - } } else if (startLocation.y <= 2 * viewHeight / 3.0) { gestureSection = GestureSectionMiddle; - // Cancel the gesture if the mode is disabled - if (GetInteger(@"playerGestureMiddleSelection") == GestureModeDisabled) { - panGestureRecognizer.state = UIGestureRecognizerStateCancelled; - return; - } } else if (startLocation.y <= viewHeight) { gestureSection = GestureSectionBottom; - // Cancel the gesture if the mode is disabled - if (GetInteger(@"playerGestureBottomSelection") == GestureModeDisabled) { - panGestureRecognizer.state = UIGestureRecognizerStateCancelled; - return; - } } else { gestureSection = GestureSectionInvalid; } + // Cancel the gesture if the chosen mode for this section is disabled + if ( ((gestureSection == GestureSectionTop) && (GetInteger(@"playerGestureTopSelection") == GestureModeDisabled)) + || ((gestureSection == GestureSectionMiddle) && (GetInteger(@"playerGestureMiddleSelection") == GestureModeDisabled)) + || ((gestureSection == GestureSectionBottom) && (GetInteger(@"playerGestureBottomSelection") == GestureModeDisabled))) { + panGestureRecognizer.state = UIGestureRecognizerStateCancelled; + return; + } // Deactive the activity flag isValidHorizontalPan = NO; } + // Handle changed gesture state by activating the gesture once it has exited the deadzone, + // and then adjusting the player based on the selected gesture mode if (panGestureRecognizer.state == UIGestureRecognizerStateChanged) { // Determine if the gesture is predominantly horizontal CGPoint translation = [panGestureRecognizer translationInView:self.view]; @@ -799,12 +890,29 @@ BOOL isTabSelected = NO; // If outside the deadzone, activate the pan gesture and store the initial values isValidHorizontalPan = YES; deadzoneStartingXTranslation = translation.x; - initialBrightness = [UIScreen mainScreen].brightness; - initialVolume = [[AVAudioSession sharedInstance] outputVolume]; - currentTime = self.currentVideoMediaTime; + adjustedTranslationX = 0; + smoothedTranslationX = 0; + // Run the setup for the selected gesture mode + switch (gestureSection) { + case GestureSectionTop: + runSelectedGestureSetup(@"playerGestureTopSelection"); + break; + case GestureSectionMiddle: + runSelectedGestureSetup(@"playerGestureMiddleSelection"); + break; + case GestureSectionBottom: + runSelectedGestureSetup(@"playerGestureBottomSelection"); + break; + default: + // If the section is invalid, cancel the gesture + panGestureRecognizer.state = UIGestureRecognizerStateCancelled; + break; + } // Provide haptic feedback to indicate a gesture start - [feedbackGenerator prepare]; - [feedbackGenerator impactOccurred]; + if (IS_ENABLED(@"playerGesturesHapticFeedback_enabled")) { + [feedbackGenerator prepare]; + [feedbackGenerator impactOccurred]; + } } else { // Cancel the gesture if the translation is not horizontal panGestureRecognizer.state = UIGestureRecognizerStateCancelled; @@ -814,34 +922,72 @@ BOOL isTabSelected = NO; // Handle the gesture based on the identified section if (isValidHorizontalPan) { - // Adjust the X translation based on the value hit after - // exiting the deadzone - CGFloat adjustedTranslationX = translation.x - deadzoneStartingXTranslation; + // Adjust the X translation based on the value hit after exiting the deadzone + adjustedTranslationX = translation.x - deadzoneStartingXTranslation; + // Smooth the translation value + adjustedTranslationX = applyLowPassFilter(adjustedTranslationX); // Pass the adjusted translation to the selected gesture - if (gestureSection == GestureSectionTop) { - runSelectedGesture(@"playerGestureTopSelection", adjustedTranslationX, initialBrightness, initialVolume, currentTime); - } else if (gestureSection == GestureSectionMiddle) { - runSelectedGesture(@"playerGestureMiddleSelection", adjustedTranslationX, initialBrightness, initialVolume, currentTime); - } else if (gestureSection == GestureSectionBottom) { - runSelectedGesture(@"playerGestureBottomSelection", adjustedTranslationX, initialBrightness, initialVolume, currentTime); - } else { - // If the section is invalid, cancel the gesture - panGestureRecognizer.state = UIGestureRecognizerStateCancelled; + switch (gestureSection) { + case GestureSectionTop: + runSelectedGestureChanged(@"playerGestureTopSelection"); + break; + case GestureSectionMiddle: + runSelectedGestureChanged(@"playerGestureMiddleSelection"); + break; + case GestureSectionBottom: + runSelectedGestureChanged(@"playerGestureBottomSelection"); + break; + default: + // If the section is invalid, cancel the gesture + panGestureRecognizer.state = UIGestureRecognizerStateCancelled; + break; } } } - if (panGestureRecognizer.state == UIGestureRecognizerStateEnded) { - if (isValidHorizontalPan) { - // Provide haptic feedback upon successful gesture recognition - [feedbackGenerator prepare]; - [feedbackGenerator impactOccurred]; + + // Handle the gesture end state by running the selected gesture mode's end action + if (panGestureRecognizer.state == UIGestureRecognizerStateEnded && isValidHorizontalPan) { + switch (gestureSection) { + case GestureSectionTop: + runSelectedGestureEnded(@"playerGestureTopSelection"); + break; + case GestureSectionMiddle: + runSelectedGestureEnded(@"playerGestureMiddleSelection"); + break; + case GestureSectionBottom: + runSelectedGestureEnded(@"playerGestureBottomSelection"); + break; + default: + break; } + // Provide haptic feedback upon successful gesture recognition + // [feedbackGenerator prepare]; + // [feedbackGenerator impactOccurred]; } } // allow the pan gesture to be recognized simultaneously with other gestures %new - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { + if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]] && [otherGestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) { + // Do not allow this gesture to activate with the normal seek bar gesture + YTMainAppVideoPlayerOverlayViewController *mainVideoPlayerController = (YTMainAppVideoPlayerOverlayViewController *)self.childViewControllers.firstObject; + YTPlayerBarController *playerBarController = mainVideoPlayerController.playerBarController; + YTInlinePlayerBarContainerView *playerBar = playerBarController.playerBar; + if (otherGestureRecognizer == playerBar.scrubGestureRecognizer) { + return NO; + } + // Do not allow this gesture to activate with the fine scrubber gesture + YTFineScrubberFilmstripView *fineScrubberFilmstrip = playerBar.fineScrubberFilmstrip; + if (!fineScrubberFilmstrip) { + return YES; + } + YTFineScrubberFilmstripCollectionView *filmstripCollectionView = [fineScrubberFilmstrip valueForKey:@"_filmstripCollectionView"]; + if (filmstripCollectionView && otherGestureRecognizer == filmstripCollectionView.panGestureRecognizer) { + return NO; + } + + } return YES; } %end @@ -1156,7 +1302,7 @@ NSInteger pageStyle = 0; %init(gDisableEngagementOverlay); } if (IsEnabled(@"playerGestures_enabled")) { - %init(playerGestures); + %init(gPlayerGestures); } if (IsEnabled(@"videoPlayerButton_enabled")) { %init(gVideoPlayerButton); @@ -1196,4 +1342,7 @@ NSInteger pageStyle = 0; if (![allKeys containsObject:@"playerGesturesSensitivity"]) { [[NSUserDefaults standardUserDefaults] setFloat:1.0 forKey:@"playerGesturesSensitivity"]; } + if (![allKeys containsObject:@"playerGesturesHapticFeedback_enabled"]) { + [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"playerGesturesHapticFeedback_enabled"]; + } } diff --git a/lang/YTLitePlus.bundle/ar.lproj/Localizable.strings b/lang/YTLitePlus.bundle/ar.lproj/Localizable.strings index 026cecc..caf6eae 100644 --- a/lang/YTLitePlus.bundle/ar.lproj/Localizable.strings +++ b/lang/YTLitePlus.bundle/ar.lproj/Localizable.strings @@ -31,6 +31,7 @@ "TOP_SECTION" = "Top Section"; "MIDDLE_SECTION" = "Middle Section"; "BOTTOM_SECTION" = "Bottom Section"; +"PLAYER_GESTURES_HAPTIC_FEEDBACK" = "Enable Haptic Feedback"; // Video controls overlay options "VIDEO_CONTROLS_OVERLAY_OPTIONS" = "خيارات تراكب ضوابط الفيديو"; diff --git a/lang/YTLitePlus.bundle/bg.lproj/Localizable.strings b/lang/YTLitePlus.bundle/bg.lproj/Localizable.strings index b23def9..8f0be9c 100644 --- a/lang/YTLitePlus.bundle/bg.lproj/Localizable.strings +++ b/lang/YTLitePlus.bundle/bg.lproj/Localizable.strings @@ -31,6 +31,7 @@ "TOP_SECTION" = "Top Section"; "MIDDLE_SECTION" = "Middle Section"; "BOTTOM_SECTION" = "Bottom Section"; +"PLAYER_GESTURES_HAPTIC_FEEDBACK" = "Enable Haptic Feedback"; // Video controls overlay options "VIDEO_CONTROLS_OVERLAY_OPTIONS" = "Опции за контрол на видеото"; diff --git a/lang/YTLitePlus.bundle/de.lproj/Localizable.strings b/lang/YTLitePlus.bundle/de.lproj/Localizable.strings index 3cda752..81a0374 100644 --- a/lang/YTLitePlus.bundle/de.lproj/Localizable.strings +++ b/lang/YTLitePlus.bundle/de.lproj/Localizable.strings @@ -31,6 +31,7 @@ "TOP_SECTION" = "Top Section"; "MIDDLE_SECTION" = "Middle Section"; "BOTTOM_SECTION" = "Bottom Section"; +"PLAYER_GESTURES_HAPTIC_FEEDBACK" = "Enable Haptic Feedback"; // Video controls overlay options "VIDEO_CONTROLS_OVERLAY_OPTIONS" = "Overlay-Optionen für Videosteuerungen"; diff --git a/lang/YTLitePlus.bundle/en.lproj/Localizable.strings b/lang/YTLitePlus.bundle/en.lproj/Localizable.strings index b946b85..0803b06 100644 --- a/lang/YTLitePlus.bundle/en.lproj/Localizable.strings +++ b/lang/YTLitePlus.bundle/en.lproj/Localizable.strings @@ -31,6 +31,7 @@ "TOP_SECTION" = "Top Section"; "MIDDLE_SECTION" = "Middle Section"; "BOTTOM_SECTION" = "Bottom Section"; +"PLAYER_GESTURES_HAPTIC_FEEDBACK" = "Enable Haptic Feedback"; // Video controls overlay options "VIDEO_CONTROLS_OVERLAY_OPTIONS" = "Video Controls Overlay Options"; diff --git a/lang/YTLitePlus.bundle/es.lproj/Localizable.strings b/lang/YTLitePlus.bundle/es.lproj/Localizable.strings index e948959..604bdbe 100644 --- a/lang/YTLitePlus.bundle/es.lproj/Localizable.strings +++ b/lang/YTLitePlus.bundle/es.lproj/Localizable.strings @@ -31,6 +31,7 @@ "TOP_SECTION" = "Top Section"; "MIDDLE_SECTION" = "Middle Section"; "BOTTOM_SECTION" = "Bottom Section"; +"PLAYER_GESTURES_HAPTIC_FEEDBACK" = "Enable Haptic Feedback"; // Video controls overlay options "VIDEO_CONTROLS_OVERLAY_OPTIONS" = "Opciones de superposición de controles de vídeo"; diff --git a/lang/YTLitePlus.bundle/fr.lproj/Localizable.strings b/lang/YTLitePlus.bundle/fr.lproj/Localizable.strings index d013f36..378c71b 100644 --- a/lang/YTLitePlus.bundle/fr.lproj/Localizable.strings +++ b/lang/YTLitePlus.bundle/fr.lproj/Localizable.strings @@ -31,6 +31,7 @@ "TOP_SECTION" = "Top Section"; "MIDDLE_SECTION" = "Middle Section"; "BOTTOM_SECTION" = "Bottom Section"; +"PLAYER_GESTURES_HAPTIC_FEEDBACK" = "Enable Haptic Feedback"; // Video controls overlay options "VIDEO_CONTROLS_OVERLAY_OPTIONS" = "Options de l'overlay des contrôles vidéo"; diff --git a/lang/YTLitePlus.bundle/ja.lproj/Localizable.strings b/lang/YTLitePlus.bundle/ja.lproj/Localizable.strings index 30d55fc..ab97363 100644 --- a/lang/YTLitePlus.bundle/ja.lproj/Localizable.strings +++ b/lang/YTLitePlus.bundle/ja.lproj/Localizable.strings @@ -31,6 +31,7 @@ "TOP_SECTION" = "Top Section"; "MIDDLE_SECTION" = "Middle Section"; "BOTTOM_SECTION" = "Bottom Section"; +"PLAYER_GESTURES_HAPTIC_FEEDBACK" = "Enable Haptic Feedback"; // Video controls overlay options "VIDEO_CONTROLS_OVERLAY_OPTIONS" = "動画コントロールオーバーレイの設定"; diff --git a/lang/YTLitePlus.bundle/pt.lproj/Localizable.strings b/lang/YTLitePlus.bundle/pt.lproj/Localizable.strings index 0ee549a..e5734e0 100644 --- a/lang/YTLitePlus.bundle/pt.lproj/Localizable.strings +++ b/lang/YTLitePlus.bundle/pt.lproj/Localizable.strings @@ -31,6 +31,7 @@ "TOP_SECTION" = "Top Section"; "MIDDLE_SECTION" = "Middle Section"; "BOTTOM_SECTION" = "Bottom Section"; +"PLAYER_GESTURES_HAPTIC_FEEDBACK" = "Enable Haptic Feedback"; // Video controls overlay options "VIDEO_CONTROLS_OVERLAY_OPTIONS" = "Opções de Sobreposição de Controles de Vídeo"; diff --git a/lang/YTLitePlus.bundle/ro.lproj/Localizable.strings b/lang/YTLitePlus.bundle/ro.lproj/Localizable.strings index b3a6364..72b5002 100644 --- a/lang/YTLitePlus.bundle/ro.lproj/Localizable.strings +++ b/lang/YTLitePlus.bundle/ro.lproj/Localizable.strings @@ -31,6 +31,7 @@ "TOP_SECTION" = "Top Section"; "MIDDLE_SECTION" = "Middle Section"; "BOTTOM_SECTION" = "Bottom Section"; +"PLAYER_GESTURES_HAPTIC_FEEDBACK" = "Enable Haptic Feedback"; // Video controls overlay options "VIDEO_CONTROLS_OVERLAY_OPTIONS" = "Opțiuni Overlay Controale Video"; diff --git a/lang/YTLitePlus.bundle/ru.lproj/Localizable.strings b/lang/YTLitePlus.bundle/ru.lproj/Localizable.strings index 9ae37ae..253c9c3 100644 --- a/lang/YTLitePlus.bundle/ru.lproj/Localizable.strings +++ b/lang/YTLitePlus.bundle/ru.lproj/Localizable.strings @@ -31,6 +31,7 @@ "TOP_SECTION" = "Top Section"; "MIDDLE_SECTION" = "Middle Section"; "BOTTOM_SECTION" = "Bottom Section"; +"PLAYER_GESTURES_HAPTIC_FEEDBACK" = "Enable Haptic Feedback"; // Video controls overlay options "VIDEO_CONTROLS_OVERLAY_OPTIONS" = "Video Controls Overlay Options"; diff --git a/lang/YTLitePlus.bundle/template.lproj/Localizable.strings b/lang/YTLitePlus.bundle/template.lproj/Localizable.strings index 8cde9a3..2c01dc1 100644 --- a/lang/YTLitePlus.bundle/template.lproj/Localizable.strings +++ b/lang/YTLitePlus.bundle/template.lproj/Localizable.strings @@ -46,6 +46,7 @@ https://github.com/PoomSmart/Return-YouTube-Dislikes/tree/main/layout/Library/Ap "TOP_SECTION" = "Top Section"; "MIDDLE_SECTION" = "Middle Section"; "BOTTOM_SECTION" = "Bottom Section"; +"PLAYER_GESTURES_HAPTIC_FEEDBACK" = "Enable Haptic Feedback"; // Video controls overlay options "VIDEO_CONTROLS_OVERLAY_OPTIONS" = "Video Controls Overlay Options"; diff --git a/lang/YTLitePlus.bundle/tr.lproj/Localizable.strings b/lang/YTLitePlus.bundle/tr.lproj/Localizable.strings index a90afe2..ac169c9 100644 --- a/lang/YTLitePlus.bundle/tr.lproj/Localizable.strings +++ b/lang/YTLitePlus.bundle/tr.lproj/Localizable.strings @@ -31,6 +31,7 @@ "TOP_SECTION" = "Top Section"; "MIDDLE_SECTION" = "Middle Section"; "BOTTOM_SECTION" = "Bottom Section"; +"PLAYER_GESTURES_HAPTIC_FEEDBACK" = "Enable Haptic Feedback"; // Video controls overlay options "VIDEO_CONTROLS_OVERLAY_OPTIONS" = "Video Kontrol Seç."; diff --git a/lang/YTLitePlus.bundle/vi.lproj/Localizable.strings b/lang/YTLitePlus.bundle/vi.lproj/Localizable.strings index 21f02b4..ea69a3f 100644 --- a/lang/YTLitePlus.bundle/vi.lproj/Localizable.strings +++ b/lang/YTLitePlus.bundle/vi.lproj/Localizable.strings @@ -31,6 +31,7 @@ "TOP_SECTION" = "Top Section"; "MIDDLE_SECTION" = "Middle Section"; "BOTTOM_SECTION" = "Bottom Section"; +"PLAYER_GESTURES_HAPTIC_FEEDBACK" = "Enable Haptic Feedback"; // Video player options "VIDEO_PLAYER_OPTIONS" = "Tùy chọn trình phát video"; diff --git a/lang/YTLitePlus.bundle/zh_TW.lproj/Localizable.strings b/lang/YTLitePlus.bundle/zh_TW.lproj/Localizable.strings index 2356280..4d53485 100644 --- a/lang/YTLitePlus.bundle/zh_TW.lproj/Localizable.strings +++ b/lang/YTLitePlus.bundle/zh_TW.lproj/Localizable.strings @@ -32,6 +32,7 @@ "TOP_SECTION" = "Top Section"; "MIDDLE_SECTION" = "Middle Section"; "BOTTOM_SECTION" = "Bottom Section"; +"PLAYER_GESTURES_HAPTIC_FEEDBACK" = "Enable Haptic Feedback"; // Video controls overlay options "VIDEO_CONTROLS_OVERLAY_OPTIONS" = "影片區覆蓋按鈕設定";