Search in sources :

Example 1 with InfoFullscreenHelper

use of xyz.zedler.patrick.grocy.helper.InfoFullscreenHelper in project grocy-android by patzly.

the class InventoryFragment method onViewCreated.

@Override
public void onViewCreated(@Nullable View view, @Nullable Bundle savedInstanceState) {
    activity = (MainActivity) requireActivity();
    InventoryFragmentArgs args = InventoryFragmentArgs.fromBundle(requireArguments());
    viewModel = new ViewModelProvider(this, new InventoryViewModelFactory(activity.getApplication(), args)).get(InventoryViewModel.class);
    binding.setActivity(activity);
    binding.setViewModel(viewModel);
    binding.setFragment(this);
    binding.setFormData(viewModel.getFormData());
    binding.setLifecycleOwner(getViewLifecycleOwner());
    infoFullscreenHelper = new InfoFullscreenHelper(binding.container);
    // INITIALIZE VIEWS
    viewModel.getInfoFullscreenLive().observe(getViewLifecycleOwner(), infoFullscreen -> infoFullscreenHelper.setInfo(infoFullscreen));
    viewModel.getIsLoadingLive().observe(getViewLifecycleOwner(), isDownloading -> binding.swipe.setRefreshing(isDownloading));
    viewModel.getEventHandler().observeEvent(getViewLifecycleOwner(), event -> {
        if (event.getType() == Event.SNACKBAR_MESSAGE) {
            activity.showSnackbar(((SnackbarMessage) event).getSnackbar(activity, activity.binding.frameMainContainer));
        } else if (event.getType() == Event.TRANSACTION_SUCCESS) {
            assert getArguments() != null;
            if (InventoryFragmentArgs.fromBundle(getArguments()).getCloseWhenFinished()) {
                activity.navigateUp();
            } else {
                clearFormAndFocusProductInput();
            }
        } else if (event.getType() == Event.BOTTOM_SHEET) {
            BottomSheetEvent bottomSheetEvent = (BottomSheetEvent) event;
            activity.showBottomSheet(bottomSheetEvent.getBottomSheet(), event.getBundle());
        } else if (event.getType() == Event.FOCUS_INVALID_VIEWS) {
            focusNextInvalidView();
        } else if (event.getType() == Event.QUICK_MODE_ENABLED) {
            focusProductInputIfNecessary();
        } else if (event.getType() == Event.QUICK_MODE_DISABLED) {
            clearInputFocus();
        } else if (event.getType() == Event.CONTINUE_SCANNING) {
            embeddedFragmentScanner.startScannerIfVisible();
        } else if (event.getType() == Event.CHOOSE_PRODUCT) {
            String barcode = event.getBundle().getString(ARGUMENT.BARCODE);
            navigate(InventoryFragmentDirections.actionInventoryFragmentToChooseProductFragment(barcode));
        }
    });
    Integer productIdSavedSate = (Integer) getFromThisDestinationNow(Constants.ARGUMENT.PRODUCT_ID);
    if (productIdSavedSate != null) {
        removeForThisDestination(Constants.ARGUMENT.PRODUCT_ID);
        viewModel.setQueueEmptyAction(() -> viewModel.setProduct(productIdSavedSate));
    } else if (NumUtil.isStringInt(args.getProductId())) {
        int productId = Integer.parseInt(args.getProductId());
        setArguments(new InventoryFragmentArgs.Builder(args).setProductId(null).build().toBundle());
        viewModel.setQueueEmptyAction(() -> viewModel.setProduct(productId));
    }
    String barcode = (String) getFromThisDestinationNow(ARGUMENT.BARCODE);
    if (barcode != null) {
        removeForThisDestination(Constants.ARGUMENT.BARCODE);
        viewModel.addBarcodeToExistingProduct(barcode);
    }
    embeddedFragmentScanner.setScannerVisibilityLive(viewModel.getFormData().getScannerVisibilityLive());
    // following lines are necessary because no observers are set in Views
    viewModel.getFormData().getQuantityUnitStockLive().observe(getViewLifecycleOwner(), i -> {
    });
    if (savedInstanceState == null) {
        viewModel.loadFromDatabase(true);
    }
    focusProductInputIfNecessary();
    setHasOptionsMenu(true);
    updateUI(args.getAnimateStart() && savedInstanceState == null);
}
Also used : InventoryViewModelFactory(xyz.zedler.patrick.grocy.viewmodel.InventoryViewModel.InventoryViewModelFactory) BottomSheetEvent(xyz.zedler.patrick.grocy.model.BottomSheetEvent) InventoryViewModel(xyz.zedler.patrick.grocy.viewmodel.InventoryViewModel) InfoFullscreenHelper(xyz.zedler.patrick.grocy.helper.InfoFullscreenHelper) ViewModelProvider(androidx.lifecycle.ViewModelProvider)

Example 2 with InfoFullscreenHelper

use of xyz.zedler.patrick.grocy.helper.InfoFullscreenHelper in project grocy-android by patzly.

the class MasterProductCatAmountFragment method onViewCreated.

@Override
public void onViewCreated(@Nullable View view, @Nullable Bundle savedInstanceState) {
    activity = (MainActivity) requireActivity();
    MasterProductFragmentArgs args = MasterProductFragmentArgs.fromBundle(requireArguments());
    viewModel = new ViewModelProvider(this, new MasterProductCatAmountViewModel.MasterProductCatAmountViewModelFactory(activity.getApplication(), args)).get(MasterProductCatAmountViewModel.class);
    binding.setActivity(activity);
    binding.setFormData(viewModel.getFormData());
    binding.setViewModel(viewModel);
    binding.setFragment(this);
    binding.setLifecycleOwner(getViewLifecycleOwner());
    viewModel.getEventHandler().observeEvent(getViewLifecycleOwner(), event -> {
        if (event.getType() == Event.SNACKBAR_MESSAGE) {
            SnackbarMessage message = (SnackbarMessage) event;
            Snackbar snack = message.getSnackbar(activity, activity.binding.frameMainContainer);
            activity.showSnackbar(snack);
        } else if (event.getType() == Event.NAVIGATE_UP) {
            activity.navigateUp();
        } else if (event.getType() == Event.SET_SHOPPING_LIST_ID) {
            int id = event.getBundle().getInt(Constants.ARGUMENT.SELECTED_ID);
            setForDestination(R.id.shoppingListFragment, Constants.ARGUMENT.SELECTED_ID, id);
        } else if (event.getType() == Event.BOTTOM_SHEET) {
            BottomSheetEvent bottomSheetEvent = (BottomSheetEvent) event;
            activity.showBottomSheet(bottomSheetEvent.getBottomSheet(), event.getBundle());
        }
    });
    infoFullscreenHelper = new InfoFullscreenHelper(binding.container);
    viewModel.getInfoFullscreenLive().observe(getViewLifecycleOwner(), infoFullscreen -> infoFullscreenHelper.setInfo(infoFullscreen));
    viewModel.getIsLoadingLive().observe(getViewLifecycleOwner(), isLoading -> {
        if (!isLoading) {
            viewModel.setCurrentQueueLoading(null);
        }
    });
    if (savedInstanceState == null) {
        viewModel.loadFromDatabase(true);
    }
    updateUI(savedInstanceState == null);
}
Also used : SnackbarMessage(xyz.zedler.patrick.grocy.model.SnackbarMessage) BottomSheetEvent(xyz.zedler.patrick.grocy.model.BottomSheetEvent) InfoFullscreenHelper(xyz.zedler.patrick.grocy.helper.InfoFullscreenHelper) MasterProductCatAmountViewModel(xyz.zedler.patrick.grocy.viewmodel.MasterProductCatAmountViewModel) ViewModelProvider(androidx.lifecycle.ViewModelProvider) Snackbar(com.google.android.material.snackbar.Snackbar)

Example 3 with InfoFullscreenHelper

use of xyz.zedler.patrick.grocy.helper.InfoFullscreenHelper in project grocy-android by patzly.

the class MasterProductCatBarcodesFragment method onViewCreated.

@Override
public void onViewCreated(@Nullable View view, @Nullable Bundle savedInstanceState) {
    activity = (MainActivity) requireActivity();
    clickUtil = new ClickUtil();
    MasterProductFragmentArgs args = MasterProductFragmentArgs.fromBundle(requireArguments());
    viewModel = new ViewModelProvider(this, new MasterProductCatBarcodesViewModel.MasterProductCatBarcodesViewModelFactory(activity.getApplication(), args)).get(MasterProductCatBarcodesViewModel.class);
    binding.setActivity(activity);
    binding.setViewModel(viewModel);
    binding.setFragment(this);
    binding.setLifecycleOwner(getViewLifecycleOwner());
    viewModel.getEventHandler().observeEvent(getViewLifecycleOwner(), event -> {
        if (event.getType() == Event.SNACKBAR_MESSAGE) {
            SnackbarMessage message = (SnackbarMessage) event;
            Snackbar snack = message.getSnackbar(activity, activity.binding.frameMainContainer);
            activity.showSnackbar(snack);
        } else if (event.getType() == Event.NAVIGATE_UP) {
            activity.navigateUp();
        } else if (event.getType() == Event.BOTTOM_SHEET) {
            BottomSheetEvent bottomSheetEvent = (BottomSheetEvent) event;
            activity.showBottomSheet(bottomSheetEvent.getBottomSheet(), event.getBundle());
        }
    });
    infoFullscreenHelper = new InfoFullscreenHelper(binding.frameContainer);
    viewModel.getInfoFullscreenLive().observe(getViewLifecycleOwner(), infoFullscreen -> infoFullscreenHelper.setInfo(infoFullscreen));
    viewModel.getIsLoadingLive().observe(getViewLifecycleOwner(), isLoading -> {
        if (!isLoading) {
            viewModel.setCurrentQueueLoading(null);
        }
    });
    binding.recycler.setLayoutManager(new LinearLayoutManager(activity, LinearLayoutManager.VERTICAL, false));
    binding.recycler.setItemAnimator(new DefaultItemAnimator());
    binding.recycler.setAdapter(new MasterPlaceholderAdapter());
    viewModel.getProductBarcodesLive().observe(getViewLifecycleOwner(), barcodes -> {
        if (barcodes == null) {
            return;
        }
        if (barcodes.isEmpty()) {
            InfoFullscreen info = new InfoFullscreen(InfoFullscreen.INFO_EMPTY_PRODUCT_BARCODES);
            viewModel.getInfoFullscreenLive().setValue(info);
        } else {
            viewModel.getInfoFullscreenLive().setValue(null);
        }
        if (binding.recycler.getAdapter() instanceof ProductBarcodeAdapter) {
            ((ProductBarcodeAdapter) binding.recycler.getAdapter()).updateData(barcodes);
        } else {
            binding.recycler.setAdapter(new ProductBarcodeAdapter(barcodes, this, viewModel.getQuantityUnits(), viewModel.getStores()));
        }
    });
    if (savedInstanceState == null) {
        viewModel.loadFromDatabase(true);
    }
    updateUI(savedInstanceState == null);
}
Also used : BottomSheetEvent(xyz.zedler.patrick.grocy.model.BottomSheetEvent) MasterPlaceholderAdapter(xyz.zedler.patrick.grocy.adapter.MasterPlaceholderAdapter) InfoFullscreenHelper(xyz.zedler.patrick.grocy.helper.InfoFullscreenHelper) LinearLayoutManager(androidx.recyclerview.widget.LinearLayoutManager) DefaultItemAnimator(androidx.recyclerview.widget.DefaultItemAnimator) MasterProductCatBarcodesViewModel(xyz.zedler.patrick.grocy.viewmodel.MasterProductCatBarcodesViewModel) SnackbarMessage(xyz.zedler.patrick.grocy.model.SnackbarMessage) InfoFullscreen(xyz.zedler.patrick.grocy.model.InfoFullscreen) ClickUtil(xyz.zedler.patrick.grocy.util.ClickUtil) ViewModelProvider(androidx.lifecycle.ViewModelProvider) Snackbar(com.google.android.material.snackbar.Snackbar) ProductBarcodeAdapter(xyz.zedler.patrick.grocy.adapter.ProductBarcodeAdapter)

Example 4 with InfoFullscreenHelper

use of xyz.zedler.patrick.grocy.helper.InfoFullscreenHelper in project grocy-android by patzly.

the class MasterProductCatConversionsFragment method onViewCreated.

@Override
public void onViewCreated(@Nullable View view, @Nullable Bundle savedInstanceState) {
    activity = (MainActivity) requireActivity();
    clickUtil = new ClickUtil();
    MasterProductFragmentArgs args = MasterProductFragmentArgs.fromBundle(requireArguments());
    viewModel = new ViewModelProvider(this, new MasterProductCatConversionsViewModel.MasterProductCatConversionsViewModelFactory(activity.getApplication(), args)).get(MasterProductCatConversionsViewModel.class);
    binding.setActivity(activity);
    binding.setViewModel(viewModel);
    binding.setFragment(this);
    binding.setLifecycleOwner(getViewLifecycleOwner());
    viewModel.getEventHandler().observeEvent(getViewLifecycleOwner(), event -> {
        if (event.getType() == Event.SNACKBAR_MESSAGE) {
            SnackbarMessage message = (SnackbarMessage) event;
            Snackbar snack = message.getSnackbar(activity, activity.binding.frameMainContainer);
            activity.showSnackbar(snack);
        } else if (event.getType() == Event.NAVIGATE_UP) {
            activity.navigateUp();
        } else if (event.getType() == Event.BOTTOM_SHEET) {
            BottomSheetEvent bottomSheetEvent = (BottomSheetEvent) event;
            activity.showBottomSheet(bottomSheetEvent.getBottomSheet(), event.getBundle());
        }
    });
    infoFullscreenHelper = new InfoFullscreenHelper(binding.frameContainer);
    viewModel.getInfoFullscreenLive().observe(getViewLifecycleOwner(), infoFullscreen -> infoFullscreenHelper.setInfo(infoFullscreen));
    viewModel.getIsLoadingLive().observe(getViewLifecycleOwner(), isLoading -> {
        if (!isLoading) {
            viewModel.setCurrentQueueLoading(null);
        }
    });
    binding.recycler.setLayoutManager(new LinearLayoutManager(activity, LinearLayoutManager.VERTICAL, false));
    binding.recycler.setItemAnimator(new DefaultItemAnimator());
    binding.recycler.setAdapter(new MasterPlaceholderAdapter());
    viewModel.getQuantityUnitConversionsLive().observe(getViewLifecycleOwner(), conversions -> {
        if (conversions == null) {
            return;
        }
        if (conversions.isEmpty()) {
            InfoFullscreen info = new InfoFullscreen(InfoFullscreen.INFO_EMPTY_UNIT_CONVERSIONS);
            viewModel.getInfoFullscreenLive().setValue(info);
        } else {
            viewModel.getInfoFullscreenLive().setValue(null);
        }
        if (binding.recycler.getAdapter() instanceof QuantityUnitConversionAdapter) {
            ((QuantityUnitConversionAdapter) binding.recycler.getAdapter()).updateData(conversions);
        } else {
            binding.recycler.setAdapter(new QuantityUnitConversionAdapter(requireContext(), conversions, this, viewModel.getQuantityUnitHashMap()));
        }
    });
    if (savedInstanceState == null) {
        viewModel.loadFromDatabase(true);
    }
    updateUI(savedInstanceState == null);
}
Also used : MasterProductCatConversionsViewModel(xyz.zedler.patrick.grocy.viewmodel.MasterProductCatConversionsViewModel) BottomSheetEvent(xyz.zedler.patrick.grocy.model.BottomSheetEvent) MasterPlaceholderAdapter(xyz.zedler.patrick.grocy.adapter.MasterPlaceholderAdapter) QuantityUnitConversionAdapter(xyz.zedler.patrick.grocy.adapter.QuantityUnitConversionAdapter) InfoFullscreenHelper(xyz.zedler.patrick.grocy.helper.InfoFullscreenHelper) LinearLayoutManager(androidx.recyclerview.widget.LinearLayoutManager) DefaultItemAnimator(androidx.recyclerview.widget.DefaultItemAnimator) SnackbarMessage(xyz.zedler.patrick.grocy.model.SnackbarMessage) InfoFullscreen(xyz.zedler.patrick.grocy.model.InfoFullscreen) ClickUtil(xyz.zedler.patrick.grocy.util.ClickUtil) ViewModelProvider(androidx.lifecycle.ViewModelProvider) Snackbar(com.google.android.material.snackbar.Snackbar)

Example 5 with InfoFullscreenHelper

use of xyz.zedler.patrick.grocy.helper.InfoFullscreenHelper in project grocy-android by patzly.

the class MasterObjectListFragment method onViewCreated.

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    activity = (MainActivity) requireActivity();
    clickUtil = new ClickUtil();
    viewModel = new ViewModelProvider(this, new MasterObjectListViewModel.MasterObjectListViewModelFactory(activity.getApplication(), entity)).get(MasterObjectListViewModel.class);
    viewModel.setOfflineLive(!activity.isOnline());
    viewModel.getIsLoadingLive().observe(getViewLifecycleOwner(), state -> {
        binding.swipe.setRefreshing(state);
        if (!state) {
            viewModel.setCurrentQueueLoading(null);
        }
    });
    binding.swipe.setOnRefreshListener(() -> viewModel.downloadDataForceUpdate());
    // for offline info in app bar
    binding.swipe.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
    binding.swipe.setProgressBackgroundColorSchemeColor(ContextCompat.getColor(activity, R.color.surface));
    binding.swipe.setColorSchemeColors(ContextCompat.getColor(activity, R.color.secondary));
    viewModel.getDisplayedItemsLive().observe(getViewLifecycleOwner(), objects -> {
        if (objects == null) {
            return;
        }
        if (objects.isEmpty()) {
            InfoFullscreen info;
            if (viewModel.isSearchActive()) {
                info = new InfoFullscreen(InfoFullscreen.INFO_NO_SEARCH_RESULTS);
            } else {
                int fullscreenType;
                switch(entity) {
                    case GrocyApi.ENTITY.PRODUCTS:
                        fullscreenType = InfoFullscreen.INFO_EMPTY_PRODUCTS;
                        break;
                    case GrocyApi.ENTITY.QUANTITY_UNITS:
                        fullscreenType = InfoFullscreen.INFO_EMPTY_QUS;
                        break;
                    case GrocyApi.ENTITY.LOCATIONS:
                        fullscreenType = InfoFullscreen.INFO_EMPTY_LOCATIONS;
                        break;
                    case GrocyApi.ENTITY.PRODUCT_GROUPS:
                        fullscreenType = InfoFullscreen.INFO_EMPTY_PRODUCT_GROUPS;
                        break;
                    case ENTITY.TASK_CATEGORIES:
                        fullscreenType = InfoFullscreen.INFO_EMPTY_TASK_CATEGORIES;
                        break;
                    default:
                        // STORES
                        fullscreenType = InfoFullscreen.INFO_EMPTY_STORES;
                }
                info = new InfoFullscreen(fullscreenType);
            }
            viewModel.getInfoFullscreenLive().setValue(info);
        } else {
            viewModel.getInfoFullscreenLive().setValue(null);
        }
        if (binding.recycler.getAdapter() instanceof MasterObjectListAdapter) {
            ((MasterObjectListAdapter) binding.recycler.getAdapter()).updateData(objects);
        } else {
            binding.recycler.setAdapter(new MasterObjectListAdapter(getContext(), entity, objects, this, viewModel.getHorizontalFilterBarMulti()));
            binding.recycler.scheduleLayoutAnimation();
        }
    });
    viewModel.getEventHandler().observeEvent(getViewLifecycleOwner(), event -> {
        if (event.getType() == Event.SNACKBAR_MESSAGE) {
            SnackbarMessage msg = (SnackbarMessage) event;
            Snackbar snackbar = msg.getSnackbar(activity, activity.binding.frameMainContainer);
            activity.showSnackbar(snackbar);
        } else if (event.getType() == Event.BOTTOM_SHEET) {
            BottomSheetEvent bottomSheetEvent = (BottomSheetEvent) event;
            activity.showBottomSheet(bottomSheetEvent.getBottomSheet(), event.getBundle());
        }
    });
    viewModel.getOfflineLive().observe(getViewLifecycleOwner(), this::appBarOfflineInfo);
    if (savedInstanceState == null) {
        // delete search if navigating back from other fragment
        viewModel.deleteSearch();
    }
    // INITIALIZE VIEWS
    binding.back.setOnClickListener(v -> activity.onBackPressed());
    binding.searchClose.setOnClickListener(v -> dismissSearch());
    binding.editTextSearch.addTextChangedListener(new TextWatcher() {

        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        }

        public void onTextChanged(CharSequence s, int start, int before, int count) {
        }

        public void afterTextChanged(Editable s) {
            if (appBarBehavior.isPrimaryLayout()) {
                return;
            }
            viewModel.setSearch(s != null ? s.toString() : "");
        }
    });
    binding.editTextSearch.setOnEditorActionListener((TextView v, int actionId, KeyEvent event) -> {
        if (actionId == EditorInfo.IME_ACTION_SEARCH) {
            activity.hideKeyboard();
            return true;
        }
        return false;
    });
    infoFullscreenHelper = new InfoFullscreenHelper(binding.frameContainer);
    viewModel.getInfoFullscreenLive().observe(getViewLifecycleOwner(), infoFullscreen -> infoFullscreenHelper.setInfo(infoFullscreen));
    // APP BAR BEHAVIOR
    appBarBehavior = new AppBarBehavior(activity, binding.appBarDefault, binding.appBarSearch, savedInstanceState);
    if (viewModel.isSearchActive()) {
        appBarBehavior.switchToSecondary();
    }
    binding.recycler.setLayoutManager(new LinearLayoutManager(activity, LinearLayoutManager.VERTICAL, false));
    binding.recycler.setItemAnimator(new DefaultItemAnimator());
    binding.recycler.setAdapter(new MasterPlaceholderAdapter());
    if (savedInstanceState == null) {
        viewModel.loadFromDatabase(true);
    }
    // UPDATE UI
    updateUI(true);
}
Also used : MasterObjectListViewModel(xyz.zedler.patrick.grocy.viewmodel.MasterObjectListViewModel) AppBarBehavior(xyz.zedler.patrick.grocy.behavior.AppBarBehavior) BottomSheetEvent(xyz.zedler.patrick.grocy.model.BottomSheetEvent) MasterPlaceholderAdapter(xyz.zedler.patrick.grocy.adapter.MasterPlaceholderAdapter) InfoFullscreenHelper(xyz.zedler.patrick.grocy.helper.InfoFullscreenHelper) LinearLayoutManager(androidx.recyclerview.widget.LinearLayoutManager) DefaultItemAnimator(androidx.recyclerview.widget.DefaultItemAnimator) KeyEvent(android.view.KeyEvent) SnackbarMessage(xyz.zedler.patrick.grocy.model.SnackbarMessage) InfoFullscreen(xyz.zedler.patrick.grocy.model.InfoFullscreen) ClickUtil(xyz.zedler.patrick.grocy.util.ClickUtil) MasterObjectListAdapter(xyz.zedler.patrick.grocy.adapter.MasterObjectListAdapter) TextWatcher(android.text.TextWatcher) Editable(android.text.Editable) TextView(android.widget.TextView) ViewModelProvider(androidx.lifecycle.ViewModelProvider) Snackbar(com.google.android.material.snackbar.Snackbar)

Aggregations

ViewModelProvider (androidx.lifecycle.ViewModelProvider)23 InfoFullscreenHelper (xyz.zedler.patrick.grocy.helper.InfoFullscreenHelper)23 BottomSheetEvent (xyz.zedler.patrick.grocy.model.BottomSheetEvent)16 SnackbarMessage (xyz.zedler.patrick.grocy.model.SnackbarMessage)15 Snackbar (com.google.android.material.snackbar.Snackbar)14 InfoFullscreen (xyz.zedler.patrick.grocy.model.InfoFullscreen)9 ClickUtil (xyz.zedler.patrick.grocy.util.ClickUtil)8 LinearLayoutManager (androidx.recyclerview.widget.LinearLayoutManager)7 MenuItem (android.view.MenuItem)4 MasterPlaceholderAdapter (xyz.zedler.patrick.grocy.adapter.MasterPlaceholderAdapter)4 AppBarBehavior (xyz.zedler.patrick.grocy.behavior.AppBarBehavior)4 Bundle (android.os.Bundle)3 Handler (android.os.Handler)3 LayoutInflater (android.view.LayoutInflater)3 View (android.view.View)3 ViewGroup (android.view.ViewGroup)3 NonNull (androidx.annotation.NonNull)3 Nullable (androidx.annotation.Nullable)3 DefaultItemAnimator (androidx.recyclerview.widget.DefaultItemAnimator)3 RecyclerView (androidx.recyclerview.widget.RecyclerView)3