1486 lines
62 KiB
Markdown
1486 lines
62 KiB
Markdown
# ЛАБОРАТОРНАЯ РАБОТА
|
||
## Разработка кроссплатформенного мобильного приложения «Телефонный справочник» с использованием .NET MAUI и C#
|
||
|
||
---
|
||
|
||
## СОДЕРЖАНИЕ
|
||
|
||
1. [Введение](#1-введение)
|
||
2. [Теоретическая часть](#2-теоретическая-часть)
|
||
3. [Практическая часть](#3-практическая-часть)
|
||
4. [Заключение](#4-заключение)
|
||
5. [Список использованных источников](#5-список-использованных-источников)
|
||
|
||
---
|
||
|
||
## 1. ВВЕДЕНИЕ
|
||
|
||
### 1.1 Актуальность темы
|
||
|
||
В современном мире мобильные приложения стали неотъемлемой частью повседневной жизни. Смартфоны используются для хранения контактной информации, что делает приложения-справочники одними из наиболее востребованных утилит. Традиционные телефонные справочники, встроенные в операционные системы, зачастую не满足ают потребностям пользователей в части гибкости, кастомизации интерфейса и дополнительной функциональности.
|
||
|
||
Создание собственного приложения «Телефонный справочник» является актуальной задачей по следующим причинам:
|
||
|
||
- **Персонализация** — возможность адаптировать приложение под конкретные нужды пользователя
|
||
- **Офлайн-доступ** — данные хранятся локально и доступны без интернет-соединения
|
||
- **Кроссплатформенность** — охват максимальной аудитории при минимальных затратах на разработку
|
||
- **Практическое применение** — разработка реального приложения с CRUD-операциями, базой данных и графическим интерфейсом
|
||
|
||
### 1.2 Почему .NET MAUI и C#?
|
||
|
||
**.NET MAUI** (Multi-platform App UI) — это современный фреймворк от Microsoft для создания кроссплатформенных приложений с единой кодовой базой. Выбор данной технологии обусловлен рядом преимуществ:
|
||
|
||
| Критерий | Описание |
|
||
|----------|----------|
|
||
| **Единая кодовая база** | Один проект для iOS, Android, Windows, macOS |
|
||
| **Нативная производительность** | Компиляция в нативный код для каждой платформы |
|
||
| **C# как язык разработки** | Современный, типобезопасный объектно-ориентированный язык |
|
||
| **Прямой доступ к API платформ** | Возможность использовать платформо-специфичный код |
|
||
| **Встроенные контролы** | Богатая библиотека кроссплатформенных UI-компонентов |
|
||
| **Интеграция с экосистемой .NET** | Доступ к NuGet-пакетам и инструментам |
|
||
|
||
**C#** является оптимальным выбором благодаря:
|
||
- Строгой типизации и безопасности памяти
|
||
- Мощной стандартной библиотеке
|
||
-LINQ для работы с коллекциями
|
||
- Асинхронному программированию (async/await)
|
||
- Активной поддержке и развитию языка
|
||
|
||
### 1.3 Цель и задачи лабораторной работы
|
||
|
||
**Цель:** разработать кроссплатформенное мобильное приложение «Телефонный справочник» с использованием .NET MAUI и языка программирования C#.
|
||
|
||
**Задачи:**
|
||
1. Изучить теоретические основы .NET MAUI
|
||
2. Спроектировать архитектуру приложения
|
||
3. Реализовать CRUD-операции для контактов
|
||
4. Настроить хранение данных с использованием SQLite
|
||
5. Реализовать систему смены тем оформления
|
||
6. Добавить функционал импорта/экспорта контактов
|
||
7. Провести тестирование приложения
|
||
|
||
---
|
||
|
||
## 2. ТЕОРЕТИЧЕСКАЯ ЧАСТЬ
|
||
|
||
### 2.1 Обзор технологического стека
|
||
|
||
#### 2.1.1 .NET 9.0
|
||
|
||
**.NET 9** — latest stable release платформы .NET, обеспечивающий высокую производительность и широкие возможности для разработки приложений. В данном проекте используется как рантайм для MAUI-приложений.
|
||
|
||
**Ключевые особенности .NET 9:**
|
||
- Улучшенная производительность JIT-компилятора (Just-In-Time)
|
||
- **Native AOT компиляция** — компиляция Ahead-of-Time для быстрого запуска
|
||
- Обновлённые библиотеки Base Class Library (BCL)
|
||
- Улучшенная работа с JSON (System.Text.Json)
|
||
- Новые функции в LINQ
|
||
- Поддержка регулярных выражений нового поколения
|
||
|
||
**.NET Runtime** обеспечивает:
|
||
- Управление памятью через сборщик мусора (GC)
|
||
- JIT/AOT компиляцию IL-кода в нативный
|
||
- Common Type System (CTS) для единообразия типов данных
|
||
- Безопасность и управление доступом кода
|
||
|
||
#### 2.1.2 .NET MAUI
|
||
|
||
**.NET MAUI** (Multi-platform App UI) — эволюция Xamarin.Forms, предоставляющая единый фреймворк для создания нативных приложений для всех поддерживаемых платформ. Релиз состоялся в 2022 году как часть .NET 7.
|
||
|
||
**История развития:**
|
||
```
|
||
Xamarin.Forms (2014) → Xamarin.Forms 5.0 → .NET MAUI (.NET 7, 2022) → .NET MAUI (.NET 9, 2024)
|
||
```
|
||
|
||
**Архитектура MAUI:**
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ Код приложения (C#) │
|
||
├─────────────────────────────────────────────────────────────────┤
|
||
│ UI-слой (XAML/C#) │
|
||
│ ┌──────────────────────────────────────────┐ │
|
||
│ │ Pages │ Layouts │ Controls │ Converters │ │
|
||
│ └──────────────────────────────────────────┘ │
|
||
├─────────────────────────────────────────────────────────────────┤
|
||
│ Platform Features │
|
||
│ Geolocation │ Contacts │ Media │ Sensors │ Sharing │
|
||
├─────────────────────────────────────────────────────────────────┤
|
||
│ Graphics (Сжатые графики) │
|
||
├─────────────────────────────────────────────────────────────────┤
|
||
│ Android │ iOS │ Windows │ Mac │ Linux │ Tizen │
|
||
│ (Skia) │(CoreG)│ (WinUI) │(CoreG) │ (GTK) │ (Tizen) │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**Жизненный цикл приложения MAUI:**
|
||
|
||
| Метод | Описание |
|
||
|-------|---------|
|
||
| `OnStart()` | Вызывается при первом запуске приложения |
|
||
| `OnResume()` | Вызывается при возобновлении из фона |
|
||
| `OnSleep()` | Вызывается при переходе в фоновый режим |
|
||
|
||
**Целевые платформы проекта:**
|
||
- `net9.0-android` — Android 5.0+ (API 21+)
|
||
- `net9.0-ios` — iOS 11.0+
|
||
- `net9.0-maccatalyst` — macOS 10.15+
|
||
- `net9.0-windows10.0.19041.0` — Windows 10+
|
||
|
||
**Ключевые преимущества MAUI:**
|
||
1. **Hot Reload** — мгновенное обновление UI без перезапуска
|
||
2. **Handlers Architecture** — настраиваемая система обработчиков для платформо-специфичных элементов
|
||
3. **Graphics API** — новый единый API для рисования через `Microsoft.Maui.Graphics`
|
||
4. **Shell** — навигация на основе оболочки с поддержкой меню и вкладок
|
||
|
||
#### 2.1.3 Язык C# 12
|
||
|
||
**C# 12** — современный объектно-ориентированный язык программирования с поддержкой:
|
||
- Nullable reference types
|
||
- Pattern matching
|
||
- Record types
|
||
- Init-only properties
|
||
- Async/await
|
||
- LINQ
|
||
- Primary constructors
|
||
- Collection expressions
|
||
- Alias any type
|
||
|
||
**Ключевые концепции C# в контексте MAUI:**
|
||
|
||
**Асинхронное программирование (async/await):**
|
||
```csharp
|
||
// Паттерн асинхронного программирования
|
||
public async Task<List<Contact>> GetContactsAsync()
|
||
{
|
||
var contacts = await _database.Table<Contact>().ToListAsync();
|
||
return contacts.OrderBy(c => c.Name).ToList();
|
||
}
|
||
```
|
||
|
||
**LINQ (Language Integrated Query):**
|
||
```csharp
|
||
// Запросы к коллекциям на уровне языка
|
||
var results = contacts
|
||
.Where(c => c.Name.Contains(searchTerm))
|
||
.OrderBy(c => c.Name)
|
||
.Select(c => new { c.Name, c.PhoneNumber });
|
||
```
|
||
|
||
**Обнуляемые ссылочные типы:**
|
||
```csharp
|
||
// ? делает тип обнуляемым
|
||
private Contact? _contact;
|
||
private string? _photoBase64;
|
||
|
||
// ! оператор подавления null-предупреждения
|
||
_contactService.OnContactsUpdated += LoadContacts;
|
||
```
|
||
|
||
#### 2.1.4 XAML
|
||
|
||
**XAML** (Extensible Application Markup Language) — декларативный язык разметки для определения пользовательского интерфейса в .NET-приложениях.
|
||
|
||
**Синтаксис XAML:**
|
||
```xml
|
||
<!-- Пространства имён -->
|
||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||
xmlns:local="clr-namespace:Phonebook">
|
||
|
||
<!-- Встроенные ресурсы -->
|
||
<ContentPage.Resources>
|
||
<converters:Base64ToPhoto x:Key="Base64ToPhoto"/>
|
||
</ContentPage.Resources>
|
||
|
||
<!-- Визуальная иерархия -->
|
||
<Grid>
|
||
<Label Text="Телефонный справочник" />
|
||
</Grid>
|
||
</ContentPage>
|
||
```
|
||
|
||
**Основные элементы MAUI:**
|
||
|
||
| Категория | Элементы |
|
||
|-----------|----------|
|
||
| **Pages** | ContentPage, NavigationPage, TabbedPage, Shell |
|
||
| **Layouts** | StackLayout, Grid, FlexLayout, AbsoluteLayout |
|
||
| **Controls** | Label, Button, Entry, Image, ListView, CollectionView |
|
||
| **Views** | Frame, Border, ScrollView |
|
||
|
||
**Привязка данных (Data Binding):**
|
||
```xml
|
||
<!-- Односторонняя привязка -->
|
||
<Label Text="{Binding Name}" />
|
||
|
||
<!-- Привязка с конвертером -->
|
||
<Image Source="{Binding PhotoBase64,
|
||
Converter={StaticResource Base64ToPhoto}}" />
|
||
|
||
<!-- Привязка с AppTheme -->
|
||
<Button BackgroundColor="{AppThemeBinding Light=#4CAF50, Dark=#333}" />
|
||
```
|
||
|
||
**Ресурсы и стили:**
|
||
```xml
|
||
<ResourceDictionary>
|
||
<Color x:Key="Primary">#4CAF50</Color>
|
||
|
||
<Style TargetType="Button">
|
||
<Setter Property="BackgroundColor" Value="{StaticResource Primary}"/>
|
||
<Setter Property="TextColor" Value="White"/>
|
||
</Style>
|
||
</ResourceDictionary>
|
||
```
|
||
|
||
### 2.2 Архитектура приложения
|
||
|
||
Приложение построено на принципах **сервис-ориентированной архитектуры** (SOA):
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ Pages (UI Layer) │
|
||
│ MainPage │ ContactDetailPage │ SettingsPage │ AppShell │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ Services (Business Logic) │
|
||
│ ContactService │ DatabaseService │ ThemeService │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ Models (Data Layer) │
|
||
│ Contact.cs (Entity) │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ Data Persistence Layer │
|
||
│ SQLite Database │ Preferences Storage │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 2.3 Компоненты архитектуры
|
||
|
||
#### 2.3.1 Слои приложения
|
||
|
||
Приложение состоит из следующих логических слоёв:
|
||
|
||
| Слой | Описание | Компоненты |
|
||
|------|----------|------------|
|
||
| **UI Layer** | Пользовательский интерфейс | XAML-страницы, обработчики событий |
|
||
| **Business Logic Layer** | Бизнес-логика приложения | ContactService, ThemeService |
|
||
| **Data Access Layer** | Взаимодействие с хранилищем | DatabaseService |
|
||
| **Infrastructure Layer** | Системные сервисы | SQLite, Preferences, Permissions |
|
||
|
||
#### 2.3.2 Dependency Injection (DI)
|
||
|
||
Внедрение зависимостей через `MauiProgram.cs`:
|
||
|
||
```csharp
|
||
builder.Services.AddSingleton<DatabaseService>();
|
||
builder.Services.AddSingleton<ThemeService>();
|
||
builder.Services.AddSingleton<ContactService>();
|
||
```
|
||
|
||
### 2.4 Технологии хранения данных
|
||
|
||
#### 2.4.1 SQLite
|
||
|
||
**SQLite** — легковесная встраиваемая реляционная база данных, широко используемая в мобильных приложениях. SQLite хранит всю базу данных в одном файле, что делает её идеальной для автономных приложений.
|
||
|
||
**Архитектура SQLite:**
|
||
```
|
||
┌─────────────────────────────────────────┐
|
||
│ Приложение (C# код) │
|
||
├─────────────────────────────────────────┤
|
||
│ SQLite-net-pcl (ORM Layer) │
|
||
├─────────────────────────────────────────┤
|
||
│ SQLite3 (Native C Library) │
|
||
├─────────────────────────────────────────┤
|
||
│ Database File (.db3) │
|
||
└─────────────────────────────────────────┘
|
||
```
|
||
|
||
**Преимущества SQLite:**
|
||
- Нулевая конфигурация — не требует сервера или администратора
|
||
- Хранение в одном переносимом файле
|
||
- Высокая производительность для мобильных приложений
|
||
- ACID-совместимость (атомарность, согласованность, изоляция, долговечность)
|
||
- Асинхронные операции через `SQLiteAsyncConnection`
|
||
- Поддержка SQL-запросов
|
||
|
||
**NuGet-пакет:** `sqlite-net-pcl` версии 1.9.172
|
||
|
||
**Основные операции SQLite-net:**
|
||
```csharp
|
||
// Создание таблицы
|
||
await _database.CreateTableAsync<Contact>();
|
||
|
||
// Вставка записи
|
||
await _database.InsertAsync(contact);
|
||
|
||
// Обновление записи
|
||
await _database.UpdateAsync(contact);
|
||
|
||
// Удаление записи
|
||
await _database.DeleteAsync(contact);
|
||
|
||
// Выборка всех записей
|
||
var contacts = await _database.Table<Contact>().ToListAsync();
|
||
|
||
// SQL-запрос
|
||
var result = await _database.QueryAsync<Contact>(
|
||
"SELECT * FROM Contact WHERE Name LIKE ?", "%search%");
|
||
```
|
||
|
||
#### 2.4.2 Preferences API
|
||
|
||
Для хранения пользовательских настроек (выбранная тема) используется `Microsoft.Maui.Storage.Preferences`.
|
||
|
||
**Особенности Preferences:**
|
||
- Простое API для хранения пар «ключ-значение»
|
||
- Автоматическая сериализация базовых типов
|
||
- Данные хранятся в platform-specific location
|
||
- Поддержка шифрования на мобильных платформах
|
||
|
||
```csharp
|
||
// Сохранение настройки
|
||
await Preferences.SetAsync(ThemePreferenceKey, (int)theme);
|
||
|
||
// Загрузка настройки
|
||
var savedTheme = Preferences.Get(ThemePreferenceKey, -1);
|
||
|
||
// Удаление настройки
|
||
Preferences.Remove(ThemePreferenceKey);
|
||
```
|
||
|
||
#### 2.4.3 Сравнение способов хранения данных
|
||
|
||
| Способ | Объём данных | Скорость | Сложность | Применение |
|
||
|--------|--------------|----------|-----------|------------|
|
||
| **Preferences** | < 1 КБ | Быстро | Минимальная | Настройки, темы |
|
||
| **SQLite** | До ГБ | Средняя | Средняя | Структурированные данные |
|
||
| **File System** | Неограничено | Варьируется | Высокая | Файлы, кэш, экспорт |
|
||
|
||
### 2.5 Используемые NuGet-пакеты
|
||
|
||
| Пакет | Версия | Назначение |
|
||
|-------|--------|------------|
|
||
| `sqlite-net-pcl` | 1.9.172 | SQLite ORM для C# |
|
||
| `ClosedXML` | 0.104.2 | Экспорт данных в Excel (XLSX) |
|
||
| `CommunityToolkit.Maui` | 11.2.0 | Расширения MAUI (Notifications, Behaviors) |
|
||
| `Microsoft.Extensions` | * | Dependency Injection и логирование |
|
||
| `Microsoft.Extensions.Logging.Debug` | * | Логирование в Output Window |
|
||
|
||
**Подробное описание пакетов:**
|
||
|
||
**sqlite-net-pcl:**
|
||
- Кроссплатформенная ORM для SQLite
|
||
- Атрибуты для определения схемы БД ([PrimaryKey], [AutoIncrement])
|
||
- Асинхронные методы для всех операций
|
||
- Минимальный API-поверхность
|
||
|
||
**ClosedXML:**
|
||
- Обёртка над Open XML SDK для работы с Excel
|
||
- Создание и чтение файлов XLSX
|
||
- Форматирование ячеек, добавление формул
|
||
- Поддержка .NET Standard 2.0+
|
||
|
||
**CommunityToolkit.Maui:**
|
||
- Расширения для MAUI от сообщества
|
||
- Behaviors, Converters, Effects
|
||
- Popup, Snackbar, Permissions
|
||
- Различные утилиты для повседневных задач
|
||
|
||
### 2.6 Dependency Injection (DI) в .NET MAUI
|
||
|
||
**Внедрение зависимостей** — паттерн проектирования, при котором зависимости передаются объекту извне, вместо того чтобы создаваться внутри.
|
||
|
||
**Зачем нужен DI:**
|
||
1. **Слабая связанность** — классы не зависят от конкретных реализаций
|
||
2. **Тестируемость** — легко подменить зависимости на моки
|
||
3. **Управление жизненным циклом** — .NET сам создаёт и уничтожает объекты
|
||
4. **Singletons** — один экземпляр на всё приложение
|
||
|
||
**Регистрация сервисов в MauiProgram.cs:**
|
||
```csharp
|
||
public static MauiApp CreateMauiApp()
|
||
{
|
||
var builder = MauiApp.CreateBuilder();
|
||
|
||
builder.Services.AddSingleton<DatabaseService>(); // Один экземпляр
|
||
builder.Services.AddSingleton<ThemeService>();
|
||
builder.Services.AddSingleton<ContactService>();
|
||
|
||
return builder.Build();
|
||
}
|
||
```
|
||
|
||
**Жизненный цикл сервисов:**
|
||
|
||
| Метод | Описание |
|
||
|-------|----------|
|
||
| `AddSingleton` | Один экземпляр на всё время жизни приложения |
|
||
| `AddScoped` | Один экземпляр на окно/страницу |
|
||
| `AddTransient` | Новый экземпляр при каждом запросе |
|
||
| `AddTransient<T, TImpl>` | Привязка интерфейса к реализации |
|
||
|
||
**Получение сервисов:**
|
||
```csharp
|
||
// В code-behind страницы
|
||
var service = IPlatformApplication.Current!
|
||
.Services.GetService<ContactService>();
|
||
|
||
// Или через конструктор (рекомендуется)
|
||
public class MyPage : ContentPage
|
||
{
|
||
private readonly ContactService _contactService;
|
||
|
||
public MyPage(ContactService contactService)
|
||
{
|
||
_contactService = contactService;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2.7 Система тем оформления
|
||
|
||
Приложение поддерживает динамическую смену тем:
|
||
- **Зелёная тема** (Green) — Primary: `#4CAF50`, светлый и чистый дизайн
|
||
- **Синяя тема** (Blue) — Primary: `#2196F3`, профессиональный вид
|
||
- **Тёмная тема** (Dark) — Primary: `#BB86FC`, Background: `#121212`
|
||
|
||
**Реализация тем в MAUI:**
|
||
|
||
Темы реализованы через динамическую загрузку ResourceDictionary:
|
||
```csharp
|
||
var themeDict = new ResourceDictionary
|
||
{
|
||
Source = new Uri("resource://Phonebook.Resources.Raw.Themes.GreenTheme.xaml")
|
||
};
|
||
|
||
app.Resources.MergedDictionaries.Clear();
|
||
app.Resources.MergedDictionaries.Add(themeDict);
|
||
```
|
||
|
||
**AppThemeBinding** позволяет автоматически адаптировать цвета:
|
||
```xml
|
||
<Button BackgroundColor="{AppThemeBinding Light=#4CAF50, Dark=#333}"
|
||
TextColor="{AppThemeBinding Light=White, Dark=White}"/>
|
||
```
|
||
|
||
### 2.8 Навигация в MAUI
|
||
|
||
В приложении используется **иерархическая навигация** на основе `NavigationPage`:
|
||
|
||
```csharp
|
||
// Переход к новой странице
|
||
await Navigation.PushAsync(new ContactDetailPage(contact));
|
||
|
||
// Возврат на предыдущую страницу
|
||
await Navigation.PopAsync();
|
||
|
||
// Замена текущей страницы
|
||
await Navigation.PushModalAsync(new Page()); // Модальная навигация
|
||
```
|
||
|
||
**Типы навигации MAUI:**
|
||
|
||
| Тип | Описание | Пример |
|
||
|-----|----------|--------|
|
||
| Иерархическая | Стек страниц | `Navigation.Push/Pop` |
|
||
| Модальная | Поверх текущей страницы | `Navigation.PushModalAsync` |
|
||
| Shell | На основе оболочки с меню | FlyoutItem, Tab |
|
||
|
||
**Shell-навигация (AppShell):**
|
||
```xml
|
||
<Shell>
|
||
<FlyoutItem Title="Контакты">
|
||
<ShellContent ContentTemplate="{DataTemplate local:MainPage}" />
|
||
</FlyoutItem>
|
||
<FlyoutItem Title="Настройки">
|
||
<ShellContent ContentTemplate="{DataTemplate local:SettingsPage}" />
|
||
</FlyoutItem>
|
||
</Shell>
|
||
```
|
||
|
||
### 2.9 Обработка событий и делегаты
|
||
|
||
В приложении используется паттерн **Observer (Наблюдатель)** для оповещения об изменениях:
|
||
|
||
```csharp
|
||
// Определение события в сервисе
|
||
public event Action? OnContactsUpdated;
|
||
|
||
// Вызов события после изменения данных
|
||
public async Task SaveContact(Contact contact)
|
||
{
|
||
await _databaseService.SaveContactAsync(contact);
|
||
OnContactsUpdated?.Invoke(); // Оповещаем подписчиков
|
||
}
|
||
|
||
// Подписка на событие в UI
|
||
public MainPage()
|
||
{
|
||
_contactService.OnContactsUpdated += LoadContacts;
|
||
}
|
||
```
|
||
|
||
**Преимущества паттерна Observer:**
|
||
- Разделение компонентов (издатель и подписчик)
|
||
- Loose coupling — компоненты не зависят друг от друга
|
||
- Уведомление всех заинтересованных сторон автоматически
|
||
|
||
### 2.10 Работа с платформенными API
|
||
|
||
MAUI предоставляет кроссплатформенный доступ к нативным функциям через `Microsoft.Maui.Essentials`:
|
||
|
||
| Пространство имён | Функциональность |
|
||
|-------------------|------------------|
|
||
| `Microsoft.Maui.ApplicationModel.Communication` | Контакты, звонки, Email, SMS |
|
||
| `Microsoft.Maui.Media` | Камера, галерея, файлы |
|
||
| `Microsoft.Maui.Storage` | Файловая система, обмен данными |
|
||
| `Microsoft.Maui.Devices.Sensors` | GPS, акселерометр, компас |
|
||
| `Microsoft.Maui.ApplicationModel` | Разрешения, информация об устройстве |
|
||
|
||
**Пример работы с контактами:**
|
||
```csharp
|
||
var deviceContacts = await ContactPicker.PickContactAsync();
|
||
var contact = new Contact
|
||
{
|
||
Name = deviceContacts.DisplayName ?? "",
|
||
PhoneNumber = deviceContacts.Phones?.FirstOrDefault()?.Number ?? ""
|
||
};
|
||
```
|
||
|
||
**Пример инициации звонка:**
|
||
```csharp
|
||
PhoneDialer.Open(phoneNumber);
|
||
```
|
||
|
||
### 2.11 Конвертеры значений (Value Converters)
|
||
|
||
Конвертеры позволяют преобразовывать данные при привязке:
|
||
|
||
```csharp
|
||
// Реализация IValueConverter
|
||
public class Base64ToPhoto : IValueConverter
|
||
{
|
||
public object Convert(object value, Type targetType,
|
||
object parameter, CultureInfo culture)
|
||
{
|
||
var base64 = value as string;
|
||
if (string.IsNullOrWhiteSpace(base64))
|
||
return ImageSource.FromFile("default_contact.png");
|
||
|
||
byte[] bytes = Convert.FromBase64String(base64);
|
||
return ImageSource.FromStream(() => new MemoryStream(bytes));
|
||
}
|
||
|
||
public object ConvertBack(object value, Type targetType,
|
||
object parameter, CultureInfo culture)
|
||
{
|
||
throw new NotImplementedException();
|
||
}
|
||
}
|
||
|
||
// Использование в XAML
|
||
<ContentPage.Resources>
|
||
<converters:Base64ToPhoto x:Key="Base64ToPhoto"/>
|
||
</ContentPage.Resources>
|
||
|
||
<Image Source="{Binding PhotoBase64,
|
||
Converter={StaticResource Base64ToPhoto}}"/>
|
||
```
|
||
|
||
---
|
||
|
||
## 3. ПРАКТИЧЕСКАЯ ЧАСТЬ
|
||
|
||
### 3.1 Структура проекта
|
||
|
||
```
|
||
Phonebook/
|
||
├── Phonebook.sln
|
||
├── Phonebook.csproj
|
||
├── App.xaml / App.xaml.cs
|
||
├── MauiProgram.cs
|
||
├── AppShell.xaml / AppShell.xaml.cs
|
||
├── MainPage.xaml / MainPage.xaml.cs
|
||
├── ContactDetailPage.xaml / .cs
|
||
├── SettingsPage.xaml / .xaml.cs
|
||
├── Models/
|
||
│ └── Contact.cs
|
||
├── Services/
|
||
│ ├── DatabaseService.cs
|
||
│ ├── ContactService.cs
|
||
│ └── ThemeService.cs
|
||
├── Converters/
|
||
│ ├── Base64ToPhoto.cs
|
||
│ └── NullToBoolConverter.cs
|
||
├── Utils/
|
||
│ ├── ImageHelper.cs
|
||
│ └── PermissionHelper.cs
|
||
└── Resources/
|
||
├── Styles/
|
||
│ ├── Colors.xaml
|
||
│ └── Styles.xaml
|
||
└── Raw/Themes/
|
||
├── GreenTheme.xaml
|
||
├── BlueTheme.xaml
|
||
└── DarkTheme.xaml
|
||
```
|
||
|
||
**[Скриншот: Структура проекта в Visual Studio]**
|
||
|
||
### 3.2 Модель данных
|
||
|
||
Файл: `Models/Contact.cs`
|
||
|
||
```csharp
|
||
using SQLite;
|
||
|
||
namespace Phonebook.Models
|
||
{
|
||
public class Contact
|
||
{
|
||
[PrimaryKey, AutoIncrement]
|
||
public int Id { get; set; }
|
||
public string Name { get; set; }
|
||
public string PhoneNumber { get; set; }
|
||
public string Email { get; set; }
|
||
public string Address { get; set; }
|
||
public string Description { get; set; }
|
||
public string PhotoPath { get; set; }
|
||
public string PhotoBase64 { get; set; }
|
||
}
|
||
}
|
||
```
|
||
|
||
**Описание полей:**
|
||
- `Id` — первичный ключ с автоинкрементом
|
||
- `Name` — имя контакта
|
||
- `PhoneNumber` — номер телефона
|
||
- `Email` — адрес электронной почты
|
||
- `Address` — физический адрес
|
||
- `Description` — дополнительное описание
|
||
- `PhotoPath` — путь к файлу изображения (устаревший)
|
||
- `PhotoBase64` — изображение в формате Base64
|
||
|
||
**[Скриншот: Диаграмма модели Contact]**
|
||
|
||
### 3.3 Сервис базы данных
|
||
|
||
Файл: `Services/DatabaseService.cs`
|
||
|
||
```csharp
|
||
using SQLite;
|
||
using Phonebook.Models;
|
||
|
||
namespace Phonebook.Services
|
||
{
|
||
public class DatabaseService
|
||
{
|
||
private readonly SQLiteAsyncConnection _database;
|
||
|
||
public DatabaseService()
|
||
{
|
||
var dbPath = Path.Combine(
|
||
FileSystem.AppDataDirectory,
|
||
"contacts.db3"
|
||
);
|
||
_database = new SQLiteAsyncConnection(dbPath);
|
||
}
|
||
|
||
public Task Initialize()
|
||
{
|
||
return _database.CreateTableAsync<Contact>();
|
||
}
|
||
|
||
public Task<List<Contact>> GetContactsAsync()
|
||
{
|
||
return _database.Table<Contact>().ToListAsync();
|
||
}
|
||
|
||
public Task<List<Contact>> SearchContactsAsync(string searchTerm)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(searchTerm))
|
||
return GetContactsAsync();
|
||
|
||
var allContacts = _database.Table<Contact>().ToListAsync();
|
||
var compareInfo = CultureInfo.CurrentCulture.CompareInfo;
|
||
|
||
return allContacts.ContinueWith(t =>
|
||
t.Result.Where(c =>
|
||
compareInfo.IndexOf(c.Name ?? "", searchTerm,
|
||
CompareOptions.IgnoreCase) >= 0 ||
|
||
compareInfo.IndexOf(c.PhoneNumber ?? "", searchTerm,
|
||
CompareOptions.IgnoreCase) >= 0 ||
|
||
compareInfo.IndexOf(c.Email ?? "", searchTerm,
|
||
CompareOptions.IgnoreCase) >= 0
|
||
).DistinctBy(c => c.Id).ToList()
|
||
);
|
||
}
|
||
|
||
public Task<int> SaveContactAsync(Contact contact)
|
||
{
|
||
if (contact.Id != 0)
|
||
return _database.UpdateAsync(contact);
|
||
return _database.InsertAsync(contact);
|
||
}
|
||
|
||
public Task<int> DeleteContactAsync(Contact contact)
|
||
{
|
||
return _database.DeleteAsync(contact);
|
||
}
|
||
|
||
public Task<int> ClearAll()
|
||
{
|
||
return _database.DeleteAllAsync<Contact>();
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**Описание методов:**
|
||
|
||
| Метод | Описание | Параметры | Возвращаемое значение |
|
||
|-------|----------|-----------|----------------------|
|
||
| `Initialize()` | Создаёт таблицу контактов в БД | — | `Task` |
|
||
| `GetContactsAsync()` | Получает все контакты | — | `Task<List<Contact>>` |
|
||
| `SearchContactsAsync()` | Поиск с учётом культуры | `searchTerm: string` | `Task<List<Contact>>` |
|
||
| `SaveContactAsync()` | Сохраняет/обновляет контакт | `contact: Contact` | `Task<int>` (количество затронутых строк) |
|
||
| `DeleteContactAsync()` | Удаляет контакт | `contact: Contact` | `Task<int>` |
|
||
| `ClearAll()` | Удаляет все контакты | — | `Task<int>` |
|
||
|
||
### 3.4 Сервис контактов (бизнес-логика)
|
||
|
||
Файл: `Services/ContactService.cs`
|
||
|
||
```csharp
|
||
using Microsoft.Maui.ApplicationModel.Communication;
|
||
using Microsoft.Maui.Storage;
|
||
using ClosedXML.Excel;
|
||
using Contact = Phonebook.Models.Contact;
|
||
|
||
namespace Phonebook.Services
|
||
{
|
||
public class ContactService
|
||
{
|
||
private readonly DatabaseService _databaseService;
|
||
|
||
public ContactService(DatabaseService databaseService)
|
||
{
|
||
_databaseService = databaseService;
|
||
}
|
||
|
||
public async Task<List<Contact>> GetAllContacts()
|
||
{
|
||
var contacts = await _databaseService.GetContactsAsync();
|
||
return contacts.OrderBy(c => c.Name).ToList();
|
||
}
|
||
|
||
public async Task<List<Contact>> SearchContacts(string searchTerm)
|
||
{
|
||
var contacts = await _databaseService.SearchContactsAsync(searchTerm);
|
||
return contacts.OrderBy(c => c.Name).ToList();
|
||
}
|
||
|
||
public async Task SaveContact(Contact contact)
|
||
{
|
||
await _databaseService.SaveContactAsync(contact);
|
||
OnContactsUpdated?.Invoke();
|
||
}
|
||
|
||
public async Task DeleteContact(Contact contact)
|
||
{
|
||
await _databaseService.DeleteContactAsync(contact);
|
||
OnContactsUpdated?.Invoke();
|
||
}
|
||
|
||
public async Task ImportFromLocalContact()
|
||
{
|
||
var hasPermission = await PermissionHelper.RequestContactsPermission();
|
||
if (!hasPermission) return;
|
||
|
||
var deviceContacts = await ContactPicker.PickContactAsync();
|
||
if (deviceContacts == null) return;
|
||
|
||
var contact = new Contact
|
||
{
|
||
Name = deviceContacts.DisplayName ?? "",
|
||
PhoneNumber = deviceContacts.Phones?.FirstOrDefault()?.Number ?? "",
|
||
Email = deviceContacts.Emails?.FirstOrDefault()?.EmailAddress ?? ""
|
||
};
|
||
|
||
await SaveContact(contact);
|
||
}
|
||
|
||
public async Task ExportToXlsx()
|
||
{
|
||
var contacts = await GetAllContacts();
|
||
|
||
using var workbook = new XLWorkbook();
|
||
var worksheet = workbook.Worksheets.Add("Contacts");
|
||
|
||
worksheet.Cell(1, 1).Value = "Id";
|
||
worksheet.Cell(1, 2).Value = "Имя";
|
||
worksheet.Cell(1, 3).Value = "Телефон";
|
||
worksheet.Cell(1, 4).Value = "Email";
|
||
worksheet.Cell(1, 5).Value = "Адрес";
|
||
worksheet.Cell(1, 6).Value = "Описание";
|
||
|
||
for (int i = 0; i < contacts.Count; i++)
|
||
{
|
||
var c = contacts[i];
|
||
worksheet.Cell(i + 2, 1).Value = c.Id;
|
||
worksheet.Cell(i + 2, 2).Value = c.Name;
|
||
worksheet.Cell(i + 2, 3).Value = c.PhoneNumber;
|
||
worksheet.Cell(i + 2, 4).Value = c.Email;
|
||
worksheet.Cell(i + 2, 5).Value = c.Address;
|
||
worksheet.Cell(i + 2, 6).Value = c.Description;
|
||
}
|
||
|
||
worksheet.Columns().AdjustToContents();
|
||
|
||
var fileName = $"contacts_{DateTime.Now:yyyyMMdd_HHmmss}.xlsx";
|
||
var localPath = Path.Combine(
|
||
FileSystem.CacheDirectory,
|
||
fileName
|
||
);
|
||
workbook.SaveAs(localPath);
|
||
|
||
await Share.Default.RequestAsync(new ShareFileRequest
|
||
{
|
||
Title = "Сохранить контакты",
|
||
File = new ShareFile(localPath)
|
||
});
|
||
}
|
||
|
||
public async Task ClearAllContacts()
|
||
{
|
||
await _databaseService.ClearAll();
|
||
OnContactsUpdated?.Invoke();
|
||
}
|
||
|
||
public event Action? OnContactsUpdated;
|
||
}
|
||
}
|
||
```
|
||
|
||
**Описание методов:**
|
||
|
||
| Метод | Описание |
|
||
|-------|----------|
|
||
| `GetAllContacts()` | Получает все контакты, отсортированные по имени |
|
||
| `SearchContacts()` | Осуществляет поиск по контактам |
|
||
| `SaveContact()` | Сохраняет контакт и вызывает событие обновления |
|
||
| `DeleteContact()` | Удаляет контакт и вызывает событие обновления |
|
||
| `ImportFromLocalContact()` | Импортирует контакт из телефонной книги устройства |
|
||
| `ExportToXlsx()` | Экспортирует все контакты в Excel-файл и открывает диалог сохранения |
|
||
| `ClearAllContacts()` | Удаляет все контакты |
|
||
|
||
**[Скриншот: Процесс экспорта контактов]**
|
||
|
||
### 3.5 Сервис тем оформления
|
||
|
||
Файл: `Services/ThemeService.cs`
|
||
|
||
```csharp
|
||
using Microsoft.Maui.Storage;
|
||
|
||
namespace Phonebook.Services
|
||
{
|
||
public enum AppThemeType { Green, Blue, Dark, Light }
|
||
|
||
public class ThemeService
|
||
{
|
||
private const string ThemePreferenceKey = "user_theme_preference";
|
||
|
||
public async Task ApplyTheme(AppThemeType theme)
|
||
{
|
||
var app = Application.Current;
|
||
if (app == null) return;
|
||
|
||
var themeName = theme switch
|
||
{
|
||
AppThemeType.Green => "GreenTheme",
|
||
AppThemeType.Blue => "BlueTheme",
|
||
AppThemeType.Dark => "DarkTheme",
|
||
_ => "GreenTheme"
|
||
};
|
||
|
||
var themeDict = new ResourceDictionary
|
||
{
|
||
Source = new Uri($"resource://Phonebook.Resources.Raw.Themes.{themeName}.xaml",
|
||
UriKind.Relative)
|
||
};
|
||
|
||
app.Resources.MergedDictionaries.Clear();
|
||
app.Resources.MergedDictionaries.Add(themeDict);
|
||
|
||
await Preferences.SetAsync(ThemePreferenceKey, (int)theme);
|
||
}
|
||
|
||
public async Task LoadSavedTheme()
|
||
{
|
||
var savedTheme = Preferences.Get(ThemePreferenceKey, -1);
|
||
if (savedTheme >= 0)
|
||
{
|
||
await ApplyTheme((AppThemeType)savedTheme);
|
||
}
|
||
else
|
||
{
|
||
await ApplyTheme(AppThemeType.Green);
|
||
}
|
||
}
|
||
|
||
public AppThemeType GetCurrentTheme()
|
||
{
|
||
var savedTheme = Preferences.Get(ThemePreferenceKey, -1);
|
||
return savedTheme >= 0 ? (AppThemeType)savedTheme : AppThemeType.Green;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**Описание методов:**
|
||
|
||
| Метод | Описание |
|
||
|-------|----------|
|
||
| `ApplyTheme()` | Динамически загружает и применяет тему из XAML-ресурсов |
|
||
| `LoadSavedTheme()` | Загружает сохранённую тему из Preferences при запуске |
|
||
| `GetCurrentTheme()` | Возвращает текущую активную тему |
|
||
|
||
**[Скриншот: Страница настроек с выбором тем]**
|
||
|
||
### 3.6 Главная страница
|
||
|
||
Файл: `MainPage.xaml`
|
||
|
||
```xml
|
||
<?xml version="1.0" encoding="utf-8" ?>
|
||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||
xmlns:local="clr-namespace:Phonebook"
|
||
xmlns:converters="clr-namespace:Phonebook.Converters"
|
||
Title="Телефоный справочник">
|
||
|
||
<ContentPage.Resources>
|
||
<converters:Base64ToPhoto x:Key="Base64ToPhoto"/>
|
||
<converters:NullToBoolConverter x:Key="NullToBool"/>
|
||
</ContentPage.Resources>
|
||
|
||
<Grid>
|
||
<Grid.RowDefinitions>
|
||
<RowDefinition Height="Auto"/>
|
||
<RowDefinition Height="*"/>
|
||
</Grid.RowDefinitions>
|
||
|
||
<SearchBar x:Name="searchBar"
|
||
Grid.Row="0"
|
||
Placeholder="Поиск контактов..."
|
||
SearchButtonPressed="SearchBar_SearchButtonPressed"
|
||
TextChanged="SearchBar_TextChanged"/>
|
||
|
||
<CollectionView x:Name="contactsCollection"
|
||
Grid.Row="1"
|
||
SelectionMode="Single"
|
||
SelectionChanged="ContactsCollection_SelectionChanged">
|
||
<CollectionView.ItemTemplate>
|
||
<DataTemplate>
|
||
<Frame Margin="10" Padding="10" CornerRadius="10">
|
||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||
<Image Grid.Column="0"
|
||
Source="{Binding PhotoBase64,
|
||
Converter={StaticResource Base64ToPhoto}}"
|
||
WidthRequest="50" HeightRequest="50"
|
||
Aspect="AspectFill"
|
||
Margin="0,0,10,0"/>
|
||
|
||
<StackLayout Grid.Column="1" VerticalOptions="Center">
|
||
<Label Text="{Binding Name}" FontSize="18"
|
||
FontAttributes="Bold"/>
|
||
<Label Text="{Binding PhoneNumber}" FontSize="14"
|
||
TextColor="Gray"/>
|
||
<Label Text="{Binding Email}" FontSize="12"
|
||
TextColor="Gray" IsVisible="{Binding Email}"/>
|
||
</StackLayout>
|
||
|
||
<ImageButton Grid.Column="2"
|
||
Source="call_icon.png"
|
||
Clicked="CallButton_Clicked"
|
||
BackgroundColor="Transparent"
|
||
WidthRequest="40" HeightRequest="40"/>
|
||
</Grid>
|
||
</Frame>
|
||
</DataTemplate>
|
||
</CollectionView.ItemTemplate>
|
||
</CollectionView>
|
||
|
||
<ImageButton x:Name="fabAdd"
|
||
Grid.Row="1"
|
||
Source="add_icon.png"
|
||
Clicked="FabAdd_Clicked"
|
||
HorizontalOptions="End"
|
||
VerticalOptions="End"
|
||
Margin="0,0,20,20"
|
||
WidthRequest="60" HeightRequest="60"
|
||
BackgroundColor="{AppThemeBinding Light=#4CAF50, Dark=#4CAF50}"
|
||
CornerRadius="30"/>
|
||
</Grid>
|
||
</ContentPage>
|
||
```
|
||
|
||
**Код-behind:** `MainPage.xaml.cs`
|
||
|
||
```csharp
|
||
namespace Phonebook;
|
||
|
||
public partial class MainPage : ContentPage
|
||
{
|
||
private readonly ContactService _contactService;
|
||
|
||
public MainPage()
|
||
{
|
||
InitializeComponent();
|
||
_contactService = IPlatformApplication.Current!
|
||
.Services.GetService<ContactService>()!;
|
||
|
||
_contactService.OnContactsUpdated += LoadContacts;
|
||
LoadContacts();
|
||
}
|
||
|
||
private async void LoadContacts()
|
||
{
|
||
var contacts = await _contactService.GetAllContacts();
|
||
contactsCollection.ItemsSource = contacts;
|
||
}
|
||
|
||
private async void SearchBar_TextChanged(object sender, TextChangedEventArgs e)
|
||
{
|
||
var searchTerm = e.NewTextValue;
|
||
if (string.IsNullOrWhiteSpace(searchTerm))
|
||
{
|
||
LoadContacts();
|
||
return;
|
||
}
|
||
|
||
var contacts = await _contactService.SearchContacts(searchTerm);
|
||
contactsCollection.ItemsSource = contacts;
|
||
}
|
||
|
||
private void ContactsCollection_SelectionChanged(object sender,
|
||
SelectionChangedEventArgs e)
|
||
{
|
||
if (e.CurrentSelection.FirstOrDefault() is Contact contact)
|
||
{
|
||
Navigation.PushAsync(new ContactDetailPage(contact));
|
||
contactsCollection.SelectedItem = null;
|
||
}
|
||
}
|
||
|
||
private void FabAdd_Clicked(object sender, EventArgs e)
|
||
{
|
||
Navigation.PushAsync(new ContactDetailPage(null));
|
||
}
|
||
|
||
private async void CallButton_Clicked(object sender, EventArgs e)
|
||
{
|
||
if (sender is ImageButton button && button.BindingContext is Contact contact)
|
||
{
|
||
try
|
||
{
|
||
PhoneDialer.Open(contact.PhoneNumber);
|
||
}
|
||
catch
|
||
{
|
||
await DisplayAlert("Ошибка",
|
||
"Невозможно совершить звонок", "OK");
|
||
}
|
||
}
|
||
}
|
||
|
||
protected override void OnAppearing()
|
||
{
|
||
base.OnAppearing();
|
||
LoadContacts();
|
||
}
|
||
}
|
||
```
|
||
|
||
**Описание обработчиков событий:**
|
||
|
||
| Метод | Описание |
|
||
|-------|----------|
|
||
| `LoadContacts()` | Загружает список контактов из сервиса и привязывает к CollectionView |
|
||
| `SearchBar_TextChanged()` | Выполняет поиск при изменении текста в строке поиска |
|
||
| `ContactsCollection_SelectionChanged()` | Открывает страницу редактирования при выборе контакта |
|
||
| `FabAdd_Clicked()` | Открывает страницу создания нового контакта |
|
||
| `CallButton_Clicked()` | Инициирует звонок по номеру телефона контакта |
|
||
| `OnAppearing()` | Обновляет список при возвращении на страницу |
|
||
|
||
**[Скриншот: Главная страница приложения со списком контактов]**
|
||
|
||
### 3.7 Страница редактирования контакта
|
||
|
||
Файл: `ContactDetailPage.xaml`
|
||
|
||
```xml
|
||
<?xml version="1.0" encoding="utf-8" ?>
|
||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||
xmlns:converters="clr-namespace:Phonebook.Converters"
|
||
Title="{Binding Name, StringFormat='Контакт: {0}'}">
|
||
|
||
<ContentPage.Resources>
|
||
<converters:Base64ToPhoto x:Key="Base64ToPhoto"/>
|
||
<converters:NullToBoolConverter x:Key="NullToBool"/>
|
||
</ContentPage.Resources>
|
||
|
||
<ScrollView>
|
||
<StackLayout Padding="20" Spacing="15">
|
||
<Frame HorizontalOptions="Center"
|
||
WidthRequest="150" HeightRequest="150"
|
||
CornerRadius="75" Padding="0" Margin="0,0,0,20">
|
||
<Image x:Name="contactPhoto"
|
||
Source="{Binding PhotoBase64,
|
||
Converter={StaticResource Base64ToPhoto}}"
|
||
Aspect="AspectFill"/>
|
||
<Frame.GestureRecognizers>
|
||
<TapGestureRecognizer Tapped="PhotoTapped"/>
|
||
</Frame.GestureRecognizers>
|
||
</Frame>
|
||
|
||
<Entry x:Name="nameEntry"
|
||
Placeholder="Имя"
|
||
Text="{Binding Name}"/>
|
||
|
||
<Entry x:Name="phoneEntry"
|
||
Placeholder="Телефон"
|
||
Text="{Binding PhoneNumber}"
|
||
Keyboard="Telephone"/>
|
||
|
||
<Entry x:Name="emailEntry"
|
||
Placeholder="Email"
|
||
Text="{Binding Email}"
|
||
Keyboard="Email"/>
|
||
|
||
<Entry x:Name="addressEntry"
|
||
Placeholder="Адрес"
|
||
Text="{Binding Address}"/>
|
||
|
||
<Editor x:Name="descriptionEditor"
|
||
Placeholder="Описание"
|
||
Text="{Binding Description}"
|
||
HeightRequest="100"/>
|
||
|
||
<Button x:Name="saveButton"
|
||
Text="Сохранить"
|
||
Clicked="SaveButton_Clicked"
|
||
Margin="0,20,0,0"/>
|
||
|
||
<Button x:Name="deleteButton"
|
||
Text="Удалить"
|
||
Clicked="DeleteButton_Clicked"
|
||
BackgroundColor="Red"
|
||
IsVisible="{Binding Id, Converter={StaticResource NullToBool}}"/>
|
||
</StackLayout>
|
||
</ScrollView>
|
||
</ContentPage>
|
||
```
|
||
|
||
**Код-behind:** `ContactDetailPage.xaml.cs`
|
||
|
||
```csharp
|
||
public partial class ContactDetailPage : ContentPage
|
||
{
|
||
private readonly ContactService _contactService;
|
||
private Contact? _contact;
|
||
private string? _photoBase64;
|
||
|
||
public ContactDetailPage(Contact? contact)
|
||
{
|
||
InitializeComponent();
|
||
_contactService = IPlatformApplication.Current!
|
||
.Services.GetService<ContactService>()!;
|
||
|
||
_contact = contact ?? new Contact();
|
||
_photoBase64 = _contact.PhotoBase64;
|
||
BindingContext = _contact;
|
||
}
|
||
|
||
private async void PhotoTapped(object sender, TappedEventArgs e)
|
||
{
|
||
var result = await FilePicker.PickAsync(new PickOptions
|
||
{
|
||
PickerTitle = "Выберите фото",
|
||
FileTypes = FilePickerFileType.Images
|
||
});
|
||
|
||
if (result != null)
|
||
{
|
||
var stream = await result.OpenReadAsync();
|
||
_photoBase64 = ImageHelper.StreamToBase64(stream);
|
||
contactPhoto.Source = ImageHelper.Base64ToStreamImageSource(_photoBase64);
|
||
}
|
||
}
|
||
|
||
private async void SaveButton_Clicked(object sender, EventArgs e)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(nameEntry.Text))
|
||
{
|
||
await DisplayAlert("Ошибка", "Введите имя контакта", "OK");
|
||
return;
|
||
}
|
||
|
||
_contact!.Name = nameEntry.Text;
|
||
_contact.PhoneNumber = phoneEntry.Text ?? "";
|
||
_contact.Email = emailEntry.Text ?? "";
|
||
_contact.Address = addressEntry.Text ?? "";
|
||
_contact.Description = descriptionEditor.Text ?? "";
|
||
_contact.PhotoBase64 = _photoBase64 ?? "";
|
||
|
||
await _contactService.SaveContact(_contact!);
|
||
await Navigation.PopAsync();
|
||
}
|
||
|
||
private async void DeleteButton_Clicked(object sender, EventArgs e)
|
||
{
|
||
var confirm = await DisplayAlert("Подтверждение",
|
||
"Вы уверены, что хотите удалить контакт?", "Да", "Нет");
|
||
|
||
if (confirm && _contact != null)
|
||
{
|
||
await _contactService.DeleteContact(_contact);
|
||
await Navigation.PopAsync();
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**Описание обработчиков:**
|
||
|
||
| Метод | Описание |
|
||
|-------|----------|
|
||
| `PhotoTapped()` | Позволяет выбрать фото из галереи устройства и конвертирует в Base64 |
|
||
| `SaveButton_Clicked()` | Валидирует данные, сохраняет контакт через сервис |
|
||
| `DeleteButton_Clicked()` | Запрашивает подтверждение и удаляет контакт |
|
||
|
||
**[Скриншот: Страница редактирования контакта]**
|
||
|
||
### 3.8 Вспомогательные утилиты
|
||
|
||
#### ImageHelper.cs
|
||
|
||
```csharp
|
||
public static class ImageHelper
|
||
{
|
||
public static string StreamToBase64(Stream stream)
|
||
{
|
||
using var memoryStream = new MemoryStream();
|
||
stream.CopyTo(memoryStream);
|
||
return Convert.ToBase64String(memoryStream.ToArray());
|
||
}
|
||
|
||
public static ImageSource Base64ToStreamImageSource(string base64String)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(base64String))
|
||
return ImageSource.FromFile("default_contact.png");
|
||
|
||
var cleanBase64 = base64String.Contains(',')
|
||
? base64String.Split(',')[1]
|
||
: base64String;
|
||
|
||
byte[] imageBytes = Convert.FromBase64String(cleanBase64);
|
||
return ImageSource.FromStream(() => new MemoryStream(imageBytes));
|
||
}
|
||
}
|
||
```
|
||
|
||
**Функции:**
|
||
|
||
| Функция | Описание |
|
||
|---------|----------|
|
||
| `StreamToBase64()` | Конвертирует поток изображения в строку Base64 |
|
||
| `Base64ToStreamImageSource()` | Конвертирует Base64 обратно в ImageSource |
|
||
|
||
**[Скриншот: Выбор фотографии из галереи]**
|
||
|
||
### 3.9 Конфигурация приложения
|
||
|
||
Файл: `MauiProgram.cs`
|
||
|
||
```csharp
|
||
public static MauiApp CreateMauiApp()
|
||
{
|
||
var builder = MauiApp.CreateBuilder();
|
||
|
||
builder.UseMauiApp<App>()
|
||
.ConfigureFonts(fonts =>
|
||
{
|
||
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
|
||
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
|
||
})
|
||
.UseMauiCommunityToolkit();
|
||
|
||
builder.Services.AddSingleton<DatabaseService>();
|
||
builder.Services.AddSingleton<ThemeService>();
|
||
builder.Services.AddSingleton<ContactService>();
|
||
|
||
var app = builder.Build();
|
||
|
||
Task.Run(async () =>
|
||
{
|
||
var dbService = app.Services.GetRequiredService<DatabaseService>();
|
||
await dbService.Initialize();
|
||
}).Wait();
|
||
|
||
var themeService = app.Services.GetRequiredService<ThemeService>();
|
||
themeService.LoadSavedTheme().Wait();
|
||
|
||
return app;
|
||
}
|
||
```
|
||
|
||
**[Скриншот: Конфигурация DI-контейнера]**
|
||
|
||
### 3.10 Разрешения Android
|
||
|
||
Файл: `Platforms/Android/AndroidManifest.xml`
|
||
|
||
```xml
|
||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
|
||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||
<uses-permission android:name="android.permission.INTERNET" />
|
||
|
||
<application ...>
|
||
</application>
|
||
</manifest>
|
||
```
|
||
|
||
### 3.11 Ресурсы тем
|
||
|
||
Файл: `Resources/Raw/Themes/GreenTheme.xaml`
|
||
|
||
```xml
|
||
<?xml version="1.0" encoding="utf-8" ?>
|
||
<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
|
||
|
||
<Color x:Key="Primary">#4CAF50</Color>
|
||
<Color x:Key="PrimaryDark">#388E3C</Color>
|
||
<Color x:Key="PrimaryLight">#C8E6C9</Color>
|
||
<Color x:Key="Accent">#8BC34A</Color>
|
||
<Color x:Key="Background">#FFFFFF</Color>
|
||
<Color x:Key="Surface">#FFFFFF</Color>
|
||
<Color x:Key="TextPrimary">#212121</Color>
|
||
<Color x:Key="TextSecondary">#757575</Color>
|
||
|
||
<Style TargetType="Button">
|
||
<Setter Property="BackgroundColor" Value="{DynamicResource Primary}"/>
|
||
<Setter Property="TextColor" Value="White"/>
|
||
</Style>
|
||
|
||
<Style TargetType="NavigationPage">
|
||
<Setter Property="BarBackgroundColor" Value="{DynamicResource Primary}"/>
|
||
<Setter Property="BarTextColor" Value="White"/>
|
||
</Style>
|
||
</ResourceDictionary>
|
||
```
|
||
|
||
---
|
||
|
||
## 4. ЗАКЛЮЧЕНИЕ
|
||
|
||
### 4.1 Результаты выполнения работы
|
||
|
||
В ходе выполнения лабораторной работы было разработано кроссплатформенное мобильное приложение «Телефонный справочник» с использованием .NET MAUI и языка программирования C#.
|
||
|
||
**Достигнутые результаты:**
|
||
|
||
| № | Задача | Статус |
|
||
|---|--------|--------|
|
||
| 1 | Изучение теоретических основ .NET MAUI | ✅ Выполнено |
|
||
| 2 | Проектирование архитектуры приложения | ✅ Выполнено |
|
||
| 3 | Реализация CRUD-операций для контактов | ✅ Выполнено |
|
||
| 4 | Настройка хранения данных с SQLite | ✅ Выполнено |
|
||
| 5 | Реализация системы смены тем оформления | ✅ Выполнено |
|
||
| 6 | Добавление функционала импорта/экспорта | ✅ Выполнено |
|
||
| 7 | Тестирование приложения | ✅ Выполнено |
|
||
|
||
### 4.2 Функциональность приложения
|
||
|
||
Разработанное приложение обеспечивает следующий функционал:
|
||
|
||
- **Создание контактов** — добавление новых записей с фото, именем, телефоном, email, адресом и описанием
|
||
- **Редактирование контактов** — изменение любых полей существующих записей
|
||
- **Удаление контактов** — удаление отдельных записей с подтверждением
|
||
- **Поиск контактов** — мгновенный поиск по всем полям с учётом локализации
|
||
- **Импорт из устройства** — получение контактов из телефонной книги
|
||
- **Экспорт в Excel** — сохранение списка контактов в формате XLSX
|
||
- **Смена темы** — переключение между зелёной, синей и тёмной темами
|
||
- **Быстрый звонок** — инициация звонка одним нажатием
|
||
|
||
### 4.3 Технические особенности
|
||
|
||
Приложение демонстрирует следующие современные подходы к разработке:
|
||
|
||
- **Кроссплатформенность** — единая кодовая база для iOS, Android, Windows, macOS
|
||
- **Архитектурный паттерн** — сервис-ориентированная архитектура с внедрением зависимостей
|
||
- **Асинхронное программирование** — использование async/await для неблокирующих операций
|
||
- **Локальное хранение данных** — SQLite для структурированных данных
|
||
- **Событийная модель** — паттерн Observer для обновления UI при изменении данных
|
||
|
||
### 4.4 Выводы
|
||
|
||
Выбор .NET MAUI и C# для разработки мобильного приложения полностью оправдал себя. Фреймворк обеспечил:
|
||
|
||
1. **Эффективность разработки** — единая кодовая база значительно сократила время разработки
|
||
2. **Качество кода** — C# с его строгой типизацией и LINQ обеспечил чистоту и поддерживаемость кода
|
||
3. **Производительность** — нативная компиляция обеспечила быстрый отклик интерфейса
|
||
4. **Масштабируемость** — архитектура приложения позволяет легко добавлять новый функционал
|
||
|
||
**Цель лабораторной работы достигнута** — создано полнофункциональное кроссплатформенное приложение «Телефонный справочник», готовое к использованию и дальнейшей доработке.
|
||
|
||
---
|
||
|
||
## 5. СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ
|
||
|
||
1. Microsoft Docs. **.NET MAUI Documentation** — https://docs.microsoft.com/dotnet/maui
|
||
2. Microsoft Docs. **.NET 9 Documentation** — https://docs.microsoft.com/dotnet
|
||
3. C# Documentation. **C# 12 Guide** — https://docs.microsoft.com/dotnet/csharp
|
||
4. SQLite-net. **SQLite for .NET** — https://github.com/praeclarum/sqlite-net
|
||
5. ClosedXML. **Excel library for .NET** — https://github.com/ClosedXML/ClosedXML
|
||
6. Microsoft. **CommunityToolkit.Maui** — https://github.com/CommunityToolkit/Maui
|
||
7. Microsoft Learn. **Build mobile and desktop apps with .NET MAUI** — https://learn.microsoft.com/training/paths/build-apps-with-dotnet-maui/
|
||
8. Петцольд Ч. **Programming Windows, 6th Edition** — Microsoft Press, 2012
|
||
9. Troelsen A., Japikse P. **Pro C# 10 with .NET 6** — Apress, 2022
|
||
|
||
---
|
||
|
||
**Приложение А. Скриншоты**
|
||
|
||
| № | Описание | Статус |
|
||
|---|----------|--------|
|
||
| 1 | Структура проекта | ⬜ |
|
||
| 2 | Главная страница со списком контактов | ⬜ |
|
||
| 3 | Страница добавления контакта | ⬜ |
|
||
| 4 | Страница редактирования контакта | ⬜ |
|
||
| 5 | Выбор фотографии из галереи | ⬜ |
|
||
| 6 | Диалог поиска контактов | ⬜ |
|
||
| 7 | Экспорт в Excel | ⬜ |
|
||
| 8 | Страница настроек с выбором тем | ⬜ |
|
||
| 9 | Зелёная тема оформления | ⬜ |
|
||
| 10 | Синяя тема оформления | ⬜ |
|
||
| 11 | Тёмная тема оформления | ⬜ |
|
||
|
||
---
|