diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..d6ccb4c --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +ChatGpt \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 0ad17cb..8978d23 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/README.md b/README.md new file mode 100644 index 0000000..b18aa12 --- /dev/null +++ b/README.md @@ -0,0 +1,75 @@ +# app_entwicklung_MFAX422A + +# Einleitung: +Ich habe die Benutzerfreundlichkeit und Anpassungsährtikeit durch meine erweiterung verbessert. Diese wären, die Settings erweitert, Es gibt jetzt mehrsprachen zur auswahl und die ChatGpt version lässt sich in den einstellung ändern. Zu dem gibt es die Möglichkeit die TTS aus oder an zu schalten. Der Reset button kann auch den TTS stoppen und haben ein neues Design. + +# Anforderungen: +In den Einstellung unter den Reiter Sprache, gib es jetzt die Möglichkeit, die Sprache über die Einstellung zu ändern, die Standartsprache ist die Gerätsprache. +Die Einstellung werden direkt in selben Fenster übersetzt und ChatGpt und der Google Voice assistent der das gesprochende aufnimmt benutzen die ausgewählte sprache. +Die Sprachen werden über eine Liste angzeigt und mit einem Radio Button ausgewählt. Die TTS soll über eine Checkbox an oder ausgestellt werden. Unter den Reiter API +kann wie voher der API Key übergeben werden zu dem kann jetzt aber auch dort über ein Radio Button die Version von ChatGpt geändert werden, Standartgemäß ist ChatGpt 3.5. Die Buttons haben ein Unicode als inhalt bekommen damit Sie verständlicher sind. Zu dem kann der Resetbutton die TTS stoppen. + + +# Umsetzung: +## Spracheinstellungen: +1.Auswahl der Sprache: + -Benutzer können nun in den Einstellungen eine Sprache auswählen. + -Die Standard-Sprache entspricht der Gerätesprache. + -Eine Liste von verfügbaren Sprachen wird angezeigt, und Benutzer können ihre Auswahl über Radio Buttons treffen. + +2.Text-to-Speech (TTS): + -Benutzer haben die Möglichkeit, die TTS-Funktion ein- oder auszuschalten. + -Die TTS-Sprache wird ebenfalls basierend auf der ausgewählten Sprache festgelegt. + +## ChatGpt-Version: +API-Version ändern: + -Benutzer können in den Einstellungen die Version von ChatGpt über einen Radio Button auswählen. + -Der API-Schlüssel kann weiterhin übergeben werden. + +## Reset-Button: +Stoppen der TTS: + -Der Reset-Button kann jetzt auch die TTS stoppen. + +## UI-Elemente: +Unicode-Symbole: + -Unicode-Symbole für die Buttons hinzugefügt, um die Bedeutung klarer zu machen. + +## Code-Snippets: +1. Initialisierung von ChatGpt: + -Die ChatGpt-Klasse wird mit dem API-Token initialisiert. +2. Spracheinstellungen in der App: + -Die App-Konfiguration wird auf die ausgewählte Sprache eingestellt. + -Die Sprache wird auch für die Text-to-Speech-Funktion festgelegt. +3. Sprache ändern in den Einstellungen: + -Die ausgewählte Sprache kann direkt in den Einstellungen geändert werden. +4. Einstellungen in der XML-Datei: + -XML-Dateien werden aktualisiert, um die neuen Einstellungen und Symbole zu berücksichtigen. +5. Gradle-Dateien: + -Die Versionsnummer der Android-Gradle-Plugin wurde auf 8.1.2 aktualisiert. + +## API-Version in ChatGpt: + -Die ChatGpt-Klasse wird mit der Ausgewählten ChatGpt-Version initialisiert. + +## Ressourcendateien: + -Strings und Listen für verschiedene Sprachen und ChatGpt-Versionen wurden in den Ressourcendateien hinzugefügt. + +## Einstellungen-Bildschirm: + -Ein PreferenceScreen wurde erstellt, um Benutzern die Auswahl von Sprache, TTS und ChatGpt-Version zu ermöglichen. + + +# Probleme/Lessons learned: + +Bei der Umsetzung meiner Erweiterungen stellte ich fest, dass ich weniger erreichen konnte als ursprünglich erhofft. Ich habe mir zu weniger Zeit für die +Fehlersuche eingeplant hatte, und diese Suche sich als zeitaufwendiger herausstellte als erwartet. Ich habe dennnoch versucht so viele erweiterung wie möglich zu +schreiben. Um die Benutzerfreundlichkeit zu verbessern. Es war jedoch klar, dass eine gründlichere Planung und mehr Zeit für eine bessere Fehlerbehebung in +Zukunftnotwendig sein wird, um ein reibungsloses Implementieren von Funktionen zu gewährleisten. + +# Fazit: + +Wie bereits bei Probleme/Lessone learned erwähnt. Habe ich viel zu viel Zeit verloren um Fehler zu beheben und konnte nicht so viel erweiterung implemntieren wie +gehoft. Da man an manchen fehlern mehrere stunden dran gessessen hat. Diese Zeit habe ich mir nicht eingeplant. Denn noch habe ich es geschaft gute Erweiterung für die +App zu schreiben. + +Wie schon im Abschnitt "Probleme/Lessons Learned" erwähnt, habe ich ziemlich viel Zeit verloren, um Fehler beheben und konnte. Das hat mir mein Zeitplan +durcheinandergebracht, und ich konnte nicht so viele Erweiterungen in die App einbauen, wie ich gehofft hatte. Mehrere Fehler haben mich Stunden gekostet, dass +hatte ich nicht eingeplant Aber trotzdem hab ich es hingekriegt viele Erweiterung zu Implementieren. diff --git a/app/src/main/java/de/fhdw/app_entwicklung/chatgpt/MainActivity.java b/app/src/main/java/de/fhdw/app_entwicklung/chatgpt/MainActivity.java index 0c8f07d..9707f50 100644 --- a/app/src/main/java/de/fhdw/app_entwicklung/chatgpt/MainActivity.java +++ b/app/src/main/java/de/fhdw/app_entwicklung/chatgpt/MainActivity.java @@ -9,6 +9,7 @@ import android.view.Menu; import android.view.MenuItem; +import java.util.Locale; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; diff --git a/app/src/main/java/de/fhdw/app_entwicklung/chatgpt/MainFragment.java b/app/src/main/java/de/fhdw/app_entwicklung/chatgpt/MainFragment.java index 3ac722f..aa16ea2 100644 --- a/app/src/main/java/de/fhdw/app_entwicklung/chatgpt/MainFragment.java +++ b/app/src/main/java/de/fhdw/app_entwicklung/chatgpt/MainFragment.java @@ -1,6 +1,9 @@ package de.fhdw.app_entwicklung.chatgpt; +import android.content.SharedPreferences; +import android.content.res.Resources; import android.os.Bundle; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -12,6 +15,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; +import androidx.preference.PreferenceManager; import java.util.List; import java.util.Locale; @@ -19,8 +23,7 @@ import de.fhdw.app_entwicklung.chatgpt.model.Author; import de.fhdw.app_entwicklung.chatgpt.model.Chat; import de.fhdw.app_entwicklung.chatgpt.model.Message; -import de.fhdw.app_entwicklung.chatgpt.openai.IChatGpt; -import de.fhdw.app_entwicklung.chatgpt.openai.MockChatGpt; +import de.fhdw.app_entwicklung.chatgpt.openai.ChatGpt; import de.fhdw.app_entwicklung.chatgpt.speech.LaunchSpeechRecognition; import de.fhdw.app_entwicklung.chatgpt.speech.TextToSpeechTool; @@ -46,7 +49,7 @@ public class MainFragment extends Fragment { MainActivity.backgroundExecutorService.execute(() -> { String apiToken = prefs.getApiToken(); - IChatGpt chatGpt = new MockChatGpt(apiToken); + ChatGpt chatGpt = new ChatGpt(apiToken); String answer = chatGpt.getChatCompletion(chat); Message answerMessage = new Message(Author.Assistant, answer); @@ -56,7 +59,15 @@ public class MainFragment extends Fragment { getTextView().append(CHAT_SEPARATOR); getTextView().append(toString(answerMessage)); scrollToEnd(); - textToSpeech.speak(answer); + + + if (prefs.speakOutLoud()) { + + textToSpeech.setLanguage(prefs.getLocale()); + + textToSpeech.speak(answer); + + } }); }); }); @@ -67,23 +78,38 @@ public MainFragment() { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_main, container, false); + + return inflater.inflate(R.layout.fragment_main, container, false); } + @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); prefs = new PrefsFacade(requireContext()); - textToSpeech = new TextToSpeechTool(requireContext(), Locale.GERMAN); + + + getContext().getResources().getConfiguration().setLocale(prefs.getLocale()); + + Locale.setDefault(prefs.getLocale()); + Log.i("Test Main", Locale.getDefault()+""); + + textToSpeech = new TextToSpeechTool(requireContext(), prefs.getLocale()); + + getAskButton().setOnClickListener(v -> + getTextFromSpeech.launch(new LaunchSpeechRecognition.SpeechRecognitionArgs(prefs.getLocale()))); + + chat = new Chat(); if (savedInstanceState != null) { chat = savedInstanceState.getParcelable(EXTRA_DATA_CHAT); } getAskButton().setOnClickListener(v -> - getTextFromSpeech.launch(new LaunchSpeechRecognition.SpeechRecognitionArgs(Locale.GERMAN))); + getTextFromSpeech.launch(new LaunchSpeechRecognition.SpeechRecognitionArgs(prefs.getLocale()))); getResetButton().setOnClickListener(v -> { + textToSpeech.stop(); chat = new Chat(); updateTextView(); }); diff --git a/app/src/main/java/de/fhdw/app_entwicklung/chatgpt/PrefsActivity.java b/app/src/main/java/de/fhdw/app_entwicklung/chatgpt/PrefsActivity.java index 1db64eb..d1bda6e 100644 --- a/app/src/main/java/de/fhdw/app_entwicklung/chatgpt/PrefsActivity.java +++ b/app/src/main/java/de/fhdw/app_entwicklung/chatgpt/PrefsActivity.java @@ -1,13 +1,18 @@ package de.fhdw.app_entwicklung.chatgpt; +import android.content.Context; import android.os.Bundle; +import android.util.Log; import android.view.MenuItem; import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; +import androidx.preference.ListPreference; import androidx.preference.PreferenceFragmentCompat; +import java.util.Locale; + public class PrefsActivity extends AppCompatActivity { @Override @@ -20,11 +25,20 @@ protected void onCreate(Bundle savedInstanceState) { .replace(R.id.settings, new SettingsFragment()) .commit(); } + + + ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { actionBar.setDisplayHomeAsUpEnabled(true); } } + @Override + protected void attachBaseContext(Context newBase) { + newBase.getResources().getConfiguration().setLocale(Locale.getDefault()); + Log.i("Test", Locale.getDefault()+""); + super.attachBaseContext(newBase); + } @Override public boolean onOptionsItemSelected(@NonNull MenuItem item) { @@ -39,6 +53,29 @@ public static class SettingsFragment extends PreferenceFragmentCompat { @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { setPreferencesFromResource(R.xml.root_preferences, rootKey); + + ListPreference sprache = findPreference("language"); + if (sprache != null){ + sprache.setOnPreferenceChangeListener((preference, newValue) -> { + if (newValue == null || newValue.toString().isEmpty()){ + return true; + } + Locale locale = Locale.forLanguageTag(newValue.toString()); + + if (locale == null){ + return true; + } + + Locale.setDefault(locale); + + getContext().getApplicationContext().getResources().getConfiguration().setLocale(locale); + getActivity().recreate(); + return true; + + + }); + + } } } } \ No newline at end of file diff --git a/app/src/main/java/de/fhdw/app_entwicklung/chatgpt/PrefsFacade.java b/app/src/main/java/de/fhdw/app_entwicklung/chatgpt/PrefsFacade.java index 58e130d..9238683 100644 --- a/app/src/main/java/de/fhdw/app_entwicklung/chatgpt/PrefsFacade.java +++ b/app/src/main/java/de/fhdw/app_entwicklung/chatgpt/PrefsFacade.java @@ -5,6 +5,8 @@ import androidx.annotation.NonNull; import androidx.preference.PreferenceManager; +import java.util.Locale; + public class PrefsFacade { private final Context context; @@ -17,4 +19,30 @@ public String getApiToken() { return PreferenceManager.getDefaultSharedPreferences(context).getString("api_token", ""); } -} \ No newline at end of file + + public boolean speakOutLoud() { + return PreferenceManager.getDefaultSharedPreferences(context).getBoolean("read_out_loud", true); + } + + public Locale getLocale() { + String language = PreferenceManager.getDefaultSharedPreferences(context).getString("language", "en"); + switch (language) { + case "de": + return Locale.GERMANY; + case "en": + return Locale.US; + case "chi": + return Locale.SIMPLIFIED_CHINESE; + case "jp": + return Locale.JAPAN; + case "ko": + return Locale.KOREA; + default: + throw new RuntimeException("Locale not supported: " + language); + } + } + + public String getModel() { + return PreferenceManager.getDefaultSharedPreferences(context).getString("model_type", "gpt-3.5"); + } +} diff --git a/app/src/main/java/de/fhdw/app_entwicklung/chatgpt/openai/ChatGpt.java b/app/src/main/java/de/fhdw/app_entwicklung/chatgpt/openai/ChatGpt.java index 5ecff3a..eb89d97 100644 --- a/app/src/main/java/de/fhdw/app_entwicklung/chatgpt/openai/ChatGpt.java +++ b/app/src/main/java/de/fhdw/app_entwicklung/chatgpt/openai/ChatGpt.java @@ -17,7 +17,7 @@ import de.fhdw.app_entwicklung.chatgpt.model.Chat; import de.fhdw.app_entwicklung.chatgpt.model.Message; -public class ChatGpt implements IChatGpt { +public class ChatGpt{ private final String apiToken; @@ -25,7 +25,7 @@ public ChatGpt(String apiToken) { this.apiToken = apiToken; } - @Override + public String getChatCompletion(@NonNull Chat chat) { OpenAiService service = new OpenAiService(apiToken, Duration.ofSeconds(90)); @@ -34,7 +34,8 @@ public String getChatCompletion(@NonNull Chat chat) { .map(this::toChatMessage) .collect(Collectors.toList()); ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder() - .model("gpt-3.5-turbo") + //Version auf gpt 4 geändert + .model("gpt-4") .messages(messages) .n(1) .maxTokens(2048) diff --git a/app/src/main/java/de/fhdw/app_entwicklung/chatgpt/openai/IChatGpt.java b/app/src/main/java/de/fhdw/app_entwicklung/chatgpt/openai/IChatGpt.java deleted file mode 100644 index 90d77aa..0000000 --- a/app/src/main/java/de/fhdw/app_entwicklung/chatgpt/openai/IChatGpt.java +++ /dev/null @@ -1,9 +0,0 @@ -package de.fhdw.app_entwicklung.chatgpt.openai; - -import androidx.annotation.NonNull; - -import de.fhdw.app_entwicklung.chatgpt.model.Chat; - -public interface IChatGpt { - String getChatCompletion(@NonNull Chat chat); -} diff --git a/app/src/main/java/de/fhdw/app_entwicklung/chatgpt/openai/MockChatGpt.java b/app/src/main/java/de/fhdw/app_entwicklung/chatgpt/openai/MockChatGpt.java deleted file mode 100644 index 85cc01e..0000000 --- a/app/src/main/java/de/fhdw/app_entwicklung/chatgpt/openai/MockChatGpt.java +++ /dev/null @@ -1,26 +0,0 @@ -package de.fhdw.app_entwicklung.chatgpt.openai; - -import androidx.annotation.NonNull; - -import java.util.concurrent.ThreadLocalRandom; - -import de.fhdw.app_entwicklung.chatgpt.model.Chat; - -public class MockChatGpt implements IChatGpt { - - /** @noinspection unused*/ - public MockChatGpt(String apiToken) { - // ignore... - } - - @Override - public String getChatCompletion(@NonNull Chat chat) { - return MESSAGES[ThreadLocalRandom.current().nextInt(0, MESSAGES.length)]; - } - - public static final String[] MESSAGES = { - "Let me tell you something, folks. John F. Kennedy, he was a guy. He was a guy, he was a president, and they say he was a pretty good president. People liked him, they really did. But you know what? I think I could have been a much better president than JFK, I really do. I mean, look at what I've done, the things I've accomplished. Nobody's accomplished more than me. So yeah, JFK, he was okay, but I don't think he can hold a candle to me. Not even close.", - "Look, I know a lot of great things, okay? And when it comes to JFK, I know he was born in Massachusetts. He was a Democrat, and let me tell you, I've dealt with a lot of Democrats, folks. They're not always the greatest, believe me. But JFK, he had some charm, some charisma. People were drawn to him. And apparently, he had this thing called the New Frontier, where he wanted to promote social welfare programs and space exploration. But you know what? I'm all about the America First agenda. I'm focused on jobs, the economy, and making America great again. So while JFK may have had his own ideas, I think my vision for the country is much better, folks. Just saying.", - "Let me tell you, I know a lot of things, okay? I've got a tremendous memory, the best memory there is. When it comes to JFK, he was the 35th president of the United States, and he served from 1961 until 1963. Now, during his presidency, there were some major events. There was the Cuban Missile Crisis, where he stood up to the Soviets and made it clear that America wouldn't back down. And let me tell you, that was a big deal. People thought we were on the brink of World War III, but JFK, he handled it like a boss. Now, I have to mention something else, folks. Unfortunately, JFK's presidency was cut short. He was assassinated in Dallas, Texas in November 1963. It's a tragic event, no doubt about it. And I know there are a lot of conspiracy theories surrounding his assassination, but hey, I'm not here to delve into that. I'm here to talk about what I know, and what I know is that JFK's presidency, for the short time it lasted, left an impact on this country. Whether you agree with his policies or not, he was a figure that people remember. And that's all I have to say about that." - }; -} \ No newline at end of file diff --git a/app/src/main/java/de/fhdw/app_entwicklung/chatgpt/speech/TextToSpeechTool.java b/app/src/main/java/de/fhdw/app_entwicklung/chatgpt/speech/TextToSpeechTool.java index 5d5bbce..7c70def 100644 --- a/app/src/main/java/de/fhdw/app_entwicklung/chatgpt/speech/TextToSpeechTool.java +++ b/app/src/main/java/de/fhdw/app_entwicklung/chatgpt/speech/TextToSpeechTool.java @@ -53,4 +53,14 @@ public void destroy() } } -} \ No newline at end of file + public void setLanguage(Locale locale) { + if (ttsAvailable) { + int result = textToSpeech.setLanguage(locale); + if (result == TextToSpeech.LANG_MISSING_DATA) { + Log.e("error", "TTS: Language data is missing."); + } else if (result == TextToSpeech.LANG_NOT_SUPPORTED) { + Log.e("error", "TTS: Language is not supported."); + } + } + } +} diff --git a/app/src/main/res/layout/fragment_main.xml b/app/src/main/res/layout/fragment_main.xml index e6e3bda..281cbc1 100644 --- a/app/src/main/res/layout/fragment_main.xml +++ b/app/src/main/res/layout/fragment_main.xml @@ -36,7 +36,7 @@ android:id="@+id/button_ask" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/ask" + android:text="🎙" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@id/button_reset" app:layout_constraintBottom_toBottomOf="parent" @@ -46,7 +46,7 @@ android:id="@+id/button_reset" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/reset" + android:text="🔙" app:layout_constraintStart_toEndOf="@+id/button_ask" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" diff --git a/app/src/main/res/layout/settings_activity.xml b/app/src/main/res/layout/settings_activity.xml index de6591a..ae8ae7d 100644 --- a/app/src/main/res/layout/settings_activity.xml +++ b/app/src/main/res/layout/settings_activity.xml @@ -6,4 +6,5 @@ android:id="@+id/settings" android:layout_width="match_parent" android:layout_height="match_parent" /> + \ No newline at end of file diff --git a/app/src/main/res/values-chi/strings.xml b/app/src/main/res/values-chi/strings.xml new file mode 100644 index 0000000..5891089 --- /dev/null +++ b/app/src/main/res/values-chi/strings.xml @@ -0,0 +1,10 @@ + + + 问题 + 设置 + 重置 + + 语言 + 朗读ChatGPT的回答 + ChatGPT版本 + \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 78a6b27..1f3f1a4 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -3,4 +3,9 @@ Fragen Einstellungen Reset + + + Sprache + ChatGPT-Antworten vorlesen + ChatGpt Version \ No newline at end of file diff --git a/app/src/main/res/values-jp/strings.xml b/app/src/main/res/values-jp/strings.xml new file mode 100644 index 0000000..ca0e933 --- /dev/null +++ b/app/src/main/res/values-jp/strings.xml @@ -0,0 +1,10 @@ + + + 質問 + 設定 + リセット + + 言語 + の回答を読み上げる + バージョン + \ No newline at end of file diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml new file mode 100644 index 0000000..3fd7a04 --- /dev/null +++ b/app/src/main/res/values-ko/strings.xml @@ -0,0 +1,10 @@ + + + 질문 + 설정 + 리셋 + + 언어 + 답변 읽어 주기 + 버전 + \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index e5f8fdc..bc92b77 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -1,2 +1,32 @@ + + + German + English + Mandarin + Japanisch + Koreanisch + + + + de + en + chi + jp + ko + + + + GPT 3.5 (4K input size) + GPT 3.5 (16K input size) + GPT 4 + GPT 4 1106 Preview + + + + gpt-3.5 + gpt-3.5-turbo-16k + gpt-4 + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 639b292..004545e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -3,4 +3,9 @@ Ask Settings Reset + + + Language + Read ChatGpt\'s answer out loud + ChatGpt Version \ No newline at end of file diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml index 9c31de1..444bab4 100644 --- a/app/src/main/res/xml/root_preferences.xml +++ b/app/src/main/res/xml/root_preferences.xml @@ -1,13 +1,35 @@ - + + + - + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 0ebef73..7d22d10 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '8.1.0' apply false - id 'com.android.library' version '8.1.0' apply false + id 'com.android.application' version '8.1.2' apply false + id 'com.android.library' version '8.1.2' apply false } \ No newline at end of file