Sıfırdan İleri Seviye Flutter Projesi – 2

Aşağıdaki size kodu verip daha sonra ise adım adım açıklayacağım. Ekran çıktıları vererek ise çalışmayı görselleştireceğim.

import 'dart:async';
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_svg/svg.dart';
import 'package:harpia_project/utils/Utils.dart';
import 'package:harpia_project/views/loginscreen.dart';
import 'package:harpia_project/views/network/network_error.dart';
import 'package:harpia_project/utils/MySharedPreferences.dart';
import 'package:harpia_project/views/preview.dart';
import 'package:get_storage/get_storage.dart';

ThemeMode appTheme = ThemeMode.dark; // Varsayılan tema

void main() async {
WidgetsFlutterBinding.ensureInitialized();
await GetStorage.init();
await EasyLocalization.ensureInitialized();

SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(statusBarColor: Colors.transparent));
runApp(
EasyLocalization(
supportedLocales: const [
Locale('en'),
Locale('tr'),
Locale('fr'),
Locale('zh'),
],
path: 'assets/translations',
fallbackLocale: const Locale('en'),
child: MyApp()),
);
}

class MyApp extends StatelessWidget {
MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ScreenUtilInit(
designSize: const Size(300, 800),
builder: (context, child) => MaterialApp(
localizationsDelegates: context.localizationDelegates,
supportedLocales: context.supportedLocales,
locale: context.locale,
debugShowCheckedModeBanner: false,
home: SplashScreen(),
),
);
}
}

class SplashScreen extends StatefulWidget {
const SplashScreen({Key? key}) : super(key: key);

@override
SplashScreenState createState() => SplashScreenState();
}

class SplashScreenState extends State<SplashScreen> {
Color selectedBackgroundColor = Colors.white; // Seçilen arka plan rengi

@override
void initState() {
super.initState();
_checkInternetAndNavigate();
loadBackgroundColor();
}

@override
Widget build(BuildContext context) {
MySharedPreferences.setLocalName(Platform.localeName);

// Enable immersive mode
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive);
return Scaffold(
backgroundColor: selectedBackgroundColor,
body: Stack(
children: [
// First LinearLayout with background image
Container(
height: 200,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/header_login.png'),
fit: BoxFit.fill,
),
),
),
// Centered Logo ImageView
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(
'assets/images/dikeycizgili.svg',
width: 100,
height: 100,
),
// TextView with "step_by_step" text
const SizedBox(
height: 25,
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 3.sp),
child: Text(
"step_by_step".tr(),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18.sp,
color: Colors.black,
fontStyle: FontStyle.italic,
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(
height: 25,
),
const CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.orange),
),
],
),
),
// Second LinearLayout with background image (rotated)
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Transform.rotate(
angle: 3.14159, // Rotate 180 degrees (Pi approximation)
child: Container(
height: 200,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/header_login.png'),
fit: BoxFit.fill,
),
),
),
),
),
// Copyright TextView - horizontally centered
Align(
alignment: Alignment.bottomCenter,
child: Container(
padding: const EdgeInsets.only(bottom: 40.0),
child: Text(
"${'hakan_aydin'.tr()} ${DateTime.now().year}",
style: const TextStyle(
fontSize: 20,
fontStyle: FontStyle.italic,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
);
}

Future<void> _checkInternetAndNavigate() async {
bool hasInternet = await checkInternetConnection();
if (hasInternet) {
bool isFirstLaunch = await MySharedPreferences.isFirstLaunch();
if (isFirstLaunch) {
_navigateToPreviewPage();
} else {
_navigateToLoginPage();
}
} else {
_navigateToNetworkErrorPage();
}
}

void _navigateToPreviewPage() async {
Timer(
const Duration(seconds: 3),
() {
MySharedPreferences.setFirstLaunch(false);
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const PreviewPage()),
);
},
);
}

void _navigateToLoginPage() {
Future.delayed(const Duration(seconds: 2)).then((value) {
Navigator.pushReplacement(
context, CupertinoPageRoute(builder: (_) => const LoginScreen()));
});
}

void _navigateToNetworkErrorPage() {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => NetworkErrorPage()),
);
}

void loadBackgroundColor() async {
int colorValue = await MySharedPreferences.getBackgroundColor();
setState(() {
selectedBackgroundColor = Color(colorValue);
});
}
}

İlk olarak, gerekli kütüphaneler ve paketler import ifadeleriyle projeye dahil edilir. Bu paketler, uygulamanın çalışması için gerekli olan işlevleri sağlar. ThemeMode adında bir değişken tanımlanır ve varsayılan olarak ThemeMode.dark değeri atanır. Bu değişken, uygulamanın tema modunu temsil eder.

main fonksiyonu oluşturulur. Bu fonksiyon, uygulama başlatıldığında çalıştırılacak ilk fonksiyondur.

WidgetsFlutterBinding.ensureInitialized() çağrısı ile Flutter bağlamı başlatılır. GetStorage.init() ve EasyLocalization.ensureInitialized() fonksiyonları çağrılarak, veri depolama ve çoklu dil desteği için gerekli olan paketlerin başlatılması sağlanır.

SystemChrome.setSystemUIOverlayStyle() çağrısı ile sistem arayüzü overlay stili ayarlanır. Bu durumda, durum çubuğunun şeffaf olması sağlanır. runApp() fonksiyonu çağrılarak uygulama başlatılır. Bu fonksiyon, uygulamanın kök widget’ını alır ve çalıştırır. EasyLocalization widget’ı, uygulamanın çoklu dil desteğini sağlar.

    EasyLocalization(
supportedLocales: const [
Locale('en'),
Locale('tr'),
Locale('fr'),
Locale('zh'),
],
path: 'assets/translations',
fallbackLocale: const Locale('en'),
child: MyApp()),

Yukarıdaki resimde uygulamanın dil desteği için gerekli olan json dosyaları gösterilmektedir. Burada

tr.json için;

"login_successful": "Giriş Başarılı",

en.json için;
"login_successful": "Login Successful",

fr.json için;
"login_successful": "Connexion réussie",

zh.json için ise;
"login_successful": "登录成功",

MyApp adında bir sınıf tanımlanır ve StatelessWidget sınıfından türetilir. Bu sınıf, uygulamanın ana widget’ını temsil eder. build metodu, MyApp sınıfının bir parçası olarak tanımlanır. Bu metot, widget’ın görüntüsünü oluşturur ve döndürür. ScreenUtilInit widget’ı, ekran boyutlarını ve düzenini ayarlar. Bu, farklı ekran boyutlarında ve yoğunluklarında tutarlı bir görünüm sağlar.

MaterialApp widget’ı, Flutter uygulamasının temel yapılandırmasını sağlar. localizationsDelegates, supportedLocales ve locale parametreleri, uygulamanın dil desteğini yapılandırır. debugShowCheckedModeBanner parametresi, hata ayıklama bandının görüntülenip görüntülenmeyeceğini belirler.SplashScreen widget’ı, uygulamanın başlangıç ekranını temsil eder. StatefulWidget sınıfından türetilir. initState metodu, SplashScreenState sınıfının bir parçası olarak tanımlanır ve widget’ın durumunu başlatır. Bu metot, widget oluşturulduğunda bir kez çağrılır. build metodu, SplashScreenState sınıfının bir parçası olarak tanımlanır. Bu metot, widget’ın görüntüsünü oluşturur ve döndürür. MySharedPreferences.setLocalName() çağrısı ile yerel dil ayarları depolanır. SystemChrome.setEnabledSystemUIMode() çağrısı ile tam ekran modu etkinleştirilir. Scaffold widget’ı, bir materyal tasarım yapısını temsil eder. backgroundColor özelliği, widget’ın arka plan rengini belirler.

  Future<void> _checkInternetAndNavigate() async {
bool hasInternet = await checkInternetConnection();
if (hasInternet) {
bool isFirstLaunch = await MySharedPreferences.isFirstLaunch();
if (isFirstLaunch) {
_navigateToPreviewPage();
} else {
_navigateToLoginPage();
}
} else {
_navigateToNetworkErrorPage();
}
}
  void _navigateToNetworkErrorPage() {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => NetworkErrorPage()),
);
}

Bu kod bloğu, _navigateToNetworkErrorPage() adında bir fonksiyon tanımlar. Bu fonksiyon, ağ hatası sayfasına yönlendirme işlemini gerçekleştirir. İşlevi şu adımları takip eder. Navigator.pushReplacement() fonksiyonu çağrılır. Bu fonksiyon, yeni bir sayfaya geçişi sağlar ve önceki sayfayı bellekten kaldırır. context parametresi, mevcut widgetin işlevsel bağlamını temsil eder. Bu parametre, yönlendirme işlemi için gereklidir. MaterialPageRoute widget’ı, sayfa geçişini sağlar ve hedef sayfanın oluşturulmasını belirtir. builder parametresi, hedef sayfanın nasıl oluşturulacağını belirten bir anonim fonksiyonu alır. Bu durumda, NetworkErrorPage sayfası oluşturulur. Yönlendirme işlemi tamamlanır ve kullanıcı ağ hatası sayfasına yönlendirilir.

network_error.dart dosyası:
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_svg/svg.dart';

import '../../core/ResponsiveDesign.dart';
import '../loginscreen.dart';

class NetworkErrorPage extends StatefulWidget {
@override
State<NetworkErrorPage> createState() => NetworkErrorPageState();
}

class NetworkErrorPageState extends State<NetworkErrorPage> {
bool isOnline = false;
bool isButtonEnabled = true;
bool isLoading = false; // İlerleme çemberinin görünürlüğü

@override
void initState() {
super.initState();
checkInternetConnection();
}

@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
backgroundColor: Colors.white,
body: Stack(
children: [
// First LinearLayout with background image
Container(
height: 200,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/header_login.png'),
fit: BoxFit.fill,
),
),
),
// Centered Logo ImageView
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(
isOnline
? 'assets/images/network_yes.svg'
: 'assets/images/network_no.svg',
width: 100,
height: 100,
),
// TextView with "step_by_step" text
const SizedBox(
height: 25,
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 3.sp),
child: Column(
children: [
Text(
isOnline ? "yes".tr() : "oh_no".tr(),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14.sp,
color: Colors.black,
fontStyle: FontStyle.italic,
fontWeight: FontWeight.bold,
),
),
Text(
isOnline
? 'internet_problem_control'.tr()
: "no_internet_connection".tr(),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14.sp,
color: Colors.black,
fontStyle: FontStyle.italic,
fontWeight: FontWeight.bold,
),
),
Text(
isOnline ? "" : "or_try_again".tr(),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18.sp,
color: Colors.black,
fontStyle: FontStyle.italic,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 20.h),
SizedBox(
width: 180.w,
height: 50.h,
child: ElevatedButton(
onPressed: () {
showLoadingDialog(
context); // İlerleme çemberi göster
retryProcess(context);
},
style: ButtonStyle(
shape: MaterialStateProperty.all<
RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(18.sp),
side: BorderSide(color: Colors.pink))),
backgroundColor: MaterialStateColor.resolveWith(
(states) => Colors.pink),
foregroundColor: MaterialStateColor.resolveWith(
(states) => Colors.white)),
child: Text('retry'.tr()),
),
),
],
),
),
const SizedBox(
height: 25,
),
],
),
),
// Second LinearLayout with background image (rotated)
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Transform.rotate(
angle: 3.14159, // Rotate 180 degrees (Pi approximation)
child: Container(
height: 200,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/header_login.png'),
fit: BoxFit.fill,
),
),
),
),
),
// Copyright TextView - horizontally centered
],
),
),
);
}

Future<void> retryProcess(BuildContext context) async {
if (!isButtonEnabled) return;

setState(() {
isButtonEnabled = false;
isLoading = true; // İlerleme çemberini göster
});

bool isOnlineNow = await checkInternetConnection();

if (isOnlineNow) {
setState(() {
isOnline = true;
});

Future.delayed(const Duration(seconds: 2), () {
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => const LoginScreen(),
),
);
});
} else {
// İnternet bağlantısı yoksa işlem yapma
setState(() {
isButtonEnabled = true;
isLoading = false; // İlerleme çemberini gizle
});
}
}

Future<void> showLoadingDialog(BuildContext context) async {
showDialog(
context: context,
builder: (BuildContext context) {
return const AlertDialog(
backgroundColor: Colors.white,
shadowColor: Colors.white,
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(
backgroundColor: Colors.white,
),
SizedBox(height: 10),
Text('Loading...'),
],
),
);
},
);

await Future.delayed(Duration(seconds: 2)); // 2 saniye bekle

Navigator.of(context).pop(); // AlertDialog'ı kapat
}

Future<bool> checkInternetConnection() async {
var connectivityResult = await (Connectivity().checkConnectivity());
return connectivityResult != ConnectivityResult.none;
}
}

Bu kod, Flutter kullanılarak yazılmış NetworkErrorPage adlı bir sayfayı tanımlar. Bu sayfa, ağ hatası durumunda görüntülenen bir arayüzü içerir. İşlevleri aşağıdaki gibi açıklayabiliriz:

  1. NetworkErrorPage sınıfı, StatefulWidget sınıfını genişleterek bir durum sınıfı oluşturur. Bu durum sınıfı, sayfanın dinamik durumunu yönetir.
  2. isOnline, isButtonEnabled ve isLoading gibi değişkenler, sayfanın durumu için kullanılır. Örneğin, isOnline değişkeni, internet bağlantısı durumunu temsil eder.
  3. initState() metodu, sayfa oluşturulduğunda çağrılır ve checkInternetConnection() fonksiyonunu çağırarak internet bağlantısı durumunu kontrol eder.
  4. build() metodu, sayfanın arayüzünü oluşturur ve döndürür. Bu metot içinde SafeArea ve Scaffold widget’ları kullanılır. Arka plan rengi beyaz olan bir Stack widget’ı içinde diğer widget’lar yer alır.
  5. SvgPicture.asset() widget’ı, SVG formatında bir resmi görüntüler. isOnline durumuna bağlı olarak farklı resimler gösterilir.
  6. Metin widget’ları, dil desteği eklemek için easy_localization paketinden yararlanır. Metinler, tr() metodunu kullanarak yerelleştirilir.
  7. ElevatedButton widget’ı, yeniden deneme işlemi için bir düğmeyi temsil eder. onPressed özelliği, düğmeye basıldığında showLoadingDialog() ve retryProcess() fonksiyonlarını çağırır.
  8. retryProcess() fonksiyonu, yeniden deneme işlemini gerçekleştirir. İnternet bağlantısı kontrol edilir ve duruma bağlı olarak ilgili işlemler yapılır. Örneğin, internet bağlantısı varsa LoginScreen sayfasına yönlendirilir.
  9. showLoadingDialog() fonksiyonu, yükleniyor iletişim kutusunu (AlertDialog) gösterir. CircularProgressIndicator widget’ı, ilerleme çemberini temsil eder. Belirli bir süre bekledikten sonra iletişim kutusu kapatılır.
  10. checkInternetConnection() fonksiyonu, internet bağlantısını kontrol eder. Connectivity sınıfı kullanılarak bağlantı durumu kontrol edilir.
isOnline
? 'assets/images/network_yes.svg'
: 'assets/images/network_no.svg',

Bu kod parçası, bir koşul ifadesi kullanarak isOnline değişkeninin durumuna bağlı olarak farklı SVG resimlerinin yolunu belirler. 'assets/images/network_yes.svg' ve 'assets/images/network_no.svg', SVG formatında bulunan iki farklı resmin dosya yolunu temsil eder. Bu resimler, internet bağlantısı durumuna göre farklı ikonları veya görselleri temsil edebilir. Koşul ifadesi, isOnline değişkeninin değerini kontrol eder. Eğer isOnline true ise, yani internet bağlantısı varsa, 'assets/images/network_yes.svg' yolunu temsil eden resim kullanılır. Eğer isOnline false ise, yani internet bağlantısı yoksa, 'assets/images/network_no.svg' yolunu temsil eden resim kullanılır. Bu yöntem sayesinde, internet bağlantısı durumuna göre farklı resimlerin görüntülenmesi sağlanır.

  Future<void> showLoadingDialog(BuildContext context) async {
showDialog(
context: context,
builder: (BuildContext context) {
return const AlertDialog(
backgroundColor: Colors.white,
shadowColor: Colors.white,
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(
backgroundColor: Colors.white,
),
SizedBox(height: 10),
Text('Loading...'),
],
),
);
},
);

await Future.delayed(Duration(seconds: 2)); // 2 saniye bekle

Navigator.of(context).pop(); // AlertDialog'ı kapat
}

Bu kod, showLoadingDialog adlı bir asenkron fonksiyonu tanımlar. Bu fonksiyon, bir yükleniyor iletişim kutusu (AlertDialog) gösterir, belirli bir süre bekler ve sonra iletişim kutusunu kapatır. İşlevin ana gövdesi showDialog fonksiyonunu kullanır. showDialog fonksiyonu, context parametresiyle birlikte bir builder işlevi alır. builder işlevi, iletişim kutusunun içeriğini döndürür. builder işlevinde, AlertDialog widget’ı kullanılır. AlertDialog widget’ının backgroundColor özelliği beyaz olarak ayarlanır ve gölgelendirme için shadowColor özelliği de beyaz olarak ayarlanır. İçerik olarak, bir sütun (Column) widget’ı kullanılır. Bu sütunun içinde, CircularProgressIndicator widget’ı yüklenme çemberini göstermek için kullanılır. backgroundColor özelliği de beyaz olarak ayarlanır. Ardından, SizedBox widget’ı kullanılarak bir boşluk eklenir ve altında bir metin (Text) widget’ı görüntülenir. Bu metin, “Loading…” olarak ayarlanır. showDialog fonksiyonu çağrıldığında, iletişim kutusu görüntülenir. Ardından, await Future.delayed(Duration(seconds: 2)) ifadesi kullanılarak 2 saniye beklenir. Bu, iletişim kutusunun belirli bir süre görüntülenmesini sağlar. Son olarak, Navigator.of(context).pop() ifadesi kullanılarak iletişim kutusu kapatılır. pop() fonksiyonu, en üstteki arayüz elemanını (burada iletişim kutusunu) kapatır ve altındaki elemanlara geri döner. Bu şekilde, showLoadingDialog fonksiyonu kullanılarak bir yükleniyor iletişim kutusu gösterilebilir ve belirli bir süre sonra otomatik olarak kapatılabilir.