Skip to content
This repository has been archived by the owner on Feb 17, 2020. It is now read-only.

SQ-166/Empty state(s) for favourites #188

Merged
merged 23 commits into from
Mar 29, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
9692cca
Show empty view when no favorites are available
alexstyl Mar 28, 2017
0fc70a3
Create references for illustrations
alexstyl Mar 28, 2017
b35fefc
Style for empty text
alexstyl Mar 28, 2017
34a6f2b
Some key renaming
alexstyl Mar 28, 2017
5128ba6
Show a dedicated empty view for favoriting
alexstyl Mar 28, 2017
346dbac
Add fun 🎉
alexstyl Mar 28, 2017
a1a7b44
Remove obsolete reference
alexstyl Mar 28, 2017
92459d2
Merge branch 'develop' into SQ-55/favorite-empty
alexstyl Mar 28, 2017
855bddf
Add missing primary_card_feedback_thing
alexstyl Mar 29, 2017
0b61006
Add missing primary_card_feedback_thing (take2)
alexstyl Mar 29, 2017
4cfe216
Rename toScheduleAndLoggedInStuff to.. toScheduleAndLoggedIn!
alexstyl Mar 29, 2017
4c71799
Remove empty lines
alexstyl Mar 29, 2017
9e3857c
Throw exception when changing orientation
alexstyl Mar 29, 2017
5b0599d
Remove the concept of pages from Schedule
alexstyl Mar 29, 2017
665e8cb
Remove empty line
alexstyl Mar 29, 2017
587834e
Remove unused layout
alexstyl Mar 29, 2017
53a2a9a
Extract attrs to style
alexstyl Mar 29, 2017
095e7ec
Merge branch 'develop' into SQ-55/favorite-empty
rock3r Mar 29, 2017
0c30a32
Merge branch 'develop' into SQ-55/favorite-empty
alexstyl Mar 29, 2017
dfdd48b
Merge branch 'develop' into SQ-55/favorite-empty
alexstyl Mar 29, 2017
c293cd7
Merge branch 'develop' into SQ-55/favorite-empty
rock3r Mar 29, 2017
3e6478e
Make sure favorites appear when just signed in
rock3r Mar 29, 2017
7bc837a
Fix event details not appearing
rock3r Mar 29, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 0 additions & 1 deletion .idea/inspectionProfiles/Squanchy.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Expand Up @@ -117,6 +117,7 @@ private int getColorFromTheme(Resources.Theme theme, @AttrRes int attributeId) {
private void updateDescription(Optional<String> description) {
if (description.isPresent()) {
descriptionHeader.setVisibility(VISIBLE);
descriptionTextView.setVisibility(VISIBLE);
descriptionTextView.setText(parseHtml(description.get()));
} else {
descriptionHeader.setVisibility(GONE);
Expand Down
81 changes: 71 additions & 10 deletions app/src/main/java/net/squanchy/favorites/FavoritesPageView.java
Expand Up @@ -18,8 +18,10 @@
import net.squanchy.schedule.domain.view.Schedule;
import net.squanchy.schedule.view.ScheduleViewPagerAdapter;

import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.BiFunction;

import static net.squanchy.support.ContextUnwrapper.unwrapToActivityContext;

Expand All @@ -31,6 +33,8 @@ public class FavoritesPageView extends CoordinatorLayout implements LifecycleVie
private Navigator navigate;
private Analytics analytics;
private FavoritesListView favoritesListView;
private View emptyViewSignedIn;
private View emptyViewSignedOut;

public FavoritesPageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
Expand All @@ -44,17 +48,21 @@ public FavoritesPageView(Context context, AttributeSet attrs, int defStyleAttr)
protected void onFinishInflate() {
super.onFinishInflate();

Activity activity = unwrapToActivityContext(getContext());
FavoritesComponent component = FavoritesInjector.obtain(activity);
service = component.service();
navigate = component.navigator();
analytics = component.analytics();

progressBar = findViewById(R.id.progressbar);

favoritesListView = (FavoritesListView) findViewById(R.id.favorites_list);
emptyViewSignedIn = findViewById(R.id.empty_view_signed_in);
emptyViewSignedOut = findViewById(R.id.empty_view_signed_out);
emptyViewSignedOut.setOnClickListener(view -> navigate.toSignIn());

setupToolbar();

if (!isInEditMode()) {
Activity activity = unwrapToActivityContext(getContext());
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This block was moved to the !isInEditMode() so that we can have a preview of this screen. As it turns out there is a specific kind of Context being used in the Preview mode and unwrapToActivityContext() is crashing because it is unhandled. The specific Context is BridgeContext and looks like a Hidden API, so we cannot possibly handle it properly

FavoritesComponent component = FavoritesInjector.obtain(activity);
service = component.service();
navigate = component.navigator();
analytics = component.analytics();
}
}

private void setupToolbar() {
Expand All @@ -77,11 +85,15 @@ private void setupToolbar() {

@Override
public void onStart() {
subscription = service.schedule(true)
subscription = Observable.combineLatest(service.schedule(true), service.currentUserIsSignedIn(), toScheduleAndLoggedIn())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(schedule -> updateWith(schedule, this::onEventClicked));
}

private BiFunction<Schedule, Boolean, ScheduledAndSignedIn> toScheduleAndLoggedIn() {
return ScheduledAndSignedIn::new;
}

private void onEventClicked(Event event) {
analytics.trackItemSelected(ContentType.FAVORITES_ITEM, event.id());
navigate.toEventDetails(event.id());
Expand All @@ -92,8 +104,57 @@ public void onStop() {
subscription.dispose();
}

public void updateWith(Schedule schedule, ScheduleViewPagerAdapter.OnEventClickedListener listener) {
favoritesListView.updateWith(schedule, listener);
private void updateWith(ScheduledAndSignedIn scheduledAndSignedIn, ScheduleViewPagerAdapter.OnEventClickedListener listener) {
if (scheduledAndSignedIn.hasFavorites()) {
updateWith(scheduledAndSignedIn.schedule, listener);
} else {
if (scheduledAndSignedIn.signedIn) {
promptToFavorite();
} else {
promptToSign();
}
}
}

private void updateWith(Schedule schedule, ScheduleViewPagerAdapter.OnEventClickedListener listener) {
if (schedule.isEmpty()) {
promptToFavorite();
} else {
favoritesListView.updateWith(schedule, listener);
favoritesListView.setVisibility(VISIBLE);
emptyViewSignedIn.setVisibility(GONE);
}

progressBar.setVisibility(GONE);
emptyViewSignedOut.setVisibility(GONE);
}

private void promptToFavorite() {
favoritesListView.setVisibility(GONE);
progressBar.setVisibility(GONE);
emptyViewSignedOut.setVisibility(GONE);
emptyViewSignedIn.setVisibility(VISIBLE);
}

public void promptToSign() {
favoritesListView.setVisibility(GONE);
progressBar.setVisibility(GONE);
emptyViewSignedOut.setVisibility(VISIBLE);
emptyViewSignedIn.setVisibility(GONE);
}

private static class ScheduledAndSignedIn {

final Schedule schedule;
final boolean signedIn;

private ScheduledAndSignedIn(Schedule schedule, boolean signedIn) {
this.schedule = schedule;
this.signedIn = signedIn;
}

boolean hasFavorites() {
return !schedule.isEmpty();
}
}
}
60 changes: 60 additions & 0 deletions app/src/main/java/net/squanchy/favorites/view/NoFavoritesView.java
@@ -0,0 +1,60 @@
package net.squanchy.favorites.view;

import android.content.Context;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;

import net.squanchy.R;

public class NoFavoritesView extends LinearLayout {

private static final int MAGIC_NUMBER_TO_TRIGGER_ACHIEVEMENT = 5;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

magic

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is magic as in... magical ~ 💖

private FloatingActionButton favoriteButton;

public NoFavoritesView(Context context) {
super(context);
}

public NoFavoritesView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}

public NoFavoritesView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}

@Override
protected void onFinishInflate() {
super.onFinishInflate();
super.setOrientation(VERTICAL);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might want to override setOrientation() then and throw if the passed one is HORIZONTAL

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We usually throw regardless in those cases (see any extends LinearLayout in the codebase)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done 💃

inflate(getContext(), R.layout.merge_no_favorites_view, this);

favoriteButton = (FloatingActionButton) findViewById(R.id.favorite_fab_example);
favoriteButton.setOnClickListener(new OnClickListener() {
int counter = 0;

@Override
public void onClick(View view) {
favoriteButton.setImageResource(
counter % 2 == 0 ?
R.drawable.ic_favorite_filled :
R.drawable.ic_favorite_empty
);

counter++;
if (counter == MAGIC_NUMBER_TO_TRIGGER_ACHIEVEMENT) {
Snackbar.make(NoFavoritesView.this, R.string.achievement_unlocked, Snackbar.LENGTH_LONG).show();
}
}
});
}

@Override
public void setOrientation(int orientation) {
throw new UnsupportedOperationException("Changing orientation is not supported for " + NoFavoritesView.class.getSimpleName());
}
}
5 changes: 5 additions & 0 deletions app/src/main/java/net/squanchy/schedule/ScheduleService.java
Expand Up @@ -56,6 +56,11 @@ public Observable<Schedule> schedule(boolean onlyFavorites) {
});
}

public Observable<Boolean> currentUserIsSignedIn() {
return authService.currentUser()
.map(optionalUser -> optionalUser.map(user -> !user.isAnonymous()).or(false));
}

private Function<List<Event>, HashMap<String, List<Event>>> groupEventsByDay() {
return events -> Lists.reduce(new HashMap<>(), events, listToDaysHashMap());
}
Expand Down
Expand Up @@ -6,9 +6,14 @@

@AutoValue
public abstract class Schedule {

public static Schedule create(List<SchedulePage> pages) {
return new AutoValue_Schedule(pages);
}

public abstract List<SchedulePage> pages();

public boolean isEmpty() {
return pages().isEmpty();
}
}
27 changes: 27 additions & 0 deletions app/src/main/res/layout/merge_no_favorites_view.xml
@@ -0,0 +1,27 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical"
tools:parentTag="LinearLayout">

<android.support.design.widget.FloatingActionButton
android:id="@+id/favorite_fab_example"
style="@style/EventDetails.Fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" />

<TextView
android:id="@+id/text"
style="@style/Favorite.Empty.Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="20dp"
android:layout_marginLeft="@dimen/card_horizontal_margin"
android:layout_marginRight="@dimen/card_horizontal_margin"
android:foreground="@drawable/primary_touch_feedback"
android:text="@string/prompt_to_favorite" />
</merge>
29 changes: 27 additions & 2 deletions app/src/main/res/layout/view_page_favorites.xml
Expand Up @@ -5,8 +5,7 @@
android:id="@+id/favorites_content_root"
android:theme="@style/Theme.Squanchy.Favorites"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="net.squanchy.schedule.ScheduleActivity">
android:layout_height="match_parent">

<android.support.design.widget.AppBarLayout
style="@style/Squanchy.Appbar"
Expand All @@ -27,6 +26,32 @@
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="@dimen/card_horizontal_margin"
android:layout_marginRight="@dimen/card_horizontal_margin"
android:gravity="center_horizontal"
android:orientation="vertical">

<net.squanchy.favorites.view.NoFavoritesView
android:id="@+id/empty_view_signed_in"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone" />

<TextView
android:id="@+id/empty_view_signed_out"
style="@style/Favorite.Empty.Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableTop="@drawable/illustration_prompt_to_sign_in"
android:visibility="gone"
android:text="@string/prompt_to_sign_in_for_favorites"
tools:visibility="visible" />
</LinearLayout>

<ProgressBar
android:id="@+id/progressbar"
android:layout_width="wrap_content"
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/res/values/refs.xml
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<drawable name="illustration_prompt_to_sign_in">@drawable/illustration_lunch</drawable>
</resources>
8 changes: 5 additions & 3 deletions app/src/main/res/values/strings.xml
Expand Up @@ -14,9 +14,8 @@

<string name="event_notification_starting_by">By %s</string>
<string name="event_notification_starting_in">In %s</string>
<string name="event_notification_count_starting">%d sessions are starting</string>
<string name="room_event_notification"
translatable="false">%s: %s</string>
<string name="event_notification_count_starting">%s sessions are starting</string>
<string name="room_event_notification" translatable="false">%s: %s</string>
<plurals name="event_notification_ticker">
<item quantity="one">1 session is about to start.</item>
<item quantity="other">%d sessions are about to start.</item>
Expand Down Expand Up @@ -70,6 +69,9 @@
<string name="venue_map_content_description">Map to the venue</string>
<string name="about_title">About</string>
<string name="settings_account_not_logged_in">Not logged in</string>
<string name="prompt_to_sign_in_for_favorites">Get notified about upcoming talks. Your favourite talks are also get synced across all your devices. \n\nTap to sign in</string>
<string name="prompt_to_favorite">You have no favourite talks, yet. Find some talk, touch the ♥ button and you are good to go.</string>
<string name="achievement_unlocked">Achievement Unlocked: The Fast Learner!</string>

<string name="about_label">About</string>
<string name="about_powered_by">is powered by</string>
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/res/values/styles.xml
Expand Up @@ -449,4 +449,10 @@
<item name="fontPath">@string/license_notice_typeface</item>
</style>

<style name="Favorite.Empty.Text" parent="None">
<item name="android:foreground">@drawable/primary_touch_feedback</item>
<item name="android:gravity">center_horizontal</item>
<item name="android:drawablePadding">@dimen/card_horizontal_margin</item>
</style>

</resources>