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

Replace custom widgets with Material components for SnackBar, NavigationView, FAB and TabLayout

This commit is contained in:
Christophe Beyls 2018-12-16 19:47:20 +01:00
parent a7a62ebd15
commit 961a6b0c8e
26 changed files with 258 additions and 1311 deletions

View file

@ -26,6 +26,7 @@ android {
dependencies {
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'com.google.android.material:material:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.0.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation ('androidx.preference:preference:1.1.0-alpha01') {

View file

@ -11,42 +11,36 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.format.DateUtils;
import android.text.style.ForegroundColorSpan;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.DrawableRes;
import com.google.android.material.navigation.NavigationView;
import com.google.android.material.snackbar.Snackbar;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar;
import androidx.browser.customtabs.CustomTabsIntent;
import androidx.core.content.ContextCompat;
import androidx.core.view.GravityCompat;
import androidx.core.widget.TextViewCompat;
import androidx.core.view.OnApplyWindowInsetsListener;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
@ -64,7 +58,6 @@ import be.digitalia.fosdem.fragments.LiveFragment;
import be.digitalia.fosdem.fragments.MapFragment;
import be.digitalia.fosdem.fragments.PersonsListFragment;
import be.digitalia.fosdem.fragments.TracksFragment;
import be.digitalia.fosdem.widgets.AdapterLinearLayout;
/**
* Main entry point of the application. Allows to switch between section fragments and update the database.
@ -77,41 +70,33 @@ public class MainActivity extends AppCompatActivity {
public static final String ACTION_SHORTCUT_LIVE = BuildConfig.APPLICATION_ID + ".intent.action.SHORTCUT_LIVE";
private enum Section {
TRACKS(TracksFragment.class, R.string.menu_tracks, R.drawable.ic_event_grey600_24dp, true, true),
BOOKMARKS(BookmarksListFragment.class, R.string.menu_bookmarks, R.drawable.ic_bookmark_grey600_24dp, false, false),
LIVE(LiveFragment.class, R.string.menu_live, R.drawable.ic_play_circle_outline_grey600_24dp, true, false),
SPEAKERS(PersonsListFragment.class, R.string.menu_speakers, R.drawable.ic_people_grey600_24dp, false, false),
MAP(MapFragment.class, R.string.menu_map, R.drawable.ic_map_grey600_24dp, false, false);
TRACKS(R.id.menu_tracks, TracksFragment.class, true, true),
BOOKMARKS(R.id.menu_bookmarks, BookmarksListFragment.class, false, false),
LIVE(R.id.menu_live, LiveFragment.class, true, false),
SPEAKERS(R.id.menu_speakers, PersonsListFragment.class, false, false),
MAP(R.id.menu_map, MapFragment.class, false, false);
private final int menuItemId;
private final String fragmentClassName;
private final int titleResId;
private final int iconResId;
private final boolean extendsAppBar;
private final boolean keep;
Section(Class<? extends Fragment> fragmentClass, int titleResId, int iconResId,
boolean extendsAppBar, boolean keep) {
Section(@IdRes int menuItemId, Class<? extends Fragment> fragmentClass, boolean extendsAppBar, boolean keep) {
this.menuItemId = menuItemId;
this.fragmentClassName = fragmentClass.getName();
this.titleResId = titleResId;
this.iconResId = iconResId;
this.extendsAppBar = extendsAppBar;
this.keep = keep;
}
@IdRes
public int getMenuItemId() {
return menuItemId;
}
public String getFragmentClassName() {
return fragmentClassName;
}
@StringRes
public int getTitleResId() {
return titleResId;
}
@DrawableRes
public int getIconResId() {
return iconResId;
}
public boolean extendsAppBar() {
return extendsAppBar;
}
@ -119,12 +104,21 @@ public class MainActivity extends AppCompatActivity {
public boolean shouldKeep() {
return keep;
}
@Nullable
public static Section fromMenuItemId(@IdRes int menuItemId) {
for (Section section : Section.values()) {
if (section.menuItemId == menuItemId) {
return section;
}
}
return null;
}
}
private static final long DATABASE_VALIDITY_DURATION = DateUtils.DAY_IN_MILLIS;
private static final long DOWNLOAD_REMINDER_SNOOZE_DURATION = DateUtils.DAY_IN_MILLIS;
private static final String PREF_LAST_DOWNLOAD_REMINDER_TIME = "last_download_reminder_time";
private static final String STATE_CURRENT_SECTION = "current_section";
private static final String LAST_UPDATE_DATE_FORMAT = "d MMM yyyy kk:mm:ss";
@ -133,13 +127,12 @@ public class MainActivity extends AppCompatActivity {
// Main menu
Section currentSection;
int pendingMenuSection = -1;
int pendingMenuFooter = -1;
MenuItem pendingNavigationMenuItem = null;
DrawerLayout drawerLayout;
private ActionBarDrawerToggle drawerToggle;
View mainMenu;
private NavigationView navigationView;
private TextView lastUpdateTextView;
private MainMenuAdapter menuAdapter;
private MenuItem searchMenuItem;
@ -162,7 +155,7 @@ public class MainActivity extends AppCompatActivity {
default:
message = getResources().getQuantityString(R.plurals.events_download_completed, result, result);
}
Toast.makeText(MainActivity.this, message, Toast.LENGTH_LONG).show();
Snackbar.make(findViewById(R.id.content), message, Snackbar.LENGTH_LONG).show();
}
};
@ -251,8 +244,7 @@ public class MainActivity extends AppCompatActivity {
public void onDrawerStateChanged(int newState) {
super.onDrawerStateChanged(newState);
if (newState == DrawerLayout.STATE_DRAGGING) {
pendingMenuSection = -1;
pendingMenuFooter = -1;
pendingNavigationMenuItem = null;
}
}
@ -266,13 +258,9 @@ public class MainActivity extends AppCompatActivity {
@Override
public void onDrawerClosed(View drawerView) {
super.onDrawerClosed(drawerView);
if (pendingMenuSection != -1) {
selectMenuSection(pendingMenuSection);
pendingMenuSection = -1;
}
if (pendingMenuFooter != -1) {
selectMenuFooter(pendingMenuFooter);
pendingMenuFooter = -1;
if (pendingNavigationMenuItem != null) {
handleNavigationMenuItem(pendingNavigationMenuItem);
pendingNavigationMenuItem = null;
}
}
};
@ -284,11 +272,22 @@ public class MainActivity extends AppCompatActivity {
// Setup Main menu
mainMenu = findViewById(R.id.main_menu);
final AdapterLinearLayout sectionsList = findViewById(R.id.sections);
menuAdapter = new MainMenuAdapter(getLayoutInflater());
sectionsList.setAdapter(menuAdapter);
mainMenu.findViewById(R.id.settings).setOnClickListener(menuFooterClickListener);
mainMenu.findViewById(R.id.volunteer).setOnClickListener(menuFooterClickListener);
// Forward window insets to NavigationView
ViewCompat.setOnApplyWindowInsetsListener(mainMenu, new OnApplyWindowInsetsListener() {
@Override
public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
return insets;
}
});
navigationView = findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
pendingNavigationMenuItem = menuItem;
drawerLayout.closeDrawer(mainMenu);
return true;
}
});
LocalBroadcastManager.getInstance(this).registerReceiver(scheduleRefreshedReceiver, new IntentFilter(DatabaseManager.ACTION_SCHEDULE_REFRESHED));
@ -296,8 +295,8 @@ public class MainActivity extends AppCompatActivity {
lastUpdateTextView = mainMenu.findViewById(R.id.last_update);
updateLastUpdateTime();
// Restore current section
if (savedInstanceState == null) {
// Select initial section
currentSection = Section.TRACKS;
String action = getIntent().getAction();
if (action != null) {
@ -310,33 +309,18 @@ public class MainActivity extends AppCompatActivity {
break;
}
}
navigationView.setCheckedItem(currentSection.getMenuItemId());
String fragmentClassName = currentSection.getFragmentClassName();
Fragment f = Fragment.instantiate(this, fragmentClassName);
getSupportFragmentManager().beginTransaction().add(R.id.content, f, fragmentClassName).commit();
} else {
currentSection = Section.values()[savedInstanceState.getInt(STATE_CURRENT_SECTION)];
}
// Ensure the current section is visible in the menu
sectionsList.post(new Runnable() {
@Override
public void run() {
if (sectionsList.getChildCount() > currentSection.ordinal()) {
ScrollView mainMenuScrollView = findViewById(R.id.main_menu_scroll);
int requiredScroll = sectionsList.getTop()
+ sectionsList.getChildAt(currentSection.ordinal()).getBottom()
- mainMenuScrollView.getHeight();
mainMenuScrollView.scrollTo(0, Math.max(0, requiredScroll));
}
}
});
updateActionBar();
}
private void updateActionBar() {
setTitle(currentSection.getTitleResId());
private void updateActionBar(@NonNull Section section, @NonNull MenuItem menuItem) {
setTitle(menuItem.getTitle());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
toolbar.setElevation(currentSection.extendsAppBar()
toolbar.setElevation(section.extendsAppBar()
? 0f : getResources().getDimension(R.dimen.toolbar_elevation));
}
}
@ -353,6 +337,17 @@ public class MainActivity extends AppCompatActivity {
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
drawerToggle.syncState();
// Restore current section from NavigationView
final MenuItem menuItem = navigationView.getCheckedItem();
if (menuItem != null) {
if (currentSection == null) {
currentSection = Section.fromMenuItemId(menuItem.getItemId());
}
if (currentSection != null) {
updateActionBar(currentSection, menuItem);
}
}
}
@Override
@ -367,10 +362,13 @@ public class MainActivity extends AppCompatActivity {
@Override
protected void onSaveInstanceState(Bundle outState) {
// Ensure no fragment transaction attempt will occur after onSaveInstanceState()
pendingMenuSection = -1;
pendingMenuFooter = -1;
if (pendingNavigationMenuItem != null) {
pendingNavigationMenuItem = null;
if (currentSection != null) {
navigationView.setCheckedItem(currentSection.getMenuItemId());
}
}
super.onSaveInstanceState(outState);
outState.putInt(STATE_CURRENT_SECTION, currentSection.ordinal());
}
@Override
@ -473,69 +471,32 @@ public class MainActivity extends AppCompatActivity {
// MAIN MENU
private class MainMenuAdapter extends AdapterLinearLayout.Adapter<Section> {
private final Section[] sections = Section.values();
private final LayoutInflater inflater;
private final int currentSectionForegroundColor;
public MainMenuAdapter(LayoutInflater inflater) {
this.inflater = inflater;
// Select the primary color to tint the current section
TypedArray a = getTheme().obtainStyledAttributes(new int[]{R.attr.colorPrimary});
try {
currentSectionForegroundColor = a.getColor(0, Color.TRANSPARENT);
} finally {
a.recycle();
void handleNavigationMenuItem(@NonNull MenuItem menuItem) {
final int menuItemId = menuItem.getItemId();
final Section section = Section.fromMenuItemId(menuItemId);
if (section != null) {
selectMenuSection(section, menuItem);
} else {
switch (menuItemId) {
case R.id.menu_settings:
startActivity(new Intent(MainActivity.this, SettingsActivity.class));
overridePendingTransition(R.anim.slide_in_right, R.anim.partial_zoom_out);
break;
case R.id.menu_volunteer:
try {
new CustomTabsIntent.Builder()
.setToolbarColor(ContextCompat.getColor(this, R.color.color_primary))
.setShowTitle(true)
.build()
.launchUrl(this, Uri.parse(FosdemUrls.getVolunteer()));
} catch (ActivityNotFoundException ignore) {
}
break;
}
}
@Override
public int getCount() {
return sections.length;
}
@Override
public Section getItem(int position) {
return sections[position];
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = inflater.inflate(R.layout.item_main_menu, parent, false);
convertView.setOnClickListener(sectionClickListener);
}
Section section = getItem(position);
convertView.setSelected(section == currentSection);
TextView tv = convertView.findViewById(R.id.section_text);
SpannableString sectionTitle = new SpannableString(getString(section.getTitleResId()));
Drawable sectionIcon = AppCompatResources.getDrawable(MainActivity.this, section.getIconResId());
if (section == currentSection) {
// Special color for the current section
sectionTitle.setSpan(new ForegroundColorSpan(currentSectionForegroundColor), 0, sectionTitle.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
// We need to mutate the drawable before applying the ColorFilter, or else all the similar drawable instances will be tinted.
sectionIcon.mutate().setColorFilter(currentSectionForegroundColor, PorterDuff.Mode.SRC_IN);
}
tv.setText(sectionTitle);
TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(tv, sectionIcon, null, null, null);
return convertView;
}
}
final View.OnClickListener sectionClickListener = new View.OnClickListener() {
@Override
public void onClick(View view) {
pendingMenuSection = ((ViewGroup) view.getParent()).indexOfChild(view);
drawerLayout.closeDrawer(mainMenu);
}
};
void selectMenuSection(int position) {
Section section = menuAdapter.getItem(position);
void selectMenuSection(@NonNull Section section, @NonNull MenuItem menuItem) {
if (section != currentSection) {
// Switch to new section
FragmentManager fm = getSupportFragmentManager();
@ -558,36 +519,7 @@ public class MainActivity extends AppCompatActivity {
ft.commit();
currentSection = section;
updateActionBar();
menuAdapter.notifyDataSetChanged();
}
}
private final View.OnClickListener menuFooterClickListener = new View.OnClickListener() {
@Override
public void onClick(View view) {
pendingMenuFooter = view.getId();
drawerLayout.closeDrawer(mainMenu);
}
};
void selectMenuFooter(int id) {
switch (id) {
case R.id.settings:
startActivity(new Intent(MainActivity.this, SettingsActivity.class));
overridePendingTransition(R.anim.slide_in_right, R.anim.partial_zoom_out);
break;
case R.id.volunteer:
try {
new CustomTabsIntent.Builder()
.setToolbarColor(ContextCompat.getColor(this, R.color.color_primary))
.setShowTitle(true)
.build()
.launchUrl(this, Uri.parse(FosdemUrls.getVolunteer()));
} catch (ActivityNotFoundException ignore) {
}
break;
updateActionBar(section, menuItem);
}
}
}

View file

@ -24,7 +24,8 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.material.snackbar.Snackbar;
import java.text.DateFormat;
import java.util.Date;
@ -369,7 +370,7 @@ public class EventDetailsFragment extends Fragment {
try {
startActivity(intent);
} catch (ActivityNotFoundException e) {
Toast.makeText(getActivity(), R.string.calendar_not_found, Toast.LENGTH_LONG).show();
Snackbar.make(getView(), R.string.calendar_not_found, Snackbar.LENGTH_LONG).show();
}
}

View file

@ -6,6 +6,8 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.google.android.material.tabs.TabLayout;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
@ -13,13 +15,12 @@ import androidx.fragment.app.FragmentPagerAdapter;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager.widget.ViewPager;
import be.digitalia.fosdem.R;
import be.digitalia.fosdem.widgets.SlidingTabLayout;
public class LiveFragment extends Fragment implements RecycledViewPoolProvider {
static class ViewHolder {
ViewPager pager;
SlidingTabLayout slidingTabs;
TabLayout tabs;
RecyclerView.RecycledViewPool recycledViewPool;
}
@ -32,8 +33,8 @@ public class LiveFragment extends Fragment implements RecycledViewPoolProvider {
holder = new ViewHolder();
holder.pager = view.findViewById(R.id.pager);
holder.pager.setAdapter(new LivePagerAdapter(getChildFragmentManager(), getResources()));
holder.slidingTabs = view.findViewById(R.id.sliding_tabs);
holder.slidingTabs.setViewPager(holder.pager);
holder.tabs = view.findViewById(R.id.tabs);
holder.tabs.setupWithViewPager(holder.pager, false);
holder.recycledViewPool = new RecyclerView.RecycledViewPool();
return view;

View file

@ -7,6 +7,8 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.google.android.material.tabs.TabLayout;
import java.util.List;
import androidx.annotation.NonNull;
@ -20,7 +22,6 @@ import androidx.viewpager.widget.ViewPager;
import be.digitalia.fosdem.R;
import be.digitalia.fosdem.db.DatabaseManager;
import be.digitalia.fosdem.model.Day;
import be.digitalia.fosdem.widgets.SlidingTabLayout;
public class TracksFragment extends Fragment implements RecycledViewPoolProvider, Observer<List<Day>> {
@ -28,7 +29,7 @@ public class TracksFragment extends Fragment implements RecycledViewPoolProvider
View contentView;
View emptyView;
ViewPager pager;
SlidingTabLayout slidingTabs;
TabLayout tabs;
DaysAdapter daysAdapter;
RecyclerView.RecycledViewPool recycledViewPool;
}
@ -56,7 +57,7 @@ public class TracksFragment extends Fragment implements RecycledViewPoolProvider
holder.contentView = view.findViewById(R.id.content);
holder.emptyView = view.findViewById(android.R.id.empty);
holder.pager = view.findViewById(R.id.pager);
holder.slidingTabs = view.findViewById(R.id.sliding_tabs);
holder.tabs = view.findViewById(R.id.tabs);
holder.daysAdapter = new DaysAdapter(getChildFragmentManager());
holder.recycledViewPool = new RecyclerView.RecycledViewPool();
@ -108,7 +109,7 @@ public class TracksFragment extends Fragment implements RecycledViewPoolProvider
holder.emptyView.setVisibility(View.GONE);
if (holder.pager.getAdapter() == null) {
holder.pager.setAdapter(holder.daysAdapter);
holder.slidingTabs.setViewPager(holder.pager);
holder.tabs.setupWithViewPager(holder.pager);
}
if (savedCurrentPage != -1) {
holder.pager.setCurrentItem(Math.min(savedCurrentPage, totalPages - 1), false);

View file

@ -1,115 +0,0 @@
package be.digitalia.fosdem.widgets;
import android.content.Context;
import android.database.DataSetObservable;
import android.database.DataSetObserver;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
/**
* Vertical LinearLayout populated by a special adapter.
*
* @author Christophe Beyls
*/
public class AdapterLinearLayout extends LinearLayout {
/**
* Implement this Adapter to populate the layout.
* Call notifyDataSetChanged() to update it.
*/
public static abstract class Adapter<T> {
final DataSetObservable mDataSetObservable = new DataSetObservable();
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
public abstract int getCount();
public abstract T getItem(int position);
public abstract View getView(int position, View convertView, ViewGroup parent);
}
class AdapterLinearLayoutDataSetObserver extends DataSetObserver {
@Override
public void onChanged() {
populateFromAdapter();
}
}
private Adapter mAdapter;
private AdapterLinearLayoutDataSetObserver mDataSetObserver;
public AdapterLinearLayout(Context context) {
this(context, null);
}
public AdapterLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
setOrientation(VERTICAL);
}
public void setAdapter(Adapter adapter) {
if (mAdapter == adapter) {
return;
}
if (mAdapter != null && mDataSetObserver != null) {
mAdapter.mDataSetObservable.unregisterObserver(mDataSetObserver);
}
removeAllViews();
mAdapter = adapter;
if (adapter != null && mDataSetObserver != null) {
populateFromAdapter();
adapter.mDataSetObservable.registerObserver(mDataSetObserver);
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mDataSetObserver = new AdapterLinearLayoutDataSetObserver();
if (mAdapter != null) {
populateFromAdapter();
mAdapter.mDataSetObservable.registerObserver(mDataSetObserver);
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if ((mAdapter != null) && (mDataSetObserver != null)) {
mAdapter.mDataSetObservable.unregisterObserver(mDataSetObserver);
}
mDataSetObserver = null;
}
void populateFromAdapter() {
final Adapter adapter = mAdapter;
final int currentCount = getChildCount();
final int newCount = adapter.getCount();
final int commonCount = Math.min(currentCount, newCount);
// 1. Update common views
for (int i = 0; i < commonCount; ++i) {
final View currentView = getChildAt(i);
final View newView = adapter.getView(i, currentView, this);
if (currentView != newView) {
// Edge case: View is not recycled
removeViewAt(i);
addView(newView, i);
}
}
// 2a. Add missing views
for (int i = commonCount; i < newCount; ++i) {
addView(adapter.getView(i, null, this));
}
// 2b. Remove extra views (starting from the end to avoid array copies)
for (int i = currentCount - 1; i >= commonCount; --i) {
removeViewAt(i);
}
}
}

View file

@ -1,230 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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 be.digitalia.fosdem.widgets;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.view.Gravity;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.LinearLayoutCompat;
import be.digitalia.fosdem.R;
public class ForegroundLinearLayout extends LinearLayoutCompat {
private Drawable mForeground;
private final Rect mSelfBounds = new Rect();
private final Rect mOverlayBounds = new Rect();
private int mForegroundGravity = Gravity.FILL;
protected boolean mForegroundInPadding = true;
boolean mForegroundBoundsChanged = false;
public ForegroundLinearLayout(Context context) {
this(context, null);
}
public ForegroundLinearLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ForegroundLinearLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ForegroundLinearLayout,
defStyle, 0);
mForegroundGravity = a.getInt(
R.styleable.ForegroundLinearLayout_android_foregroundGravity, mForegroundGravity);
final Drawable d = a.getDrawable(R.styleable.ForegroundLinearLayout_android_foreground);
if (d != null) {
setForeground(d);
}
mForegroundInPadding = a.getBoolean(
R.styleable.ForegroundLinearLayout_foregroundInsidePadding, true);
a.recycle();
}
/**
* Describes how the foreground is positioned.
*
* @return foreground gravity.
* @see #setForegroundGravity(int)
*/
public int getForegroundGravity() {
return mForegroundGravity;
}
/**
* Describes how the foreground is positioned. Defaults to START and TOP.
*
* @param foregroundGravity See {@link Gravity}
* @see #getForegroundGravity()
*/
public void setForegroundGravity(int foregroundGravity) {
if (mForegroundGravity != foregroundGravity) {
if ((foregroundGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
foregroundGravity |= Gravity.START;
}
if ((foregroundGravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
foregroundGravity |= Gravity.TOP;
}
mForegroundGravity = foregroundGravity;
if (mForegroundGravity == Gravity.FILL && mForeground != null) {
Rect padding = new Rect();
mForeground.getPadding(padding);
}
requestLayout();
}
}
@Override
protected boolean verifyDrawable(@NonNull Drawable who) {
return super.verifyDrawable(who) || (who == mForeground);
}
@Override
public void jumpDrawablesToCurrentState() {
super.jumpDrawablesToCurrentState();
if (mForeground != null) {
mForeground.jumpToCurrentState();
}
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
if (mForeground != null && mForeground.isStateful()) {
mForeground.setState(getDrawableState());
}
}
/**
* Supply a Drawable that is to be rendered on top of all of the child
* views in the frame layout. Any padding in the Drawable will be taken
* into account by ensuring that the children are inset to be placed
* inside of the padding area.
*
* @param drawable The Drawable to be drawn on top of the children.
*/
public void setForeground(Drawable drawable) {
if (mForeground != drawable) {
if (mForeground != null) {
mForeground.setCallback(null);
unscheduleDrawable(mForeground);
}
mForeground = drawable;
if (drawable != null) {
setWillNotDraw(false);
drawable.setCallback(this);
if (drawable.isStateful()) {
drawable.setState(getDrawableState());
}
if (mForegroundGravity == Gravity.FILL) {
Rect padding = new Rect();
drawable.getPadding(padding);
}
} else {
setWillNotDraw(true);
}
requestLayout();
invalidate();
}
}
/**
* Returns the drawable used as the foreground of this FrameLayout. The
* foreground drawable, if non-null, is always drawn on top of the children.
*
* @return A Drawable or null if no foreground was set.
*/
public Drawable getForeground() {
return mForeground;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mForegroundBoundsChanged |= changed;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mForegroundBoundsChanged = true;
}
@Override
public void draw(@NonNull Canvas canvas) {
super.draw(canvas);
if (mForeground != null) {
final Drawable foreground = mForeground;
if (mForegroundBoundsChanged) {
mForegroundBoundsChanged = false;
final Rect selfBounds = mSelfBounds;
final Rect overlayBounds = mOverlayBounds;
final int w = getRight() - getLeft();
final int h = getBottom() - getTop();
if (mForegroundInPadding) {
selfBounds.set(0, 0, w, h);
} else {
selfBounds.set(getPaddingLeft(), getPaddingTop(),
w - getPaddingRight(), h - getPaddingBottom());
}
Gravity.apply(mForegroundGravity, foreground.getIntrinsicWidth(),
foreground.getIntrinsicHeight(), selfBounds, overlayBounds);
foreground.setBounds(overlayBounds);
}
foreground.draw(canvas);
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void drawableHotspotChanged(float x, float y) {
super.drawableHotspotChanged(x, y);
if (mForeground != null) {
mForeground.setHotspot(x, y);
}
}
}

View file

@ -1,116 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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 be.digitalia.fosdem.widgets;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import be.digitalia.fosdem.R;
public class ScrimInsetsFrameLayout extends FrameLayout {
Drawable mInsetForeground;
Rect mInsets;
private Rect mTempRect = new Rect();
public ScrimInsetsFrameLayout(Context context) {
this(context, null);
}
public ScrimInsetsFrameLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ScrimInsetsFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
final TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.ScrimInsetsFrameLayout, defStyleAttr,
R.style.Widget_Design_ScrimInsetsFrameLayout);
mInsetForeground = a.getDrawable(R.styleable.ScrimInsetsFrameLayout_insetForeground);
a.recycle();
setWillNotDraw(true); // No need to draw until the insets are adjusted
ViewCompat.setOnApplyWindowInsetsListener(this,
new androidx.core.view.OnApplyWindowInsetsListener() {
@Override
public WindowInsetsCompat onApplyWindowInsets(View v,
WindowInsetsCompat insets) {
if (null == mInsets) {
mInsets = new Rect();
}
mInsets.set(insets.getSystemWindowInsetLeft(),
insets.getSystemWindowInsetTop(),
insets.getSystemWindowInsetRight(),
insets.getSystemWindowInsetBottom());
setWillNotDraw(mInsets.isEmpty() || mInsetForeground == null);
ViewCompat.postInvalidateOnAnimation(ScrimInsetsFrameLayout.this);
return insets.consumeSystemWindowInsets();
}
});
}
@Override
public void draw(@NonNull Canvas canvas) {
super.draw(canvas);
int width = getWidth();
int height = getHeight();
if (mInsets != null && mInsetForeground != null) {
int sc = canvas.save();
canvas.translate(getScrollX(), getScrollY());
// Top
mTempRect.set(0, 0, width, mInsets.top);
mInsetForeground.setBounds(mTempRect);
mInsetForeground.draw(canvas);
// Bottom
mTempRect.set(0, height - mInsets.bottom, width, height);
mInsetForeground.setBounds(mTempRect);
mInsetForeground.draw(canvas);
// Left
mTempRect.set(0, mInsets.top, mInsets.left, height - mInsets.bottom);
mInsetForeground.setBounds(mTempRect);
mInsetForeground.draw(canvas);
// Right
mTempRect.set(width - mInsets.right, mInsets.top, width, height - mInsets.bottom);
mInsetForeground.setBounds(mTempRect);
mInsetForeground.draw(canvas);
canvas.restoreToCount(sc);
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (mInsetForeground != null) {
mInsetForeground.setCallback(this);
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mInsetForeground != null) {
mInsetForeground.setCallback(null);
}
}
}

View file

@ -1,424 +0,0 @@
/*
* Copyright 2014 Chris Banes
* Copyright 2016 Christophe Beyls
*
* 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 be.digitalia.fosdem.widgets;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.os.Build;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.ColorInt;
import androidx.annotation.IdRes;
import androidx.annotation.LayoutRes;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import be.digitalia.fosdem.R;
/**
*/
public class SlidingTabLayout extends HorizontalScrollView {
public interface TabListener {
void onTabSelected(int pos);
void onTabReSelected(int pos);
}
private static final int[][] TAB_COLOR_STATES = new int[][]{SELECTED_STATE_SET, EMPTY_STATE_SET};
private int mTitleOffset;
private int mTabViewLayoutId;
private int mTabViewTextViewId;
private boolean mDistributeEvenly;
private ColorStateList mTextColor;
ViewPager mViewPager;
PagerAdapter mAdapter;
private final InternalViewPagerListener mPageChangeListener = new InternalViewPagerListener();
private final PagerAdapterObserver mPagerAdapterObserver = new PagerAdapterObserver();
TabListener mTabListener;
final SlidingTabStrip mTabStrip;
public SlidingTabLayout(Context context) {
this(context, null);
}
public SlidingTabLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SlidingTabLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// Disable the Scroll Bar
setHorizontalScrollBarEnabled(false);
// Make sure that the Tab Strips fills this View
setFillViewport(true);
mTabStrip = new SlidingTabStrip(context);
addView(mTabStrip, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SlidingTabLayout,
defStyle, R.style.SlidingTabLayout);
mTabStrip.setSelectedIndicatorHeight(a.getDimensionPixelSize(R.styleable.SlidingTabLayout_indicatorHeight, 0));
mTabStrip.setSelectedIndicatorColor(a.getColor(R.styleable.SlidingTabLayout_indicatorColor, 0));
mTextColor = a.getColorStateList(R.styleable.SlidingTabLayout_textColor);
if ((mTextColor != null) && a.hasValue(R.styleable.SlidingTabLayout_selectedTextColor)) {
setTabTextColors(mTextColor.getDefaultColor(), a.getColor(R.styleable.SlidingTabLayout_selectedTextColor, 0));
}
setContentInsetStart(a.getDimensionPixelSize(R.styleable.SlidingTabLayout_contentInsetStart, 0));
setDistributeEvenly(a.getBoolean(R.styleable.SlidingTabLayout_distributeEvenly, false));
a.recycle();
}
public void setContentInsetStart(int offset) {
mTitleOffset = offset;
mTabStrip.setPadding(offset, 0, 0, 0);
}
public void setDistributeEvenly(boolean distributeEvenly) {
mDistributeEvenly = distributeEvenly;
}
/**
* Sets the color to be used for indicating the selected tab.
* This will override the style color.
*/
public void setSelectedTabIndicatorColor(@ColorInt int color) {
mTabStrip.setSelectedIndicatorColor(color);
}
public void setSelectedTabIndicatorHeight(int height) {
mTabStrip.setSelectedIndicatorHeight(height);
}
public void setTabTextColors(ColorStateList color) {
mTextColor = color;
}
public void setTabTextColors(@ColorInt int normalColor, @ColorInt int selectedColor) {
mTextColor = createColorStateList(normalColor, selectedColor);
}
private static ColorStateList createColorStateList(int defaultColor, int selectedColor) {
final int[] colors = new int[]{selectedColor, defaultColor};
return new ColorStateList(TAB_COLOR_STATES, colors);
}
/**
* Set the custom layout to be inflated for the tab views.
*
* @param layoutResId Layout id to be inflated
* @param textViewId id of the {@link TextView} in the inflated view
*/
public void setCustomTabView(@LayoutRes int layoutResId, @IdRes int textViewId) {
mTabViewLayoutId = layoutResId;
mTabViewTextViewId = textViewId;
}
public void setTabListener(TabListener tabListener) {
mTabListener = tabListener;
}
/**
* Sets the associated view pager. The ViewPager must have an adapter set.
* The SlidingTabLayout will then listen for changes and update the tabs automatically.
*/
public void setViewPager(ViewPager viewPager) {
if (mViewPager != null) {
mViewPager.removeOnPageChangeListener(mPageChangeListener);
mAdapter.unregisterDataSetObserver(mPagerAdapterObserver);
}
if (viewPager != null) {
PagerAdapter adapter = viewPager.getAdapter();
if (adapter == null) {
throw new IllegalArgumentException("ViewPager does not have a PagerAdapter set");
}
mViewPager = viewPager;
mAdapter = adapter;
mPageChangeListener.reset();
viewPager.addOnPageChangeListener(mPageChangeListener);
adapter.registerDataSetObserver(mPagerAdapterObserver);
} else {
mViewPager = null;
mAdapter = null;
}
notifyDataSetChanged();
}
void notifyDataSetChanged() {
mTabStrip.removeAllViews();
if (mViewPager != null) {
populateTabStrip();
}
}
static void setSelectedCompat(View view, boolean selected) {
final boolean becomeSelected = selected && !view.isSelected();
view.setSelected(selected);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN && becomeSelected) {
// Pre-JB we need to manually send the TYPE_VIEW_SELECTED event
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
}
}
private void populateTabStrip() {
final int adapterCount = mAdapter.getCount();
final View.OnClickListener tabClickListener = new TabClickListener();
final LayoutInflater inflater = LayoutInflater.from(getContext());
final int currentItem = mViewPager.getCurrentItem();
for (int i = 0; i < adapterCount; i++) {
View tabView;
TextView tabTitleView;
if (mTabViewLayoutId != 0) {
// If there is a custom tab view layout id set, try and inflate it
tabView = inflater.inflate(mTabViewLayoutId, mTabStrip, false);
tabTitleView = tabView.findViewById(mTabViewTextViewId);
if (tabTitleView == null) {
tabTitleView = (TextView) tabView;
}
} else {
// Inflate our default tab layout
tabView = inflater.inflate(R.layout.widget_sliding_tab_layout_text, mTabStrip, false);
tabTitleView = (TextView) tabView;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
// Emulate Roboto Medium in previous Android versions
tabTitleView.setTypeface(Typeface.DEFAULT_BOLD);
}
}
if (mTextColor != null) {
tabTitleView.setTextColor(mTextColor);
}
if (mDistributeEvenly) {
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) tabView.getLayoutParams();
lp.width = 0;
lp.weight = 1;
}
tabTitleView.setText(mAdapter.getPageTitle(i));
tabView.setFocusable(true);
tabView.setOnClickListener(tabClickListener);
mTabStrip.addView(tabView);
if (i == currentItem) {
setSelectedCompat(tabView, true);
}
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (mViewPager != null) {
scrollToTab(mViewPager.getCurrentItem(), 0);
}
announceSelectedTab(hasWindowFocus());
}
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
announceSelectedTab(hasWindowFocus);
}
private void announceSelectedTab(boolean hasWindowFocus) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && hasWindowFocus) {
getHandler().post(new Runnable() {
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@Override
public void run() {
if (mViewPager != null && mAdapter != null && mAdapter.getCount() > 0) {
CharSequence pageTitle = mAdapter.getPageTitle(mViewPager.getCurrentItem());
if (!TextUtils.isEmpty(pageTitle)) {
announceForAccessibility(pageTitle);
}
}
}
});
}
}
void scrollToTab(int tabIndex, float positionOffset) {
final int tabStripChildCount = mTabStrip.getChildCount();
if (tabStripChildCount == 0 || tabIndex < 0 || tabIndex >= tabStripChildCount) {
return;
}
View selectedChild = mTabStrip.getChildAt(tabIndex);
if (selectedChild != null) {
int targetScrollX = selectedChild.getLeft() +
Math.round(positionOffset * selectedChild.getWidth()) - mTitleOffset;
scrollTo(targetScrollX, 0);
}
}
class InternalViewPagerListener implements ViewPager.OnPageChangeListener {
private int mScrollState;
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
int tabStripChildCount = mTabStrip.getChildCount();
if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) {
return;
}
mTabStrip.onViewPagerPageChanged(position, positionOffset);
scrollToTab(position, positionOffset);
}
@Override
public void onPageScrollStateChanged(int state) {
mScrollState = state;
}
@Override
public void onPageSelected(int position) {
if (mScrollState == ViewPager.SCROLL_STATE_IDLE) {
mTabStrip.onViewPagerPageChanged(position, 0f);
scrollToTab(position, 0);
}
final int childCount = mTabStrip.getChildCount();
for (int i = 0; i < childCount; i++) {
setSelectedCompat(mTabStrip.getChildAt(i), position == i);
}
}
public void reset() {
mScrollState = ViewPager.SCROLL_STATE_IDLE;
}
}
class PagerAdapterObserver extends DataSetObserver {
@Override
public void onChanged() {
notifyDataSetChanged();
}
@Override
public void onInvalidated() {
notifyDataSetChanged();
}
}
class TabClickListener implements View.OnClickListener {
@Override
public void onClick(View v) {
final int childCount = mTabStrip.getChildCount();
for (int i = 0; i < childCount; i++) {
if (v == mTabStrip.getChildAt(i)) {
final int previousPos = mViewPager.getCurrentItem();
mViewPager.setCurrentItem(i);
if (mTabListener != null) {
if (previousPos != i) {
mTabListener.onTabSelected(i);
} else {
mTabListener.onTabReSelected(i);
}
}
return;
}
}
}
}
static class SlidingTabStrip extends LinearLayout {
private int mSelectedIndicatorHeight;
private final Paint mSelectedIndicatorPaint;
private int mSelectedPosition;
private float mSelectionOffset;
SlidingTabStrip(Context context) {
super(context);
setWillNotDraw(false);
mSelectedIndicatorPaint = new Paint();
}
void setSelectedIndicatorColor(@ColorInt int color) {
mSelectedIndicatorPaint.setColor(color);
invalidate();
}
void setSelectedIndicatorHeight(int height) {
mSelectedIndicatorHeight = height;
invalidate();
}
void onViewPagerPageChanged(int position, float positionOffset) {
mSelectedPosition = position;
mSelectionOffset = positionOffset;
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
final int height = getHeight();
final int childCount = getChildCount();
// Thick colored underline below the current selection
if (childCount > 0) {
View selectedTitle = getChildAt(mSelectedPosition);
int left = selectedTitle.getLeft();
int right = selectedTitle.getRight();
if (mSelectionOffset > 0f && mSelectedPosition < (getChildCount() - 1)) {
// Draw the selection partway between the tabs
View nextTitle = getChildAt(mSelectedPosition + 1);
left = (int) (mSelectionOffset * nextTitle.getLeft() +
(1.0f - mSelectionOffset) * left);
right = (int) (mSelectionOffset * nextTitle.getRight() +
(1.0f - mSelectionOffset) * right);
}
canvas.drawRect(left, height - mSelectedIndicatorHeight, right,
height, mSelectedIndicatorPaint);
}
}
}
}

View file

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_enabled="true"
android:state_pressed="true">
<objectAnimator
android:duration="@android:integer/config_shortAnimTime"
android:propertyName="translationZ"
android:valueTo="@dimen/fab_press_translation_z"
android:valueType="floatType"/>
</item>
<item>
<objectAnimator
android:duration="@android:integer/config_shortAnimTime"
android:propertyName="translationZ"
android:valueTo="0"
android:valueType="floatType"/>
</item>
</selector>

View file

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/fab_background_highlighted" android:state_pressed="true"/>
<item android:drawable="@drawable/fab_background_highlighted" android:state_focused="true"/>
<item>
<shape android:shape="oval">
<solid android:color="@color/color_accent"/>
</shape>
</item>
</selector>

View file

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="oval">
<solid android:color="@color/color_accent"/>
</shape>
</item>
<item>
<shape android:shape="oval">
<solid android:color="@color/ripple_material_dark"/>
</shape>
</item>
</layer-list>

View file

@ -1,52 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activities.TrackScheduleActivity">
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activities.TrackScheduleActivity">
<LinearLayout
android:layout_width="match_parent"
<FrameLayout
android:id="@+id/schedule"
android:layout_width="@dimen/schedule_column_width"
android:layout_height="match_parent"
android:orientation="vertical">
android:layout_gravity="start"
android:layout_marginTop="128dp" />
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
style="@style/Toolbar.Fosdem"
android:layout_width="match_parent"
android:layout_height="128dp"
android:elevation="4dp"
android:gravity="top"/>
<FrameLayout
android:id="@+id/schedule"
android:layout_width="@dimen/schedule_column_width"
android:layout_height="0dp"
android:layout_gravity="start"
android:layout_weight="1"/>
</LinearLayout>
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
style="@style/Toolbar.Fosdem"
android:layout_width="match_parent"
android:layout_height="128dp"
android:elevation="4dp"
android:gravity="top" />
<androidx.cardview.widget.CardView
android:id="@+id/event"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="@dimen/detail_card_view_margin_bottom"
android:layout_marginEnd="@dimen/detail_card_view_margin_end"
android:layout_marginLeft="@dimen/detail_card_view_margin_start"
android:layout_marginRight="@dimen/detail_card_view_margin_end"
android:layout_marginStart="@dimen/detail_card_view_margin_start"
android:layout_marginLeft="@dimen/detail_card_view_margin_start"
android:layout_marginTop="@dimen/detail_card_view_margin_top"
app:cardElevation="@dimen/detail_card_view_elevation"/>
android:layout_marginEnd="@dimen/detail_card_view_margin_end"
android:layout_marginRight="@dimen/detail_card_view_margin_end"
android:layout_marginBottom="@dimen/detail_card_view_margin_bottom"
app:cardElevation="@dimen/detail_card_view_elevation"
app:cardPreventCornerOverlap="false" />
<ImageView
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
style="@style/FloatingActionButton"
android:layout_gravity="top|end"
android:layout_marginEnd="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="100dp"
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"
app:srcCompat="@drawable/ic_bookmark_outline_white_24dp"/>
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" />
</merge>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -1,18 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<be.digitalia.fosdem.widgets.SlidingTabLayout
android:id="@+id/sliding_tabs"
style="@style/SlidingTabs"
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
style="@style/Tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="@dimen/toolbar_elevation"
app:contentInsetStart="0dp"
app:distributeEvenly="true"/>
android:elevation="@dimen/toolbar_elevation"/>
<androidx.viewpager.widget.ViewPager
android:id="@+id/pager"

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
@ -9,12 +10,13 @@
android:layout_height="match_parent"
android:orientation="vertical">
<be.digitalia.fosdem.widgets.SlidingTabLayout
android:id="@+id/sliding_tabs"
style="@style/SlidingTabs"
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
style="@style/Tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="@dimen/toolbar_elevation"/>
android:elevation="@dimen/toolbar_elevation"
app:tabMode="scrollable"/>
<androidx.viewpager.widget.ViewPager
android:id="@+id/pager"

View file

@ -1,69 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<be.digitalia.fosdem.widgets.ScrimInsetsFrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main_menu"
android:layout_width="@dimen/main_menu_width"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="@color/main_menu_background"
android:fitsSystemWindows="true">
<ScrollView
android:id="@+id/main_menu_scroll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="@dimen/main_menu_footer_height">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:layout_width="match_parent"
android:layout_height="@dimen/main_menu_header_height"
android:contentDescription="@string/app_name"
android:padding="@dimen/main_menu_padding"
android:scaleType="center"
app:srcCompat="@drawable/fosdem_title"/>
<be.digitalia.fosdem.widgets.AdapterLinearLayout
android:id="@+id/sections"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<View
style="@style/SeparatorLine"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"/>
<TextView
android:id="@+id/settings"
style="@style/MainMenuItem"
android:text="@string/settings"/>
<TextView
android:id="@+id/volunteer"
style="@style/MainMenuItem"
android:text="@string/volunteer"/>
</LinearLayout>
</ScrollView>
<TextView
android:id="@+id/last_update"
style="@style/TextAppearance.AppCompat.Caption"
android:layout_width="match_parent"
android:layout_height="@dimen/main_menu_footer_height"
android:layout_gravity="bottom"
android:background="@color/main_menu_footer_background"
android:ellipsize="end"
android:gravity="center"
android:lines="1"
android:padding="4dp"
tools:text="DB last updated: 1 jan. 2015 13:37:00"/>
</be.digitalia.fosdem.widgets.ScrimInsetsFrameLayout>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<be.digitalia.fosdem.widgets.ForegroundLinearLayout
<com.google.android.material.internal.ForegroundLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
@ -46,4 +46,4 @@
android:textAppearance="@style/TextAppearance.AppCompat.Small"
tools:text="Saturday, 09:30 - 09:55 | Janson"/>
</be.digitalia.fosdem.widgets.ForegroundLinearLayout>
</com.google.android.material.internal.ForegroundLinearLayout>

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView
android:id="@+id/section_text"
style="@style/MainMenuItem"
xmlns:android="http://schemas.android.com/apk/res/android"/>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<be.digitalia.fosdem.widgets.PhotoViewDrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<be.digitalia.fosdem.widgets.PhotoViewDrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
@ -10,7 +10,7 @@
<!-- Main content view -->
<FrameLayout
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
@ -23,13 +23,13 @@
android:id="@+id/toolbar"
style="@style/Toolbar.Fosdem"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
android:layout_height="wrap_content" />
<FrameLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
android:layout_weight="1" />
</LinearLayout>
@ -42,12 +42,46 @@
android:layout_marginTop="-7dp"
android:max="100"
android:visibility="gone"
tools:visibility="visible"/>
tools:visibility="visible" />
</FrameLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<!-- Navigation drawer -->
<include layout="@layout/include_navigation_drawer"/>
<FrameLayout
android:id="@+id/main_menu"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true">
<com.google.android.material.navigation.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginBottom="@dimen/main_menu_footer_height"
android:background="@color/main_menu_background"
android:fitsSystemWindows="true"
app:elevation="0dp"
app:headerLayout="@layout/navigation_header"
app:menu="@menu/main_navigation" />
<TextView
android:id="@+id/last_update"
style="@style/TextAppearance.AppCompat.Caption"
android:layout_width="match_parent"
android:layout_height="@dimen/main_menu_footer_height"
android:layout_gravity="bottom"
android:background="@color/main_menu_footer_background"
android:ellipsize="end"
android:gravity="center"
android:lines="1"
android:paddingStart="4dp"
android:paddingLeft="4dp"
android:paddingEnd="4dp"
android:paddingRight="4dp"
tools:text="DB last updated: 1 jan. 2015 13:37:00" />
</FrameLayout>
</be.digitalia.fosdem.widgets.PhotoViewDrawerLayout>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="@dimen/main_menu_header_height"
android:contentDescription="@string/app_name"
android:padding="@dimen/main_menu_padding"
android:scaleType="center"
app:srcCompat="@drawable/fosdem_title" />

View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group
android:id="@+id/menu_group_sections"
android:checkableBehavior="single">
<item
android:id="@+id/menu_tracks"
android:icon="@drawable/ic_event_grey600_24dp"
android:title="@string/menu_tracks" />
<item
android:id="@+id/menu_bookmarks"
android:icon="@drawable/ic_bookmark_grey600_24dp"
android:title="@string/menu_bookmarks" />
<item
android:id="@+id/menu_live"
android:icon="@drawable/ic_play_circle_outline_grey600_24dp"
android:title="@string/menu_live" />
<item
android:id="@+id/menu_speakers"
android:icon="@drawable/ic_people_grey600_24dp"
android:title="@string/menu_speakers" />
<item
android:id="@+id/menu_map"
android:icon="@drawable/ic_map_grey600_24dp"
android:title="@string/menu_map" />
</group>
<group
android:id="@+id/menu_group_extras"
android:checkableBehavior="none">
<item
android:id="@+id/menu_settings"
android:title="@string/settings" />
<item
android:id="@+id/menu_volunteer"
android:title="@string/volunteer" />
</group>
</menu>

View file

@ -6,5 +6,6 @@
<dimen name="detail_card_view_margin_end">96dp</dimen>
<dimen name="detail_card_view_margin_top">64dp</dimen>
<dimen name="detail_card_view_margin_bottom">0dp</dimen>
<dimen name="detail_fab_margin_end">20dp</dimen>
</resources>

View file

@ -10,11 +10,4 @@
<item name="android:statusBarColor">@android:color/transparent</item>
</style>
<!-- Styles -->
<style name="FloatingActionButton" parent="FloatingActionButtonBase">
<item name="android:elevation">@dimen/fab_elevation</item>
<item name="android:stateListAnimator">@animator/fab_state_list</item>
</style>
</resources>

View file

@ -5,34 +5,6 @@
<attr name="activatedBackgroundIndicator" format="reference"/>
<attr name="recyclerViewStyle" format="reference"/>
<declare-styleable name="SlidingTabLayout">
<attr name="indicatorColor" format="color"/>
<attr name="indicatorHeight" format="dimension"/>
<attr name="textColor" format="color|reference"/>
<attr name="selectedTextColor" format="color"/>
<attr name="contentInsetStart"/>
<attr name="distributeEvenly" format="boolean"/>
</declare-styleable>
<style name="SlidingTabLayout" parent="android:Widget">
<item name="indicatorColor">?attr/colorAccent</item>
<item name="indicatorHeight">2dp</item>
<item name="textColor">?android:textColorSecondary</item>
<item name="selectedTextColor">?android:textColorPrimary</item>
<item name="contentInsetStart">24dp</item>
<item name="distributeEvenly">false</item>
</style>
<declare-styleable name="ScrimInsetsFrameLayout">
<attr name="insetForeground" format="color|reference"/>
</declare-styleable>
<declare-styleable name="ForegroundLinearLayout">
<attr name="android:foreground"/>
<attr name="android:foregroundGravity"/>
<attr name="foregroundInsidePadding" format="boolean"/>
</declare-styleable>
<declare-styleable name="PrimaryTextColors">
<attr name="android:textColorPrimary"/>
<attr name="android:textColorPrimaryInverse"/>

View file

@ -1,25 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="main_menu_width">260dp</dimen>
<dimen name="main_menu_header_height">128dp</dimen>
<dimen name="main_menu_padding">16dp</dimen>
<dimen name="main_menu_drawable_padding">32dp</dimen>
<dimen name="main_menu_item_height">48dp</dimen>
<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="schedule_column_width">360dp</dimen>
<dimen name="detail_card_view_elevation">6dp</dimen>
<!-- Enlarge the card view by 7dp left & right and 10dp top & bottom
to compensate for the compatibility padding of the 6dp elevation -->
<dimen name="detail_card_view_margin_start">353dp</dimen>
<dimen name="detail_card_view_margin_end">89dp</dimen>
<dimen name="detail_card_view_margin_top">54dp</dimen>
<dimen name="detail_card_view_margin_bottom">-10dp</dimen>
<dimen name="fab_diameter">56dp</dimen>
<dimen name="fab_elevation">8dp</dimen>
<dimen name="fab_press_translation_z">6dp</dimen>
<!-- Enlarge the card view by 6dp left & right and 9dp top & bottom
to compensate for the compatibility padding of the elevation -->
<dimen name="detail_card_view_margin_start">354dp</dimen>
<dimen name="detail_card_view_margin_end">90dp</dimen>
<dimen name="detail_card_view_margin_top">55dp</dimen>
<dimen name="detail_card_view_margin_bottom">-9dp</dimen>
<dimen name="detail_fab_margin_end">8dp</dimen>
</resources>

View file

@ -64,38 +64,12 @@
<item name="android:scrollbars">vertical</item>
</style>
<style name="Widget.Design.ScrimInsetsFrameLayout" parent="">
<item name="insetForeground">#4000</item>
</style>
<style name="MainMenuItem" parent="TextAppearance.AppCompat.Body2">
<item name="android:background">@drawable/menu_item_background</item>
<item name="android:focusable">true</item>
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">@dimen/main_menu_item_height</item>
<item name="android:gravity">start|center_vertical</item>
<item name="android:paddingLeft">@dimen/main_menu_padding</item>
<item name="android:paddingStart" tools:ignore="NewApi">@dimen/main_menu_padding</item>
<item name="android:drawablePadding">@dimen/main_menu_drawable_padding</item>
</style>
<style name="SlidingTabs" parent="SlidingTabLayout">
<style name="Tabs" parent="Widget.Design.TabLayout">
<item name="android:theme">@style/ThemeOverlay.AppCompat.Dark</item>
<item name="android:background">?attr/colorPrimary</item>
<item name="indicatorColor">@android:color/white</item>
<item name="tabIndicatorColor">@android:color/white</item>
</style>
<style name="FloatingActionButtonBase">
<item name="android:layout_width">@dimen/fab_diameter</item>
<item name="android:layout_height">@dimen/fab_diameter</item>
<item name="android:background">@drawable/fab_background</item>
<item name="android:scaleType">center</item>
<item name="android:clickable">true</item>
<item name="android:focusable">true</item>
</style>
<style name="FloatingActionButton" parent="FloatingActionButtonBase"/>
<style name="SeparatorLine">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">1dp</item>