
كيفية برمجة تطبيق لتحويل النص إلى كلام على اندرويد ستوديو Kotlin | Java
في عالم اليوم الرقمي السريع، أصبحت تطبيقات تحويل النص إلى كلام
أدوات لا غنى عنها للعديد من المستخدمين، سواء لأغراض تسهيل الوصول،
أو زيادة الإنتاجية، أو حتى الاستمتاع بتجربة استماع مريحة للمحتوى المكتوب.
يهدف هذا المقال إلى تزويدك بخطوات مفصلة وأكواد عملية لإنشاء تطبيق
أندرويد احترافي يقوم بتحويل النص إلى كلام، مع التركيز على الجودة وسهولة الاستخدام.
خطوات برمجة تطبيق لتحويل النص إلى كلام على اندرويد ستوديو Kotlin | Java
1. إنشاء مشروع جديد في Android Studio :
من خلال واجهة Android Studio. ابدأ بفتح Android Studio واختر
"Start a new Android Studio project". حدد "Empty Activity"
واختر اسم التطبيق واللغة (Java أو Kotlin) وموقع الحفظ.
2. تصميم واجهة المستخدم (UI) :
الكلمات المفتاحية: واجهة مستخدم أندرويد, تصميم UI, XML, EditText, Button.
الكود (ملف activity_main.xml) :
XML
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="16dp" tools:context=".MainActivity">
<EditText android:id="@+id/editText" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:gravity="top|start" android:hint="@string/enter_text" android:inputType="textMultiLine" />
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center">
<Button android:id="@+id/speakButton" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="@string/speak" android:layout_marginEnd="8dp"/>
<Button android:id="@+id/saveButton" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="@string/save" android:layout_marginStart="8dp" android:layout_marginEnd="8dp"/>
<Button android:id="@+id/shareButton" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="@string/share" android:layout_marginStart="8dp"/>
</LinearLayout>
</LinearLayout>
--
* الكلمات المفتاحية للموارد (ملف strings.xml) :
XML
<resources>
<string name="app_name">TextToSpeechApp</string>
<string name="enter_text">أدخل النص هنا...</string>
<string name="speak">تحدث</string>
<string name="save">حفظ</string>
<string name="share">مشاركة</string>
<string name="text_saved">تم حفظ النص بنجاح</string>
<string name="error_saving">حدث خطأ أثناء الحفظ</string>
</resources>
--
3. برمجة وظيفة تحويل النص إلى كلام (TTS):
قي (ملف MainActivity.java أو MainActivity.kt):
** لغة Java :
Java
package com.example.texttospeechapp;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;import android.speech.tts.TextToSpeech;import android.view.View;import android.widget.Button;import android.widget.EditText;import android.widget.Toast;
import java.util.Locale;
public class MainActivity extends AppCompatActivity implements TextToSpeech.OnInitListener {
private TextToSpeech mTTS; private EditText mEditText; private Button mSpeakButton;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
mEditText = findViewById(R.id.editText); mSpeakButton = findViewById(R.id.speakButton); Button saveButton = findViewById(R.id.saveButton); Button shareButton = findViewById(R.id.shareButton);
mTTS = new TextToSpeech(this, this);
mSpeakButton.setOnClickListener(v -> { String text = mEditText.getText().toString(); if (text != null && !text.isEmpty()) { mTTS.speak(text, TextToSpeech.QUEUE_FLUSH, null, null); } });
// سيتم إضافة أكواد حفظ ومشاركة النص لاحقاً }
@Override public void onInit(int status) { if (status == TextToSpeech.SUCCESS) { int result = mTTS.setLanguage(Locale.getDefault());
if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) { Toast.makeText(this, "اللغة غير مدعومة", Toast.LENGTH_SHORT).show(); } else { mSpeakButton.setEnabled(true); } } else { Toast.makeText(this, "فشل تهيئة محرك تحويل النص إلى كلام", Toast.LENGTH_SHORT).show(); } }
@Override public void onDestroy() { if (mTTS != null) { mTTS.stop(); mTTS.shutdown(); } super.onDestroy(); }}
--
** لغة Kotlin :
Kotlin
package com.example.texttospeechapp
import android.os.Bundleimport android.speech.tts.TextToSpeechimport android.view.Viewimport android.widget.Buttonimport android.widget.EditTextimport android.widget.Toastimport androidx.appcompat.app.AppCompatActivityimport java.util.Locale
class MainActivity : AppCompatActivity(), TextToSpeech.OnInitListener {
private var mTTS: TextToSpeech? = null private lateinit var mEditText: EditText private lateinit var mSpeakButton: Button
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main)
mEditText = findViewById(R.id.editText) mSpeakButton = findViewById(R.id.speakButton) val saveButton: Button = findViewById(R.id.saveButton) val shareButton: Button = findViewById(R.id.shareButton)
mTTS = TextToSpeech(this, this)
mSpeakButton.setOnClickListener { val text = mEditText.text.toString() if (!text.isNullOrEmpty()) { mTTS?.speak(text, TextToSpeech.QUEUE_FLUSH, null, null) } }
// سيتم إضافة أكواد حفظ ومشاركة النص لاحقاً }
override fun onInit(status: Int) { if (status == TextToSpeech.SUCCESS) { val result = mTTS?.setLanguage(Locale.getDefault())
if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) { Toast.makeText(this, "اللغة غير مدعومة", Toast.LENGTH_SHORT).show() } else { mSpeakButton.isEnabled = true } } else { Toast.makeText(this, "فشل تهيئة محرك تحويل النص إلى كلام", Toast.LENGTH_SHORT).show() } }
override fun onDestroy() { if (mTTS != null) { mTTS?.stop() mTTS?.shutdown() } super.onDestroy() }}
--
4. برمجة وظيفة حفظ النص :
الكود (إضافة داخل onCreate في MainActivity.java أو MainActivity.kt):
** لغة Java :
Java
saveButton.setOnClickListener(v -> {
String textToSave = mEditText.getText().toString();
if (!textToSave.isEmpty()) {
try {
java.io.FileOutputStream fos = openFileOutput("saved_text.txt", MODE_PRIVATE);
fos.write(textToSave.getBytes());
fos.close();
Toast.makeText(this, R.string.text_saved, Toast.LENGTH_SHORT).show();
} catch (java.io.IOException e) {
e.printStackTrace();
Toast.makeText(this, R.string.error_saving, Toast.LENGTH_SHORT).show();
}
}
});
--
** لغة Kotlin :
Kotlin
saveButton.setOnClickListener {
val textToSave = mEditText.text.toString()
if (!textToSave.isEmpty()) {
try {
val fos = openFileOutput("saved_text.txt", MODE_PRIVATE)
fos.write(textToSave.toByteArray())
fos.close()
Toast.makeText(this, R.string.text_saved, Toast.LENGTH_SHORT).show()
} catch (e: java.io.IOException) {
e.printStackTrace()
Toast.makeText(this, R.string.error_saving, Toast.LENGTH_SHORT).show()
}
}
}
--
5. برمجة وظيفة مشاركة النص :
(إضافة داخل onCreate في MainActivity.java أو MainActivity.kt) :
** لغة Java :
Java
shareButton.setOnClickListener(v -> {
String textToShare = mEditText.getText().toString();
if (!textToShare.isEmpty()) {
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_TEXT, textToShare);
startActivity(Intent.createChooser(shareIntent, "مشاركة النص عبر..."));
}
});
--
** لغة Kotlin :
Kotlin
shareButton.setOnClickListener {
val textToShare = mEditText.text.toString()
if (!textToShare.isEmpty()) {
val shareIntent = Intent(Intent.ACTION_SEND)
shareIntent.type = "text/plain"
shareIntent.putExtra(Intent.EXTRA_TEXT, textToShare)
startActivity(Intent.createChooser(shareIntent, "مشاركة النص عبر..."))
}
}
--
6. جعل التطبيق احترافياً - إضافات وميزات متقدمة :
1. خيارات صوت متقدمة: تغيير سرعة ونبرة الصوت
(إضافة دالة لعرض خيارات الصوت في MainActivity.java أو MainActivity.kt):
** لغة Java :
Java
private void showSpeechSettings() { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("إعدادات الصوت");
View view = getLayoutInflater().inflate(R.layout.dialog_speech_settings, null); SeekBar speedSeekBar = view.findViewById(R.id.speedSeekBar); SeekBar pitchSeekBar = view.findViewById(R.id.pitchSeekBar); TextView speedValue = view.findViewById(R.id.speedValue); TextView pitchValue = view.findViewById(R.id.pitchValue);
speedSeekBar.setProgress(50); // قيمة افتراضية للسرعة (0.0 - 1.0) pitchSeekBar.setProgress(50); // قيمة افتراضية للنبرة (0.0 - 2.0)
speedSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { float speed = (float) progress / 50.0f; mTTS.setSpeechRate(speed); speedValue.setText(String.format("%.2f", speed)); }
@Override public void onStartTrackingTouch(SeekBar seekBar) { // لا يلزم فعل شيء هنا }
@Override public void onStopTrackingTouch(SeekBar seekBar) { // لا يلزم فعل شيء هنا } });
pitchSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { float pitch = (float) progress / 50.0f; mTTS.setPitch(pitch); pitchValue.setText(String.format("%.2f", pitch)); }
@Override public void onStartTrackingTouch(SeekBar seekBar) { // لا يلزم فعل شيء هنا }
@Override public void onStopTrackingTouch(SeekBar seekBar) { // لا يلزم فعل شيء هنا } });
builder.setView(view) .setPositiveButton("موافق", (dialog, which) -> { // تم تطبيق الإعدادات عند الضغط على موافق }) .setNegativeButton("إلغاء", (dialog, which) -> { dialog.cancel(); }) .show(); }
--
** لغة Kotlin :
Kotlin
private fun showSpeechSettings() { val builder = AlertDialog.Builder(this) builder.setTitle("إعدادات الصوت")
val view = layoutInflater.inflate(R.layout.dialog_speech_settings, null) val speedSeekBar = view.findViewById<SeekBar>(R.id.speedSeekBar) val pitchSeekBar = view.findViewById<SeekBar>(R.id.pitchSeekBar) val speedValue = view.findViewById<TextView>(R.id.speedValue) val pitchValue = view.findViewById<TextView>(R.id.pitchValue)
speedSeekBar.progress = 50 // قيمة افتراضية للسرعة (0.0 - 1.0) pitchSeekBar.progress = 50 // قيمة افتراضية للنبرة (0.0 - 2.0)
speedSeekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { val speed = progress / 50.0f mTTS?.setSpeechRate(speed) speedValue.text = String.format("%.2f", speed) }
override fun onStartTrackingTouch(seekBar: SeekBar?) { // لا يلزم فعل شيء هنا }
override fun onStopTrackingTouch(seekBar: SeekBar?) { // لا يلزم فعل شيء هنا } })
pitchSeekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { val pitch = progress / 50.0f mTTS?.setPitch(pitch) pitchValue.text = String.format("%.2f", pitch) }
override fun onStartTrackingTouch(seekBar: SeekBar?) { // لا يلزم فعل شيء هنا }
override fun onStopTrackingTouch(seekBar: SeekBar?) { // لا يلزم فعل شيء هنا } })
builder.setView(view) .setPositiveButton("موافق") { dialog, which -> // تم تطبيق الإعدادات عند الضغط على موافق } .setNegativeButton("إلغاء") { dialog, which -> dialog.cancel() } .show() }
--
* (ملف dialog_speech_settings.xml) :
XML
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="16dp">
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="السرعة:" android:textAppearance="?android:attr/textAppearanceMedium" />
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_vertical">
<SeekBar android:id="@+id/speedSeekBar" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:max="100" />
<TextView android:id="@+id/speedValue" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:text="1.00" />
</LinearLayout>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="النبرة:" android:textAppearance="?android:attr/textAppearanceMedium" />
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_vertical">
<SeekBar android:id="@+id/pitchSeekBar" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:max="200" />
<TextView android:id="@+id/pitchValue" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:text="1.00" />
</LinearLayout>
</LinearLayout>
--
* لاستدعاء هذه الدالة عند الضغط على زر في واجهة المستخدم :
** لغة Java :
Java
Button speechSettingsButton = findViewById(R.id.speechSettingsButton); // افترض وجود زر بهذا المعرف
speechSettingsButton.setOnClickListener(v -> showSpeechSettings());
--
** لغة Kotlin :
Kotlin
val speechSettingsButton: Button = findViewById(R.id.speechSettingsButton) // افترض وجود زر بهذا المعرف
speechSettingsButton.setOnClickListener { showSpeechSettings() }
--
2. خيارات صوت متقدمة: عرض قائمة باللغات المتاحة
الكود (إضافة دالة لعرض قائمة اللغات في MainActivity.java أو MainActivity.kt):
** لغة Java :
Java
private void showLanguageSelection() { Set<Locale> availableLanguages = mTTS.getAvailableLanguages(); List<String> languageNames = new ArrayList<>(); List<Locale> languageLocales = new ArrayList<>();
for (Locale locale : availableLanguages) { languageNames.add(locale.getDisplayLanguage(Locale.getDefault())); languageLocales.add(locale); }
ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, languageNames);
AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("اختر اللغة"); builder.setAdapter(adapter, (dialog, which) -> { Locale selectedLocale = languageLocales.get(which); int result = mTTS.setLanguage(selectedLocale); if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) { Toast.makeText(this, "هذه اللغة غير مدعومة", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "تم تغيير اللغة إلى " + selectedLocale.getDisplayLanguage(Locale.getDefault()), Toast.LENGTH_SHORT).show(); } }); builder.show(); }
--
** لغة Kotlin :
Kotlin
private fun showLanguageSelection() { val availableLanguages = mTTS?.availableLanguages val languageNames = mutableListOf<String>() val languageLocales = mutableListOf<Locale>()
availableLanguages?.forEach { locale -> languageNames.add(locale.getDisplayLanguage(Locale.getDefault())) languageLocales.add(locale) }
val adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, languageNames)
val builder = AlertDialog.Builder(this) builder.setTitle("اختر اللغة") builder.setAdapter(adapter) { dialog, which -> val selectedLocale = languageLocales[which] val result = mTTS?.setLanguage(selectedLocale) if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) { Toast.makeText(this, "هذه اللغة غير مدعومة", Toast.LENGTH_SHORT).show() } else { Toast.makeText(this, "تم تغيير اللغة إلى ${selectedLocale.getDisplayLanguage(Locale.getDefault())}", Toast.LENGTH_SHORT).show() } } builder.show() }
--
* لاستدعاء هذه الدالة عند الضغط على زر في واجهة المستخدم :
* لغة Java :
Java
Button languageButton = findViewById(R.id.languageButton); // افترض وجود زر بهذا المعرف
languageButton.setOnClickListener(v -> showLanguageSelection());
** لغة Kotlin :
Kotlin
val languageButton: Button = findViewById(R.id.languageButton) // افترض وجود زر بهذا المعرف
languageButton.setOnClickListener { showLanguageSelection() }
--
3. إدارة النصوص المحفوظة: إنشاء شاشة لعرض قائمة بالنصوص المحفوظة
(باستخدام RecyclerView و Room)
أ. إنشاء كيان (Entity) لتمثيل النص المحفوظ (SavedText.java أو SavedText.kt):
** لغة Java :
import androidx.room.Entity;import androidx.room.PrimaryKey;
@Entity(tableName = "saved_texts")public class SavedText { @PrimaryKey(autoGenerate = true) private int id;
private String text; private long timestamp;
public SavedText(String text, long timestamp) { this.text = text; this.timestamp = timestamp; }
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getText() { return text; }
public long getTimestamp() { return timestamp; }}
--
** لغة Kotlin :
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "saved_texts")
data class SavedText(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
val text: String,
val timestamp: Long
)
--
ب. إنشاء واجهة الوصول إلى البيانات (DAO)
(SavedTextDao.java أو SavedTextDao.kt) :
** لغة Java :
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import java.util.List;
@Dao
public interface SavedTextDao {
@Query("SELECT * FROM saved_texts ORDER BY timestamp DESC")
List<SavedText> getAll();
@Insert
void insert(SavedText savedText);
@Delete
void delete(SavedText savedText);
}
--
** لغة Kotlin :
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
@Dao
interface SavedTextDao {
@Query("SELECT * FROM saved_texts ORDER BY timestamp DESC")
fun getAll(): List<SavedText>
@Insert
fun insert(savedText: SavedText)
@Delete
fun delete(savedText: SavedText)
}
--
ج. إنشاء قاعدة بيانات Room (AppDatabase.java أو AppDatabase.kt):
** لغة Java
import androidx.room.Database;import androidx.room.RoomDatabase;
@Database(entities = {SavedText.class}, version = 1)public abstract class AppDatabase extends RoomDatabase { public abstract SavedTextDao savedTextDao();
private static volatile AppDatabase INSTANCE;
public static AppDatabase getDatabase(final Context context) { if (INSTANCE == null) { synchronized (AppDatabase.class) { if (INSTANCE == null) { INSTANCE = androidx.room.Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "saved_texts_database") .build(); } } } return INSTANCE; }}
--
** لغة Kotlin :
import android.content.Contextimport androidx.room.Databaseimport androidx.room.Roomimport androidx.room.RoomDatabase
@Database(entities = [SavedText::class], version = 1)abstract class AppDatabase : RoomDatabase() { abstract fun savedTextDao(): SavedTextDao
companion object { @Volatile private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase { return INSTANCE ?: synchronized(this) { val instance = Room.databaseBuilder( context.applicationContext, AppDatabase::class.java, "saved_texts_database" ).build() INSTANCE = instance instance } } }}
--
د. إنشاء محول (RecyclerView.Adapter) لعرض النصوص المحفوظة
(SavedTextAdapter.java أو SavedTextAdapter.kt) :
** لغة Java :
import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.TextView;
import androidx.annotation.NonNull;import androidx.recyclerview.widget.RecyclerView;
import java.text.DateFormat;import java.util.List;import java.util.Locale;
public class SavedTextAdapter extends RecyclerView.Adapter<SavedTextAdapter.SavedTextViewHolder> {
private List<SavedText> savedTextList; private OnItemClickListener listener;
public SavedTextAdapter(List<SavedText> savedTextList, OnItemClickListener listener) { this.savedTextList = savedTextList; this.listener = listener; }
@NonNull @Override public SavedTextViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View itemView = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_saved_text, parent, false); return new SavedTextViewHolder(itemView); }
@Override public void onBindViewHolder(@NonNull SavedTextViewHolder holder, int position) { SavedText currentText = savedTextList.get(position); holder.textViewText.setText(currentText.getText()); String formattedDate = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.SHORT, Locale.getDefault()).format(currentText.getTimestamp()); holder.textViewTimestamp.setText(formattedDate);
holder.itemView.setOnClickListener(v -> { listener.onItemClick(currentText); }); }
@Override public int getItemCount() { return savedTextList.size(); }
public static class SavedTextViewHolder extends RecyclerView.ViewHolder { public TextView textViewText; public TextView textViewTimestamp;
public SavedTextViewHolder(@NonNull View itemView) { super(itemView); textViewText = itemView.findViewById(R.id.textViewSavedText); textViewTimestamp = itemView.findViewById(R.id.textViewTimestamp); } }
public interface OnItemClickListener { void onItemClick(SavedText savedText); }
public void setSavedTexts(List<SavedText> savedTexts) { this.savedTextList = savedTexts; notifyDataSetChanged(); }}
--
** لغة Kotlin :
import android.view.LayoutInflaterimport android.view.Viewimport android.view.ViewGroupimport android.widget.TextViewimport androidx.recyclerview.widget.RecyclerViewimport java.text.DateFormatimport java.util.Dateimport java.util.Locale
class SavedTextAdapter( private var savedTextList: List<SavedText>, private val listener: OnItemClickListener) : RecyclerView.Adapter<SavedTextAdapter.SavedTextViewHolder>() {
inner class SavedTextViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val textViewText: TextView = itemView.findViewById(R.id.textViewSavedText) val textViewTimestamp: TextView = itemView.findViewById(R.id.textViewTimestamp)
init { itemView.setOnClickListener { val position = adapterPosition if (position != RecyclerView.NO_POSITION) { listener.onItemClick(savedTextList[position]) } } } }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SavedTextViewHolder { val itemView = LayoutInflater.from(parent.context) .inflate(R.layout.item_saved_text, parent, false) return SavedTextViewHolder(itemView) }
override fun onBindViewHolder(holder: SavedTextViewHolder, position: Int) { val currentText = savedTextList[position] holder.textViewText.text = currentText.text val formattedDate = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.SHORT, Locale.getDefault()).format(Date(currentText.timestamp)) holder.textViewTimestamp.text = formattedDate }
override fun getItemCount(): Int { return savedTextList.size }
fun setSavedTexts(savedTexts: List<SavedText>) { this.savedTextList = savedTexts notifyDataSetChanged() }
interface OnItemClickListener { fun onItemClick(savedText: SavedText) }}
--
* (ملف item_saved_text.xml لتصميم شكل كل عنصر في القائمة) :
XML
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="16dp">
<TextView android:id="@+id/textViewSavedText" android:layout_width="match_parent" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:textStyle="bold" />
<TextView android:id="@+id/textViewTimestamp" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout>
--
هـ. إنشاء شاشة لعرض النصوص المحفوظة
(SavedTextsActivity.java أو SavedTextsActivity.kt) :
** لغة Java
import androidx.appcompat.app.AppCompatActivity;import androidx.recyclerview.widget.LinearLayoutManager;import androidx.recyclerview.widget.RecyclerView;
import android.os.Bundle;import android.os.Handler;import android.os.Looper;import android.widget.Toast;
import java.util.List;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;
public class SavedTextsActivity extends AppCompatActivity implements SavedTextAdapter.OnItemClickListener {
private RecyclerView recyclerView; private SavedTextAdapter adapter; private AppDatabase db;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_saved_texts);
recyclerView = findViewById(R.id.recyclerViewSavedTexts); recyclerView.setLayoutManager(new LinearLayoutManager(this));
db = AppDatabase.getDatabase(getApplicationContext());
ExecutorService executor = Executors.newSingleThreadExecutor(); Handler handler = new Handler(Looper.getMainLooper());
executor.execute(() -> { List<SavedText> savedTexts = db.savedTextDao().getAll(); handler.post(() -> { adapter = new SavedTextAdapter(savedTexts, SavedTextsActivity.this); recyclerView.setAdapter(adapter); }); }); }
@Override public void onItemClick(SavedText savedText) { // يمكنك هنا إضافة منطق لفتح النص المحدد أو حذفه Toast.makeText(this, "تم النقر على: " + savedText.getText(), Toast.LENGTH_SHORT).show(); // مثال للحذف عند النقر: ExecutorService executor = Executors.newSingleThreadExecutor(); Handler handler = new Handler(Looper.getMainLooper()); executor.execute(() -> { db.savedTextDao().delete(savedText); List<SavedText> updatedList = db.savedTextDao().getAll(); handler.post(() -> { adapter.setSavedTexts(updatedList); }); }); }}
--
** لغة Kotlin
import android.os.Bundleimport android.os.Handlerimport android.os.Looperimport android.widget.Toastimport androidx.appcompat.app.AppCompatActivityimport androidx.recyclerview.widget.LinearLayoutManagerimport androidx.recyclerview.widget.RecyclerViewimport java.util.concurrent.Executors
class SavedTextsActivity : AppCompatActivity(), SavedTextAdapter.OnItemClickListener {
private lateinit var recyclerView: RecyclerView private lateinit var adapter: SavedTextAdapter private lateinit var db: AppDatabase
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_saved_texts)
recyclerView = findViewById(R.id.recyclerViewSavedTexts) recyclerView.layoutManager = LinearLayoutManager(this)
db = AppDatabase.getDatabase(applicationContext)
val executor = Executors.newSingleThreadExecutor() val handler = Handler(Looper.getMainLooper())
executor.execute { val savedTexts = db.savedTextDao().getAll() handler.post { adapter = SavedTextAdapter(savedTexts, this@SavedTextsActivity) recyclerView.adapter = adapter } } }
override fun onItemClick(savedText: SavedText) { // يمكنك هنا إضافة منطق لفتح النص المحدد أو حذفه Toast.makeText(this, "تم النقر على: ${savedText.text}", Toast.LENGTH_SHORT).show() // مثال للحذف عند النقر: val executor = Executors.newSingleThreadExecutor() val handler = Handler(Looper.getMainLooper()) executor.execute { db.savedTextDao().delete(savedText) val updatedList = db.savedTextDao().getAll() handler.post { adapter.setSavedTexts(updatedList) } } }}
--
* (ملف activity_saved_texts.xml) :
XML
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/recyclerViewSavedTexts"
android:layout_width="match_parent"
android:layout_height="match_parent" />
--
و. لتحديث كود الحفظ في MainActivity لإضافة الطابع الزمني :
** لغة Java:
Java
saveButton.setOnClickListener(v -> {
String textToSave = mEditText.getText().toString();
if (!textToSave.isEmpty()) {
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(() -> {
SavedText savedText = new SavedText(textToSave, System.currentTimeMillis());
db.savedTextDao().insert(savedText);
runOnUiThread(() -> Toast.makeText(MainActivity.this, R.string.text_saved, Toast.LENGTH_SHORT).show());
});
}
});
--
* لغة Kotlin :
Kotlin
saveButton.setOnClickListener {
val textToSave = mEditText.text.toString()
if (!textToSave.isEmpty()) {
val executor = Executors.newSingleThreadExecutor()
executor.execute {
val savedText = SavedText(text = textToSave, timestamp = System.currentTimeMillis())
db.savedTextDao().insert(savedText)
runOnUiThread { Toast.makeText(this@MainActivity, R.string.text_saved, Toast.LENGTH_SHORT).show() }
}
}
}
--
ز. إضافة زر في واجهة MainActivity للانتقال إلى شاشة النصوص المحفوظة:
XML
<Button
android:id="@+id/viewSavedButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="المحفوظات"
android:layout_marginStart="8dp"/>
--
ح. إضافة وظيفة الانتقال إلى شاشة النصوص المحفوظة في MainActivity:
** Java :
Java
Button viewSavedButton = findViewById(R.id.viewSavedButton);
viewSavedButton.setOnClickListener(v -> {
Intent intent = new Intent(MainActivity.this, SavedTextsActivity.class);
startActivity(intent);
});
--
** لغة Kotlin :
Kotlin
val viewSavedButton: Button = findViewById(R.id.viewSavedButton)
viewSavedButton.setOnClickListener {
val intent = Intent(this, SavedTextsActivity::class.java)
startActivity(intent)
}
--
4. دعم اللغات المتعددة
الخطوات :
- في مجلد res/values/، لديك بالفعل ملف strings.xml للغة الافتراضية (عادة الإنجليزية).
- لإضافة دعم للغة العربية، انقر بزر الفأرة الأيمن على مجلد values واختر New > Values Resource File.
- في نافذة "New Resource File"، أدخل strings في اسم الملف واختر Locale من قائمة "Qualifier".
- اختر ar (Arabic) وانقر OK. سيتم إنشاء مجلد جديد باسم values-ar يحتوي على ملف strings.xml.
- كرر هذه العملية لإضافة لغات أخرى (مثل values-en للغة الإنجليزية إذا لم يكن موجوداً).
- في كل ملف strings.xml، قم بتوفير ترجمة للقيم النصية المستخدمة في تطبيقك.
** مثال (res/values/strings.xml - الإنجليزية):
XML
<resources>
<string name="app_name">TextToSpeechApp</string>
<string name="enter_text">Enter text here...</string>
<string name="speak">Speak</string>
<string name="save">Save</string>
<string name="share">Share</string>
<string name="text_saved">Text saved successfully</string>
<string name="error_saving">Error saving text</string>
<string name="speech_settings">Speech Settings</string>
<string name="language">Language</string>
<string name="saved_texts">Saved Texts</string>
</resources>
--
* مثال (res/values-ar/strings.xml - العربية) :
XML
<resources>
<string name="app_name">تطبيق تحويل النص إلى كلام</string>
<string name="enter_text">أدخل النص هنا...</string>
<string name="speak">تحدث</string>
<string name="save">حفظ</string>
<string name="share">مشاركة</string>
<string name="text_saved">تم حفظ النص بنجاح</string>
<string name="error_saving">حدث خطأ أثناء الحفظ</string>
<string name="speech_settings">إعدادات الصوت</string>
<string name="language">اللغة</string>
<string name="saved_texts">النصوص المحفوظة</string>
</resources>
--
السماح للمستخدم باختيار لغة الإخراج الصوتي (تم تغطيته في الجزء الخاص بخيارات الصوت المتقدمة).
5. إعدادات التطبيق
* الخطوات :
إنشاء ملف XML للإعدادات (preferences.xml في مجلد res/xml/) :
XML
<?xml version="1.0" encoding="utf-8"?><PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory android:title="إعدادات الصوت">
<ListPreference android:key="pref_language" android:title="لغة الإخراج" android:entries="@array/languages_array" android:entryValues="@array/languages_values" android:defaultValue="default" android:summary="اختر اللغة التي سيتم استخدامها لتحويل النص إلى كلام" />
<EditTextPreference android:key="pref_speech_rate" android:title="سرعة الكلام الافتراضية" android:defaultValue="1.0" android:inputType="numberDecimal" android:summary="اضبط سرعة الكلام الافتراضية" />
</PreferenceCategory>
</PreferenceScreen>
--
* إنشاء مصفوفات للغات في res/values/arrays.xml :
XML
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="languages_array">
<item>افتراضي</item>
<item>الإنجليزية</item>
<item>العربية</item>
</string-array>
<string-array name="languages_values">
<item>default</item>
<item>en</item>
<item>ar</item>
</string-array>
</resources>
--
* إنشاء Fragment لعرض الإعدادات
(SettingsFragment.java أو SettingsFragment.kt) :
Java
import android.os.Bundle;
import androidx.preference.PreferenceFragmentCompat;
public class SettingsFragment extends PreferenceFragmentCompat {
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.preferences, rootKey);
}
}
--
import android.os.Bundle
import androidx.preference.PreferenceFragmentCompat
class SettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences, rootKey)
}
}
--
* إنشاء Activity لاستضافة Fragment الإعدادات
(SettingsActivity.java أو SettingsActivity.kt) :
** لغة Java
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.FragmentManager;
import android.os.Bundle;
public class SettingsActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(android.R.id.content, new SettingsFragment())
.commit();
}
}
--
* لغة Kotlin
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.FragmentManager
class SettingsActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings)
val fragmentManager: FragmentManager = supportFragmentManager
fragmentManager.beginTransaction()
.replace(android.R.id.content, SettingsFragment())
.commit()
}
}
--
* إنشاء تخطيط بسيط لـ SettingsActivity (activity_settings.xml) :
XML
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" />
* ربط الإعدادات بـ MainActivity :
لجعل تطبيقك يستخدم الإعدادات التي يختارها المستخدم، ستحتاج إلى قراءة
قيم SharedPreferences في MainActivity وتطبيقها على
محرك تحويل النص إلى كلام (TextToSpeech).
* (إضافة في MainActivity.java أو MainActivity.kt) :
** لغة Java :
Java
import android.content.Intent;import android.content.SharedPreferences;import android.preference.PreferenceManager;import android.speech.tts.TextToSpeech;import android.widget.Button;
// ... باقي الأكواد ...
public class MainActivity extends AppCompatActivity implements TextToSpeech.OnInitListener {
private TextToSpeech mTTS; private EditText mEditText; private Button mSpeakButton;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
mEditText = findViewById(R.id.editText); mSpeakButton = findViewById(R.id.speakButton); Button saveButton = findViewById(R.id.saveButton); Button shareButton = findViewById(R.id.shareButton); Button settingsButton = findViewById(R.id.settingsButton); // افترض وجود زر للإعدادات
mTTS = new TextToSpeech(this, this);
settingsButton.setOnClickListener(v -> { Intent intent = new Intent(MainActivity.this, SettingsActivity.class); startActivity(intent); });
mSpeakButton.setOnClickListener(v -> { String text = mEditText.getText().toString(); if (text != null && !text.isEmpty()) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); String languageCode = prefs.getString("pref_language", Locale.getDefault().getLanguage()); float speechRate = Float.parseFloat(prefs.getString("pref_speech_rate", "1.0"));
Locale localeToSpeak; if (languageCode.equals("en")) { localeToSpeak = Locale.ENGLISH; } else if (languageCode.equals("ar")) { localeToSpeak = new Locale("ar"); } else { localeToSpeak = Locale.getDefault(); }
int result = mTTS.setLanguage(localeToSpeak); if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) { Toast.makeText(this, "اللغة المحددة غير مدعومة", Toast.LENGTH_SHORT).show(); } else { mTTS.setSpeechRate(speechRate); mTTS.speak(text, TextToSpeech.QUEUE_FLUSH, null, null); } } });
// ... باقي أكواد حفظ ومشاركة النص ... }
@Override public void onInit(int status) { if (status == TextToSpeech.SUCCESS) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); String languageCode = prefs.getString("pref_language", Locale.getDefault().getLanguage());
Locale localeOnInit; if (languageCode.equals("en")) { localeOnInit = Locale.ENGLISH; } else if (languageCode.equals("ar")) { localeOnInit = new Locale("ar"); } else { localeOnInit = Locale.getDefault(); }
int result = mTTS.setLanguage(localeOnInit);
if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) { Toast.makeText(this, "اللغة الافتراضية غير مدعومة", Toast.LENGTH_SHORT).show(); } else { mSpeakButton.setEnabled(true); } } else { Toast.makeText(this, "فشل تهيئة محرك تحويل النص إلى كلام", Toast.LENGTH_SHORT).show(); } }
// ... باقي أكواد onDestroy ...}
--
** لغة Kotlin :
Kotlin
import android.content.Intentimport android.content.SharedPreferencesimport android.os.Bundleimport android.preference.PreferenceManagerimport android.speech.tts.TextToSpeechimport android.widget.Buttonimport android.widget.EditTextimport android.widget.Toastimport androidx.appcompat.app.AppCompatActivityimport java.util.Locale
class MainActivity : AppCompatActivity(), TextToSpeech.OnInitListener {
private var mTTS: TextToSpeech? = null private lateinit var mEditText: EditText private lateinit var mSpeakButton: Button
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main)
mEditText = findViewById(R.id.editText) mSpeakButton = findViewById(R.id.speakButton) val saveButton: Button = findViewById(R.id.saveButton) val shareButton: Button = findViewById(R.id.shareButton) val settingsButton: Button = findViewById(R.id.settingsButton) // افترض وجود زر للإعدادات
mTTS = TextToSpeech(this, this)
settingsButton.setOnClickListener { val intent = Intent(this, SettingsActivity::class.java) startActivity(intent) }
mSpeakButton.setOnClickListener { val text = mEditText.text.toString() if (!text.isNullOrEmpty()) { val prefs = PreferenceManager.getDefaultSharedPreferences(this) val languageCode = prefs.getString("pref_language", Locale.getDefault().language) val speechRate = prefs.getString("pref_speech_rate", "1.0")?.toFloat() ?: 1.0f
val localeToSpeak = when (languageCode) { "en" -> Locale.ENGLISH "ar" -> Locale("ar") else -> Locale.getDefault() }
val result = mTTS?.setLanguage(localeToSpeak) if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) { Toast.makeText(this, "اللغة المحددة غير مدعومة", Toast.LENGTH_SHORT).show() } else { mTTS?.speechRate = speechRate mTTS?.speak(text, TextToSpeech.QUEUE_FLUSH, null, null) } } }
// ... باقي أكواد حفظ ومشاركة النص ... }
override fun onInit(status: Int) { if (status == TextToSpeech.SUCCESS) { val prefs = PreferenceManager.getDefaultSharedPreferences(this) val languageCode = prefs.getString("pref_language", Locale.getDefault().language)
val localeOnInit = when (languageCode) { "en" -> Locale.ENGLISH "ar" -> Locale("ar") else -> Locale.getDefault() }
val result = mTTS?.setLanguage(localeOnInit)
if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) { Toast.makeText(this, "اللغة الافتراضية غير مدعومة", Toast.LENGTH_SHORT).show() } else { mSpeakButton.isEnabled = true } } else { Toast.makeText(this, "فشل تهيئة محرك تحويل النص إلى كلام", Toast.LENGTH_SHORT).show() } }
// ... باقي أكواد onDestroy ...}
--
* تأكد من إضافة زر في تخطيط activity_main.xml للانتقال إلى شاشة الإعدادات :
<Button
android:id="@+id/settingsButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="الإعدادات"
android:layout_marginTop="16dp"/>
--
(يمكنك تعديل مكان هذا الزر وتصميمه حسب تخطيطك).
الآن، تم استكمال الجزء الخاص بإعدادات التطبيق مع الأكواد اللازمة لإنشاء شاشة
الإعدادات وربطها بوظيفة تحويل النص إلى كلام في MainActivity.
7. نشر التطبيق :
* الخطوات :
- إنشاء حساب مطور على Google Play Console : يتطلب ذلك دفع رسوم تسجيل لمرة واحدة.
- إعداد قائمة التطبيق : قم بتوفير اسم التطبيق، ووصفاً مفصلاً،
وفئة التطبيق، ومعلومات الاتصال، وسياسة الخصوصية.
- تحميل ملف APK أو AAB : قم بإنشاء ملف الإصدار من مشروعك في
Android Studio (Build > Generate Signed Bundle / APK).
يُفضل استخدام Android App Bundle (AAB) لتوفير حجم تنزيل أصغر للمستخدمين.
- توفير الأصول الرسومية : قم بتحميل أيقونة التطبيق، ولقطات شاشة تعرض وظائف التطبيق، وصور مميزة.
- تحديد التسعير والتوزيع : حدد ما إذا كان التطبيق مجانياً أو مدفوعاً، واختر
البلدان التي ترغب في توفير التطبيق فيها.
- مراجعة ونشر : بعد ملء جميع المعلومات وتحميل الملفات المطلوبة، قم بمراجعة القائمة
ثم انقر على "Go live" أو "نشر". قد تستغرق عملية المراجعة بضعة أيام.
* خاتمة :
باتباع هذه الخطوات وإضافة التحسينات المقترحة، يمكنك إنشاء تطبيق احترافي
لتحويل النص إلى كلام يلبي احتياجات المستخدمين ويوفر تجربة استخدام ممتازة.
تذكر أن التطوير المستمر والاستماع إلى ملاحظات المستخدمين
يلعبان دوراً حاسماً في نجاح تطبيقك على المدى الطويل.