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);
        }
Example #2
0
        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);
        }
Example #3
0
 private void onFirstTimeEntryChanged(TimeEntriesLogViewCell nextFirstTimeEntry)
 {
     updateSwipeDismissGestures(nextFirstTimeEntry);
     firstTimeEntry = nextFirstTimeEntry;
     updateTooltipPositions();
 }
Example #4
0
        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);
        }
Example #7
0
 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);
        }