package com.example.myapplication.api;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.os.Build;
import android.util.Log;

import com.example.myapplication.CurrencyRate;
import com.example.myapplication.CurrencyRepository;

import org.json.JSONObject;
import org.json.JSONException;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.*;

public class LoadCurrencyRatesTask extends AsyncTask<Void, Void, Boolean> {
    private static final String TAG = "LoadCurrencyRatesTask";
    private static final String API_KEY = "05ZPKFI7IKOPLLRM"; // Замените на ваш API ключ Alpha Vantage

    private final Context context;
    private final CurrencyRepository repository;
    private final Callback callback;
    private final List<CurrencyRate> newRates = new ArrayList<>();

    public interface Callback {
        void onRatesLoaded();

        void onError(Exception e);
    }

    public LoadCurrencyRatesTask(Context context, CurrencyRepository repository, Callback callback) {
        this.context = context;
        this.repository = repository;
        this.callback = callback;
    }

    @Override
    protected Boolean doInBackground(Void... voids) {
        try {
            if (isInternetAvailable()) {
                Log.d(TAG, "Интернет-соединение доступно. Обновление курсов через API.");

                List<String> currencies = Arrays.asList("EUR");
                boolean allSuccess = true;

                for (String targetCurrency : currencies) {
                    boolean success = fetchHistoricalRates("USD", targetCurrency);
                    if (!success) {
                        allSuccess = false;
                        Log.e(TAG, "Ошибка при загрузке курса для " + targetCurrency);
                        break; // Если требуется остановиться при первой ошибке
                    }
                }

                if (allSuccess) {
                    // Все запросы успешны, обновляем базу данных
                    repository.clearAllRates();
                    repository.insertCurrencyRatesSync(newRates);
                    // Вставка обратных курсов, если необходимо
                    for (CurrencyRate rate : newRates) {
                        if (!rate.baseCurrency.equals(rate.targetCurrency)) {
                            double reverseRate = rate.rate != 0 ? 1 / rate.rate : 0;
                            CurrencyRate reverseCurrencyRate = new CurrencyRate(rate.targetCurrency, rate.baseCurrency, reverseRate, rate.date);
                            repository.insertCurrencyRateSync(reverseCurrencyRate);
                        }
                    }
                    return true;
                } else {
                    // Если хотя бы один запрос не удался, не обновляем базу данных
                    return false;
                }
            } else {
                Log.d(TAG, "Нет интернет-соединения. Используются кэшированные данные из базы данных.");
                return true; // Используем кэшированные данные
            }
//            return false;
        } catch (Exception e) {
            Log.e(TAG, "Ошибка при загрузке курсов валют", e);
            return false;
        }
    }

    @Override
    protected void onPostExecute(Boolean success) {
        if (success) {
            if (callback != null) {
                callback.onRatesLoaded();
            }
        } else {
            if (callback != null) {
                callback.onError(new Exception("Не удалось обновить курсы валют. Используются кэшированные данные."));
            }
        }
    }

    private boolean isInternetAvailable() {
        ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        if (connectivityManager != null) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                Network network = connectivityManager.getActiveNetwork();
                if (network == null) {
                    Log.d(TAG, "Нет активной сети.");
                    return false;
                }
                NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network);
                boolean hasInternet = capabilities != null &&
                        (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ||
                                capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ||
                                capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET));
                Log.d(TAG, "Интернет доступен: " + hasInternet);
                return hasInternet;
            } else {
                // Для старых версий Android
                NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
                boolean isConnected = networkInfo != null && networkInfo.isConnected();
                Log.d(TAG, "Интернет доступен (старый метод): " + isConnected);
                return isConnected;
            }
        }
        Log.d(TAG, "ConnectivityManager равен null.");
        return false;
    }

    /**
     * Возвращает true, если курс валют успешно загружен и добавлен в список newRates.
     */
    private boolean fetchHistoricalRates(String baseCurrency, String targetCurrency) {
        try {
            String apiUrl = String.format(
                    Locale.US,
                    "https://www.alphavantage.co/query?function=FX_DAILY&from_symbol=%s&to_symbol=%s&apikey=%s&outputsize=compact",
                    baseCurrency,
                    targetCurrency,
                    API_KEY
            );

            URL url = new URL(apiUrl);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");

            int responseCode = connection.getResponseCode();

            InputStream inputStream;
            if (responseCode == HttpURLConnection.HTTP_OK) {
                inputStream = connection.getInputStream();
            } else {
                inputStream = connection.getErrorStream();
                Log.e(TAG, "HTTP ошибка при запросе к API: " + responseCode);
                return false;
            }

            BufferedReader reader = new BufferedReader(
                    new InputStreamReader(inputStream)
            );
            StringBuilder response = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                response.append(line);
            }
            reader.close();

            String jsonResponseString = response.toString();

            Log.d(TAG, "API Response для " + baseCurrency + "/" + targetCurrency + ": " + jsonResponseString);

            JSONObject jsonResponse = new JSONObject(jsonResponseString);

            if (jsonResponse.has("Error Message")) {
                Log.e(TAG, "Ошибка при получении данных для " + targetCurrency + ": " + jsonResponse.getString("Error Message"));
                return false;
            }

            if (jsonResponse.has("Note")) {
                Log.e(TAG, "Достигнут лимит частоты API вызовов: " + jsonResponse.getString("Note"));
                return false;
            }

            if (!jsonResponse.has("Time Series FX (Daily)")) {
                Log.e(TAG, "Неожиданный формат JSON для валюты: " + targetCurrency);
                return false;
            }

            JSONObject timeSeries = jsonResponse.getJSONObject("Time Series FX (Daily)");

            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.US);

            Iterator<String> dates = timeSeries.keys();
            while (dates.hasNext()) {
                String dateStr = dates.next();
                JSONObject dailyData = timeSeries.getJSONObject(dateStr);

                double rate = dailyData.getDouble("4. close");

                Date date = dateFormat.parse(dateStr);

                CurrencyRate currencyRate = new CurrencyRate(baseCurrency, targetCurrency, rate, date);
                newRates.add(currencyRate);
            }

            Log.d(TAG, "Исторические курсы получены для " + baseCurrency + "/" + targetCurrency);
            return true;

        } catch (JSONException e) {
            Log.e(TAG, "Ошибка парсинга JSON для " + targetCurrency, e);
            return false;
        } catch (Exception e) {
            Log.e(TAG, "Ошибка при получении исторических курсов для " + targetCurrency, e);
            return false;
        }
    }
}
package com.example.myapplication;

import android.content.Context;

import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import androidx.room.TypeConverters;

@Database(entities = {CurrencyRate.class}, version = 4, exportSchema = false)
@TypeConverters({Converters.class})
public abstract class AppDatabase extends RoomDatabase {
    private static volatile AppDatabase INSTANCE;

    public abstract CurrencyRateDao currencyRateDao();

    public static AppDatabase getDatabase(final Context context) {
        if (INSTANCE == null) {
            synchronized (AppDatabase.class) {
                if (INSTANCE == null) {
                    INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
                                    AppDatabase.class, "currency_database")
                            .fallbackToDestructiveMigration()
                            .build();
                }
            }
        }
        return INSTANCE;
    }
}package com.example.myapplication;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.util.Log;

public class BootReceiver extends BroadcastReceiver {

    private static final String TAG = "BootReceiver";

    @Override
    public void onReceive(Context context, Intent intent) {
        if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
            Log.d(TAG, "BOOT_COMPLETED получен");

            SharedPreferences prefs = context.getSharedPreferences("preferences", Context.MODE_PRIVATE);
            int hour = prefs.getInt("notification_hour", -1);
            int minute = prefs.getInt("notification_minute", -1);

            if (hour != -1 && minute != -1) {
                CurrencyAlarmHelper.scheduleDailyNotification(context, hour, minute);
                Log.d(TAG, "Повторное установление уведомления после перезагрузки");
            }
        }
    }
}package com.example.myapplication;

import android.content.Intent; // Добавлено для обработки нажатия на кнопку
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout; // Добавлено для работы с time_range_layout
import android.widget.Spinner;
import android.widget.TextView; // Добавлено для работы с time_range_label
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet;
import com.github.mikephil.charting.formatter.ValueFormatter;
import com.github.mikephil.charting.utils.ColorTemplate;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.button.MaterialButtonToggleGroup;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;

public class ChartActivity extends AppCompatActivity {
    private static final String TAG = "ChartActivity";
    private CurrencyRepository repository;
    private LineChart lineChart;

    // Выбранные валюты
    private Spinner spinnerFromChart;
    private Spinner spinnerToChart;
    private MaterialButton buttonSwapChart;
    private MaterialButtonToggleGroup timeRangeToggleGroup;
    private MaterialButton buttonBackToMain;
    private MaterialButton buttonCurrencyList; // Новая кнопка

    private boolean isRatesLoaded = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_chart);

        repository = new CurrencyRepository(this);
        lineChart = findViewById(R.id.line_chart);
        timeRangeToggleGroup = findViewById(R.id.time_range_toggle_group_chart);
        buttonBackToMain = findViewById(R.id.button_back_to_main);
        spinnerFromChart = findViewById(R.id.spinner_from_chart);
        spinnerToChart = findViewById(R.id.spinner_to_chart);
        buttonSwapChart = findViewById(R.id.button_swap_chart);

        // Настройка обработчика кнопок выбора периода
        timeRangeToggleGroup.addOnButtonCheckedListener((group, checkedId, isChecked) -> {
            if (isChecked) {
                Log.d(TAG, "Выбран период: " + checkedId);
                loadChartData(); // Перезагрузка графика при изменении периода
            }
        });

        // Настройка обработчика кнопки "Назад"
        buttonBackToMain.setOnClickListener(v -> finish());

        // Настройка обработчика кнопки свапа
        buttonSwapChart.setOnClickListener(v -> {
            swapCurrencies();
            loadChartData();
        });

        // Настройка слушателя для выбора валют из спиннеров
        AdapterView.OnItemSelectedListener spinnerListener = new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                if (isRatesLoaded) {
                    loadChartData();
                }
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {}
        };

        spinnerFromChart.setOnItemSelectedListener(spinnerListener);
        spinnerToChart.setOnItemSelectedListener(spinnerListener);

        // Загрузка валют в спиннеры
        new LoadCurrenciesTask().execute();
    }

    private void swapCurrencies() {
        int fromPosition = spinnerFromChart.getSelectedItemPosition();
        int toPosition = spinnerToChart.getSelectedItemPosition();

        spinnerFromChart.setSelection(toPosition);
        spinnerToChart.setSelection(fromPosition);
    }

    /**
     * AsyncTask для загрузки списка валют после обновления курсов.
     */
    private class LoadCurrenciesTask extends AsyncTask<Void, Void, List<String>> {
        @Override
        protected void onPreExecute() {
            // Можно добавить индикатор загрузки, если необходимо
        }

        @Override
        protected List<String> doInBackground(Void... voids) {
            // Загрузка списка валют из репозитория
            List<String> currencies = repository.getAllUniqueCurrencies();
            Log.d("LoadCurrenciesTask", "Получены валюты: " + currencies.toString());
            return currencies;
        }

        @Override
        protected void onPostExecute(List<String> currencies) {
            super.onPostExecute(currencies);
            if (currencies != null && !currencies.isEmpty()) {
                isRatesLoaded = true;
                ArrayAdapter<String> adapterFrom = new ArrayAdapter<>(ChartActivity.this, android.R.layout.simple_spinner_item, currencies);
                adapterFrom.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
                spinnerFromChart.setAdapter(adapterFrom);

                ArrayAdapter<String> adapterTo = new ArrayAdapter<>(ChartActivity.this, android.R.layout.simple_spinner_item, currencies);
                adapterTo.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
                spinnerToChart.setAdapter(adapterTo);

                // Устанавливаем стандартные значения
                String defaultFromCurrency = getIntent().getStringExtra("FROM_CURRENCY");
                String defaultToCurrency = getIntent().getStringExtra("TO_CURRENCY");

                if (defaultFromCurrency != null) {
                    int fromIndex = currencies.indexOf(defaultFromCurrency);
                    if (fromIndex >= 0) spinnerFromChart.setSelection(fromIndex);
                }

                if (defaultToCurrency != null) {
                    int toIndex = currencies.indexOf(defaultToCurrency);
                    if (toIndex >= 0) spinnerToChart.setSelection(toIndex);
                }

                // Устанавливаем кнопку по умолчанию в ToggleGroup
                timeRangeToggleGroup.check(R.id.button_7_days_chart);

                loadChartData();

            } else {
                Toast.makeText(ChartActivity.this, "Не удалось загрузить валюты.", Toast.LENGTH_SHORT).show();
                Log.e("LoadCurrenciesTask", "Список валют пуст");
            }
        }
    }

    /**
     * Загружает данные для графика на основе выбранных валют и периода.
     */
    private void loadChartData() {
        int checkedButtonId = timeRangeToggleGroup.getCheckedButtonId();
        int timeRangeDays;

        if (checkedButtonId == R.id.button_30_days_chart) {
            timeRangeDays = 30;
        } else if (checkedButtonId == R.id.button_90_days_chart) {
            timeRangeDays = 90;
        } else {
            timeRangeDays = 7;
        }

        String fromCurrency = spinnerFromChart.getSelectedItem().toString().toUpperCase();
        String toCurrency = spinnerToChart.getSelectedItem().toString().toUpperCase();

        new LoadChartDataTask(fromCurrency, toCurrency, timeRangeDays).execute();
    }

    private class LoadChartDataTask extends AsyncTask<Void, Void, LineData> {
        private final String fromCurrency;
        private final String toCurrency;
        private final int timeRangeDays;
        private List<Date> dates;

        public LoadChartDataTask(String fromCurrency, String toCurrency, int timeRangeDays) {
            this.fromCurrency = fromCurrency;
            this.toCurrency = toCurrency;
            this.timeRangeDays = timeRangeDays;
        }

        @Override
        protected LineData doInBackground(Void... voids) {
            Date endDate = new Date();  // Текущая дата
            Calendar cal = Calendar.getInstance();
            cal.setTime(endDate);
            cal.add(Calendar.DAY_OF_YEAR, -timeRangeDays);
            Date startDate = cal.getTime();

            List<CurrencyRate> historyFrom = repository.getCurrencyRateHistoryWithinDates("USD", fromCurrency, startDate, endDate);
            List<CurrencyRate> historyTo = repository.getCurrencyRateHistoryWithinDates("USD", toCurrency, startDate, endDate);

            if (fromCurrency.equals("USD")) {
                historyFrom = generateUsdHistory(startDate, endDate);
            }

            if (toCurrency.equals("USD")) {
                historyTo = generateUsdHistory(startDate, endDate);
            }

            if (historyFrom == null || historyTo == null || historyFrom.isEmpty() || historyTo.isEmpty()) {
                return null;
            }

            int size = Math.min(historyFrom.size(), historyTo.size());
            List<Entry> entries = new ArrayList<>();
            dates = new ArrayList<>();

            for (int i = 0; i < size; i++) {
                CurrencyRate rateFrom = historyFrom.get(i);
                CurrencyRate rateTo = historyTo.get(i);

                double rateFromUSD = rateFrom.rate;
                double rateToUSD = rateTo.rate;

                double crossRate = rateToUSD / rateFromUSD;

                entries.add(new Entry(i, (float) crossRate));
                dates.add(rateFrom.date);
            }

            LineDataSet dataSet = new LineDataSet(entries, "Курс " + fromCurrency + "/" + toCurrency);
            dataSet.setColor(ColorTemplate.getHoloBlue());
            dataSet.setLineWidth(2f);
            dataSet.setDrawCircles(false);
            dataSet.setMode(LineDataSet.Mode.CUBIC_BEZIER);
            dataSet.setDrawFilled(true);
            dataSet.setFillAlpha(50);
            dataSet.setFillColor(ColorTemplate.getHoloBlue());
            dataSet.setDrawValues(false);

            return new LineData(dataSet);
        }

        /**
         * Генерирует список курсов USD/USD (стабильный курс 1.0).
         */
        private List<CurrencyRate> generateUsdHistory(Date startDate, Date endDate) {
            List<Date> allDates = repository.getAllDatesWithinRange(startDate, endDate);
            List<CurrencyRate> usdHistory = new ArrayList<>();
            for (Date date : allDates) {
                usdHistory.add(new CurrencyRate("USD", "USD", 1.0, date));
            }
            return usdHistory;
        }

        @Override
        protected void onPostExecute(LineData lineData) {
            if (lineData != null && lineData.getEntryCount() > 0) {
                XAxis xAxis = lineChart.getXAxis();
                xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
                xAxis.setLabelRotationAngle(-45f);
                xAxis.setGranularity(1f);
                xAxis.setDrawGridLines(false);
                xAxis.setValueFormatter(new ValueFormatter() {
                    private final SimpleDateFormat mFormat = new SimpleDateFormat("dd MMM", Locale.getDefault());

                    @Override
                    public String getFormattedValue(float value) {
                        int index = (int) value;
                        if (index >= 0 && index < dates.size()) {
                            Date date = dates.get(index);
                            return mFormat.format(date);
                        } else {
                            return "";
                        }
                    }
                });

                // Настраиваем ось Y
                lineChart.getAxisRight().setEnabled(false);

                // Обновляем данные графика
                lineChart.setData(lineData);

                // Разрешаем взаимодействие
                lineChart.setTouchEnabled(true);
                lineChart.setDragEnabled(true);
                lineChart.setScaleEnabled(true);

                // Устанавливаем кастомный MarkerView (при наличии)
                // Например:
                // CustomMarkerView markerView = new CustomMarkerView(ChartActivity.this, R.layout.custom_marker_view, dates);
                // markerView.setChartView(lineChart);
                // lineChart.setMarker(markerView);

                lineChart.invalidate();
            } else {
                lineChart.clear();
                lineChart.invalidate();
                Toast.makeText(ChartActivity.this, "Нет данных для отображения графика.", Toast.LENGTH_SHORT).show();
            }
        }
    }
}package com.example.myapplication;

import android.os.AsyncTask;
import android.util.Log;







public class ConvertCurrencyTask extends AsyncTask<Void, Void, Double> {
    private final String fromCurrency;
    private final String toCurrency;
    private final double amount;
    private final CurrencyRepository repository;
    private final Callback callback;

    public interface Callback {
        void onConversionResult(Double result);
    }

    public ConvertCurrencyTask(String fromCurrency, String toCurrency, double amount, CurrencyRepository repository, Callback callback) {
        this.fromCurrency = fromCurrency;
        this.toCurrency = toCurrency;
        this.amount = amount;
        this.repository = repository;
        this.callback = callback;
    }

    @Override
    protected Double doInBackground(Void... voids) {
        // Get exchange rates relative to USD
        Double rateFromUSD;
        if (fromCurrency.equals("USD")) {
            rateFromUSD = 1.0;
        } else {
            rateFromUSD = repository.getExchangeRate("USD", fromCurrency);
            if (rateFromUSD != null) {
                rateFromUSD = 1 / rateFromUSD; // Convert to rate fromCurrency -> USD
            }
        }

        Double rateToUSD;
        if (toCurrency.equals("USD")) {
            rateToUSD = 1.0;
        } else {
            rateToUSD = repository.getExchangeRate("USD", toCurrency);
            if (rateToUSD != null) {
                rateToUSD = 1 / rateToUSD; // Convert to rate toCurrency -> USD
            }
        }

        if (rateFromUSD == null || rateToUSD == null) {
            Log.e("ConvertCurrencyTask", "Conversion rate not found for " + fromCurrency + " or " + toCurrency);
            return null;
        }

        // Correct cross-rate calculation
        double crossRate = rateFromUSD / rateToUSD;

        // Perform conversion
        return amount * crossRate;
    }

    @Override
    protected void onPostExecute(Double result) {
        callback.onConversionResult(result);
    }


}package com.example.myapplication;

import androidx.room.TypeConverter;

import java.util.Date;

public class Converters {
    @TypeConverter
    public static Date fromTimestamp(Long value) {
        return value == null ? null : new Date(value);
    }

    @TypeConverter
    public static Long dateToTimestamp(Date date) {
        return date == null ? null : date.getTime();
    }
}package com.example.myapplication;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.TextView;

import androidx.recyclerview.widget.RecyclerView;

import java.util.List;

/**
 * Adapter class for managing and binding currency pairs to the RecyclerView.
 */
public class CurrencyAdapter extends RecyclerView.Adapter<CurrencyAdapter.ViewHolder> {

    /**
     * Interface to handle favorite (remove) button clicks.
     */
    public interface OnFavoriteClickListener {
        /**
         * Called when the favorite button is clicked.
         *
         * @param currencyPair The currency pair that was clicked.
         * @param isFavorite   Indicates whether the pair is being added or removed from favorites.
         */
        void onFavoriteClick(String currencyPair, boolean isFavorite);
    }

    private List<String> currencyPairs; // List of currency pairs to display
    private OnFavoriteClickListener listener; // Listener for favorite button clicks

    /**
     * Constructor for the adapter.
     *
     * @param currencyPairs List of currency pairs to display.
     * @param listener      Listener to handle favorite button clicks.
     */
    public CurrencyAdapter(List<String> currencyPairs, OnFavoriteClickListener listener) {
        this.currencyPairs = currencyPairs;
        this.listener = listener;
    }

    /**
     * ViewHolder class that holds references to the views in each RecyclerView item.
     */
    public static class ViewHolder extends RecyclerView.ViewHolder {
        public TextView textViewPair; // TextView to display the currency pair
        public ImageButton buttonFavorite; // Button to remove the pair from favorites

        public ViewHolder(View view) {
            super(view);
            textViewPair = view.findViewById(R.id.text_view_pair);
            buttonFavorite = view.findViewById(R.id.button_favorite);
        }
    }

    @Override
    public CurrencyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        // Inflate the item layout for each currency pair
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_currency_pair, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(CurrencyAdapter.ViewHolder holder, int position) {
        String pair = currencyPairs.get(position); // Get the currency pair at this position

        holder.textViewPair.setText(pair); // Set the currency pair text

        // Set the favorite button icon as filled heart since all displayed pairs are favorites
        holder.buttonFavorite.setImageResource(R.drawable.star);

        // Handle favorite button click to remove the pair from favorites
        holder.buttonFavorite.setOnClickListener(v -> {
            // Notify the listener (CurrencyListActivity) to handle removal
            listener.onFavoriteClick(pair, false);
        });
    }

    @Override
    public int getItemCount() {
        return currencyPairs.size(); // Return the total number of currency pairs
    }

    /**
     * Adds a new currency pair to the adapter and notifies the RecyclerView.
     *
     * @param pair The currency pair to add.
     */
    public void addCurrencyPair(String pair) {
        currencyPairs.add(pair); // Add the new pair to the list
        notifyItemInserted(currencyPairs.size() - 1); // Notify the adapter of the new item
    }

    /**
     * Removes a currency pair from the adapter at the specified position and notifies the RecyclerView.
     *
     * @param position The position of the currency pair to remove.
     */
    public void removeCurrencyPair(int position) {
        if (position >= 0 && position < currencyPairs.size()) {
            currencyPairs.remove(position); // Remove the pair from the list
            notifyItemRemoved(position); // Notify the adapter of the removed item
        }
    }
}package com.example.myapplication;

import android.app.AlarmManager;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.util.Log;

import java.util.Calendar;

/**
 * Помощник для настройки и отмены уведомлений о курсах валют.
 */
public class CurrencyAlarmHelper {

    private static final String TAG = "CurrencyAlarmHelper";
    private static final String CHANNEL_ID = "daily_rate_channel";
    private static final String CHANNEL_NAME = "Daily Rate Channel";
    private static final String CHANNEL_DESCRIPTION = "Channel for daily exchange rate notifications";

    /**
     * Устанавливает ежедневное уведомление в указанное время.
     *
     * @param context Контекст приложения.
     * @param hour    Часы (24-часовой формат) для уведомления.
     * @param minute  Минуты для уведомления.
     */
    public static void scheduleDailyNotification(Context context, int hour, int minute) {
        // Создание Notification Channel (если необходимо)
        createNotificationChannel(context);

        // Отмена существующих уведомлений перед установкой новых
        cancelAllNotifications(context);

        // Получение AlarmManager
        AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        if (alarmManager == null) {
            Log.e(TAG, "AlarmManager не доступен");
            return;
        }

        // Создание Intent для DailyNotificationReceiver
        Intent intent = new Intent(context, DailyNotificationReceiver.class);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(
                context,
                0,
                intent,
                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
        );

        // Настройка времени срабатывания уведомления
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(System.currentTimeMillis());
        calendar.set(Calendar.HOUR_OF_DAY, hour);
        calendar.set(Calendar.MINUTE, minute);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);

        // Если время уже прошло, установить на следующий день
        if (calendar.getTimeInMillis() <= System.currentTimeMillis()) {
            calendar.add(Calendar.DAY_OF_MONTH, 1);
        }

        // Установка точного будильника
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            // Для API 23 и выше используем setExactAndAllowWhileIdle
            alarmManager.setExactAndAllowWhileIdle(
                    AlarmManager.RTC_WAKEUP,
                    calendar.getTimeInMillis(),
                    pendingIntent
            );
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            // Для API от 19 до 22 используем setExact
            alarmManager.setExact(
                    AlarmManager.RTC_WAKEUP,
                    calendar.getTimeInMillis(),
                    pendingIntent
            );
        } else {
            // Для API ниже 19 используем set
            alarmManager.set(
                    AlarmManager.RTC_WAKEUP,
                    calendar.getTimeInMillis(),
                    pendingIntent
            );
        }

        Log.d(TAG, "Уведомление установлено на " + calendar.getTime().toString());
    }

    /**
     * Отменяет все уведомления и будильники.
     *
     * @param context Контекст приложения.
     */
    public static void cancelAllNotifications(Context context) {
        // Получение AlarmManager
        AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        if (alarmManager == null) {
            Log.e(TAG, "AlarmManager не доступен");
            return;
        }

        // Создание Intent для DailyNotificationReceiver
        Intent intent = new Intent(context, DailyNotificationReceiver.class);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(
                context,
                0,
                intent,
                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
        );

        // Отмена будильника
        alarmManager.cancel(pendingIntent);
        Log.d(TAG, "Все уведомления и будильники отменены");
    }

    /**
     * Создаёт Notification Channel для отправки уведомлений.
     *
     * @param context Контекст приложения.
     */
    private static void createNotificationChannel(Context context) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            // Проверка, существует ли уже канал
            NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
            if (notificationManager == null) {
                Log.e(TAG, "NotificationManager не доступен");
                return;
            }

            if (notificationManager.getNotificationChannel(CHANNEL_ID) == null) {
                NotificationChannel channel = new NotificationChannel(
                        CHANNEL_ID,
                        CHANNEL_NAME,
                        NotificationManager.IMPORTANCE_DEFAULT
                );
                channel.setDescription(CHANNEL_DESCRIPTION);
                notificationManager.createNotificationChannel(channel);
                Log.d(TAG, "Notification Channel создан");
            } else {
                Log.d(TAG, "Notification Channel уже существует");
            }
        }
    }
}package com.example.myapplication;

import android.Manifest;
import android.app.TimePickerDialog;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.view.LayoutInflater;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class CurrencyListActivity extends AppCompatActivity {

    // RecyclerView для отображения списка избранных пар валют
    private RecyclerView recyclerView;

    // Адаптер для управления данными и связывания их с RecyclerView
    private CurrencyAdapter currencyAdapter;

    // Репозиторий для получения данных о валютах
    private CurrencyRepository repository;

    // Набор для хранения избранных пар валют
    private Set<String> favoriteCurrencyPairs;

    // TextView для отображения, когда нет избранных пар
    private TextView textEmpty;

    // TextView для отображения выбранного времени уведомлений
    private TextView textSelectedTime;

    // Кнопка для настройки времени уведомлений
    private Button buttonSetTime;

    private static final int REQUEST_NOTIFICATION_PERMISSION = 1001;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_currency_list);

        // Инициализация репозитория
        repository = new CurrencyRepository(this);

        // Получение избранных пар валют из SharedPreferences
        favoriteCurrencyPairs = getFavoriteCurrencyPairs();

        // Инициализация RecyclerView и установка менеджера компоновки
        recyclerView = findViewById(R.id.currency_recycler_view);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));

        // Ссылка на TextView для пустого состояния
        textEmpty = findViewById(R.id.text_empty);

        // Ссылка на TextView для отображения выбранного времени
        textSelectedTime = findViewById(R.id.text_selected_time);

        // Инициализация адаптера с списком избранных пар и установка слушателя
        currencyAdapter = new CurrencyAdapter(new ArrayList<>(favoriteCurrencyPairs), new CurrencyAdapter.OnFavoriteClickListener() {
            @Override
            public void onFavoriteClick(String currencyPair, boolean isFavorite) {
                // Обработка нажатия на кнопку избранного

                if (isFavorite) {
                    // Добавление в избранное
                    favoriteCurrencyPairs.add(currencyPair);
                    Toast.makeText(CurrencyListActivity.this, "Добавлено в избранное: " + currencyPair, Toast.LENGTH_SHORT).show();
                } else {
                    // Удаление из избранного
                    favoriteCurrencyPairs.remove(currencyPair);
                    Toast.makeText(CurrencyListActivity.this, "Удалено из избранного: " + currencyPair, Toast.LENGTH_SHORT).show();
                }

                // Сохранение обновленного набора избранных пар
                saveFavoriteCurrencyPairs(favoriteCurrencyPairs);

                // Обновление интерфейса
                updateUI();
            }
        });

        // Установка адаптера в RecyclerView
        recyclerView.setAdapter(currencyAdapter);

        // Инициализация кнопки для настройки времени уведомлений
        buttonSetTime = findViewById(R.id.button_set_notification_time);

        // Получение сохраненного времени уведомлений
        SharedPreferences prefs = getSharedPreferences("preferences", MODE_PRIVATE);
        int savedHour = prefs.getInt("notification_hour", -1);
        int savedMinute = prefs.getInt("notification_minute", -1);

        if (savedHour != -1 && savedMinute != -1) {
            String time = String.format("%02d:%02d", savedHour, savedMinute);
            textSelectedTime.setText("Время уведомлений: " + time);
        } else {
            textSelectedTime.setText("Время уведомлений: не установлено");
        }

        // Проверка и запрос разрешения на отправку уведомлений для Android 13 и выше
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.POST_NOTIFICATIONS}, REQUEST_NOTIFICATION_PERMISSION);
            }
        }

        // Установка слушателя для кнопки настройки времени уведомлений
        buttonSetTime.setOnClickListener(v -> showTimePickerDialog());

        // Инициализация и установка слушателя для кнопки "Назад"
        Button backButton = findViewById(R.id.button_back);
        backButton.setOnClickListener(v -> finish());

        // Инициализация и установка слушателя для кнопки "Добавить пару"
        Button addPairButton = findViewById(R.id.button_add_pair);
        addPairButton.setOnClickListener(v -> new LoadCurrenciesTaskForDialog().execute());

        // Обновление интерфейса в зависимости от наличия избранных пар
        updateUI();
    }

    /**
     * Обработка результата запроса разрешений.
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if(requestCode == REQUEST_NOTIFICATION_PERMISSION) {
            if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
                Toast.makeText(this, "Разрешение на уведомления предоставлено", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(this, "Разрешение на уведомления отклонено. Уведомления не будут работать.", Toast.LENGTH_LONG).show();
                // Отключение уведомлений, если разрешение не предоставлено
                textSelectedTime.setText("Время уведомлений: не установлено");
                CurrencyAlarmHelper.cancelAllNotifications(this);
            }
        }
    }

    /**
     * Получает набор избранных пар валют из SharedPreferences.
     *
     * @return Новый HashSet с избранными пар.
     */
    private Set<String> getFavoriteCurrencyPairs() {
        SharedPreferences prefs = getSharedPreferences("favorites", MODE_PRIVATE);
        Set<String> defaultSet = new HashSet<>();
        Set<String> storedSet = prefs.getStringSet("favorite_pairs", defaultSet);
        // Возвращаем новый HashSet, чтобы избежать изменения оригинального набора из SharedPreferences
        return new HashSet<>(storedSet);
    }

    /**
     * Сохраняет обновленный набор избранных пар валют в SharedPreferences.
     *
     * @param favorites Набор избранных пар валют для сохранения.
     */
    private void saveFavoriteCurrencyPairs(Set<String> favorites) {
        SharedPreferences prefs = getSharedPreferences("favorites", MODE_PRIVATE);
        SharedPreferences.Editor editor = prefs.edit();
        editor.putStringSet("favorite_pairs", favorites);
        editor.apply();
    }

    /**
     * Обновляет интерфейс, переключая видимость RecyclerView и TextView для пустого состояния.
     */
    private void updateUI() {
        if (favoriteCurrencyPairs.isEmpty()) {
            // Если нет избранных пар, показать сообщение и скрыть RecyclerView
            recyclerView.setVisibility(View.GONE);
            textEmpty.setVisibility(View.VISIBLE);
        } else {
            // Если есть избранные пары, показать RecyclerView и скрыть сообщение
            recyclerView.setVisibility(View.VISIBLE);
            textEmpty.setVisibility(View.GONE);
        }
    }

    /**
     * Показывает диалог выбора времени уведомлений.
     */
    private void showTimePickerDialog() {
        // Получение сохраненного времени
        SharedPreferences prefs = getSharedPreferences("preferences", MODE_PRIVATE);
        int savedHour = prefs.getInt("notification_hour", 8); // По умолчанию 08:00
        int savedMinute = prefs.getInt("notification_minute", 0);

        // Создание и отображение TimePickerDialog
        TimePickerDialog timePickerDialog = new TimePickerDialog(this, (view, hourOfDay, minute) -> {
            // Сохранение выбранного времени
            saveNotificationTime(hourOfDay, minute);
            String time = String.format("%02d:%02d", hourOfDay, minute);
            textSelectedTime.setText("Время уведомлений: " + time);

            // Настройка уведомлений с новым временем
            CurrencyAlarmHelper.scheduleDailyNotification(this, hourOfDay, minute);
            Toast.makeText(this, "Уведомления будут отправляться в " + time, Toast.LENGTH_SHORT).show();
        }, savedHour, savedMinute, true); // true для 24-часового формата

        timePickerDialog.setTitle("Выберите время уведомлений");
        timePickerDialog.show();
    }

    /**
     * Сохраняет выбранное время уведомлений в SharedPreferences.
     *
     * @param hour   Выбранный час.
     * @param minute Выбранные минуты.
     */
    private void saveNotificationTime(int hour, int minute) {
        SharedPreferences prefs = getSharedPreferences("preferences", MODE_PRIVATE);
        SharedPreferences.Editor editor = prefs.edit();
        editor.putInt("notification_hour", hour);
        editor.putInt("notification_minute", minute);
        editor.apply();
    }

    /**
     * Отменяет все уведомления и будильники.
     */
    private void cancelAllNotifications() {
        CurrencyAlarmHelper.cancelAllNotifications(this);
    }

    /**
     * AsyncTask для загрузки списка валют в фоновом режиме для диалога добавления пары.
     */
    private class LoadCurrenciesTaskForDialog extends AsyncTask<Void, Void, List<String>> {
        @Override
        protected List<String> doInBackground(Void... voids) {
            // Получение всех уникальных валют из репозитория
            return repository.getAllUniqueCurrencies();
        }

        @Override
        protected void onPostExecute(List<String> currencies) {
            if (currencies != null && !currencies.isEmpty()) {
                // Если валюты доступны, показать диалог добавления пары
                showAddPairDialog(currencies);
            } else {
                // Если валюты недоступны, уведомить пользователя
                Toast.makeText(CurrencyListActivity.this, "Нет доступных валют для добавления.", Toast.LENGTH_SHORT).show();
            }
        }
    }

    /**
     * Показывает AlertDialog, позволяющее пользователю выбрать и добавить новую пару валют.
     *
     * @param currencies Список доступных кодов валют.
     */
    private void showAddPairDialog(List<String> currencies) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("Добавить валютную пару");

        // Раздувание пользовательского макета диалога
        LayoutInflater inflater = LayoutInflater.from(this);
        View dialogView = inflater.inflate(R.layout.dialog_add_pair, null);
        Spinner spinnerBase = dialogView.findViewById(R.id.spinner_base);
        Spinner spinnerTarget = dialogView.findViewById(R.id.spinner_target);

        // Настройка адаптеров для Spinner
        ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, currencies);
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        spinnerBase.setAdapter(adapter);
        spinnerTarget.setAdapter(adapter);

        // Установка пользовательского вида в диалог
        builder.setView(dialogView);

        // Определение действия для положительной кнопки (Добавить)
        builder.setPositiveButton("Добавить", (dialog, which) -> {
            String base = spinnerBase.getSelectedItem().toString();
            String target = spinnerTarget.getSelectedItem().toString();

            // Проверка, что базовая и целевая валюты не совпадают
            if (base.equals(target)) {
                Toast.makeText(this, "Базовая и целевая валюты не могут быть одинаковыми.", Toast.LENGTH_SHORT).show();
                return;
            }

            String pair = base + "/" + target;

            // Проверка, существует ли уже пара в избранном
            if (favoriteCurrencyPairs.contains(pair)) {
                Toast.makeText(this, "Эта пара уже добавлена в избранное.", Toast.LENGTH_SHORT).show();
                return;
            }

            // Добавление новой пары в избранное
            favoriteCurrencyPairs.add(pair);
            saveFavoriteCurrencyPairs(favoriteCurrencyPairs);

            // Обновление адаптера с новой парой
            currencyAdapter.addCurrencyPair(pair);

            // Уведомление пользователя
            Toast.makeText(this, "Добавлено в избранное: " + pair, Toast.LENGTH_SHORT).show();

            // Обновление интерфейса
            updateUI();
        });

        // Определение действия для отрицательной кнопки (Отмена)
        builder.setNegativeButton("Отмена", (dialog, which) -> dialog.dismiss());

        // Показ диалога
        builder.show();
    }
}// CurrencyPair.java
package com.example.myapplication;

import java.util.Objects;

public class CurrencyPair {
    private String base;
    private String target;

    public CurrencyPair(String base, String target) {
        this.base = base;
        this.target = target;
    }

    public String getBase() {
        return base;
    }

    public String getTarget() {
        return target;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        CurrencyPair that = (CurrencyPair) o;

        return Objects.equals(base, that.base) && Objects.equals(target, that.target);
    }

    @Override
    public int hashCode() {
        return Objects.hash(base, target);
    }

    @Override
    public String toString() {
        return base + "/" + target;
    }
}package com.example.myapplication;

import androidx.room.Entity;
import androidx.room.PrimaryKey;
import androidx.room.TypeConverters;

import java.util.Date;

@Entity(tableName = "currency_rate")
public class CurrencyRate {
    @PrimaryKey(autoGenerate = true)
    public int id;

    public String baseCurrency;
    public String targetCurrency;
    public double rate;

    @TypeConverters({Converters.class})
    public Date date;

    public CurrencyRate(String baseCurrency, String targetCurrency, double rate, Date date) {
        this.baseCurrency = baseCurrency;
        this.targetCurrency = targetCurrency;
        this.rate = rate;
        this.date = date;
    }

    @Override
    public String toString() {
        return baseCurrency + " -> " + targetCurrency + " = " + rate + " на " + date.toString();
    }
}package com.example.myapplication;

import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;

import java.util.Date;
import java.util.List;

@Dao
public interface CurrencyRateDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    void insertCurrencyRate(CurrencyRate rate);

    /**
     * Пакетная вставка курсов валют.
     */
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    void insertCurrencyRates(List<CurrencyRate> rates);

    @Query("DELETE FROM currency_rate")
    void clearAllRates();

    @Query("SELECT DISTINCT baseCurrency FROM currency_rate")
    List<String> getAllBaseCurrencies();

    @Query("SELECT DISTINCT targetCurrency FROM currency_rate")
    List<String> getAllTargetCurrencies();

    @Query("SELECT DISTINCT date FROM currency_rate WHERE date BETWEEN :startDate AND :endDate ORDER BY date DESC")
    List<Date> getAllDatesWithinRange(Date startDate, Date endDate);

    @Query("SELECT * FROM currency_rate WHERE baseCurrency = :baseCurrency AND targetCurrency = :targetCurrency AND date BETWEEN :startDate AND :endDate ORDER BY date DESC")
    List<CurrencyRate> getCurrencyRateHistoryWithinDates(String baseCurrency, String targetCurrency, Date startDate, Date endDate);

    @Query("SELECT rate FROM currency_rate WHERE baseCurrency = :fromCurrency AND targetCurrency = :toCurrency ORDER BY date DESC LIMIT 1")
    Double getLatestExchangeRate(String fromCurrency, String toCurrency);

    @Query("SELECT DISTINCT date FROM currency_rate ORDER BY date DESC")
    List<Date> getAllDates();
}package com.example.myapplication;

import android.content.Context;
import android.util.Log;

import com.example.myapplication.api.LoadCurrencyRatesTask;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class CurrencyRepository {
    private final CurrencyRateDao currencyRateDao;
    private final Context context;

    public CurrencyRepository(Context context) {
        this.context = context;
        AppDatabase db = AppDatabase.getDatabase(context);
        currencyRateDao = db.currencyRateDao();
    }

    public void insertCurrencyRateSync(CurrencyRate rate) {
        currencyRateDao.insertCurrencyRate(rate);
        Log.d("CurrencyRepository", "Вставлен курс: " + rate.toString());
    }

    /**
     * Пакетная вставка курсов валют.
     */
    public void insertCurrencyRatesSync(List<CurrencyRate> rates) {
        currencyRateDao.insertCurrencyRates(rates);
        Log.d("CurrencyRepository", "Вставлены курсы валют: " + rates.size());
    }

    public void clearAllRates() {
        currencyRateDao.clearAllRates();
        Log.d("CurrencyRepository", "Все курсы очищены из базы данных.");
    }

    public List<Date> getAllDates() {
        return currencyRateDao.getAllDates();
    }

    // Метод для получения дат в заданном диапазоне
    public List<Date> getAllDatesWithinRange(Date startDate, Date endDate) {
        List<Date> dates = currencyRateDao.getAllDatesWithinRange(startDate, endDate);
        Log.d("CurrencyRepository", "Получены даты в диапазоне: " + dates.toString());
        return dates;
    }

    // Метод для получения исторических данных курса за заданный период
    public List<CurrencyRate> getCurrencyRateHistoryWithinDates(String baseCurrency, String targetCurrency, Date startDate, Date endDate) {
        List<CurrencyRate> rates = currencyRateDao.getCurrencyRateHistoryWithinDates(baseCurrency, targetCurrency, startDate, endDate);
        Log.d("CurrencyRepository", "Получены курсы для " + baseCurrency + "/" + targetCurrency + " в диапазоне: " + rates.toString());
        return rates;
    }

    public List<String> getAllUniqueCurrencies() {
        List<String> baseCurrencies = currencyRateDao.getAllBaseCurrencies();
        List<String> targetCurrencies = currencyRateDao.getAllTargetCurrencies();
        Set<String> allCurrencies = new HashSet<>(baseCurrencies);
        allCurrencies.addAll(targetCurrencies);
        List<String> uniqueCurrencies = new ArrayList<>(allCurrencies);
        Log.d("CurrencyRepository", "Уникальные валюты: " + uniqueCurrencies.toString());
        return uniqueCurrencies;
    }

    public Double getExchangeRate(String fromCurrency, String toCurrency) {
        Double rate = currencyRateDao.getLatestExchangeRate(fromCurrency, toCurrency);
        Log.d("CurrencyRepository", "Последний курс с " + fromCurrency + " на " + toCurrency + ": " + rate);
        return rate;
    }

    public void loadAllCurrencyRatesFromApi(LoadCurrencyRatesTask.Callback callback) {
        new LoadCurrencyRatesTask(context, this, callback).execute();
    }

    public List<CurrencyPair> getAllUniqueCurrencyPairs() {
        List<String> currencies = getAllUniqueCurrencies();
        List<CurrencyPair> pairs = new ArrayList<>();
        for (int i = 0; i < currencies.size(); i++) {
            for (int j = i + 1; j < currencies.size(); j++) {
                pairs.add(new CurrencyPair(currencies.get(i), currencies.get(j)));
                pairs.add(new CurrencyPair(currencies.get(j), currencies.get(i)));
            }
        }
        return pairs;
    }
}package com.example.myapplication;

import android.content.Context;
import android.widget.TextView;

import com.github.mikephil.charting.components.MarkerView;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.highlight.Highlight;
import com.example.myapplication.R;
import com.github.mikephil.charting.utils.MPPointF;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;

public class CustomMarkerView extends MarkerView {

    private final TextView tvContent;
    private final List<Date> dates;
    private MPPointF mOffset;

    public CustomMarkerView(Context context, int layoutResource, List<Date> dates) {
        super(context, layoutResource);
        this.dates = dates;
        tvContent = findViewById(R.id.tvContent);
    }

    @Override
    public void refreshContent(Entry e, Highlight highlight) {
        int index = (int) e.getX();
        if (index >= 0 && index < dates.size()) {
            Date date = dates.get(index);
            SimpleDateFormat dateFormat = new SimpleDateFormat("dd MMM yyyy", Locale.getDefault());
            String dateStr = dateFormat.format(date);
            String rateStr = String.format(Locale.getDefault(), "%.4f", e.getY());
            tvContent.setText(dateStr + "\nКурс: " + rateStr);
        } else {
            tvContent.setText("");
        }
        super.refreshContent(e, highlight);
    }

    @Override
    public MPPointF getOffset() {
        if (mOffset == null) {
            mOffset = new MPPointF(-(getWidth() / 2f), -getHeight());
        }
        return mOffset;
    }
}package com.example.myapplication;

import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;

import androidx.core.app.NotificationCompat;

import java.util.Set;

/**
 * BroadcastReceiver, который обрабатывает ежедневные уведомления по избранным курсам валют.
 */
public class DailyNotificationReceiver extends BroadcastReceiver {

    private static final String CHANNEL_ID = "daily_rate_channel";
    private static final String NOTIFICATION_TITLE = "Курсы избранных валют";
    private static final int NOTIFICATION_ID = 1;

    @Override
    public void onReceive(Context context, Intent intent) {
        try {
            // Доступ к SharedPreferences для получения избранных пар валют и времени уведомлений
            SharedPreferences prefs = context.getSharedPreferences("preferences", Context.MODE_PRIVATE);
            Set<String> favoritePairs = prefs.getStringSet("favorite_pairs", null);
            int notificationHour = prefs.getInt("notification_hour", -1);
            int notificationMinute = prefs.getInt("notification_minute", -1);

            // Продолжаем только если есть избранные пары и время уведомлений установлено
            if (favoritePairs != null && !favoritePairs.isEmpty() && notificationHour != -1 && notificationMinute != -1) {
                StringBuilder contentBuilder = new StringBuilder();

                // Создаём экземпляр CurrencyRepository для получения курсов валют
                CurrencyRepository repository = new CurrencyRepository(context);

                for (String pair : favoritePairs) {
                    String[] currencies = pair.split("/");
                    if (currencies.length == 2) {
                        String fromCurrency = currencies[0];
                        String toCurrency = currencies[1];
                        Double rate = repository.getExchangeRate(fromCurrency, toCurrency);
                        if (rate != null) {
                            contentBuilder.append(String.format("%s/%s: %.4f\n", fromCurrency, toCurrency, rate));
                        } else {
                            contentBuilder.append(String.format("%s/%s: нет данных\n", fromCurrency, toCurrency));
                        }
                    } else {
                        contentBuilder.append(String.format("%s: неверный формат пары\n", pair));
                    }
                }

                String contentText = contentBuilder.toString();

                // Создание Notification Channel для Android O и выше
                createNotificationChannel(context);

                // Построение уведомления
                NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
                        .setSmallIcon(R.drawable.star) // Убедитесь, что иконка существует
                        .setContentTitle(NOTIFICATION_TITLE)
                        .setContentText("Курсы валют на сегодня")
                        .setStyle(new NotificationCompat.BigTextStyle().bigText(contentText))
                        .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                        .setAutoCancel(true);

                // Получение NotificationManager для отображения уведомления
                NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

                if (notificationManager != null) {
                    notificationManager.notify(NOTIFICATION_ID, builder.build());
                }

                // Перенастройка следующего уведомления
                CurrencyAlarmHelper.scheduleDailyNotification(context, notificationHour, notificationMinute);
            }
        } catch (Exception e) {
            e.printStackTrace();
            // Здесь можно добавить логирование или другие действия по обработке исключений
        }
    }

    /**
     * Создаёт Notification Channel для отправки уведомлений.
     *
     * @param context Контекст приложения.
     */
    private void createNotificationChannel(Context context) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            CharSequence name = "Daily Rate Channel";
            String description = "Channel for daily exchange rate notifications";
            int importance = NotificationManager.IMPORTANCE_DEFAULT;
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance);
            channel.setDescription(description);
            NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
            if (notificationManager != null) {
                notificationManager.createNotificationChannel(channel);
            }
        }
    }
}package com.example.myapplication;

import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log; // Добавлено для логирования
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.Spinner;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import com.example.myapplication.api.LoadCurrencyRatesTask;
import com.google.android.material.button.MaterialButton;

import java.util.List;

public class MainActivity extends AppCompatActivity {
    private Spinner spinnerFrom;
    private Spinner spinnerTo;
    private EditText inputFrom;
    private EditText inputTo;
    private ProgressBar loadingIndicator;
    private CurrencyRepository repository;
    private MaterialButton buttonSwap;
    private MaterialButton buttonShowChart; // Кнопка для перехода к графику
    private Button buttonCurrencyList;

    private boolean isRatesLoaded = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Инициализация компонентов
        repository = new CurrencyRepository(this);
        spinnerFrom = findViewById(R.id.spinner_from);
        spinnerTo = findViewById(R.id.spinner_to);
        inputFrom = findViewById(R.id.input_from);
        inputTo = findViewById(R.id.input_to);
        loadingIndicator = findViewById(R.id.loading_indicator);
        buttonSwap = findViewById(R.id.button_swap);
        buttonShowChart = findViewById(R.id.button_show_chart); // Кнопка для показа графика
        buttonCurrencyList = findViewById(R.id.button_currency_list); // Инициализация кнопки избранных валют

        // Настройка обработчика кнопки избранных валют
        buttonCurrencyList.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, CurrencyListActivity.class);
                startActivity(intent);
            }
        });

        // Настройка обработчика кнопки перехода к графику
        buttonShowChart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                if (isRatesLoaded) {
                    Intent intent = new Intent(MainActivity.this, ChartActivity.class);
                    String selectedFromCurrency = spinnerFrom.getSelectedItem().toString();
                    String selectedToCurrency = spinnerTo.getSelectedItem().toString();
                    intent.putExtra("FROM_CURRENCY", selectedFromCurrency);
                    intent.putExtra("TO_CURRENCY", selectedToCurrency);
                    startActivity(intent);
                } else {
                    showToast("Подождите, пока загрузятся курсы валют.");
                }
            }
        });

        // Настройка обработчика кнопки свапа
        buttonSwap.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                int fromPosition = spinnerFrom.getSelectedItemPosition();
                int toPosition = spinnerTo.getSelectedItemPosition();

                spinnerFrom.setSelection(toPosition);
                spinnerTo.setSelection(fromPosition);

                // Обмен значениями в полях ввода (опционально)
                String inputFromValue = inputFrom.getText().toString();
                String inputToValue = inputTo.getText().toString();
                inputFrom.setText(inputToValue);
                inputTo.setText(inputFromValue);

                if (isRatesLoaded) {
                    performConversion();
                } else {
                    showToast("Подождите, пока загрузятся курсы валют.");
                }
            }
        });

        // Показываем индикатор загрузки и загружаем курсы
        loadingIndicator.setVisibility(View.VISIBLE);
        repository.loadAllCurrencyRatesFromApi(new LoadCurrencyRatesTask.Callback() {
            @Override
            public void onRatesLoaded() {
                runOnUiThread(() -> {
                    new LoadCurrenciesTask().execute();
                });
            }

            @Override
            public void onError(Exception e) {
                runOnUiThread(() -> {
                    // Не очищаем базу данных, используем кэшированные данные
                    loadingIndicator.setVisibility(View.GONE);
                    showToast("Ошибка обновления курсов валют: " + e.getMessage());
                    Log.e("MainActivity", "Error updating currency rates", e);
                    // Теперь загружаем данные из кэша
                    new LoadCurrenciesTask().execute();
                });
            }
        });

        inputFrom.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

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

            }
            @Override
            public void afterTextChanged(Editable s) {
                if (isRatesLoaded) {
                    performConversion();
                } else {
                    showToast("Подождите, пока загрузятся курсы валют.");
                }
            }
        });

        AdapterView.OnItemSelectedListener spinnerListener = new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                if (isRatesLoaded) {
                    performConversion();
                }
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {}
        };

        spinnerFrom.setOnItemSelectedListener(spinnerListener);
        spinnerTo.setOnItemSelectedListener(spinnerListener);
    }

    private class LoadCurrenciesTask extends AsyncTask<Void, Void, List<String>> {
        @Override
        protected void onPreExecute() {
            loadingIndicator.setVisibility(View.VISIBLE);
        }

        @Override
        protected List<String> doInBackground(Void... voids) {
            List<String> currencies = repository.getAllUniqueCurrencies();
            Log.d("LoadCurrenciesTask", "Получены валюты: " + currencies.toString());
            return currencies;
        }

        @Override
        protected void onPostExecute(List<String> currencies) {
            super.onPostExecute(currencies);
            loadingIndicator.setVisibility(View.GONE);
            Log.d("LoadCurrenciesTask", "onPostExecute с валютами: " + currencies);
            if (currencies != null && !currencies.isEmpty()) {
                isRatesLoaded = true;
                ArrayAdapter<String> adapterFrom = new ArrayAdapter<>(MainActivity.this, android.R.layout.simple_spinner_item, currencies);
                adapterFrom.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
                spinnerFrom.setAdapter(adapterFrom);

                ArrayAdapter<String> adapterTo = new ArrayAdapter<>(MainActivity.this, android.R.layout.simple_spinner_item, currencies);
                adapterTo.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
                spinnerTo.setAdapter(adapterTo);

                // Устанавливаем стандартные значения
                spinnerFrom.setSelection(0);
                spinnerTo.setSelection(1);

                // Выполняем конвертацию
                performConversion();

            } else {
                if (!isInternetAvailable()) { // Метод для проверки сети
                    showToast("Нет интернет-соединения и нет кэшированных данных.");
                } else {
                    showToast("Не удалось загрузить валюты.");
                }
                Log.e("LoadCurrenciesTask", "Список валют пуст");
            }
        }
    }

    private void performConversion() {
        String fromCurrency = spinnerFrom.getSelectedItem().toString().toUpperCase();
        String toCurrency = spinnerTo.getSelectedItem().toString().toUpperCase();
        String amountStr = inputFrom.getText().toString();

        if (amountStr.isEmpty()) {
            inputTo.setText("");
            return;
        }

        if (fromCurrency.equals(toCurrency)) {
            inputTo.setText(amountStr);
            return;
        }

        try {
            double amount = Double.parseDouble(amountStr);

            // Выполняем конвертацию
            new ConvertCurrencyTask(fromCurrency, toCurrency, amount, repository, result -> {
                if (result != null) {
                    inputTo.setText(String.format("%.2f", result));
                } else {
                    showToast("Ошибка при конвертации валюты: Курс не найден");
                    inputTo.setText("");
                }
            }).execute();
        } catch (NumberFormatException e) {
            inputTo.setText("");
        }
    }

    private boolean isInternetAvailable() {
        ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
        if (connectivityManager != null) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                Network network = connectivityManager.getActiveNetwork();
                if (network == null) return false;
                NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network);
                return capabilities != null &&
                        (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ||
                                capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ||
                                capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET));
            } else {
                NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
                return networkInfo != null && networkInfo.isConnected();
            }
        }
        return false;
    }

    private void showToast(String message) {
        Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show();
    }
}<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/chart_constraint_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp"
    android:background="@color/background_color">

    <!-- Кнопка "Назад" -->
    <com.google.android.material.button.MaterialButton
        android:id="@+id/button_back_to_main"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Назад"
        app:cornerRadius="8dp"
        android:layout_marginBottom="16dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <!-- Спиннеры для выбора валют -->
    <Spinner
        android:id="@+id/spinner_from_chart"
        style="@style/SpinnerStyle"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        app:layout_constraintTop_toBottomOf="@id/button_back_to_main"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toStartOf="@id/button_swap_chart" />

    <!-- Кнопка для свапа валют -->
    <com.google.android.material.button.MaterialButton
        android:id="@+id/button_swap_chart"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:layout_marginStart="8dp"
        android:layout_marginEnd="8dp"
        app:cornerRadius="24dp"
        app:backgroundTint="@color/colorPrimary"
        app:icon="@drawable/baseline_swap_horiz"
        app:iconTint="@android:color/white"
        app:iconGravity="start"
        app:rippleColor="@color/colorPrimaryDark"
        android:elevation="4dp"
        android:contentDescription="Поменять местами валюты"
        app:layout_constraintTop_toTopOf="@id/spinner_from_chart"
        app:layout_constraintStart_toEndOf="@id/spinner_from_chart"
        app:layout_constraintEnd_toStartOf="@id/spinner_to_chart" />

    <!-- Второй спиннер -->
    <Spinner
        android:id="@+id/spinner_to_chart"
        style="@style/SpinnerStyle"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="@id/spinner_from_chart"
        app:layout_constraintStart_toEndOf="@id/button_swap_chart"
        app:layout_constraintEnd_toEndOf="parent" />

    <!-- Layout для выбора периода -->
    <LinearLayout
        android:id="@+id/time_range_layout"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="center_vertical"
        android:layout_marginTop="16dp"
        app:layout_constraintTop_toBottomOf="@id/spinner_from_chart"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent">

        <TextView
            android:id="@+id/time_range_label"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Период:"
            android:textSize="16sp"
            android:layout_marginEnd="8dp"
            android:textColor="@color/black" />

        <!-- Группа переключателей периода -->
        <com.google.android.material.button.MaterialButtonToggleGroup
            android:id="@+id/time_range_toggle_group_chart"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:singleSelection="true">

            <com.google.android.material.button.MaterialButton
                android:id="@+id/button_7_days_chart"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="7 дней"
                android:checked="true"
                style="@style/Widget.MaterialComponents.Button.OutlinedButton" />

            <com.google.android.material.button.MaterialButton
                android:id="@+id/button_30_days_chart"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="30 дней"
                style="@style/Widget.MaterialComponents.Button.OutlinedButton" />

            <com.google.android.material.button.MaterialButton
                android:id="@+id/button_90_days_chart"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="90 дней"
                style="@style/Widget.MaterialComponents.Button.OutlinedButton" />

        </com.google.android.material.button.MaterialButtonToggleGroup>
    </LinearLayout>

    <!-- График LineChart -->
    <com.github.mikephil.charting.charts.LineChart
        android:id="@+id/line_chart"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="@android:color/white"
        android:elevation="4dp"
        android:layout_marginTop="24dp"
        android:layout_marginBottom="16dp"
        app:layout_constraintTop_toBottomOf="@id/time_range_layout"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout><?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    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/constraint_layout_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp"
    android:background="@color/background_color"
    tools:context=".MainActivity">

    <!-- Загрузочный индикатор (ProgressBar) -->
    <ProgressBar
        android:id="@+id/loading_indicator"
        style="?android:attr/progressBarStyleLarge"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:indeterminateTint="@color/colorPrimary"
        android:visibility="gone"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <!-- Спиннеры выбора валют -->
    <Spinner
        android:id="@+id/spinner_from"
        style="@style/SpinnerStyle"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        app:layout_constraintEnd_toStartOf="@+id/button_swap"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <!-- Кнопка для свапа валют с улучшенной стилизацией -->
    <com.google.android.material.button.MaterialButton
        android:id="@+id/button_swap"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:layout_marginStart="8dp"
        android:layout_marginEnd="8dp"
        app:cornerRadius="24dp"
        app:backgroundTint="@color/colorPrimary"
        app:icon="@drawable/baseline_swap_horiz"
        app:iconTint="@android:color/white"
        app:iconGravity="start"
        app:rippleColor="@color/colorPrimaryDark"
        android:elevation="4dp"
        android:contentDescription="Поменять местами валюты"
        app:layout_constraintStart_toEndOf="@id/spinner_from"
        app:layout_constraintEnd_toStartOf="@id/spinner_to"
        app:layout_constraintTop_toTopOf="@id/spinner_from" />

    <Spinner
        android:id="@+id/spinner_to"
        style="@style/SpinnerStyle"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toEndOf="@id/button_swap"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="@id/spinner_from" />

    <!-- Поле ввода суммы -->
    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/input_layout_from"
        style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:hint="Введите сумму"
        app:boxStrokeColor="@color/colorPrimary"
        android:layout_marginTop="16dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/spinner_from">

        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/input_from"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="numberDecimal"
            android:padding="12dp" />

    </com.google.android.material.textfield.TextInputLayout>

    <!-- Поле вывода конвертированной суммы -->
    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/input_layout_to"
        style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:hint="Конвертированная сумма"
        app:boxStrokeColor="@color/colorPrimary"
        android:layout_marginTop="16dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/input_layout_from">

        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/input_to"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="none"
            android:focusable="false"
            android:padding="12dp"
            android:background="@drawable/edittext_background_disabled" />

    </com.google.android.material.textfield.TextInputLayout>

    <!-- Кнопка для перехода к графику -->
    <com.google.android.material.button.MaterialButton
        android:id="@+id/button_show_chart"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Показать график"
        app:cornerRadius="8dp"
        android:layout_marginTop="16dp"
        app:layout_constraintTop_toBottomOf="@id/input_layout_to"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

    <!-- Кнопка "Избранные валюты" -->
    <com.google.android.material.button.MaterialButton
        android:id="@+id/button_currency_list"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Избранные валюты"
        app:cornerRadius="8dp"
        android:layout_marginTop="16dp"
        app:layout_constraintTop_toBottomOf="@id/button_show_chart"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout> нужно добавть в активностях дату текущего курса