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 = 3, 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.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) {
        // Convert using USD as an intermediary
        if (fromCurrency.equals("USD")) {
            // Direct conversion from USD to the target currency
            Double rateToTarget = repository.getExchangeRate("USD", toCurrency);
            if (rateToTarget != null) {
                return amount * rateToTarget;
            }
        } else if (toCurrency.equals("USD")) {
            // Direct conversion from source currency to USD
            Double rateFromSource = repository.getExchangeRate(fromCurrency, "USD");
            if (rateFromSource != null) {
                return amount * rateFromSource;
            }
        } else {
            // Conversion from source currency to USD, then USD to target currency
            Double rateFromSource = repository.getExchangeRate(fromCurrency, "USD");
            Double rateToTarget = repository.getExchangeRate("USD", toCurrency);

            if (rateFromSource != null && rateToTarget != null) {
                // First convert amount to USD, then from USD to target currency
                double amountInUSD = amount * rateFromSource;
                return amountInUSD * rateToTarget;
            }
        }

        Log.e("ConvertCurrencyTask", "Conversion rate not found for " + fromCurrency + " to " + toCurrency);
        return null; // Return null if conversion is not possible
    }

    @Override
    protected void onPostExecute(Double result) {
        callback.onConversionResult(result);
    }
}
// Converters.java
package com.example.myapplication;

import androidx.room.TypeConverter;
import java.util.Date;

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 androidx.room.Entity;
import androidx.room.Index;
import androidx.room.PrimaryKey;
import androidx.room.TypeConverters;
import java.util.Date;

@Entity(tableName = "currency_rates",
        indices = {@Index(value = {"baseCurrency", "targetCurrency", "date"}, unique = true)})
@TypeConverters({Converters.class})
public class CurrencyRate {
    @PrimaryKey(autoGenerate = true)
    public int id;

    public String baseCurrency;
    public String targetCurrency;
    public double rate;
    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;
    }
}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);

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

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

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

    @Query("SELECT * FROM currency_rates WHERE UPPER(baseCurrency) = UPPER(:baseCurrency) AND UPPER(targetCurrency) = UPPER(:targetCurrency) AND date <= :currentDate ORDER BY date ASC")
    List<CurrencyRate> getCurrencyRateHistory(String baseCurrency, String targetCurrency, Date currentDate);
}// CurrencyRepository.java
package com.example.myapplication;

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

import com.example.myapplication.api.LoadCurrencyRatesTask;

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

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

public class CurrencyRepository {
    private final CurrencyRateDao currencyRateDao;

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

    // Синхронный метод вставки курса
    public void insertCurrencyRateSync(CurrencyRate rate) {
        currencyRateDao.insertCurrencyRate(rate);
    }

    // Метод для получения списка уникальных валют
    public List<String> getAllUniqueCurrencies() {
        List<String> baseCurrencies = currencyRateDao.getAllBaseCurrencies();
        List<String> targetCurrencies = currencyRateDao.getAllTargetCurrencies();
        Set<String> allCurrencies = new HashSet<>(baseCurrencies);
        allCurrencies.addAll(targetCurrencies);
        return new ArrayList<>(allCurrencies);
    }

    // Получение последнего курса для конвертации
    public Double getExchangeRate(String fromCurrency, String toCurrency) {
        return currencyRateDao.getLatestExchangeRate(fromCurrency, toCurrency);
    }

    // Получение истории курсов для графика
    public List<CurrencyRate> getCurrencyRateHistory(String baseCurrency, String targetCurrency, Date date) {
        return currencyRateDao.getCurrencyRateHistory(baseCurrency, targetCurrency, date);
    }

    // Загрузка курсов из API
    public void loadAllCurrencyRatesFromApi(LoadCurrencyRatesTask.Callback callback) {
        new LoadCurrencyRatesTask(this, callback).execute();
    }
}package com.example.myapplication;

import android.annotation.SuppressLint;
import android.os.AsyncTask;
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.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.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.data.Entry; // Импорт Entry для точек данных на графике
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet; // Импорт LineDataSet для набора данных
import com.github.mikephil.charting.formatter.ValueFormatter; // Импорт ValueFormatter для пользовательских подписей оси
import com.github.mikephil.charting.utils.ColorTemplate; // Импорт ColorTemplate для цветов

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

public class MainActivity extends AppCompatActivity {
    private Spinner spinnerFrom;
    private Spinner spinnerTo;
    private EditText inputFrom;
    private EditText inputTo;
    private ProgressBar loadingIndicator;
    private CurrencyRepository repository;
    private boolean isRatesLoaded = false;

    private LineChart lineChart; // Инициализация графика

    @SuppressLint("MissingInflatedId")
    @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);
        lineChart = findViewById(R.id.line_chart); // Инициализация графика

        // Показываем индикатор загрузки и загружаем курсы
        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());
                });
            }
        });

        inputFrom.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                // No action needed here
            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                // No action needed here
            }

            @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();
                    loadChartData(); // Загрузить данные для графика при изменении выбора
                }
            }

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

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

    private void loadChartData() {
        String fromCurrency = spinnerFrom.getSelectedItem().toString().toUpperCase();
        String toCurrency = spinnerTo.getSelectedItem().toString().toUpperCase();

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

    private class LoadChartDataTask extends AsyncTask<Void, Void, LineData> {
        private final String fromCurrency;
        private final String toCurrency;

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

        @Override
        protected LineData doInBackground(Void... voids) {
            // In LoadChartDataTask
            List<CurrencyRate> historyList = repository.getCurrencyRateHistory(fromCurrency, toCurrency, new Date());
            Log.d("LoadChartDataTask", "Количество записей истории: " + historyList.size());

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

            // Создаем список точек для графика
            List<Entry> entries = new ArrayList<>();

            for (int i = 0; i < historyList.size(); i++) {
                CurrencyRate history = historyList.get(i);
                // Используем индекс как x-координату, а курс как y-координату
                entries.add(new Entry(i, (float) history.rate));
            }

            LineDataSet dataSet = new LineDataSet(entries, "Курс " + fromCurrency + "/" + toCurrency);
            dataSet.setColor(ColorTemplate.getHoloBlue());
            dataSet.setCircleColor(ColorTemplate.getHoloBlue());
            dataSet.setLineWidth(1.5f);
            dataSet.setCircleRadius(3f);
            dataSet.setDrawCircleHole(false);
            dataSet.setValueTextSize(9f);
            dataSet.setDrawFilled(true);
            dataSet.setFillColor(ColorTemplate.getHoloBlue());

            return new LineData(dataSet);
        }

        @Override
        protected void onPostExecute(LineData lineData) {
            if (lineData != null) {
                lineChart.setData(lineData);

                // Настраиваем ось X для отображения дат
                XAxis xAxis = lineChart.getXAxis();
                xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
                xAxis.setGranularity(1f);
                xAxis.setValueFormatter(new ValueFormatter() {
                    @Override
                    public String getFormattedValue(float value) {
                        int index = (int) value;
                        List<CurrencyRate> historyList = repository.getCurrencyRateHistory(fromCurrency, toCurrency, new Date());
                        if (index >= 0 && index < historyList.size()) {
                            Date date = historyList.get(index).date;
                            return new SimpleDateFormat("dd.MM", Locale.getDefault()).format(date);
                        } else {
                            return "";
                        }
                    }
                });

                lineChart.invalidate(); // обновляем график
            } else {
                lineChart.clear();
                lineChart.invalidate();
                showToast("Нет данных для отображения графика");
            }
        }
    }

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

        @Override
        protected List<String> doInBackground(Void... voids) {
            // Загрузка списка валют из репозитория
            return repository.getAllUniqueCurrencies();
        }

        @Override
        protected void onPostExecute(List<String> currencies) {
            loadingIndicator.setVisibility(View.GONE);
            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();
                loadChartData();

            } else {
                showToast("Не удалось загрузить валюты");
            }
        }
    }

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

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

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

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

            // Perform conversion
            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 void showToast(String message) {
        Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show();
    }
}package com.example.myapplication.api;

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

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

import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.Random;

public class LoadCurrencyRatesTask extends AsyncTask<Void, Void, Void> {
    private static final String TAG = "LoadCurrencyRatesTask";
    private static final String API_URL = "https://api.exchangerate-api.com/v4/latest/USD";
    private final CurrencyRepository repository;

    private final Callback callback;

    public interface Callback {
        void onRatesLoaded();
        void onError(Exception e);
    }

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

    @Override
    protected Void doInBackground(Void... voids) {
        try {
            // Загрузка текущих курсов
            URL url = new URL(API_URL);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            connection.connect();

            int responseCode = connection.getResponseCode();
            if (responseCode != HttpURLConnection.HTTP_OK) {
                Log.e(TAG, "Failed to connect to API. Response Code: " + responseCode);
                if (callback != null) {
                    callback.onError(new Exception("Failed to connect to API. Response Code: " + responseCode));
                }
                return null;
            }

            // Чтение ответа
            BufferedReader reader = new BufferedReader(
                    new InputStreamReader(connection.getInputStream())
            );
            StringBuilder response = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                response.append(line);
            }
            reader.close();
            Log.d(TAG, "API response received.");

            // Разбор JSON ответа
            JSONObject jsonResponse = new JSONObject(response.toString());
            JSONObject rates = jsonResponse.getJSONObject("rates");
            Log.d(TAG, "Parsing JSON response.");

            for (Iterator<String> it = rates.keys(); it.hasNext(); ) {
                String currency = it.next().toUpperCase();
                double rate = rates.getDouble(currency);
                Date currentDate = new Date();

                // Вставляем текущий курс
                repository.insertCurrencyRateSync(new CurrencyRate("USD", currency, rate, currentDate));
                repository.insertCurrencyRateSync(new CurrencyRate(currency, "USD", 1 / rate, currentDate));

                // Генерируем и вставляем исторические курсы
                generateMockHistory("USD", currency, rate);
                generateMockHistory(currency, "USD", 1 / rate);
            }

            if (callback != null) {
                callback.onRatesLoaded();
            }
        } catch (Exception e) {
            Log.e(TAG, "Error loading currency rates", e);
            if (callback != null) {
                callback.onError(e);
            }
        }
        return null;
    }

    private void generateMockHistory(String baseCurrency, String targetCurrency, double currentRate) {
        Random random = new Random();
        Calendar calendar = Calendar.getInstance();
        // Get the current date once
        Date currentDate = new Date();
        calendar.setTime(currentDate);

        // Generate history for the last 30 days
        for (int i = 30; i >= 1; i--) {
            // Reset calendar to current date at each iteration
            calendar.setTime(currentDate);
            calendar.add(Calendar.DAY_OF_MONTH, -i);

            // Set time components to zero
            calendar.set(Calendar.HOUR_OF_DAY, 0);
            calendar.set(Calendar.MINUTE, 0);
            calendar.set(Calendar.SECOND, 0);
            calendar.set(Calendar.MILLISECOND, 0);

            Date date = calendar.getTime();

            // Generate fluctuation
            double fluctuation = (random.nextDouble() - 0.5) * 0.1; // ±5%
            double historicalRate = currentRate * (1 + fluctuation);

            CurrencyRate historyRate = new CurrencyRate(
                    baseCurrency,
                    targetCurrency,
                    historicalRate,
                    date
            );

            repository.insertCurrencyRateSync(historyRate);
            Log.d("LoadCurrencyRatesTask", "Inserting CurrencyRate: baseCurrency=" + baseCurrency +
                    ", targetCurrency=" + targetCurrency + ", date=" + date + ", rate=" + historicalRate);
        }
    }
} при запуске пишет нет данных для графика