12 Haziran 2026 15:47, Cuma 34 0
GİRİŞ
Bu uygulama, kullanıcının kendi yemek tariflerini dijital ortamda saklayabileceği,
diğer kayıtlı şeflerin tariflerini inceleyebileceği, arama yapıp favorilerine
ekleyebileceği gelişmiş ve çoklu kullanıcı destekli bir tarif defteri uygulamasıdır.
Uygulama, verileri yerel SQLite veritabanı üzerinde saklar. Kullanıcı oturum yönetimi
için SharedPreferences kullanılmıştır. Bu sayede internet bağlantısı kopsa dahi veriler
cihazda güvenle saklanır, internete bağlanıldığında ise uzaktaki yemek görselleri
asenkron (arka planda) olarak indirilir.
Bu proje sayesinde;
- SQLite veritabanı ile ilişkisel tablo tasarımları (Bire-Çok İlişkisi)
- SharedPreferences ile Oturum Yönetimi (Session Management)
- Çok kanallı arka plan işlemleri (ExecutorService ve Handler ile Async Image Loading)
- BaseAdapter ve ListView ile CardView tabanlı dinamik veri listeleme
- EditText TextWatcher ile anlık / gerçek zamanlı filtreleme (Real-time Search)
- AlertDialog ve kullanıcı onay mekanizmaları
- Intent ile ekranlar arası veri taşıma (Bundle / putExtra)
konularını profesyonel ve pratik bir şekilde öğrenebilirsiniz.
Geliştirilen modern yemek tarifi uygulaması şu özelliklere sahiptir:
✔ Kullanıcı Kayıt & Giriş Paneli: Güvenli hesap oluşturma ve şifreli giriş.
✔ Oturum Hatırlama: SharedPreferences ile kullanıcıyı hatırlama ve otomatik giriş.
✔ Dinamik Tarif Akışı (Ana Sayfa): Tüm şeflerin eklediği tariflerin tek akışta gösterimi.
✔ Gerçek Zamanlı Arama: Tarif adına, malzemelere veya kategoriye göre anlık filtreleme.
✔ Kategori ve Emoji Eşleme: Tarif kategorisine göre otomatik görsel emoji ataması.
✔ Uzak Resim Desteği: URL vererek tarif görseli yükleme ve arka planda asenkron indirme.
✔ Gelişmiş Favori Sistemi: Akış üzerinden tek tıkla tarif favorileme ve favorileri listeleme.
✔ Profil ve Hesap Yönetimi: Kullanıcının tarif sayısını görmesi, tariflerini veya hesabını tamamen silebilmesi.
✔ Şefler (Kullanıcılar) Listesi: Kayıtlı tüm kullanıcıları ve paylaştıkları tarif sayılarını görme.
Bu projede aşağıdaki Android ve Java mimarileri kullanılmıştır:
→ SQLite ve SQLiteOpenHelper : Verilerin cihazda ilişkisel tablolarla kalıcı saklanması.
→ **SharedPreferences** : Giriş yapan kullanıcının oturum bilgilerinin saklanması.
→ **ExecutorService & Handler**: Resimlerin UI thread'i bloke etmeden arka planda indirilmesi.
→ **TextWatcher** : Arama çubuğuna yazılan metnin anlık olarak dinlenmesi.
→ BaseAdapter / ListView : Kart görünümündeki listelerin dinamik olarak yönetimi.
→ **AlertDialog** : Hesap veya tarif silme işlemlerinde onay pencereleri.
→ **CardView** : Modern ve yuvarlatılmış köşeli kart tasarımları.
→ **Intent & Flags** : Sayfa geçişleri ve geri yığınının (back stack) temizlenmesi.
Uygulama akışı şu şekildedir:
[1] Uygulama açılır → SharedPreferences kontrol edilir:
↳ Oturum varsa direkt Ana Sayfaya (MainActivity) yönlendirilir.
↳ Oturum yoksa Giriş Ekranına (LoginActivity) yönlendirilir.
↓
[2] Giriş Ekranı (LoginActivity) / Kayıt Ekranı (RegisterActivity)
↓
[3] Ana Sayfa (MainActivity) — Tarif Akışı, Arama Barı ve Alt Navigasyon Bar
↓
[4] Alt Navigasyon Butonları ile Geçiş Yapılan Ekranlar:
↳ [Tarif Ekle] → MainActivity2 (Form: Başlık, Malzeme, Yapılış, Kategori, Görsel URL)
↳ [Şefler] → UsersActivity (Kayıtlı kullanıcılar ve tarif sayıları listesi)
↳ [Favoriler] → MainActivity5 (Sadece favori olarak işaretlenmiş tarifler)
↳ [Profilim] ↳ ProfileActivity (Oturum bilgileri, tarif sayıları ve hesap/tarif silme)
↓
[5] Herhangi bir tarife tıklandığında:
↳ Tarif Detay Sayfası (MainActivity4) açılır.
Uygulama "YemekRehberi.db" adlı SQLite veritabanı dosyasını kullanır.
İçerisinde birbiriyle ilişkili **2 tablo** bulunur:
── `users` Tablosu (Kullanıcılar) ─────────────────
Sütun │ Türü │ Açıklama
id INTEGER (PK, AUTO) Kullanıcı ID numarası
name TEXT Kullanıcının adı soyadı
email TEXT (UNIQUE) Giriş e-posta adresi (Benzersiz)
password TEXT Giriş şifresi
── `tarifler` Tablosu (Yemek Tarifleri) ───────────
Sütun │ Türü │ Açıklama
id INTEGER (PK, AUTO) Tarif ID numarası
ad TEXT Yemeğin adı
malzeme TEXT Gerekli malzemeler
yapilis TEXT Hazırlanış aşamaları
kategori TEXT Yemek kategorisi (Çorba, Tatlı vb.)
resim_url TEXT İnternetteki görsel bağlantısı
ekleyen_id INTEGER (FK) Tarifi ekleyen kullanıcının ID'si
ekleyen_adi TEXT Tarifi ekleyen kullanıcının adı
favori INTEGER (DEFAULT 0) 0 = normal, 1 = favorilerde
SQL Oluşturma Sorguları:
```sql
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
email TEXT UNIQUE,
password TEXT
);
CREATE TABLE tarifler (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ad TEXT,
malzeme TEXT,
yapilis TEXT,
kategori TEXT,
resim_url TEXT,
ekleyen_id INTEGER,
ekleyen_adi TEXT,
favori INTEGER DEFAULT 0
);
LoginActivity: Oturum kontrolü yapar. Giriş başarılıysa bilgileri SharedPreferences üzerine yazar ve ana sayfayı açar.
RegisterActivity: Yeni şef kaydı oluşturur. E-posta adresinin benzersiz olmasını şart koşar.
MainActivity (Tarif Akışı): Ana akışı yönetir. Arama motorunu, kategori emojilerini, resim yükleyicilerini ve alt navigasyonu barındırır.
MainActivity2 (Tarif Ekle): Seçmeli kategori (Spinner) ve resim URL'si desteğiyle yeni tarifi veritabanına kaydeder.
MainActivity4 (Tarif Detayı): Seçilen tarifin malzemelerini, yapılışını ve görselini şık bir biçimde sunar.
MainActivity5 (Favorilerim): Kullanıcının favorilediği tarifleri filtreler.
ProfileActivity: Kullanıcının toplam paylaştığı tarif istatistiğini gösterir. Tarif silme, hesap silme ve çıkış işlemlerini yürütür.
UsersActivity: Uygulamadaki tüm kayıtlı şefleri ve onların aktif tarif sayılarını listeler.
Veritabanı tablolarının oluşturulması, kullanıcı kaydı, girişi, tarif arama, favori işlemleri ve ilişkisel silme sorgularını barındırır.
DatabaseHelper.java
:
java
package com.example.myapplication;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class DatabaseHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "YemekRehberi.db";
private static final int DATABASE_VERSION = 2; // Veritabanı şema güncellemesi için sürüm 2
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
// Kullanıcılar tablosu oluşturuluyor
db.execSQL("CREATE TABLE users (" +
"id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"name TEXT, " +
"email TEXT UNIQUE, " +
"password TEXT)");
// Tarifler tablosu oluşturuluyor (İlişkisel alanlar eklenmiş halde)
db.execSQL("CREATE TABLE tarifler (" +
"id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"ad TEXT, " +
"malzeme TEXT, " +
"yapilis TEXT, " +
"kategori TEXT, " +
"resim_url TEXT, " +
"ekleyen_id INTEGER, " +
"ekleyen_adi TEXT, " +
"favori INTEGER DEFAULT 0)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS tarifler");
db.execSQL("DROP TABLE IF EXISTS users");
onCreate(db);
}
public boolean registerUser(String name, String email, String password) {
SQLiteDatabase db = this.getWritableDatabase();
// E-posta adresi kontrolü
Cursor cursor = db.rawQuery("SELECT id FROM users WHERE email = ?", new String[]{email});
if (cursor != null && cursor.getCount() > 0) {
cursor.close();
return false; // E-posta zaten kayıtlıysa false dön
}
if (cursor != null) cursor.close();
ContentValues values = new ContentValues();
values.put("name", name);
values.put("email", email);
values.put("password", password);
long result = db.insert("users", null, values);
return result != -1;
}
public boolean checkUser(String email, String password) {
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.rawQuery("SELECT id FROM users WHERE email = ? AND password = ?", new String[]{email, password});
boolean exists = (cursor != null && cursor.getCount() > 0);
if (cursor != null) cursor.close();
return exists;
}
public Cursor getUser(String email) {
SQLiteDatabase db = this.getReadableDatabase();
return db.rawQuery("SELECT id, name, email FROM users WHERE email = ?", new String[]{email});
}
public boolean deleteUser(int userId) {
SQLiteDatabase db = this.getWritableDatabase();
int affected = db.delete("users", "id = ?", new String[]{String.valueOf(userId)});
return affected > 0;
}
public Cursor getAllUsers() {
SQLiteDatabase db = this.getReadableDatabase();
// LEFT JOIN kullanarak her kullanıcının eklediği tarif sayısı hesaplanıyor
return db.rawQuery("SELECT u.id, u.name, u.email, COUNT(t.id) as tarif_sayisi " +
"FROM users u " +
"LEFT JOIN tarifler t ON u.id = t.ekleyen_id " +
"GROUP BY u.id", null);
}
public void kaydet(String ad, String mal, String yap, String kategori, String resimUrl, int ekleyenId, String ekleyenAdi) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues v = new ContentValues();
v.put("ad", ad);
v.put("malzeme", mal);
v.put("yapilis", yap);
v.put("kategori", kategori);
v.put("resim_url", resimUrl);
v.put("ekleyen_id", ekleyenId);
v.put("ekleyen_adi", ekleyenAdi);
v.put("favori", 0);
db.insert("tarifler", null, v);
}
public Cursor tumVeriler() {
SQLiteDatabase db = this.getReadableDatabase();
return db.rawQuery("SELECT * FROM tarifler ORDER BY id DESC", null); // En yeni tarif en üstte görünecek
}
public void deleteUserRecipes(int userId) {
SQLiteDatabase db = this.getWritableDatabase();
db.delete("tarifler", "ekleyen_id = ?", new String[]{String.valueOf(userId)});
}
public Cursor searchRecipes(String query) {
SQLiteDatabase db = this.getReadableDatabase();
String likeQuery = "%" + query + "%";
// Tarif adı, malzemeleri veya kategorisine göre anlık arama sorgusu
return db.rawQuery("SELECT * FROM tarifler WHERE ad LIKE ? OR malzeme LIKE ? OR kategori LIKE ? ORDER BY id DESC",
new String[]{likeQuery, likeQuery, likeQuery});
}
public Cursor favorileriGetir() {
SQLiteDatabase db = this.getReadableDatabase();
return db.rawQuery("SELECT * FROM tarifler WHERE favori = 1 ORDER BY id DESC", null);
}
public void favoriGuncelle(int id, int durum) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues v = new ContentValues();
v.put("favori", durum);
db.update("tarifler", v, "id=?", new String[]{String.valueOf(id)});
}
}
Uygulama açılışında oturum kontrolü yapar. Form doğrulamasının ardından oturum açıp verileri saklar.
LoginActivity.java
:
java
package com.example.myapplication;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class LoginActivity extends AppCompatActivity {
private EditText etEmail, etPassword;
private Button btnLogin;
private TextView tvGoToRegister;
private DatabaseHelper db;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Kullanıcı önceden giriş yaptıysa direkt ana sayfaya yönlendir
SharedPreferences pref = getSharedPreferences("UserSession", Context.MODE_PRIVATE);
if (pref.contains("userId")) {
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
startActivity(intent);
finish();
return;
}
setContentView(R.layout.activity_login);
db = new DatabaseHelper(this);
etEmail = findViewById(R.id.etEmail);
etPassword = findViewById(R.id.etPassword);
btnLogin = findViewById(R.id.btnLogin);
tvGoToRegister = findViewById(R.id.tvGoToRegister);
btnLogin.setOnClickListener(v -> {
String email = etEmail.getText().toString().trim();
String password = etPassword.getText().toString().trim();
if (email.isEmpty() || password.isEmpty()) {
Toast.makeText(this, "Lütfen tüm alanları doldurun!", Toast.LENGTH_SHORT).show();
return;
}
boolean isSuccess = db.checkUser(email, password);
if (isSuccess) {
Cursor cursor = db.getUser(email);
if (cursor != null && cursor.moveToFirst()) {
int userId = cursor.getInt(0);
String name = cursor.getString(1);
String userEmail = cursor.getString(2);
cursor.close();
// Oturum bilgilerini cihaz hafızasına yaz
SharedPreferences.Editor editor = pref.edit();
editor.putInt("userId", userId);
editor.putString("userName", name);
editor.putString("userEmail", userEmail);
editor.apply();
Toast.makeText(this, "Giriş başarılı! Hoş geldin " + name + " ????", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
startActivity(intent);
finish();
}
} else {
Toast.makeText(this, "E-posta veya şifre hatalı!", Toast.LENGTH_SHORT).show();
}
});
tvGoToRegister.setOnClickListener(v -> {
Intent intent = new Intent(LoginActivity.this, RegisterActivity.class);
startActivity(intent);
});
}
}
Kullanıcı kayıt işlemlerini üstlenir. Şifre uzunluğunu doğrular ve veritabanına kullanıcıyı işler.
RegisterActivity.java
:
java
package com.example.myapplication;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class RegisterActivity extends AppCompatActivity {
private EditText etName, etEmail, etPassword;
private Button btnRegister;
private TextView tvGoToLogin;
private DatabaseHelper db;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_register);
db = new DatabaseHelper(this);
etName = findViewById(R.id.etRegisterName);
etEmail = findViewById(R.id.etRegisterEmail);
etPassword = findViewById(R.id.etRegisterPassword);
btnRegister = findViewById(R.id.btnRegister);
tvGoToLogin = findViewById(R.id.tvGoToLogin);
btnRegister.setOnClickListener(v -> {
String name = etName.getText().toString().trim();
String email = etEmail.getText().toString().trim();
String password = etPassword.getText().toString().trim();
if (name.isEmpty() || email.isEmpty() || password.isEmpty()) {
Toast.makeText(this, "Lütfen tüm alanları doldurun!", Toast.LENGTH_SHORT).show();
return;
}
if (password.length() < 4) {
Toast.makeText(this, "Şifre en az 4 karakter olmalıdır!", Toast.LENGTH_SHORT).show();
return;
}
boolean success = db.registerUser(name, email, password);
if (success) {
Toast.makeText(this, "Kayıt başarılı! Giriş yapabilirsiniz. ????", Toast.LENGTH_SHORT).show();
finish(); // Login sayfasına geri dön
} else {
Toast.makeText(this, "Bu e-posta adresiyle zaten bir üyelik mevcut!", Toast.LENGTH_SHORT).show();
}
});
tvGoToLogin.setOnClickListener(v -> finish());
}
}
Oturumu kontrol eder, yemek listesini ve arama barını yönetir. İçerisinde resimleri asenkron yükleyen metot ile ListView için özel CustomAdapter barındırır.
MainActivity.java
:
java
package com.example.myapplication;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MainActivity extends AppCompatActivity {
private TextView tvWelcomeUser, btnQuickLogout;
private EditText etSearch;
private ListView listViewRecipes;
private LinearLayout llEmptyState;
private DatabaseHelper db;
// Alt navigasyon butonları
private View btnNavAdd, btnNavUsers, btnNavFavs, btnNavProfile;
// Yemek listesi verileri
private ArrayList<Integer> ids = new ArrayList<>();
private ArrayList<String> adlar = new ArrayList<>();
private ArrayList<String> mallar = new ArrayList<>();
private ArrayList<String> yaplar = new ArrayList<>();
private ArrayList<String> kategoriler = new ArrayList<>();
private ArrayList<String> resimler = new ArrayList<>();
private ArrayList<Integer> ekleyenIds = new ArrayList<>();
private ArrayList<String> ekleyenAdlar = new ArrayList<>();
private ArrayList<Integer> favoriler = new ArrayList<>();
private CustomAdapter adapter;
private ExecutorService executorService = Executors.newFixedThreadPool(4); // 4 kanallı havuz
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Oturum kontrolü
SharedPreferences pref = getSharedPreferences("UserSession", Context.MODE_PRIVATE);
if (!pref.contains("userId")) {
Intent intent = new Intent(MainActivity.this, LoginActivity.class);
startActivity(intent);
finish();
return;
}
setContentView(R.layout.activity_main);
db = new DatabaseHelper(this);
tvWelcomeUser = findViewById(R.id.tvWelcomeUser);
btnQuickLogout = findViewById(R.id.btnQuickLogout);
etSearch = findViewById(R.id.etSearch);
listViewRecipes = findViewById(R.id.listViewRecipes);
llEmptyState = findViewById(R.id.llEmptyState);
btnNavAdd = findViewById(R.id.btnNavAdd);
btnNavUsers = findViewById(R.id.btnNavUsers);
btnNavFavs = findViewById(R.id.btnNavFavs);
btnNavProfile = findViewById(R.id.btnNavProfile);
String userName = pref.getString("userName", "Şef");
tvWelcomeUser.setText("Merhaba " + userName + "! ????");
adapter = new CustomAdapter();
listViewRecipes.setAdapter(adapter);
verileriYukle("");
// Arama kutusu TextWatcher (Real-time Search)
etSearch.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
verileriYukle(s.toString().trim());
}
@Override
public void afterTextChanged(Editable s) {}
});
btnQuickLogout.setOnClickListener(v -> {
SharedPreferences.Editor editor = pref.edit();
editor.clear();
editor.apply();
Toast.makeText(this, "Çıkış yapıldı!", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(MainActivity.this, LoginActivity.class);
startActivity(intent);
finish();
});
// Alt Navigasyon Yönlendirmeleri
btnNavAdd.setOnClickListener(v -> startActivity(new Intent(this, MainActivity2.class)));
btnNavUsers.setOnClickListener(v -> startActivity(new Intent(this, UsersActivity.class)));
btnNavFavs.setOnClickListener(v -> startActivity(new Intent(this, MainActivity5.class)));
btnNavProfile.setOnClickListener(v -> startActivity(new Intent(this, ProfileActivity.class)));
}
@Override
protected void onResume() {
super.onResume();
if (etSearch != null) {
verileriYukle(etSearch.getText().toString().trim());
}
}
private void verileriYukle(String query) {
ids.clear(); adlar.clear(); mallar.clear(); yaplar.clear();
kategoriler.clear(); resimler.clear(); ekleyenIds.clear();
ekleyenAdlar.clear(); favoriler.clear();
Cursor c = (query == null || query.isEmpty()) ? db.tumVeriler() : db.searchRecipes(query);
if (c != null) {
while (c.moveToNext()) {
ids.add(c.getInt(0));
adlar.add(c.getString(1));
mallar.add(c.getString(2));
yaplar.add(c.getString(3));
kategoriler.add(c.getString(4));
resimler.add(c.getString(5));
ekleyenIds.add(c.getInt(6));
ekleyenAdlar.add(c.getString(7));
favoriler.add(c.getInt(8));
}
c.close();
}
// Boş Liste Kontrolü (Empty State)
if (adlar.isEmpty()) {
llEmptyState.setVisibility(View.VISIBLE);
listViewRecipes.setVisibility(View.GONE);
} else {
llEmptyState.setVisibility(View.GONE);
listViewRecipes.setVisibility(View.VISIBLE);
}
adapter.notifyDataSetChanged();
}
public static String getEmojiForCategory(String category) {
if (category == null) return "????️";
switch (category) {
case "Çorba": return "????";
case "Ana Yemek":
case "Kebap": return "????";
case "Pizzalar":
case "Fast Food": return "????";
case "Salata": return "????";
case "Tatlı": return "????";
case "Burger": return "????";
case "Makarna": return "????";
case "Kahvaltı": return "????";
default: return "????️";
}
}
// Uzaktaki resmi asenkron (arka planda) yükleyen metot
private void loadRemoteImage(String urlString, ImageView imageView) {
if (urlString == null || urlString.trim().isEmpty()) {
imageView.setVisibility(View.GONE);
return;
}
imageView.setTag(urlString);
executorService.execute(() -> {
try {
URL url = new URL(urlString);
InputStream is = url.openStream();
Bitmap bmp = BitmapFactory.decodeStream(is);
new Handler(Looper.getMainLooper()).post(() -> {
if (urlString.equals(imageView.getTag())) {
if (bmp != null) {
imageView.setImageBitmap(bmp);
imageView.setVisibility(View.VISIBLE);
} else {
imageView.setVisibility(View.GONE);
}
}
});
} catch (Exception e) {
new Handler(Looper.getMainLooper()).post(() -> {
if (urlString.equals(imageView.getTag())) {
imageView.setVisibility(View.GONE);
}
});
}
});
}
class CustomAdapter extends BaseAdapter {
@Override public int getCount() { return adlar.size(); }
@Override public Object getItem(int i) { return null; }
@Override public long getItemId(int i) { return 0; }
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = getLayoutInflater().inflate(R.layout.list_item_layout, null);
}
TextView txtAd = convertView.findViewById(R.id.txtYemekAdi);
TextView txtKategori = convertView.findViewById(R.id.txtYemekKategori);
TextView txtEkleyen = convertView.findViewById(R.id.txtYemekEkleyen);
TextView txtEmoji = convertView.findViewById(R.id.txtCategoryEmoji);
ImageView imgPoster = convertView.findViewById(R.id.imgRecipePoster);
TextView btnFav = convertView.findViewById(R.id.btnFavoriEkle);
txtAd.setText(adlar.get(position));
String category = kategoriler.get(position);
txtKategori.setText("Kategori: " + category);
String ekleyen = ekleyenAdlar.get(position);
txtEkleyen.setText("Şef: " + (ekleyen == null ? "Bilinmeyen Şef" : ekleyen));
txtEmoji.setText(getEmojiForCategory(category));
String imageUrl = resimler.get(position);
imgPoster.setVisibility(View.GONE);
if (imageUrl != null && !imageUrl.trim().isEmpty()) {
loadRemoteImage(imageUrl, imgPoster);
}
int isFav = favoriler.get(position);
btnFav.setText(isFav == 1 ? "❤️" : "????");
btnFav.setOnClickListener(v -> {
int newFavStatus = isFav == 1 ? 0 : 1;
db.favoriGuncelle(ids.get(position), newFavStatus);
Toast.makeText(MainActivity.this, adlar.get(position) +
(newFavStatus == 1 ? " favorilere eklendi! ❤️" : " favorilerden çıkarıldı! ????"), Toast.LENGTH_SHORT).show();
verileriYukle(etSearch.getText().toString().trim());
});
convertView.setOnClickListener(v -> {
Intent intent = new Intent(MainActivity.this, MainActivity4.class);
intent.putExtra("pId", ids.get(position));
intent.putExtra("pAd", adlar.get(position));
intent.putExtra("pMal", mallar.get(position));
intent.putExtra("pYap", yaplar.get(position));
intent.putExtra("pKategori", category);
intent.putExtra("pResimUrl", imageUrl);
intent.putExtra("pEkleyen", ekleyen);
startActivity(intent);
});
return convertView;
}
}
}
Spinner yardımıyla kategori seçimi yapılmasını ve görseller için URL girilmesini sağlayarak tarifi veritabanına kaydeder.
MainActivity2.java
:
java
package com.example.myapplication;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity2 extends AppCompatActivity {
private DatabaseHelper db;
private int loggedInUserId;
private String loggedInUserName;
private EditText etAd, etMal, etYap, etResimUrl;
private Spinner spnKategori;
private Button btnKaydet, btnGeri;
private final String[] kategoriler = {"Çorba", "Ana Yemek", "Pizzalar", "Salata", "Tatlı", "Burger", "Makarna", "Kahvaltı", "Diğer"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedPreferences pref = getSharedPreferences("UserSession", Context.MODE_PRIVATE);
if (!pref.contains("userId")) {
startActivity(new Intent(this, LoginActivity.class));
finish();
return;
}
loggedInUserId = pref.getInt("userId", -1);
loggedInUserName = pref.getString("userName", "Bilinmeyen Şef");
setContentView(R.layout.activity_main2);
db = new DatabaseHelper(this);
etAd = findViewById(R.id.etYemekAd);
etMal = findViewById(R.id.etMalzemeler);
etYap = findViewById(R.id.etYapilis);
etResimUrl = findViewById(R.id.etResimUrl);
spnKategori = findViewById(R.id.spnKategori);
btnKaydet = findViewById(R.id.btnKaydet);
btnGeri = findViewById(R.id.btnGeri);
// Spinner Veri Adaptörü
ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, kategoriler);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spnKategori.setAdapter(adapter);
btnKaydet.setOnClickListener(v -> {
String ad = etAd.getText().toString().trim();
String mal = etMal.getText().toString().trim();
String yap = etYap.getText().toString().trim();
String resimUrl = etResimUrl.getText().toString().trim();
String kategori = spnKategori.getSelectedItem().toString();
if (ad.isEmpty()) {
Toast.makeText(this, "Lütfen yemeğin adını yazın!", Toast.LENGTH_SHORT).show();
return;
}
if (mal.isEmpty()) {
Toast.makeText(this, "Lütfen malzemeleri girin!", Toast.LENGTH_SHORT).show();
return;
}
if (yap.isEmpty()) {
Toast.makeText(this, "Lütfen yapılışını yazın!", Toast.LENGTH_SHORT).show();
return;
}
db.kaydet(ad, mal, yap, kategori, resimUrl, loggedInUserId, loggedInUserName);
Toast.makeText(this, "Tarif başarıyla eklendi! ✨", Toast.LENGTH_SHORT).show();
finish();
});
btnGeri.setOnClickListener(v -> finish());
}
}
Önceki ekrandan gelen verileri toplayıp, internetten resmi yükleyerek kullanıcıya sunar.
MainActivity4.java
:
java
package com.example.myapplication;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import java.io.InputStream;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MainActivity4 extends AppCompatActivity {
private TextView txtBaslik, txtKategori, txtEkleyen, txtMalzemeler, txtYapilis, txtEmoji;
private ImageView imgPoster;
private TextView btnBackCircle;
private Button btnKapat;
private ExecutorService executorService = Executors.newSingleThreadExecutor();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main4);
txtBaslik = findViewById(R.id.txtDetayBaslik);
txtKategori = findViewById(R.id.txtDetayKategori);
txtEkleyen = findViewById(R.id.txtDetayEkleyen);
txtMalzemeler = findViewById(R.id.txtDetayMalzemeler);
txtYapilis = findViewById(R.id.txtDetayYapilis);
txtEmoji = findViewById(R.id.txtDetayEmoji);
imgPoster = findViewById(R.id.imgDetayPoster);
btnBackCircle = findViewById(R.id.btnDetayBackCircle);
btnKapat = findViewById(R.id.btnDetayKapat);
Intent intent = getIntent();
if (intent != null) {
String ad = intent.getStringExtra("pAd");
String mal = intent.getStringExtra("pMal");
String yap = intent.getStringExtra("pYap");
String kategori = intent.getStringExtra("pKategori");
String resimUrl = intent.getStringExtra("pResimUrl");
String ekleyen = intent.getStringExtra("pEkleyen");
txtBaslik.setText(ad);
txtKategori.setText(kategori == null ? "Kategori: Diğer" : "Kategori: " + kategori);
txtEkleyen.setText("Şef: " + (ekleyen == null ? "Bilinmeyen" : ekleyen));
txtMalzemeler.setText(mal);
txtYapilis.setText(yap);
txtEmoji.setText(MainActivity.getEmojiForCategory(kategori));
if (resimUrl != null && !resimUrl.trim().isEmpty()) {
loadRemoteImage(resimUrl);
}
}
btnBackCircle.setOnClickListener(v -> finish());
btnKapat.setOnClickListener(v -> finish());
}
private void loadRemoteImage(String urlString) {
executorService.execute(() -> {
try {
URL url = new URL(urlString);
InputStream is = url.openStream();
Bitmap bmp = BitmapFactory.decodeStream(is);
new Handler(Looper.getMainLooper()).post(() -> {
if (bmp != null) {
imgPoster.setImageBitmap(bmp);
imgPoster.setVisibility(View.VISIBLE);
}
});
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
Sadece favori verileri db.favorileriGetir() ile filtreleyerek sunar.
MainActivity5.java
:
java
package com.example.myapplication;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MainActivity5 extends AppCompatActivity {
private DatabaseHelper db;
private ListView listViewFavs;
private LinearLayout llFavEmptyState;
private Button btnBack;
private ArrayList<Integer> ids = new ArrayList<>();
private ArrayList<String> adlar = new ArrayList<>();
private ArrayList<String> mallar = new ArrayList<>();
private ArrayList<String> yaplar = new ArrayList<>();
private ArrayList<String> kategoriler = new ArrayList<>();
private ArrayList<String> resimler = new ArrayList<>();
private ArrayList<Integer> ekleyenIds = new ArrayList<>();
private ArrayList<String> ekleyenAdlar = new ArrayList<>();
private ArrayList<Integer> favoriler = new ArrayList<>();
private CustomFavAdapter adapter;
private ExecutorService executorService = Executors.newFixedThreadPool(4);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedPreferences pref = getSharedPreferences("UserSession", Context.MODE_PRIVATE);
if (!pref.contains("userId")) {
startActivity(new Intent(this, LoginActivity.class));
finish();
return;
}
setContentView(R.layout.activity_main5);
db = new DatabaseHelper(this);
listViewFavs = findViewById(R.id.liste_view_favori);
llFavEmptyState = findViewById(R.id.llFavEmptyState);
btnBack = findViewById(R.id.btnFavsBack);
adapter = new CustomFavAdapter();
listViewFavs.setAdapter(adapter);
verileriYukle();
btnBack.setOnClickListener(v -> finish());
}
@Override
protected void onResume() {
super.onResume();
verileriYukle();
}
private void verileriYukle() {
ids.clear(); adlar.clear(); mallar.clear(); yaplar.clear();
kategoriler.clear(); resimler.clear(); ekleyenIds.clear();
ekleyenAdlar.clear(); favoriler.clear();
Cursor c = db.favorileriGetir();
if (c != null) {
while (c.moveToNext()) {
ids.add(c.getInt(0));
adlar.add(c.getString(1));
mallar.add(c.getString(2));
yaplar.add(c.getString(3));
kategoriler.add(c.getString(4));
resimler.add(c.getString(5));
ekleyenIds.add(c.getInt(6));
ekleyenAdlar.add(c.getString(7));
favoriler.add(c.getInt(8));
}
c.close();
}
if (adlar.isEmpty()) {
llFavEmptyState.setVisibility(View.VISIBLE);
listViewFavs.setVisibility(View.GONE);
} else {
llFavEmptyState.setVisibility(View.GONE);
listViewFavs.setVisibility(View.VISIBLE);
}
adapter.notifyDataSetChanged();
}
private void loadRemoteImage(String urlString, ImageView imageView) {
if (urlString == null || urlString.trim().isEmpty()) {
imageView.setVisibility(View.GONE);
return;
}
imageView.setTag(urlString);
executorService.execute(() -> {
try {
URL url = new URL(urlString);
InputStream is = url.openStream();
Bitmap bmp = BitmapFactory.decodeStream(is);
new Handler(Looper.getMainLooper()).post(() -> {
if (urlString.equals(imageView.getTag())) {
if (bmp != null) {
imageView.setImageBitmap(bmp);
imageView.setVisibility(View.VISIBLE);
}
}
});
} catch (Exception ignored) {}
});
}
class CustomFavAdapter extends BaseAdapter {
@Override public int getCount() { return adlar.size(); }
@Override public Object getItem(int i) { return null; }
@Override public long getItemId(int i) { return 0; }
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = getLayoutInflater().inflate(R.layout.list_item_layout, null);
}
TextView txtAd = convertView.findViewById(R.id.txtYemekAdi);
TextView txtKategori = convertView.findViewById(R.id.txtYemekKategori);
TextView txtEkleyen = convertView.findViewById(R.id.txtYemekEkleyen);
TextView txtEmoji = convertView.findViewById(R.id.txtCategoryEmoji);
ImageView imgPoster = convertView.findViewById(R.id.imgRecipePoster);
TextView btnFav = convertView.findViewById(R.id.btnFavoriEkle);
txtAd.setText(adlar.get(position));
String category = kategoriler.get(position);
txtKategori.setText("Kategori: " + category);
String ekleyen = ekleyenAdlar.get(position);
txtEkleyen.setText("Şef: " + (ekleyen == null ? "Bilinmeyen Şef" : ekleyen));
txtEmoji.setText(MainActivity.getEmojiForCategory(category));
String imageUrl = resimler.get(position);
imgPoster.setVisibility(View.GONE);
if (imageUrl != null && !imageUrl.trim().isEmpty()) {
loadRemoteImage(imageUrl, imgPoster);
}
btnFav.setText("❤️");
btnFav.setOnClickListener(v -> {
db.favoriGuncelle(ids.get(position), 0); // Favoriden çıkar
Toast.makeText(MainActivity5.this, adlar.get(position) + " favorilerden çıkarıldı! ????", Toast.LENGTH_SHORT).show();
verileriYukle();
});
convertView.setOnClickListener(v -> {
Intent intent = new Intent(MainActivity5.this, MainActivity4.class);
intent.putExtra("pId", ids.get(position));
intent.putExtra("pAd", adlar.get(position));
intent.putExtra("pMal", mallar.get(position));
intent.putExtra("pYap", yaplar.get(position));
intent.putExtra("pKategori", category);
intent.putExtra("pResimUrl", imageUrl);
intent.putExtra("pEkleyen", ekleyen);
startActivity(intent);
});
return convertView;
}
}
}
Aktif oturumu bitirme, kullanıcının paylaştığı tarifleri toplu silme ya da hesabı tamamen kapatma işlemlerinde doğrulama AlertDialog'larını tetikler.
ProfileActivity.java
:
java
package com.example.myapplication;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
public class ProfileActivity extends AppCompatActivity {
private DatabaseHelper db;
private int userId;
private String userName, userEmail;
private TextView tvName, tvEmail, tvRecipeCount;
private Button btnDeleteRecipes, btnDeleteAccount, btnLogout, btnBack;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedPreferences pref = getSharedPreferences("UserSession", Context.MODE_PRIVATE);
if (!pref.contains("userId")) {
startActivity(new Intent(this, LoginActivity.class));
finish();
return;
}
userId = pref.getInt("userId", -1);
userName = pref.getString("userName", "Şef");
userEmail = pref.getString("userEmail", "");
setContentView(R.layout.activity_profile);
db = new DatabaseHelper(this);
tvName = findViewById(R.id.tvProfileName);
tvEmail = findViewById(R.id.tvProfileEmail);
tvRecipeCount = findViewById(R.id.tvProfileRecipeCount);
btnDeleteRecipes = findViewById(R.id.btnDeleteMyRecipes);
btnDeleteAccount = findViewById(R.id.btnDeleteAccount);
btnLogout = findViewById(R.id.btnProfileLogout);
btnBack = findViewById(R.id.btnProfileBack);
tvName.setText(userName);
tvEmail.setText(userEmail);
tarifSayisiniGuncelle();
// Sadece kendi eklediğim tarifleri sil
btnDeleteRecipes.setOnClickListener(v -> new AlertDialog.Builder(this)
.setTitle("Tarifleri Sil")
.setMessage("Eklediğiniz tüm yemek tariflerini silmek istediğinize emin misiniz? Bu işlem geri alınamaz!")
.setIcon(android.R.drawable.ic_dialog_alert)
.setPositiveButton("Evet, Sil", (dialog, which) -> {
db.deleteUserRecipes(userId);
Toast.makeText(ProfileActivity.this, "Eklediğiniz tüm tarifler silindi! ????", Toast.LENGTH_SHORT).show();
tarifSayisiniGuncelle();
})
.setNegativeButton("İptal", null)
.show());
// Hesabı ve eklenen tarifleri tamamen sil
btnDeleteAccount.setOnClickListener(v -> new AlertDialog.Builder(this)
.setTitle("Üyeliği Sil")
.setMessage("Üyeliğinizi ve tüm tariflerinizi kalıcı olarak silmek istediğinize emin misiniz?")
.setIcon(android.R.drawable.ic_dialog_alert)
.setPositiveButton("Evet, Hesabımı Sil", (dialog, which) -> {
db.deleteUserRecipes(userId);
db.deleteUser(userId);
SharedPreferences.Editor editor = pref.edit();
editor.clear();
editor.apply();
Toast.makeText(ProfileActivity.this, "Hesabınız silindi. ????", Toast.LENGTH_LONG).show();
Intent intent = new Intent(ProfileActivity.this, LoginActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
finish();
})
.setNegativeButton("İptal", null)
.show());
btnLogout.setOnClickListener(v -> {
SharedPreferences.Editor editor = pref.edit();
editor.clear();
editor.apply();
Toast.makeText(this, "Çıkış yapıldı!", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(ProfileActivity.this, LoginActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
finish();
});
btnBack.setOnClickListener(v -> finish());
}
private void tarifSayisiniGuncelle() {
int count = 0;
SQLiteDatabase sdb = db.getReadableDatabase();
Cursor c = sdb.rawQuery("SELECT COUNT(*) FROM tarifler WHERE ekleyen_id = ?", new String[]{String.valueOf(userId)});
if (c != null) {
if (c.moveToFirst()) count = c.getInt(0);
c.close();
}
tvRecipeCount.setText(String.valueOf(count));
}
}
Sistemdeki tüm kullanıcıları ve paylaştıkları toplam tarif miktarlarını listeler.
UsersActivity.java
:
java
package com.example.myapplication;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import java.util.ArrayList;
public class UsersActivity extends AppCompatActivity {
private DatabaseHelper db;
private ListView listViewUsers;
private Button btnBack;
private ArrayList<Integer> userIds = new ArrayList<>();
private ArrayList<String> userNames = new ArrayList<>();
private ArrayList<String> userEmails = new ArrayList<>();
private ArrayList<Integer> recipeCounts = new ArrayList<>();
private CustomUsersAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedPreferences pref = getSharedPreferences("UserSession", Context.MODE_PRIVATE);
if (!pref.contains("userId")) {
startActivity(new Intent(this, LoginActivity.class));
finish();
return;
}
setContentView(R.layout.activity_users);
db = new DatabaseHelper(this);
listViewUsers = findViewById(R.id.listViewUsers);
btnBack = findViewById(R.id.btnUsersBack);
adapter = new CustomUsersAdapter();
listViewUsers.setAdapter(adapter);
kullanicilariYukle();
btnBack.setOnClickListener(v -> finish());
}
private void kullanicilariYukle() {
userIds.clear(); userNames.clear(); userEmails.clear(); recipeCounts.clear();
Cursor c = db.getAllUsers();
if (c != null) {
while (c.moveToNext()) {
userIds.add(c.getInt(0));
userNames.add(c.getString(1));
userEmails.add(c.getString(2));
recipeCounts.add(c.getInt(3));
}
c.close();
}
adapter.notifyDataSetChanged();
}
class CustomUsersAdapter extends BaseAdapter {
@Override public int getCount() { return userNames.size(); }
@Override public Object getItem(int i) { return null; }
@Override public long getItemId(int i) { return 0; }
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = getLayoutInflater().inflate(R.layout.user_item_layout, null);
}
TextView txtName = convertView.findViewById(R.id.txtUserName);
TextView txtEmail = convertView.findViewById(R.id.txtUserEmail);
TextView txtCount = convertView.findViewById(R.id.txtUserRecipeCount);
txtName.setText(userNames.get(position));
txtEmail.setText(userEmails.get(position));
txtCount.setText(recipeCounts.get(position) + " Tarif");
return convertView;
}
}
}
Uygulamanın görsel kalitesini artıran ve kart yapısını yöneten iki kritik XML şeması aşağıda belirtilmiştir:
── activity_main.xml (Ana Akış Ekranı Tasarımı): Ekranın en üstünde hoş geldin mesajı, altında anlık arama yapmayı sağlayan EditText arama barı, onun altında tarifleri listeleyen bir ListView ve en altta ise CardView içerisine yerleştirilmiş 4 sekmeli modern navigasyon barı bulunur.
── list_item_layout.xml (Kart Görünümü Satır Tasarımı): Her yemeğin listelendiği kart tasarımını ifade eder. Sol kısımda yuvarlatılmış köşeli CardView içerisinde bir görsel (ImageView) yer alır. Eğer resim yoksa kategori emojisi gösterilir. Orta kısımda yemek adı, kategori etiketi ve ekleyen şefin bilgileri dikey olarak hizalanmıştır. Sağ köşede ise favoriye eklemeyi sağlayan kalp ikonu bulunur.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━ ÖZET — NE ÖĞRENDİK? ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Bu gelişmiş proje ile birlikte şunları öğrendik:
✔ Çoklu kullanıcı sistemlerinde SQLite üzerinde ilişkisel veri modeli kurmayı,
✔ Giriş ve Kayıt mimarileri kurarak oturum verilerini SharedPreferences ile kalıcı kılmayı,
✔ Büyük verilerin akışını ListView ve BaseAdapter kullanarak özelleştirilmiş kart tasarımlarıyla (CardView) sunmayı,
✔ ExecutorService ile arka planda veri/resim indirme yaparak akıcı bir arayüz (UI Thread) sunmayı,
✔ Arama motorlarında TextWatcher kullanarak kullanıcı deneyimini iyileştirmeyi.
Bu proje, gerçek dünyadaki büyük çaplı Android uygulamalarının (Sosyal medya akışları, e-ticaret listelemeleri, vb.) temel mantığını ve veritabanı kurgusunu tam anlamıyla yansıtmaktadır.
Rozerin Erdinç
atp 11 a
Toplam 3 Makale
Lütfen yorumlarınızda saygılı, yapıcı ve anlaşılır bir dil kullanın.
Küfür, hakaret ya da spam içerikler onaylanmaz.