Ejemplo n.º 1
0
        //Spider is added in code, because IB doesn't allow adding subviews
        //to a UITableView and the spider needs to be a subview of the table
        //view so it reacts to pulling down to refresh
        private void prepareWelcomeBackViews()
        {
            // Welcome back view must be placed inside of the time entries
            // log table view below the spider so that it does not overlay
            // the spider at any time.
            WelcomeBackView.RemoveFromSuperview();
            TimeEntriesLogTableView.AddSubview(WelcomeBackView);
            NSLayoutConstraint.ActivateConstraints(new[]
            {
                WelcomeBackView.CenterXAnchor.ConstraintEqualTo(TimeEntriesLogTableView.CenterXAnchor),
                WelcomeBackView.TopAnchor.ConstraintEqualTo(TimeEntriesLogTableView.TopAnchor, welcomeViewTopDistance),
                WelcomeBackView.LeadingAnchor.ConstraintEqualTo(TimeEntriesLogTableView.LeadingAnchor, welcomeViewSideMargin),
                WelcomeBackView.TrailingAnchor.ConstraintEqualTo(TimeEntriesLogTableView.TrailingAnchor, welcomeViewSideMargin)
            });

            var spiderHinge = new UIView();

            spiderHinge.Layer.CornerRadius = spiderHingeCornerRadius;
            spiderHinge.TranslatesAutoresizingMaskIntoConstraints = false;
            spiderHinge.BackgroundColor = Colors.Main.SpiderHinge.ToNativeColor();
            spiderContainerView.TranslatesAutoresizingMaskIntoConstraints = false;
            spiderBroView.TranslatesAutoresizingMaskIntoConstraints       = false;
            spiderContainerView.BackgroundColor = UIColor.Clear;

            spiderContainerView.AddSubview(spiderHinge);
            spiderContainerView.AddSubview(spiderBroView);
            TimeEntriesLogTableView.AddSubview(spiderContainerView);

            //Container constraints
            spiderContainerView.WidthAnchor.ConstraintEqualTo(TimeEntriesLogTableView.WidthAnchor).Active     = true;
            spiderContainerView.HeightAnchor.ConstraintEqualTo(TimeEntriesLogTableView.HeightAnchor).Active   = true;
            spiderContainerView.CenterYAnchor.ConstraintEqualTo(TimeEntriesLogTableView.CenterYAnchor).Active = true;
            spiderContainerView.CenterXAnchor.ConstraintEqualTo(TimeEntriesLogTableView.CenterXAnchor).Active = true;

            //Hinge constraints
            spiderHinge.WidthAnchor.ConstraintEqualTo(spiderHingeWidth).Active                    = true;
            spiderHinge.HeightAnchor.ConstraintEqualTo(spiderHingeHeight).Active                  = true;
            spiderHinge.TopAnchor.ConstraintEqualTo(spiderContainerView.TopAnchor).Active         = true;
            spiderHinge.CenterXAnchor.ConstraintEqualTo(spiderContainerView.CenterXAnchor).Active = true;

            //Spider constraints
            spiderBroView.TopAnchor.ConstraintEqualTo(spiderContainerView.TopAnchor).Active         = true;
            spiderBroView.WidthAnchor.ConstraintEqualTo(spiderContainerView.WidthAnchor).Active     = true;
            spiderBroView.BottomAnchor.ConstraintEqualTo(spiderContainerView.BottomAnchor).Active   = true;
            spiderBroView.CenterXAnchor.ConstraintEqualTo(spiderContainerView.CenterXAnchor).Active = true;
        }
Ejemplo n.º 2
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);
        }
Ejemplo n.º 3
0
        public override void ViewDidLoad()
        {
            base.ViewDidLoad();

            TableShadow.Layer.MasksToBounds = false;
            TableShadow.Layer.ShadowColor   = UIColor.Black.CGColor;
            TableShadow.Layer.ShadowOffset  = new CGSize(0, 0);
            TableShadow.Layer.ShadowOpacity = 0.1f;
            TableShadow.Layer.ShadowRadius  = 4;
            TableShadow.Hidden = TraitCollection.HorizontalSizeClass != UIUserInterfaceSizeClass.Regular;

            var separator = NavigationController.NavigationBar.InsertSeparator();

            separator.BackgroundColor = ColorAssets.OpaqueSeparator;

            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;

            StartTimeEntryButton.AccessibilityLabel = Resources.StartTimeEntry;
            StopTimeEntryButton.AccessibilityLabel  = Resources.StopCurrentlyRunningTimeEntry;

            tableViewSource = new TimeEntriesLogViewSource();

            prepareViews();
            prepareOnboarding();
            setupTableViewHeader();

            ViewModel.SwipeActionsEnabled
            .Subscribe(tableViewSource.SetSwipeActionsEnabled)
            .DisposedBy(disposeBag);

            TimeEntriesLogTableView.Source          = tableViewSource;
            TimeEntriesLogTableView.BackgroundColor = ColorAssets.TableBackground;

            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(onFirstTimeEntryChanged)
            .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);

            tableViewSource.Rx().ItemsChanged()
            .Subscribe(updateTooltipPositions)
            .DisposedBy(DisposeBag);

            ViewModel.TimeEntriesViewModel.TimeEntriesPendingDeletion
            .Subscribe(toggleUndoDeletion)
            .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, useFeedback: true).DisposedBy(DisposeBag);

            CurrentTimeEntryCard.Rx().Tap()
            .WithLatestFrom(ViewModel.CurrentRunningTimeEntry, (_, te) => te)
            .Where(te => te != null)
            .Select(te => new EditTimeEntryInfo(EditTimeEntryOrigin.RunningTimeEntryCard, te.Id))
            .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 = ColorAssets.Text3;

            ViewModel.CurrentRunningTimeEntry
            .Select(te => te?.ToFormattedTimeEntryString(capHeight, clientColor, shouldColorProject: true))
            .Subscribe(CurrentTimeEntryProjectTaskClientLabel.Rx().AttributedText())
            .DisposedBy(DisposeBag);

            //Accessibility
            CurrentTimeEntryCard.IsAccessibilityElementFocused
            .CombineLatest(ViewModel.CurrentRunningTimeEntry,
                           (_, runningEntry) => createAccessibilityLabelForRunningEntryCard(runningEntry))
            .Subscribe(CurrentTimeEntryCard.Rx().AccessibilityLabel())
            .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(suggestions =>
            {
                suggestionsView.OnSuggestions(suggestions);
                layoutTableHeader();
            })
            .DisposedBy(DisposeBag);

            // Intent Donation
            IosDependencyContainer.Instance.IntentDonationService.SetDefaultShortcutSuggestions();

            Observable.Merge(
                ViewModel.ContinueTimeEntry.Elements,
                ViewModel.SuggestionsViewModel.StartTimeEntry.Elements
                )
            .Subscribe(IosDependencyContainer.Instance.IntentDonationService.DonateStartTimeEntry)
            .DisposedBy(DisposeBag);

            ViewModel.StopTimeEntry.Elements
            .Subscribe(IosDependencyContainer.Instance.IntentDonationService.DonateStopCurrentTimeEntry)
            .DisposedBy(DisposeBag);

            View.SetNeedsLayout();
            View.LayoutIfNeeded();

            NSNotificationCenter.DefaultCenter.AddObserver(UIApplication.DidBecomeActiveNotification, onApplicationDidBecomeActive);
        }
Ejemplo n.º 4
0
        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);
        }