public static IDisposable RevealSwipeActionAnimation(this TimeEntriesLogViewCell cell, Direction direction) { if (cell == null) { return(Disposable.Empty); } var animation = CAKeyFrameAnimation.FromKeyPath("transform.translation.x"); animation.Duration = 1.51; animation.TimingFunctions = new CAMediaTimingFunction[] { new CAMediaTimingFunction(1f, 1f, 1f, 1f), new CAMediaTimingFunction(.1f, .2f, .1f, 0f), new CAMediaTimingFunction(.1f, .1f, .1f, 0f), new CAMediaTimingFunction(.0f, .32f, .7f, 1f), new CAMediaTimingFunction(.38f, .1f, .7f, 1f), new CAMediaTimingFunction(1f, 1f, 1f, 1f) }; animation.KeyTimes = new NSNumber[] { 0.0, 0.5, 0.6, 0.73, 0.81, 1.51 }; animation.Values = new[] { NSObject.FromObject(0.0), NSObject.FromObject((double)direction * 50.0), NSObject.FromObject(0.0), NSObject.FromObject((double)direction * 3.5), NSObject.FromObject(0.0), NSObject.FromObject(0.0) }; animation.RepeatCount = int.MaxValue; cell.Layer.AddAnimation(animation, direction.ToString()); return(animation); }
private void updateSwipeDismissGestures(TimeEntriesLogViewCell nextFirstTimeEntry) { if (swipeLeftGestureRecognizer != null) { firstTimeEntry?.RemoveGestureRecognizer(swipeLeftGestureRecognizer); } swipeLeftAnimationDisposable?.Dispose(); swipeRightAnimationDisposable?.Dispose(); if (nextFirstTimeEntry == null) { return; } swipeLeftAnimationDisposable = swipeLeftStep.ManageSwipeActionAnimationOf(nextFirstTimeEntry, Direction.Left); swipeRightAnimationDisposable = swipeRightStep.ManageSwipeActionAnimationOf(nextFirstTimeEntry, Direction.Right); swipeLeftGestureRecognizer = swipeLeftStep.DismissBySwiping(nextFirstTimeEntry, Direction.Left); }
private void onFirstTimeEntryChanged(TimeEntriesLogViewCell nextFirstTimeEntry) { updateSwipeDismissGestures(nextFirstTimeEntry); firstTimeEntry = nextFirstTimeEntry; updateTooltipPositions(); }
public override void ViewDidLoad() { base.ViewDidLoad(); SwipeRightBubbleLabel.Text = Resources.SwipeRightToContinue; SwipeLeftBubbleLabel.Text = Resources.SwipeLeftToDelete; WelcomeBackLabel.Text = Resources.LogEmptyStateTitle; WelcomeBackDescriptionLabel.Text = Resources.LogEmptyStateText; CreatedFirstTimeEntryLabel.Text = Resources.YouHaveCreatedYourFirstTimeEntry; TapToEditItLabel.Text = Resources.TapToEditIt; StartTimerBubbleLabel.Text = Resources.TapToStartTimer; TapToStopTimerLabel.Text = Resources.TapToStopTimer; FeedbackSentSuccessTitleLabel.Text = Resources.DoneWithExclamationMark.ToUpper(); FeedbackSentDescriptionLabel.Text = Resources.ThankYouForTheFeedback; prepareViews(); prepareOnboarding(); setupTableViewHeader(); tableViewSource = new TimeEntriesLogViewSource(); TimeEntriesLogTableView.Source = tableViewSource; ViewModel.TimeEntries .Subscribe(TimeEntriesLogTableView.Rx().AnimateSections <MainLogSection, DaySummaryViewModel, LogItemViewModel, IMainLogKey>(tableViewSource)) .DisposedBy(disposeBag); ViewModel.ShouldReloadTimeEntryLog .WithLatestFrom(ViewModel.TimeEntries, (_, timeEntries) => timeEntries) .Subscribe(TimeEntriesLogTableView.Rx().ReloadSections(tableViewSource)) .DisposedBy(disposeBag); tableViewSource.ToggleGroupExpansion .Subscribe(ViewModel.TimeEntriesViewModel.ToggleGroupExpansion.Inputs) .DisposedBy(disposeBag); tableViewSource.FirstCell .Subscribe(f => { onFirstTimeEntryChanged(f); firstTimeEntryCell = f; }) .DisposedBy(DisposeBag); tableViewSource.Rx().Scrolled() .Subscribe(onTableScroll) .DisposedBy(DisposeBag); tableViewSource.ContinueTap .Select(item => timeEntryContinuation(item, false)) .Subscribe(ViewModel.ContinueTimeEntry.Inputs) .DisposedBy(DisposeBag); tableViewSource.SwipeToContinue .Select(item => timeEntryContinuation(item, true)) .Subscribe(ViewModel.ContinueTimeEntry.Inputs) .DisposedBy(DisposeBag); tableViewSource.SwipeToDelete .Select(logItem => logItem.RepresentedTimeEntriesIds) .Subscribe(ViewModel.TimeEntriesViewModel.DelayDeleteTimeEntries.Inputs) .DisposedBy(DisposeBag); tableViewSource.Rx().ModelSelected() .Select(editEventInfo) .Subscribe(ViewModel.SelectTimeEntry.Inputs) .DisposedBy(DisposeBag); ViewModel.TimeEntriesViewModel.TimeEntriesPendingDeletion .Subscribe(toggleUndoDeletion) .DisposedBy(DisposeBag); tableViewSource.SwipeToContinue .Subscribe(_ => swipeRightStep.Dismiss()) .DisposedBy(disposeBag); tableViewSource.SwipeToDelete .Subscribe(_ => swipeLeftStep.Dismiss()) .DisposedBy(disposeBag); // Refresh Control var refreshControl = new RefreshControl( ViewModel.SyncProgressState, tableViewSource.Rx().Scrolled(), tableViewSource.IsDragging); refreshControl.Refresh .Subscribe(ViewModel.Refresh.Inputs) .DisposedBy(DisposeBag); TimeEntriesLogTableView.CustomRefreshControl = refreshControl; //Actions settingsButton.Rx().BindAction(ViewModel.OpenSettings).DisposedBy(DisposeBag); syncFailuresButton.Rx().BindAction(ViewModel.OpenSyncFailures).DisposedBy(DisposeBag); StopTimeEntryButton.Rx().BindAction(ViewModel.StopTimeEntry, _ => TimeEntryStopOrigin.Manual).DisposedBy(DisposeBag); StartTimeEntryButton.Rx().BindAction(ViewModel.StartTimeEntry, _ => true).DisposedBy(DisposeBag); StartTimeEntryButton.Rx().BindAction(ViewModel.StartTimeEntry, _ => false, ButtonEventType.LongPress).DisposedBy(DisposeBag); CurrentTimeEntryCard.Rx().Tap() .WithLatestFrom(ViewModel.CurrentRunningTimeEntry, (_, te) => te) .Where(te => te != null) .Select(te => (new[] { te.Id }, EditTimeEntryOrigin.RunningTimeEntryCard)) .Subscribe(ViewModel.SelectTimeEntry.Inputs) .DisposedBy(DisposeBag); //Visibility var shouldWelcomeBack = ViewModel.ShouldShowWelcomeBack; ViewModel.ShouldShowEmptyState .Subscribe(visible => emptyStateView.Hidden = !visible) .DisposedBy(DisposeBag); shouldWelcomeBack .Subscribe(WelcomeBackView.Rx().IsVisible()) .DisposedBy(DisposeBag); shouldWelcomeBack .Subscribe(spiderContainerView.Rx().IsVisible()) .DisposedBy(DisposeBag); shouldWelcomeBack .Subscribe(visible => { if (visible) { spiderBroView.Show(); } else { spiderBroView.Hide(); } }) .DisposedBy(DisposeBag); //Text ViewModel.CurrentRunningTimeEntry .Select(te => te?.Description) .Subscribe(CurrentTimeEntryDescriptionLabel.Rx().Text()) .DisposedBy(DisposeBag); ViewModel.ElapsedTime .Subscribe(CurrentTimeEntryElapsedTimeLabel.Rx().Text()) .DisposedBy(DisposeBag); var capHeight = CurrentTimeEntryProjectTaskClientLabel.Font.CapHeight; var clientColor = Colors.Main.CurrentTimeEntryClientColor.ToNativeColor(); ViewModel.CurrentRunningTimeEntry .Select(te => te?.ToFormattedTimeEntryString(capHeight, clientColor, shouldColorProject: true)) .Subscribe(CurrentTimeEntryProjectTaskClientLabel.Rx().AttributedText()) .DisposedBy(DisposeBag); //The start button var trackModeImage = UIImage.FromBundle("playIcon"); var manualModeImage = UIImage.FromBundle("manualIcon"); ViewModel.IsInManualMode .Select(isInManualMode => isInManualMode ? manualModeImage : trackModeImage) .Subscribe(image => StartTimeEntryButton.SetImage(image, UIControlState.Normal)) .DisposedBy(DisposeBag); //The sync failures button ViewModel.NumberOfSyncFailures .Select(numberOfSyncFailures => numberOfSyncFailures > 0) .Subscribe(syncFailuresButton.Rx().IsVisible()) .DisposedBy(DisposeBag); ViewModel.RatingViewModel.IsFeedbackSuccessViewShowing .Subscribe(SendFeedbackSuccessView.Rx().AnimatedIsVisible()) .DisposedBy(DisposeBag); SendFeedbackSuccessView.Rx().Tap() .Subscribe(ViewModel.RatingViewModel.CloseFeedbackSuccessView) .DisposedBy(DisposeBag); ViewModel.ShouldShowRatingView .Subscribe(showHideRatingView) .DisposedBy(disposeBag); // Suggestion View suggestionsView.SuggestionTapped .Subscribe(ViewModel.SuggestionsViewModel.StartTimeEntry.Inputs) .DisposedBy(DisposeBag); ViewModel.SuggestionsViewModel.IsEmpty.Invert() .Subscribe(suggestionsView.Rx().IsVisible()) .DisposedBy(DisposeBag); ViewModel.SuggestionsViewModel.Suggestions .ReemitWhen(traitCollectionSubject) .Subscribe(suggestionsView.OnSuggestions) .DisposedBy(DisposeBag); View.SetNeedsLayout(); View.LayoutIfNeeded(); NSNotificationCenter.DefaultCenter.AddObserver(UIApplication.DidBecomeActiveNotification, onApplicationDidBecomeActive); }
public static IDisposable ManageSwipeActionAnimationOf(this IOnboardingStep step, TimeEntriesLogViewCell cell, Direction direction) { IDisposable animation = null; void toggleVisibility(bool shouldBeVisible) { var isVisible = animation != null; if (isVisible == shouldBeVisible) { return; } if (shouldBeVisible) { animation = cell.RevealSwipeActionAnimation(direction); } else { cell.Layer.RemoveAnimation(direction.ToString()); animation?.Dispose(); animation = null; } } var subscriptionDisposable = step.ShouldBeVisible.Subscribe(toggleVisibility); return(Disposable.Create(() => { cell?.Layer.RemoveAllAnimations(); animation?.Dispose(); animation = null; subscriptionDisposable?.Dispose(); subscriptionDisposable = null; })); }
public static UIPanGestureRecognizer DismissBySwiping(this DismissableOnboardingStep step, TimeEntriesLogViewCell cell, Direction direction) { async void onGesture(UIPanGestureRecognizer recognizer) { var isOneTouch = recognizer.NumberOfTouches == 1; var isVisible = await step.ShouldBeVisible.FirstAsync(); var isInDesiredDirection = Sign(recognizer.VelocityInView(cell).X) == Sign((int)direction); if (isOneTouch && isVisible && isInDesiredDirection) { step.Dismiss(); } } var panGestureRecognizer = new UIPanGestureRecognizer(onGesture) { ShouldRecognizeSimultaneously = (a, b) => true }; IDisposable visibilityDisposable = null; visibilityDisposable = step.ShouldBeVisible .Where(visible => visible == false) .ObserveOn(SynchronizationContext.Current) .Subscribe(_ => { cell.RemoveGestureRecognizer(panGestureRecognizer); visibilityDisposable?.Dispose(); }); cell.AddGestureRecognizer(panGestureRecognizer); return(panGestureRecognizer); }
private void onFirstTimeEntryChanged(TimeEntriesLogViewCell nextFirstTimeEntry) { firstTimeEntryCell = nextFirstTimeEntry; updateTooltipPositions(); }
public override void ViewDidLoad() { base.ViewDidLoad(); prepareViews(); prepareOnboarding(); setupTableViewHeader(); var visibilityConverter = new MvxVisibilityValueConverter(); var projectTaskClientCombiner = new ProjectTaskClientValueCombiner( CurrentTimeEntryProjectTaskClientLabel.Font.CapHeight, Color.Main.CurrentTimeEntryClientColor.ToNativeColor(), true ); var startTimeEntryButtonManualModeIconConverter = new BoolToConstantValueConverter <UIImage>( UIImage.FromBundle("manualIcon"), UIImage.FromBundle("playIcon") ); var durationCombiner = new DurationValueCombiner(); var bindingSet = this.CreateBindingSet <MainViewController, MainViewModel>(); // Table view tableViewSource = new TimeEntriesLogViewSource(ViewModel.TimeEntries, TimeEntriesLogViewCell.Identifier); TimeEntriesLogTableView .Rx() .Bind(tableViewSource) .DisposedBy(disposeBag); this.Bind(tableViewSource.FirstCell, f => { onFirstTimeEntryChanged(f); firstTimeEntryCell = f; }); this.Bind(tableViewSource.ScrollOffset, onTableScroll); var continueTimeEntry = Observable.Merge( tableViewSource.ContinueTap, tableViewSource.SwipeToContinue ); this.Bind(continueTimeEntry, ViewModel.ContinueTimeEntry); this.Bind(tableViewSource.SwipeToDelete, ViewModel.TimeEntriesViewModel.DelayDeleteTimeEntry); this.Bind(tableViewSource.ItemSelected, ViewModel.SelectTimeEntry); this.Bind(ViewModel.TimeEntriesViewModel.ShouldShowUndo, toggleUndoDeletion); tableViewSource.SwipeToContinue .VoidSubscribe(() => { swipeRightStep.Dismiss(); }) .DisposedBy(disposeBag); tableViewSource.SwipeToDelete .VoidSubscribe(() => { swipeLeftStep.Dismiss(); }) .DisposedBy(disposeBag); // Refresh Control var refreshControl = new RefreshControl(ViewModel.SyncProgressState, tableViewSource); this.Bind(refreshControl.Refresh, ViewModel.RefreshAction); TimeEntriesLogTableView.CustomRefreshControl = refreshControl; //Commands bindingSet.Bind(settingsButton).To(vm => vm.OpenSettingsCommand); bindingSet.Bind(StopTimeEntryButton).To(vm => vm.StopTimeEntryCommand); bindingSet.Bind(StartTimeEntryButton).To(vm => vm.StartTimeEntryCommand); bindingSet.Bind(EditTimeEntryButton).To(vm => vm.EditTimeEntryCommand); bindingSet.Bind(syncFailuresButton).To(vm => vm.OpenSyncFailuresCommand); bindingSet.Bind(CurrentTimeEntryCard) .For(v => v.BindTap()) .To(vm => vm.EditTimeEntryCommand); bindingSet.Bind(suggestionsView) .For(v => v.SuggestionTappedCommad) .To(vm => vm.SuggestionsViewModel.StartTimeEntryCommand); bindingSet.Bind(StartTimeEntryButton) .For(v => v.BindLongPress()) .To(vm => vm.AlternativeStartTimeEntryCommand); //Visibility var shouldWelcomeBack = ViewModel.ShouldShowWelcomeBack; this.Bind(ViewModel.ShouldShowEmptyState, visible => emptyStateView.Hidden = !visible); this.Bind(shouldWelcomeBack, WelcomeBackView.Rx().IsVisible()); this.Bind(shouldWelcomeBack, spiderContainerView.Rx().IsVisible()); this.Bind(shouldWelcomeBack, visible => { if (visible) { spiderBroView.Show(); } else { spiderBroView.Hide(); } }); //Text bindingSet.Bind(CurrentTimeEntryDescriptionLabel).To(vm => vm.CurrentTimeEntryDescription); bindingSet.Bind(CurrentTimeEntryElapsedTimeLabel) .ByCombining(durationCombiner, vm => vm.CurrentTimeEntryElapsedTime, vm => vm.CurrentTimeEntryElapsedTimeFormat); bindingSet.Bind(CurrentTimeEntryProjectTaskClientLabel) .For(v => v.AttributedText) .ByCombining(projectTaskClientCombiner, v => v.CurrentTimeEntryProject, v => v.CurrentTimeEntryTask, v => v.CurrentTimeEntryClient, v => v.CurrentTimeEntryProjectColor); //The start button bindingSet.Bind(StartTimeEntryButton) .For(v => v.BindImage()) .To(vm => vm.IsInManualMode) .WithConversion(startTimeEntryButtonManualModeIconConverter); //The sync failures button bindingSet.Bind(syncFailuresButton) .For(v => v.BindVisibility()) .To(vm => vm.NumberOfSyncFailures) .WithConversion(visibilityConverter); bindingSet.Apply(); this.Bind(ViewModel.RatingViewModel.IsFeedbackSuccessViewShowing, SendFeedbackSuccessView.Rx().AnimatedIsVisible()); this.BindVoid(SendFeedbackSuccessView.Rx().Tap(), ViewModel.RatingViewModel.CloseFeedbackSuccessView); ViewModel.ShouldReloadTimeEntryLog .VoidSubscribe(reload) .DisposedBy(disposeBag); View.SetNeedsLayout(); View.LayoutIfNeeded(); NSNotificationCenter.DefaultCenter.AddObserver(UIApplication.DidBecomeActiveNotification, onApplicationDidBecomeActive); }