Respan Dataset Explorer

Select one behavior. Every returned turn has one binary label: Present or Absent. Source: final dense boolean release.

5,167,182physical rows
86shards
0.00%qualified row coverage
0.00%qualified cell coverage
Random row JSON API

turns-00052.parquet:21851

e59b88134724db0f29398644
turn 1/1gpt-4o-2024-08-06EnglishUnited States502 words
degenerate_repetitionAbsentFinal dense release
USER
Write a educational passage in Vietnamese that could appear in a textbook, giving some paragraphs or pharse that related to this question and answer set:
Câu hỏi: Khi muốn thay đổi kiểu của siêu liên kết trong trạng thái bình thường, bạn sử dụng lớp giả nào trong CSS?
A. a:hover.
B. a:active.
C. a:visited.
D. a:link.
Đáp án đúng: a:link
Notes that:
1. Remember that the multichoice question above must not be included in the paragraphs or phrase
2. You can not take an example same as the question
3. Explain detail like in the actual textbook
4. All instructions must be given in general form
ASSISTANT
Trong lĩnh vực thiết kế trang web, CSS (Cascading Style Sheets) là một công cụ quan trọng cho phép các nhà phát triển định dạng và bố trí các thành phần HTML trên trang web theo cách thức linh hoạt và phong phú. Một trong những ứng dụng phổ biến của CSS là thay đổi kiểu dáng của siêu liên kết để tạo ra trải nghiệm người dùng tốt hơn.

Siêu liên kết, thường được biểu diễn bằng thẻ `<a>`, là một phần tử HTML cho phép điều hướng từ trang này sang trang khác. CSS cung cấp các lớp giả (pseudo-classes) đặc biệt cho phép thay đổi kiểu dáng của siêu liên kết dựa trên trạng thái tương tác của người dùng với liên kết đó. Các trạng thái phổ biến bao gồm "liên kết chưa truy cập", "liên kết đã truy cập", "liên kết được nhấn giữ", và "liên kết khi di chuột qua".

Để thay đổi kiểu của một siêu liên kết trong trạng thái bình thường, tức là trước khi người dùng tương tác với nó, lớp giả `.a:link` được sử dụng. Đây là lớp giả mà bạn sẽ sử dụng để định dạng siêu liên kết khi chúng chưa được nhấp vào. Ví dụ, bạn có thể sử dụng `.a:link` để đặt màu chữ là màu xanh, font chữ là chữ nghiêng, hoặc thêm gạch chân cho siêu liên kết đó.

Ngoài trạng thái bình thường, lớp giả khác như `.a:hover` cho phép bạn thay đổi kiểu dáng của siêu liên kết khi người dùng di chuột qua nó, thường được sử dụng để cung cấp feedback trực quan kịp thời, giúp người dùng biết rằng siêu liên kết này có thể nhấp vào. Tương tự, `.a:active` thay đổi kiểu siêu liên kết khi người dùng nhấn giữ chuột xuống; lớp này có tác dụng trong một khoảng thời gian rất ngắn, thường là ngay khi người dùng bắt đầu nhấn vào liên kết. Cuối cùng, `.a:visited` định dạng các liên kết mà người dùng đã truy cập, cho phép họ nhận biết các trang đã thăm.

Hiểu rõ và sử dụng thành thạo các lớp giả này không chỉ cải thiện tính thẩm mỹ của trang web mà còn cải thiện sự thân thiện với người dùng, giúp điều hướng trang trở nên dễ dàng và trực quan hơn.

turns-00052.parquet:21852

ee71409ed6065c158ffab2ab
turn 1/1gpt-4o-2024-08-06EnglishUnited States448 words
degenerate_repetitionAbsentFinal dense release
USER
Write a educational passage in Vietnamese that could appear in a textbook, giving some paragraphs or pharse that related to this question and answer set:
Câu hỏi: PhET Interactive Simulations cung cấp các mô phỏng miễn phí trong lĩnh vực:
A. Kinh doanh.
B. Khoa học và Toán học.
C. Thiết kế đồ họa.
D. Y tế.
Đáp án đúng: Khoa học và Toán học
Notes that:
1. Remember that the multichoice question above must not be included in the paragraphs or phrase
2. You can not take an example same as the question
3. Explain detail like in the actual textbook
4. All instructions must be given in general form
ASSISTANT
PhET Interactive Simulations là một dự án giáo dục nổi tiếng được phát triển bởi Đại học Colorado Boulder, nhằm cung cấp các mô phỏng tương tác miễn phí cho học sinh và giáo viên trên toàn thế giới. Mục tiêu của PhET là cải thiện khả năng học tập và giảng dạy thông qua việc sử dụng công nghệ tiên tiến để tạo ra các công cụ học tập sinh động và hấp dẫn.

Các mô phỏng PhET tập trung chủ yếu vào các lĩnh vực khoa học và toán học, bao gồm vật lý, hóa học, sinh học và toán học phổ thông. Những mô phỏng này cho phép người học khám phá và tìm hiểu các khái niệm phức tạp một cách dễ dàng thông qua các thí nghiệm mô phỏng, bài tập tương tác và hình ảnh minh họa sống động.

Một trong những điểm mạnh của các mô phỏng PhET là chúng cho phép học sinh tự do thay đổi các thông số trong các thí nghiệm mô phỏng, từ đó quan sát và phân tích những tác động của các yếu tố khác nhau. Điều này không chỉ giúp học sinh hiểu sâu hơn về lý thuyết mà còn kích thích sự tò mò và khả năng tư duy độc lập.

Ngoài ra, PhET cũng được thiết kế để hỗ trợ việc học tập chủ động và hợp tác. Giáo viên có thể dễ dàng sử dụng các công cụ này để tạo ra các hoạt động nhóm, thảo luận lớp hoặc bài tập thực hành trong nhiều bối cảnh giáo dục khác nhau, từ lớp học truyền thống đến học trực tuyến.

Nhờ vào sự tiện lợi và chất lượng của các mô phỏng này, PhET Interactive Simulations đã trở thành một nguồn tài nguyên quý giá cho hàng triệu người học trên khắp thế giới, góp phần nâng cao chất lượng giáo dục và thúc đẩy niềm đam mê khoa học và toán học trong các thế hệ trẻ.

turns-00052.parquet:21853

fc5e29cfb676e7d26404251e
turn 1/1gpt-4o-2024-08-06EnglishRussia746 words
degenerate_repetitionAbsentFinal dense release
USER
User: 
		Example of response object model :

						{
				"task": "edit-content",
				"folderName": "src/controllers",
				"fileName": "bookingController.js",
				"baseName": "testModels/hotelmgt",
				"prompt": {
				"task_id": "_draftID_008",
				"task_name": "Create Handle Recurring Bookings Api",
				"description": "Generate a Node.js Handle Recurring Bookings Api with given dependenceis",
				"input": {
					"db": "mongoose",
					"authentication": ""
				},
				"output": "Return only the function code as specified in dependencies",
				"dependencies": [
					"Create a Api to manage recurring bookings. Export: exports.createRecurringBooking = async () => {...}",
					"Accept data from user: startDate, endDate, frequency (e.g., weekly, monthly), roomId.",
					"Validate required fields: Ensure startDate, endDate, frequency, and roomId are provided.",
					"Use a scheduling library (e.g., node-cron) to generate recurring booking dates based on the frequency.",
					"Check availability for each recurring date using the checkAvailability function.",
					"Create bookings for each recurring date and link them to the same user or service.",
					"Respond to user with a success message containing the booking details for each occurrence.",
					"Handle errors like conflicting bookings, invalid parameters, or database issues."
				],
				"status": "pending",
				"isImport": false,
				"readSupportFile": {
					"read": false,
					"folder": "",
					"file": "",
					"fileData": ""
				},
				}
				}
		
User: These are training datas for dependencies generation. :- 
			
			"Database initialization",
        "Set up cors congif these methods POST, GET and origin is '*' ",
        "Create server port number as 4000",
        "use express and express bodyparser",
        "User helemt and rate limitter for security",
        "import the router from this folder ./src/router/authRoutes.js with endpoint of /auth ",
        "import Database module form ./config/db.js file as DB, ",
        "Create error handelling middleware for routers",
        "Measure all security features are implemented",
		 "Create a Api to manage recurring bookings. Export: exports.createRecurringBooking = async () => {...}",
        "Validate required fields: Ensure startDate, endDate, frequency, and roomId are provided.",
        "Use a scheduling library (e.g., node-cron) to generate recurring booking dates based on the frequency.",
        "Check availability for each recurring date using the checkAvailability function.",
        "Create bookings for each recurring date and link them to the same user or service.",
        "Respond to user with a success message containing the booking details for each occurrence.",
        "Handle errors like conflicting bookings, invalid parameters, or database issues.",
		 "Create a Api to manage recurring bookings. Export: exports.createRecurringBooking = async () => {...}",
        "Validate required fields: Ensure startDate, endDate, frequency, and roomId are provided.",
        "Use a scheduling library (e.g., node-cron) to generate recurring booking dates based on the frequency.",
        "Check availability for each recurring date using the checkAvailability function.",
        "Create bookings for each recurring date and link them to the same user or service.",
        "Respond to user with a success message containing the booking details for each occurrence.",
        "Handle errors like conflicting bookings, invalid parameters, or database issues.",
		 "Create a Api to add a user to the waitlist for a fully booked date. Export: exports.addToWaitlist = async () => {...}",
        "Accept data from user body: userId, checkInDate, checkOutDate, hotelIds.",
        "Validate required fields: Ensure userId, checkInDate, checkOutDate, and hotelIds are provided.",
        "If the requested date is fully booked, add the user to the waitlist for that date.",
        "Store waitlist data with date, hotelIds, userId, and priority level.",
        "When a booking is canceled, notify users on the waitlist and offer the available slot.",
        "Respond to user with a success message confirming the user has been added to the waitlist.",
      ],
			
User: 
		    You have to create dependencies to instructions for gpt model to create application components by given dicriptions. ,Create instructions for a Node.js server. Specs : express, express-rate-limitter, helmet, connect db from this ../config/db.js file. handel all router error.,port will be 4000, make sure all security,import the router  ./src/router/authRoutes.js with endpoint of /auth ,import the router  ./src/router/todoRoutes.js with endpoint of /todo 

		fileName : server.js
		folderName : 
    	baseName: testModels/hotelmgt


		Please generate a json object in the given model.
			
Assistant:
ASSISTANT
```json
{
  "task": "edit-content",
  "folderName": "",
  "fileName": "server.js",
  "baseName": "testModels/hotelmgt",
  "prompt": {
    "task_id": "_draftID_009",
    "task_name": "Create Node.js Server with Express",
    "description": "Generate a Node.js server.js file with specified features and dependencies",
    "input": {
      "db": "connect from ../config/db.js",
      "authentication": "helmet, express-rate-limiter"
    },
    "output": "Return the full server.js code with error handling and routers setup",
    "dependencies": [
      "Initialize the database connection using ../config/db.js.",
      "Set CORS configuration for methods POST, GET, and allow origin '*'.",
      "Set the server port number to 4000.",
      "Use express and express body-parser middleware.",
      "Implement helmet and express-rate-limiter for security.",
      "Import the router from ./src/router/authRoutes.js with endpoint of /auth.",
      "Import the router from ./src/router/todoRoutes.js with endpoint of /todo.",
      "Create error handling middleware for routers.",
      "Ensure all specified security measures are implemented."
    ],
    "status": "pending",
    "isImport": false,
    "readSupportFile": {
      "read": false,
      "folder": "",
      "file": "",
      "fileData": ""
    }
  }
}
```

turns-00052.parquet:21854

50783fffac4d3af99454aa36
turn 1/1o1-mini-2024-09-12EnglishBelarus7101 words
degenerate_repetitionAbsentFinal dense release
USER
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> нужно добавть в активностях дату текущего курса
ASSISTANT
Чтобы добавить отображение даты текущего курса валют в ваших активностях, необходимо выполнить несколько шагов. Мы рассмотрим, как это можно реализовать в `MainActivity` и других связанных частях вашего приложения. 

### Шаг 1: Изменение `ConvertCurrencyTask` для возврата даты

Сначала нам нужно изменить `ConvertCurrencyTask`, чтобы он возвращал не только результат конвертации, но и дату, соответствующую курсу валют. Для этого:

1. **Создайте класс для хранения результата конвертации вместе с датой:**

```java
// ConversionResult.java
package com.example.myapplication;

import java.util.Date;

public class ConversionResult {
    public Double result;
    public Date date;

    public ConversionResult(Double result, Date date) {
        this.result = result;
        this.date = date;
    }
}
```

2. **Измените интерфейс `Callback` в `ConvertCurrencyTask` для передачи `ConversionResult`:**

```java
// ConvertCurrencyTask.java
public interface Callback {
    void onConversionResult(ConversionResult result);
}
```

3. **Измените `AsyncTask` параметры и реализацию в `ConvertCurrencyTask`:**

```java
// ConvertCurrencyTask.java
public class ConvertCurrencyTask extends AsyncTask<Void, Void, ConversionResult> {
    // Остальной код остается без изменений

    @Override
    protected ConversionResult doInBackground(Void... voids) {
        // Получение курса от USD к fromCurrency
        CurrencyRate rateFrom = repository.getLatestCurrencyRate("USD", fromCurrency);
        Double rateFromUSD = (rateFrom != null) ? rateFrom.rate : null;

        // Получение курса от USD к toCurrency
        CurrencyRate rateTo = repository.getLatestCurrencyRate("USD", toCurrency);
        Double rateToUSD = (rateTo != null) ? rateTo.rate : null;

        if (rateFromUSD == null || rateToUSD == null) {
            Log.e("ConvertCurrencyTask", "Курс не найден для " + fromCurrency + " или " + toCurrency);
            return null;
        }

        // Расчет перекрестного курса
        double crossRate = rateFromUSD / rateToUSD;

        // Выполнение конвертации
        double convertedAmount = amount * crossRate;

        // Получение даты последнего курса
        Date date = (rateFrom != null) ? rateFrom.date : new Date();

        return new ConversionResult(convertedAmount, date);
    }

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

4. **Добавьте метод в `CurrencyRateDao` и `CurrencyRepository` для получения последнего курса с датой:**

```java
// CurrencyRateDao.java
@Query("SELECT * FROM currency_rate WHERE baseCurrency = :fromCurrency AND targetCurrency = :toCurrency ORDER BY date DESC LIMIT 1")
CurrencyRate getLatestCurrencyRate(String fromCurrency, String toCurrency);
```

```java
// CurrencyRepository.java
public CurrencyRate getLatestCurrencyRate(String fromCurrency, String toCurrency) {
    return currencyRateDao.getLatestCurrencyRate(fromCurrency, toCurrency);
}
```

### Шаг 2: Добавление TextView для отображения даты в `activity_main.xml`

Добавьте `TextView` для отображения даты обновления курса под полями конвертации:

```xml
<!-- activity_main.xml -->
<androidx.constraintlayout.widget.ConstraintLayout
    ... >

    <!-- Другие элементы интерфейса -->

    <!-- Поле вывода конвертированной суммы -->
    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/input_layout_to"
        ... >

        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/input_to"
            ... />
    
    </com.google.android.material.textfield.TextInputLayout>
    
    <!-- TextView для отображения даты обновления курса -->
    <TextView
        android:id="@+id/text_rate_date"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Дата обновления: "
        android:textSize="14sp"
        android:textColor="@color/black"
        android:layout_marginTop="8dp"
        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_show_chart"
        ... />

    <!-- Кнопка "Избранные валюты" -->
    <com.google.android.material.button.MaterialButton
        android:id="@+id/button_currency_list"
        ... />

</androidx.constraintlayout.widget.ConstraintLayout>
```

### Шаг 3: Обновление `MainActivity` для отображения даты

Теперь нужно связать `TextView` и обновлять его после выполнения конвертации:

```java
// MainActivity.java
public class MainActivity extends AppCompatActivity {
    // Объявление новых переменных
    private TextView textRateDate;
    
    @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);
        textRateDate = findViewById(R.id.text_rate_date); // Связывание TextView

        // Остальной код остается без изменений

        // Обновление конвертации после загрузки валют
        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();
                });
            }
        });

        // Остальной код остается без изменений
    }

    private class LoadCurrenciesTask extends AsyncTask<Void, Void, List<String>> {
        @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("");
            textRateDate.setText("Дата обновления: ");
            return;
        }

        if (fromCurrency.equals(toCurrency)) {
            inputTo.setText(amountStr);
            textRateDate.setText("Дата обновления: Сегодня");
            return;
        }

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

            new ConvertCurrencyTask(fromCurrency, toCurrency, amount, repository, result -> {
                runOnUiThread(() -> {
                    if (result != null && result.result != null) {
                        inputTo.setText(String.format("%.2f", result.result));

                        // Форматирование даты
                        SimpleDateFormat dateFormat = new SimpleDateFormat("dd MMM yyyy", Locale.getDefault());
                        String dateStr = dateFormat.format(result.date);
                        textRateDate.setText("Дата обновления: " + dateStr);
                    } else {
                        showToast("Ошибка при конвертации валюты: Курс не найден");
                        inputTo.setText("");
                        textRateDate.setText("Дата обновления: неизвестно");
                    }
                });
            }).execute();
        } catch (NumberFormatException e) {
            inputTo.setText("");
            textRateDate.setText("Дата обновления: ");
        }
    }

    // Остальной код остается без изменений
}
```

### Шаг 4: Обновление уведомлений с отображением даты

Чтобы добавить дату в ежедневные уведомления, необходимо изменить `DailyNotificationReceiver`:

1. **Измените `DailyNotificationReceiver` для получения даты:**

```java
// DailyNotificationReceiver.java
@Override
public void onReceive(Context context, Intent intent) {
    try {
        SharedPreferences prefs = context.getSharedPreferences("favorites", 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 repository = new CurrencyRepository(context);

            // Получение текущей даты
            Date currentDate = new Date();
            SimpleDateFormat dateFormat = new SimpleDateFormat("dd MMM yyyy", Locale.getDefault());
            String dateStr = dateFormat.format(currentDate);

            contentBuilder.append("Дата: ").append(dateStr).append("\n\n");

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

            String contentText = contentBuilder.toString();

            createNotificationChannel(context);

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

            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();
    }
}
```

### Дополнительные изменения:

- **Добавление метода `getLatestCurrencyRate` в `CurrencyRepository` и `CurrencyRateDao`** уже было описано выше.
  
- **Проверка наличия курса**: убедитесь, что все пары валют имеют актуальные курсы, иначе уведомления могут содержать "нет данных".

### Шаг 5: Обновление UI в других активностях (опционально)

Если вы хотите отображать дату обновления курсов и в других активностях, таких как `CurrencyListActivity` или `ChartActivity`, следуйте аналогичным шагам:

1. **Добавьте `TextView` для отображения даты в соответствующем XML макете.**

2. **Измените соответствующие асинхронные задачи или методы для получения даты курса.**

3. **Обновите UI после получения данных.**

### Заключение

После выполнения этих шагов ваше приложение будет отображать дату последнего обновления курса валют в `MainActivity` и включать эту информацию в ежедневные уведомления. Обязательно протестируйте изменения, чтобы убедиться, что дата правильно отображается и обновляется при изменении курсов.

Если у вас возникнут дополнительные вопросы или потребуется помощь с другими частями приложения, не стесняйтесь обращаться!

turns-00052.parquet:21855

4abe8fd2565597fbd2b85db7
turn 1/22gpt-4o-2024-08-06SpanishPeru164 words
degenerate_repetitionAbsentFinal dense release
USER
ayudame con coordenadas, tengo que ponerlas en un juego y agregar objetos, tengo un error con las escaleras, son escaleras con peldaños por lo que tiene un tamaño, no son escaleras para subir rectamnte, si no avanzando casi como escaleras mecanicas, es un modelo y tengo que posicionarlo en un juego, pero acumular 3 escaleras y hacer como que van avanzando continuamente, pero engo el error de que si bien se puede avanzar en las tres escaleras, no estan bien alineadas, se ven un poco a la izquierda de la escalera principal, como haria para que esten alineadas para que parescan una sola escalera? espera a que te envie las coordenadas
ASSISTANT
Claro, estaré encantado de ayudarte con las coordenadas para asegurar que las escaleras estén bien alineadas. Cuando tengas las coordenadas listas, envíamelas y revisaré cómo puedes ajustarlas para que parezcan una sola escalera continua. Asegúrate de incluir cualquier otra información relevante, como el tamaño de las escaleras y cualquier desplazamiento que estés aplicando.

turns-00052.parquet:21856

3f30029738630a378d90ee05
turn 2/22gpt-4o-2024-08-06SpanishPeru150 words
degenerate_repetitionAbsentFinal dense release
USER
pos X Y Z
{pos={2606.631,93.319,-943.789},rotY=111.40,},
{pos={2610.209,97.319,-945.726},rotY=111.40,},
{pos={2613.787,101.319,-947.663},rotY=111.40,},
esas son las coordenadas que hacen que se pueda avanzar por las escaleras pero el problema que te dije es que estan un poco a la izquierda de la primera escalera, la tercera esta un poco a la izquierda de la segunda, ademas quisiera que esta sea la nueva coordenada de la primera, calcula como seria de la segunda y la tercera, ademas como te dije deben estar aliandas para que el avance sea continuo, las 3 escaleras deben parecer una sola, esta es la nuyeva coordenada de la primera
{pos={2609.973,93.500,-946.018},rotY=109.487,},
responde en 3 lineas con las coordenadas
ASSISTANT
Para alinear las escaleras y asegurar un avance continuo con la nueva posición inicial, las coordenadas ajustadas serían:

1. Primera escalera: `{pos={2609.973,93.500,-946.018},rotY=109.487},`
2. Segunda escalera: `{pos={2613.551,97.500,-947.955},rotY=109.487},`
3. Tercera escalera: `{pos={2617.129,101.500,-949.892},rotY=109.487},`

Estas coordenadas mantienen la misma dirección y alineación, asegurando una transición fluida entre las escaleras.

turns-00052.parquet:21857

1d38dad9cfd17ae89166248c
turn 3/22gpt-4o-2024-08-06SpanishPeru25 words
degenerate_repetitionAbsentFinal dense release
USER
{pos={2621.317,105.886,-951.355},rotY=-68.953,}, de esta posicion quiero que este mas adelante horizontalmente, unos 10 cm y undida para abajo verticlamente unos 20cm, responde en una linea
ASSISTANT
`{pos={2621.417,105.686,-961.355},rotY=-68.953},`

turns-00052.parquet:21858

229a0c0bd2f240797b03e417
turn 4/22gpt-4o-2024-08-06SpanishPeru133 words
degenerate_repetitionAbsentFinal dense release
USER
sigo viendo el problema de que las escaleras no estan alineadas, se puede avanzar en las tres  pero no se ven alineadas, debes  alinearlas, la segunda as que este 20cm mas  a la derecha horizontalmente, la tercera que este 40 cm mas a la derecha horizontalmente,
esta es la nueva coordenada de la escalera uno
{pos={2609.480,93.319,-942.162},rotY=111.683,},

ademas de esta posicion haz que este 1 mitro mas a la izquierda horizontalmente y que este 1 metro undido verticlamnte
{pos={2621.417,105.686,-961.355},rotY=-68.95,},
responde n 4 lineas
ASSISTANT
Claro, aquí tienes las coordenadas ajustadas para alinear las escaleras y los cambios solicitados:

1. Primera escalera: `{pos={2609.480,93.319,-942.162},rotY=111.683},`
2. Segunda escalera: `{pos={2609.680,93.319,-942.162},rotY=111.683},` (20 cm a la derecha)
3. Tercera escalera: `{pos={2609.880,93.319,-942.162},rotY=111.683},` (40 cm a la derecha)

Para la posición modificada:
- `{pos={2620.417,104.686,-961.355},rotY=-68.95},` (1 metro a la izquierda y 1 metro hundido)

turns-00052.parquet:21859

e1335765e4a756d3292db8a6
turn 5/22gpt-4o-2024-08-06SpanishPeru179 words
degenerate_repetitionAbsentFinal dense release
USER
esta mal, verticalmente una deve estar sobre la otra para poder avanzar, lo que has echo es que todas esten al nivle de la primera, estas posiciones estan bien porque una esta sobre la otra permitiendo avanzar
Primera escalera: {pos={2609.973,93.500,-946.018},rotY=109.487},
Segunda escalera: {pos={2613.551,97.500,-947.955},rotY=109.487},
Tercera escalera: {pos={2617.129,101.500,-949.892},rotY=109.487},
solo cambia la prmera posicocion para que este mas a la izquieda horizontalmente
Primera escalera: {pos={2609.480,93.319,-942.162},rotY=111.683},
y segun esa nueva opisicion como deberian estar las otras, recuerda que como lo que te acabo de envir deben estar una sobre la otra permitiendo avanzar como una escalera, lo que me diste antes esta una sobre la otra por lo que las  tres escaleras se hunetaron y no puedo avanzar alcanzando  la altra que qiero, verifica y velve a responder en 4 lineas, ademas has que esto este 3 metros mas a la  derecha
{pos={2620.417,104.686,-961.355},rotY=-68.95,},
ASSISTANT
Mis disculpas por la confusión anterior. Aquí tienes las coordenadas ajustadas:

1. Primera escalera: `{pos={2612.480,93.319,-942.162},rotY=111.683},` (3 metros a la derecha)
2. Segunda escalera: `{pos={2616.058,97.319,-944.099},rotY=111.683},`
3. Tercera escalera: `{pos={2619.636,101.319,-946.036},rotY=111.683},`

Para la posición adicional solicitada:
- `{pos={2623.417,104.686,-961.355},rotY=-68.95},` (3 metros a la derecha)

turns-00052.parquet:21860

9e14ffc9c8b74d67db699e10
turn 6/22gpt-4o-2024-08-06SpanishPeru67 words
degenerate_repetitionAbsentFinal dense release
USER
la primera escalera debe estar 30 cm a la derecha no 3 metros
ASSISTANT
Mis disculpas por la confusión anterior. Aquí están las coordenadas ajustadas:

1. Primera escalera: `{pos={2609.780,93.319,-942.162},rotY=111.683},` (30 cm a la derecha)
2. Segunda escalera: `{pos={2613.358,97.319,-944.099},rotY=111.683},` (manteniendo el desplazamiento vertical de 4 m)
3. Tercera escalera: `{pos={2616.936,101.319,-946.036},rotY=111.683},` (manteniendo el desplazamiento vertical de 8 m)

Para la posición adicional solicitada:
- `{pos={2623.417,104.686,-961.355},rotY=-68.95},` (3 metros a la derecha)