tenzor 5.1.0
tenzor: ^5.1.0 copied to clipboard
A comprehensive Flutter utility & UI toolkit containing widgets, utilities, helpers, and integrations for rapid development.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:file_picker/file_picker.dart';
// Tüm Tenzor bileşenlerini içe aktar
import 'package:tenzor/tenzor.dart';
Future<void> main() async {
// Flutter binding'lerini başlat
WidgetsFlutterBinding.ensureInitialized();
// Firebase'i başlat - Sadece kendi Firebase projenizin konfigürasyonunu eklemeniz yeterli
await Firebase.initializeApp();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Tenzor Firebase Örneği',
theme: ThemeData(
primarySwatch: Colors.blue,
useMaterial3: true,
),
home: const AuthPage(),
debugShowCheckedModeBanner: false,
);
}
}
// Firebase ile Giriş/Kayıt Sayfası Örneği
class AuthPage extends StatefulWidget {
const AuthPage({super.key});
@override
State<AuthPage> createState() => _AuthPageState();
}
class _AuthPageState extends State<AuthPage> {
// Tenzor'un Firebase servisini oluştur
final _firebase = TenzorFirebaseService();
// Form kontrolleri
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
final _nameController = TextEditingController();
bool _isLogin = true;
bool _isLoading = false;
// Giriş yapma fonksiyonu
Future<void> _submit() async {
setState(() => _isLoading = true);
if (_isLogin) {
// Giriş yap
final result = await _firebase.signInWithEmailAndPassword(
email: _emailController.text.trim(),
password: _passwordController.text.trim(),
);
if (result is TenzorSuccess<UserCredential>) {
if (mounted) {
// Giriş başarılı, ana sayfaya yönlendir
TenzorSnackbar.success(context, "Giriş başarılı!");
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const HomePage()),
);
}
} else if (result is TenzorFailure) {
if (mounted) {
TenzorSnackbar.error(context, "Hata: ${result.errorOrNull}");
}
}
} else {
// Kayıt ol
final result = await _firebase.registerWithEmailAndPassword(
email: _emailController.text.trim(),
password: _passwordController.text.trim(),
displayName: _nameController.text.trim(),
additionalData: {
"createdAt": DateTime.now().toIso8601String(),
},
);
if (result is TenzorSuccess<UserCredential>) {
if (mounted) {
TenzorSnackbar.success(context, "Kayıt başarılı!");
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const HomePage()),
);
}
} else if (result is TenzorFailure) {
if (mounted) {
TenzorSnackbar.error(context, "Hata: ${result.error}");
}
}
}
setState(() => _isLoading = false);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_isLogin ? "Giriş Yap" : "Kayıt Ol"),
centerTitle: true,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Logo/Başlık
const Icon(
Icons.flutter_dash,
size: 80,
color: Colors.blue,
),
const SizedBox(height: 32),
// İsim alanı (sadece kayıt sırasında göster)
if (!_isLogin)
TenzorTextField(
controller: _nameController,
labelText: "Ad Soyad",
prefixIcon: const Icon(Icons.person),
),
if (!_isLogin) const SizedBox(height: 16),
// Email alanı
TenzorEmailField(
controller: _emailController,
labelText: "E-posta",
),
const SizedBox(height: 16),
// Şifre alanı
TenzorPasswordField(
controller: _passwordController,
labelText: "Şifre",
),
const SizedBox(height: 24),
// Gönder butonu
TenzorLoadingButton(
onPressed: _submit,
text: _isLogin ? "Giriş Yap" : "Kayıt Ol",
isLoading: _isLoading,
),
const SizedBox(height: 16),
// Mod değiştirme butonu
TextButton(
onPressed: () {
setState(() {
_isLogin = !_isLogin;
});
},
child: Text(
_isLogin
? "Hesabınız yok mu? Kayıt olun"
: "Zaten hesabınız var mı? Giriş yapın",
),
),
// Şifremi unuttum butonu (sadece giriş ekranında)
if (_isLogin)
TextButton(
onPressed: () async {
if (_emailController.text.isNotEmpty) {
await _firebase.auth.sendPasswordResetEmail(
_emailController.text.trim(),
);
if (mounted) {
TenzorAlert.show(
context,
title: "Şifre Sıfırlama",
message: "Şifre sıfırlama bağlantısı e-posta adresinize gönderildi.",
);
}
} else {
showDialog(
context: context,
builder: (context) => const ResetPasswordDialog(),
);
}
},
child: const Text("Şifremi unuttum"),
),
const SizedBox(height: 24),
// Ayraç çizgisi
const Row(
children: [
Expanded(child: Divider()),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Text("veya"),
),
Expanded(child: Divider()),
],
),
const SizedBox(height: 24),
// Sosyal giriş butonları
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Google ile giriş
IconButton(
icon: Image.asset(
'assets/google_logo.png',
height: 40,
width: 40,
errorBuilder: (context, error, stackTrace) {
return const Icon(Icons.g_mobiledata, size: 40, color: Colors.blue);
},
),
onPressed: _isLoading ? null : () async {
setState(() => _isLoading = true);
final result = await _firebase.signInWithGoogle();
if (result is TenzorSuccess<UserCredential> && mounted) {
TenzorSnackbar.success(context, "Google ile giriş başarılı!");
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const HomePage()),
);
} else if (result is TenzorFailure && mounted) {
TenzorSnackbar.error(context, "Hata: ${result.error}");
}
setState(() => _isLoading = false);
},
tooltip: "Google ile giriş yap",
),
const SizedBox(width: 32),
// Apple ile giriş
IconButton(
icon: const Icon(Icons.apple, size: 40, color: Colors.black),
onPressed: _isLoading ? null : () async {
setState(() => _isLoading = true);
final result = await _firebase.signInWithApple();
if (result is TenzorSuccess<UserCredential> && mounted) {
TenzorSnackbar.success(context, "Apple ile giriş başarılı!");
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const HomePage()),
);
} else if (result is TenzorFailure && mounted) {
TenzorSnackbar.error(context, "Hata: ${result.error}");
}
setState(() => _isLoading = false);
},
tooltip: "Apple ile giriş yap",
),
const SizedBox(width: 32),
// Telefon ile giriş
IconButton(
icon: const Icon(Icons.phone_android, size: 40, color: Colors.green),
onPressed: _isLoading ? null : () {
// Telefon ile giriş modalını aç
showDialog(
context: context,
builder: (context) => const PhoneLoginDialog(),
);
},
tooltip: "Telefon ile giriş yap",
),
],
),
],
),
),
);
}
}
// Giriş sonrası ana sayfa
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final _firebase = TenzorFirebaseService();
// Kullanıcı modeli
UserModel? _currentUser;
@override
void initState() {
super.initState();
// Kullanıcı verilerini yükle
_loadUserData();
}
Future<void> _loadUserData() async {
final result = await _firebase.getCurrentUserData(
fromMap: (map) => UserModel.fromMap(map!),
);
if (result is TenzorSuccess<UserModel?>) {
setState(() {
_currentUser = result.data;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: TenzorAppBar(
title: const Text("Ana Sayfa"),
actions: [
IconButton(
icon: const Icon(Icons.edit),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const ProfileEditPage()),
);
},
tooltip: "Profili Düzenle",
),
if (!_firebase.auth.isEmailVerified)
IconButton(
icon: const Icon(Icons.mark_email_unread, color: Colors.orange),
onPressed: () async {
final result = await _firebase.sendEmailVerification();
if (result is TenzorSuccess && mounted) {
TenzorSnackbar.success(context, "Doğrulama e-postası gönderildi!");
}
},
tooltip: "E-posta Doğrula",
),
IconButton(
icon: const Icon(Icons.delete_forever, color: Colors.red),
onPressed: () {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text("Hesabı Sil"),
content: const Text("Hesabınızı kalıcı olarak silmek istediğinizden emin misiniz? Bu işlem geri alınamaz."),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text("İptal"),
),
TextButton(
onPressed: () async {
Navigator.pop(context);
final result = await _firebase.deleteAccount();
if (result is TenzorSuccess && mounted) {
TenzorSnackbar.success(context, "Hesap başarıyla silindi");
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const AuthPage()),
);
} else if (result is TenzorFailure && mounted) {
TenzorSnackbar.error(context, "Hata: ${result.error}");
}
},
child: const Text("Sil", style: TextStyle(color: Colors.red)),
),
],
),
);
},
tooltip: "Hesabı Sil",
),
IconButton(
icon: const Icon(Icons.verified_user),
onPressed: () async {
await _firebase.auth.sendEmailVerification();
if (mounted) {
TenzorSnackbar.show(context, "E-posta doğrulama bağlantısı gönderildi!");
}
},
tooltip: "E-postayı Doğrula",
),
IconButton(
icon: const Icon(Icons.delete_forever, color: Colors.red),
onPressed: () async {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text("Hesabı Sil"),
content: const Text("Hesabınızı kalıcı olarak silmek istediğinizden emin misiniz? Bu işlem geri alınamaz."),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text("İptal"),
),
TextButton(
onPressed: () async {
Navigator.pop(context);
await _firebase.deleteAccount();
if (mounted) {
TenzorSnackbar.show(context, "Hesap başarıyla silindi");
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const AuthPage()),
);
}
},
child: const Text("Sil", style: TextStyle(color: Colors.red)),
),
],
),
);
},
tooltip: "Hesabı Sil",
),
IconButton(
icon: const Icon(Icons.logout),
onPressed: () async {
await _firebase.signOut();
if (mounted) {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const AuthPage()),
);
}
},
tooltip: "Çıkış Yap",
),
],
),
body: Center(
child: SingleChildScrollView(
padding: const EdgeInsets.all(24.0),
child: Column(
children: [
// Kullanıcı bilgileri kartı
TenzorContextualCard(
contentItems: [
TenzorCardContentItem(
id: 'user_info',
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
const CircleAvatar(
radius: 40,
child: Icon(Icons.person, size: 40),
),
const SizedBox(height: 16),
Text(
"Hoş geldiniz, ${_firebase.auth.currentUser?.displayName ?? 'Kullanıcı'}!",
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
_firebase.auth.currentUser?.email ?? '',
style: TextStyle(color: Colors.grey[600]),
),
if (_currentUser != null) ...[
const SizedBox(height: 16),
const Divider(),
const SizedBox(height: 16),
Text(
"Kayıt tarihi: ${_currentUser?.formattedDate ?? 'Bilinmiyor'}",
style: const TextStyle(fontSize: 14),
),
const SizedBox(height: 8),
Text(
"Giriş yöntemi: ${_currentUser?.provider ?? 'Bilinmiyor'}",
style: const TextStyle(fontSize: 14),
),
],
],
),
),
),
],
),
const SizedBox(height: 32),
// Tüm kullanıcıları listele
ExpansionTile(
title: const Text("Tüm Kullanıcılar (Admin)", style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
children: [
StreamBuilder<QuerySnapshot>(
stream: _firebase.getAllUsers(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return const Padding(
padding: EdgeInsets.all(16.0),
child: Text("Kullanıcılar yüklenemedi"),
);
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Padding(
padding: EdgeInsets.all(16.0),
child: TenzorCircularLoader(),
);
}
final users = snapshot.data?.docs ?? [];
return ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: users.length,
itemBuilder: (context, index) {
final userData = users[index].data() as Map<String, dynamic>;
return ListTile(
leading: const CircleAvatar(child: Icon(Icons.person)),
title: Text(userData['displayName'] ?? "Bilinmeyen Kullanıcı"),
subtitle: Text(userData['email'] ?? ""),
);
},
);
},
),
],
),
const SizedBox(height: 24),
// Firebase servisleri bilgisi
const Text(
"Tenzor Firebase Servisleri Aktif!",
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
),
const SizedBox(height: 16),
const Text(
"• Auth oturum durumu: Aktif\n• Firestore bağlantısı: Hazır\n• Kullanıcı verileri: Senkronize",
textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey),
),
],
),
),
),
);
}
}
// Şifre sıfırlama dialogu
class ResetPasswordDialog extends StatefulWidget {
const ResetPasswordDialog({super.key});
@override
State<ResetPasswordDialog> createState() => _ResetPasswordDialogState();
}
class _ResetPasswordDialogState extends State<ResetPasswordDialog> {
final _firebase = TenzorFirebaseService();
final _emailController = TextEditingController();
bool _isLoading = false;
Future<void> _sendResetLink() async {
setState(() => _isLoading = true);
final result = await _firebase.sendPasswordResetEmail(
email: _emailController.text.trim(),
);
if (result is TenzorSuccess && mounted) {
Navigator.pop(context);
TenzorSnackbar.success(context, "Şifre sıfırlama bağlantısı e-postanıza gönderildi!");
} else if (result is TenzorFailure && mounted) {
TenzorSnackbar.error(context, "Hata: ${result.error}");
setState(() => _isLoading = false);
}
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: const Text("Şifremi Unuttum"),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TenzorTextField(
controller: _emailController,
labelText: "E-posta Adresiniz",
prefixIcon: const Icon(Icons.email),
keyboardType: TextInputType.emailAddress,
),
const SizedBox(height: 16),
TenzorLoadingButton(
onPressed: _sendResetLink,
text: "Sıfırlama Bağlantısı Gönder",
isLoading: _isLoading,
),
],
),
);
}
}
// Telefon ile giriş dialogu
class PhoneLoginDialog extends StatefulWidget {
const PhoneLoginDialog({super.key});
@override
State<PhoneLoginDialog> createState() => _PhoneLoginDialogState();
}
class _PhoneLoginDialogState extends State<PhoneLoginDialog> {
final _firebase = TenzorFirebaseService();
final _phoneController = TextEditingController();
final _otpController = TextEditingController();
String? _verificationId;
bool _codeSent = false;
bool _isLoading = false;
Future<void> _sendOTP() async {
setState(() => _isLoading = true);
await _firebase.verifyPhoneNumber(
phoneNumber: _phoneController.text.trim(),
onCodeSent: (verificationId) {
setState(() {
_verificationId = verificationId;
_codeSent = true;
_isLoading = false;
});
if (mounted) {
TenzorSnackbar.show(context, "Doğrulama kodu gönderildi");
}
},
onError: (error) {
setState(() => _isLoading = false);
if (mounted) {
TenzorSnackbar.show(context, "Hata: $error");
}
},
);
}
Future<void> _verifyOTP() async {
if (_verificationId == null) return;
setState(() => _isLoading = true);
final result = await _firebase.signInWithPhoneNumber(
verificationId: _verificationId!,
smsCode: _otpController.text.trim(),
);
if (result is TenzorSuccess<UserCredential> && mounted) {
Navigator.pop(context);
TenzorSnackbar.success(context, "Telefon ile giriş başarılı!");
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const HomePage()),
);
} else if (result is TenzorFailure && mounted) {
TenzorSnackbar.error(context, "Hata: ${result.error}");
setState(() => _isLoading = false);
}
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: const Text("Telefon ile Giriş"),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (!_codeSent)
TenzorTextField(
controller: _phoneController,
labelText: "Telefon Numarası (+90...)",
prefixIcon: const Icon(Icons.phone),
keyboardType: TextInputType.phone,
)
else
TenzorTextField(
controller: _otpController,
labelText: "Doğrulama Kodu",
prefixIcon: const Icon(Icons.pin),
keyboardType: TextInputType.number,
),
const SizedBox(height: 16),
TenzorLoadingButton(
onPressed: _codeSent ? _verifyOTP : _sendOTP,
text: _codeSent ? "Doğrula" : "Kod Gönder",
isLoading: _isLoading,
),
],
),
);
}
}
// Profil düzenleme sayfası
class ProfileEditPage extends StatefulWidget {
const ProfileEditPage({super.key});
@override
State<ProfileEditPage> createState() => _ProfileEditPageState();
}
class _ProfileEditPageState extends State<ProfileEditPage> {
final _firebase = TenzorFirebaseService();
final _nameController = TextEditingController();
final _emailController = TextEditingController();
bool _isLoading = false;
@override
void initState() {
super.initState();
_nameController.text = _firebase.auth.currentUserName ?? '';
_emailController.text = _firebase.auth.currentUserEmail ?? '';
}
Future<void> _saveProfile() async {
setState(() => _isLoading = true);
final newEmail = _emailController.text.trim();
final currentEmail = _firebase.auth.currentUserEmail;
// E-posta değiştiyse güncelle
if (newEmail != currentEmail) {
await _firebase.auth.updateEmail(newEmail);
}
// Profili güncelle
await _firebase.updateProfile(
displayName: _nameController.text.trim(),
);
setState(() => _isLoading = false);
if (mounted) {
TenzorSnackbar.show(context, "Profil başarıyla güncellendi!");
Navigator.pop(context);
}
}
Future<void> _pickAndUploadPhoto() async {
try {
final result = await FilePicker.platform.pickFiles(
type: FileType.image,
allowMultiple: false,
);
if (result != null && result.files.single.path != null) {
setState(() => _isLoading = true);
final uploadResult = await _firebase.uploadProfilePhoto(
filePath: result.files.single.path!,
);
if (uploadResult is TenzorSuccess<String> && mounted) {
TenzorSnackbar.success(context, "Profil fotoğrafı yüklendi");
setState(() {}); // UI'ı yenile
} else if (uploadResult is TenzorFailure && mounted) {
TenzorSnackbar.error(context, "Hata: ${uploadResult.error}");
}
setState(() => _isLoading = false);
}
} catch (e) {
if (mounted) {
TenzorSnackbar.show(context, "Dosya seçilemedi: $e");
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Profili Düzenle")),
body: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
children: [
Stack(
children: [
CircleAvatar(
radius: 50,
backgroundImage: _firebase.auth.currentUser?.photoURL != null
? NetworkImage(_firebase.auth.currentUser!.photoURL!)
: null,
child: _firebase.auth.currentUser?.photoURL == null
? const Icon(Icons.person, size: 50)
: null,
),
Positioned(
bottom: 0,
right: 0,
child: IconButton(
icon: const Icon(Icons.camera_alt, color: Colors.blue),
onPressed: _isLoading ? null : _pickAndUploadPhoto,
),
),
],
),
const SizedBox(height: 32),
TenzorTextField(
controller: _nameController,
labelText: "Ad Soyad",
prefixIcon: const Icon(Icons.person),
),
const SizedBox(height: 16),
TenzorTextField(
controller: _emailController,
labelText: "E-posta",
prefixIcon: const Icon(Icons.email),
keyboardType: TextInputType.emailAddress,
),
const SizedBox(height: 24),
TenzorLoadingButton(
onPressed: _saveProfile,
text: "Kaydet",
isLoading: _isLoading,
),
],
),
),
);
}
}
// Kullanıcı Modeli
class UserModel {
final String? email;
final String? displayName;
final String? photoURL;
final DateTime? createdAt;
final String? provider;
UserModel.fromMap(Map<String, dynamic> map)
: email = map['email'],
displayName = map['displayName'],
photoURL = map['photoURL'],
provider = map['provider'],
createdAt = (map['createdAt'] as Timestamp?)?.toDate();
String get formattedDate => createdAt != null
? "${createdAt!.day}.${createdAt!.month}.${createdAt!.year}"
: "Bilinmiyor";
}