Flutter Pulse
431 subscribers
283 photos
579 links
На канале будут новости про flutter с сайтов, информация об обновлении пакетов, а также авторский контент.
Download Telegram
Умный запрос оценки
Не спамите запросом оценки! 🤔

Почему?
• Apple Store ограничивает запрос оценки через нативное всплывающее окно до 2 раз в год 📆
• Google Play Store ограничивает запрос почти 2 раза каждые 3 месяца... 🤷‍♂️
• Вы не хотите раздражать пользователей и получать плохие оценки 😠

Мы создаем метод, подобный showDialog, чтобы можно было запрашивать оценку где угодно в приложении 📱


class RatingSettings {
final Duration delayBeforeAsking;
final Duration delayBeforeAskingAgain;

RatingSettings({
required this.delayBeforeAsking,
required this.delayBeforeAskingAgain,
});
}

class Rating {
final RatingApi _ratingApi;
final RatingSettings settings;
final DateTime? lastAskingDate;
final DateTime userCreationDate;
final DateTime _current;
final bool hasRateApp;

Rating({
required this.settings,
required RatingApi ratingApi,
required this.lastAskingDate,
required this.userCreationDate,
required this.hasRateApp,
}) : _ratingApi = ratingApi,
_current = DateTime.now();

bool shouldAsk() {
// Если пользователь уже оценил приложение или мы не знаем дату создания пользователя, пропустим
if (userCreationDate == null || hasRateApp) {
return false;
}

final appInstallDiff = _current.difference(userCreationDate);
if (appInstallDiff < settings.delayBeforeAsking) {
return false;
}

if (lastAskingDate == null) {
return true;
}
return lastAskingDate!.difference(_current) > settings.delayBeforeAskingAgain;
}

Future<void> openStoreListing() => _ratingApi.openStoreListing();
Future<void> showRatingDialog() => _ratingApi.showRatingDialog();
}



void showRatingPopup(WidgetRef ref) {
final ratingRepository = ref.watch(ratingRepositoryProvider);
final userState = ref.watch(userStateNotifierProvider);
final ratingFuture = ratingRepository.getUserState(user);

ratingFuture.then((rating) {
if (rating.shouldAsk()) {
rating.delay();
showDialog(
context: context,
builder: (context) => AlertDialog.adaptive(
title: const Text('Спасибо за использование ULY'),
content: const Text('У вас есть минута, чтобы оставить отзыв?'),
actions: [
TextButton(
child: const Text('Позже'),
onPressed: () async {
Navigator.of(context).pop();
},
),
TextButton(
child: const Text('Да, конечно!'),
onPressed: () async {
Navigator.of(context).pop();
rating.rate();
},
),
],
),
);
}
});
}


Вы можете хранить RatingSettings в Firebase Remote Config, чтобы регулировать эти настройки без пересборки приложения и находить оптимальные значения 🔧

Также вы можете использовать этот же подход, чтобы открыть страницу приложения в магазине или напрямую запросить оценку. Страница в магазине требует больше усилий от пользователя, но текстовые отзывы более ценны 💬

Оцените новую рубрику! 👍👎
Все подобные новости можно найти по хэштегу #FlutterPulseTips

#flutter #dart #flutterpulse #FlutterPulseTips #MobileDev #AppDev #SmartRating #RatingSystem #UXTips
Как открыть страницу настроек приложения в системе
Привет, друзья! 👋 Сегодня мы поделимся с вами полезным советом о том, как открыть страницу настроек вашего приложения на устройстве пользователя. 📱💻

Зачем это нужно? 🤔
Например, если пользователь отклонил уведомления, система больше не будет запрашивать разрешение. В этом случае вы можете помочь пользователю перейти в настройки и включить уведомления вручную. 🔔

Используем url_launcher 📲

import 'package:url_launcher/url_launcher.dart';

Future<void> openSettings() async {
Uri url;
if (defaultTargetPlatform == TargetPlatform.iOS) {
url = Uri.parse('app-settings:');
} else if (defaultTargetPlatform == TargetPlatform.android) {
url = Uri.parse('package:uly.vlog.diary');
} else {
throw Exception('Unsupported platform');
}

if (await canLaunchUrl(url)) {
await launchUrl(url);
} else {
throw 'Could not launch $url';
}
}


Или проще с permission_handler 📦

import 'package:permission_handler/permission_handler.dart';

openAppSettings();



Оцените новую рубрику и напишите в комментариях, что вы думаете о ней! 💬 Все подобные новости можно найти по хэштегу #FlutterPulseTips
#flutter #dart #flutterpulse #FlutterPulseTips #MobileDev #AppDev #ProgrammingTips #CodingTips
Flutter tips: how to create a responsive layout
Адаптивный макет: советы по Flutter

Привет, разработчики! 👋 Сегодня мы поговорим о создании адаптивного макета в Flutter. 📱💻

Адаптация макета под разные размеры экранов 📐
Чтобы ваше приложение выглядело отлично на разных устройствах, необходимо адаптировать макет под различные размеры экранов. 📊


import 'package:flutter/widgets.dart';

// Перечисление типов устройств
enum DeviceType { small, medium, large, xlarge }

// Виджет, который адаптирует свой контент к текущему типу устройства
class ResponsiveLayout extends StatelessWidget {
final Widget? small; // Виджет для маленьких экранов
final Widget? medium; // Виджет для средних экранов
final Widget? large; // Виджет для больших экранов
final Widget? xlarge; // Виджет для очень больших экранов

const ResponsiveLayout({
super.key,
required this.small,
this.medium,
this.large,
this.xlarge,
});

@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
// Определение типа устройства на основе ширины экрана
switch (getDeviceType(constraints)) {
case DeviceType.small:
return small!; // Возвращаем виджет для маленьких экранов
case DeviceType.medium:
return medium ?? small!; // Возвращаем виджет для средних экранов или fallback к маленькому
case DeviceType.large:
return large ?? medium ?? small!; // Возвращаем виджет для больших экранов или fallback
case DeviceType.xlarge:
return xlarge ?? large ?? medium ?? small!; // Возвращаем виджет для очень больших экранов или fallback
}
},
);
}

// Метод для определения типа устройства на основе ограничений
DeviceType getDeviceType(BoxConstraints constraints) {
return switch (constraints.maxWidth) {
> 1200 => DeviceType.xlarge, // Очень большой экран
> 1024 => DeviceType.large, // Большой экран
> 768 => DeviceType.medium, // Средний экран
_ => DeviceType.small, // Маленький экран
};
}
}


Пример использования:

ResponsiveLayout(
small: SigninForm(), // Форма входа для маленьких экранов
medium: Center( // Центрирование для средних экранов и больше
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 600), // Ограничение максимальной ширины
child: SigninForm(), // Форма входа
),
),
)


Этот код позволяет создать адаптивный макет, который будет корректно отображаться на различных устройствах. 📱💻

Оцените новую рубрику и напишите своё мнение в комментариях! 💬

Все подобные новости можно найти по хэштегу #FlutterPulseTips
#flutter #dart #flutterpulse #FlutterPulseTips #MobileDev #AppDev #UIUX #ProgrammingTips #CodingTips
Flutter tips: Расширения для работы с датами

Привет, подписчики! Сегодня мы поговорим о полезных расширениях для работы с датами в Flutter. Расширения позволяют писать более чистый и читаемый код, что упрощает разработку и поддержку приложений.

Использование расширений для дат

Расширения в Dart позволяют добавлять новые методы к существующим классам. В данном случае мы будем использовать расширение для класса DateTime, чтобы добавить полезные методы для работы с датами.

extension DateExtension on DateTime {
DateTime get firstDayOfWeek {
return subtract(Duration(days: weekday - 1));
}

DateTime get lastDayOfWeek {
return add(Duration(days: 7 - weekday));
}

DateTime get firstDayOfMonth {
return DateTime(year, month);
}

bool isToday() => isSameDay(DateTime.now());

bool isSameDay(DateTime other) {
return day == other.day && month == other.month && year == other.year;
}

bool isAfterDayOrEqual(DateTime other) {
return isAfter(other) || isSameDay(other);
}

bool isBeforeDayOrEqual(DateTime other) {
return isBefore(other) || isSameDay(other);
}

bool isSameWeek(DateTime other) {
final kfirstDayOfTheWeek = firstDayOfWeek;
return other.isAfterDayOrEqual(kfirstDayOfTheWeek) &&
other.isBefore(lastDayOfWeek);
}
}


Пример использования

Теперь давайте рассмотрим пример использования этих расширений. Вместо того, чтобы писать громоздкий код для проверки, является ли дата сегодняшней, мы можем использовать метод isToday():

if (!activities.hasActivity(date) && date.isToday()) {
// код
}


Это намного чище и читаемее, чем:

final today = DateTime.now();
if (!activities.hasActivity(date) && date.day == today.day && date.month == today.month && date.year == today.year) {
// код
}


Оцените новую рубрику! Все подобные новости можно найти по хэштегу #FlutterPulseTips
#flutter #dart #flutterpulse #FlutterPulseTips #MobileDev #CodingTips #AppDev #FlutterTips
Использование ИИ в вашем приложении с помощью Gemini

Привет, разработчики Flutter! 🤖💻 Сегодня мы расскажем, как добавить Gemini в ваше приложение с помощью Firebase Functions безопасным способом 🔒.

Почему не стоит использовать плагин flutter_gemini?

Из соображений безопасности я настоятельно предпочитаю не раскрывать свой ключ API Gemini в приложении 🔑.

Шаги по интеграции Gemini с Firebase:

1. Создайте новый проект Firebase с помощью команды firebase init genkit или установите необходимые зависимости 📦.
- Установите следующие пакеты:
- @genkit-ai/ai
- @genkit-ai/core
- @genkit-ai/dotprompt
- @genkit-ai/firebase
- @genkit-ai/flow
- @genkit-ai/googleai
- zod

2. Установите genkit глобально: npm install -g genkit 🌐.

3. Инициализируйте gemini, используя configureGenkit 🔧.



import { initializeApp } from "firebase/app";
import { defineString } from "firebase-functions/params";
import { configureGenkit } from '@genkit-ai/core';
import { firebase } from '@genkit-ai/firebase';

defineString('GOOGLE_GENAI_API_KEY');
const firebaseApp = initializeApp();

configureGenkit({
plugins: [
firebase(),
googleAI({ apiKey: defineString('GOOGLE_GENAI_API_KEY') }),
],
// ...
enableTracingAndMetrics: true,
});



Добавьте ключ API в файл .env и не забудьте удалить .env из .gitignore, иначе Firebase не сможет его использовать 🚫.



export const suggestionFlow = onFlow({
name: "suggestionFlow",
httpsOptions: { cors: true },
region: "europe-west1",
inputSchema: z.object({ uid: z.string(), query: z.string(), language: z.string() }),
outputSchema: z.string(),
authPolicy: (auth, input) => {
// Проверка аутентификации пользователя
if (auth.uid != input.uid) {
throw new Error("You can only access your own data");
}
// Проверка поддерживаемого языка
if (input.language != "fr" && input.language != "en") {
throw new Error("Only French and English are supported for now");
}
// Разрешить доступ только аутентифицированным пользователям
return !!auth.uid;
},
}, async (input) => {
const exercicesContext = JSON.stringify(exercices);
const userLanguage = input.language;
const prompt = `You are an AI assistant that helps users with XXX. You will speak in the user's language: $userLanguage.
You will answer with the JSON format below:
{"days": [{"day": number, "steps": {...}, "description": string}]}
Here is the user query: ${input.query}`;

const llmResponse = await generate({
model: gemini15Flash,
prompt,
config: {
temperature: 1,
maxOutputTokens: 3000,
},
});

return llmResponse.text();
});



Разверните эту функцию на Firebase с помощью команды firebase deploy --only functions 🚀.

Вызов функции из Flutter:


Future<String> fetchStretchingSuggestionFlow(
String uid,
String query,
String userLanguage,
) async {
final callable = FirebaseFunctions.instance.httpsCallable('suggestionFlow');
final result = await callable.call<String>({
'uid': uid,
'query': query,
'language': userLanguage,
});
final data = (await decode(result.data)) as Map<String, dynamic>;
return AIexercice.fromJson(data);
}



Оцените нашу новую рубрику и оставьте свои отзывы! 😊👍

Все подобные новости можно найти по хэштегу #FlutterPulseTips.

#flutter #dart #flutterpulse #FlutterPulseTips #Firebase #Gemini #AI #FlutterTips #MobileDev #AppDev
Отслеживание видимости клавиатуры
Без каких-либо плагинов 😉

В разработке Flutter-приложений часто возникает необходимость отслеживать состояние клавиатуры (видима она или нет). Это может быть полезно, например, когда нужно скрыть или показать определенные элементы интерфейса в зависимости от того, открыта клавиатура или нет.

Проблема:
Иногда клавиатура заставляет некоторые элементы контента перемещаться вверх, и вы можете захотеть скрыть определенные элементы на экране, когда пользователь печатает. Я предпочитаю иметь статическую кнопку действия внизу. Однако проблема в том, что эта кнопка может перекрывать поле ввода, пока пользователь печатает. Поэтому я использую метод, чтобы скрыть кнопку действия, когда клавиатура открыта.

Решение:
Для отслеживания состояния клавиатуры мы можем использовать виджет KeyboardVisibility, который не требует установки дополнительных плагинов.



import 'package:flutter/material.dart';

// состояние клавиатуры
enum KeyboardVisibilityState { visible, hidden }

// определение функции слушателя
typedef OnKeyboardStateChanged = void Function(KeyboardVisibilityState state);

// stateful виджет для отслеживания изменения видимости клавиатуры
class KeyboardVisibility extends StatefulWidget {
final Widget child;
final OnKeyboardStateChanged onKeyboardStateChanged;

const KeyboardVisibility({
super.key,
required this.child,
required this.onKeyboardStateChanged,
});

@override
State<KeyboardVisibility> createState() => _KeyboardVisibilityState();
}

class _KeyboardVisibilityState extends State<KeyboardVisibility> with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}

@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}

@override
void didChangeMetrics() {
super.didChangeMetrics();
checkState();
}

void checkState() {
final value = WidgetsBinding.instance.platformDispatcher.views.first.viewInsets.bottom;
switch (value != 0.0) {
case true:
widget.onKeyboardStateChanged(KeyboardVisibilityState.visible);
case false:
widget.onKeyboardStateChanged(KeyboardVisibilityState.hidden);
}
}

@override
Widget build(BuildContext context) {
return widget.child;
}
}



Использование:
Виджет KeyboardVisibility можно использовать следующим образом:



KeyboardVisibility(
onKeyboardStateChanged: (state) => _showOrHideBottomAction(state),
child: Form(...),
)



Оцените новую рубрику и напишите свое мнение в комментариях! 👍

Все подобные новости можно найти по хэштегу #FlutterPulseTips

#flutter #dart #flutterpulse #FlutterPulseTips #MobileDev #UIUX #CodingTips #AppDev #DevTips
Создание изображения из виджета

Привет, Flutter-разработчики! 👋 Сегодня мы рассмотрим интересную и полезную тему: создание изображения из любого виджета в вашем приложении. 📸

Шаг 1: Добавьте RepaintBoundary поверх виджета, который хотите экспортировать

Чтобы создать изображение из виджета, сначала нужно обернуть его в RepaintBoundary. Для этого потребуется GlobalKey, который поможет найти нужный виджет в дереве рендеринга.



final GlobalKey _repaintBoundaryKey = GlobalKey();

RepaintBoundary(
key: _repaintBoundaryKey,
child: CustomPaint(
painter: MyPainter(),
...
),
)



Этот ключ позволит нам найти объект рендеринга и вызвать метод toImage. 📚 Для более глубокого понимания можно изучить, что такое Widget tree, Element tree и RenderObject tree.

Шаг 2: Вызовите метод для экспорта изображения в галерею телефона

Теперь напишем функцию, которая будет экспортировать изображение:



import 'package:image_gallery_saver/image_gallery_saver.dart';

Future<void> exportImage() async {
try {
RenderRepaintBoundary boundary = _repaintBoundaryKey.currentContext!.findRenderObject() as RenderRepaintBoundary;
ui.Image image = await boundary.toImage(pixelRatio: 3.0);
ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png);
Uint8List pngBytes = byteData!.buffer.asUint8List();

final directory = await getDownloadsDirectory();
final imgName = 'myapp-${DateTime.now().microsecondsSinceEpoch}';
final result = await ImageGallerySaver.saveImage(pngBytes, quality: 100, name: imgName);

if (result['isSuccess']) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Изображение экспортировано')),
);
}
} catch (e) {
// Обработайте ошибку (покажите сообщение пользователю, сохраните отчет о сбое)
}
}



Мы используем пакет image_gallery_saver, чтобы сохранить изображение в галерею телефона. 📁📸

Оцените новую рубрику и напишите в комментариях, что хотите видеть в следующих постах! 💬

Все подобные новости можно найти по хэштегу #FlutterPulseTips

#flutter #dart #flutterpulse #FlutterPulseTips #MobileDev #AppDev #CodingTips #UIUX #FlutterTips