1
0
Fork 0
mirror of https://github.com/MatomoCamp/matomocamp-companion-android.git synced 2024-09-19 16:13:46 +02:00

Move bookmark status into its own ViewModel controlled by the activity and integrate a bottom app bar with a floating action button to manage bookmark status.

Auto-hide the top toolbar on scroll in event details screens
Remove legacy UnderlinePageIndicator. Swiping is still possible.
This commit is contained in:
Christophe Beyls 2019-01-29 12:55:21 +01:00
parent 2e17feb3ef
commit 700ad50eb9
22 changed files with 401 additions and 749 deletions

View file

@ -53,11 +53,13 @@
android:theme="@style/AppTheme.NoActionBar"/>
<activity
android:name=".activities.TrackScheduleEventActivity"
android:label="@string/event_details"/>
android:label="@string/event_details"
android:theme="@style/AppTheme.NoActionBar"/>
<activity
android:name=".activities.EventDetailsActivity"
android:label="@string/event_details"
android:parentActivityName=".activities.TrackScheduleActivity">
android:parentActivityName=".activities.TrackScheduleActivity"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>

View file

@ -1,15 +1,19 @@
package be.digitalia.fosdem.activities;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.nfc.NdefRecord;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageButton;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.NavUtils;
import androidx.core.app.TaskStackBuilder;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.Observer;
@ -17,10 +21,15 @@ import androidx.lifecycle.ViewModelProviders;
import be.digitalia.fosdem.R;
import be.digitalia.fosdem.fragments.EventDetailsFragment;
import be.digitalia.fosdem.model.Event;
import be.digitalia.fosdem.model.Track;
import be.digitalia.fosdem.utils.NfcUtils;
import be.digitalia.fosdem.utils.NfcUtils.CreateNfcAppDataCallback;
import be.digitalia.fosdem.utils.ThemeUtils;
import be.digitalia.fosdem.viewmodels.BookmarkStatusViewModel;
import be.digitalia.fosdem.viewmodels.EventViewModel;
import be.digitalia.fosdem.widgets.BookmarkStatusAdapter;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.bottomappbar.BottomAppBar;
/**
* Displays a single event passed either as a complete Parcelable object in extras or as an id in data.
@ -31,16 +40,25 @@ public class EventDetailsActivity extends AppCompatActivity implements Observer<
public static final String EXTRA_EVENT = "event";
private AppBarLayout appBarLayout;
private Toolbar toolbar;
private BottomAppBar bottomAppBar;
private BookmarkStatusViewModel bookmarkStatusViewModel;
private Event event;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.content);
setContentView(R.layout.single_event);
appBarLayout = findViewById(R.id.appbar);
toolbar = findViewById(R.id.toolbar);
bottomAppBar = findViewById(R.id.bottom_appbar);
setSupportActionBar(bottomAppBar);
ActionBar bar = getSupportActionBar();
bar.setDisplayHomeAsUpEnabled(false);
bar.setDisplayShowTitleEnabled(false);
ImageButton floatingActionButton = findViewById(R.id.fab);
bookmarkStatusViewModel = ViewModelProviders.of(this).get(BookmarkStatusViewModel.class);
BookmarkStatusAdapter.setupWithImageButton(bookmarkStatusViewModel, this, floatingActionButton);
Event event = getIntent().getParcelableExtra(EXTRA_EVENT);
@ -94,14 +112,28 @@ public class EventDetailsActivity extends AppCompatActivity implements Observer<
private void initEvent(@NonNull Event event) {
this.event = event;
// Enable up navigation only after getting the event details
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
ThemeUtils.setActionBarTrackColor(this, event.getTrack().getType());
toolbar.setNavigationIcon(R.drawable.abc_ic_ab_back_material);
toolbar.setNavigationContentDescription(R.string.abc_action_bar_up_description);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
navigateUp();
}
});
final Track.Type trackType = event.getTrack().getType();
ThemeUtils.setStatusBarTrackColor(this, trackType);
final ColorStateList trackColor = ContextCompat.getColorStateList(this, trackType.getColorResId());
appBarLayout.setBackgroundColor(trackColor.getDefaultColor());
bottomAppBar.setBackgroundTint(trackColor);
bookmarkStatusViewModel.setEvent(event);
// Enable Android Beam
NfcUtils.setAppDataPushMessageCallbackIfAvailable(this, this);
}
@Override
public boolean onSupportNavigateUp() {
void navigateUp() {
// Navigate up to the track associated with this event
Intent upIntent = new Intent(this, TrackScheduleActivity.class);
upIntent.putExtra(TrackScheduleActivity.EXTRA_DAY, event.getDay());
@ -121,7 +153,6 @@ public class EventDetailsActivity extends AppCompatActivity implements Observer<
startActivity(upIntent);
finish();
}
return true;
}
// CreateNfcAppDataCallback

View file

@ -3,13 +3,15 @@ package be.digitalia.fosdem.activities;
import android.content.Intent;
import android.nfc.NdefRecord;
import android.os.Bundle;
import android.widget.ImageView;
import android.widget.ImageButton;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.ViewModelProviders;
import be.digitalia.fosdem.R;
import be.digitalia.fosdem.fragments.EventDetailsFragment;
import be.digitalia.fosdem.fragments.RoomImageDialogFragment;
@ -20,6 +22,8 @@ import be.digitalia.fosdem.model.Track;
import be.digitalia.fosdem.utils.NfcUtils;
import be.digitalia.fosdem.utils.NfcUtils.CreateNfcAppDataCallback;
import be.digitalia.fosdem.utils.ThemeUtils;
import be.digitalia.fosdem.viewmodels.BookmarkStatusViewModel;
import be.digitalia.fosdem.widgets.BookmarkStatusAdapter;
/**
* Track Schedule container, works in both single pane and dual pane modes.
@ -27,9 +31,7 @@ import be.digitalia.fosdem.utils.ThemeUtils;
* @author Christophe Beyls
*/
public class TrackScheduleActivity extends AppCompatActivity
implements TrackScheduleListFragment.Callbacks,
EventDetailsFragment.FloatingActionButtonProvider,
CreateNfcAppDataCallback {
implements TrackScheduleListFragment.Callbacks, CreateNfcAppDataCallback {
public static final String EXTRA_DAY = "day";
public static final String EXTRA_TRACK = "track";
@ -41,15 +43,14 @@ public class TrackScheduleActivity extends AppCompatActivity
private boolean isTabletLandscape;
private Event lastSelectedEvent;
private ImageView floatingActionButton;
private BookmarkStatusViewModel bookmarkStatusViewModel = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.track_schedule);
setSupportActionBar((Toolbar) findViewById(R.id.toolbar));
floatingActionButton = findViewById(R.id.fab);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
Bundle extras = getIntent().getExtras();
day = extras.getParcelable(EXTRA_DAY);
@ -60,7 +61,9 @@ public class TrackScheduleActivity extends AppCompatActivity
bar.setTitle(track.toString());
bar.setSubtitle(day.toString());
setTitle(String.format("%1$s, %2$s", track.toString(), day.toString()));
ThemeUtils.setActionBarTrackColor(this, track.getType());
ThemeUtils.setStatusBarTrackColor(this, track.getType());
final int trackColor = ContextCompat.getColor(this, track.getType().getColorResId());
toolbar.setBackgroundColor(trackColor);
isTabletLandscape = getResources().getBoolean(R.bool.tablet_landscape);
@ -100,6 +103,12 @@ public class TrackScheduleActivity extends AppCompatActivity
}
if (isTabletLandscape) {
ImageButton floatingActionButton = findViewById(R.id.fab);
if (floatingActionButton != null) {
bookmarkStatusViewModel = ViewModelProviders.of(this).get(BookmarkStatusViewModel.class);
BookmarkStatusAdapter.setupWithImageButton(bookmarkStatusViewModel, this, floatingActionButton);
}
// Enable Android Beam
NfcUtils.setAppDataPushMessageCallbackIfAvailable(this, this);
}
@ -128,6 +137,10 @@ public class TrackScheduleActivity extends AppCompatActivity
fm.beginTransaction().remove(currentFragment).commitAllowingStateLoss();
}
}
if (bookmarkStatusViewModel != null) {
bookmarkStatusViewModel.setEvent(event);
}
} else {
// Classic mode: Show event details in a new activity
Intent intent = new Intent(this, TrackScheduleEventActivity.class);
@ -138,13 +151,6 @@ public class TrackScheduleActivity extends AppCompatActivity
}
}
// EventDetailsFragment.FloatingActionButtonProvider
@Override
public ImageView getActionButton() {
return floatingActionButton;
}
// CreateNfcAppDataCallback
@Override

View file

@ -1,10 +1,12 @@
package be.digitalia.fosdem.activities;
import android.content.res.ColorStateList;
import android.nfc.NdefRecord;
import android.os.Bundle;
import android.view.View;
import androidx.appcompat.app.ActionBar;
import android.widget.ImageButton;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
@ -21,9 +23,12 @@ import be.digitalia.fosdem.model.Track;
import be.digitalia.fosdem.utils.NfcUtils;
import be.digitalia.fosdem.utils.NfcUtils.CreateNfcAppDataCallback;
import be.digitalia.fosdem.utils.ThemeUtils;
import be.digitalia.fosdem.viewmodels.BookmarkStatusViewModel;
import be.digitalia.fosdem.viewmodels.TrackScheduleViewModel;
import be.digitalia.fosdem.widgets.BookmarkStatusAdapter;
import be.digitalia.fosdem.widgets.ContentLoadingProgressBar;
import com.viewpagerindicator.UnderlinePageIndicator;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.bottomappbar.BottomAppBar;
import java.util.List;
@ -41,14 +46,18 @@ public class TrackScheduleEventActivity extends AppCompatActivity implements Obs
private int initialPosition = -1;
private ContentLoadingProgressBar progress;
private ViewPager pager;
private UnderlinePageIndicator pageIndicator;
private TrackScheduleEventAdapter adapter;
TrackScheduleEventAdapter adapter;
BookmarkStatusViewModel bookmarkStatusViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.track_schedule_event);
AppBarLayout appBarLayout = findViewById(R.id.appbar);
Toolbar toolbar = findViewById(R.id.toolbar);
BottomAppBar bottomAppBar = findViewById(R.id.bottom_appbar);
setSupportActionBar(bottomAppBar);
Bundle extras = getIntent().getExtras();
final Day day = extras.getParcelable(EXTRA_DAY);
@ -57,26 +66,45 @@ public class TrackScheduleEventActivity extends AppCompatActivity implements Obs
progress = findViewById(R.id.progress);
pager = findViewById(R.id.pager);
adapter = new TrackScheduleEventAdapter(getSupportFragmentManager());
pageIndicator = findViewById(R.id.indicator);
pageIndicator.setSelectedColor(ContextCompat.getColor(this, track.getType().getColorResId()));
if (savedInstanceState == null) {
initialPosition = extras.getInt(EXTRA_POSITION, -1);
}
ActionBar bar = getSupportActionBar();
bar.setDisplayHomeAsUpEnabled(true);
bar.setTitle(track.toString());
bar.setSubtitle(day.toString());
ThemeUtils.setActionBarTrackColor(this, track.getType());
toolbar.setNavigationIcon(R.drawable.abc_ic_ab_back_material);
toolbar.setNavigationContentDescription(R.string.abc_action_bar_up_description);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
toolbar.setTitle(track.toString());
toolbar.setSubtitle(day.toString());
setTitle(String.format("%1$s, %2$s", track.toString(), day.toString()));
ThemeUtils.setStatusBarTrackColor(this, track.getType());
final ColorStateList trackColor = ContextCompat.getColorStateList(this, track.getType().getColorResId());
appBarLayout.setBackgroundColor(trackColor.getDefaultColor());
bottomAppBar.setBackgroundTint(trackColor);
// Enable Android Beam
NfcUtils.setAppDataPushMessageCallbackIfAvailable(this, this);
// Monitor the currently displayed event to update the bookmark status in FAB
ImageButton floatingActionButton = findViewById(R.id.fab);
bookmarkStatusViewModel = ViewModelProviders.of(this).get(BookmarkStatusViewModel.class);
BookmarkStatusAdapter.setupWithImageButton(bookmarkStatusViewModel, this, floatingActionButton);
pager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
@Override
public void onPageSelected(int position) {
bookmarkStatusViewModel.setEvent(adapter.getEvent(position));
}
});
setCustomProgressVisibility(true);
final TrackScheduleViewModel viewModel = ViewModelProviders.of(this).get(TrackScheduleViewModel.class);
viewModel.setTrack(day, track);
viewModel.getSchedule().observe(this, this);
// Enable Android Beam
NfcUtils.setAppDataPushMessageCallbackIfAvailable(this, this);
}
private void setCustomProgressVisibility(boolean isVisible) {
@ -99,12 +127,6 @@ public class TrackScheduleEventActivity extends AppCompatActivity implements Obs
return NfcUtils.createEventAppData(this, event);
}
@Override
public boolean onSupportNavigateUp() {
finish();
return true;
}
@Override
public void onChanged(List<StatusEvent> schedule) {
setCustomProgressVisibility(false);
@ -117,21 +139,25 @@ public class TrackScheduleEventActivity extends AppCompatActivity implements Obs
// to ensure the current position is restored properly
if (pager.getAdapter() == null) {
pager.setAdapter(adapter);
pageIndicator.setViewPager(pager);
}
if (initialPosition != -1) {
pager.setCurrentItem(initialPosition, false);
initialPosition = -1;
if (initialPosition != -1) {
pager.setCurrentItem(initialPosition, false);
initialPosition = -1;
}
final int currentPosition = pager.getCurrentItem();
if (currentPosition >= 0) {
bookmarkStatusViewModel.setEvent(adapter.getEvent(currentPosition));
}
}
}
}
public static class TrackScheduleEventAdapter extends FragmentStatePagerAdapter {
private static class TrackScheduleEventAdapter extends FragmentStatePagerAdapter {
private List<StatusEvent> events = null;
public TrackScheduleEventAdapter(FragmentManager fm) {
TrackScheduleEventAdapter(FragmentManager fm) {
super(fm);
}

View file

@ -1,11 +1,8 @@
package be.digitalia.fosdem.fragments;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Animatable;
import android.net.Uri;
import android.os.Bundle;
import android.provider.CalendarContract;
@ -13,10 +10,8 @@ import android.text.*;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.view.*;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.browser.customtabs.CustomTabsIntent;
import androidx.core.app.ShareCompat;
import androidx.core.content.ContextCompat;
@ -40,14 +35,6 @@ import java.util.Map;
public class EventDetailsFragment extends Fragment {
/**
* Interface implemented by container activities
*/
public interface FloatingActionButtonProvider {
// May return null
ImageView getActionButton();
}
static class ViewHolder {
LayoutInflater inflater;
TextView personsTextView;
@ -62,9 +49,6 @@ public class EventDetailsFragment extends Fragment {
ViewHolder holder;
EventDetailsViewModel viewModel;
private MenuItem bookmarkMenuItem;
private ImageView actionButton;
public static EventDetailsFragment newInstance(Event event) {
EventDetailsFragment f = new EventDetailsFragment();
Bundle args = new Bundle();
@ -79,6 +63,7 @@ public class EventDetailsFragment extends Fragment {
event = getArguments().getParcelable(ARG_EVENT);
viewModel = ViewModelProviders.of(this).get(EventDetailsViewModel.class);
viewModel.setEvent(event);
setHasOptionsMenu(true);
}
public Event getEvent() {
@ -181,23 +166,6 @@ public class EventDetailsFragment extends Fragment {
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Activity activity = getActivity();
if (activity instanceof FloatingActionButtonProvider) {
actionButton = ((FloatingActionButtonProvider) activity).getActionButton();
if (actionButton != null) {
actionButton.setOnClickListener(actionButtonClickListener);
}
}
// Ensure the actionButton is initialized before creating the options menu
setHasOptionsMenu(true);
viewModel.getBookmarkStatus().observe(getViewLifecycleOwner(), new Observer<Boolean>() {
@Override
public void onChanged(@Nullable Boolean isBookmarked) {
updateBookmarkMenuItem(isBookmarked, true);
}
});
viewModel.getEventDetails().observe(getViewLifecycleOwner(), new Observer<EventDetails>() {
@Override
public void onChanged(EventDetails eventDetails) {
@ -222,34 +190,16 @@ public class EventDetailsFragment extends Fragment {
});
}
private final View.OnClickListener actionButtonClickListener = new View.OnClickListener() {
@Override
public void onClick(View view) {
viewModel.toggleBookmarkStatus();
}
};
@Override
public void onDestroyView() {
super.onDestroyView();
holder = null;
if (actionButton != null) {
// Clear the reference to this fragment
actionButton.setOnClickListener(null);
actionButton = null;
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.event, menu);
menu.findItem(R.id.share).setIntent(getShareChooserIntent());
bookmarkMenuItem = menu.findItem(R.id.bookmark);
if (actionButton != null) {
bookmarkMenuItem.setEnabled(false).setVisible(false);
}
updateBookmarkMenuItem(viewModel.getBookmarkStatus().getValue(), false);
}
private Intent getShareChooserIntent() {
@ -261,67 +211,9 @@ public class EventDetailsFragment extends Fragment {
.createChooserIntent();
}
void updateBookmarkMenuItem(Boolean isBookmarked, boolean animate) {
if (actionButton != null) {
// Action Button is used as bookmark button
if (isBookmarked == null) {
actionButton.setEnabled(false);
} else {
// Only animate if the button was showing a previous value
animate = animate && actionButton.isEnabled();
actionButton.setEnabled(true);
if (isBookmarked) {
actionButton.setContentDescription(getString(R.string.remove_bookmark));
actionButton.setImageResource(animate ? R.drawable.avd_bookmark_add_24dp : R.drawable.ic_bookmark_white_24dp);
} else {
actionButton.setContentDescription(getString(R.string.add_bookmark));
actionButton.setImageResource(animate ? R.drawable.avd_bookmark_remove_24dp : R.drawable.ic_bookmark_outline_white_24dp);
}
if (animate) {
((Animatable) actionButton.getDrawable()).start();
}
}
} else {
// Standard menu item is used as bookmark button
if (bookmarkMenuItem != null) {
if (isBookmarked == null) {
bookmarkMenuItem.setEnabled(false);
} else {
// Only animate if the menu item was showing a previous value
animate = animate && bookmarkMenuItem.isEnabled();
bookmarkMenuItem.setEnabled(true);
if (isBookmarked) {
bookmarkMenuItem.setTitle(R.string.remove_bookmark);
bookmarkMenuItem.setIcon(animate ? R.drawable.avd_bookmark_add_24dp : R.drawable.ic_bookmark_white_24dp);
} else {
bookmarkMenuItem.setTitle(R.string.add_bookmark);
bookmarkMenuItem.setIcon(animate ? R.drawable.avd_bookmark_remove_24dp : R.drawable.ic_bookmark_outline_white_24dp);
}
if (animate) {
((Animatable) bookmarkMenuItem.getIcon()).stop();
((Animatable) bookmarkMenuItem.getIcon()).start();
}
}
}
}
}
@Override
public void onDestroyOptionsMenu() {
super.onDestroyOptionsMenu();
bookmarkMenuItem = null;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.bookmark:
viewModel.toggleBookmarkStatus();
return true;
case R.id.add_to_agenda:
addToAgenda();
return true;
@ -329,7 +221,6 @@ public class EventDetailsFragment extends Fragment {
return false;
}
@SuppressLint("InlinedApi")
private void addToAgenda() {
Intent intent = new Intent(Intent.ACTION_EDIT);
intent.setType("vnd.android.cursor.item/event");
@ -344,8 +235,7 @@ public class EventDetailsFragment extends Fragment {
EventDetails details = viewModel.getEventDetails().getValue();
final int personsCount = (details == null) ? 0 : details.getPersons().size();
if (personsCount > 0) {
description = String.format("%1$s: %2$s\n\n%3$s", getResources().getQuantityString(R.plurals.speakers, personsCount), event.getPersonsSummary(),
description);
description = String.format("%1$s: %2$s\n\n%3$s", getResources().getQuantityString(R.plurals.speakers, personsCount), event.getPersonsSummary(), description);
}
intent.putExtra(CalendarContract.Events.DESCRIPTION, description);
Date time = event.getStartTime();

View file

@ -0,0 +1,20 @@
package be.digitalia.fosdem.model;
public class BookmarkStatus {
private final boolean isBookmarked;
private final boolean isUpdate;
public BookmarkStatus(boolean isBookmarked, boolean isUpdate) {
this.isBookmarked = isBookmarked;
this.isUpdate = isUpdate;
}
public boolean isBookmarked() {
return isBookmarked;
}
public boolean isUpdate() {
return isUpdate;
}
}

View file

@ -1,24 +1,17 @@
package be.digitalia.fosdem.utils;
import android.app.Activity;
import android.app.ActivityManager;
import android.graphics.drawable.ColorDrawable;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import be.digitalia.fosdem.model.Track;
public class ThemeUtils {
@SuppressWarnings("deprecation")
public static void setActionBarTrackColor(@NonNull AppCompatActivity activity, @NonNull Track.Type trackType) {
ActionBar actionBar = activity.getSupportActionBar();
final int color = ContextCompat.getColor(activity, trackType.getColorResId());
actionBar.setBackgroundDrawable(new ColorDrawable(color));
public static void setStatusBarTrackColor(@NonNull Activity activity, @NonNull Track.Type trackType) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
final int color = ContextCompat.getColor(activity, trackType.getColorResId());
final int darkColor = ContextCompat.getColor(activity, trackType.getDarkColorResId());
activity.getWindow().setStatusBarColor(darkColor);
final ActivityManager.TaskDescription taskDescription;

View file

@ -0,0 +1,83 @@
package be.digitalia.fosdem.viewmodels;
import android.app.Application;
import android.os.AsyncTask;
import androidx.annotation.NonNull;
import androidx.arch.core.util.Function;
import androidx.core.util.ObjectsCompat;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations;
import be.digitalia.fosdem.db.AppDatabase;
import be.digitalia.fosdem.livedata.ExtraTransformations;
import be.digitalia.fosdem.model.BookmarkStatus;
import be.digitalia.fosdem.model.Event;
public class BookmarkStatusViewModel extends AndroidViewModel {
private final AppDatabase appDatabase = AppDatabase.getInstance(getApplication());
private final MutableLiveData<Event> event = new MutableLiveData<>();
private final LiveData<BookmarkStatus> bookmarkStatus = Transformations.switchMap(event,
new Function<Event, LiveData<BookmarkStatus>>() {
@Override
public LiveData<BookmarkStatus> apply(Event event) {
if (event == null) {
MutableLiveData<BookmarkStatus> singleNullResult = new MutableLiveData<>();
singleNullResult.setValue(null);
return singleNullResult;
}
return Transformations.map(
// Prevent updating the UI when a bookmark is added back or removed back
ExtraTransformations.distinctUntilChanged(
appDatabase.getBookmarksDao().getBookmarkStatus(event)
), new Function<Boolean, BookmarkStatus>() {
@Override
public BookmarkStatus apply(Boolean isBookmarked) {
if (isBookmarked == null) {
return null;
}
final boolean isUpdate = firstResultReceived;
firstResultReceived = true;
return new BookmarkStatus(isBookmarked, isUpdate);
}
}
);
}
});
private boolean firstResultReceived = false;
public BookmarkStatusViewModel(@NonNull Application application) {
super(application);
}
public void setEvent(Event event) {
if (!ObjectsCompat.equals(event, this.event.getValue())) {
firstResultReceived = false;
this.event.setValue(event);
}
}
public LiveData<BookmarkStatus> getBookmarkStatus() {
return bookmarkStatus;
}
public void toggleBookmarkStatus() {
final Event event = this.event.getValue();
final BookmarkStatus currentStatus = bookmarkStatus.getValue();
// Ignore the action if the status for the current event hasn't been received yet
if (event != null && currentStatus != null && firstResultReceived) {
AsyncTask.SERIAL_EXECUTOR.execute(new Runnable() {
@Override
public void run() {
if (currentStatus.isBookmarked()) {
appDatabase.getBookmarksDao().removeBookmark(event);
} else {
appDatabase.getBookmarksDao().addBookmark(event);
}
}
});
}
}
}

View file

@ -1,7 +1,6 @@
package be.digitalia.fosdem.viewmodels;
import android.app.Application;
import android.os.AsyncTask;
import androidx.annotation.NonNull;
import androidx.arch.core.util.Function;
import androidx.lifecycle.AndroidViewModel;
@ -9,7 +8,6 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations;
import be.digitalia.fosdem.db.AppDatabase;
import be.digitalia.fosdem.livedata.ExtraTransformations;
import be.digitalia.fosdem.model.Event;
import be.digitalia.fosdem.model.EventDetails;
@ -17,16 +15,6 @@ public class EventDetailsViewModel extends AndroidViewModel {
private final AppDatabase appDatabase = AppDatabase.getInstance(getApplication());
private final MutableLiveData<Event> event = new MutableLiveData<>();
private final LiveData<Boolean> bookmarkStatus = Transformations.switchMap(event,
new Function<Event, LiveData<Boolean>>() {
@Override
public LiveData<Boolean> apply(Event event) {
// Prevent animating the UI when a bookmark is added back or removed back
return ExtraTransformations.distinctUntilChanged(
appDatabase.getBookmarksDao().getBookmarkStatus(event)
);
}
});
private final LiveData<EventDetails> eventDetails = Transformations.switchMap(event,
new Function<Event, LiveData<EventDetails>>() {
@Override
@ -45,27 +33,6 @@ public class EventDetailsViewModel extends AndroidViewModel {
}
}
public LiveData<Boolean> getBookmarkStatus() {
return bookmarkStatus;
}
public void toggleBookmarkStatus() {
final Event event = this.event.getValue();
final Boolean isBookmarked = bookmarkStatus.getValue();
if (event != null && isBookmarked != null) {
AsyncTask.SERIAL_EXECUTOR.execute(new Runnable() {
@Override
public void run() {
if (isBookmarked) {
appDatabase.getBookmarksDao().removeBookmark(event);
} else {
appDatabase.getBookmarksDao().addBookmark(event);
}
}
});
}
}
public LiveData<EventDetails> getEventDetails() {
return eventDetails;
}

View file

@ -0,0 +1,56 @@
package be.digitalia.fosdem.widgets;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.widget.ImageButton;
import androidx.annotation.NonNull;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.Observer;
import be.digitalia.fosdem.R;
import be.digitalia.fosdem.model.BookmarkStatus;
import be.digitalia.fosdem.viewmodels.BookmarkStatusViewModel;
public class BookmarkStatusAdapter {
private BookmarkStatusAdapter() {
}
/**
* Connect an ImageButton to a BookmarkStatusViewModel
* to update its icon according to the current status and trigger a bookmark toggle on click.
*/
public static void setupWithImageButton(@NonNull final BookmarkStatusViewModel viewModel, @NonNull LifecycleOwner owner,
@NonNull final ImageButton imageButton) {
imageButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
viewModel.toggleBookmarkStatus();
}
});
viewModel.getBookmarkStatus().observe(owner, new Observer<BookmarkStatus>() {
@Override
public void onChanged(BookmarkStatus bookmarkStatus) {
if (bookmarkStatus == null) {
imageButton.setEnabled(false);
imageButton.setImageResource(R.drawable.ic_bookmark_outline_white_24dp);
} else {
// Only animate updates, when the button was already enabled
final boolean animate = bookmarkStatus.isUpdate() && imageButton.isEnabled();
imageButton.setEnabled(true);
if (bookmarkStatus.isBookmarked()) {
imageButton.setContentDescription(imageButton.getContext().getString(R.string.remove_bookmark));
imageButton.setImageResource(animate ? R.drawable.avd_bookmark_add_24dp : R.drawable.ic_bookmark_white_24dp);
} else {
imageButton.setContentDescription(imageButton.getContext().getString(R.string.add_bookmark));
imageButton.setImageResource(animate ? R.drawable.avd_bookmark_remove_24dp : R.drawable.ic_bookmark_outline_white_24dp);
}
final Drawable drawable = imageButton.getDrawable();
if (drawable instanceof Animatable) {
((Animatable) drawable).start();
}
}
}
});
}
}

View file

@ -1,56 +0,0 @@
/*
* Copyright (C) 2011 Patrik Akerfeldt
* Copyright (C) 2011 Jake Wharton
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.viewpagerindicator;
import androidx.viewpager.widget.ViewPager;
/**
* A PageIndicator is responsible to show an visual indicator on the total views
* number and the current visible view.
*/
public interface PageIndicator extends ViewPager.OnPageChangeListener {
/**
* Bind the indicator to a ViewPager.
*
* @param view
*/
void setViewPager(ViewPager view);
/**
* Bind the indicator to a ViewPager.
*
* @param view
* @param initialPosition
*/
void setViewPager(ViewPager view, int initialPosition);
/**
* <p>Set the current page of both the ViewPager and indicator.</p>
*
* <p>This <strong>must</strong> be used if you need to set the page before
* the views are drawn on screen (e.g., default start page).</p>
*
* @param item
*/
void setCurrentItem(int item);
/**
* Notify the indicator that the fragment list has changed.
*/
void notifyDataSetChanged();
}

View file

@ -1,377 +0,0 @@
/*
* Copyright (C) 2012 Jake Wharton
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.viewpagerindicator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import androidx.annotation.NonNull;
import androidx.core.view.ViewCompat;
import androidx.viewpager.widget.ViewPager;
import be.digitalia.fosdem.R;
/**
* Draws a line for each page. The current page line is colored differently
* than the unselected page lines.
*/
public class UnderlinePageIndicator extends View implements PageIndicator {
private static final int INVALID_POINTER = -1;
static final int FADE_FRAME_MS = 30;
final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
boolean mFades;
private int mFadeDelay;
private int mFadeLength;
int mFadeBy;
private ViewPager mViewPager;
private int mScrollState;
private int mCurrentPage;
private float mPositionOffset;
private int mTouchSlop;
private float mLastMotionX = -1;
private int mActivePointerId = INVALID_POINTER;
private boolean mIsDragging;
final Runnable mFadeRunnable = new Runnable() {
@Override public void run() {
if (!mFades) return;
final int alpha = Math.max(mPaint.getAlpha() - mFadeBy, 0);
mPaint.setAlpha(alpha);
invalidate();
if (alpha > 0) {
postDelayed(this, FADE_FRAME_MS);
}
}
};
public UnderlinePageIndicator(Context context) {
this(context, null);
}
public UnderlinePageIndicator(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.vpiUnderlinePageIndicatorStyle);
}
public UnderlinePageIndicator(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
if (isInEditMode()) return;
//Retrieve styles attributes
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.UnderlinePageIndicator, defStyle, R.style.UnderlinePageIndicator);
setFades(a.getBoolean(R.styleable.UnderlinePageIndicator_fades, false));
setSelectedColor(a.getColor(R.styleable.UnderlinePageIndicator_selectedColor, 0));
setFadeDelay(a.getInteger(R.styleable.UnderlinePageIndicator_fadeDelay, 0));
setFadeLength(a.getInteger(R.styleable.UnderlinePageIndicator_fadeLength, 0));
Drawable background = a.getDrawable(R.styleable.UnderlinePageIndicator_android_background);
if (background != null) {
ViewCompat.setBackground(this, background);
}
a.recycle();
mTouchSlop = ViewConfiguration.get(context).getScaledPagingTouchSlop();
}
public boolean getFades() {
return mFades;
}
public void setFades(boolean fades) {
if (fades != mFades) {
mFades = fades;
if (fades) {
post(mFadeRunnable);
} else {
removeCallbacks(mFadeRunnable);
mPaint.setAlpha(0xFF);
invalidate();
}
}
}
public int getFadeDelay() {
return mFadeDelay;
}
public void setFadeDelay(int fadeDelay) {
mFadeDelay = fadeDelay;
}
public int getFadeLength() {
return mFadeLength;
}
public void setFadeLength(int fadeLength) {
mFadeLength = fadeLength;
mFadeBy = 0xFF / (mFadeLength / FADE_FRAME_MS);
}
public int getSelectedColor() {
return mPaint.getColor();
}
public void setSelectedColor(int selectedColor) {
mPaint.setColor(selectedColor);
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mViewPager == null) {
return;
}
final int count = mViewPager.getAdapter().getCount();
if (count == 0) {
return;
}
if (mCurrentPage >= count) {
setCurrentItem(count - 1);
return;
}
final int paddingLeft = getPaddingLeft();
final float pageWidth = (getWidth() - paddingLeft - getPaddingRight()) / (1f * count);
final float left = paddingLeft + pageWidth * (mCurrentPage + mPositionOffset);
final float right = left + pageWidth;
final float top = getPaddingTop();
final float bottom = getHeight() - getPaddingBottom();
canvas.drawRect(left, top, right, bottom, mPaint);
}
@Override
public boolean onTouchEvent(@NonNull MotionEvent ev) {
if (super.onTouchEvent(ev)) {
return true;
}
if ((mViewPager == null) || (mViewPager.getAdapter().getCount() == 0)) {
return false;
}
final int action = ev.getAction() & MotionEvent.ACTION_MASK;
switch (action) {
case MotionEvent.ACTION_DOWN:
mActivePointerId = ev.getPointerId(0);
mLastMotionX = ev.getX();
break;
case MotionEvent.ACTION_MOVE: {
final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
final float x = ev.getX(activePointerIndex);
final float deltaX = x - mLastMotionX;
if (!mIsDragging) {
if (Math.abs(deltaX) > mTouchSlop) {
mIsDragging = true;
}
}
if (mIsDragging) {
mLastMotionX = x;
if (mViewPager.isFakeDragging() || mViewPager.beginFakeDrag()) {
mViewPager.fakeDragBy(deltaX);
}
}
break;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if (!mIsDragging) {
final int count = mViewPager.getAdapter().getCount();
final int width = getWidth();
final float halfWidth = width / 2f;
final float sixthWidth = width / 6f;
if ((mCurrentPage > 0) && (ev.getX() < halfWidth - sixthWidth)) {
if (action != MotionEvent.ACTION_CANCEL) {
mViewPager.setCurrentItem(mCurrentPage - 1);
}
return true;
} else if ((mCurrentPage < count - 1) && (ev.getX() > halfWidth + sixthWidth)) {
if (action != MotionEvent.ACTION_CANCEL) {
mViewPager.setCurrentItem(mCurrentPage + 1);
}
return true;
}
}
mIsDragging = false;
mActivePointerId = INVALID_POINTER;
if (mViewPager.isFakeDragging()) mViewPager.endFakeDrag();
break;
case MotionEvent.ACTION_POINTER_DOWN: {
final int index = ev.getActionIndex();
mLastMotionX = ev.getX(index);
mActivePointerId = ev.getPointerId(index);
break;
}
case MotionEvent.ACTION_POINTER_UP:
final int pointerIndex = ev.getActionIndex();
final int pointerId = ev.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mActivePointerId = ev.getPointerId(newPointerIndex);
}
mLastMotionX = ev.getX(ev.findPointerIndex(mActivePointerId));
break;
}
return true;
}
@Override
public void setViewPager(ViewPager viewPager) {
if (mViewPager == viewPager) {
return;
}
if (mViewPager != null) {
//Clear us from the old pager.
mViewPager.removeOnPageChangeListener(this);
}
if (viewPager.getAdapter() == null) {
throw new IllegalStateException("ViewPager does not have adapter instance.");
}
mViewPager = viewPager;
mViewPager.addOnPageChangeListener(this);
invalidate();
post(new Runnable() {
@Override public void run() {
if (mFades) {
post(mFadeRunnable);
}
}
});
}
@Override
public void setViewPager(ViewPager view, int initialPosition) {
setViewPager(view);
setCurrentItem(initialPosition);
}
@Override
public void setCurrentItem(int item) {
if (mViewPager == null) {
throw new IllegalStateException("ViewPager has not been bound.");
}
mViewPager.setCurrentItem(item);
mCurrentPage = item;
invalidate();
}
@Override
public void notifyDataSetChanged() {
invalidate();
}
@Override
public void onPageScrollStateChanged(int state) {
mScrollState = state;
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
mCurrentPage = position;
mPositionOffset = positionOffset;
if (mFades) {
if (positionOffsetPixels > 0) {
removeCallbacks(mFadeRunnable);
mPaint.setAlpha(0xFF);
} else if (mScrollState != ViewPager.SCROLL_STATE_DRAGGING) {
postDelayed(mFadeRunnable, mFadeDelay);
}
}
invalidate();
}
@Override
public void onPageSelected(int position) {
if (mScrollState == ViewPager.SCROLL_STATE_IDLE) {
mCurrentPage = position;
mPositionOffset = 0;
invalidate();
mFadeRunnable.run();
}
}
@Override
public void onRestoreInstanceState(Parcelable state) {
SavedState savedState = (SavedState)state;
super.onRestoreInstanceState(savedState.getSuperState());
mCurrentPage = savedState.currentPage;
requestLayout();
}
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState savedState = new SavedState(superState);
savedState.currentPage = mCurrentPage;
return savedState;
}
static class SavedState extends BaseSavedState {
int currentPage;
public SavedState(Parcelable superState) {
super(superState);
}
SavedState(Parcel in) {
super(in);
currentPage = in.readInt();
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(currentPage);
}
public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
}

View file

@ -18,7 +18,7 @@
style="@style/Toolbar.Fosdem"
android:layout_width="match_parent"
android:layout_height="128dp"
android:elevation="4dp"
android:elevation="@dimen/toolbar_elevation"
android:gravity="top" />
<androidx.cardview.widget.CardView
@ -36,15 +36,12 @@
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
style="@style/FloatingActionButton.BookmarkStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/detail_fab_margin_end"
android:layout_marginRight="@dimen/detail_fab_margin_end"
android:contentDescription="@string/add_bookmark"
android:theme="@style/ThemeOverlay.AppCompat.Dark"
app:fabSize="normal"
app:layout_anchor="@+id/toolbar"
app:layout_anchorGravity="bottom|end"
app:srcCompat="@drawable/ic_bookmark_outline_white_24dp" />
app:layout_anchorGravity="bottom|end" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -1,15 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activities.EventDetailsActivity">
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
tools:context=".activities.EventDetailsActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:orientation="vertical"
android:paddingBottom="@dimen/detail_bottom_padding">
<LinearLayout
android:layout_width="match_parent"
@ -144,4 +146,4 @@
android:visibility="gone"/>
</LinearLayout>
</ScrollView>
</androidx.core.widget.NestedScrollView>

View file

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:layout_scrollFlags="scroll|enterAlways"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
</com.google.android.material.appbar.AppBarLayout>
<FrameLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="?attr/actionBarSize"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<com.google.android.material.bottomappbar.BottomAppBar
android:id="@+id/bottom_appbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_gravity="bottom"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:backgroundTint="?attr/colorPrimary"
app:fabAlignmentMode="end" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
style="@style/FloatingActionButton.BookmarkStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_anchor="@id/bottom_appbar" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -1,7 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:layout_scrollFlags="scroll|enterAlways"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
</com.google.android.material.appbar.AppBarLayout>
<be.digitalia.fosdem.widgets.ContentLoadingProgressBar
android:id="@+id/progress"
@ -9,18 +26,30 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone"/>
android:visibility="gone" />
<androidx.viewpager.widget.ViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"/>
android:layout_marginBottom="?attr/actionBarSize"
android:visibility="gone"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<com.viewpagerindicator.UnderlinePageIndicator
android:id="@+id/indicator"
<com.google.android.material.bottomappbar.BottomAppBar
android:id="@+id/bottom_appbar"
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_gravity="bottom"/>
android:layout_height="?attr/actionBarSize"
android:layout_gravity="bottom"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:backgroundTint="?attr/colorPrimary"
app:fabAlignmentMode="end" />
</merge>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
style="@style/FloatingActionButton.BookmarkStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_anchor="@id/bottom_appbar" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -1,21 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/bookmark"
android:icon="@drawable/ic_bookmark_outline_white_24dp"
android:title="@string/add_bookmark"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/share"
android:icon="@drawable/ic_share_white_24dp"
android:title="@string/share"
app:showAsAction="ifRoom"/>
app:showAsAction="ifRoom" />
<item
android:id="@+id/add_to_agenda"
android:icon="@drawable/ic_event_add_white_24dp"
android:title="@string/add_to_agenda"
app:showAsAction="ifRoom"/>
app:showAsAction="ifRoom" />
</menu>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="detail_bottom_padding">0dp</dimen>
</resources>

View file

@ -6,8 +6,9 @@
<dimen name="main_menu_footer_height">32dp</dimen>
<dimen name="list_item_padding">8dp</dimen>
<dimen name="content_margin">16dp</dimen>
<dimen name="toolbar_elevation">8dp</dimen>
<dimen name="toolbar_elevation">4dp</dimen>
<dimen name="schedule_column_width">360dp</dimen>
<dimen name="detail_bottom_padding">12dp</dimen>
<dimen name="detail_card_view_elevation">6dp</dimen>
<!-- Enlarge the card view by 6dp left & right and 9dp top & bottom
to compensate for the compatibility padding of the elevation -->

View file

@ -70,6 +70,14 @@
<item name="tabIndicatorColor">@android:color/white</item>
</style>
<style name="FloatingActionButton.BookmarkStatus" parent="Widget.Design.FloatingActionButton">
<item name="android:contentDescription">@string/add_bookmark</item>
<item name="android:enabled">false</item>
<item name="android:theme">@style/ThemeOverlay.AppCompat.Dark</item>
<item name="fabSize">normal</item>
<item name="srcCompat">@drawable/ic_bookmark_outline_white_24dp</item>
</style>
<style name="SeparatorLine">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">1dp</item>

View file

@ -1,46 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2012 Jake Wharton
Copyright (C) 2011 Patrik Åkerfeldt
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<declare-styleable name="ViewPagerIndicator">
<!-- Style of the underline indicator. -->
<attr name="vpiUnderlinePageIndicatorStyle" format="reference" />
</declare-styleable>
<attr name="centered" format="boolean" />
<attr name="selectedColor" format="color" />
<attr name="strokeWidth" format="dimension" />
<attr name="unselectedColor" format="color" />
<declare-styleable name="UnderlinePageIndicator">
<!-- Whether or not the selected indicator fades. -->
<attr name="fades" format="boolean" />
<!-- Length of the delay to fade the indicator. -->
<attr name="fadeDelay" format="integer" />
<!-- Length of the indicator fade to transparent. -->
<attr name="fadeLength" format="integer" />
<!-- Color of the selected line that represents the current page. -->
<attr name="selectedColor" />
<!-- View background -->
<attr name="android:background" />
</declare-styleable>
</resources>

View file

@ -1,27 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2012 Jake Wharton
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<style name="UnderlinePageIndicator" parent="android:Widget">
<item name="fades">true</item>
<item name="fadeDelay">300</item>
<item name="fadeLength">400</item>
<item name="selectedColor">?attr/colorPrimary</item>
</style>
</resources>